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.SDKObject}
     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.SDKObject.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     /**
    460      * @param {function(?string)=} callback
    461      */
    462     copyNode: function(callback)
    463     {
    464         function copy(error, text)
    465         {
    466             if (!error)
    467                 InspectorFrontendHost.copyText(text);
    468             if (callback)
    469                 callback(error ? null : text);
    470         }
    471         this._agent.getOuterHTML(this.id, copy);
    472     },
    473 
    474     /**
    475      * @param {string} objectGroupId
    476      * @param {function(?Array.<!WebInspector.DOMModel.EventListener>)} callback
    477      */
    478     eventListeners: function(objectGroupId, callback)
    479     {
    480         var target = this.target();
    481 
    482         /**
    483          * @param {?Protocol.Error} error
    484          * @param {!Array.<!DOMAgent.EventListener>} payloads
    485          */
    486         function mycallback(error, payloads)
    487         {
    488             if (error) {
    489                 callback(null);
    490                 return;
    491             }
    492             callback(payloads.map(function(payload) {
    493                 return new WebInspector.DOMModel.EventListener(target, payload);
    494             }));
    495         }
    496         this._agent.getEventListenersForNode(this.id, objectGroupId, mycallback);
    497     },
    498 
    499     /**
    500      * @return {string}
    501      */
    502     path: function()
    503     {
    504         /**
    505          * @param {?WebInspector.DOMNode} node
    506          */
    507         function canPush(node)
    508         {
    509             return node && ("index" in node || (node.isShadowRoot() && node.parentNode)) && node._nodeName.length;
    510         }
    511 
    512         var path = [];
    513         var node = this;
    514         while (canPush(node)) {
    515             var index = typeof node.index === "number" ? node.index : (node.shadowRootType() === WebInspector.DOMNode.ShadowRootTypes.UserAgent ? "u" : "a");
    516             path.push([index, node._nodeName]);
    517             node = node.parentNode;
    518         }
    519         path.reverse();
    520         return path.join(",");
    521     },
    522 
    523     /**
    524      * @param {!WebInspector.DOMNode} node
    525      * @return {boolean}
    526      */
    527     isAncestor: function(node)
    528     {
    529         if (!node)
    530             return false;
    531 
    532         var currentNode = node.parentNode;
    533         while (currentNode) {
    534             if (this === currentNode)
    535                 return true;
    536             currentNode = currentNode.parentNode;
    537         }
    538         return false;
    539     },
    540 
    541     /**
    542      * @param {!WebInspector.DOMNode} descendant
    543      * @return {boolean}
    544      */
    545     isDescendant: function(descendant)
    546     {
    547         return descendant !== null && descendant.isAncestor(this);
    548     },
    549 
    550     /**
    551      * @return {?PageAgent.FrameId}
    552      */
    553     frameId: function()
    554     {
    555         var node = this;
    556         while (!node._frameId && node.parentNode)
    557             node = node.parentNode;
    558         return node._frameId;
    559     },
    560 
    561     /**
    562      * @param {!Array.<string>} attrs
    563      * @return {boolean}
    564      */
    565     _setAttributesPayload: function(attrs)
    566     {
    567         var attributesChanged = !this._attributes || attrs.length !== this._attributes.length * 2;
    568         var oldAttributesMap = this._attributesMap || {};
    569 
    570         this._attributes = [];
    571         this._attributesMap = {};
    572 
    573         for (var i = 0; i < attrs.length; i += 2) {
    574             var name = attrs[i];
    575             var value = attrs[i + 1];
    576             this._addAttribute(name, value);
    577 
    578             if (attributesChanged)
    579                 continue;
    580 
    581             if (!oldAttributesMap[name] || oldAttributesMap[name].value !== value)
    582               attributesChanged = true;
    583         }
    584         return attributesChanged;
    585     },
    586 
    587     /**
    588      * @param {!WebInspector.DOMNode} prev
    589      * @param {!DOMAgent.Node} payload
    590      * @return {!WebInspector.DOMNode}
    591      */
    592     _insertChild: function(prev, payload)
    593     {
    594         var node = new WebInspector.DOMNode(this._domModel, this.ownerDocument, this._isInShadowTree, payload);
    595         this._children.splice(this._children.indexOf(prev) + 1, 0, node);
    596         this._renumber();
    597         return node;
    598     },
    599 
    600     /**
    601      * @param {!WebInspector.DOMNode} node
    602      */
    603     _removeChild: function(node)
    604     {
    605         if (node.pseudoType()) {
    606             delete this._pseudoElements[node.pseudoType()];
    607         } else {
    608             var shadowRootIndex = this._shadowRoots.indexOf(node);
    609             if (shadowRootIndex !== -1)
    610                 this._shadowRoots.splice(shadowRootIndex, 1);
    611             else
    612                 this._children.splice(this._children.indexOf(node), 1);
    613         }
    614         node.parentNode = null;
    615         node._updateChildUserPropertyCountsOnRemoval(this);
    616         this._renumber();
    617     },
    618 
    619     /**
    620      * @param {!Array.<!DOMAgent.Node>} payloads
    621      */
    622     _setChildrenPayload: function(payloads)
    623     {
    624         // We set children in the constructor.
    625         if (this._contentDocument)
    626             return;
    627 
    628         this._children = [];
    629         for (var i = 0; i < payloads.length; ++i) {
    630             var payload = payloads[i];
    631             var node = new WebInspector.DOMNode(this._domModel, this.ownerDocument, this._isInShadowTree, payload);
    632             this._children.push(node);
    633         }
    634         this._renumber();
    635     },
    636 
    637     /**
    638      * @param {!Array.<!DOMAgent.Node>|undefined} payloads
    639      */
    640     _setPseudoElements: function(payloads)
    641     {
    642         this._pseudoElements = {};
    643         if (!payloads)
    644             return;
    645 
    646         for (var i = 0; i < payloads.length; ++i) {
    647             var node = new WebInspector.DOMNode(this._domModel, this.ownerDocument, this._isInShadowTree, payloads[i]);
    648             node.parentNode = this;
    649             this._pseudoElements[node.pseudoType()] = node;
    650         }
    651     },
    652 
    653     _renumber: function()
    654     {
    655         this._childNodeCount = this._children.length;
    656         if (this._childNodeCount == 0) {
    657             this.firstChild = null;
    658             this.lastChild = null;
    659             return;
    660         }
    661         this.firstChild = this._children[0];
    662         this.lastChild = this._children[this._childNodeCount - 1];
    663         for (var i = 0; i < this._childNodeCount; ++i) {
    664             var child = this._children[i];
    665             child.index = i;
    666             child.nextSibling = i + 1 < this._childNodeCount ? this._children[i + 1] : null;
    667             child.previousSibling = i - 1 >= 0 ? this._children[i - 1] : null;
    668             child.parentNode = this;
    669         }
    670     },
    671 
    672     /**
    673      * @param {string} name
    674      * @param {string} value
    675      */
    676     _addAttribute: function(name, value)
    677     {
    678         var attr = {
    679             name: name,
    680             value: value,
    681             _node: this
    682         };
    683         this._attributesMap[name] = attr;
    684         this._attributes.push(attr);
    685     },
    686 
    687     /**
    688      * @param {string} name
    689      * @param {string} value
    690      */
    691     _setAttribute: function(name, value)
    692     {
    693         var attr = this._attributesMap[name];
    694         if (attr)
    695             attr.value = value;
    696         else
    697             this._addAttribute(name, value);
    698     },
    699 
    700     /**
    701      * @param {string} name
    702      */
    703     _removeAttribute: function(name)
    704     {
    705         var attr = this._attributesMap[name];
    706         if (attr) {
    707             this._attributes.remove(attr);
    708             delete this._attributesMap[name];
    709         }
    710     },
    711 
    712     /**
    713      * @param {!WebInspector.DOMNode} targetNode
    714      * @param {?WebInspector.DOMNode} anchorNode
    715      * @param {function(?Protocol.Error, !DOMAgent.NodeId=)=} callback
    716      */
    717     copyTo: function(targetNode, anchorNode, callback)
    718     {
    719         this._agent.copyTo(this.id, targetNode.id, anchorNode ? anchorNode.id : undefined, this._domModel._markRevision(this, callback));
    720     },
    721 
    722     /**
    723      * @param {!WebInspector.DOMNode} targetNode
    724      * @param {?WebInspector.DOMNode} anchorNode
    725      * @param {function(?Protocol.Error, !DOMAgent.NodeId=)=} callback
    726      */
    727     moveTo: function(targetNode, anchorNode, callback)
    728     {
    729         this._agent.moveTo(this.id, targetNode.id, anchorNode ? anchorNode.id : undefined, this._domModel._markRevision(this, callback));
    730     },
    731 
    732     /**
    733      * @return {boolean}
    734      */
    735     isXMLNode: function()
    736     {
    737         return !!this.ownerDocument && !!this.ownerDocument.xmlVersion;
    738     },
    739 
    740     _updateChildUserPropertyCountsOnRemoval: function(parentNode)
    741     {
    742         var result = {};
    743         if (this._userProperties) {
    744             for (var name in this._userProperties)
    745                 result[name] = (result[name] || 0) + 1;
    746         }
    747 
    748         if (this._descendantUserPropertyCounters) {
    749             for (var name in this._descendantUserPropertyCounters) {
    750                 var counter = this._descendantUserPropertyCounters[name];
    751                 result[name] = (result[name] || 0) + counter;
    752             }
    753         }
    754 
    755         for (var name in result)
    756             parentNode._updateDescendantUserPropertyCount(name, -result[name]);
    757     },
    758 
    759     _updateDescendantUserPropertyCount: function(name, delta)
    760     {
    761         if (!this._descendantUserPropertyCounters.hasOwnProperty(name))
    762             this._descendantUserPropertyCounters[name] = 0;
    763         this._descendantUserPropertyCounters[name] += delta;
    764         if (!this._descendantUserPropertyCounters[name])
    765             delete this._descendantUserPropertyCounters[name];
    766         if (this.parentNode)
    767             this.parentNode._updateDescendantUserPropertyCount(name, delta);
    768     },
    769 
    770     setUserProperty: function(name, value)
    771     {
    772         if (value === null) {
    773             this.removeUserProperty(name);
    774             return;
    775         }
    776 
    777         if (this.parentNode && !this._userProperties.hasOwnProperty(name))
    778             this.parentNode._updateDescendantUserPropertyCount(name, 1);
    779 
    780         this._userProperties[name] = value;
    781     },
    782 
    783     removeUserProperty: function(name)
    784     {
    785         if (!this._userProperties.hasOwnProperty(name))
    786             return;
    787 
    788         delete this._userProperties[name];
    789         if (this.parentNode)
    790             this.parentNode._updateDescendantUserPropertyCount(name, -1);
    791     },
    792 
    793     /**
    794      * @param {string} name
    795      * @return {?T}
    796      * @template T
    797      */
    798     getUserProperty: function(name)
    799     {
    800         return (this._userProperties && this._userProperties[name]) || null;
    801     },
    802 
    803     /**
    804      * @param {string} name
    805      * @return {number}
    806      */
    807     descendantUserPropertyCount: function(name)
    808     {
    809         return this._descendantUserPropertyCounters && this._descendantUserPropertyCounters[name] ? this._descendantUserPropertyCounters[name] : 0;
    810     },
    811 
    812     /**
    813      * @param {string} url
    814      * @return {?string}
    815      */
    816     resolveURL: function(url)
    817     {
    818         if (!url)
    819             return url;
    820         for (var frameOwnerCandidate = this; frameOwnerCandidate; frameOwnerCandidate = frameOwnerCandidate.parentNode) {
    821             if (frameOwnerCandidate.baseURL)
    822                 return WebInspector.ParsedURL.completeURL(frameOwnerCandidate.baseURL, url);
    823         }
    824         return null;
    825     },
    826 
    827     /**
    828      * @param {string=} mode
    829      * @param {!RuntimeAgent.RemoteObjectId=} objectId
    830      */
    831     highlight: function(mode, objectId)
    832     {
    833         this._domModel.highlightDOMNode(this.id, mode, objectId);
    834     },
    835 
    836     highlightForTwoSeconds: function()
    837     {
    838         this._domModel.highlightDOMNodeForTwoSeconds(this.id);
    839     },
    840 
    841     /**
    842      * @param {string=} objectGroup
    843      * @param {function(?WebInspector.RemoteObject)=} callback
    844      */
    845     resolveToObject: function(objectGroup, callback)
    846     {
    847         this._agent.resolveNode(this.id, objectGroup, mycallback.bind(this));
    848 
    849         /**
    850          * @param {?Protocol.Error} error
    851          * @param {!RuntimeAgent.RemoteObject} object
    852          * @this {WebInspector.DOMNode}
    853          */
    854         function mycallback(error, object)
    855         {
    856             if (!callback)
    857                 return;
    858 
    859             if (error || !object)
    860                 callback(null);
    861             else
    862                 callback(this.target().runtimeModel.createRemoteObject(object));
    863         }
    864     },
    865 
    866     /**
    867      * @param {function(?DOMAgent.BoxModel)} callback
    868      */
    869     boxModel: function(callback)
    870     {
    871         this._agent.getBoxModel(this.id, this._domModel._wrapClientCallback(callback));
    872     },
    873 
    874     __proto__: WebInspector.SDKObject.prototype
    875 }
    876 
    877 /**
    878  * @param {!WebInspector.Target} target
    879  * @param {number} backendNodeId
    880  * @constructor
    881  */
    882 WebInspector.DeferredDOMNode = function(target, backendNodeId)
    883 {
    884     this._target = target;
    885     this._backendNodeId = backendNodeId;
    886 }
    887 
    888 WebInspector.DeferredDOMNode.prototype = {
    889     /**
    890      * @param {function(?WebInspector.DOMNode)} callback
    891      */
    892     resolve: function(callback)
    893     {
    894         this._target.domModel.pushNodesByBackendIdsToFrontend([this._backendNodeId], onGotNode.bind(this));
    895 
    896         /**
    897          * @param {?Array.<number>} nodeIds
    898          * @this {WebInspector.DeferredDOMNode}
    899          */
    900         function onGotNode(nodeIds)
    901         {
    902             if (!nodeIds || !nodeIds[0]) {
    903                 callback(null);
    904                 return;
    905             }
    906             callback(this._target.domModel.nodeForId(nodeIds[0]));
    907         }
    908     }
    909 }
    910 
    911 /**
    912  * @extends {WebInspector.DOMNode}
    913  * @constructor
    914  * @param {!WebInspector.DOMModel} domModel
    915  * @param {!DOMAgent.Node} payload
    916  */
    917 WebInspector.DOMDocument = function(domModel, payload)
    918 {
    919     WebInspector.DOMNode.call(this, domModel, this, false, payload);
    920     this.documentURL = payload.documentURL || "";
    921     this.baseURL = payload.baseURL || "";
    922     this.xmlVersion = payload.xmlVersion;
    923     this._listeners = {};
    924 }
    925 
    926 WebInspector.DOMDocument.prototype = {
    927     __proto__: WebInspector.DOMNode.prototype
    928 }
    929 
    930 /**
    931  * @constructor
    932  * @extends {WebInspector.SDKModel}
    933  * @param {!WebInspector.Target} target
    934  */
    935 WebInspector.DOMModel = function(target) {
    936     WebInspector.SDKModel.call(this, WebInspector.DOMModel, target);
    937 
    938     this._agent = target.domAgent();
    939 
    940     /** @type {!Object.<number, !WebInspector.DOMNode>} */
    941     this._idToDOMNode = {};
    942     /** @type {?WebInspector.DOMDocument} */
    943     this._document = null;
    944     /** @type {!Object.<number, boolean>} */
    945     this._attributeLoadNodeIds = {};
    946     target.registerDOMDispatcher(new WebInspector.DOMDispatcher(this));
    947 
    948     this._defaultHighlighter = new WebInspector.DefaultDOMNodeHighlighter(this._agent);
    949     this._highlighter = this._defaultHighlighter;
    950 
    951     if (Runtime.experiments.isEnabled("disableAgentsWhenProfile"))
    952         WebInspector.profilingLock().addEventListener(WebInspector.Lock.Events.StateChanged, this._profilingStateChanged, this);
    953 
    954     this._agent.enable();
    955 }
    956 
    957 WebInspector.DOMModel.Events = {
    958     AttrModified: "AttrModified",
    959     AttrRemoved: "AttrRemoved",
    960     CharacterDataModified: "CharacterDataModified",
    961     NodeInserted: "NodeInserted",
    962     NodeInspected: "NodeInspected",
    963     NodeRemoved: "NodeRemoved",
    964     DocumentUpdated: "DocumentUpdated",
    965     ChildNodeCountUpdated: "ChildNodeCountUpdated",
    966     UndoRedoRequested: "UndoRedoRequested",
    967     UndoRedoCompleted: "UndoRedoCompleted",
    968 }
    969 
    970 WebInspector.DOMModel.prototype = {
    971     _profilingStateChanged: function()
    972     {
    973         if (WebInspector.profilingLock().isAcquired())
    974             this._agent.disable();
    975         else
    976             this._agent.enable();
    977     },
    978 
    979     /**
    980      * @param {function(!WebInspector.DOMDocument)=} callback
    981      */
    982     requestDocument: function(callback)
    983     {
    984         if (this._document) {
    985             if (callback)
    986                 callback(this._document);
    987             return;
    988         }
    989 
    990         if (this._pendingDocumentRequestCallbacks) {
    991             this._pendingDocumentRequestCallbacks.push(callback);
    992             return;
    993         }
    994 
    995         this._pendingDocumentRequestCallbacks = [callback];
    996 
    997         /**
    998          * @this {WebInspector.DOMModel}
    999          * @param {?Protocol.Error} error
   1000          * @param {!DOMAgent.Node} root
   1001          */
   1002         function onDocumentAvailable(error, root)
   1003         {
   1004             if (!error)
   1005                 this._setDocument(root);
   1006 
   1007             for (var i = 0; i < this._pendingDocumentRequestCallbacks.length; ++i) {
   1008                 var callback = this._pendingDocumentRequestCallbacks[i];
   1009                 if (callback)
   1010                     callback(this._document);
   1011             }
   1012             delete this._pendingDocumentRequestCallbacks;
   1013         }
   1014 
   1015         this._agent.getDocument(onDocumentAvailable.bind(this));
   1016     },
   1017 
   1018     /**
   1019      * @return {?WebInspector.DOMDocument}
   1020      */
   1021     existingDocument: function()
   1022     {
   1023         return this._document;
   1024     },
   1025 
   1026     /**
   1027      * @param {!RuntimeAgent.RemoteObjectId} objectId
   1028      * @param {function(?WebInspector.DOMNode)=} callback
   1029      */
   1030     pushNodeToFrontend: function(objectId, callback)
   1031     {
   1032         /**
   1033          * @param {?DOMAgent.NodeId} nodeId
   1034          * @this {!WebInspector.DOMModel}
   1035          */
   1036         function mycallback(nodeId)
   1037         {
   1038             callback(nodeId ? this.nodeForId(nodeId) : null);
   1039         }
   1040         this._dispatchWhenDocumentAvailable(this._agent.requestNode.bind(this._agent, objectId), mycallback.bind(this));
   1041     },
   1042 
   1043     /**
   1044      * @param {string} path
   1045      * @param {function(?number)=} callback
   1046      */
   1047     pushNodeByPathToFrontend: function(path, callback)
   1048     {
   1049         this._dispatchWhenDocumentAvailable(this._agent.pushNodeByPathToFrontend.bind(this._agent, path), callback);
   1050     },
   1051 
   1052     /**
   1053      * @param {!Array.<number>} backendNodeIds
   1054      * @param {function(?Array.<number>)=} callback
   1055      */
   1056     pushNodesByBackendIdsToFrontend: function(backendNodeIds, callback)
   1057     {
   1058         this._dispatchWhenDocumentAvailable(this._agent.pushNodesByBackendIdsToFrontend.bind(this._agent, backendNodeIds), callback);
   1059     },
   1060 
   1061     /**
   1062      * @param {function(!T)=} callback
   1063      * @return {function(?Protocol.Error, !T=)|undefined}
   1064      * @template T
   1065      */
   1066     _wrapClientCallback: function(callback)
   1067     {
   1068         if (!callback)
   1069             return;
   1070         /**
   1071          * @param {?Protocol.Error} error
   1072          * @param {!T=} result
   1073          * @template T
   1074          */
   1075         var wrapper = function(error, result)
   1076         {
   1077             // Caller is responsible for handling the actual error.
   1078             callback(error ? null : result);
   1079         };
   1080         return wrapper;
   1081     },
   1082 
   1083     /**
   1084      * @param {function(function(?Protocol.Error, !T=)=)} func
   1085      * @param {function(!T)=} callback
   1086      * @template T
   1087      */
   1088     _dispatchWhenDocumentAvailable: function(func, callback)
   1089     {
   1090         var callbackWrapper = this._wrapClientCallback(callback);
   1091 
   1092         /**
   1093          * @this {WebInspector.DOMModel}
   1094          */
   1095         function onDocumentAvailable()
   1096         {
   1097             if (this._document)
   1098                 func(callbackWrapper);
   1099             else {
   1100                 if (callbackWrapper)
   1101                     callbackWrapper("No document");
   1102             }
   1103         }
   1104         this.requestDocument(onDocumentAvailable.bind(this));
   1105     },
   1106 
   1107     /**
   1108      * @param {!DOMAgent.NodeId} nodeId
   1109      * @param {string} name
   1110      * @param {string} value
   1111      */
   1112     _attributeModified: function(nodeId, name, value)
   1113     {
   1114         var node = this._idToDOMNode[nodeId];
   1115         if (!node)
   1116             return;
   1117 
   1118         node._setAttribute(name, value);
   1119         this.dispatchEventToListeners(WebInspector.DOMModel.Events.AttrModified, { node: node, name: name });
   1120     },
   1121 
   1122     /**
   1123      * @param {!DOMAgent.NodeId} nodeId
   1124      * @param {string} name
   1125      */
   1126     _attributeRemoved: function(nodeId, name)
   1127     {
   1128         var node = this._idToDOMNode[nodeId];
   1129         if (!node)
   1130             return;
   1131         node._removeAttribute(name);
   1132         this.dispatchEventToListeners(WebInspector.DOMModel.Events.AttrRemoved, { node: node, name: name });
   1133     },
   1134 
   1135     /**
   1136      * @param {!Array.<!DOMAgent.NodeId>} nodeIds
   1137      */
   1138     _inlineStyleInvalidated: function(nodeIds)
   1139     {
   1140         for (var i = 0; i < nodeIds.length; ++i)
   1141             this._attributeLoadNodeIds[nodeIds[i]] = true;
   1142         if ("_loadNodeAttributesTimeout" in this)
   1143             return;
   1144         this._loadNodeAttributesTimeout = setTimeout(this._loadNodeAttributes.bind(this), 20);
   1145     },
   1146 
   1147     _loadNodeAttributes: function()
   1148     {
   1149         /**
   1150          * @this {WebInspector.DOMModel}
   1151          * @param {!DOMAgent.NodeId} nodeId
   1152          * @param {?Protocol.Error} error
   1153          * @param {!Array.<string>} attributes
   1154          */
   1155         function callback(nodeId, error, attributes)
   1156         {
   1157             if (error) {
   1158                 // We are calling _loadNodeAttributes asynchronously, it is ok if node is not found.
   1159                 return;
   1160             }
   1161             var node = this._idToDOMNode[nodeId];
   1162             if (node) {
   1163                 if (node._setAttributesPayload(attributes))
   1164                     this.dispatchEventToListeners(WebInspector.DOMModel.Events.AttrModified, { node: node, name: "style" });
   1165             }
   1166         }
   1167 
   1168         delete this._loadNodeAttributesTimeout;
   1169 
   1170         for (var nodeId in this._attributeLoadNodeIds) {
   1171             var nodeIdAsNumber = parseInt(nodeId, 10);
   1172             this._agent.getAttributes(nodeIdAsNumber, callback.bind(this, nodeIdAsNumber));
   1173         }
   1174         this._attributeLoadNodeIds = {};
   1175     },
   1176 
   1177     /**
   1178      * @param {!DOMAgent.NodeId} nodeId
   1179      * @param {string} newValue
   1180      */
   1181     _characterDataModified: function(nodeId, newValue)
   1182     {
   1183         var node = this._idToDOMNode[nodeId];
   1184         node._nodeValue = newValue;
   1185         this.dispatchEventToListeners(WebInspector.DOMModel.Events.CharacterDataModified, node);
   1186     },
   1187 
   1188     /**
   1189      * @param {!DOMAgent.NodeId} nodeId
   1190      * @return {?WebInspector.DOMNode}
   1191      */
   1192     nodeForId: function(nodeId)
   1193     {
   1194         return this._idToDOMNode[nodeId] || null;
   1195     },
   1196 
   1197     _documentUpdated: function()
   1198     {
   1199         this._setDocument(null);
   1200     },
   1201 
   1202     /**
   1203      * @param {?DOMAgent.Node} payload
   1204      */
   1205     _setDocument: function(payload)
   1206     {
   1207         this._idToDOMNode = {};
   1208         if (payload && "nodeId" in payload)
   1209             this._document = new WebInspector.DOMDocument(this, payload);
   1210         else
   1211             this._document = null;
   1212         this.dispatchEventToListeners(WebInspector.DOMModel.Events.DocumentUpdated, this._document);
   1213     },
   1214 
   1215     /**
   1216      * @param {!DOMAgent.Node} payload
   1217      */
   1218     _setDetachedRoot: function(payload)
   1219     {
   1220         if (payload.nodeName === "#document")
   1221             new WebInspector.DOMDocument(this, payload);
   1222         else
   1223             new WebInspector.DOMNode(this, null, false, payload);
   1224     },
   1225 
   1226     /**
   1227      * @param {!DOMAgent.NodeId} parentId
   1228      * @param {!Array.<!DOMAgent.Node>} payloads
   1229      */
   1230     _setChildNodes: function(parentId, payloads)
   1231     {
   1232         if (!parentId && payloads.length) {
   1233             this._setDetachedRoot(payloads[0]);
   1234             return;
   1235         }
   1236 
   1237         var parent = this._idToDOMNode[parentId];
   1238         parent._setChildrenPayload(payloads);
   1239     },
   1240 
   1241     /**
   1242      * @param {!DOMAgent.NodeId} nodeId
   1243      * @param {number} newValue
   1244      */
   1245     _childNodeCountUpdated: function(nodeId, newValue)
   1246     {
   1247         var node = this._idToDOMNode[nodeId];
   1248         node._childNodeCount = newValue;
   1249         this.dispatchEventToListeners(WebInspector.DOMModel.Events.ChildNodeCountUpdated, node);
   1250     },
   1251 
   1252     /**
   1253      * @param {!DOMAgent.NodeId} parentId
   1254      * @param {!DOMAgent.NodeId} prevId
   1255      * @param {!DOMAgent.Node} payload
   1256      */
   1257     _childNodeInserted: function(parentId, prevId, payload)
   1258     {
   1259         var parent = this._idToDOMNode[parentId];
   1260         var prev = this._idToDOMNode[prevId];
   1261         var node = parent._insertChild(prev, payload);
   1262         this._idToDOMNode[node.id] = node;
   1263         this.dispatchEventToListeners(WebInspector.DOMModel.Events.NodeInserted, node);
   1264     },
   1265 
   1266     /**
   1267      * @param {!DOMAgent.NodeId} parentId
   1268      * @param {!DOMAgent.NodeId} nodeId
   1269      */
   1270     _childNodeRemoved: function(parentId, nodeId)
   1271     {
   1272         var parent = this._idToDOMNode[parentId];
   1273         var node = this._idToDOMNode[nodeId];
   1274         parent._removeChild(node);
   1275         this._unbind(node);
   1276         this.dispatchEventToListeners(WebInspector.DOMModel.Events.NodeRemoved, {node: node, parent: parent});
   1277     },
   1278 
   1279     /**
   1280      * @param {!DOMAgent.NodeId} hostId
   1281      * @param {!DOMAgent.Node} root
   1282      */
   1283     _shadowRootPushed: function(hostId, root)
   1284     {
   1285         var host = this._idToDOMNode[hostId];
   1286         if (!host)
   1287             return;
   1288         var node = new WebInspector.DOMNode(this, host.ownerDocument, true, root);
   1289         node.parentNode = host;
   1290         this._idToDOMNode[node.id] = node;
   1291         host._shadowRoots.push(node);
   1292         this.dispatchEventToListeners(WebInspector.DOMModel.Events.NodeInserted, node);
   1293     },
   1294 
   1295     /**
   1296      * @param {!DOMAgent.NodeId} hostId
   1297      * @param {!DOMAgent.NodeId} rootId
   1298      */
   1299     _shadowRootPopped: function(hostId, rootId)
   1300     {
   1301         var host = this._idToDOMNode[hostId];
   1302         if (!host)
   1303             return;
   1304         var root = this._idToDOMNode[rootId];
   1305         if (!root)
   1306             return;
   1307         host._removeChild(root);
   1308         this._unbind(root);
   1309         this.dispatchEventToListeners(WebInspector.DOMModel.Events.NodeRemoved, {node: root, parent: host});
   1310     },
   1311 
   1312     /**
   1313      * @param {!DOMAgent.NodeId} parentId
   1314      * @param {!DOMAgent.Node} pseudoElement
   1315      */
   1316     _pseudoElementAdded: function(parentId, pseudoElement)
   1317     {
   1318         var parent = this._idToDOMNode[parentId];
   1319         if (!parent)
   1320             return;
   1321         var node = new WebInspector.DOMNode(this, parent.ownerDocument, false, pseudoElement);
   1322         node.parentNode = parent;
   1323         this._idToDOMNode[node.id] = node;
   1324         console.assert(!parent._pseudoElements[node.pseudoType()]);
   1325         parent._pseudoElements[node.pseudoType()] = node;
   1326         this.dispatchEventToListeners(WebInspector.DOMModel.Events.NodeInserted, node);
   1327     },
   1328 
   1329     /**
   1330      * @param {!DOMAgent.NodeId} parentId
   1331      * @param {!DOMAgent.NodeId} pseudoElementId
   1332      */
   1333     _pseudoElementRemoved: function(parentId, pseudoElementId)
   1334     {
   1335         var parent = this._idToDOMNode[parentId];
   1336         if (!parent)
   1337             return;
   1338         var pseudoElement = this._idToDOMNode[pseudoElementId];
   1339         if (!pseudoElement)
   1340             return;
   1341         parent._removeChild(pseudoElement);
   1342         this._unbind(pseudoElement);
   1343         this.dispatchEventToListeners(WebInspector.DOMModel.Events.NodeRemoved, {node: pseudoElement, parent: parent});
   1344     },
   1345 
   1346     /**
   1347      * @param {!WebInspector.DOMNode} node
   1348      */
   1349     _unbind: function(node)
   1350     {
   1351         delete this._idToDOMNode[node.id];
   1352         for (var i = 0; node._children && i < node._children.length; ++i)
   1353             this._unbind(node._children[i]);
   1354         for (var i = 0; i < node._shadowRoots.length; ++i)
   1355             this._unbind(node._shadowRoots[i]);
   1356         var pseudoElements = node.pseudoElements();
   1357         for (var id in pseudoElements)
   1358             this._unbind(pseudoElements[id]);
   1359         if (node._templateContent)
   1360             this._unbind(node._templateContent);
   1361     },
   1362 
   1363     /**
   1364      * @param {!DOMAgent.NodeId} nodeId
   1365      */
   1366     _inspectNodeRequested: function(nodeId)
   1367     {
   1368         this.dispatchEventToListeners(WebInspector.DOMModel.Events.NodeInspected, this.nodeForId(nodeId));
   1369     },
   1370 
   1371     /**
   1372      * @param {string} query
   1373      * @param {boolean} includeUserAgentShadowDOM
   1374      * @param {function(number)} searchCallback
   1375      */
   1376     performSearch: function(query, includeUserAgentShadowDOM, searchCallback)
   1377     {
   1378         this.cancelSearch();
   1379 
   1380         /**
   1381          * @param {?Protocol.Error} error
   1382          * @param {string} searchId
   1383          * @param {number} resultsCount
   1384          * @this {WebInspector.DOMModel}
   1385          */
   1386         function callback(error, searchId, resultsCount)
   1387         {
   1388             this._searchId = searchId;
   1389             searchCallback(resultsCount);
   1390         }
   1391         this._agent.performSearch(query, includeUserAgentShadowDOM, callback.bind(this));
   1392     },
   1393 
   1394     /**
   1395      * @param {string} query
   1396      * @param {boolean} includeUserAgentShadowDOM
   1397      * @return {!Promise.<number>}
   1398      */
   1399     performSearchPromise: function(query, includeUserAgentShadowDOM)
   1400     {
   1401         return new Promise(performSearch.bind(this));
   1402 
   1403         /**
   1404          * @param {function(number)} resolve
   1405          * @this {WebInspector.DOMModel}
   1406          */
   1407         function performSearch(resolve)
   1408         {
   1409             this._agent.performSearch(query, includeUserAgentShadowDOM, callback.bind(this));
   1410 
   1411             /**
   1412              * @param {?Protocol.Error} error
   1413              * @param {string} searchId
   1414              * @param {number} resultsCount
   1415              * @this {WebInspector.DOMModel}
   1416              */
   1417             function callback(error, searchId, resultsCount)
   1418             {
   1419                 if (!error)
   1420                     this._searchId = searchId;
   1421                 resolve(error ? 0 : resultsCount);
   1422             }
   1423         }
   1424     },
   1425 
   1426     /**
   1427      * @param {number} index
   1428      * @param {?function(?WebInspector.DOMNode)} callback
   1429      */
   1430     searchResult: function(index, callback)
   1431     {
   1432         if (this._searchId)
   1433             this._agent.getSearchResults(this._searchId, index, index + 1, searchResultsCallback.bind(this));
   1434         else
   1435             callback(null);
   1436 
   1437         /**
   1438          * @param {?Protocol.Error} error
   1439          * @param {!Array.<number>} nodeIds
   1440          * @this {WebInspector.DOMModel}
   1441          */
   1442         function searchResultsCallback(error, nodeIds)
   1443         {
   1444             if (error) {
   1445                 console.error(error);
   1446                 callback(null);
   1447                 return;
   1448             }
   1449             if (nodeIds.length != 1)
   1450                 return;
   1451 
   1452             callback(this.nodeForId(nodeIds[0]));
   1453         }
   1454     },
   1455 
   1456     cancelSearch: function()
   1457     {
   1458         if (this._searchId) {
   1459             this._agent.discardSearchResults(this._searchId);
   1460             delete this._searchId;
   1461         }
   1462     },
   1463 
   1464     /**
   1465      * @param {!DOMAgent.NodeId} nodeId
   1466      * @param {string} selectors
   1467      * @param {function(?DOMAgent.NodeId)=} callback
   1468      */
   1469     querySelector: function(nodeId, selectors, callback)
   1470     {
   1471         this._agent.querySelector(nodeId, selectors, this._wrapClientCallback(callback));
   1472     },
   1473 
   1474     /**
   1475      * @param {!DOMAgent.NodeId} nodeId
   1476      * @param {string} selectors
   1477      * @param {function(!Array.<!DOMAgent.NodeId>=)=} callback
   1478      */
   1479     querySelectorAll: function(nodeId, selectors, callback)
   1480     {
   1481         this._agent.querySelectorAll(nodeId, selectors, this._wrapClientCallback(callback));
   1482     },
   1483 
   1484     /**
   1485      * @param {!DOMAgent.NodeId=} nodeId
   1486      * @param {string=} mode
   1487      * @param {!RuntimeAgent.RemoteObjectId=} objectId
   1488      */
   1489     highlightDOMNode: function(nodeId, mode, objectId)
   1490     {
   1491         this.highlightDOMNodeWithConfig(nodeId, { mode: mode }, objectId);
   1492     },
   1493 
   1494     /**
   1495      * @param {!DOMAgent.NodeId=} nodeId
   1496      * @param {!{mode: (string|undefined), showInfo: (boolean|undefined)}=} config
   1497      * @param {!RuntimeAgent.RemoteObjectId=} objectId
   1498      */
   1499     highlightDOMNodeWithConfig: function(nodeId, config, objectId)
   1500     {
   1501         config = config || { mode: "all", showInfo: undefined };
   1502         if (this._hideDOMNodeHighlightTimeout) {
   1503             clearTimeout(this._hideDOMNodeHighlightTimeout);
   1504             delete this._hideDOMNodeHighlightTimeout;
   1505         }
   1506         var highlightConfig = this._buildHighlightConfig(config.mode);
   1507         if (typeof config.showInfo !== "undefined")
   1508             highlightConfig.showInfo = config.showInfo;
   1509         this._highlighter.highlightDOMNode(this.nodeForId(nodeId || 0), highlightConfig, objectId);
   1510     },
   1511 
   1512     hideDOMNodeHighlight: function()
   1513     {
   1514         this.highlightDOMNode(0);
   1515     },
   1516 
   1517     /**
   1518      * @param {!DOMAgent.NodeId} nodeId
   1519      */
   1520     highlightDOMNodeForTwoSeconds: function(nodeId)
   1521     {
   1522         this.highlightDOMNode(nodeId);
   1523         this._hideDOMNodeHighlightTimeout = setTimeout(this.hideDOMNodeHighlight.bind(this), 2000);
   1524     },
   1525 
   1526     /**
   1527      * @param {boolean} enabled
   1528      * @param {boolean} inspectUAShadowDOM
   1529      * @param {function(?Protocol.Error)=} callback
   1530      */
   1531     setInspectModeEnabled: function(enabled, inspectUAShadowDOM, callback)
   1532     {
   1533         /**
   1534          * @this {WebInspector.DOMModel}
   1535          */
   1536         function onDocumentAvailable()
   1537         {
   1538             this._highlighter.setInspectModeEnabled(enabled, inspectUAShadowDOM, this._buildHighlightConfig(), callback);
   1539         }
   1540         this.requestDocument(onDocumentAvailable.bind(this));
   1541     },
   1542 
   1543     /**
   1544      * @param {string=} mode
   1545      * @return {!DOMAgent.HighlightConfig}
   1546      */
   1547     _buildHighlightConfig: function(mode)
   1548     {
   1549         mode = mode || "all";
   1550         var highlightConfig = { showInfo: mode === "all", showRulers: WebInspector.overridesSupport.showMetricsRulers(), showExtensionLines: WebInspector.overridesSupport.showExtensionLines()};
   1551         if (mode === "all" || mode === "content")
   1552             highlightConfig.contentColor = WebInspector.Color.PageHighlight.Content.toProtocolRGBA();
   1553 
   1554         if (mode === "all" || mode === "padding")
   1555             highlightConfig.paddingColor = WebInspector.Color.PageHighlight.Padding.toProtocolRGBA();
   1556 
   1557         if (mode === "all" || mode === "border")
   1558             highlightConfig.borderColor = WebInspector.Color.PageHighlight.Border.toProtocolRGBA();
   1559 
   1560         if (mode === "all" || mode === "margin")
   1561             highlightConfig.marginColor = WebInspector.Color.PageHighlight.Margin.toProtocolRGBA();
   1562 
   1563         if (mode === "all") {
   1564             highlightConfig.eventTargetColor = WebInspector.Color.PageHighlight.EventTarget.toProtocolRGBA();
   1565             highlightConfig.shapeColor = WebInspector.Color.PageHighlight.Shape.toProtocolRGBA();
   1566             highlightConfig.shapeMarginColor = WebInspector.Color.PageHighlight.ShapeMargin.toProtocolRGBA();
   1567         }
   1568         return highlightConfig;
   1569     },
   1570 
   1571     /**
   1572      * @param {!WebInspector.DOMNode} node
   1573      * @param {function(?Protocol.Error, ...)=} callback
   1574      * @return {function(...)}
   1575      * @template T
   1576      */
   1577     _markRevision: function(node, callback)
   1578     {
   1579         /**
   1580          * @param {?Protocol.Error} error
   1581          * @this {WebInspector.DOMModel}
   1582          */
   1583         function wrapperFunction(error)
   1584         {
   1585             if (!error)
   1586                 this.markUndoableState();
   1587 
   1588             if (callback)
   1589                 callback.apply(this, arguments);
   1590         }
   1591         return wrapperFunction.bind(this);
   1592     },
   1593 
   1594     /**
   1595      * @param {boolean} emulationEnabled
   1596      */
   1597     emulateTouchEventObjects: function(emulationEnabled)
   1598     {
   1599         const injectedFunction = function() {
   1600             const touchEvents = ["ontouchstart", "ontouchend", "ontouchmove", "ontouchcancel"];
   1601             var recepients = [window.__proto__, document.__proto__];
   1602             for (var i = 0; i < touchEvents.length; ++i) {
   1603                 for (var j = 0; j < recepients.length; ++j) {
   1604                     if (!(touchEvents[i] in recepients[j]))
   1605                         Object.defineProperty(recepients[j], touchEvents[i], { value: null, writable: true, configurable: true, enumerable: true });
   1606                 }
   1607             }
   1608         }
   1609 
   1610         if (emulationEnabled && !this._addTouchEventsScriptInjecting) {
   1611             this._addTouchEventsScriptInjecting = true;
   1612             PageAgent.addScriptToEvaluateOnLoad("(" + injectedFunction.toString() + ")()", scriptAddedCallback.bind(this));
   1613         } else {
   1614             if (typeof this._addTouchEventsScriptId !== "undefined") {
   1615                 PageAgent.removeScriptToEvaluateOnLoad(this._addTouchEventsScriptId);
   1616                 delete this._addTouchEventsScriptId;
   1617             }
   1618         }
   1619 
   1620         /**
   1621          * @param {?Protocol.Error} error
   1622          * @param {string} scriptId
   1623          * @this {WebInspector.DOMModel}
   1624          */
   1625         function scriptAddedCallback(error, scriptId)
   1626         {
   1627             delete this._addTouchEventsScriptInjecting;
   1628             if (error)
   1629                 return;
   1630             this._addTouchEventsScriptId = scriptId;
   1631         }
   1632 
   1633         PageAgent.setTouchEmulationEnabled(emulationEnabled);
   1634     },
   1635 
   1636     markUndoableState: function()
   1637     {
   1638         this._agent.markUndoableState();
   1639     },
   1640 
   1641     /**
   1642      * @param {function(?Protocol.Error)=} callback
   1643      */
   1644     undo: function(callback)
   1645     {
   1646         /**
   1647          * @param {?Protocol.Error} error
   1648          * @this {WebInspector.DOMModel}
   1649          */
   1650         function mycallback(error)
   1651         {
   1652             this.dispatchEventToListeners(WebInspector.DOMModel.Events.UndoRedoCompleted);
   1653             callback(error);
   1654         }
   1655 
   1656         this.dispatchEventToListeners(WebInspector.DOMModel.Events.UndoRedoRequested);
   1657         this._agent.undo(callback);
   1658     },
   1659 
   1660     /**
   1661      * @param {function(?Protocol.Error)=} callback
   1662      */
   1663     redo: function(callback)
   1664     {
   1665         /**
   1666          * @param {?Protocol.Error} error
   1667          * @this {WebInspector.DOMModel}
   1668          */
   1669         function mycallback(error)
   1670         {
   1671             this.dispatchEventToListeners(WebInspector.DOMModel.Events.UndoRedoCompleted);
   1672             callback(error);
   1673         }
   1674 
   1675         this.dispatchEventToListeners(WebInspector.DOMModel.Events.UndoRedoRequested);
   1676         this._agent.redo(callback);
   1677     },
   1678 
   1679     /**
   1680      * @param {?WebInspector.DOMNodeHighlighter} highlighter
   1681      */
   1682     setHighlighter: function(highlighter)
   1683     {
   1684         this._highlighter = highlighter || this._defaultHighlighter;
   1685     },
   1686 
   1687     /**
   1688      * @param {number} x
   1689      * @param {number} y
   1690      * @param {function(?WebInspector.DOMNode)} callback
   1691      */
   1692     nodeForLocation: function(x, y, callback)
   1693     {
   1694         this._agent.getNodeForLocation(x, y, mycallback.bind(this));
   1695 
   1696         /**
   1697          * @param {?Protocol.Error} error
   1698          * @param {number} nodeId
   1699          * @this {WebInspector.DOMModel}
   1700          */
   1701         function mycallback(error, nodeId)
   1702         {
   1703             if (error) {
   1704                 callback(null);
   1705                 return;
   1706             }
   1707             callback(this.nodeForId(nodeId));
   1708         }
   1709     },
   1710 
   1711     __proto__: WebInspector.SDKModel.prototype
   1712 }
   1713 
   1714 /**
   1715  * @constructor
   1716  * @implements {DOMAgent.Dispatcher}
   1717  * @param {!WebInspector.DOMModel} domModel
   1718  */
   1719 WebInspector.DOMDispatcher = function(domModel)
   1720 {
   1721     this._domModel = domModel;
   1722 }
   1723 
   1724 WebInspector.DOMDispatcher.prototype = {
   1725     documentUpdated: function()
   1726     {
   1727         this._domModel._documentUpdated();
   1728     },
   1729 
   1730     /**
   1731      * @param {!DOMAgent.NodeId} nodeId
   1732      */
   1733     inspectNodeRequested: function(nodeId)
   1734     {
   1735         this._domModel._inspectNodeRequested(nodeId);
   1736     },
   1737 
   1738     /**
   1739      * @param {!DOMAgent.NodeId} nodeId
   1740      * @param {string} name
   1741      * @param {string} value
   1742      */
   1743     attributeModified: function(nodeId, name, value)
   1744     {
   1745         this._domModel._attributeModified(nodeId, name, value);
   1746     },
   1747 
   1748     /**
   1749      * @param {!DOMAgent.NodeId} nodeId
   1750      * @param {string} name
   1751      */
   1752     attributeRemoved: function(nodeId, name)
   1753     {
   1754         this._domModel._attributeRemoved(nodeId, name);
   1755     },
   1756 
   1757     /**
   1758      * @param {!Array.<!DOMAgent.NodeId>} nodeIds
   1759      */
   1760     inlineStyleInvalidated: function(nodeIds)
   1761     {
   1762         this._domModel._inlineStyleInvalidated(nodeIds);
   1763     },
   1764 
   1765     /**
   1766      * @param {!DOMAgent.NodeId} nodeId
   1767      * @param {string} characterData
   1768      */
   1769     characterDataModified: function(nodeId, characterData)
   1770     {
   1771         this._domModel._characterDataModified(nodeId, characterData);
   1772     },
   1773 
   1774     /**
   1775      * @param {!DOMAgent.NodeId} parentId
   1776      * @param {!Array.<!DOMAgent.Node>} payloads
   1777      */
   1778     setChildNodes: function(parentId, payloads)
   1779     {
   1780         this._domModel._setChildNodes(parentId, payloads);
   1781     },
   1782 
   1783     /**
   1784      * @param {!DOMAgent.NodeId} nodeId
   1785      * @param {number} childNodeCount
   1786      */
   1787     childNodeCountUpdated: function(nodeId, childNodeCount)
   1788     {
   1789         this._domModel._childNodeCountUpdated(nodeId, childNodeCount);
   1790     },
   1791 
   1792     /**
   1793      * @param {!DOMAgent.NodeId} parentNodeId
   1794      * @param {!DOMAgent.NodeId} previousNodeId
   1795      * @param {!DOMAgent.Node} payload
   1796      */
   1797     childNodeInserted: function(parentNodeId, previousNodeId, payload)
   1798     {
   1799         this._domModel._childNodeInserted(parentNodeId, previousNodeId, payload);
   1800     },
   1801 
   1802     /**
   1803      * @param {!DOMAgent.NodeId} parentNodeId
   1804      * @param {!DOMAgent.NodeId} nodeId
   1805      */
   1806     childNodeRemoved: function(parentNodeId, nodeId)
   1807     {
   1808         this._domModel._childNodeRemoved(parentNodeId, nodeId);
   1809     },
   1810 
   1811     /**
   1812      * @param {!DOMAgent.NodeId} hostId
   1813      * @param {!DOMAgent.Node} root
   1814      */
   1815     shadowRootPushed: function(hostId, root)
   1816     {
   1817         this._domModel._shadowRootPushed(hostId, root);
   1818     },
   1819 
   1820     /**
   1821      * @param {!DOMAgent.NodeId} hostId
   1822      * @param {!DOMAgent.NodeId} rootId
   1823      */
   1824     shadowRootPopped: function(hostId, rootId)
   1825     {
   1826         this._domModel._shadowRootPopped(hostId, rootId);
   1827     },
   1828 
   1829     /**
   1830      * @param {!DOMAgent.NodeId} parentId
   1831      * @param {!DOMAgent.Node} pseudoElement
   1832      */
   1833     pseudoElementAdded: function(parentId, pseudoElement)
   1834     {
   1835         this._domModel._pseudoElementAdded(parentId, pseudoElement);
   1836     },
   1837 
   1838     /**
   1839      * @param {!DOMAgent.NodeId} parentId
   1840      * @param {!DOMAgent.NodeId} pseudoElementId
   1841      */
   1842     pseudoElementRemoved: function(parentId, pseudoElementId)
   1843     {
   1844         this._domModel._pseudoElementRemoved(parentId, pseudoElementId);
   1845     }
   1846 }
   1847 
   1848 /**
   1849  * @constructor
   1850  * @extends {WebInspector.SDKObject}
   1851  * @param {!WebInspector.Target} target
   1852  * @param {!DOMAgent.EventListener} payload
   1853  */
   1854 WebInspector.DOMModel.EventListener = function(target, payload)
   1855 {
   1856     WebInspector.SDKObject.call(this, target);
   1857     this._payload = payload;
   1858     var sourceName = this._payload.sourceName;
   1859     if (!sourceName) {
   1860         var script = target.debuggerModel.scriptForId(payload.location.scriptId);
   1861         sourceName = script ? script.contentURL() : "";
   1862     }
   1863     this._sourceName = sourceName;
   1864 }
   1865 
   1866 WebInspector.DOMModel.EventListener.prototype = {
   1867     /**
   1868      * @return {!DOMAgent.EventListener}
   1869      */
   1870     payload: function()
   1871     {
   1872         return this._payload;
   1873     },
   1874 
   1875     /**
   1876      * @return {?WebInspector.DOMNode}
   1877      */
   1878     node: function()
   1879     {
   1880         return this.target().domModel.nodeForId(this._payload.nodeId);
   1881     },
   1882 
   1883     /**
   1884      * @return {!WebInspector.DebuggerModel.Location}
   1885      */
   1886     location: function()
   1887     {
   1888         return WebInspector.DebuggerModel.Location.fromPayload(this.target(), this._payload.location);
   1889     },
   1890 
   1891     /**
   1892      * @return {?WebInspector.RemoteObject}
   1893      */
   1894     handler: function()
   1895     {
   1896         return this._payload.handler ? this.target().runtimeModel.createRemoteObject(this._payload.handler) : null;
   1897     },
   1898 
   1899     /**
   1900      * @return {string}
   1901      */
   1902     sourceName: function()
   1903     {
   1904         return this._sourceName;
   1905     },
   1906 
   1907     __proto__: WebInspector.SDKObject.prototype
   1908 }
   1909 
   1910 /**
   1911  * @interface
   1912  */
   1913 WebInspector.DOMNodeHighlighter = function() {
   1914 }
   1915 
   1916 WebInspector.DOMNodeHighlighter.prototype = {
   1917     /**
   1918      * @param {?WebInspector.DOMNode} node
   1919      * @param {!DOMAgent.HighlightConfig} config
   1920      * @param {!RuntimeAgent.RemoteObjectId=} objectId
   1921      */
   1922     highlightDOMNode: function(node, config, objectId) {},
   1923 
   1924     /**
   1925      * @param {boolean} enabled
   1926      * @param {boolean} inspectUAShadowDOM
   1927      * @param {!DOMAgent.HighlightConfig} config
   1928      * @param {function(?Protocol.Error)=} callback
   1929      */
   1930     setInspectModeEnabled: function(enabled, inspectUAShadowDOM, config, callback) {}
   1931 }
   1932 
   1933 /**
   1934  * @constructor
   1935  * @implements {WebInspector.DOMNodeHighlighter}
   1936  * @param {!Protocol.DOMAgent} agent
   1937  */
   1938 WebInspector.DefaultDOMNodeHighlighter = function(agent)
   1939 {
   1940     this._agent = agent;
   1941 }
   1942 
   1943 WebInspector.DefaultDOMNodeHighlighter.prototype = {
   1944     /**
   1945      * @param {?WebInspector.DOMNode} node
   1946      * @param {!DOMAgent.HighlightConfig} config
   1947      * @param {!RuntimeAgent.RemoteObjectId=} objectId
   1948      */
   1949     highlightDOMNode: function(node, config, objectId)
   1950     {
   1951         if (objectId || node)
   1952             this._agent.highlightNode(config, objectId ? undefined : node.id, objectId);
   1953         else
   1954             this._agent.hideHighlight();
   1955     },
   1956 
   1957     /**
   1958      * @param {boolean} enabled
   1959      * @param {boolean} inspectUAShadowDOM
   1960      * @param {!DOMAgent.HighlightConfig} config
   1961      * @param {function(?Protocol.Error)=} callback
   1962      */
   1963     setInspectModeEnabled: function(enabled, inspectUAShadowDOM, config, callback)
   1964     {
   1965         WebInspector.overridesSupport.setTouchEmulationSuspended(enabled);
   1966         this._agent.setInspectModeEnabled(enabled, inspectUAShadowDOM, config, callback);
   1967     }
   1968 }
   1969