Commit 46f4fe3f authored by apavlov@chromium.org's avatar apavlov@chromium.org

DevTools: [Elements] Highlight DOM updates

R=vsevik
BUG=424085

Review URL: https://codereview.chromium.org/701153002

git-svn-id: svn://svn.chromium.org/blink/trunk@185115 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent 690263ec
...@@ -507,6 +507,45 @@ InspectorTest.dumpElementsTree = function(rootNode, depth, resultsArray) ...@@ -507,6 +507,45 @@ InspectorTest.dumpElementsTree = function(rootNode, depth, resultsArray)
print(rootNode ? treeOutline.findTreeElement(rootNode) : treeOutline, "", depth || 10000); print(rootNode ? treeOutline.findTreeElement(rootNode) : treeOutline, "", depth || 10000);
}; };
InspectorTest.dumpDOMUpdateHighlights = function(rootNode, depth)
{
var treeOutline = InspectorTest.firstElementsTreeOutline();
treeOutline._updateModifiedNodes();
print(rootNode ? treeOutline.findTreeElement(rootNode) : treeOutline, "", depth || 10000);
function print(treeItem, prefix, depth)
{
if (treeItem.listItemElement) {
var elementXPath = WebInspector.DOMPresentationUtils.xPath(treeItem._node, true);
var highlightedElements = treeItem.listItemElement.querySelectorAll(".dom-update-highlight");
for (var i = 0; i < highlightedElements.length; ++i) {
var element = highlightedElements[i];
var classList = element.classList;
var xpath = elementXPath;
if (classList.contains("webkit-html-attribute-name")) {
xpath += "/@" + element.textContent + " (empty)";
} else if (classList.contains("webkit-html-attribute-value")) {
name = element.parentElement.querySelector(".webkit-html-attribute-name").textContent;
xpath += "/@" + name + " " + element.textContent;
} else if (classList.contains("webkit-html-text-node")) {
xpath += "/text() \"" + element.textContent + "\"";
}
InspectorTest.addResult(prefix + xpath);
}
}
if (!treeItem.expanded)
return;
var children = treeItem.children;
var newPrefix = treeItem === treeItem.treeOutline ? "" : prefix + " ";
for (var i = 0; depth && children && i < children.length; ++i) {
if (!children[i]._elementCloseTag)
print(children[i], newPrefix, depth - 1);
}
}
}
InspectorTest.expandElementsTree = function(callback) InspectorTest.expandElementsTree = function(callback)
{ {
var expandedSomething = false; var expandedSomething = false;
......
Tests DOM update highlights in the DOM tree.
Running: testDumpInitial
========= Original ========
Running: testSetAttributeOtherValue
//*[@id="attrTest"]/@attrfoo bar
Running: testSetAttributeEmpty
//*[@id="attrTest"]/@attrfoo (empty)
Running: testAddAttribute
//*[@id="attrTest"]/@attrbar newBar
Running: testRemoveAttribute
//*[@id="attrTest"]
Running: testAppendChildToEmpty
//*[@id="childTest"]
Running: testAppendChildToExpanded
//*[@id="child2"]
Running: testRemoveChild1
//*[@id="childTest"]
Running: testRemoveChild2
//*[@id="childTest"]
Running: testSetTextContent
//*[@id="textTest"]/text() "Text"
Running: testSetTextNodeTextContent
//*[@id="textTest"]/text() "NewText"
Running: testRemoveInlineTextNode
//*[@id="textTest"]
Running: testSetTextContentWithEmptyText
//*[@id="textTest"]/text() "Text"
Running: testClearTextNodeTextContent
//*[@id="textTest"]
//*[@id="textTest"]/text() ""
<html>
<head>
<script src="../../http/tests/inspector/inspector-test.js"></script>
<script src="../../http/tests/inspector/elements-test.js"></script>
<script>
function appendChild(parentId, id)
{
var e = document.createElement("span");
e.id = id;
document.getElementById(parentId).appendChild(e);
}
function remove(id)
{
document.getElementById(id).remove();
}
function removeFirstChild(id)
{
document.getElementById(id).firstChild.remove();
}
function setAttribute(id, name, value)
{
var e = document.getElementById(id);
if (value === undefined)
e.removeAttribute(name);
else
e.setAttribute(name, value);
}
function setTextContent(id, content)
{
document.getElementById(id).textContent = content;
}
function setFirstChildTextContent(id, content)
{
document.getElementById(id).firstChild.textContent = content;
}
function test()
{
var containerNode;
var attrTestNode;
var childTestNode;
var textTestNode;
InspectorTest.runTestSuite([
function testDumpInitial(next)
{
function callback(node)
{
containerNode = InspectorTest.expandedNodeWithId("container");
attrTestNode = InspectorTest.expandedNodeWithId("attrTest");
childTestNode = InspectorTest.expandedNodeWithId("childTest");
textTestNode = InspectorTest.expandedNodeWithId("textTest");
InspectorTest.addResult("========= Original ========");
InspectorTest.dumpDOMUpdateHighlights(containerNode);
next();
}
InspectorTest.expandElementsTree(callback);
},
function testSetAttributeOtherValue(next)
{
runAndDumpHighlights("setAttribute('attrTest', 'attrFoo', 'bar')", attrTestNode, next);
},
function testSetAttributeEmpty(next)
{
runAndDumpHighlights("setAttribute('attrTest', 'attrFoo', '')", attrTestNode, next);
},
function testAddAttribute(next)
{
runAndDumpHighlights("setAttribute('attrTest', 'attrBar', 'newBar')", attrTestNode, next);
},
function testRemoveAttribute(next)
{
runAndDumpHighlights("setAttribute('attrTest', 'attrFoo')", attrTestNode, next);
},
function testAppendChildToEmpty(next)
{
runAndDumpHighlights("appendChild('childTest', 'child1')", childTestNode, callback);
function callback()
{
// Expand the #childTest node.
InspectorTest.expandElementsTree(next);
}
},
function testAppendChildToExpanded(next)
{
runAndDumpHighlights("appendChild('childTest', 'child2')", childTestNode, next);
},
function testRemoveChild1(next)
{
runAndDumpHighlights("remove('child1')", childTestNode, next);
},
function testRemoveChild2(next)
{
runAndDumpHighlights("remove('child2')", childTestNode, next);
},
function testSetTextContent(next)
{
runAndDumpHighlights("setTextContent('textTest', 'Text')", textTestNode, next);
},
function testSetTextNodeTextContent(next)
{
runAndDumpHighlights("setFirstChildTextContent('textTest', 'NewText')", textTestNode, next);
},
function testRemoveInlineTextNode(next)
{
runAndDumpHighlights("removeFirstChild('textTest')", textTestNode, next);
},
function testSetTextContentWithEmptyText(next)
{
runAndDumpHighlights("setTextContent('textTest', 'Text')", textTestNode, next);
},
function testClearTextNodeTextContent(next)
{
runAndDumpHighlights("setFirstChildTextContent('textTest', '')", textTestNode, next);
}
]);
function runAndDumpHighlights(script, root, next)
{
InspectorTest.evaluateInPage(script, callback);
function callback()
{
// InspectorTest.dumpElementsTree(root);
InspectorTest.dumpDOMUpdateHighlights(root);
var treeOutline = InspectorTest.firstElementsTreeOutline();
var highlights = treeOutline._element.getElementsByClassName("dom-update-highlight");
for (var i = 0; i < highlights.length; ++i)
highlights[i].classList.remove("dom-update-highlight");
next();
}
}
}
</script>
</head>
<body onload="runTest()">
<p>
Tests DOM update highlights in the DOM tree.
</p>
<div id="container">
<div id="attrTest" attrFoo="foo"></div>
<div id="childTest"></div>
<div id="textTest"></div>
</div>
</body>
</html>
...@@ -91,6 +91,7 @@ WebInspector.Settings = function() ...@@ -91,6 +91,7 @@ WebInspector.Settings = function()
this.disableOverridesWarning = this.createSetting("disableOverridesWarning", false); this.disableOverridesWarning = this.createSetting("disableOverridesWarning", false);
this.testPath = this.createSetting("testPath", ""); this.testPath = this.createSetting("testPath", "");
this.frameViewerHideChromeWindow = this.createSetting("frameViewerHideChromeWindow", false); this.frameViewerHideChromeWindow = this.createSetting("frameViewerHideChromeWindow", false);
this.highlightDOMUpdates = this.createSetting("highlightDOMUpdates", true);
// Rendering options // Rendering options
this.showPaintRects = this.createSetting("showPaintRects", false); this.showPaintRects = this.createSetting("showPaintRects", false);
......
...@@ -1053,6 +1053,15 @@ WebInspector.ElementsTreeElement.ChildrenDisplayMode = { ...@@ -1053,6 +1053,15 @@ WebInspector.ElementsTreeElement.ChildrenDisplayMode = {
HasChildren: 2 HasChildren: 2
} }
/**
* @param {!WebInspector.ElementsTreeElement} treeElement
*/
WebInspector.ElementsTreeElement.animateOnDOMUpdate = function(treeElement)
{
var tagName = treeElement.listItemElement.querySelector(".webkit-html-tag-name");
WebInspector.runCSSAnimationOnce(tagName || treeElement.listItemElement, "dom-update-highlight");
}
WebInspector.ElementsTreeElement.prototype = { WebInspector.ElementsTreeElement.prototype = {
/** /**
* @return {!WebInspector.DOMNode} * @return {!WebInspector.DOMNode}
...@@ -1295,6 +1304,21 @@ WebInspector.ElementsTreeElement.prototype = { ...@@ -1295,6 +1304,21 @@ WebInspector.ElementsTreeElement.prototype = {
child.select(); child.select();
}, },
/**
* @param {!WebInspector.DOMNode=} node
* @return {!WebInspector.ElementsTreeUpdater.UpdateInfo|undefined}
*/
_updateInfo: function(node)
{
if (!WebInspector.settings.highlightDOMUpdates.get())
return undefined;
var updater = this.treeOutline._elementsTreeUpdater;
if (!updater)
return undefined;
var effectiveNode = node || this._node;
return updater._recentlyModifiedNodes.get(effectiveNode) || updater._recentlyModifiedParentNodes.get(effectiveNode);
},
/** /**
* @param {boolean} fullRefresh * @param {boolean} fullRefresh
* @param {?Array.<!WebInspector.DOMNode>} children * @param {?Array.<!WebInspector.DOMNode>} children
...@@ -1348,6 +1372,9 @@ WebInspector.ElementsTreeElement.prototype = { ...@@ -1348,6 +1372,9 @@ WebInspector.ElementsTreeElement.prototype = {
} else { } else {
// No existing element found, insert a new element. // No existing element found, insert a new element.
var newElement = this.insertChildElement(child, i); var newElement = this.insertChildElement(child, i);
var updateRecord = this._updateInfo();
if (updateRecord)
WebInspector.ElementsTreeElement.animateOnDOMUpdate(newElement);
if (child === selectedNode) if (child === selectedNode)
elementToSelect = newElement; elementToSelect = newElement;
// If a node was inserted in the middle of existing list dynamically we might need to increase the limit. // If a node was inserted in the middle of existing list dynamically we might need to increase the limit.
...@@ -2285,6 +2312,10 @@ WebInspector.ElementsTreeElement.prototype = { ...@@ -2285,6 +2312,10 @@ WebInspector.ElementsTreeElement.prototype = {
var attrValueElement = attrSpanElement.createChild("span", "webkit-html-attribute-value"); var attrValueElement = attrSpanElement.createChild("span", "webkit-html-attribute-value");
var updates = this._updateInfo();
if (updates && updates.isAttributeModified(name))
WebInspector.runCSSAnimationOnce(hasText ? attrValueElement : attrNameElement, "dom-update-highlight");
/** /**
* @this {WebInspector.ElementsTreeElement} * @this {WebInspector.ElementsTreeElement}
* @param {string} value * @param {string} value
...@@ -2356,14 +2387,34 @@ WebInspector.ElementsTreeElement.prototype = { ...@@ -2356,14 +2387,34 @@ WebInspector.ElementsTreeElement.prototype = {
tagElement.createTextChild("<"); tagElement.createTextChild("<");
var tagNameElement = tagElement.createChild("span", isClosingTag ? "" : "webkit-html-tag-name"); var tagNameElement = tagElement.createChild("span", isClosingTag ? "" : "webkit-html-tag-name");
tagNameElement.textContent = (isClosingTag ? "/" : "") + tagName; tagNameElement.textContent = (isClosingTag ? "/" : "") + tagName;
if (!isClosingTag && node.hasAttributes()) { if (!isClosingTag) {
var attributes = node.attributes(); if (node.hasAttributes()) {
for (var i = 0; i < attributes.length; ++i) { var attributes = node.attributes();
var attr = attributes[i]; for (var i = 0; i < attributes.length; ++i) {
tagElement.createTextChild(" "); var attr = attributes[i];
this._buildAttributeDOM(tagElement, attr.name, attr.value, false, node, linkify); tagElement.createTextChild(" ");
this._buildAttributeDOM(tagElement, attr.name, attr.value, false, node, linkify);
}
}
var hasUpdates;
var updates = this._updateInfo();
if (updates) {
hasUpdates |= updates.hasRemovedAttributes();
var hasInlineText = this._childrenDisplayMode === WebInspector.ElementsTreeElement.ChildrenDisplayMode.InlineText;
hasUpdates |= (!hasInlineText || this.expanded) && updates.hasChangedChildren();
// Highlight the tag name, as the inserted node is not visible (either child of a collapsed tree element or empty inline text).
hasUpdates |= !this.expanded && updates.hasInsertedNodes() && (!hasInlineText || this._node.firstChild.nodeValue().length === 0);
// Highlight the tag name, as the inline text node value has been cleared.
// The respective empty node will be highlighted, but the highlight will not be visible to the user.
hasUpdates |= hasInlineText && (updates.isCharDataModified() || updates.hasChangedChildren()) && this._node.firstChild.nodeValue().length === 0;
} }
if (hasUpdates)
WebInspector.runCSSAnimationOnce(tagNameElement, "dom-update-highlight");
} }
tagElement.createTextChild(">"); tagElement.createTextChild(">");
parentElement.createTextChild("\u200B"); parentElement.createTextChild("\u200B");
}, },
...@@ -2375,7 +2426,6 @@ WebInspector.ElementsTreeElement.prototype = { ...@@ -2375,7 +2426,6 @@ WebInspector.ElementsTreeElement.prototype = {
_convertWhitespaceToEntities: function(text) _convertWhitespaceToEntities: function(text)
{ {
var result = ""; var result = "";
var resultLength = 0;
var lastIndexAfterEntity = 0; var lastIndexAfterEntity = 0;
var entityRanges = []; var entityRanges = [];
var charToEntity = WebInspector.ElementsTreeOutline.MappedCharToEntity; var charToEntity = WebInspector.ElementsTreeOutline.MappedCharToEntity;
...@@ -2442,6 +2492,12 @@ WebInspector.ElementsTreeElement.prototype = { ...@@ -2442,6 +2492,12 @@ WebInspector.ElementsTreeElement.prototype = {
info.titleDOM.createTextChild("\u200B"); info.titleDOM.createTextChild("\u200B");
info.hasChildren = false; info.hasChildren = false;
this._buildTagDOM(info.titleDOM, tagName, true, false); this._buildTagDOM(info.titleDOM, tagName, true, false);
var updates = this._updateInfo();
if (updates && (updates.hasInsertedNodes() || updates.hasChangedChildren()))
WebInspector.runCSSAnimationOnce(textNodeElement, "dom-update-highlight");
updates = this._updateInfo(this._node.firstChild);
if (updates && updates.isCharDataModified())
WebInspector.runCSSAnimationOnce(textNodeElement, "dom-update-highlight");
break; break;
case WebInspector.ElementsTreeElement.ChildrenDisplayMode.NoChildren: case WebInspector.ElementsTreeElement.ChildrenDisplayMode.NoChildren:
...@@ -2471,6 +2527,9 @@ WebInspector.ElementsTreeElement.prototype = { ...@@ -2471,6 +2527,9 @@ WebInspector.ElementsTreeElement.prototype = {
textNodeElement.textContent = result.text; textNodeElement.textContent = result.text;
WebInspector.highlightRangesWithStyleClass(textNodeElement, result.entityRanges, "webkit-html-entity-value"); WebInspector.highlightRangesWithStyleClass(textNodeElement, result.entityRanges, "webkit-html-entity-value");
info.titleDOM.createTextChild("\""); info.titleDOM.createTextChild("\"");
var updates = this._updateInfo();
if (updates && updates.isCharDataModified())
WebInspector.runCSSAnimationOnce(textNodeElement, "dom-update-highlight");
} }
break; break;
...@@ -2499,6 +2558,7 @@ WebInspector.ElementsTreeElement.prototype = { ...@@ -2499,6 +2558,7 @@ WebInspector.ElementsTreeElement.prototype = {
var cdataElement = info.titleDOM.createChild("span", "webkit-html-text-node"); var cdataElement = info.titleDOM.createChild("span", "webkit-html-text-node");
cdataElement.createTextChild("<![CDATA[" + node.nodeValue() + "]]>"); cdataElement.createTextChild("<![CDATA[" + node.nodeValue() + "]]>");
break; break;
case Node.DOCUMENT_FRAGMENT_NODE: case Node.DOCUMENT_FRAGMENT_NODE:
var fragmentElement = info.titleDOM.createChild("span", "webkit-html-fragment"); var fragmentElement = info.titleDOM.createChild("span", "webkit-html-fragment");
if (node.isInShadowTree()) { if (node.isInShadowTree()) {
...@@ -2747,18 +2807,18 @@ WebInspector.ElementsTreeUpdater = function(domModel, treeOutline) ...@@ -2747,18 +2807,18 @@ WebInspector.ElementsTreeUpdater = function(domModel, treeOutline)
{ {
domModel.addEventListener(WebInspector.DOMModel.Events.NodeInserted, this._nodeInserted, this); domModel.addEventListener(WebInspector.DOMModel.Events.NodeInserted, this._nodeInserted, this);
domModel.addEventListener(WebInspector.DOMModel.Events.NodeRemoved, this._nodeRemoved, this); domModel.addEventListener(WebInspector.DOMModel.Events.NodeRemoved, this._nodeRemoved, this);
domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributesUpdated, this); domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributeModified, this);
domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributesUpdated, this); domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributeRemoved, this);
domModel.addEventListener(WebInspector.DOMModel.Events.CharacterDataModified, this._characterDataModified, this); domModel.addEventListener(WebInspector.DOMModel.Events.CharacterDataModified, this._characterDataModified, this);
domModel.addEventListener(WebInspector.DOMModel.Events.DocumentUpdated, this._documentUpdated, this); domModel.addEventListener(WebInspector.DOMModel.Events.DocumentUpdated, this._documentUpdated, this);
domModel.addEventListener(WebInspector.DOMModel.Events.ChildNodeCountUpdated, this._childNodeCountUpdated, this); domModel.addEventListener(WebInspector.DOMModel.Events.ChildNodeCountUpdated, this._childNodeCountUpdated, this);
this._domModel = domModel; this._domModel = domModel;
this._treeOutline = treeOutline; this._treeOutline = treeOutline;
/** @type {!Set.<!WebInspector.DOMNode>} */ /** @type {!Map.<!WebInspector.DOMNode, !WebInspector.ElementsTreeUpdater.UpdateInfo>} */
this._recentlyModifiedNodes = new Set(); this._recentlyModifiedNodes = new Map();
/** @type {!Set.<!WebInspector.DOMNode>} */ /** @type {!Map.<!WebInspector.DOMNode, !WebInspector.ElementsTreeUpdater.UpdateInfo>} */
this._recentlyModifiedParentNodes = new Set(); this._recentlyModifiedParentNodes = new Map();
} }
WebInspector.ElementsTreeUpdater.prototype = { WebInspector.ElementsTreeUpdater.prototype = {
...@@ -2766,8 +2826,8 @@ WebInspector.ElementsTreeUpdater.prototype = { ...@@ -2766,8 +2826,8 @@ WebInspector.ElementsTreeUpdater.prototype = {
{ {
this._domModel.removeEventListener(WebInspector.DOMModel.Events.NodeInserted, this._nodeInserted, this); this._domModel.removeEventListener(WebInspector.DOMModel.Events.NodeInserted, this._nodeInserted, this);
this._domModel.removeEventListener(WebInspector.DOMModel.Events.NodeRemoved, this._nodeRemoved, this); this._domModel.removeEventListener(WebInspector.DOMModel.Events.NodeRemoved, this._nodeRemoved, this);
this._domModel.removeEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributesUpdated, this); this._domModel.removeEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributeModified, this);
this._domModel.removeEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributesUpdated, this); this._domModel.removeEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributeRemoved, this);
this._domModel.removeEventListener(WebInspector.DOMModel.Events.CharacterDataModified, this._characterDataModified, this); this._domModel.removeEventListener(WebInspector.DOMModel.Events.CharacterDataModified, this._characterDataModified, this);
this._domModel.removeEventListener(WebInspector.DOMModel.Events.DocumentUpdated, this._documentUpdated, this); this._domModel.removeEventListener(WebInspector.DOMModel.Events.DocumentUpdated, this._documentUpdated, this);
this._domModel.removeEventListener(WebInspector.DOMModel.Events.ChildNodeCountUpdated, this._childNodeCountUpdated, this); this._domModel.removeEventListener(WebInspector.DOMModel.Events.ChildNodeCountUpdated, this._childNodeCountUpdated, this);
...@@ -2775,33 +2835,47 @@ WebInspector.ElementsTreeUpdater.prototype = { ...@@ -2775,33 +2835,47 @@ WebInspector.ElementsTreeUpdater.prototype = {
/** /**
* @param {?WebInspector.DOMNode} parentNode * @param {?WebInspector.DOMNode} parentNode
* @return {!WebInspector.ElementsTreeUpdater.UpdateInfo}
*/ */
_parentNodeModified: function(parentNode) _parentNodeModified: function(parentNode)
{ {
if (!parentNode) if (!parentNode)
return; return new WebInspector.ElementsTreeUpdater.UpdateInfo(); // Bogus info.
this._recentlyModifiedParentNodes.add(parentNode);
var record = this._recentlyModifiedParentNodes.get(parentNode);
if (!record) {
record = new WebInspector.ElementsTreeUpdater.UpdateInfo();
this._recentlyModifiedParentNodes.set(parentNode, record);
}
var treeElement = this._treeOutline.findTreeElement(parentNode); var treeElement = this._treeOutline.findTreeElement(parentNode);
if (treeElement) { if (treeElement) {
var oldDisplayMode = treeElement._childrenDisplayMode; var oldDisplayMode = treeElement._childrenDisplayMode;
treeElement._updateChildrenDisplayMode(); treeElement._updateChildrenDisplayMode();
if (treeElement._childrenDisplayMode !== oldDisplayMode) if (treeElement._childrenDisplayMode !== oldDisplayMode)
this._nodeModified(parentNode); this._nodeModified(parentNode).childrenModified();
} }
if (this._treeOutline._visible) if (this._treeOutline._visible)
this._updateModifiedNodesSoon(); this._updateModifiedNodesSoon();
return record;
}, },
/** /**
* @param {!WebInspector.DOMNode} node * @param {!WebInspector.DOMNode} node
* @return {!WebInspector.ElementsTreeUpdater.UpdateInfo}
*/ */
_nodeModified: function(node) _nodeModified: function(node)
{ {
this._recentlyModifiedNodes.add(node);
if (this._treeOutline._visible) if (this._treeOutline._visible)
this._updateModifiedNodesSoon(); this._updateModifiedNodesSoon();
var record = this._recentlyModifiedNodes.get(node);
if (!record) {
record = new WebInspector.ElementsTreeUpdater.UpdateInfo();
this._recentlyModifiedNodes.set(node, record);
}
return record;
}, },
/** /**
...@@ -2822,10 +2896,19 @@ WebInspector.ElementsTreeUpdater.prototype = { ...@@ -2822,10 +2896,19 @@ WebInspector.ElementsTreeUpdater.prototype = {
/** /**
* @param {!WebInspector.Event} event * @param {!WebInspector.Event} event
*/ */
_attributesUpdated: function(event) _attributeModified: function(event)
{ {
var node = /** @type {!WebInspector.DOMNode} */ (event.data.node); var node = /** @type {!WebInspector.DOMNode} */ (event.data.node);
this._nodeModified(node); this._nodeModified(node).attributeModified(event.data.name);
},
/**
* @param {!WebInspector.Event} event
*/
_attributeRemoved: function(event)
{
var node = /** @type {!WebInspector.DOMNode} */ (event.data.node);
this._nodeModified(node).attributeRemoved(event.data.name);
}, },
/** /**
...@@ -2834,8 +2917,8 @@ WebInspector.ElementsTreeUpdater.prototype = { ...@@ -2834,8 +2917,8 @@ WebInspector.ElementsTreeUpdater.prototype = {
_characterDataModified: function(event) _characterDataModified: function(event)
{ {
var node = /** @type {!WebInspector.DOMNode} */ (event.data); var node = /** @type {!WebInspector.DOMNode} */ (event.data);
this._parentNodeModified(node.parentNode); this._parentNodeModified(node.parentNode).charDataModified();
this._nodeModified(node); this._nodeModified(node).charDataModified();
}, },
/** /**
...@@ -2844,7 +2927,7 @@ WebInspector.ElementsTreeUpdater.prototype = { ...@@ -2844,7 +2927,7 @@ WebInspector.ElementsTreeUpdater.prototype = {
_nodeInserted: function(event) _nodeInserted: function(event)
{ {
var node = /** @type {!WebInspector.DOMNode} */ (event.data); var node = /** @type {!WebInspector.DOMNode} */ (event.data);
this._parentNodeModified(node.parentNode); this._parentNodeModified(node.parentNode).nodeInserted(node);
}, },
/** /**
...@@ -2855,7 +2938,7 @@ WebInspector.ElementsTreeUpdater.prototype = { ...@@ -2855,7 +2938,7 @@ WebInspector.ElementsTreeUpdater.prototype = {
var node = /** @type {!WebInspector.DOMNode} */ (event.data.node); var node = /** @type {!WebInspector.DOMNode} */ (event.data.node);
var parentNode = /** @type {!WebInspector.DOMNode} */ (event.data.parent); var parentNode = /** @type {!WebInspector.DOMNode} */ (event.data.parent);
this._treeOutline._resetClipboardIfNeeded(node); this._treeOutline._resetClipboardIfNeeded(node);
this._parentNodeModified(parentNode); this._parentNodeModified(parentNode).childrenModified();
}, },
/** /**
...@@ -2881,7 +2964,7 @@ WebInspector.ElementsTreeUpdater.prototype = { ...@@ -2881,7 +2964,7 @@ WebInspector.ElementsTreeUpdater.prototype = {
delete this._updateModifiedNodesTimeout; delete this._updateModifiedNodesTimeout;
} }
var updatedNodes = this._recentlyModifiedNodes.valuesArray().concat(this._recentlyModifiedParentNodes.valuesArray()); var updatedNodes = this._recentlyModifiedNodes.keysArray().concat(this._recentlyModifiedParentNodes.keysArray());
var hidePanelWhileUpdating = updatedNodes.length > 10; var hidePanelWhileUpdating = updatedNodes.length > 10;
if (hidePanelWhileUpdating) { if (hidePanelWhileUpdating) {
var treeOutlineContainerElement = this._treeOutline.element.parentNode; var treeOutlineContainerElement = this._treeOutline.element.parentNode;
...@@ -2893,18 +2976,16 @@ WebInspector.ElementsTreeUpdater.prototype = { ...@@ -2893,18 +2976,16 @@ WebInspector.ElementsTreeUpdater.prototype = {
// Document's children have changed, perform total update. // Document's children have changed, perform total update.
this._treeOutline.update(); this._treeOutline.update();
} else { } else {
var nodes = this._recentlyModifiedNodes.valuesArray(); for (var node of this._recentlyModifiedNodes.keys()) {
for (var i = 0, size = nodes.length; i < size; ++i) { var nodeItem = this._treeOutline.findTreeElement(node);
var nodeItem = this._treeOutline.findTreeElement(nodes[i]);
if (nodeItem) if (nodeItem)
nodeItem.updateTitle(); nodeItem.updateTitle(false);
} }
var parentNodes = this._recentlyModifiedParentNodes.valuesArray(); for (var node of this._recentlyModifiedParentNodes.keys()) {
for (var i = 0, size = parentNodes.length; i < size; ++i) { var parentNodeItem = this._treeOutline.findTreeElement(node);
var parentNodeItem = this._treeOutline.findTreeElement(parentNodes[i]);
if (parentNodeItem && parentNodeItem.populated) if (parentNodeItem && parentNodeItem.populated)
parentNodeItem.updateChildren(); parentNodeItem.updateChildren(false);
} }
} }
...@@ -2930,6 +3011,108 @@ WebInspector.ElementsTreeUpdater.prototype = { ...@@ -2930,6 +3011,108 @@ WebInspector.ElementsTreeUpdater.prototype = {
} }
} }
/**
* @constructor
*/
WebInspector.ElementsTreeUpdater.UpdateInfo = function()
{
}
WebInspector.ElementsTreeUpdater.UpdateInfo.prototype = {
/**
* @param {string} attrName
*/
attributeModified: function(attrName)
{
if (this._removedAttributes && this._removedAttributes.has(attrName))
this._removedAttributes.delete(attrName);
if (!this._modifiedAttributes)
this._modifiedAttributes = /** @type {!Set.<string>} */ (new Set());
this._modifiedAttributes.add(attrName);
},
/**
* @param {string} attrName
*/
attributeRemoved: function(attrName)
{
if (this._modifiedAttributes && this._modifiedAttributes.has(attrName))
this._modifiedAttributes.delete(attrName);
if (!this._removedAttributes)
this._removedAttributes = /** @type {!Set.<string>} */ (new Set());
this._removedAttributes.add(attrName);
},
/**
* @param {!WebInspector.DOMNode} node
*/
nodeInserted: function(node)
{
if (!this._insertedNodes)
this._insertedNodes = /** @type {!Set.<!WebInspector.DOMNode>} */ (new Set());
this._insertedNodes.add(/** @type {!WebInspector.DOMNode} */ (node));
},
charDataModified: function()
{
this._charDataModified = true;
},
childrenModified: function()
{
this._hasChangedChildren = true;
},
/**
* @param {string} attributeName
* @return {boolean}
*/
isAttributeModified: function(attributeName)
{
return this._modifiedAttributes && this._modifiedAttributes.has(attributeName);
},
/**
* @return {boolean}
*/
hasRemovedAttributes: function()
{
return !!this._removedAttributes && !!this._removedAttributes.size;
},
/**
* @return {boolean}
*/
hasInsertedNodes: function()
{
return !!this._insertedNodes && !!this._insertedNodes.size;
},
/**
* @return {boolean}
*/
isCharDataModified: function()
{
return !!this._charDataModified;
},
/**
* @return {boolean}
*/
isNodeInserted: function(node)
{
return !!this._insertedNodes && this._insertedNodes.has(node);
},
/**
* @return {boolean}
*/
hasChangedChildren: function()
{
return !!this._hasChangedChildren;
}
}
/** /**
* @constructor * @constructor
* @implements {WebInspector.Renderer} * @implements {WebInspector.Renderer}
......
...@@ -271,3 +271,16 @@ body.inactive button.text-button, .text-button:disabled { ...@@ -271,3 +271,16 @@ body.inactive button.text-button, .text-button:disabled {
.outline-disclosure .nowrap li { .outline-disclosure .nowrap li {
word-wrap: normal; word-wrap: normal;
} }
/* DOM update highlight */
@-webkit-keyframes dom-update-highlight-animation {
from { background-color: rgb(255, 242, 114); }
90% { background-color: rgb(255, 242, 114); }
to { background-color: inherit; }
}
.dom-update-highlight {
-webkit-animation: dom-update-highlight-animation 2s 1;
color: red !important;
}
...@@ -94,6 +94,13 @@ ...@@ -94,6 +94,13 @@
"title": "Show rulers", "title": "Show rulers",
"settingName": "showMetricsRulers", "settingName": "showMetricsRulers",
"settingType": "checkbox" "settingType": "checkbox"
},
{
"type": "ui-setting",
"section": "Elements",
"title": "Highlight DOM updates",
"settingName": "highlightDOMUpdates",
"settingType": "checkbox"
} }
], ],
"dependencies": [ "dependencies": [
......
...@@ -462,6 +462,7 @@ function Symbol(description) {} ...@@ -462,6 +462,7 @@ function Symbol(description) {}
/** /**
* @interface * @interface
* @extends $jscomp.Iterable.<T>
* @template T * @template T
*/ */
var Iterator = function() { } var Iterator = function() { }
...@@ -470,14 +471,20 @@ Iterator.prototype = { ...@@ -470,14 +471,20 @@ Iterator.prototype = {
/** /**
* @return {{done: boolean, value: (T|undefined)}} * @return {{done: boolean, value: (T|undefined)}}
*/ */
next: function() { } next: function() { },
// FIXME: This should be removed once transpilation is not required for closure compiler ES6
$$iterator: function() { }
} }
// FIXME: $jscomp.Iterable hack below should be removed once transpilation is not required for closure compiler ES6
/** /**
* @constructor * @constructor
* @implements $jscomp.Iterable.<!Array.<K|V>>
* @param {!Array.<!Array.<K|V>>|!Iterator.<!Array.<K|V>>=} iterable
* @template K, V * @template K, V
*/ */
var Map = function() { } var Map = function(iterable) { }
Map.prototype = { Map.prototype = {
/** /**
...@@ -502,6 +509,11 @@ Map.prototype = { ...@@ -502,6 +509,11 @@ Map.prototype = {
*/ */
values: function() { }, values: function() { },
/**
* @return {!Array.<!Array.<K|V>>}
*/
entries: function() { },
/** /**
* @param {K} key * @param {K} key
* @return {V} * @return {V}
...@@ -519,7 +531,10 @@ Map.prototype = { ...@@ -519,7 +531,10 @@ Map.prototype = {
/** /**
* @return {number} * @return {number}
*/ */
get size() { } get size() { },
// FIXME: This should be removed once transpilation is not required for closure compiler ES6
$$iterator: function() { }
} }
// FIXME: $jscomp.Iterable hack below should be removed once transpilation is not required for closure compiler ES6 // FIXME: $jscomp.Iterable hack below should be removed once transpilation is not required for closure compiler ES6
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment