Index: Source/devtools/front_end/elements/ElementsTreeOutline.js |
diff --git a/Source/devtools/front_end/elements/ElementsTreeOutline.js b/Source/devtools/front_end/elements/ElementsTreeOutline.js |
index c5e2d6f8f3eff068f09bcb413942338cdb083757..1ad9d011bcb06da51ffef765b0237b8812a7a16f 100644 |
--- a/Source/devtools/front_end/elements/ElementsTreeOutline.js |
+++ b/Source/devtools/front_end/elements/ElementsTreeOutline.js |
@@ -1018,8 +1018,9 @@ WebInspector.ElementsTreeOutline.PseudoStateDecorator.prototype = { |
* @extends {TreeElement} |
* @param {!WebInspector.DOMNode} node |
* @param {boolean=} elementCloseTag |
+ * @param {boolean=} isUpdated |
*/ |
-WebInspector.ElementsTreeElement = function(node, elementCloseTag) |
+WebInspector.ElementsTreeElement = function(node, elementCloseTag, isUpdated) |
{ |
// The title will be updated in onattach. |
TreeElement.call(this, "", node); |
@@ -1030,6 +1031,7 @@ WebInspector.ElementsTreeElement = function(node, elementCloseTag) |
if (this._node.nodeType() == Node.ELEMENT_NODE && !elementCloseTag) |
this._canAddAttributes = true; |
+ this._isUpdated = isUpdated; |
this._searchQuery = null; |
this._expandedChildrenLimit = WebInspector.ElementsTreeElement.InitialChildrenLimit; |
} |
@@ -1151,7 +1153,7 @@ WebInspector.ElementsTreeElement.prototype = { |
this._expandedChildrenLimit = x; |
if (this.treeOutline && !this._updateChildrenInProgress) |
- this._updateChildren(true, this.children); |
+ this._updateChildren(true, undefined, this.children); |
}, |
get expandedChildCount() |
@@ -1179,7 +1181,7 @@ WebInspector.ElementsTreeElement.prototype = { |
if (index >= this.expandedChildrenLimit) { |
this._expandedChildrenLimit = index + 1; |
- this._updateChildren(true, this.children); |
+ this._updateChildren(true, undefined, this.children); |
} |
// Whether index-th child is visible in the children tree |
@@ -1244,24 +1246,26 @@ WebInspector.ElementsTreeElement.prototype = { |
/** |
* @param {boolean=} fullRefresh |
+ * @param {!WebInspector.ElementsTreeUpdater.Record=} updates |
*/ |
- updateChildren: function(fullRefresh) |
+ updateChildren: function(fullRefresh, updates) |
{ |
if (!this.hasChildren) |
return; |
console.assert(!this._elementCloseTag); |
- this._node.getChildNodes(this._updateChildren.bind(this, fullRefresh || false)); |
+ this._node.getChildNodes(this._updateChildren.bind(this, fullRefresh || false, updates)); |
}, |
/** |
* @param {!WebInspector.DOMNode} child |
* @param {number} index |
* @param {boolean=} closingTag |
+ * @param {boolean=} isUpdated |
* @return {!WebInspector.ElementsTreeElement} |
*/ |
- insertChildElement: function(child, index, closingTag) |
+ insertChildElement: function(child, index, closingTag, isUpdated) |
{ |
- var newElement = new WebInspector.ElementsTreeElement(child, closingTag); |
+ var newElement = new WebInspector.ElementsTreeElement(child, closingTag, isUpdated); |
newElement.selectable = this.treeOutline._selectEnabled; |
this.insertChild(newElement, index); |
return newElement; |
@@ -1278,9 +1282,10 @@ WebInspector.ElementsTreeElement.prototype = { |
/** |
* @param {boolean} fullRefresh |
+ * @param {!WebInspector.ElementsTreeUpdater.Record|undefined} childUpdates |
* @param {?Array.<!WebInspector.DOMNode>} children |
*/ |
- _updateChildren: function(fullRefresh, children) |
+ _updateChildren: function(fullRefresh, childUpdates, children) |
{ |
if (!children || this._updateChildrenInProgress || !this.treeOutline._visible) |
return; |
@@ -1327,7 +1332,7 @@ WebInspector.ElementsTreeElement.prototype = { |
} else { |
// No existing element found, insert a new element. |
if (treeChildIndex < this.expandedChildrenLimit) { |
- var newElement = this.insertChildElement(child, treeChildIndex); |
+ var newElement = this.insertChildElement(child, treeChildIndex, false, !!childUpdates && childUpdates.isNodeInserted(child)); |
pfeldman
2014/11/07 12:54:15
Can you do something like:
if (childUpdates.isNod
apavlov
2014/11/10 09:52:56
Done something along these lines with mыrging...
|
if (child === selectedNode) |
elementToSelect = newElement; |
if (this.expandedChildCount > this.expandedChildrenLimit) |
@@ -1360,7 +1365,7 @@ WebInspector.ElementsTreeElement.prototype = { |
} |
var elementToSelect = updateChildrenOfNode.call(this); |
- this.updateTitle(); |
+ this.updateTitle(false, childUpdates); |
this._adjustCollapsedRange(); |
var lastChild = this.children[this.children.length - 1]; |
@@ -2162,14 +2167,16 @@ WebInspector.ElementsTreeElement.prototype = { |
/** |
* @param {boolean=} onlySearchQueryChanged |
+ * @param {!WebInspector.ElementsTreeUpdater.Record=} updates |
*/ |
- updateTitle: function(onlySearchQueryChanged) |
+ updateTitle: function(onlySearchQueryChanged, updates) |
{ |
// If we are editing, return early to prevent canceling the edit. |
// After editing is committed updateTitle will be called. |
if (this._editing) |
return; |
+ this._updates = updates; |
if (onlySearchQueryChanged) { |
if (this._highlightResult) |
this._updateSearchHighlight(false); |
@@ -2185,6 +2192,7 @@ WebInspector.ElementsTreeElement.prototype = { |
delete this._highlightResult; |
} |
+ delete this._isUpdated; |
delete this.selectionElement; |
if (this.selected) |
this.updateSelection(); |
@@ -2293,6 +2301,9 @@ WebInspector.ElementsTreeElement.prototype = { |
var attrValueElement = attrSpanElement.createChild("span", "webkit-html-attribute-value"); |
+ if (this._updates && this._updates.isAttributeModified(name)) |
+ WebInspector.runCSSAnimationOnce(hasText ? attrValueElement : attrNameElement, "dom-update-highlight"); |
+ |
/** |
* @this {WebInspector.ElementsTreeElement} |
* @param {string} value |
@@ -2364,14 +2375,19 @@ WebInspector.ElementsTreeElement.prototype = { |
tagElement.createTextChild("<"); |
var tagNameElement = tagElement.createChild("span", isClosingTag ? "" : "webkit-html-tag-name"); |
tagNameElement.textContent = (isClosingTag ? "/" : "") + tagName; |
- if (!isClosingTag && node.hasAttributes()) { |
- var attributes = node.attributes(); |
- for (var i = 0; i < attributes.length; ++i) { |
- var attr = attributes[i]; |
- tagElement.createTextChild(" "); |
- this._buildAttributeDOM(tagElement, attr.name, attr.value, false, node, linkify); |
+ if (!isClosingTag) { |
+ if (node.hasAttributes()) { |
+ var attributes = node.attributes(); |
+ for (var i = 0; i < attributes.length; ++i) { |
+ var attr = attributes[i]; |
+ tagElement.createTextChild(" "); |
+ this._buildAttributeDOM(tagElement, attr.name, attr.value, false, node, linkify); |
+ } |
} |
+ if (this._isUpdated || (this._updates && (this._updates.hasRemovedAttributes() || this._updates.hasChangedChildren()))) |
+ WebInspector.runCSSAnimationOnce(tagNameElement, "dom-update-highlight"); |
} |
+ |
tagElement.createTextChild(">"); |
parentElement.createTextChild("\u200B"); |
}, |
@@ -2454,6 +2470,8 @@ WebInspector.ElementsTreeElement.prototype = { |
info.titleDOM.createTextChild("\u200B"); |
this._buildTagDOM(info.titleDOM, tagName, true, false); |
info.hasChildren = false; |
+ if (this._updates && (this._updates.isCharDataModified() || this._updates.hasInsertedNodes())) |
+ WebInspector.runCSSAnimationOnce(textNodeElement, "dom-update-highlight"); |
} |
break; |
@@ -2477,6 +2495,8 @@ WebInspector.ElementsTreeElement.prototype = { |
textNodeElement.textContent = result.text; |
WebInspector.highlightRangesWithStyleClass(textNodeElement, result.entityRanges, "webkit-html-entity-value"); |
info.titleDOM.createTextChild("\""); |
+ if (this._isUpdated || (this._updates && this._updates.isCharDataModified())) |
+ WebInspector.runCSSAnimationOnce(textNodeElement, "dom-update-highlight"); |
} |
break; |
@@ -2505,6 +2525,7 @@ WebInspector.ElementsTreeElement.prototype = { |
var cdataElement = info.titleDOM.createChild("span", "webkit-html-text-node"); |
cdataElement.createTextChild("<![CDATA[" + node.nodeValue() + "]]>"); |
break; |
+ |
case Node.DOCUMENT_FRAGMENT_NODE: |
var fragmentElement = info.titleDOM.createChild("span", "webkit-html-fragment"); |
if (node.isInShadowTree()) { |
@@ -2735,18 +2756,18 @@ WebInspector.ElementsTreeUpdater = function(domModel, treeOutline) |
{ |
domModel.addEventListener(WebInspector.DOMModel.Events.NodeInserted, this._nodeInserted, this); |
domModel.addEventListener(WebInspector.DOMModel.Events.NodeRemoved, this._nodeRemoved, this); |
- domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributesUpdated, this); |
- domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributesUpdated, this); |
+ domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributeModified, this); |
+ domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributeRemoved, this); |
domModel.addEventListener(WebInspector.DOMModel.Events.CharacterDataModified, this._characterDataModified, this); |
domModel.addEventListener(WebInspector.DOMModel.Events.DocumentUpdated, this._documentUpdated, this); |
domModel.addEventListener(WebInspector.DOMModel.Events.ChildNodeCountUpdated, this._childNodeCountUpdated, this); |
this._domModel = domModel; |
this._treeOutline = treeOutline; |
- /** @type {!Set.<!WebInspector.DOMNode>} */ |
- this._recentlyModifiedNodes = new Set(); |
- /** @type {!Set.<!WebInspector.DOMNode>} */ |
- this._recentlyModifiedParentNodes = new Set(); |
+ /** @type {!Map.<!WebInspector.DOMNode, !WebInspector.ElementsTreeUpdater.Record>} */ |
+ this._recentlyModifiedNodes = new Map(); |
+ /** @type {!Map.<!WebInspector.DOMNode, !WebInspector.ElementsTreeUpdater.Record>} */ |
+ this._recentlyModifiedParentNodes = new Map(); |
} |
WebInspector.ElementsTreeUpdater.prototype = { |
@@ -2754,8 +2775,8 @@ WebInspector.ElementsTreeUpdater.prototype = { |
{ |
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.AttrModified, this._attributesUpdated, this); |
- this._domModel.removeEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributesUpdated, this); |
+ this._domModel.removeEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributeModified, 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.DocumentUpdated, this._documentUpdated, this); |
this._domModel.removeEventListener(WebInspector.DOMModel.Events.ChildNodeCountUpdated, this._childNodeCountUpdated, this); |
@@ -2763,12 +2784,19 @@ WebInspector.ElementsTreeUpdater.prototype = { |
/** |
* @param {?WebInspector.DOMNode} parentNode |
+ * @param {!WebInspector.ElementsTreeUpdater.Record.ChangeType} changeType |
+ * @param {string|!WebInspector.DOMNode=} relatedTarget |
*/ |
- _parentNodeModified: function(parentNode) |
+ _parentNodeModified: function(parentNode, changeType, relatedTarget) |
{ |
if (!parentNode) |
return; |
- this._recentlyModifiedParentNodes.add(parentNode); |
+ var record = this._recentlyModifiedParentNodes.get(parentNode); |
+ if (!record) { |
+ record = new WebInspector.ElementsTreeUpdater.Record(); |
+ this._recentlyModifiedParentNodes.set(parentNode, record); |
+ } |
+ record.merge(parentNode, changeType, relatedTarget); |
var treeElement = this._treeOutline.findTreeElement(parentNode); |
if (treeElement) { |
@@ -2776,7 +2804,7 @@ WebInspector.ElementsTreeUpdater.prototype = { |
var oldShowInlineText = treeElement._showInlineText(); |
treeElement._updateHasChildren(); |
if (treeElement.hasChildren !== oldHasChildren || oldShowInlineText || treeElement._showInlineText()) |
- this._nodeModified(parentNode); |
+ this._nodeModified(parentNode, changeType, relatedTarget); |
} |
if (this._treeOutline._visible) |
@@ -2785,10 +2813,17 @@ WebInspector.ElementsTreeUpdater.prototype = { |
/** |
* @param {!WebInspector.DOMNode} node |
+ * @param {!WebInspector.ElementsTreeUpdater.Record.ChangeType} changeType |
+ * @param {string|!WebInspector.DOMNode=} relatedTarget |
*/ |
- _nodeModified: function(node) |
+ _nodeModified: function(node, changeType, relatedTarget) |
{ |
- this._recentlyModifiedNodes.add(node); |
+ var record = this._recentlyModifiedNodes.get(node); |
+ if (!record) { |
+ record = new WebInspector.ElementsTreeUpdater.Record(); |
+ this._recentlyModifiedNodes.set(node, record); |
+ } |
+ record.merge(node, changeType, relatedTarget); |
if (this._treeOutline._visible) |
this._updateModifiedNodesSoon(); |
}, |
@@ -2811,10 +2846,19 @@ WebInspector.ElementsTreeUpdater.prototype = { |
/** |
* @param {!WebInspector.Event} event |
*/ |
- _attributesUpdated: function(event) |
+ _attributeModified: function(event) |
{ |
var node = /** @type {!WebInspector.DOMNode} */ (event.data.node); |
- this._nodeModified(node); |
+ this._nodeModified(node, WebInspector.ElementsTreeUpdater.Record.ChangeType.AttrModified, event.data.name); |
pfeldman
2014/11/07 12:54:15
Lets inline record creation here.
apavlov
2014/11/10 09:52:56
This will result in one record per single DOM upda
|
+ }, |
+ |
+ /** |
+ * @param {!WebInspector.Event} event |
+ */ |
+ _attributeRemoved: function(event) |
+ { |
+ var node = /** @type {!WebInspector.DOMNode} */ (event.data.node); |
+ this._nodeModified(node, WebInspector.ElementsTreeUpdater.Record.ChangeType.AttrRemoved, event.data.name); |
}, |
/** |
@@ -2823,8 +2867,8 @@ WebInspector.ElementsTreeUpdater.prototype = { |
_characterDataModified: function(event) |
{ |
var node = /** @type {!WebInspector.DOMNode} */ (event.data); |
- this._parentNodeModified(node.parentNode); |
- this._nodeModified(node); |
+ this._parentNodeModified(node.parentNode, WebInspector.ElementsTreeUpdater.Record.ChangeType.CharDataModified, node); |
+ this._nodeModified(node, WebInspector.ElementsTreeUpdater.Record.ChangeType.CharDataModified); |
}, |
/** |
@@ -2833,7 +2877,7 @@ WebInspector.ElementsTreeUpdater.prototype = { |
_nodeInserted: function(event) |
{ |
var node = /** @type {!WebInspector.DOMNode} */ (event.data); |
- this._parentNodeModified(node.parentNode); |
+ this._parentNodeModified(node.parentNode, WebInspector.ElementsTreeUpdater.Record.ChangeType.NodeInserted, node); |
}, |
/** |
@@ -2844,7 +2888,7 @@ WebInspector.ElementsTreeUpdater.prototype = { |
var node = /** @type {!WebInspector.DOMNode} */ (event.data.node); |
var parentNode = /** @type {!WebInspector.DOMNode} */ (event.data.parent); |
this._treeOutline._resetClipboardIfNeeded(node); |
- this._parentNodeModified(parentNode); |
+ this._parentNodeModified(parentNode, WebInspector.ElementsTreeUpdater.Record.ChangeType.ChildNodeRemoved); |
}, |
/** |
@@ -2853,7 +2897,7 @@ WebInspector.ElementsTreeUpdater.prototype = { |
_childNodeCountUpdated: function(event) |
{ |
var node = /** @type {!WebInspector.DOMNode} */ (event.data); |
- this._parentNodeModified(node); |
+ this._parentNodeModified(node, WebInspector.ElementsTreeUpdater.Record.ChangeType.ChildNodeCountUpdated); |
pfeldman
2014/11/07 12:54:15
Lets not use this signal.
apavlov
2014/11/10 09:52:56
Done.
|
}, |
_updateModifiedNodesSoon: function() |
@@ -2870,7 +2914,7 @@ WebInspector.ElementsTreeUpdater.prototype = { |
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; |
if (hidePanelWhileUpdating) { |
var treeOutlineContainerElement = this._treeOutline.element.parentNode; |
@@ -2882,18 +2926,19 @@ WebInspector.ElementsTreeUpdater.prototype = { |
// Document's children have changed, perform total update. |
this._treeOutline.update(); |
} else { |
- var nodes = this._recentlyModifiedNodes.valuesArray(); |
+ var highlightDOMUpdates = WebInspector.settings.highlightDOMUpdates.get(); |
+ var nodes = this._recentlyModifiedNodes.keysArray(); |
for (var i = 0, size = nodes.length; i < size; ++i) { |
var nodeItem = this._treeOutline.findTreeElement(nodes[i]); |
if (nodeItem) |
- nodeItem.updateTitle(); |
+ nodeItem.updateTitle(false, highlightDOMUpdates ? this._recentlyModifiedNodes.get(nodes[i]) : undefined); |
pfeldman
2014/11/07 12:54:15
updateTitle can reach out for it on its own
apavlov
2014/11/10 09:52:56
Done.
|
} |
- var parentNodes = this._recentlyModifiedParentNodes.valuesArray(); |
+ var parentNodes = this._recentlyModifiedParentNodes.keysArray(); |
for (var i = 0, size = parentNodes.length; i < size; ++i) { |
var parentNodeItem = this._treeOutline.findTreeElement(parentNodes[i]); |
if (parentNodeItem && parentNodeItem.populated) |
- parentNodeItem.updateChildren(); |
+ parentNodeItem.updateChildren(false, highlightDOMUpdates ? this._recentlyModifiedParentNodes.get(parentNodes[i]) : undefined); |
pfeldman
2014/11/07 12:54:16
ditto
apavlov
2014/11/10 09:52:56
Done.
|
} |
} |
@@ -2921,6 +2966,122 @@ WebInspector.ElementsTreeUpdater.prototype = { |
/** |
* @constructor |
+ */ |
+WebInspector.ElementsTreeUpdater.Record = function() |
+{ |
+ this._removedAttributeCount = 0; |
+} |
+ |
+WebInspector.ElementsTreeUpdater.Record.prototype = { |
pfeldman
2014/11/07 12:54:15
Since you merge those, I'd call it UpdateInfo
apavlov
2014/11/10 09:52:56
Done.
|
+ /** |
+ * @param {!WebInspector.DOMNode} node |
+ * @param {!WebInspector.ElementsTreeUpdater.Record.ChangeType} changeType |
+ * @param {!WebInspector.DOMNode|string|undefined} relatedTarget |
+ */ |
+ merge: function(node, changeType, relatedTarget) |
+ { |
+ var attrName; |
+ switch (changeType) { |
+ case WebInspector.ElementsTreeUpdater.Record.ChangeType.AttrModified: |
+ attrName = /** @type {string} */ (relatedTarget); |
+ if (this.removedAttributes && this.removedAttributes.has(attrName)) { |
+ this.removedAttributes.delete(attrName); |
+ --this._removedAttributeCount; |
+ } |
+ if (!this.modifiedAttributes) |
pfeldman
2014/11/07 12:54:15
make them all private.
apavlov
2014/11/10 09:52:56
Done.
|
+ this.modifiedAttributes = /** @type {!Set.<string>} */ (new Set()); |
+ this.modifiedAttributes.add(attrName); |
+ break; |
+ case WebInspector.ElementsTreeUpdater.Record.ChangeType.AttrRemoved: |
+ attrName = /** @type {string} */ (relatedTarget); |
+ 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); |
+ ++this._removedAttributeCount; |
+ break; |
+ case WebInspector.ElementsTreeUpdater.Record.ChangeType.NodeInserted: |
+ if (!this.insertedNodes) |
+ this.insertedNodes = /** @type {!Set.<!WebInspector.DOMNode>} */ (new Set()); |
+ this.insertedNodes.add(/** @type {!WebInspector.DOMNode} */ (relatedTarget)); |
+ break; |
+ case WebInspector.ElementsTreeUpdater.Record.ChangeType.CharDataModified: |
+ this._charDataModified = true; |
+ break; |
+ case WebInspector.ElementsTreeUpdater.Record.ChangeType.ChildNodeRemoved: |
+ case WebInspector.ElementsTreeUpdater.Record.ChangeType.ChildNodeCountUpdated: |
+ this._hasChangedChildren = true; |
+ break; |
+ default: |
+ console.error("Invalid change type: " + changeType); |
+ } |
+ }, |
+ |
+ /** |
+ * @param {string} attributeName |
+ * @return {boolean} |
+ */ |
+ isAttributeModified: function(attributeName) |
+ { |
+ return this.modifiedAttributes && this.modifiedAttributes.has(attributeName); |
+ }, |
+ |
+ /** |
+ * @return {boolean} |
+ */ |
+ hasRemovedAttributes: function() |
+ { |
+ return !!this._removedAttributeCount; |
+ }, |
+ |
+ /** |
+ * @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; |
+ } |
+} |
+ |
+/** |
+ * @enum {number} |
+ */ |
+WebInspector.ElementsTreeUpdater.Record.ChangeType = { |
+ AttrModified: 1, |
+ AttrRemoved: 2, |
+ CharDataModified: 3, |
+ NodeInserted: 4, |
+ ChildNodeRemoved: 5, |
+ ChildNodeCountUpdated: 6 |
+} |
+ |
+/** |
+ * @constructor |
* @implements {WebInspector.Renderer} |
*/ |
WebInspector.ElementsTreeOutline.Renderer = function() |