Skip to content

Commit 94ca07e

Browse files
Web Inspector: add contextmenu item to arbitrarily add HTML/Child to DOMTree
https://bugs.webkit.org/show_bug.cgi?id=179042 Reviewed by Joseph Pecoraro. * Localizations/en.lproj/localizedStrings.js: * UserInterface/Models/DOMNode.js: (WI.DOMNode.prototype.insertAdjacentHTML.inspectedPage_node_insertAdjacentHTML): (WI.DOMNode.prototype.insertAdjacentHTML): Call-through to `insertAdjacentHTML` on the corresponding node in the inspected page. * UserInterface/Views/DOMTreeElement.js: (WI.DOMTreeElement): (WI.DOMTreeElement.prototype._populateNodeContextMenu): (WI.DOMTreeElement.prototype._startEditingAsHTML.dispose): (WI.DOMTreeElement.prototype._startEditingAsHTML): (WI.DOMTreeElement.prototype._insertAdjacentHTML.commitCallback): (WI.DOMTreeElement.prototype._insertAdjacentHTML): (WI.DOMTreeElement.prototype.updateTitle): (WI.DOMTreeElement.prototype._singleTextChild): (WI.DOMTreeElement.prototype._nodeTitleInfo): (WI.DOMTreeElement.prototype._addHTML): (WI.DOMTreeElement.prototype._addPreviousSibling): (WI.DOMTreeElement.prototype._addNextSibling): (WI.DOMTreeElement.prototype._editAsHTML): Adjust where the editing element is placed depending on the options passed to `_editAsHTML`. If the placement is as a child, put it inside the child list. If the placement is as a sibling, place it before/after the selected TreeElement. Otherwise, add it as a child. * UserInterface/Views/DOMTreeOutline.css: (.tree-outline.dom): (.tree-outline.dom li): (.tree-outline.dom li:not(.editing)): (.tree-outline.dom li.editing): (body[dir=ltr] .tree-outline.dom li): Deleted. (body[dir=rtl] .tree-outline.dom li): Deleted. Don't add padding when the list item is being edited. git-svn-id: http://svn.webkit.org/repository/webkit/trunk@224456 268f45cc-cd09-0410-ab3c-d52691b4dbfc
1 parent b34d12e commit 94ca07e

File tree

5 files changed

+218
-45
lines changed

5 files changed

+218
-45
lines changed

Source/WebInspectorUI/ChangeLog

+41
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,44 @@
1+
2017-11-04 Devin Rousso <[email protected]>
2+
3+
Web Inspector: add contextmenu item to arbitrarily add HTML/Child to DOMTree
4+
https://bugs.webkit.org/show_bug.cgi?id=179042
5+
6+
Reviewed by Joseph Pecoraro.
7+
8+
* Localizations/en.lproj/localizedStrings.js:
9+
10+
* UserInterface/Models/DOMNode.js:
11+
(WI.DOMNode.prototype.insertAdjacentHTML.inspectedPage_node_insertAdjacentHTML):
12+
(WI.DOMNode.prototype.insertAdjacentHTML):
13+
Call-through to `insertAdjacentHTML` on the corresponding node in the inspected page.
14+
15+
* UserInterface/Views/DOMTreeElement.js:
16+
(WI.DOMTreeElement):
17+
(WI.DOMTreeElement.prototype._populateNodeContextMenu):
18+
(WI.DOMTreeElement.prototype._startEditingAsHTML.dispose):
19+
(WI.DOMTreeElement.prototype._startEditingAsHTML):
20+
(WI.DOMTreeElement.prototype._insertAdjacentHTML.commitCallback):
21+
(WI.DOMTreeElement.prototype._insertAdjacentHTML):
22+
(WI.DOMTreeElement.prototype.updateTitle):
23+
(WI.DOMTreeElement.prototype._singleTextChild):
24+
(WI.DOMTreeElement.prototype._nodeTitleInfo):
25+
(WI.DOMTreeElement.prototype._addHTML):
26+
(WI.DOMTreeElement.prototype._addPreviousSibling):
27+
(WI.DOMTreeElement.prototype._addNextSibling):
28+
(WI.DOMTreeElement.prototype._editAsHTML):
29+
Adjust where the editing element is placed depending on the options passed to `_editAsHTML`.
30+
If the placement is as a child, put it inside the child list. If the placement is as a
31+
sibling, place it before/after the selected TreeElement. Otherwise, add it as a child.
32+
33+
* UserInterface/Views/DOMTreeOutline.css:
34+
(.tree-outline.dom):
35+
(.tree-outline.dom li):
36+
(.tree-outline.dom li:not(.editing)):
37+
(.tree-outline.dom li.editing):
38+
(body[dir=ltr] .tree-outline.dom li): Deleted.
39+
(body[dir=rtl] .tree-outline.dom li): Deleted.
40+
Don't add padding when the list item is being edited.
41+
142
2017-11-03 Nikita Vasilyev <[email protected]>
243

