Web Inspector: Uncaught Exception: null is not an object (evaluating 'this.ownerDocum...
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Models / DOMNode.js
1 /*
2  * Copyright (C) 2009, 2010 Google Inc. All rights reserved.
3  * Copyright (C) 2009 Joseph Pecoraro
4  * Copyright (C) 2013, 2016 Apple Inc. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are
8  * met:
9  *
10  *     * Redistributions of source code must retain the above copyright
11  * notice, this list of conditions and the following disclaimer.
12  *     * Redistributions in binary form must reproduce the above
13  * copyright notice, this list of conditions and the following disclaimer
14  * in the documentation and/or other materials provided with the
15  * distribution.
16  *     * Neither the name of Google Inc. nor the names of its
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 WI.DOMNode = class DOMNode extends WI.Object
34 {
35     constructor(domManager, doc, isInShadowTree, payload)
36     {
37         super();
38
39         this._domManager = domManager;
40         this._isInShadowTree = isInShadowTree;
41
42         this.id = payload.nodeId;
43         this._domManager._idToDOMNode[this.id] = this;
44
45         this._nodeType = payload.nodeType;
46         this._nodeName = payload.nodeName;
47         this._localName = payload.localName;
48         this._nodeValue = payload.nodeValue;
49         this._pseudoType = payload.pseudoType;
50         this._shadowRootType = payload.shadowRootType;
51         this._computedRole = null;
52         this._contentSecurityPolicyHash = payload.contentSecurityPolicyHash;
53
54         if (this._nodeType === Node.DOCUMENT_NODE)
55             this.ownerDocument = this;
56         else
57             this.ownerDocument = doc;
58
59         this._frame = null;
60
61         // COMPATIBILITY (iOS 12.2): DOM.Node.frameId was changed to represent the owner frame, not the content frame.
62         if (InspectorBackend.domains.Timeline && !InspectorBackend.domains.Timeline.hasEvent("programmaticCaptureStarted")) {
63             if (payload.frameId)
64                 this._frame = WI.networkManager.frameForIdentifier(payload.frameId);
65         }
66
67         if (!this._frame && this.ownerDocument)
68             this._frame = WI.networkManager.frameForIdentifier(this.ownerDocument.frameIdentifier);
69
70         this._attributes = [];
71         this._attributesMap = new Map;
72         if (payload.attributes)
73             this._setAttributesPayload(payload.attributes);
74
75         this._childNodeCount = payload.childNodeCount;
76         this._children = null;
77         this._filteredChildren = null;
78         this._filteredChildrenNeedsUpdating = true;
79
80         this._nextSibling = null;
81         this._previousSibling = null;
82         this.parentNode = null;
83
84         this._enabledPseudoClasses = [];
85
86         // FIXME: The logic around this._shadowRoots and this._children is very confusing.
87         // We eventually include shadow roots at the start of _children. However we might
88         // not have our actual children yet. So we try to defer initializing _children until
89         // we have both shadowRoots and child nodes.
90         this._shadowRoots = [];
91         if (payload.shadowRoots) {
92             for (var i = 0; i < payload.shadowRoots.length; ++i) {
93                 var root = payload.shadowRoots[i];
94                 var node = new WI.DOMNode(this._domManager, this.ownerDocument, true, root);
95                 node.parentNode = this;
96                 this._shadowRoots.push(node);
97             }
98         }
99
100         if (payload.children)
101             this._setChildrenPayload(payload.children);
102         else if (this._shadowRoots.length && !this._childNodeCount)
103             this._children = this._shadowRoots.slice();
104
105         if (this._nodeType === Node.ELEMENT_NODE)
106             this._customElementState = payload.customElementState || WI.DOMNode.CustomElementState.Builtin;
107         else
108             this._customElementState = null;
109
110         if (payload.templateContent) {
111             this._templateContent = new WI.DOMNode(this._domManager, this.ownerDocument, false, payload.templateContent);
112             this._templateContent.parentNode = this;
113         }
114
115         this._pseudoElements = new Map;
116         if (payload.pseudoElements) {
117             for (var i = 0; i < payload.pseudoElements.length; ++i) {
118                 var node = new WI.DOMNode(this._domManager, this.ownerDocument, this._isInShadowTree, payload.pseudoElements[i]);
119                 node.parentNode = this;
120                 this._pseudoElements.set(node.pseudoType(), node);
121             }
122         }
123
124         if (payload.contentDocument) {
125             this._contentDocument = new WI.DOMNode(this._domManager, null, false, payload.contentDocument);
126             this._children = [this._contentDocument];
127             this._renumber();
128         }
129
130         if (this._nodeType === Node.ELEMENT_NODE) {
131             // HTML and BODY from internal iframes should not overwrite top-level ones.
132             if (this.ownerDocument && !this.ownerDocument.documentElement && this._nodeName === "HTML")
133                 this.ownerDocument.documentElement = this;
134             if (this.ownerDocument && !this.ownerDocument.body && this._nodeName === "BODY")
135                 this.ownerDocument.body = this;
136             if (payload.documentURL)
137                 this.documentURL = payload.documentURL;
138         } else if (this._nodeType === Node.DOCUMENT_TYPE_NODE) {
139             this.publicId = payload.publicId;
140             this.systemId = payload.systemId;
141         } else if (this._nodeType === Node.DOCUMENT_NODE) {
142             this.documentURL = payload.documentURL;
143             this.xmlVersion = payload.xmlVersion;
144         } else if (this._nodeType === Node.ATTRIBUTE_NODE) {
145             this.name = payload.name;
146             this.value = payload.value;
147         }
148
149         this._domEvents = [];
150         this._lowPowerRanges = [];
151
152         if (this._shouldListenForEventListeners())
153             WI.DOMNode.addEventListener(WI.DOMNode.Event.DidFireEvent, this._handleDOMNodeDidFireEvent, this);
154     }
155
156     // Static
157
158     static getFullscreenDOMEvents(domEvents)
159     {
160         return domEvents.reduce((accumulator, current) => {
161             if (current.eventName === "webkitfullscreenchange" && current.data && (!accumulator.length || accumulator.lastValue.data.enabled !== current.data.enabled))
162                 accumulator.push(current);
163             return accumulator;
164         }, []);
165     }
166
167     static isPlayEvent(eventName)
168     {
169         return eventName === "play"
170             || eventName === "playing";
171     }
172
173     static isPauseEvent(eventName)
174     {
175         return eventName === "pause"
176             || eventName === "stall";
177     }
178
179     static isStopEvent(eventName)
180     {
181         return eventName === "emptied"
182             || eventName === "ended"
183             || eventName === "suspend";
184     }
185
186
187     // Public
188
189     get frame() { return this._frame; }
190     get domEvents() { return this._domEvents; }
191     get lowPowerRanges() { return this._lowPowerRanges; }
192
193     get attached()
194     {
195         for (let node = this; node; node = node.parentNode) {
196             if (node.ownerDocument === node)
197                 return true;
198         }
199         return false;
200     }
201
202     get children()
203     {
204         if (!this._children)
205             return null;
206
207         if (WI.settings.showShadowDOM.value)
208             return this._children;
209
210         if (this._filteredChildrenNeedsUpdating) {
211             this._filteredChildrenNeedsUpdating = false;
212             this._filteredChildren = this._children.filter(function(node) {
213                 return !node._isInShadowTree;
214             });
215         }
216
217         return this._filteredChildren;
218     }
219
220     get firstChild()
221     {
222         var children = this.children;
223
224         if (children && children.length > 0)
225             return children[0];
226
227         return null;
228     }
229
230     get lastChild()
231     {
232         var children = this.children;
233
234         if (children && children.length > 0)
235             return children.lastValue;
236
237         return null;
238     }
239
240     get nextSibling()
241     {
242         if (WI.settings.showShadowDOM.value)
243             return this._nextSibling;
244
245         var node = this._nextSibling;
246         while (node) {
247             if (!node._isInShadowTree)
248                 return node;
249             node = node._nextSibling;
250         }
251         return null;
252     }
253
254     get previousSibling()
255     {
256         if (WI.settings.showShadowDOM.value)
257             return this._previousSibling;
258
259         var node = this._previousSibling;
260         while (node) {
261             if (!node._isInShadowTree)
262                 return node;
263             node = node._previousSibling;
264         }
265         return null;
266     }
267
268     get childNodeCount()
269     {
270         var children = this.children;
271         if (children)
272             return children.length;
273
274         if (WI.settings.showShadowDOM.value)
275             return this._childNodeCount + this._shadowRoots.length;
276
277         return this._childNodeCount;
278     }
279
280     set childNodeCount(count)
281     {
282         this._childNodeCount = count;
283     }
284
285     computedRole()
286     {
287         return this._computedRole;
288     }
289
290     contentSecurityPolicyHash()
291     {
292         return this._contentSecurityPolicyHash;
293     }
294
295     hasAttributes()
296     {
297         return this._attributes.length > 0;
298     }
299
300     hasChildNodes()
301     {
302         return this.childNodeCount > 0;
303     }
304
305     hasShadowRoots()
306     {
307         return !!this._shadowRoots.length;
308     }
309
310     isInShadowTree()
311     {
312         return this._isInShadowTree;
313     }
314
315     isInUserAgentShadowTree()
316     {
317         return this._isInShadowTree && this.ancestorShadowRoot().isUserAgentShadowRoot();
318     }
319
320     isCustomElement()
321     {
322         return this._customElementState === WI.DOMNode.CustomElementState.Custom;
323     }
324
325     customElementState()
326     {
327         return this._customElementState;
328     }
329
330     isShadowRoot()
331     {
332         return !!this._shadowRootType;
333     }
334
335     isUserAgentShadowRoot()
336     {
337         return this._shadowRootType === WI.DOMNode.ShadowRootType.UserAgent;
338     }
339
340     ancestorShadowRoot()
341     {
342         if (!this._isInShadowTree)
343             return null;
344
345         let node = this;
346         while (node && !node.isShadowRoot())
347             node = node.parentNode;
348         return node;
349     }
350
351     ancestorShadowHost()
352     {
353         let shadowRoot = this.ancestorShadowRoot();
354         return shadowRoot ? shadowRoot.parentNode : null;
355     }
356
357     isPseudoElement()
358     {
359         return this._pseudoType !== undefined;
360     }
361
362     nodeType()
363     {
364         return this._nodeType;
365     }
366
367     nodeName()
368     {
369         return this._nodeName;
370     }
371
372     nodeNameInCorrectCase()
373     {
374         return this.isXMLNode() ? this.nodeName() : this.nodeName().toLowerCase();
375     }
376
377     setNodeName(name, callback)
378     {
379         DOMAgent.setNodeName(this.id, name, this._makeUndoableCallback(callback));
380     }
381
382     localName()
383     {
384         return this._localName;
385     }
386
387     templateContent()
388     {
389         return this._templateContent || null;
390     }
391
392     pseudoType()
393     {
394         return this._pseudoType;
395     }
396
397     hasPseudoElements()
398     {
399         return this._pseudoElements.size > 0;
400     }
401
402     pseudoElements()
403     {
404         return this._pseudoElements;
405     }
406
407     beforePseudoElement()
408     {
409         return this._pseudoElements.get(WI.DOMNode.PseudoElementType.Before) || null;
410     }
411
412     afterPseudoElement()
413     {
414         return this._pseudoElements.get(WI.DOMNode.PseudoElementType.After) || null;
415     }
416
417     shadowRoots()
418     {
419         return this._shadowRoots;
420     }
421
422     shadowRootType()
423     {
424         return this._shadowRootType;
425     }
426
427     nodeValue()
428     {
429         return this._nodeValue;
430     }
431
432     setNodeValue(value, callback)
433     {
434         DOMAgent.setNodeValue(this.id, value, this._makeUndoableCallback(callback));
435     }
436
437     getAttribute(name)
438     {
439         let attr = this._attributesMap.get(name);
440         return attr ? attr.value : undefined;
441     }
442
443     setAttribute(name, text, callback)
444     {
445         DOMAgent.setAttributesAsText(this.id, text, name, this._makeUndoableCallback(callback));
446     }
447
448     setAttributeValue(name, value, callback)
449     {
450         DOMAgent.setAttributeValue(this.id, name, value, this._makeUndoableCallback(callback));
451     }
452
453     attributes()
454     {
455         return this._attributes;
456     }
457
458     removeAttribute(name, callback)
459     {
460         function mycallback(error, success)
461         {
462             if (!error) {
463                 this._attributesMap.delete(name);
464                 for (var i = 0; i < this._attributes.length; ++i) {
465                     if (this._attributes[i].name === name) {
466                         this._attributes.splice(i, 1);
467                         break;
468                     }
469                 }
470             }
471
472             this._makeUndoableCallback(callback)(error);
473         }
474         DOMAgent.removeAttribute(this.id, name, mycallback.bind(this));
475     }
476
477     toggleClass(className, flag)
478     {
479         if (!className || !className.length)
480             return;
481
482         if (this.isPseudoElement()) {
483             this.parentNode.toggleClass(className, flag);
484             return;
485         }
486
487         if (this.nodeType() !== Node.ELEMENT_NODE)
488             return;
489
490         WI.RemoteObject.resolveNode(this).then((object) => {
491             function inspectedPage_node_toggleClass(className, flag) {
492                 this.classList.toggle(className, flag);
493             }
494
495             object.callFunction(inspectedPage_node_toggleClass, [className, flag]);
496             object.release();
497         });
498     }
499
500     scrollIntoView()
501     {
502         WI.RemoteObject.resolveNode(this).then((object) => {
503             function inspectedPage_node_scrollIntoView() {
504                 this.scrollIntoViewIfNeeded(true);
505             }
506
507             object.callFunction(inspectedPage_node_scrollIntoView);
508             object.release();
509         });
510     }
511
512     getChildNodes(callback)
513     {
514         if (this.children) {
515             if (callback)
516                 callback(this.children);
517             return;
518         }
519
520         function mycallback(error) {
521             if (!error && callback)
522                 callback(this.children);
523         }
524
525         DOMAgent.requestChildNodes(this.id, mycallback.bind(this));
526     }
527
528     getSubtree(depth, callback)
529     {
530         function mycallback(error)
531         {
532             if (callback)
533                 callback(error ? null : this.children);
534         }
535
536         DOMAgent.requestChildNodes(this.id, depth, mycallback.bind(this));
537     }
538
539     getOuterHTML(callback)
540     {
541         DOMAgent.getOuterHTML(this.id, callback);
542     }
543
544     setOuterHTML(html, callback)
545     {
546         DOMAgent.setOuterHTML(this.id, html, this._makeUndoableCallback(callback));
547     }
548
549     insertAdjacentHTML(position, html)
550     {
551         if (this.nodeType() !== Node.ELEMENT_NODE)
552             return;
553
554         // COMPATIBILITY (iOS 11.0): DOM.insertAdjacentHTML did not exist.
555         if (!DOMAgent.insertAdjacentHTML) {
556             WI.RemoteObject.resolveNode(this).then((object) => {
557                 function inspectedPage_node_insertAdjacentHTML(position, html) {
558                     this.insertAdjacentHTML(position, html);
559                 }
560
561                 object.callFunction(inspectedPage_node_insertAdjacentHTML, [position, html]);
562                 object.release();
563             });
564             return;
565         }
566
567         DOMAgent.insertAdjacentHTML(this.id, position, html, this._makeUndoableCallback());
568     }
569
570     removeNode(callback)
571     {
572         DOMAgent.removeNode(this.id, this._makeUndoableCallback(callback));
573     }
574
575     copyNode()
576     {
577         function copy(error, text)
578         {
579             if (!error)
580                 InspectorFrontendHost.copyText(text);
581         }
582         DOMAgent.getOuterHTML(this.id, copy);
583     }
584
585     getEventListeners(callback)
586     {
587         console.assert(WI.domManager.inspectedNode === this);
588         DOMAgent.getEventListenersForNode(this.id, callback);
589     }
590
591     accessibilityProperties(callback)
592     {
593         function accessibilityPropertiesCallback(error, accessibilityProperties)
594         {
595             if (!error && callback && accessibilityProperties) {
596                 this._computedRole = accessibilityProperties.role;
597
598                 callback({
599                     activeDescendantNodeId: accessibilityProperties.activeDescendantNodeId,
600                     busy: accessibilityProperties.busy,
601                     checked: accessibilityProperties.checked,
602                     childNodeIds: accessibilityProperties.childNodeIds,
603                     controlledNodeIds: accessibilityProperties.controlledNodeIds,
604                     current: accessibilityProperties.current,
605                     disabled: accessibilityProperties.disabled,
606                     exists: accessibilityProperties.exists,
607                     expanded: accessibilityProperties.expanded,
608                     flowedNodeIds: accessibilityProperties.flowedNodeIds,
609                     focused: accessibilityProperties.focused,
610                     ignored: accessibilityProperties.ignored,
611                     ignoredByDefault: accessibilityProperties.ignoredByDefault,
612                     invalid: accessibilityProperties.invalid,
613                     isPopupButton: accessibilityProperties.isPopUpButton,
614                     headingLevel: accessibilityProperties.headingLevel,
615                     hierarchyLevel: accessibilityProperties.hierarchyLevel,
616                     hidden: accessibilityProperties.hidden,
617                     label: accessibilityProperties.label,
618                     liveRegionAtomic: accessibilityProperties.liveRegionAtomic,
619                     liveRegionRelevant: accessibilityProperties.liveRegionRelevant,
620                     liveRegionStatus: accessibilityProperties.liveRegionStatus,
621                     mouseEventNodeId: accessibilityProperties.mouseEventNodeId,
622                     nodeId: accessibilityProperties.nodeId,
623                     ownedNodeIds: accessibilityProperties.ownedNodeIds,
624                     parentNodeId: accessibilityProperties.parentNodeId,
625                     pressed: accessibilityProperties.pressed,
626                     readonly: accessibilityProperties.readonly,
627                     required: accessibilityProperties.required,
628                     role: accessibilityProperties.role,
629                     selected: accessibilityProperties.selected,
630                     selectedChildNodeIds: accessibilityProperties.selectedChildNodeIds
631                 });
632             }
633         }
634         DOMAgent.getAccessibilityPropertiesForNode(this.id, accessibilityPropertiesCallback.bind(this));
635     }
636
637     path()
638     {
639         var path = [];
640         var node = this;
641         while (node && "index" in node && node._nodeName.length) {
642             path.push([node.index, node._nodeName]);
643             node = node.parentNode;
644         }
645         path.reverse();
646         return path.join(",");
647     }
648
649     get escapedIdSelector()
650     {
651         let id = this.getAttribute("id");
652         if (!id)
653             return "";
654
655         id = id.trim();
656         if (!id.length)
657             return "";
658
659         id = CSS.escape(id);
660         if (/[\s'"]/.test(id))
661             return `[id="${id}"]`;
662
663         return `#${id}`;
664     }
665
666     get escapedClassSelector()
667     {
668         let classes = this.getAttribute("class");
669         if (!classes)
670             return "";
671
672         classes = classes.trim();
673         if (!classes.length)
674             return "";
675
676         let foundClasses = new Set;
677         return classes.split(/\s+/).reduce((selector, className) => {
678             if (!className.length || foundClasses.has(className))
679                 return selector;
680
681             foundClasses.add(className);
682             return `${selector}.${CSS.escape(className)}`;
683         }, "");
684     }
685
686     get displayName()
687     {
688         if (this.isPseudoElement())
689             return "::" + this._pseudoType;
690         return this.nodeNameInCorrectCase() + this.escapedIdSelector + this.escapedClassSelector;
691     }
692
693     appropriateSelectorFor(justSelector)
694     {
695         if (this.isPseudoElement())
696             return this.parentNode.appropriateSelectorFor() + "::" + this._pseudoType;
697
698         let lowerCaseName = this.localName() || this.nodeName().toLowerCase();
699
700         let id = this.escapedIdSelector;
701         if (id.length)
702             return justSelector ? id : lowerCaseName + id;
703
704         let classes = this.escapedClassSelector;
705         if (classes.length)
706             return justSelector ? classes : lowerCaseName + classes;
707
708         if (lowerCaseName === "input" && this.getAttribute("type"))
709             return lowerCaseName + "[type=\"" + this.getAttribute("type") + "\"]";
710
711         return lowerCaseName;
712     }
713
714     isAncestor(node)
715     {
716         if (!node)
717             return false;
718
719         var currentNode = node.parentNode;
720         while (currentNode) {
721             if (this === currentNode)
722                 return true;
723             currentNode = currentNode.parentNode;
724         }
725         return false;
726     }
727
728     isDescendant(descendant)
729     {
730         return descendant !== null && descendant.isAncestor(this);
731     }
732
733     get ownerSVGElement()
734     {
735         if (this._nodeName === "svg")
736             return this;
737
738         if (!this.parentNode)
739             return null;
740
741         return this.parentNode.ownerSVGElement;
742     }
743
744     isSVGElement()
745     {
746         return !!this.ownerSVGElement;
747     }
748
749     didFireEvent(eventName, timestamp, data)
750     {
751         // Called from WI.DOMManager.
752
753         this._addDOMEvent({
754             eventName,
755             timestamp: WI.timelineManager.computeElapsedTime(timestamp),
756             data,
757         });
758     }
759
760     videoLowPowerChanged(timestamp, isLowPower)
761     {
762         // Called from WI.DOMManager.
763
764         console.assert(this.canEnterLowPowerMode());
765
766         let lastValue = this._lowPowerRanges.lastValue;
767
768         if (isLowPower) {
769             console.assert(!lastValue || lastValue.endTimestamp);
770             if (!lastValue || lastValue.endTimestamp)
771                 this._lowPowerRanges.push({startTimestamp: timestamp});
772         } else {
773             console.assert(!lastValue || lastValue.startTimestamp);
774             if (!lastValue)
775                 this._lowPowerRanges.push({endTimestamp: timestamp});
776             else if (lastValue.startTimestamp)
777                 lastValue.endTimestamp = timestamp;
778         }
779
780         this.dispatchEventToListeners(WI.DOMNode.Event.LowPowerChanged, {isLowPower, timestamp});
781     }
782
783     canEnterLowPowerMode()
784     {
785         return this.localName() === "video" || this.nodeName().toLowerCase() === "video";
786     }
787
788     _handleDOMNodeDidFireEvent(event)
789     {
790         if (event.target === this || !event.target.isAncestor(this))
791             return;
792
793         let domEvent = Object.shallowCopy(event.data.domEvent);
794         domEvent.originator = event.target;
795
796         this._addDOMEvent(domEvent);
797     }
798
799     _addDOMEvent(domEvent)
800     {
801         this._domEvents.push(domEvent);
802
803         this.dispatchEventToListeners(WI.DOMNode.Event.DidFireEvent, {domEvent});
804     }
805
806     _shouldListenForEventListeners()
807     {
808         let lowerCaseName = this.localName() || this.nodeName().toLowerCase();
809         return lowerCaseName === "video" || lowerCaseName === "audio";
810     }
811
812     _setAttributesPayload(attrs)
813     {
814         this._attributes = [];
815         this._attributesMap = new Map;
816         for (var i = 0; i < attrs.length; i += 2)
817             this._addAttribute(attrs[i], attrs[i + 1]);
818     }
819
820     _insertChild(prev, payload)
821     {
822         var node = new WI.DOMNode(this._domManager, this.ownerDocument, this._isInShadowTree, payload);
823         if (!prev) {
824             if (!this._children) {
825                 // First node
826                 this._children = this._shadowRoots.concat([node]);
827             } else
828                 this._children.unshift(node);
829         } else
830             this._children.splice(this._children.indexOf(prev) + 1, 0, node);
831         this._renumber();
832         return node;
833     }
834
835     _removeChild(node)
836     {
837         // FIXME: Handle removal if this is a shadow root.
838         if (node.isPseudoElement()) {
839             this._pseudoElements.delete(node.pseudoType());
840             node.parentNode = null;
841         } else {
842             this._children.splice(this._children.indexOf(node), 1);
843             node.parentNode = null;
844             this._renumber();
845         }
846     }
847
848     _setChildrenPayload(payloads)
849     {
850         // We set children in the constructor.
851         if (this._contentDocument)
852             return;
853
854         this._children = this._shadowRoots.slice();
855         for (var i = 0; i < payloads.length; ++i) {
856             var node = new WI.DOMNode(this._domManager, this.ownerDocument, this._isInShadowTree, payloads[i]);
857             this._children.push(node);
858         }
859         this._renumber();
860     }
861
862     _renumber()
863     {
864         this._filteredChildrenNeedsUpdating = true;
865
866         var childNodeCount = this._children.length;
867         if (childNodeCount === 0)
868             return;
869
870         for (var i = 0; i < childNodeCount; ++i) {
871             var child = this._children[i];
872             child.index = i;
873             child._nextSibling = i + 1 < childNodeCount ? this._children[i + 1] : null;
874             child._previousSibling = i - 1 >= 0 ? this._children[i - 1] : null;
875             child.parentNode = this;
876         }
877     }
878
879     _addAttribute(name, value)
880     {
881         let attr = {name, value, _node: this};
882         this._attributesMap.set(name, attr);
883         this._attributes.push(attr);
884     }
885
886     _setAttribute(name, value)
887     {
888         let attr = this._attributesMap.get(name);
889         if (attr)
890             attr.value = value;
891         else
892             this._addAttribute(name, value);
893     }
894
895     _removeAttribute(name)
896     {
897         let attr = this._attributesMap.get(name);
898         if (attr) {
899             this._attributes.remove(attr);
900             this._attributesMap.delete(name);
901         }
902     }
903
904     moveTo(targetNode, anchorNode, callback)
905     {
906         DOMAgent.moveTo(this.id, targetNode.id, anchorNode ? anchorNode.id : undefined, this._makeUndoableCallback(callback));
907     }
908
909     isXMLNode()
910     {
911         return !!this.ownerDocument && !!this.ownerDocument.xmlVersion;
912     }
913
914     get enabledPseudoClasses()
915     {
916         return this._enabledPseudoClasses;
917     }
918
919     setPseudoClassEnabled(pseudoClass, enabled)
920     {
921         var pseudoClasses = this._enabledPseudoClasses;
922         if (enabled) {
923             if (pseudoClasses.includes(pseudoClass))
924                 return;
925             pseudoClasses.push(pseudoClass);
926         } else {
927             if (!pseudoClasses.includes(pseudoClass))
928                 return;
929             pseudoClasses.remove(pseudoClass);
930         }
931
932         function changed(error)
933         {
934             if (!error)
935                 this.dispatchEventToListeners(WI.DOMNode.Event.EnabledPseudoClassesChanged);
936         }
937
938         CSSAgent.forcePseudoState(this.id, pseudoClasses, changed.bind(this));
939     }
940
941     _makeUndoableCallback(callback)
942     {
943         return function(error)
944         {
945             if (!error)
946                 DOMAgent.markUndoableState();
947
948             if (callback)
949                 callback.apply(null, arguments);
950         };
951     }
952 };
953
954 WI.DOMNode.Event = {
955     EnabledPseudoClassesChanged: "dom-node-enabled-pseudo-classes-did-change",
956     AttributeModified: "dom-node-attribute-modified",
957     AttributeRemoved: "dom-node-attribute-removed",
958     EventListenersChanged: "dom-node-event-listeners-changed",
959     DidFireEvent: "dom-node-did-fire-event",
960     LowPowerChanged: "dom-node-video-low-power-changed",
961 };
962
963 WI.DOMNode.PseudoElementType = {
964     Before: "before",
965     After: "after",
966 };
967
968 WI.DOMNode.ShadowRootType = {
969     UserAgent: "user-agent",
970     Closed: "closed",
971     Open: "open",
972 };
973
974 WI.DOMNode.CustomElementState = {
975     Builtin: "builtin",
976     Custom: "custom",
977     Waiting: "waiting",
978     Failed: "failed",
979 };