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