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