Home | History | Annotate | Download | only in front_end
      1 /*
      2  * Copyright (C) 2009, 2010 Google Inc. All rights reserved.
      3  * Copyright (C) 2009 Joseph Pecoraro
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  *     * Redistributions of source code must retain the above copyright
     10  * notice, this list of conditions and the following disclaimer.
     11  *     * Redistributions in binary form must reproduce the above
     12  * copyright notice, this list of conditions and the following disclaimer
     13  * in the documentation and/or other materials provided with the
     14  * distribution.
     15  *     * Neither the name of Google Inc. nor the names of its
     16  * contributors may be used to endorse or promote products derived from
     17  * this software without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 /**
     33  * @constructor
     34  * @param {WebInspector.DOMAgent} domAgent
     35  * @param {?WebInspector.DOMDocument} doc
     36  * @param {boolean} isInShadowTree
     37  * @param {DOMAgent.Node} payload
     38  */
     39 WebInspector.DOMNode = function(domAgent, doc, isInShadowTree, payload) {
     40     this._domAgent = domAgent;
     41     this.ownerDocument = doc;
     42     this._isInShadowTree = isInShadowTree;
     43 
     44     this.id = payload.nodeId;
     45     domAgent._idToDOMNode[this.id] = this;
     46     this._nodeType = payload.nodeType;
     47     this._nodeName = payload.nodeName;
     48     this._localName = payload.localName;
     49     this._nodeValue = payload.nodeValue;
     50 
     51     this._shadowRoots = [];
     52 
     53     this._attributes = [];
     54     this._attributesMap = {};
     55     if (payload.attributes)
     56         this._setAttributesPayload(payload.attributes);
     57 
     58     this._userProperties = {};
     59     this._descendantUserPropertyCounters = {};
     60 
     61     this._childNodeCount = payload.childNodeCount || 0;
     62     this._children = null;
     63 
     64     this.nextSibling = null;
     65     this.previousSibling = null;
     66     this.firstChild = null;
     67     this.lastChild = null;
     68     this.parentNode = null;
     69 
     70     if (payload.shadowRoots) {
     71         for (var i = 0; i < payload.shadowRoots.length; ++i) {
     72             var root = payload.shadowRoots[i];
     73             var node = new WebInspector.DOMNode(this._domAgent, this.ownerDocument, true, root);
     74             this._shadowRoots.push(node);
     75             node.parentNode = this;
     76         }
     77     }
     78 
     79     if (payload.templateContent) {
     80         this._templateContent = new WebInspector.DOMNode(this._domAgent, this.ownerDocument, true, payload.templateContent);
     81         this._templateContent.parentNode = this;
     82     }
     83 
     84     if (payload.children)
     85         this._setChildrenPayload(payload.children);
     86 
     87     if (payload.contentDocument) {
     88         this._contentDocument = new WebInspector.DOMDocument(domAgent, payload.contentDocument);
     89         this._children = [this._contentDocument];
     90         this._renumber();
     91     }
     92 
     93     if (this._nodeType === Node.ELEMENT_NODE) {
     94         // HTML and BODY from internal iframes should not overwrite top-level ones.
     95         if (this.ownerDocument && !this.ownerDocument.documentElement && this._nodeName === "HTML")
     96             this.ownerDocument.documentElement = this;
     97         if (this.ownerDocument && !this.ownerDocument.body && this._nodeName === "BODY")
     98             this.ownerDocument.body = this;
     99     } else if (this._nodeType === Node.DOCUMENT_TYPE_NODE) {
    100         this.publicId = payload.publicId;
    101         this.systemId = payload.systemId;
    102         this.internalSubset = payload.internalSubset;
    103     } else if (this._nodeType === Node.ATTRIBUTE_NODE) {
    104         this.name = payload.name;
    105         this.value = payload.value;
    106     }
    107 }
    108 
    109 /**
    110  * @constructor
    111  * @param {string} value
    112  * @param {boolean} optimized
    113  */
    114 WebInspector.DOMNode.XPathStep = function(value, optimized)
    115 {
    116     this.value = value;
    117     this.optimized = optimized;
    118 }
    119 
    120 WebInspector.DOMNode.XPathStep.prototype = {
    121     toString: function()
    122     {
    123         return this.value;
    124     }
    125 }
    126 
    127 WebInspector.DOMNode.prototype = {
    128     /**
    129      * @return {Array.<WebInspector.DOMNode>}
    130      */
    131     children: function()
    132     {
    133         return this._children ? this._children.slice() : null;
    134     },
    135 
    136     /**
    137      * @return {boolean}
    138      */
    139     hasAttributes: function()
    140     {
    141         return this._attributes.length > 0;
    142     },
    143 
    144     /**
    145      * @return {number}
    146      */
    147     childNodeCount: function()
    148     {
    149         return this._childNodeCount;
    150     },
    151 
    152     /**
    153      * @return {boolean}
    154      */
    155     hasShadowRoots: function()
    156     {
    157         return !!this._shadowRoots.length;
    158     },
    159 
    160     /**
    161      * @return {Array.<WebInspector.DOMNode>}
    162      */
    163     shadowRoots: function()
    164     {
    165         return this._shadowRoots.slice();
    166     },
    167 
    168     /**
    169      * @return {WebInspector.DOMNode}
    170      */
    171     templateContent: function()
    172     {
    173         return this._templateContent;
    174     },
    175 
    176     /**
    177      * @return {number}
    178      */
    179     nodeType: function()
    180     {
    181         return this._nodeType;
    182     },
    183 
    184     /**
    185      * @return {string}
    186      */
    187     nodeName: function()
    188     {
    189         return this._nodeName;
    190     },
    191 
    192     /**
    193      * @return {boolean}
    194      */
    195     isInShadowTree: function()
    196     {
    197         return this._isInShadowTree;
    198     },
    199 
    200     /**
    201      * @return {string}
    202      */
    203     nodeNameInCorrectCase: function()
    204     {
    205         return this.isXMLNode() ? this.nodeName() : this.nodeName().toLowerCase();
    206     },
    207 
    208     /**
    209      * @param {string} name
    210      * @param {function(?Protocol.Error)=} callback
    211      */
    212     setNodeName: function(name, callback)
    213     {
    214         DOMAgent.setNodeName(this.id, name, WebInspector.domAgent._markRevision(this, callback));
    215     },
    216 
    217     /**
    218      * @return {string}
    219      */
    220     localName: function()
    221     {
    222         return this._localName;
    223     },
    224 
    225     /**
    226      * @return {string}
    227      */
    228     nodeValue: function()
    229     {
    230         return this._nodeValue;
    231     },
    232 
    233     /**
    234      * @param {string} value
    235      * @param {function(?Protocol.Error)=} callback
    236      */
    237     setNodeValue: function(value, callback)
    238     {
    239         DOMAgent.setNodeValue(this.id, value, WebInspector.domAgent._markRevision(this, callback));
    240     },
    241 
    242     /**
    243      * @param {string} name
    244      * @return {string}
    245      */
    246     getAttribute: function(name)
    247     {
    248         var attr = this._attributesMap[name];
    249         return attr ? attr.value : undefined;
    250     },
    251 
    252     /**
    253      * @param {string} name
    254      * @param {string} text
    255      * @param {function(?Protocol.Error)=} callback
    256      */
    257     setAttribute: function(name, text, callback)
    258     {
    259         DOMAgent.setAttributesAsText(this.id, text, name, WebInspector.domAgent._markRevision(this, callback));
    260     },
    261 
    262     /**
    263      * @param {string} name
    264      * @param {string} value
    265      * @param {function(?Protocol.Error)=} callback
    266      */
    267     setAttributeValue: function(name, value, callback)
    268     {
    269         DOMAgent.setAttributeValue(this.id, name, value, WebInspector.domAgent._markRevision(this, callback));
    270     },
    271 
    272     /**
    273      * @return {Object}
    274      */
    275     attributes: function()
    276     {
    277         return this._attributes;
    278     },
    279 
    280     /**
    281      * @param {string} name
    282      * @param {function(?Protocol.Error)=} callback
    283      */
    284     removeAttribute: function(name, callback)
    285     {
    286         /**
    287          *  @param {?Protocol.Error} error
    288          */
    289         function mycallback(error)
    290         {
    291             if (!error) {
    292                 delete this._attributesMap[name];
    293                 for (var i = 0;  i < this._attributes.length; ++i) {
    294                     if (this._attributes[i].name === name) {
    295                         this._attributes.splice(i, 1);
    296                         break;
    297                     }
    298                 }
    299             }
    300 
    301             WebInspector.domAgent._markRevision(this, callback)(error);
    302         }
    303         DOMAgent.removeAttribute(this.id, name, mycallback.bind(this));
    304     },
    305 
    306     /**
    307      * @param {function(Array.<WebInspector.DOMNode>)=} callback
    308      */
    309     getChildNodes: function(callback)
    310     {
    311         if (this._children) {
    312             if (callback)
    313                 callback(this.children());
    314             return;
    315         }
    316 
    317         /**
    318          * @this {WebInspector.DOMNode}
    319          * @param {?Protocol.Error} error
    320          */
    321         function mycallback(error)
    322         {
    323             if (!error && callback)
    324                 callback(this.children());
    325         }
    326 
    327         DOMAgent.requestChildNodes(this.id, undefined, mycallback.bind(this));
    328     },
    329 
    330     /**
    331      * @param {number} depth
    332      * @param {function(Array.<WebInspector.DOMNode>)=} callback
    333      */
    334     getSubtree: function(depth, callback)
    335     {
    336         /**
    337          * @this {WebInspector.DOMNode}
    338          * @param {?Protocol.Error} error
    339          */
    340         function mycallback(error)
    341         {
    342             if (callback)
    343                 callback(error ? null : this._children);
    344         }
    345 
    346         DOMAgent.requestChildNodes(this.id, depth, mycallback.bind(this));
    347     },
    348 
    349     /**
    350      * @param {function(?Protocol.Error)=} callback
    351      */
    352     getOuterHTML: function(callback)
    353     {
    354         DOMAgent.getOuterHTML(this.id, callback);
    355     },
    356 
    357     /**
    358      * @param {string} html
    359      * @param {function(?Protocol.Error)=} callback
    360      */
    361     setOuterHTML: function(html, callback)
    362     {
    363         DOMAgent.setOuterHTML(this.id, html, WebInspector.domAgent._markRevision(this, callback));
    364     },
    365 
    366     /**
    367      * @param {function(?Protocol.Error)=} callback
    368      */
    369     removeNode: function(callback)
    370     {
    371         DOMAgent.removeNode(this.id, WebInspector.domAgent._markRevision(this, callback));
    372     },
    373 
    374     copyNode: function()
    375     {
    376         function copy(error, text)
    377         {
    378             if (!error)
    379                 InspectorFrontendHost.copyText(text);
    380         }
    381         DOMAgent.getOuterHTML(this.id, copy);
    382     },
    383 
    384     /**
    385      * @param {boolean} optimized
    386      */
    387     copyXPath: function(optimized)
    388     {
    389         InspectorFrontendHost.copyText(this.xPath(optimized));
    390     },
    391 
    392     /**
    393      * @param {string} objectGroupId
    394      * @param {function(?Protocol.Error)=} callback
    395      */
    396     eventListeners: function(objectGroupId, callback)
    397     {
    398         DOMAgent.getEventListenersForNode(this.id, objectGroupId, callback);
    399     },
    400 
    401     /**
    402      * @return {string}
    403      */
    404     path: function()
    405     {
    406         var path = [];
    407         var node = this;
    408         while (node && "index" in node && node._nodeName.length) {
    409             path.push([node.index, node._nodeName]);
    410             node = node.parentNode;
    411         }
    412         path.reverse();
    413         return path.join(",");
    414     },
    415 
    416     /**
    417      * @param {boolean} justSelector
    418      * @return {string}
    419      */
    420     appropriateSelectorFor: function(justSelector)
    421     {
    422         var lowerCaseName = this.localName() || this.nodeName().toLowerCase();
    423 
    424         var id = this.getAttribute("id");
    425         if (id) {
    426             var selector = "#" + id;
    427             return (justSelector ? selector : lowerCaseName + selector);
    428         }
    429 
    430         var className = this.getAttribute("class");
    431         if (className) {
    432             var selector = "." + className.trim().replace(/\s+/g, ".");
    433             return (justSelector ? selector : lowerCaseName + selector);
    434         }
    435 
    436         if (lowerCaseName === "input" && this.getAttribute("type"))
    437             return lowerCaseName + "[type=\"" + this.getAttribute("type") + "\"]";
    438 
    439         return lowerCaseName;
    440     },
    441 
    442     /**
    443      * @param {WebInspector.DOMNode} node
    444      * @return {boolean}
    445      */
    446     isAncestor: function(node)
    447     {
    448         if (!node)
    449             return false;
    450 
    451         var currentNode = node.parentNode;
    452         while (currentNode) {
    453             if (this === currentNode)
    454                 return true;
    455             currentNode = currentNode.parentNode;
    456         }
    457         return false;
    458     },
    459 
    460     /**
    461      * @param {WebInspector.DOMNode} descendant
    462      * @return {boolean}
    463      */
    464     isDescendant: function(descendant)
    465     {
    466         return descendant !== null && descendant.isAncestor(this);
    467     },
    468 
    469     /**
    470      * @param {Array.<string>} attrs
    471      * @return {boolean}
    472      */
    473     _setAttributesPayload: function(attrs)
    474     {
    475         var attributesChanged = !this._attributes || attrs.length !== this._attributes.length * 2;
    476         var oldAttributesMap = this._attributesMap || {};
    477 
    478         this._attributes = [];
    479         this._attributesMap = {};
    480 
    481         for (var i = 0; i < attrs.length; i += 2) {
    482             var name = attrs[i];
    483             var value = attrs[i + 1];
    484             this._addAttribute(name, value);
    485 
    486             if (attributesChanged)
    487                 continue;
    488 
    489             if (!oldAttributesMap[name] || oldAttributesMap[name].value !== value)
    490               attributesChanged = true;
    491         }
    492         return attributesChanged;
    493     },
    494 
    495     /**
    496      * @param {WebInspector.DOMNode} prev
    497      * @param {DOMAgent.Node} payload
    498      * @return {WebInspector.DOMNode}
    499      */
    500     _insertChild: function(prev, payload)
    501     {
    502         var node = new WebInspector.DOMNode(this._domAgent, this.ownerDocument, this._isInShadowTree, payload);
    503         this._children.splice(this._children.indexOf(prev) + 1, 0, node);
    504         this._renumber();
    505         return node;
    506     },
    507 
    508     /**
    509      * @param {WebInspector.DOMNode} node
    510      */
    511     _removeChild: function(node)
    512     {
    513         this._children.splice(this._children.indexOf(node), 1);
    514         node.parentNode = null;
    515         node._updateChildUserPropertyCountsOnRemoval(this);
    516         this._renumber();
    517     },
    518 
    519     /**
    520      * @param {Array.<DOMAgent.Node>} payloads
    521      */
    522     _setChildrenPayload: function(payloads)
    523     {
    524         // We set children in the constructor.
    525         if (this._contentDocument)
    526             return;
    527 
    528         this._children = [];
    529         for (var i = 0; i < payloads.length; ++i) {
    530             var payload = payloads[i];
    531             var node = new WebInspector.DOMNode(this._domAgent, this.ownerDocument, this._isInShadowTree, payload);
    532             this._children.push(node);
    533         }
    534         this._renumber();
    535     },
    536 
    537     _renumber: function()
    538     {
    539         this._childNodeCount = this._children.length;
    540         if (this._childNodeCount == 0) {
    541             this.firstChild = null;
    542             this.lastChild = null;
    543             return;
    544         }
    545         this.firstChild = this._children[0];
    546         this.lastChild = this._children[this._childNodeCount - 1];
    547         for (var i = 0; i < this._childNodeCount; ++i) {
    548             var child = this._children[i];
    549             child.index = i;
    550             child.nextSibling = i + 1 < this._childNodeCount ? this._children[i + 1] : null;
    551             child.previousSibling = i - 1 >= 0 ? this._children[i - 1] : null;
    552             child.parentNode = this;
    553         }
    554     },
    555 
    556     /**
    557      * @param {string} name
    558      * @param {string} value
    559      */
    560     _addAttribute: function(name, value)
    561     {
    562         var attr = {
    563             name: name,
    564             value: value,
    565             _node: this
    566         };
    567         this._attributesMap[name] = attr;
    568         this._attributes.push(attr);
    569     },
    570 
    571     /**
    572      * @param {string} name
    573      * @param {string} value
    574      */
    575     _setAttribute: function(name, value)
    576     {
    577         var attr = this._attributesMap[name];
    578         if (attr)
    579             attr.value = value;
    580         else
    581             this._addAttribute(name, value);
    582     },
    583 
    584     /**
    585      * @param {string} name
    586      */
    587     _removeAttribute: function(name)
    588     {
    589         var attr = this._attributesMap[name];
    590         if (attr) {
    591             this._attributes.remove(attr);
    592             delete this._attributesMap[name];
    593         }
    594     },
    595 
    596     /**
    597      * @param {WebInspector.DOMNode} targetNode
    598      * @param {?WebInspector.DOMNode} anchorNode
    599      * @param {function(?Protocol.Error)=} callback
    600      */
    601     moveTo: function(targetNode, anchorNode, callback)
    602     {
    603         DOMAgent.moveTo(this.id, targetNode.id, anchorNode ? anchorNode.id : undefined, WebInspector.domAgent._markRevision(this, callback));
    604     },
    605 
    606     /**
    607      * @return {boolean}
    608      */
    609     isXMLNode: function()
    610     {
    611         return !!this.ownerDocument && !!this.ownerDocument.xmlVersion;
    612     },
    613 
    614     /**
    615      * @param {boolean} optimized
    616      * @return {string}
    617      */
    618     xPath: function(optimized)
    619     {
    620         if (this._nodeType === Node.DOCUMENT_NODE)
    621             return "/";
    622 
    623         var steps = [];
    624         var contextNode = this;
    625         while (contextNode) {
    626             var step = contextNode._xPathValue(optimized);
    627             if (!step)
    628                 break; // Error - bail out early.
    629             steps.push(step);
    630             if (step.optimized)
    631                 break;
    632             contextNode = contextNode.parentNode;
    633         }
    634 
    635         steps.reverse();
    636         return (steps.length && steps[0].optimized ? "" : "/") + steps.join("/");
    637     },
    638 
    639     /**
    640      * @param {boolean} optimized
    641      * @return {WebInspector.DOMNode.XPathStep}
    642      */
    643     _xPathValue: function(optimized)
    644     {
    645         var ownValue;
    646         var ownIndex = this._xPathIndex();
    647         if (ownIndex === -1)
    648             return null; // Error.
    649 
    650         switch (this._nodeType) {
    651         case Node.ELEMENT_NODE:
    652             if (optimized && this.getAttribute("id"))
    653                 return new WebInspector.DOMNode.XPathStep("//*[@id=\"" + this.getAttribute("id") + "\"]", true);
    654             ownValue = this._localName;
    655             break;
    656         case Node.ATTRIBUTE_NODE:
    657             ownValue = "@" + this._nodeName;
    658             break;
    659         case Node.TEXT_NODE:
    660         case Node.CDATA_SECTION_NODE:
    661             ownValue = "text()";
    662             break;
    663         case Node.PROCESSING_INSTRUCTION_NODE:
    664             ownValue = "processing-instruction()";
    665             break;
    666         case Node.COMMENT_NODE:
    667             ownValue = "comment()";
    668             break;
    669         case Node.DOCUMENT_NODE:
    670             ownValue = "";
    671             break;
    672         default:
    673             ownValue = "";
    674             break;
    675         }
    676 
    677         if (ownIndex > 0)
    678             ownValue += "[" + ownIndex + "]";
    679 
    680         return new WebInspector.DOMNode.XPathStep(ownValue, this._nodeType === Node.DOCUMENT_NODE);
    681     },
    682 
    683     /**
    684      * @return {number}
    685      */
    686     _xPathIndex: function()
    687     {
    688         // Returns -1 in case of error, 0 if no siblings matching the same expression, <XPath index among the same expression-matching sibling nodes> otherwise.
    689         function areNodesSimilar(left, right)
    690         {
    691             if (left === right)
    692                 return true;
    693 
    694             if (left._nodeType === Node.ELEMENT_NODE && right._nodeType === Node.ELEMENT_NODE)
    695                 return left._localName === right._localName;
    696 
    697             if (left._nodeType === right._nodeType)
    698                 return true;
    699 
    700             // XPath treats CDATA as text nodes.
    701             var leftType = left._nodeType === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : left._nodeType;
    702             var rightType = right._nodeType === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : right._nodeType;
    703             return leftType === rightType;
    704         }
    705 
    706         var siblings = this.parentNode ? this.parentNode._children : null;
    707         if (!siblings)
    708             return 0; // Root node - no siblings.
    709         var hasSameNamedElements;
    710         for (var i = 0; i < siblings.length; ++i) {
    711             if (areNodesSimilar(this, siblings[i]) && siblings[i] !== this) {
    712                 hasSameNamedElements = true;
    713                 break;
    714             }
    715         }
    716         if (!hasSameNamedElements)
    717             return 0;
    718         var ownIndex = 1; // XPath indices start with 1.
    719         for (var i = 0; i < siblings.length; ++i) {
    720             if (areNodesSimilar(this, siblings[i])) {
    721                 if (siblings[i] === this)
    722                     return ownIndex;
    723                 ++ownIndex;
    724             }
    725         }
    726         return -1; // An error occurred: |this| not found in parent's children.
    727     },
    728 
    729     _updateChildUserPropertyCountsOnRemoval: function(parentNode)
    730     {
    731         var result = {};
    732         if (this._userProperties) {
    733             for (var name in this._userProperties)
    734                 result[name] = (result[name] || 0) + 1;
    735         }
    736 
    737         if (this._descendantUserPropertyCounters) {
    738             for (var name in this._descendantUserPropertyCounters) {
    739                 var counter = this._descendantUserPropertyCounters[name];
    740                 result[name] = (result[name] || 0) + counter;
    741             }
    742         }
    743 
    744         for (var name in result)
    745             parentNode._updateDescendantUserPropertyCount(name, -result[name]);
    746     },
    747 
    748     _updateDescendantUserPropertyCount: function(name, delta)
    749     {
    750         if (!this._descendantUserPropertyCounters.hasOwnProperty(name))
    751             this._descendantUserPropertyCounters[name] = 0;
    752         this._descendantUserPropertyCounters[name] += delta;
    753         if (!this._descendantUserPropertyCounters[name])
    754             delete this._descendantUserPropertyCounters[name];
    755         if (this.parentNode)
    756             this.parentNode._updateDescendantUserPropertyCount(name, delta);
    757     },
    758 
    759     setUserProperty: function(name, value)
    760     {
    761         if (value === null) {
    762             this.removeUserProperty(name);
    763             return;
    764         }
    765 
    766         if (this.parentNode && !this._userProperties.hasOwnProperty(name))
    767             this.parentNode._updateDescendantUserPropertyCount(name, 1);
    768 
    769         this._userProperties[name] = value;
    770     },
    771 
    772     removeUserProperty: function(name)
    773     {
    774         if (!this._userProperties.hasOwnProperty(name))
    775             return;
    776 
    777         delete this._userProperties[name];
    778         if (this.parentNode)
    779             this.parentNode._updateDescendantUserPropertyCount(name, -1);
    780     },
    781 
    782     getUserProperty: function(name)
    783     {
    784         return this._userProperties ? this._userProperties[name] : null;
    785     },
    786 
    787     descendantUserPropertyCount: function(name)
    788     {
    789         return this._descendantUserPropertyCounters && this._descendantUserPropertyCounters[name] ? this._descendantUserPropertyCounters[name] : 0;
    790     },
    791 
    792     /**
    793      * @param {string} url
    794      * @return {?string}
    795      */
    796     resolveURL: function(url)
    797     {
    798         if (!url)
    799             return url;
    800         for (var frameOwnerCandidate = this; frameOwnerCandidate; frameOwnerCandidate = frameOwnerCandidate.parentNode) {
    801             if (frameOwnerCandidate.baseURL)
    802                 return WebInspector.ParsedURL.completeURL(frameOwnerCandidate.baseURL, url);
    803         }
    804         return null;
    805     }
    806 }
    807 
    808 /**
    809  * @extends {WebInspector.DOMNode}
    810  * @constructor
    811  * @param {WebInspector.DOMAgent} domAgent
    812  * @param {DOMAgent.Node} payload
    813  */
    814 WebInspector.DOMDocument = function(domAgent, payload)
    815 {
    816     WebInspector.DOMNode.call(this, domAgent, this, false, payload);
    817     this.documentURL = payload.documentURL || "";
    818     this.baseURL = /** @type {string} */ (payload.baseURL);
    819     console.assert(this.baseURL);
    820     this.xmlVersion = payload.xmlVersion;
    821     this._listeners = {};
    822 }
    823 
    824 WebInspector.DOMDocument.prototype = {
    825     __proto__: WebInspector.DOMNode.prototype
    826 }
    827 
    828 /**
    829  * @extends {WebInspector.Object}
    830  * @constructor
    831  */
    832 WebInspector.DOMAgent = function() {
    833     /** @type {Object|undefined} */
    834     this._idToDOMNode = {};
    835     this._document = null;
    836     this._attributeLoadNodeIds = {};
    837     InspectorBackend.registerDOMDispatcher(new WebInspector.DOMDispatcher(this));
    838 }
    839 
    840 WebInspector.DOMAgent.Events = {
    841     AttrModified: "AttrModified",
    842     AttrRemoved: "AttrRemoved",
    843     CharacterDataModified: "CharacterDataModified",
    844     NodeInserted: "NodeInserted",
    845     NodeRemoved: "NodeRemoved",
    846     DocumentUpdated: "DocumentUpdated",
    847     ChildNodeCountUpdated: "ChildNodeCountUpdated",
    848     UndoRedoRequested: "UndoRedoRequested",
    849     UndoRedoCompleted: "UndoRedoCompleted",
    850     InspectNodeRequested: "InspectNodeRequested"
    851 }
    852 
    853 WebInspector.DOMAgent.prototype = {
    854     /**
    855      * @param {function(WebInspector.DOMDocument)=} callback
    856      */
    857     requestDocument: function(callback)
    858     {
    859         if (this._document) {
    860             if (callback)
    861                 callback(this._document);
    862             return;
    863         }
    864 
    865         if (this._pendingDocumentRequestCallbacks) {
    866             this._pendingDocumentRequestCallbacks.push(callback);
    867             return;
    868         }
    869 
    870         this._pendingDocumentRequestCallbacks = [callback];
    871 
    872         /**
    873          * @this {WebInspector.DOMAgent}
    874          * @param {?Protocol.Error} error
    875          * @param {DOMAgent.Node} root
    876          */
    877         function onDocumentAvailable(error, root)
    878         {
    879             if (!error)
    880                 this._setDocument(root);
    881 
    882             for (var i = 0; i < this._pendingDocumentRequestCallbacks.length; ++i) {
    883                 var callback = this._pendingDocumentRequestCallbacks[i];
    884                 if (callback)
    885                     callback(this._document);
    886             }
    887             delete this._pendingDocumentRequestCallbacks;
    888         }
    889 
    890         DOMAgent.getDocument(onDocumentAvailable.bind(this));
    891     },
    892 
    893     /**
    894      * @return {WebInspector.DOMDocument?}
    895      */
    896     existingDocument: function()
    897     {
    898         return this._document;
    899     },
    900 
    901     /**
    902      * @param {RuntimeAgent.RemoteObjectId} objectId
    903      * @param {function(?DOMAgent.NodeId)=} callback
    904      */
    905     pushNodeToFrontend: function(objectId, callback)
    906     {
    907         this._dispatchWhenDocumentAvailable(DOMAgent.requestNode.bind(DOMAgent, objectId), callback);
    908     },
    909 
    910     /**
    911      * @param {string} path
    912      * @param {function(?number)=} callback
    913      */
    914     pushNodeByPathToFrontend: function(path, callback)
    915     {
    916         this._dispatchWhenDocumentAvailable(DOMAgent.pushNodeByPathToFrontend.bind(DOMAgent, path), callback);
    917     },
    918 
    919     /**
    920      * @param {number} backendNodeId
    921      * @param {function(?number)=} callback
    922      */
    923     pushNodeByBackendIdToFrontend: function(backendNodeId, callback)
    924     {
    925         this._dispatchWhenDocumentAvailable(DOMAgent.pushNodeByBackendIdToFrontend.bind(DOMAgent, backendNodeId), callback);
    926     },
    927 
    928     /**
    929      * @param {function(T)=} callback
    930      * @return {function(?Protocol.Error, T=)|undefined}
    931      * @template T
    932      */
    933     _wrapClientCallback: function(callback)
    934     {
    935         if (!callback)
    936             return;
    937         /**
    938          * @param {?Protocol.Error} error
    939          * @param {*=} result
    940          */
    941         return function(error, result)
    942         {
    943             // Caller is responsible for handling the actual error.
    944             callback(error ? null : result);
    945         }
    946     },
    947 
    948     /**
    949      * @param {function(function(?Protocol.Error, T=)=)} func
    950      * @param {function(T)=} callback
    951      * @template T
    952      */
    953     _dispatchWhenDocumentAvailable: function(func, callback)
    954     {
    955         var callbackWrapper = this._wrapClientCallback(callback);
    956 
    957         function onDocumentAvailable()
    958         {
    959             if (this._document)
    960                 func(callbackWrapper);
    961             else {
    962                 if (callbackWrapper)
    963                     callbackWrapper("No document");
    964             }
    965         }
    966         this.requestDocument(onDocumentAvailable.bind(this));
    967     },
    968 
    969     /**
    970      * @param {DOMAgent.NodeId} nodeId
    971      * @param {string} name
    972      * @param {string} value
    973      */
    974     _attributeModified: function(nodeId, name, value)
    975     {
    976         var node = this._idToDOMNode[nodeId];
    977         if (!node)
    978             return;
    979 
    980         node._setAttribute(name, value);
    981         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.AttrModified, { node: node, name: name });
    982     },
    983 
    984     /**
    985      * @param {DOMAgent.NodeId} nodeId
    986      * @param {string} name
    987      */
    988     _attributeRemoved: function(nodeId, name)
    989     {
    990         var node = this._idToDOMNode[nodeId];
    991         if (!node)
    992             return;
    993         node._removeAttribute(name);
    994         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.AttrRemoved, { node: node, name: name });
    995     },
    996 
    997     /**
    998      * @param {Array.<DOMAgent.NodeId>} nodeIds
    999      */
   1000     _inlineStyleInvalidated: function(nodeIds)
   1001     {
   1002         for (var i = 0; i < nodeIds.length; ++i)
   1003             this._attributeLoadNodeIds[nodeIds[i]] = true;
   1004         if ("_loadNodeAttributesTimeout" in this)
   1005             return;
   1006         this._loadNodeAttributesTimeout = setTimeout(this._loadNodeAttributes.bind(this), 0);
   1007     },
   1008 
   1009     _loadNodeAttributes: function()
   1010     {
   1011         /**
   1012          * @this {WebInspector.DOMAgent}
   1013          * @param {DOMAgent.NodeId} nodeId
   1014          * @param {?Protocol.Error} error
   1015          * @param {Array.<string>} attributes
   1016          */
   1017         function callback(nodeId, error, attributes)
   1018         {
   1019             if (error) {
   1020                 // We are calling _loadNodeAttributes asynchronously, it is ok if node is not found.
   1021                 return;
   1022             }
   1023             var node = this._idToDOMNode[nodeId];
   1024             if (node) {
   1025                 if (node._setAttributesPayload(attributes))
   1026                     this.dispatchEventToListeners(WebInspector.DOMAgent.Events.AttrModified, { node: node, name: "style" });
   1027             }
   1028         }
   1029 
   1030         delete this._loadNodeAttributesTimeout;
   1031 
   1032         for (var nodeId in this._attributeLoadNodeIds) {
   1033             var nodeIdAsNumber = parseInt(nodeId, 10);
   1034             DOMAgent.getAttributes(nodeIdAsNumber, callback.bind(this, nodeIdAsNumber));
   1035         }
   1036         this._attributeLoadNodeIds = {};
   1037     },
   1038 
   1039     /**
   1040      * @param {DOMAgent.NodeId} nodeId
   1041      * @param {string} newValue
   1042      */
   1043     _characterDataModified: function(nodeId, newValue)
   1044     {
   1045         var node = this._idToDOMNode[nodeId];
   1046         node._nodeValue = newValue;
   1047         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.CharacterDataModified, node);
   1048     },
   1049 
   1050     /**
   1051      * @param {DOMAgent.NodeId} nodeId
   1052      * @return {WebInspector.DOMNode|undefined}
   1053      */
   1054     nodeForId: function(nodeId)
   1055     {
   1056         return this._idToDOMNode[nodeId];
   1057     },
   1058 
   1059     _documentUpdated: function()
   1060     {
   1061         this._setDocument(null);
   1062     },
   1063 
   1064     /**
   1065      * @param {DOMAgent.Node} payload
   1066      */
   1067     _setDocument: function(payload)
   1068     {
   1069         this._idToDOMNode = {};
   1070         if (payload && "nodeId" in payload)
   1071             this._document = new WebInspector.DOMDocument(this, payload);
   1072         else
   1073             this._document = null;
   1074         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.DocumentUpdated, this._document);
   1075     },
   1076 
   1077     /**
   1078      * @param {DOMAgent.Node} payload
   1079      */
   1080     _setDetachedRoot: function(payload)
   1081     {
   1082         if (payload.nodeName === "#document")
   1083             new WebInspector.DOMDocument(this, payload);
   1084         else
   1085             new WebInspector.DOMNode(this, null, false, payload);
   1086     },
   1087 
   1088     /**
   1089      * @param {DOMAgent.NodeId} parentId
   1090      * @param {Array.<DOMAgent.Node>} payloads
   1091      */
   1092     _setChildNodes: function(parentId, payloads)
   1093     {
   1094         if (!parentId && payloads.length) {
   1095             this._setDetachedRoot(payloads[0]);
   1096             return;
   1097         }
   1098 
   1099         var parent = this._idToDOMNode[parentId];
   1100         parent._setChildrenPayload(payloads);
   1101     },
   1102 
   1103     /**
   1104      * @param {DOMAgent.NodeId} nodeId
   1105      * @param {number} newValue
   1106      */
   1107     _childNodeCountUpdated: function(nodeId, newValue)
   1108     {
   1109         var node = this._idToDOMNode[nodeId];
   1110         node._childNodeCount = newValue;
   1111         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.ChildNodeCountUpdated, node);
   1112     },
   1113 
   1114     /**
   1115      * @param {DOMAgent.NodeId} parentId
   1116      * @param {DOMAgent.NodeId} prevId
   1117      * @param {DOMAgent.Node} payload
   1118      */
   1119     _childNodeInserted: function(parentId, prevId, payload)
   1120     {
   1121         var parent = this._idToDOMNode[parentId];
   1122         var prev = this._idToDOMNode[prevId];
   1123         var node = parent._insertChild(prev, payload);
   1124         this._idToDOMNode[node.id] = node;
   1125         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.NodeInserted, node);
   1126     },
   1127 
   1128     /**
   1129      * @param {DOMAgent.NodeId} parentId
   1130      * @param {DOMAgent.NodeId} nodeId
   1131      */
   1132     _childNodeRemoved: function(parentId, nodeId)
   1133     {
   1134         var parent = this._idToDOMNode[parentId];
   1135         var node = this._idToDOMNode[nodeId];
   1136         parent._removeChild(node);
   1137         this._unbind(node);
   1138         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.NodeRemoved, {node: node, parent: parent});
   1139     },
   1140 
   1141     /**
   1142      * @param {DOMAgent.NodeId} hostId
   1143      * @param {DOMAgent.Node} root
   1144      */
   1145     _shadowRootPushed: function(hostId, root)
   1146     {
   1147         var host = this._idToDOMNode[hostId];
   1148         if (!host)
   1149             return;
   1150         var node = new WebInspector.DOMNode(this, host.ownerDocument, true, root);
   1151         node.parentNode = host;
   1152         this._idToDOMNode[node.id] = node;
   1153         host._shadowRoots.push(node);
   1154         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.NodeInserted, node);
   1155     },
   1156 
   1157     /**
   1158      * @param {DOMAgent.NodeId} hostId
   1159      * @param {DOMAgent.NodeId} rootId
   1160      */
   1161     _shadowRootPopped: function(hostId, rootId)
   1162     {
   1163         var host = this._idToDOMNode[hostId];
   1164         if (!host)
   1165             return;
   1166         var root = this._idToDOMNode[rootId];
   1167         if (!root)
   1168             return;
   1169         host._shadowRoots.remove(root);
   1170         this._unbind(root);
   1171         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.NodeRemoved, {node: root, parent: host});
   1172     },
   1173 
   1174     /**
   1175      * @param {WebInspector.DOMNode} node
   1176      */
   1177     _unbind: function(node)
   1178     {
   1179         delete this._idToDOMNode[node.id];
   1180         for (var i = 0; node._children && i < node._children.length; ++i)
   1181             this._unbind(node._children[i]);
   1182         for (var i = 0; i < node._shadowRoots.length; ++i)
   1183             this._unbind(node._shadowRoots[i]);
   1184         if (node._templateContent)
   1185             this._unbind(node._templateContent);
   1186     },
   1187 
   1188     /**
   1189      * @param {number} nodeId
   1190      */
   1191     inspectElement: function(nodeId)
   1192     {
   1193         var node = this._idToDOMNode[nodeId];
   1194         if (node)
   1195             this.dispatchEventToListeners(WebInspector.DOMAgent.Events.InspectNodeRequested, nodeId);
   1196     },
   1197 
   1198     /**
   1199      * @param {DOMAgent.NodeId} nodeId
   1200      */
   1201     _inspectNodeRequested: function(nodeId)
   1202     {
   1203         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.InspectNodeRequested, nodeId);
   1204     },
   1205 
   1206     /**
   1207      * @param {string} query
   1208      * @param {function(number)} searchCallback
   1209      */
   1210     performSearch: function(query, searchCallback)
   1211     {
   1212         this.cancelSearch();
   1213 
   1214         /**
   1215          * @param {?Protocol.Error} error
   1216          * @param {string} searchId
   1217          * @param {number} resultsCount
   1218          */
   1219         function callback(error, searchId, resultsCount)
   1220         {
   1221             this._searchId = searchId;
   1222             searchCallback(resultsCount);
   1223         }
   1224         DOMAgent.performSearch(query, callback.bind(this));
   1225     },
   1226 
   1227     /**
   1228      * @param {number} index
   1229      * @param {?function(DOMAgent.Node)} callback
   1230      */
   1231     searchResult: function(index, callback)
   1232     {
   1233         if (this._searchId) {
   1234             /**
   1235              * @param {?Protocol.Error} error
   1236              * @param {Array.<number>} nodeIds
   1237              */
   1238             function mycallback(error, nodeIds)
   1239             {
   1240                 if (error) {
   1241                     console.error(error);
   1242                     callback(null);
   1243                     return;
   1244                 }
   1245                 if (nodeIds.length != 1)
   1246                     return;
   1247 
   1248                 callback(this._idToDOMNode[nodeIds[0]]);
   1249             }
   1250             DOMAgent.getSearchResults(this._searchId, index, index + 1, mycallback.bind(this));
   1251         } else
   1252             callback(null);
   1253     },
   1254 
   1255     cancelSearch: function()
   1256     {
   1257         if (this._searchId) {
   1258             DOMAgent.discardSearchResults(this._searchId);
   1259             delete this._searchId;
   1260         }
   1261     },
   1262 
   1263     /**
   1264      * @param {DOMAgent.NodeId} nodeId
   1265      * @param {string} selectors
   1266      * @param {function(?DOMAgent.NodeId)=} callback
   1267      */
   1268     querySelector: function(nodeId, selectors, callback)
   1269     {
   1270         DOMAgent.querySelector(nodeId, selectors, this._wrapClientCallback(callback));
   1271     },
   1272 
   1273     /**
   1274      * @param {DOMAgent.NodeId} nodeId
   1275      * @param {string} selectors
   1276      * @param {function(?Array.<DOMAgent.NodeId>)=} callback
   1277      */
   1278     querySelectorAll: function(nodeId, selectors, callback)
   1279     {
   1280         DOMAgent.querySelectorAll(nodeId, selectors, this._wrapClientCallback(callback));
   1281     },
   1282 
   1283     /**
   1284      * @param {DOMAgent.NodeId=} nodeId
   1285      * @param {string=} mode
   1286      * @param {RuntimeAgent.RemoteObjectId=} objectId
   1287      */
   1288     highlightDOMNode: function(nodeId, mode, objectId)
   1289     {
   1290         if (this._hideDOMNodeHighlightTimeout) {
   1291             clearTimeout(this._hideDOMNodeHighlightTimeout);
   1292             delete this._hideDOMNodeHighlightTimeout;
   1293         }
   1294 
   1295         if (objectId || nodeId)
   1296             DOMAgent.highlightNode(this._buildHighlightConfig(mode), objectId ? undefined : nodeId, objectId);
   1297         else
   1298             DOMAgent.hideHighlight();
   1299     },
   1300 
   1301     hideDOMNodeHighlight: function()
   1302     {
   1303         this.highlightDOMNode(0);
   1304     },
   1305 
   1306     /**
   1307      * @param {DOMAgent.NodeId} nodeId
   1308      */
   1309     highlightDOMNodeForTwoSeconds: function(nodeId)
   1310     {
   1311         this.highlightDOMNode(nodeId);
   1312         this._hideDOMNodeHighlightTimeout = setTimeout(this.hideDOMNodeHighlight.bind(this), 2000);
   1313     },
   1314 
   1315     /**
   1316      * @param {boolean} enabled
   1317      * @param {boolean} inspectShadowDOM
   1318      * @param {function(?Protocol.Error)=} callback
   1319      */
   1320     setInspectModeEnabled: function(enabled, inspectShadowDOM, callback)
   1321     {
   1322         this._dispatchWhenDocumentAvailable(DOMAgent.setInspectModeEnabled.bind(DOMAgent, enabled, inspectShadowDOM, this._buildHighlightConfig()), callback);
   1323     },
   1324 
   1325     /**
   1326      * @param {string=} mode
   1327      */
   1328     _buildHighlightConfig: function(mode)
   1329     {
   1330         mode = mode || "all";
   1331         var highlightConfig = { showInfo: mode === "all", showRulers: WebInspector.settings.showMetricsRulers.get() };
   1332         if (mode === "all" || mode === "content")
   1333             highlightConfig.contentColor = WebInspector.Color.PageHighlight.Content.toProtocolRGBA();
   1334 
   1335         if (mode === "all" || mode === "padding")
   1336             highlightConfig.paddingColor = WebInspector.Color.PageHighlight.Padding.toProtocolRGBA();
   1337 
   1338         if (mode === "all" || mode === "border")
   1339             highlightConfig.borderColor = WebInspector.Color.PageHighlight.Border.toProtocolRGBA();
   1340 
   1341         if (mode === "all" || mode === "margin")
   1342             highlightConfig.marginColor = WebInspector.Color.PageHighlight.Margin.toProtocolRGBA();
   1343 
   1344         if (mode === "all")
   1345             highlightConfig.eventTargetColor = WebInspector.Color.PageHighlight.EventTarget.toProtocolRGBA();
   1346 
   1347         return highlightConfig;
   1348     },
   1349 
   1350     /**
   1351      * @param {WebInspector.DOMNode} node
   1352      * @param {function(?Protocol.Error)=} callback
   1353      * @return {function(?Protocol.Error)}
   1354      */
   1355     _markRevision: function(node, callback)
   1356     {
   1357         function wrapperFunction(error)
   1358         {
   1359             if (!error)
   1360                 this.markUndoableState();
   1361 
   1362             if (callback)
   1363                 callback.apply(this, arguments);
   1364         }
   1365         return wrapperFunction.bind(this);
   1366     },
   1367 
   1368     /**
   1369      * @param {boolean} emulationEnabled
   1370      */
   1371     emulateTouchEventObjects: function(emulationEnabled)
   1372     {
   1373         const injectedFunction = function() {
   1374             const touchEvents = ["ontouchstart", "ontouchend", "ontouchmove", "ontouchcancel"];
   1375             var recepients = [window.__proto__, document.__proto__];
   1376             for (var i = 0; i < touchEvents.length; ++i) {
   1377                 for (var j = 0; j < recepients.length; ++j) {
   1378                     if (!(touchEvents[i] in recepients[j]))
   1379                         Object.defineProperty(recepients[j], touchEvents[i], { value: null, writable: true, configurable: true, enumerable: true });
   1380                 }
   1381             }
   1382         }
   1383 
   1384         if (emulationEnabled && !this._addTouchEventsScriptInjecting) {
   1385             this._addTouchEventsScriptInjecting = true;
   1386             PageAgent.addScriptToEvaluateOnLoad("(" + injectedFunction.toString() + ")()", scriptAddedCallback.bind(this));
   1387         } else {
   1388             if (typeof this._addTouchEventsScriptId !== "undefined") {
   1389                 PageAgent.removeScriptToEvaluateOnLoad(this._addTouchEventsScriptId);
   1390                 delete this._addTouchEventsScriptId;
   1391             }
   1392         }
   1393 
   1394         function scriptAddedCallback(error, scriptId)
   1395         {
   1396             delete this._addTouchEventsScriptInjecting;
   1397             if (error)
   1398                 return;
   1399             this._addTouchEventsScriptId = scriptId;
   1400         }
   1401 
   1402         PageAgent.setTouchEmulationEnabled(emulationEnabled);
   1403     },
   1404 
   1405     markUndoableState: function()
   1406     {
   1407         DOMAgent.markUndoableState();
   1408     },
   1409 
   1410     /**
   1411      * @param {function(?Protocol.Error)=} callback
   1412      */
   1413     undo: function(callback)
   1414     {
   1415         function mycallback(error)
   1416         {
   1417             this.dispatchEventToListeners(WebInspector.DOMAgent.Events.UndoRedoCompleted);
   1418             callback(error);
   1419         }
   1420 
   1421         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.UndoRedoRequested);
   1422         DOMAgent.undo(callback);
   1423     },
   1424 
   1425     /**
   1426      * @param {function(?Protocol.Error)=} callback
   1427      */
   1428     redo: function(callback)
   1429     {
   1430         function mycallback(error)
   1431         {
   1432             this.dispatchEventToListeners(WebInspector.DOMAgent.Events.UndoRedoCompleted);
   1433             callback(error);
   1434         }
   1435 
   1436         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.UndoRedoRequested);
   1437         DOMAgent.redo(callback);
   1438     },
   1439 
   1440     __proto__: WebInspector.Object.prototype
   1441 }
   1442 
   1443 /**
   1444  * @constructor
   1445  * @implements {DOMAgent.Dispatcher}
   1446  * @param {WebInspector.DOMAgent} domAgent
   1447  */
   1448 WebInspector.DOMDispatcher = function(domAgent)
   1449 {
   1450     this._domAgent = domAgent;
   1451 }
   1452 
   1453 WebInspector.DOMDispatcher.prototype = {
   1454     documentUpdated: function()
   1455     {
   1456         this._domAgent._documentUpdated();
   1457     },
   1458 
   1459     /**
   1460      * @param {DOMAgent.NodeId} nodeId
   1461      */
   1462     inspectNodeRequested: function(nodeId)
   1463     {
   1464         this._domAgent._inspectNodeRequested(nodeId);
   1465     },
   1466 
   1467     /**
   1468      * @param {DOMAgent.NodeId} nodeId
   1469      * @param {string} name
   1470      * @param {string} value
   1471      */
   1472     attributeModified: function(nodeId, name, value)
   1473     {
   1474         this._domAgent._attributeModified(nodeId, name, value);
   1475     },
   1476 
   1477     /**
   1478      * @param {DOMAgent.NodeId} nodeId
   1479      * @param {string} name
   1480      */
   1481     attributeRemoved: function(nodeId, name)
   1482     {
   1483         this._domAgent._attributeRemoved(nodeId, name);
   1484     },
   1485 
   1486     /**
   1487      * @param {Array.<DOMAgent.NodeId>} nodeIds
   1488      */
   1489     inlineStyleInvalidated: function(nodeIds)
   1490     {
   1491         this._domAgent._inlineStyleInvalidated(nodeIds);
   1492     },
   1493 
   1494     /**
   1495      * @param {DOMAgent.NodeId} nodeId
   1496      * @param {string} characterData
   1497      */
   1498     characterDataModified: function(nodeId, characterData)
   1499     {
   1500         this._domAgent._characterDataModified(nodeId, characterData);
   1501     },
   1502 
   1503     /**
   1504      * @param {DOMAgent.NodeId} parentId
   1505      * @param {Array.<DOMAgent.Node>} payloads
   1506      */
   1507     setChildNodes: function(parentId, payloads)
   1508     {
   1509         this._domAgent._setChildNodes(parentId, payloads);
   1510     },
   1511 
   1512     /**
   1513      * @param {DOMAgent.NodeId} nodeId
   1514      * @param {number} childNodeCount
   1515      */
   1516     childNodeCountUpdated: function(nodeId, childNodeCount)
   1517     {
   1518         this._domAgent._childNodeCountUpdated(nodeId, childNodeCount);
   1519     },
   1520 
   1521     /**
   1522      * @param {DOMAgent.NodeId} parentNodeId
   1523      * @param {DOMAgent.NodeId} previousNodeId
   1524      * @param {DOMAgent.Node} payload
   1525      */
   1526     childNodeInserted: function(parentNodeId, previousNodeId, payload)
   1527     {
   1528         this._domAgent._childNodeInserted(parentNodeId, previousNodeId, payload);
   1529     },
   1530 
   1531     /**
   1532      * @param {DOMAgent.NodeId} parentNodeId
   1533      * @param {DOMAgent.NodeId} nodeId
   1534      */
   1535     childNodeRemoved: function(parentNodeId, nodeId)
   1536     {
   1537         this._domAgent._childNodeRemoved(parentNodeId, nodeId);
   1538     },
   1539 
   1540     /**
   1541      * @param {DOMAgent.NodeId} hostId
   1542      * @param {DOMAgent.Node} root
   1543      */
   1544     shadowRootPushed: function(hostId, root)
   1545     {
   1546         this._domAgent._shadowRootPushed(hostId, root);
   1547     },
   1548 
   1549     /**
   1550      * @param {DOMAgent.NodeId} hostId
   1551      * @param {DOMAgent.NodeId} rootId
   1552      */
   1553     shadowRootPopped: function(hostId, rootId)
   1554     {
   1555         this._domAgent._shadowRootPopped(hostId, rootId);
   1556     }
   1557 }
   1558 
   1559 /**
   1560  * @type {?WebInspector.DOMAgent}
   1561  */
   1562 WebInspector.domAgent = null;
   1563