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