344
Web Inspector: Uncaught Exception: null is not an object (evaluating 'selector.specificity.map') (at SpreadsheetCSSStyleDeclarationSection.js:199:51)

Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js

+3
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ localizedStrings["Center Y"] = "Center Y";
178178
localizedStrings["Character Data"] = "Character Data";
179179
localizedStrings["Charge ‘%s’ to Callers"] = "Charge ‘%s’ to Callers";
180180
localizedStrings["Checked"] = "Checked";
181+
localizedStrings["Child"] = "Child";
181182
localizedStrings["Child Layers"] = "Child Layers";
182183
localizedStrings["Child added to "] = "Child added to ";
183184
localizedStrings["Children"] = "Children";
@@ -599,6 +600,7 @@ localizedStrings["Network Issue"] = "Network Issue";
599600
localizedStrings["Network Requests"] = "Network Requests";
600601
localizedStrings["Network:"] = "Network:";
601602
localizedStrings["New Tab"] = "New Tab";
603+
localizedStrings["Next Sibling"] = "Next Sibling";
602604
localizedStrings["No"] = "No";
603605
localizedStrings["No Accessibility Information"] = "No Accessibility Information";
604606
localizedStrings["No Application Cache information available"] = "No Application Cache information available";
@@ -698,6 +700,7 @@ localizedStrings["Prefer indent using:"] = "Prefer indent using:";
698700
localizedStrings["Pressed"] = "Pressed";
699701
localizedStrings["Pretty print"] = "Pretty print";
700702
localizedStrings["Preview"] = "Preview";
703+
localizedStrings["Previous Sibling"] = "Previous Sibling";
701704
localizedStrings["Primary Key"] = "Primary Key";
702705
localizedStrings["Primary Key \u2014 %s"] = "Primary Key \u2014 %s";
703706
localizedStrings["Priority"] = "Priority";

Source/WebInspectorUI/UserInterface/Models/DOMNode.js

+17
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,23 @@ WI.DOMNode = class DOMNode extends WI.Object
500500
DOMAgent.setOuterHTML(this.id, html, this._makeUndoableCallback(callback));
501501
}
502502

503+
insertAdjacentHTML(position, html)
504+
{
505+
if (this.nodeType() !== Node.ELEMENT_NODE)
506+
return;
507+
508+
// FIXME: <https://webkit.org/b/179283> Web Inspector: support undo/redo of insertAdjacentHTML
509+
510+
WI.RemoteObject.resolveNode(this).then((object) => {
511+
function inspectedPage_node_insertAdjacentHTML(position, html) {
512+
this.insertAdjacentHTML(position, html);
513+
}
514+
515+
object.callFunction(inspectedPage_node_insertAdjacentHTML, [position, html]);
516+
object.release();
517+
});
518+
}
519+
503520
removeNode(callback)
504521
{
505522
DOMAgent.removeNode(this.id, this._makeUndoableCallback(callback));

Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js

+147-38
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement
5151
this._boundNodeChangedAnimationEnd = this._nodeChangedAnimationEnd.bind(this);
5252

5353
node.addEventListener(WI.DOMNode.Event.EnabledPseudoClassesChanged, this._nodePseudoClassesDidChange, this);
54+
55+
this._ignoreSingleTextChild = false;
56+
this._forceUpdateTitle = false;
5457
}
5558

