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