Home | History | Annotate | Download | only in sdk
      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  * @extends {WebInspector.TargetAware}
     35  * @param {!WebInspector.DOMModel} domModel
     36  * @param {?WebInspector.DOMDocument} doc
     37  * @param {boolean} isInShadowTree
     38  * @param {!DOMAgent.Node} payload
     39  */
     40 WebInspector.DOMNode = function(domModel, doc, isInShadowTree, payload) {
     41     WebInspector.TargetAware.call(this, domModel.target());
     42     this._domModel = domModel;
     43     this._agent = domModel._agent;
     44     this.ownerDocument = doc;
     45     this._isInShadowTree = isInShadowTree;
     46 
     47     this.id = payload.nodeId;
     48     domModel._idToDOMNode[this.id] = this;
     49     this._nodeType = payload.nodeType;
     50     this._nodeName = payload.nodeName;
     51     this._localName = payload.localName;
     52     this._nodeValue = payload.nodeValue;
     53     this._pseudoType = payload.pseudoType;
     54     this._shadowRootType = payload.shadowRootType;
     55     this._frameId = payload.frameId || null;
     56 
     57     this._shadowRoots = [];
     58 
     59     this._attributes = [];
     60     this._attributesMap = {};
     61     if (payload.attributes)
     62         this._setAttributesPayload(payload.attributes);
     63 
     64     this._userProperties = {};
     65     this._descendantUserPropertyCounters = {};
     66 
     67     this._childNodeCount = payload.childNodeCount || 0;
     68     this._children = null;
     69 
     70     this.nextSibling = null;
     71     this.previousSibling = null;
     72     this.firstChild = null;
     73     this.lastChild = null;
     74     this.parentNode = null;
     75 
     76     if (payload.shadowRoots) {
     77         for (var i = 0; i < payload.shadowRoots.length; ++i) {
     78             var root = payload.shadowRoots[i];
     79             var node = new WebInspector.DOMNode(this._domModel, this.ownerDocument, true, root);
     80             this._shadowRoots.push(node);
     81             node.parentNode = this;
     82         }
     83     }
     84 
     85     if (payload.templateContent) {
     86         this._templateContent = new WebInspector.DOMNode(this._domModel, this.ownerDocument, true, payload.templateContent);
     87         this._templateContent.parentNode = this;
     88     }
     89 
     90     if (payload.importedDocument) {
     91         this._importedDocument = new WebInspector.DOMNode(this._domModel, this.ownerDocument, true, payload.importedDocument);
     92         this._importedDocument.parentNode = this;
     93     }
     94 
     95     if (payload.children)
     96         this._setChildrenPayload(payload.children);
     97 
     98     this._setPseudoElements(payload.pseudoElements);
     99 
    100     if (payload.contentDocument) {
    101         this._contentDocument = new WebInspector.DOMDocument(domModel, payload.contentDocument);
    102         this._children = [this._contentDocument];
    103         this._renumber();
    104     }
    105 
    106     if (this._nodeType === Node.ELEMENT_NODE) {
    107         // HTML and BODY from internal iframes should not overwrite top-level ones.
    108         if (this.ownerDocument && !this.ownerDocument.documentElement && this._nodeName === "HTML")
    109             this.ownerDocument.documentElement = this;
    110         if (this.ownerDocument && !this.ownerDocument.body && this._nodeName === "BODY")
    111             this.ownerDocument.body = this;
    112     } else if (this._nodeType === Node.DOCUMENT_TYPE_NODE) {
    113         this.publicId = payload.publicId;
    114         this.systemId = payload.systemId;
    115         this.internalSubset = payload.internalSubset;
    116     } else if (this._nodeType === Node.ATTRIBUTE_NODE) {
    117         this.name = payload.name;
    118         this.value = payload.value;
    119     }
    120 }
    121 
    122 /**
    123  * @enum {string}
    124  */
    125 WebInspector.DOMNode.PseudoElementNames = {
    126     Before: "before",
    127     After: "after"
    128 }
    129 
    130 /**
    131  * @enum {string}
    132  */
    133 WebInspector.DOMNode.ShadowRootTypes = {
    134     UserAgent: "user-agent",
    135     Author: "author"
    136 }
    137 
    138 WebInspector.DOMNode.prototype = {
    139     /**
    140      * @return {!WebInspector.DOMModel}
    141      */
    142     domModel: function()
    143     {
    144         return this._domModel;
    145     },
    146 
    147     /**
    148      * @return {?Array.<!WebInspector.DOMNode>}
    149      */
    150     children: function()
    151     {
    152         return this._children ? this._children.slice() : null;
    153     },
    154 
    155     /**
    156      * @return {boolean}
    157      */
    158     hasAttributes: function()
    159     {
    160         return this._attributes.length > 0;
    161     },
    162 
    163     /**
    164      * @return {number}
    165      */
    166     childNodeCount: function()
    167     {
    168         return this._childNodeCount;
    169     },
    170 
    171     /**
    172      * @return {boolean}
    173      */
    174     hasShadowRoots: function()
    175     {
    176         return !!this._shadowRoots.length;
    177     },
    178 
    179     /**
    180      * @return {!Array.<!WebInspector.DOMNode>}
    181      */
    182     shadowRoots: function()
    183     {
    184         return this._shadowRoots.slice();
    185     },
    186 
    187     /**
    188      * @return {?WebInspector.DOMNode}
    189      */
    190     templateContent: function()
    191     {
    192         return this._templateContent;
    193     },
    194 
    195     /**
    196      * @return {?WebInspector.DOMNode}
    197      */
    198     importedDocument: function()
    199     {
    200         return this._importedDocument;
    201     },
    202 
    203     /**
    204      * @return {number}
    205      */
    206     nodeType: function()
    207     {
    208         return this._nodeType;
    209     },
    210 
    211     /**
    212      * @return {string}
    213      */
    214     nodeName: function()
    215     {
    216         return this._nodeName;
    217     },
    218 
    219     /**
    220      * @return {string|undefined}
    221      */
    222     pseudoType: function()
    223     {
    224         return this._pseudoType;
    225     },
    226 
    227     /**
    228      * @return {boolean}
    229      */
    230     hasPseudoElements: function()
    231     {
    232         return Object.keys(this._pseudoElements).length !== 0;
    233     },
    234 
    235     /**
    236      * @return {!Object.<string, !WebInspector.DOMNode>}
    237      */
    238     pseudoElements: function()
    239     {
    240         return this._pseudoElements;
    241     },
    242 
    243     /**
    244      * @return {boolean}
    245      */
    246     isInShadowTree: function()
    247     {
    248         return this._isInShadowTree;
    249     },
    250 
    251     /**
    252      * @return {?WebInspector.DOMNode}
    253      */
    254     ancestorUserAgentShadowRoot: function()
    255     {
    256         if (!this._isInShadowTree)
    257             return null;
    258 
    259         var current = this;
    260         while (!current.isShadowRoot())
    261             current = current.parentNode;
    262         return current.shadowRootType() === WebInspector.DOMNode.ShadowRootTypes.UserAgent ? current : null;
    263     },
    264 
    265     /**
    266      * @return {boolean}
    267      */
    268     isShadowRoot: function()
    269     {
    270         return !!this._shadowRootType;
    271     },
    272 
    273     /**
    274      * @return {?string}
    275      */
    276     shadowRootType: function()
    277     {
    278         return this._shadowRootType || null;
    279     },
    280 
    281     /**
    282      * @return {string}
    283      */
    284     nodeNameInCorrectCase: function()
    285     {
    286         var shadowRootType = this.shadowRootType();
    287         if (shadowRootType)
    288             return "#shadow-root" + (shadowRootType === WebInspector.DOMNode.ShadowRootTypes.UserAgent ? " (user-agent)" : "");
    289         return this.isXMLNode() ? this.nodeName() : this.nodeName().toLowerCase();
    290     },
    291 
    292     /**
    293      * @param {string} name
    294      * @param {function(?Protocol.Error, number)=} callback
    295      */
    296     setNodeName: function(name, callback)
    297     {
    298         this._agent.setNodeName(this.id, name, this._domModel._markRevision(this, callback));
    299     },
    300 
    301     /**
    302      * @return {string}
    303      */
    304     localName: function()
    305     {
    306         return this._localName;
    307     },
    308 
    309     /**
    310      * @return {string}
    311      */
    312     nodeValue: function()
    313     {
    314         return this._nodeValue;
    315     },
    316 
    317     /**
    318      * @param {string} value
    319      * @param {function(?Protocol.Error)=} callback
    320      */
    321     setNodeValue: function(value, callback)
    322     {
    323         this._agent.setNodeValue(this.id, value, this._domModel._markRevision(this, callback));
    324     },
    325 
    326     /**
    327      * @param {string} name
    328      * @return {string}
    329      */
    330     getAttribute: function(name)
    331     {
    332         var attr = this._attributesMap[name];
    333         return attr ? attr.value : undefined;
    334     },
    335 
    336     /**
    337      * @param {string} name
    338      * @param {string} text
    339      * @param {function(?Protocol.Error)=} callback
    340      */
    341     setAttribute: function(name, text, callback)
    342     {
    343         this._agent.setAttributesAsText(this.id, text, name, this._domModel._markRevision(this, callback));
    344     },
    345 
    346     /**
    347      * @param {string} name
    348      * @param {string} value
    349      * @param {function(?Protocol.Error)=} callback
    350      */
    351     setAttributeValue: function(name, value, callback)
    352     {
    353         this._agent.setAttributeValue(this.id, name, value, this._domModel._markRevision(this, callback));
    354     },
    355 
    356     /**
    357      * @return {!Object}
    358      */
    359     attributes: function()
    360     {
    361         return this._attributes;
    362     },
    363 
    364     /**
    365      * @param {string} name
    366      * @param {function(?Protocol.Error)=} callback
    367      */
    368     removeAttribute: function(name, callback)
    369     {
    370         /**
    371          * @param {?Protocol.Error} error
    372          * @this {WebInspector.DOMNode}
    373          */
    374         function mycallback(error)
    375         {
    376             if (!error) {
    377                 delete this._attributesMap[name];
    378                 for (var i = 0;  i < this._attributes.length; ++i) {
    379                     if (this._attributes[i].name === name) {
    380                         this._attributes.splice(i, 1);
    381                         break;
    382                     }
    383                 }
    384             }
    385 
    386             this._domModel._markRevision(this, callback)(error);
    387         }
    388         this._agent.removeAttribute(this.id, name, mycallback.bind(this));
    389     },
    390 
    391     /**
    392      * @param {function(?Array.<!WebInspector.DOMNode>)=} callback
    393      */
    394     getChildNodes: function(callback)
    395     {
    396         if (this._children) {
    397             if (callback)
    398                 callback(this.children());
    399             return;
    400         }
    401 
    402         /**
    403          * @this {WebInspector.DOMNode}
    404          * @param {?Protocol.Error} error
    405          */
    406         function mycallback(error)
    407         {
    408             if (callback)
    409                 callback(error ? null : this.children());
    410         }
    411 
    412         this._agent.requestChildNodes(this.id, undefined, mycallback.bind(this));
    413     },
    414 
    415     /**
    416      * @param {number} depth
    417      * @param {function(?Array.<!WebInspector.DOMNode>)=} callback
    418      */
    419     getSubtree: function(depth, callback)
    420     {
    421         /**
    422          * @this {WebInspector.DOMNode}
    423          * @param {?Protocol.Error} error
    424          */
    425         function mycallback(error)
    426         {
    427             if (callback)
    428                 callback(error ? null : this._children);
    429         }
    430 
    431         this._agent.requestChildNodes(this.id, depth, mycallback.bind(this));
    432     },
    433 
    434     /**
    435      * @param {function(?Protocol.Error, string)=} callback
    436      */
    437     getOuterHTML: function(callback)
    438     {
    439         this._agent.getOuterHTML(this.id, callback);
    440     },
    441 
    442     /**
    443      * @param {string} html
    444      * @param {function(?Protocol.Error)=} callback
    445      */
    446     setOuterHTML: function(html, callback)
    447     {
    448         this._agent.setOuterHTML(this.id, html, this._domModel._markRevision(this, callback));
    449     },
    450 
    451     /**
    452      * @param {function(?Protocol.Error, !DOMAgent.NodeId=)=} callback
    453      */
    454     removeNode: function(callback)
    455     {
    456         this._agent.removeNode(this.id, this._domModel._markRevision(this, callback));
    457     },
    458 
    459     copyNode: function()
    460     {
    461         function copy(error, text)
    462         {
    463             if (!error)
    464                 InspectorFrontendHost.copyText(text);
    465         }
    466         this._agent.getOuterHTML(this.id, copy);
    467     },
    468 
    469     /**
    470      * @param {string} objectGroupId
    471      * @param {function(?Array.<!WebInspector.DOMModel.EventListener>)} callback
    472      */
    473     eventListeners: function(objectGroupId, callback)
    474     {
    475         var target = this.target();
    476 
    477         /**
    478          * @param {?Protocol.Error} error
    479          * @param {!Array.<!DOMAgent.EventListener>} payloads
    480          */
    481         function mycallback(error, payloads)
    482         {
    483             if (error) {
    484                 callback(null);
    485                 return;
    486             }
    487             callback(payloads.map(function(payload) {
    488                 return new WebInspector.DOMModel.EventListener(target, payload);
    489             }));
    490         }
    491         this._agent.getEventListenersForNode(this.id, objectGroupId, mycallback);
    492     },
    493 
    494     /**
    495      * @return {string}
    496      */
    497     path: function()
    498     {
    499         /**
    500          * @param {?WebInspector.DOMNode} node
    501          */
    502         function canPush(node)
    503         {
    504             return node && ("index" in node || (node.isShadowRoot() && node.parentNode)) && node._nodeName.length;
    505         }
    506 
    507         var path = [];
    508         var node = this;
    509         while (canPush(node)) {
    510             var index = typeof node.index === "number" ? node.index : (node.shadowRootType() === WebInspector.DOMNode.ShadowRootTypes.UserAgent ? "u" : "a");
    511             path.push([index, node._nodeName]);
    512             node = node.parentNode;
    513         }
    514         path.reverse();
    515         return path.join(",");
    516     },
    517 
    518     /**
    519      * @param {!WebInspector.DOMNode} node
    520      * @return {boolean}
    521      */
    522     isAncestor: function(node)
    523     {
    524         if (!node)
    525             return false;
    526 
    527         var currentNode = node.parentNode;
    528         while (currentNode) {
    529             if (this === currentNode)
    530                 return true;
    531             currentNode = currentNode.parentNode;
    532         }
    533         return false;
    534     },
    535 
    536     /**
    537      * @param {!WebInspector.DOMNode} descendant
    538      * @return {boolean}
    539      */
    540     isDescendant: function(descendant)
    541     {
    542         return descendant !== null && descendant.isAncestor(this);
    543     },
    544 
    545     /**
    546      * @return {?PageAgent.FrameId}
    547      */
    548     frameId: function()
    549     {
    550         var node = this;
    551         while (!node._frameId && node.parentNode)
    552             node = node.parentNode;
    553         return node._frameId;
    554     },
    555 
    556     /**
    557      * @param {!Array.<string>} attrs
    558      * @return {boolean}
    559      */
    560     _setAttributesPayload: function(attrs)
    561     {
    562         var attributesChanged = !this._attributes || attrs.length !== this._attributes.length * 2;
    563         var oldAttributesMap = this._attributesMap || {};
    564 
    565         this._attributes = [];
    566         this._attributesMap = {};
    567 
    568         for (var i = 0; i < attrs.length; i += 2) {
    569             var name = attrs[i];
    570             var value = attrs[i + 1];
    571             this._addAttribute(name, value);
    572 
    573             if (attributesChanged)
    574                 continue;
    575 
    576             if (!oldAttributesMap[name] || oldAttributesMap[name].value !== value)
    577               attributesChanged = true;
    578         }
    579         return attributesChanged;
    580     },
    581 
    582     /**
    583      * @param {!WebInspector.DOMNode} prev
    584      * @param {!DOMAgent.Node} payload
    585      * @return {!WebInspector.DOMNode}
    586      */
    587     _insertChild: function(prev, payload)
    588     {
    589         var node = new WebInspector.DOMNode(this._domModel, this.ownerDocument, this._isInShadowTree, payload);
    590         this._children.splice(this._children.indexOf(prev) + 1, 0, node);
    591         this._renumber();
    592         return node;
    593     },
    594 
    595     /**
    596      * @param {!WebInspector.DOMNode} node
    597      */
    598     _removeChild: function(node)
    599     {
    600         if (node.pseudoType()) {
    601             delete this._pseudoElements[node.pseudoType()];
    602         } else {
    603             var shadowRootIndex = this._shadowRoots.indexOf(node);
    604             if (shadowRootIndex !== -1)
    605                 this._shadowRoots.splice(shadowRootIndex, 1);
    606             else
    607                 this._children.splice(this._children.indexOf(node), 1);
    608         }
    609         node.parentNode = null;
    610         node._updateChildUserPropertyCountsOnRemoval(this);
    611         this._renumber();
    612     },
    613 
    614     /**
    615      * @param {!Array.<!DOMAgent.Node>} payloads
    616      */
    617     _setChildrenPayload: function(payloads)
    618     {
    619         // We set children in the constructor.
    620         if (this._contentDocument)
    621             return;
    622 
    623         this._children = [];
    624         for (var i = 0; i < payloads.length; ++i) {
    625             var payload = payloads[i];
    626             var node = new WebInspector.DOMNode(this._domModel, this.ownerDocument, this._isInShadowTree, payload);
    627             this._children.push(node);
    628         }
    629         this._renumber();
    630     },
    631 
    632     /**
    633      * @param {!Array.<!DOMAgent.Node>|undefined} payloads
    634      */
    635     _setPseudoElements: function(payloads)
    636     {
    637         this._pseudoElements = {};
    638         if (!payloads)
    639             return;
    640 
    641         for (var i = 0; i < payloads.length; ++i) {
    642             var node = new WebInspector.DOMNode(this._domModel, this.ownerDocument, this._isInShadowTree, payloads[i]);
    643             node.parentNode = this;
    644             this._pseudoElements[node.pseudoType()] = node;
    645         }
    646     },
    647 
    648     _renumber: function()
    649     {
    650         this._childNodeCount = this._children.length;
    651         if (this._childNodeCount == 0) {
    652             this.firstChild = null;
    653             this.lastChild = null;
    654             return;
    655         }
    656         this.firstChild = this._children[0];
    657         this.lastChild = this._children[this._childNodeCount - 1];
    658         for (var i = 0; i < this._childNodeCount; ++i) {
    659             var child = this._children[i];
    660             child.index = i;
    661             child.nextSibling = i + 1 < this._childNodeCount ? this._children[i + 1] : null;
    662             child.previousSibling = i - 1 >= 0 ? this._children[i - 1] : null;
    663             child.parentNode = this;
    664         }
    665     },
    666 
    667     /**
    668      * @param {string} name
    669      * @param {string} value
    670      */
    671     _addAttribute: function(name, value)
    672     {
    673         var attr = {
    674             name: name,
    675             value: value,
    676             _node: this
    677         };
    678         this._attributesMap[name] = attr;
    679         this._attributes.push(attr);
    680     },
    681 
    682     /**
    683      * @param {string} name
    684      * @param {string} value
    685      */
    686     _setAttribute: function(name, value)
    687     {
    688         var attr = this._attributesMap[name];
    689         if (attr)
    690             attr.value = value;
    691         else
    692             this._addAttribute(name, value);
    693     },
    694 
    695     /**
    696      * @param {string} name
    697      */
    698     _removeAttribute: function(name)
    699     {
    700         var attr = this._attributesMap[name];
    701         if (attr) {
    702             this._attributes.remove(attr);
    703             delete this._attributesMap[name];
    704         }
    705     },
    706 
    707     /**
    708      * @param {!WebInspector.DOMNode} targetNode
    709      * @param {?WebInspector.DOMNode} anchorNode
    710      * @param {function(?Protocol.Error, !DOMAgent.NodeId=)=} callback
    711      */
    712     moveTo: function(targetNode, anchorNode, callback)
    713     {
    714         this._agent.moveTo(this.id, targetNode.id, anchorNode ? anchorNode.id : undefined, this._domModel._markRevision(this, callback));
    715     },
    716 
    717     /**
    718      * @return {boolean}
    719      */
    720     isXMLNode: function()
    721     {
    722         return !!this.ownerDocument && !!this.ownerDocument.xmlVersion;
    723     },
    724 
    725     _updateChildUserPropertyCountsOnRemoval: function(parentNode)
    726     {
    727         var result = {};
    728         if (this._userProperties) {
    729             for (var name in this._userProperties)
    730                 result[name] = (result[name] || 0) + 1;
    731         }
    732 
    733         if (this._descendantUserPropertyCounters) {
    734             for (var name in this._descendantUserPropertyCounters) {
    735                 var counter = this._descendantUserPropertyCounters[name];
    736                 result[name] = (result[name] || 0) + counter;
    737             }
    738         }
    739 
    740         for (var name in result)
    741             parentNode._updateDescendantUserPropertyCount(name, -result[name]);
    742     },
    743 
    744     _updateDescendantUserPropertyCount: function(name, delta)
    745     {
    746         if (!this._descendantUserPropertyCounters.hasOwnProperty(name))
    747             this._descendantUserPropertyCounters[name] = 0;
    748         this._descendantUserPropertyCounters[name] += delta;
    749         if (!this._descendantUserPropertyCounters[name])
    750             delete this._descendantUserPropertyCounters[name];
    751         if (this.parentNode)
    752             this.parentNode._updateDescendantUserPropertyCount(name, delta);
    753     },
    754 
    755     setUserProperty: function(name, value)
    756     {
    757         if (value === null) {
    758             this.removeUserProperty(name);
    759             return;
    760         }
    761 
    762         if (this.parentNode && !this._userProperties.hasOwnProperty(name))
    763             this.parentNode._updateDescendantUserPropertyCount(name, 1);
    764 
    765         this._userProperties[name] = value;
    766     },
    767 
    768     removeUserProperty: function(name)
    769     {
    770         if (!this._userProperties.hasOwnProperty(name))
    771             return;
    772 
    773         delete this._userProperties[name];
    774         if (this.parentNode)
    775             this.parentNode._updateDescendantUserPropertyCount(name, -1);
    776     },
    777 
    778     /**
    779      * @param {string} name
    780      * @return {?T}
    781      * @template T
    782      */
    783     getUserProperty: function(name)
    784     {
    785         return (this._userProperties && this._userProperties[name]) || null;
    786     },
    787 
    788     /**
    789      * @param {string} name
    790      * @return {number}
    791      */
    792     descendantUserPropertyCount: function(name)
    793     {
    794         return this._descendantUserPropertyCounters && this._descendantUserPropertyCounters[name] ? this._descendantUserPropertyCounters[name] : 0;
    795     },
    796 
    797     /**
    798      * @param {string} url
    799      * @return {?string}
    800      */
    801     resolveURL: function(url)
    802     {
    803         if (!url)
    804             return url;
    805         for (var frameOwnerCandidate = this; frameOwnerCandidate; frameOwnerCandidate = frameOwnerCandidate.parentNode) {
    806             if (frameOwnerCandidate.baseURL)
    807                 return WebInspector.ParsedURL.completeURL(frameOwnerCandidate.baseURL, url);
    808         }
    809         return null;
    810     },
    811 
    812     /**
    813      * @param {string=} mode
    814      * @param {!RuntimeAgent.RemoteObjectId=} objectId
    815      */
    816     highlight: function(mode, objectId)
    817     {
    818         this._domModel.highlightDOMNode(this.id, mode, objectId);
    819     },
    820 
    821     highlightForTwoSeconds: function()
    822     {
    823         this._domModel.highlightDOMNodeForTwoSeconds(this.id);
    824     },
    825 
    826     reveal: function()
    827     {
    828         WebInspector.Revealer.reveal(this);
    829     },
    830 
    831     /**
    832      * @param {string=} objectGroup
    833      * @param {function(?WebInspector.RemoteObject)=} callback
    834      */
    835     resolveToObject: function(objectGroup, callback)
    836     {
    837         this._agent.resolveNode(this.id, objectGroup, mycallback.bind(this));
    838 
    839         /**
    840          * @param {?Protocol.Error} error
    841          * @param {!RuntimeAgent.RemoteObject} object
    842          * @this {WebInspector.DOMNode}
    843          */
    844         function mycallback(error, object)
    845         {
    846             if (!callback)
    847                 return;
    848 
    849             if (error || !object)
    850                 callback(null);
    851             else
    852                 callback(this.target().runtimeModel.createRemoteObject(object));
    853         }
    854     },
    855 
    856     /**
    857      * @param {function(?DOMAgent.BoxModel)} callback
    858      */
    859     boxModel: function(callback)
    860     {
    861         this._agent.getBoxModel(this.id, this._domModel._wrapClientCallback(callback));
    862     },
    863 
    864     __proto__: WebInspector.TargetAware.prototype
    865 }
    866 
    867 /**
    868  * @extends {WebInspector.DOMNode}
    869  * @constructor
    870  * @param {!WebInspector.DOMModel} domModel
    871  * @param {!DOMAgent.Node} payload
    872  */
    873 WebInspector.DOMDocument = function(domModel, payload)
    874 {
    875     WebInspector.DOMNode.call(this, domModel, this, false, payload);
    876     this.documentURL = payload.documentURL || "";
    877     this.baseURL = payload.baseURL || "";
    878     this.xmlVersion = payload.xmlVersion;
    879     this._listeners = {};
    880 }
    881 
    882 WebInspector.DOMDocument.prototype = {
    883     __proto__: WebInspector.DOMNode.prototype
    884 }
    885 
    886 /**
    887  * @constructor
    888  * @extends {WebInspector.TargetAwareObject}
    889  * @param {!WebInspector.Target} target
    890  */
    891 WebInspector.DOMModel = function(target) {
    892     WebInspector.TargetAwareObject.call(this, target);
    893 
    894     this._agent = target.domAgent();
    895 
    896     /** @type {!Object.<number, !WebInspector.DOMNode>} */
    897     this._idToDOMNode = {};
    898     /** @type {?WebInspector.DOMDocument} */
    899     this._document = null;
    900     /** @type {!Object.<number, boolean>} */
    901     this._attributeLoadNodeIds = {};
    902     target.registerDOMDispatcher(new WebInspector.DOMDispatcher(this));
    903 
    904     this._defaultHighlighter = new WebInspector.DefaultDOMNodeHighlighter(this._agent);
    905     this._highlighter = this._defaultHighlighter;
    906     this._agent.enable();
    907 }
    908 
    909 WebInspector.DOMModel.Events = {
    910     AttrModified: "AttrModified",
    911     AttrRemoved: "AttrRemoved",
    912     CharacterDataModified: "CharacterDataModified",
    913     NodeInserted: "NodeInserted",
    914     NodeRemoved: "NodeRemoved",
    915     DocumentUpdated: "DocumentUpdated",
    916     ChildNodeCountUpdated: "ChildNodeCountUpdated",
    917     UndoRedoRequested: "UndoRedoRequested",
    918     UndoRedoCompleted: "UndoRedoCompleted",
    919 }
    920 
    921 WebInspector.DOMModel.prototype = {
    922     /**
    923      * @param {function(!WebInspector.DOMDocument)=} callback
    924      */
    925     requestDocument: function(callback)
    926     {
    927         if (this._document) {
    928             if (callback)
    929                 callback(this._document);
    930             return;
    931         }
    932 
    933         if (this._pendingDocumentRequestCallbacks) {
    934             this._pendingDocumentRequestCallbacks.push(callback);
    935             return;
    936         }
    937 
    938         this._pendingDocumentRequestCallbacks = [callback];
    939 
    940         /**
    941          * @this {WebInspector.DOMModel}
    942          * @param {?Protocol.Error} error
    943          * @param {!DOMAgent.Node} root
    944          */
    945         function onDocumentAvailable(error, root)
    946         {
    947             if (!error)
    948                 this._setDocument(root);
    949 
    950             for (var i = 0; i < this._pendingDocumentRequestCallbacks.length; ++i) {
    951                 var callback = this._pendingDocumentRequestCallbacks[i];
    952                 if (callback)
    953                     callback(this._document);
    954             }
    955             delete this._pendingDocumentRequestCallbacks;
    956         }
    957 
    958         this._agent.getDocument(onDocumentAvailable.bind(this));
    959     },
    960 
    961     /**
    962      * @return {?WebInspector.DOMDocument}
    963      */
    964     existingDocument: function()
    965     {
    966         return this._document;
    967     },
    968 
    969     /**
    970      * @param {!RuntimeAgent.RemoteObjectId} objectId
    971      * @param {function(?WebInspector.DOMNode)=} callback
    972      */
    973     pushNodeToFrontend: function(objectId, callback)
    974     {
    975         /**
    976          * @param {?DOMAgent.NodeId} nodeId
    977          * @this {!WebInspector.DOMModel}
    978          */
    979         function mycallback(nodeId)
    980         {
    981             callback(nodeId ? this.nodeForId(nodeId) : null);
    982         }
    983         this._dispatchWhenDocumentAvailable(this._agent.requestNode.bind(this._agent, objectId), mycallback.bind(this));
    984     },
    985 
    986     /**
    987      * @param {string} path
    988      * @param {function(?number)=} callback
    989      */
    990     pushNodeByPathToFrontend: function(path, callback)
    991     {
    992         this._dispatchWhenDocumentAvailable(this._agent.pushNodeByPathToFrontend.bind(this._agent, path), callback);
    993     },
    994 
    995     /**
    996      * @param {!Array.<number>} backendNodeIds
    997      * @param {function(?Array.<number>)=} callback
    998      */
    999     pushNodesByBackendIdsToFrontend: function(backendNodeIds, callback)
   1000     {
   1001         this._dispatchWhenDocumentAvailable(this._agent.pushNodesByBackendIdsToFrontend.bind(this._agent, backendNodeIds), callback);
   1002     },
   1003 
   1004     /**
   1005      * @param {function(!T)=} callback
   1006      * @return {function(?Protocol.Error, !T=)|undefined}
   1007      * @template T
   1008      */
   1009     _wrapClientCallback: function(callback)
   1010     {
   1011         if (!callback)
   1012             return;
   1013         /**
   1014          * @param {?Protocol.Error} error
   1015          * @param {!T=} result
   1016          * @template T
   1017          */
   1018         var wrapper = function(error, result)
   1019         {
   1020             // Caller is responsible for handling the actual error.
   1021             callback(error ? null : result);
   1022         };
   1023         return wrapper;
   1024     },
   1025 
   1026     /**
   1027      * @param {function(function(?Protocol.Error, !T=)=)} func
   1028      * @param {function(!T)=} callback
   1029      * @template T
   1030      */
   1031     _dispatchWhenDocumentAvailable: function(func, callback)
   1032     {
   1033         var callbackWrapper = this._wrapClientCallback(callback);
   1034 
   1035         /**
   1036          * @this {WebInspector.DOMModel}
   1037          */
   1038         function onDocumentAvailable()
   1039         {
   1040             if (this._document)
   1041                 func(callbackWrapper);
   1042             else {
   1043                 if (callbackWrapper)
   1044                     callbackWrapper("No document");
   1045             }
   1046         }
   1047         this.requestDocument(onDocumentAvailable.bind(this));
   1048     },
   1049 
   1050     /**
   1051      * @param {!DOMAgent.NodeId} nodeId
   1052      * @param {string} name
   1053      * @param {string} value
   1054      */
   1055     _attributeModified: function(nodeId, name, value)
   1056     {
   1057         var node = this._idToDOMNode[nodeId];
   1058         if (!node)
   1059             return;
   1060 
   1061         node._setAttribute(name, value);
   1062         this.dispatchEventToListeners(WebInspector.DOMModel.Events.AttrModified, { node: node, name: name });
   1063     },
   1064 
   1065     /**
   1066      * @param {!DOMAgent.NodeId} nodeId
   1067      * @param {string} name
   1068      */
   1069     _attributeRemoved: function(nodeId, name)
   1070     {
   1071         var node = this._idToDOMNode[nodeId];
   1072         if (!node)
   1073             return;
   1074         node._removeAttribute(name);
   1075         this.dispatchEventToListeners(WebInspector.DOMModel.Events.AttrRemoved, { node: node, name: name });
   1076     },
   1077 
   1078     /**
   1079      * @param {!Array.<!DOMAgent.NodeId>} nodeIds
   1080      */
   1081     _inlineStyleInvalidated: function(nodeIds)
   1082     {
   1083         for (var i = 0; i < nodeIds.length; ++i)
   1084             this._attributeLoadNodeIds[nodeIds[i]] = true;
   1085         if ("_loadNodeAttributesTimeout" in this)
   1086             return;
   1087         this._loadNodeAttributesTimeout = setTimeout(this._loadNodeAttributes.bind(this), 20);
   1088     },
   1089 
   1090     _loadNodeAttributes: function()
   1091     {
   1092         /**
   1093          * @this {WebInspector.DOMModel}
   1094          * @param {!DOMAgent.NodeId} nodeId
   1095          * @param {?Protocol.Error} error
   1096          * @param {!Array.<string>} attributes
   1097          */
   1098         function callback(nodeId, error, attributes)
   1099         {
   1100             if (error) {
   1101                 // We are calling _loadNodeAttributes asynchronously, it is ok if node is not found.
   1102                 return;
   1103             }
   1104             var node = this._idToDOMNode[nodeId];
   1105             if (node) {
   1106                 if (node._setAttributesPayload(attributes))
   1107                     this.dispatchEventToListeners(WebInspector.DOMModel.Events.AttrModified, { node: node, name: "style" });
   1108             }
   1109         }
   1110 
   1111         delete this._loadNodeAttributesTimeout;
   1112 
   1113         for (var nodeId in this._attributeLoadNodeIds) {
   1114             var nodeIdAsNumber = parseInt(nodeId, 10);
   1115             this._agent.getAttributes(nodeIdAsNumber, callback.bind(this, nodeIdAsNumber));
   1116         }
   1117         this._attributeLoadNodeIds = {};
   1118     },
   1119 
   1120     /**
   1121      * @param {!DOMAgent.NodeId} nodeId
   1122      * @param {string} newValue
   1123      */
   1124     _characterDataModified: function(nodeId, newValue)
   1125     {
   1126         var node = this._idToDOMNode[nodeId];
   1127         node._nodeValue = newValue;
   1128         this.dispatchEventToListeners(WebInspector.DOMModel.Events.CharacterDataModified, node);
   1129     },
   1130 
   1131     /**
   1132      * @param {!DOMAgent.NodeId} nodeId
   1133      * @return {?WebInspector.DOMNode}
   1134      */
   1135     nodeForId: function(nodeId)
   1136     {
   1137         return this._idToDOMNode[nodeId] || null;
   1138     },
   1139 
   1140     _documentUpdated: function()
   1141     {
   1142         this._setDocument(null);
   1143     },
   1144 
   1145     /**
   1146      * @param {?DOMAgent.Node} payload
   1147      */
   1148     _setDocument: function(payload)
   1149     {
   1150         this._idToDOMNode = {};
   1151         if (payload && "nodeId" in payload)
   1152             this._document = new WebInspector.DOMDocument(this, payload);
   1153         else
   1154             this._document = null;
   1155         this.dispatchEventToListeners(WebInspector.DOMModel.Events.DocumentUpdated, this._document);
   1156     },
   1157 
   1158     /**
   1159      * @param {!DOMAgent.Node} payload
   1160      */
   1161     _setDetachedRoot: function(payload)
   1162     {
   1163         if (payload.nodeName === "#document")
   1164             new WebInspector.DOMDocument(this, payload);
   1165         else
   1166             new WebInspector.DOMNode(this, null, false, payload);
   1167     },
   1168 
   1169     /**
   1170      * @param {!DOMAgent.NodeId} parentId
   1171      * @param {!Array.<!DOMAgent.Node>} payloads
   1172      */
   1173     _setChildNodes: function(parentId, payloads)
   1174     {
   1175         if (!parentId && payloads.length) {
   1176             this._setDetachedRoot(payloads[0]);
   1177             return;
   1178         }
   1179 
   1180         var parent = this._idToDOMNode[parentId];
   1181         parent._setChildrenPayload(payloads);
   1182     },
   1183 
   1184     /**
   1185      * @param {!DOMAgent.NodeId} nodeId
   1186      * @param {number} newValue
   1187      */
   1188     _childNodeCountUpdated: function(nodeId, newValue)
   1189     {
   1190         var node = this._idToDOMNode[nodeId];
   1191         node._childNodeCount = newValue;
   1192         this.dispatchEventToListeners(WebInspector.DOMModel.Events.ChildNodeCountUpdated, node);
   1193     },
   1194 
   1195     /**
   1196      * @param {!DOMAgent.NodeId} parentId
   1197      * @param {!DOMAgent.NodeId} prevId
   1198      * @param {!DOMAgent.Node} payload
   1199      */
   1200     _childNodeInserted: function(parentId, prevId, payload)
   1201     {
   1202         var parent = this._idToDOMNode[parentId];
   1203         var prev = this._idToDOMNode[prevId];
   1204         var node = parent._insertChild(prev, payload);
   1205         this._idToDOMNode[node.id] = node;
   1206         this.dispatchEventToListeners(WebInspector.DOMModel.Events.NodeInserted, node);
   1207     },
   1208 
   1209     /**
   1210      * @param {!DOMAgent.NodeId} parentId
   1211      * @param {!DOMAgent.NodeId} nodeId
   1212      */
   1213     _childNodeRemoved: function(parentId, nodeId)
   1214     {
   1215         var parent = this._idToDOMNode[parentId];
   1216         var node = this._idToDOMNode[nodeId];
   1217         parent._removeChild(node);
   1218         this._unbind(node);
   1219         this.dispatchEventToListeners(WebInspector.DOMModel.Events.NodeRemoved, {node: node, parent: parent});
   1220     },
   1221 
   1222     /**
   1223      * @param {!DOMAgent.NodeId} hostId
   1224      * @param {!DOMAgent.Node} root
   1225      */
   1226     _shadowRootPushed: function(hostId, root)
   1227     {
   1228         var host = this._idToDOMNode[hostId];
   1229         if (!host)
   1230             return;
   1231         var node = new WebInspector.DOMNode(this, host.ownerDocument, true, root);
   1232         node.parentNode = host;
   1233         this._idToDOMNode[node.id] = node;
   1234         host._shadowRoots.push(node);
   1235         this.dispatchEventToListeners(WebInspector.DOMModel.Events.NodeInserted, node);
   1236     },
   1237 
   1238     /**
   1239      * @param {!DOMAgent.NodeId} hostId
   1240      * @param {!DOMAgent.NodeId} rootId
   1241      */
   1242     _shadowRootPopped: function(hostId, rootId)
   1243     {
   1244         var host = this._idToDOMNode[hostId];
   1245         if (!host)
   1246             return;
   1247         var root = this._idToDOMNode[rootId];
   1248         if (!root)
   1249             return;
   1250         host._removeChild(root);
   1251         this._unbind(root);
   1252         this.dispatchEventToListeners(WebInspector.DOMModel.Events.NodeRemoved, {node: root, parent: host});
   1253     },
   1254 
   1255     /**
   1256      * @param {!DOMAgent.NodeId} parentId
   1257      * @param {!DOMAgent.Node} pseudoElement
   1258      */
   1259     _pseudoElementAdded: function(parentId, pseudoElement)
   1260     {
   1261         var parent = this._idToDOMNode[parentId];
   1262         if (!parent)
   1263             return;
   1264         var node = new WebInspector.DOMNode(this, parent.ownerDocument, false, pseudoElement);
   1265         node.parentNode = parent;
   1266         this._idToDOMNode[node.id] = node;
   1267         console.assert(!parent._pseudoElements[node.pseudoType()]);
   1268         parent._pseudoElements[node.pseudoType()] = node;
   1269         this.dispatchEventToListeners(WebInspector.DOMModel.Events.NodeInserted, node);
   1270     },
   1271 
   1272     /**
   1273      * @param {!DOMAgent.NodeId} parentId
   1274      * @param {!DOMAgent.NodeId} pseudoElementId
   1275      */
   1276     _pseudoElementRemoved: function(parentId, pseudoElementId)
   1277     {
   1278         var parent = this._idToDOMNode[parentId];
   1279         if (!parent)
   1280             return;
   1281         var pseudoElement = this._idToDOMNode[pseudoElementId];
   1282         if (!pseudoElement)
   1283             return;
   1284         parent._removeChild(pseudoElement);
   1285         this._unbind(pseudoElement);
   1286         this.dispatchEventToListeners(WebInspector.DOMModel.Events.NodeRemoved, {node: pseudoElement, parent: parent});
   1287     },
   1288 
   1289     /**
   1290      * @param {!WebInspector.DOMNode} node
   1291      */
   1292     _unbind: function(node)
   1293     {
   1294         delete this._idToDOMNode[node.id];
   1295         for (var i = 0; node._children && i < node._children.length; ++i)
   1296             this._unbind(node._children[i]);
   1297         for (var i = 0; i < node._shadowRoots.length; ++i)
   1298             this._unbind(node._shadowRoots[i]);
   1299         var pseudoElements = node.pseudoElements();
   1300         for (var id in pseudoElements)
   1301             this._unbind(pseudoElements[id]);
   1302         if (node._templateContent)
   1303             this._unbind(node._templateContent);
   1304     },
   1305 
   1306     /**
   1307      * @param {!DOMAgent.NodeId} nodeId
   1308      */
   1309     _inspectNodeRequested: function(nodeId)
   1310     {
   1311         WebInspector.Revealer.reveal(this.nodeForId(nodeId))
   1312     },
   1313 
   1314     /**
   1315      * @param {string} query
   1316      * @param {function(number)} searchCallback
   1317      */
   1318     performSearch: function(query, searchCallback)
   1319     {
   1320         this.cancelSearch();
   1321 
   1322         /**
   1323          * @param {?Protocol.Error} error
   1324          * @param {string} searchId
   1325          * @param {number} resultsCount
   1326          * @this {WebInspector.DOMModel}
   1327          */
   1328         function callback(error, searchId, resultsCount)
   1329         {
   1330             this._searchId = searchId;
   1331             searchCallback(resultsCount);
   1332         }
   1333         this._agent.performSearch(query, callback.bind(this));
   1334     },
   1335 
   1336     /**
   1337      * @param {number} index
   1338      * @param {?function(?WebInspector.DOMNode)} callback
   1339      */
   1340     searchResult: function(index, callback)
   1341     {
   1342         if (this._searchId)
   1343             this._agent.getSearchResults(this._searchId, index, index + 1, searchResultsCallback.bind(this));
   1344         else
   1345             callback(null);
   1346 
   1347         /**
   1348          * @param {?Protocol.Error} error
   1349          * @param {!Array.<number>} nodeIds
   1350          * @this {WebInspector.DOMModel}
   1351          */
   1352         function searchResultsCallback(error, nodeIds)
   1353         {
   1354             if (error) {
   1355                 console.error(error);
   1356                 callback(null);
   1357                 return;
   1358             }
   1359             if (nodeIds.length != 1)
   1360                 return;
   1361 
   1362             callback(this.nodeForId(nodeIds[0]));
   1363         }
   1364     },
   1365 
   1366     cancelSearch: function()
   1367     {
   1368         if (this._searchId) {
   1369             this._agent.discardSearchResults(this._searchId);
   1370             delete this._searchId;
   1371         }
   1372     },
   1373 
   1374     /**
   1375      * @param {!DOMAgent.NodeId} nodeId
   1376      * @param {string} selectors
   1377      * @param {function(?DOMAgent.NodeId)=} callback
   1378      */
   1379     querySelector: function(nodeId, selectors, callback)
   1380     {
   1381         this._agent.querySelector(nodeId, selectors, this._wrapClientCallback(callback));
   1382     },
   1383 
   1384     /**
   1385      * @param {!DOMAgent.NodeId} nodeId
   1386      * @param {string} selectors
   1387      * @param {function(!Array.<!DOMAgent.NodeId>=)=} callback
   1388      */
   1389     querySelectorAll: function(nodeId, selectors, callback)
   1390     {
   1391         this._agent.querySelectorAll(nodeId, selectors, this._wrapClientCallback(callback));
   1392     },
   1393 
   1394     /**
   1395      * @param {!DOMAgent.NodeId=} nodeId
   1396      * @param {string=} mode
   1397      * @param {!RuntimeAgent.RemoteObjectId=} objectId
   1398      */
   1399     highlightDOMNode: function(nodeId, mode, objectId)
   1400     {
   1401         this.highlightDOMNodeWithConfig(nodeId, { mode: mode }, objectId);
   1402     },
   1403 
   1404     /**
   1405      * @param {!DOMAgent.NodeId=} nodeId
   1406      * @param {!{mode: (string|undefined), showInfo: (boolean|undefined)}=} config
   1407      * @param {!RuntimeAgent.RemoteObjectId=} objectId
   1408      */
   1409     highlightDOMNodeWithConfig: function(nodeId, config, objectId)
   1410     {
   1411         config = config || { mode: "all", showInfo: undefined };
   1412         if (this._hideDOMNodeHighlightTimeout) {
   1413             clearTimeout(this._hideDOMNodeHighlightTimeout);
   1414             delete this._hideDOMNodeHighlightTimeout;
   1415         }
   1416         var highlightConfig = this._buildHighlightConfig(config.mode);
   1417         if (typeof config.showInfo !== "undefined")
   1418             highlightConfig.showInfo = config.showInfo;
   1419         this._highlighter.highlightDOMNode(this.nodeForId(nodeId || 0), highlightConfig, objectId);
   1420     },
   1421 
   1422     hideDOMNodeHighlight: function()
   1423     {
   1424         this.highlightDOMNode(0);
   1425     },
   1426 
   1427     /**
   1428      * @param {!DOMAgent.NodeId} nodeId
   1429      */
   1430     highlightDOMNodeForTwoSeconds: function(nodeId)
   1431     {
   1432         this.highlightDOMNode(nodeId);
   1433         this._hideDOMNodeHighlightTimeout = setTimeout(this.hideDOMNodeHighlight.bind(this), 2000);
   1434     },
   1435 
   1436     /**
   1437      * @param {boolean} enabled
   1438      * @param {boolean} inspectUAShadowDOM
   1439      * @param {function(?Protocol.Error)=} callback
   1440      */
   1441     setInspectModeEnabled: function(enabled, inspectUAShadowDOM, callback)
   1442     {
   1443         /**
   1444          * @this {WebInspector.DOMModel}
   1445          */
   1446         function onDocumentAvailable()
   1447         {
   1448             this._highlighter.setInspectModeEnabled(enabled, inspectUAShadowDOM, this._buildHighlightConfig(), callback);
   1449         }
   1450         this.requestDocument(onDocumentAvailable.bind(this));
   1451     },
   1452 
   1453     /**
   1454      * @param {string=} mode
   1455      * @return {!DOMAgent.HighlightConfig}
   1456      */
   1457     _buildHighlightConfig: function(mode)
   1458     {
   1459         mode = mode || "all";
   1460         // FIXME: split show rulers and show extension lines.
   1461         var highlightConfig = { showInfo: mode === "all", showRulers: WebInspector.overridesSupport.showMetricsRulers() };
   1462         if (mode === "all" || mode === "content")
   1463             highlightConfig.contentColor = WebInspector.Color.PageHighlight.Content.toProtocolRGBA();
   1464 
   1465         if (mode === "all" || mode === "padding")
   1466             highlightConfig.paddingColor = WebInspector.Color.PageHighlight.Padding.toProtocolRGBA();
   1467 
   1468         if (mode === "all" || mode === "border")
   1469             highlightConfig.borderColor = WebInspector.Color.PageHighlight.Border.toProtocolRGBA();
   1470 
   1471         if (mode === "all" || mode === "margin")
   1472             highlightConfig.marginColor = WebInspector.Color.PageHighlight.Margin.toProtocolRGBA();
   1473 
   1474         if (mode === "all")
   1475             highlightConfig.eventTargetColor = WebInspector.Color.PageHighlight.EventTarget.toProtocolRGBA();
   1476 
   1477         return highlightConfig;
   1478     },
   1479 
   1480     /**
   1481      * @param {!WebInspector.DOMNode} node
   1482      * @param {function(?Protocol.Error, ...)=} callback
   1483      * @return {function(...)}
   1484      * @template T
   1485      */
   1486     _markRevision: function(node, callback)
   1487     {
   1488         /**
   1489          * @param {?Protocol.Error} error
   1490          * @this {WebInspector.DOMModel}
   1491          */
   1492         function wrapperFunction(error)
   1493         {
   1494             if (!error)
   1495                 this.markUndoableState();
   1496 
   1497             if (callback)
   1498                 callback.apply(this, arguments);
   1499         }
   1500         return wrapperFunction.bind(this);
   1501     },
   1502 
   1503     /**
   1504      * @param {boolean} emulationEnabled
   1505      */
   1506     emulateTouchEventObjects: function(emulationEnabled)
   1507     {
   1508         const injectedFunction = function() {
   1509             const touchEvents = ["ontouchstart", "ontouchend", "ontouchmove", "ontouchcancel"];
   1510             var recepients = [window.__proto__, document.__proto__];
   1511             for (var i = 0; i < touchEvents.length; ++i) {
   1512                 for (var j = 0; j < recepients.length; ++j) {
   1513                     if (!(touchEvents[i] in recepients[j]))
   1514                         Object.defineProperty(recepients[j], touchEvents[i], { value: null, writable: true, configurable: true, enumerable: true });
   1515                 }
   1516             }
   1517         }
   1518 
   1519         if (emulationEnabled && !this._addTouchEventsScriptInjecting) {
   1520             this._addTouchEventsScriptInjecting = true;
   1521             PageAgent.addScriptToEvaluateOnLoad("(" + injectedFunction.toString() + ")()", scriptAddedCallback.bind(this));
   1522         } else {
   1523             if (typeof this._addTouchEventsScriptId !== "undefined") {
   1524                 PageAgent.removeScriptToEvaluateOnLoad(this._addTouchEventsScriptId);
   1525                 delete this._addTouchEventsScriptId;
   1526             }
   1527         }
   1528 
   1529         /**
   1530          * @param {?Protocol.Error} error
   1531          * @param {string} scriptId
   1532          * @this {WebInspector.DOMModel}
   1533          */
   1534         function scriptAddedCallback(error, scriptId)
   1535         {
   1536             delete this._addTouchEventsScriptInjecting;
   1537             if (error)
   1538                 return;
   1539             this._addTouchEventsScriptId = scriptId;
   1540         }
   1541 
   1542         PageAgent.setTouchEmulationEnabled(emulationEnabled);
   1543     },
   1544 
   1545     markUndoableState: function()
   1546     {
   1547         this._agent.markUndoableState();
   1548     },
   1549 
   1550     /**
   1551      * @param {function(?Protocol.Error)=} callback
   1552      */
   1553     undo: function(callback)
   1554     {
   1555         /**
   1556          * @param {?Protocol.Error} error
   1557          * @this {WebInspector.DOMModel}
   1558          */
   1559         function mycallback(error)
   1560         {
   1561             this.dispatchEventToListeners(WebInspector.DOMModel.Events.UndoRedoCompleted);
   1562             callback(error);
   1563         }
   1564 
   1565         this.dispatchEventToListeners(WebInspector.DOMModel.Events.UndoRedoRequested);
   1566         this._agent.undo(callback);
   1567     },
   1568 
   1569     /**
   1570      * @param {function(?Protocol.Error)=} callback
   1571      */
   1572     redo: function(callback)
   1573     {
   1574         /**
   1575          * @param {?Protocol.Error} error
   1576          * @this {WebInspector.DOMModel}
   1577          */
   1578         function mycallback(error)
   1579         {
   1580             this.dispatchEventToListeners(WebInspector.DOMModel.Events.UndoRedoCompleted);
   1581             callback(error);
   1582         }
   1583 
   1584         this.dispatchEventToListeners(WebInspector.DOMModel.Events.UndoRedoRequested);
   1585         this._agent.redo(callback);
   1586     },
   1587 
   1588     /**
   1589      * @param {?WebInspector.DOMNodeHighlighter} highlighter
   1590      */
   1591     setHighlighter: function(highlighter)
   1592     {
   1593         this._highlighter = highlighter || this._defaultHighlighter;
   1594     },
   1595 
   1596     /**
   1597      * @param {number} x
   1598      * @param {number} y
   1599      * @param {function(?WebInspector.DOMNode)} callback
   1600      */
   1601     nodeForLocation: function(x, y, callback)
   1602     {
   1603         this._agent.getNodeForLocation(x, y, mycallback.bind(this));
   1604 
   1605         /**
   1606          * @param {?Protocol.Error} error
   1607          * @param {number} nodeId
   1608          * @this {WebInspector.DOMModel}
   1609          */
   1610         function mycallback(error, nodeId)
   1611         {
   1612             if (error) {
   1613                 callback(null);
   1614                 return;
   1615             }
   1616             callback(this.nodeForId(nodeId));
   1617         }
   1618     },
   1619 
   1620     __proto__: WebInspector.TargetAwareObject.prototype
   1621 }
   1622 
   1623 /**
   1624  * @constructor
   1625  * @implements {DOMAgent.Dispatcher}
   1626  * @param {!WebInspector.DOMModel} domModel
   1627  */
   1628 WebInspector.DOMDispatcher = function(domModel)
   1629 {
   1630     this._domModel = domModel;
   1631 }
   1632 
   1633 WebInspector.DOMDispatcher.prototype = {
   1634     documentUpdated: function()
   1635     {
   1636         this._domModel._documentUpdated();
   1637     },
   1638 
   1639     /**
   1640      * @param {!DOMAgent.NodeId} nodeId
   1641      */
   1642     inspectNodeRequested: function(nodeId)
   1643     {
   1644         this._domModel._inspectNodeRequested(nodeId);
   1645     },
   1646 
   1647     /**
   1648      * @param {!DOMAgent.NodeId} nodeId
   1649      * @param {string} name
   1650      * @param {string} value
   1651      */
   1652     attributeModified: function(nodeId, name, value)
   1653     {
   1654         this._domModel._attributeModified(nodeId, name, value);
   1655     },
   1656 
   1657     /**
   1658      * @param {!DOMAgent.NodeId} nodeId
   1659      * @param {string} name
   1660      */
   1661     attributeRemoved: function(nodeId, name)
   1662     {
   1663         this._domModel._attributeRemoved(nodeId, name);
   1664     },
   1665 
   1666     /**
   1667      * @param {!Array.<!DOMAgent.NodeId>} nodeIds
   1668      */
   1669     inlineStyleInvalidated: function(nodeIds)
   1670     {
   1671         this._domModel._inlineStyleInvalidated(nodeIds);
   1672     },
   1673 
   1674     /**
   1675      * @param {!DOMAgent.NodeId} nodeId
   1676      * @param {string} characterData
   1677      */
   1678     characterDataModified: function(nodeId, characterData)
   1679     {
   1680         this._domModel._characterDataModified(nodeId, characterData);
   1681     },
   1682 
   1683     /**
   1684      * @param {!DOMAgent.NodeId} parentId
   1685      * @param {!Array.<!DOMAgent.Node>} payloads
   1686      */
   1687     setChildNodes: function(parentId, payloads)
   1688     {
   1689         this._domModel._setChildNodes(parentId, payloads);
   1690     },
   1691 
   1692     /**
   1693      * @param {!DOMAgent.NodeId} nodeId
   1694      * @param {number} childNodeCount
   1695      */
   1696     childNodeCountUpdated: function(nodeId, childNodeCount)
   1697     {
   1698         this._domModel._childNodeCountUpdated(nodeId, childNodeCount);
   1699     },
   1700 
   1701     /**
   1702      * @param {!DOMAgent.NodeId} parentNodeId
   1703      * @param {!DOMAgent.NodeId} previousNodeId
   1704      * @param {!DOMAgent.Node} payload
   1705      */
   1706     childNodeInserted: function(parentNodeId, previousNodeId, payload)
   1707     {
   1708         this._domModel._childNodeInserted(parentNodeId, previousNodeId, payload);
   1709     },
   1710 
   1711     /**
   1712      * @param {!DOMAgent.NodeId} parentNodeId
   1713      * @param {!DOMAgent.NodeId} nodeId
   1714      */
   1715     childNodeRemoved: function(parentNodeId, nodeId)
   1716     {
   1717         this._domModel._childNodeRemoved(parentNodeId, nodeId);
   1718     },
   1719 
   1720     /**
   1721      * @param {!DOMAgent.NodeId} hostId
   1722      * @param {!DOMAgent.Node} root
   1723      */
   1724     shadowRootPushed: function(hostId, root)
   1725     {
   1726         this._domModel._shadowRootPushed(hostId, root);
   1727     },
   1728 
   1729     /**
   1730      * @param {!DOMAgent.NodeId} hostId
   1731      * @param {!DOMAgent.NodeId} rootId
   1732      */
   1733     shadowRootPopped: function(hostId, rootId)
   1734     {
   1735         this._domModel._shadowRootPopped(hostId, rootId);
   1736     },
   1737 
   1738     /**
   1739      * @param {!DOMAgent.NodeId} parentId
   1740      * @param {!DOMAgent.Node} pseudoElement
   1741      */
   1742     pseudoElementAdded: function(parentId, pseudoElement)
   1743     {
   1744         this._domModel._pseudoElementAdded(parentId, pseudoElement);
   1745     },
   1746 
   1747     /**
   1748      * @param {!DOMAgent.NodeId} parentId
   1749      * @param {!DOMAgent.NodeId} pseudoElementId
   1750      */
   1751     pseudoElementRemoved: function(parentId, pseudoElementId)
   1752     {
   1753         this._domModel._pseudoElementRemoved(parentId, pseudoElementId);
   1754     }
   1755 }
   1756 
   1757 /**
   1758  * @constructor
   1759  * @extends {WebInspector.TargetAware}
   1760  * @param {!WebInspector.Target} target
   1761  * @param {!DOMAgent.EventListener} payload
   1762  */
   1763 WebInspector.DOMModel.EventListener = function(target, payload)
   1764 {
   1765     WebInspector.TargetAware.call(this, target);
   1766     this._payload = payload;
   1767 }
   1768 
   1769 WebInspector.DOMModel.EventListener.prototype = {
   1770     /**
   1771      * @return {!DOMAgent.EventListener}
   1772      */
   1773     payload: function()
   1774     {
   1775         return this._payload;
   1776     },
   1777 
   1778     /**
   1779      * @return {?WebInspector.DOMNode}
   1780      */
   1781     node: function()
   1782     {
   1783         return this.target().domModel.nodeForId(this._payload.nodeId);
   1784     },
   1785 
   1786     /**
   1787      * @return {!WebInspector.DebuggerModel.Location}
   1788      */
   1789     location: function()
   1790     {
   1791         return WebInspector.DebuggerModel.Location.fromPayload(this.target(), this._payload.location);
   1792     },
   1793 
   1794     /**
   1795      * @return {?WebInspector.RemoteObject}
   1796      */
   1797     handler: function()
   1798     {
   1799         return this._payload.handler ? this.target().runtimeModel.createRemoteObject(this._payload.handler) : null;
   1800     },
   1801 
   1802     __proto__: WebInspector.TargetAware.prototype
   1803 }
   1804 
   1805 /**
   1806  * @interface
   1807  */
   1808 WebInspector.DOMNodeHighlighter = function() {
   1809 }
   1810 
   1811 WebInspector.DOMNodeHighlighter.prototype = {
   1812     /**
   1813      * @param {?WebInspector.DOMNode} node
   1814      * @param {!DOMAgent.HighlightConfig} config
   1815      * @param {!RuntimeAgent.RemoteObjectId=} objectId
   1816      */
   1817     highlightDOMNode: function(node, config, objectId) {},
   1818 
   1819     /**
   1820      * @param {boolean} enabled
   1821      * @param {boolean} inspectUAShadowDOM
   1822      * @param {!DOMAgent.HighlightConfig} config
   1823      * @param {function(?Protocol.Error)=} callback
   1824      */
   1825     setInspectModeEnabled: function(enabled, inspectUAShadowDOM, config, callback) {}
   1826 }
   1827 
   1828 /**
   1829  * @constructor
   1830  * @implements {WebInspector.DOMNodeHighlighter}
   1831  * @param {!Protocol.DOMAgent} agent
   1832  */
   1833 WebInspector.DefaultDOMNodeHighlighter = function(agent)
   1834 {
   1835     this._agent = agent;
   1836 }
   1837 
   1838 WebInspector.DefaultDOMNodeHighlighter.prototype = {
   1839     /**
   1840      * @param {?WebInspector.DOMNode} node
   1841      * @param {!DOMAgent.HighlightConfig} config
   1842      * @param {!RuntimeAgent.RemoteObjectId=} objectId
   1843      */
   1844     highlightDOMNode: function(node, config, objectId)
   1845     {
   1846         if (objectId || node)
   1847             this._agent.highlightNode(config, objectId ? undefined : node.id, objectId);
   1848         else
   1849             this._agent.hideHighlight();
   1850     },
   1851 
   1852     /**
   1853      * @param {boolean} enabled
   1854      * @param {boolean} inspectUAShadowDOM
   1855      * @param {!DOMAgent.HighlightConfig} config
   1856      * @param {function(?Protocol.Error)=} callback
   1857      */
   1858     setInspectModeEnabled: function(enabled, inspectUAShadowDOM, config, callback)
   1859     {
   1860         this._agent.setInspectModeEnabled(enabled, inspectUAShadowDOM, config, callback);
   1861     }
   1862 }
   1863 
   1864 /**
   1865  * @type {!WebInspector.DOMModel}
   1866  */
   1867 WebInspector.domModel;
   1868