5659
// Static
@@ -825,7 +828,12 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement
825828
{
826829
let node = this.representedObject;
827830

828-
// FIXME: <https://webkit.org/b/179042> Web Inspector: add contextmenu item to arbitrarily add HTML/Child to DOMTree
831+
let isEditableNode = node.nodeType() === Node.ELEMENT_NODE && this.editable;
832+
let forbiddenClosingTag = WI.DOMTreeElement.ForbiddenClosingTagElements.has(node.nodeNameInCorrectCase());
833+
subMenus.add.appendItem(WI.UIString("Child"), this._addHTML.bind(this), forbiddenClosingTag || !isEditableNode);
834+
subMenus.add.appendItem(WI.UIString("Previous Sibling"), this._addPreviousSibling.bind(this), !isEditableNode);
835+
subMenus.add.appendItem(WI.UIString("Next Sibling"), this._addNextSibling.bind(this), !isEditableNode);
836+
829837
subMenus.edit.appendItem(WI.UIString("HTML"), this._editAsHTML.bind(this), !this.editable);
830838
subMenus.copy.appendItem(WI.UIString("HTML"), this._copyHTML.bind(this), node.isPseudoElement());
831839
subMenus.delete.appendItem(WI.UIString("Node"), this.remove.bind(this), !this.editable);
@@ -950,7 +958,7 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement
950958
}
951959

952960
var tagName = tagNameElement.textContent;
953-
if (WI.DOMTreeElement.EditTagBlacklist[tagName.toLowerCase()])
961+
if (WI.DOMTreeElement.EditTagBlacklist.has(tagName.toLowerCase()))
954962
return false;
955963

956964
if (WI.isBeingEdited(tagNameElement))
@@ -988,27 +996,39 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement
988996
return true;
989997
}
990998

