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