991-
_startEditingAsHTML(commitCallback, error, initialValue)
999+
_startEditingAsHTML(commitCallback, options = {})
9921000
{
993-
if (error)
994-
return;
9951001
if (this._htmlEditElement && WI.isBeingEdited(this._htmlEditElement))
9961002
return;
9971003

998-
this._htmlEditElement = document.createElement("div");
999-
this._htmlEditElement.textContent = initialValue;
1004+
if (options.hideExistingElements) {
1005+
let child = this.listItemElement.firstChild;
1006+
while (child) {
1007+
child.style.display = "none";
1008+
child = child.nextSibling;
1009+
}
1010+
if (this._childrenListNode)
1011+
this._childrenListNode.style.display = "none";
1012+
}
1013+
1014+
let positionInside = options.position === "afterbegin" || options.position === "beforeend";
1015+
if (positionInside && this._childrenListNode) {
1016+
this._htmlEditElement = document.createElement("li");
1017+
1018+
let referenceNode = options.position === "afterbegin" ? this._childrenListNode.firstElementChild : this._childrenListNode.lastElementChild;
1019+
this._childrenListNode.insertBefore(this._htmlEditElement, referenceNode);
1020+
} else if (options.position && !positionInside) {
1021+
this._htmlEditElement = document.createElement("li");
10001022

1001-
// Hide header items.
1002-
var child = this.listItemElement.firstChild;
1003-
while (child) {
1004-
child.style.display = "none";
1005-
child = child.nextSibling;
1023+
let targetNode = (options.position === "afterend" && this._childrenListNode) ? this._childrenListNode : this.listItemElement;
1024+
targetNode.insertAdjacentElement(options.position, this._htmlEditElement);
1025+
} else {
1026+
this._htmlEditElement = document.createElement("div");
1027+
this.listItemElement.appendChild(this._htmlEditElement);
10061028
}
1007-
// Hide children item.
1008-
if (this._childrenListNode)
1009-
this._childrenListNode.style.display = "none";
1010-
// Append editor.
1011-
this.listItemElement.appendChild(this._htmlEditElement);
1029+
1030+
if (options.initialValue)
1031+
this._htmlEditElement.textContent = options.initialValue;
10121032

10131033
this.updateSelectionArea();
10141034

@@ -1023,16 +1043,17 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement
10231043
this._editing = false;
10241044

10251045
// Remove editor.
1026-
this.listItemElement.removeChild(this._htmlEditElement);
1046+
this._htmlEditElement.remove();
10271047
this._htmlEditElement = null;
1028-
// Unhide children item.
1029-
if (this._childrenListNode)
1030-
this._childrenListNode.style.removeProperty("display");
1031-
// Unhide header items.
1032-
var child = this.listItemElement.firstChild;
1033-
while (child) {
1034-
child.style.removeProperty("display");
1035-
child = child.nextSibling;
1048+
1049+
if (options.hideExistingElements) {
1050+
if (this._childrenListNode)
1051+
this._childrenListNode.style.removeProperty("display");
1052+
let child = this.listItemElement.firstChild;
1053+
while (child) {
1054+
child.style.removeProperty("display");
1055+
child = child.nextSibling;
1056+
}
10361057
}
10371058

10381059
this.updateSelectionArea();
@@ -1041,6 +1062,16 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement
10411062
var config = new WI.EditingConfig(commit.bind(this), dispose.bind(this));
10421063
config.setMultiline(true);
10431064
this._editing = WI.startEditing(this._htmlEditElement, config);
1065+
1066+
if (!isNaN(options.startPosition)) {
1067+
let range = document.createRange();
1068+
range.setStart(this._htmlEditElement.firstChild, options.startPosition);
1069+
range.collapse(true);
1070+
1071+
let selection = window.getSelection();
1072+
selection.removeAllRanges();
1073+
selection.addRange(range);
1074+
}
10441075
}
10451076

10461077
_attributeEditingCommitted(element, newText, oldText, attributeName, moveDirection)
@@ -1220,7 +1251,7 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement
12201251
{
12211252
// If we are editing, return early to prevent canceling the edit.
12221253
// After editing is committed updateTitle will be called.
1223-
if (this._editing)
1254+
if (this._editing && !this._forceUpdateTitle)
12241255
return;
12251256

12261257
if (onlySearchQueryChanged) {
@@ -1388,7 +1419,7 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement
13881419
var textChild = this._singleTextChild(node);
13891420
var showInlineText = textChild && textChild.nodeValue().length < WI.DOMTreeElement.MaximumInlineTextChildLength;
13901421

1391-
if (!this.expanded && (!showInlineText && (this.treeOutline.isXMLMimeType || !WI.DOMTreeElement.ForbiddenClosingTagElements[tagName]))) {
1422+
if (!this.expanded && (!showInlineText && (this.treeOutline.isXMLMimeType || !WI.DOMTreeElement.ForbiddenClosingTagElements.has(tagName)))) {
13921423
if (this.hasChildren) {
13931424
var textNodeElement = info.titleDOM.createChild("span", "html-text-node");
13941425
textNodeElement.textContent = ellipsis;
@@ -1473,7 +1504,7 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement
14731504

14741505
_singleTextChild(node)
14751506
{
1476-
if (!node)
1507+
if (!node || this._ignoreSingleTextChild)
14771508
return null;
14781509

14791510
var firstChild = node.firstChild;
@@ -1564,6 +1595,80 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement
15641595
this.representedObject.removeNode(removeNodeCallback);
15651596
}
15661597

1598+
_insertAdjacentHTML(position, options = {})
1599+
{
1600+
let hasChildren = this.hasChildren;
1601+
1602+
let commitCallback = (value) => {
1603+
this._ignoreSingleTextChild = false;
1604+
1605+
if (!value.length) {
1606+
if (!hasChildren) {
1607+
this._forceUpdateTitle = true;
1608+
this.hasChildren = false;
1609+
this._forceUpdateTitle = false;
1610+
}
1611+
return;
1612+
}
1613+
1614+
this.representedObject.insertAdjacentHTML(position, value);
1615+
};
1616+
1617+
if (position === "afterbegin" || position === "beforeend") {
1618+
this._ignoreSingleTextChild = true;
1619+
this.hasChildren = true;
1620+
this.expand();
1621+
}
1622+
1623+
this._startEditingAsHTML(commitCallback, Object.shallowMerge(options, {position}));
1624+
}
1625+
1626+
_addHTML(event)
1627+
{
1628+
let options = {};
1629+
switch (this.representedObject.nodeNameInCorrectCase()) {
1630+
case "ul":
1631+
case "ol":
1632+
options.initialValue = "<li></li>";
1633+
options.startPosition = 4;
1634+
break;
1635+
case "table":
1636+
case "thead":
1637+
case "tbody":
1638+
case "tfoot":
1639+
options.initialValue = "<tr></tr>";
1640+
options.startPosition = 4;
1641+
break;
1642+
case "tr":
1643+
options.initializing = "<td></td>";
1644+
options.startPosition = 4;
1645+
break;
1646+
}
1647+
this._insertAdjacentHTML("beforeend", options);
1648+
}
1649+
1650+
_addPreviousSibling(event)
1651+
{
1652+
let options = {};
1653+
let nodeName = this.representedObject.nodeNameInCorrectCase();
1654+
if (nodeName === "li" || nodeName === "tr" || nodeName === "th" || nodeName === "td") {
1655+
options.initialValue = `<${nodeName}></${nodeName}>`;
1656+
options.startPosition = nodeName.length + 2;
1657+
}
1658+
this._insertAdjacentHTML("beforebegin", options);
1659+
}
1660+
1661+
_addNextSibling(event)
1662+
{
1663+
let options = {};
1664+
let nodeName = this.representedObject.nodeNameInCorrectCase();
1665+
if (nodeName === "li" || nodeName === "tr" || nodeName === "th" || nodeName === "td") {
1666+
options.initialValue = `<${nodeName}></${nodeName}>`;
1667+
options.startPosition = nodeName.length + 2;
1668+
}
1669+
this._insertAdjacentHTML("afterend", options);
1670+
}
1671+
15671672
_editAsHTML()
15681673
{
15691674
var treeOutline = this.treeOutline;
@@ -1598,7 +1703,15 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement
15981703
node.setOuterHTML(value, selectNode);
15991704
}
16001705

1601-
node.getOuterHTML(this._startEditingAsHTML.bind(this, commitChange));
1706+
node.getOuterHTML((error, initialValue) => {
1707+
if (error)
1708+
return;
1709+
1710+
this._startEditingAsHTML(commitChange, {
1711+
initialValue,
1712+
hideExistingElements: true,
1713+
});
1714+
});
16021715
}
16031716

16041717
_copyHTML()
@@ -1775,20 +1888,16 @@ WI.DOMTreeElement.MaximumInlineTextChildLength = 80;
17751888

17761889
// A union of HTML4 and HTML5-Draft elements that explicitly
17771890
// or implicitly (for HTML5) forbid the closing tag.
1778-
WI.DOMTreeElement.ForbiddenClosingTagElements = [
1891+
WI.DOMTreeElement.ForbiddenClosingTagElements = new Set([
17791892
"area", "base", "basefont", "br", "canvas", "col", "command", "embed", "frame",
17801893
"hr", "img", "input", "keygen", "link", "meta", "param", "source",
17811894
"wbr", "track", "menuitem"
1782-
].keySet();
1895+
]);
17831896

17841897
// These tags we do not allow editing their tag name.
1785-
WI.DOMTreeElement.EditTagBlacklist = [
1898+
WI.DOMTreeElement.EditTagBlacklist = new Set([
17861899
"html", "head", "body"
1787-
].keySet();
1788-
1789-
WI.DOMTreeElement.ChangeType = {
1790-
Attribute: "dom-tree-element-change-type-attribute"
1791-
};
1900+
]);
17921901

17931902
WI.DOMTreeElement.BreakpointStatus = {
17941903
None: Symbol("none"),

0 commit comments

Comments
 (0)