Web Inspector: support undo/redo of insertAdjacentHTML
[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(domTreeManager, doc, isInShadowTree, payload)
36     {
37         super();
38
39         this._domTreeManager = domTreeManager;
40         this._isInShadowTree = isInShadowTree;
41
42         this.id = payload.nodeId;
43         this._domTreeManager._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 = payload.role;
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._attributes = [];
60         this._attributesMap = new Map;
61         if (payload.attributes)
62             this._setAttributesPayload(payload.attributes);
63
64         this._childNodeCount = payload.childNodeCount;
65         this._children = null;
66         this._filteredChildren = null;
67         this._filteredChildrenNeedsUpdating = true;
68
69         this._nextSibling = null;
70         this._previousSibling = null;
71         this.parentNode = null;
72
73         this._enabledPseudoClasses = [];
74
75         // FIXME: The logic around this._shadowRoots and this._children is very confusing.
76         // We eventually include shadow roots at the start of _children. However we might
77         // not have our actual children yet. So we try to defer initializing _children until
78         // we have both shadowRoots and child nodes.
79         this._shadowRoots = [];
80         if (payload.shadowRoots) {
81             for (var i = 0; i < payload.shadowRoots.length; ++i) {
82                 var root = payload.shadowRoots[i];
83                 var node = new WI.DOMNode(this._domTreeManager, this.ownerDocument, true, root);
84                 node.parentNode = this;
85                 this._shadowRoots.push(node);
86             }
87         }
88
89         if (payload.children)
90             this._setChildrenPayload(payload.children);
91         else if (this._shadowRoots.length && !this._childNodeCount)
92             this._children = this._shadowRoots.slice();
93
94         if (this._nodeType === Node.ELEMENT_NODE)
95             this._customElementState = payload.customElementState || WI.DOMNode.CustomElementState.Builtin;
96         else
97             this._customElementState = null;
98
99         if (payload.templateContent) {
100             this._templateContent = new WI.DOMNode(this._domTreeManager, this.ownerDocument, false, payload.templateContent);
101             this._templateContent.parentNode = this;
102         }
103
104         this._pseudoElements = new Map;
105         if (payload.pseudoElements) {
106             for (var i = 0; i < payload.pseudoElements.length; ++i) {
107                 var node = new WI.DOMNode(this._domTreeManager, this.ownerDocument, this._isInShadowTree, payload.pseudoElements[i]);
108                 node.parentNode = this;
109                 this._pseudoElements.set(node.pseudoType(), node);
110             }
111         }
112
113         if (payload.contentDocument) {
114             this._contentDocument = new WI.DOMNode(this._domTreeManager, null, false, payload.contentDocument);
115             this._children = [this._contentDocument];
116             this._renumber();
117         }
118
119         if (payload.frameId)
120             this._frameIdentifier = payload.frameId;
121
122         if (this._nodeType === Node.ELEMENT_NODE) {
123             // HTML and BODY from internal iframes should not overwrite top-level ones.
124             if (this.ownerDocument && !this.ownerDocument.documentElement && this._nodeName === "HTML")
125                 this.ownerDocument.documentElement = this;
126             if (this.ownerDocument && !this.ownerDocument.body && this._nodeName === "BODY")
127                 this.ownerDocument.body = this;
128             if (payload.documentURL)
129                 this.documentURL = payload.documentURL;
130         } else if (this._nodeType === Node.DOCUMENT_TYPE_NODE) {
131             this.publicId = payload.publicId;
132             this.systemId = payload.systemId;
133         } else if (this._nodeType === Node.DOCUMENT_NODE) {
134             this.documentURL = payload.documentURL;
135             this.xmlVersion = payload.xmlVersion;
136         } else if (this._nodeType === Node.ATTRIBUTE_NODE) {
137             this.name = payload.name;
138             this.value = payload.value;
139         }
140     }
141
142     // Public
143
144     get frameIdentifier()
145     {
146         return this._frameIdentifier || this.ownerDocument.frameIdentifier;
147     }
148
149     get frame()
150     {
151         if (!this._frame)
152             this._frame = WI.frameResourceManager.frameForIdentifier(this.frameIdentifier);
153         return this._frame;
154     }
155
156     get children()
157     {
158         if (!this._children)
159             return null;
160
161         if (WI.showShadowDOMSetting.value)
162             return this._children;
163
164         if (this._filteredChildrenNeedsUpdating) {
165             this._filteredChildrenNeedsUpdating = false;
166             this._filteredChildren = this._children.filter(function(node) {
167                 return !node._isInShadowTree;
168             });
169         }
170
171         return this._filteredChildren;
172     }
173
174     get firstChild()
175     {
176         var children = this.children;
177
178         if (children && children.length > 0)
179             return children[0];
180
181         return null;
182     }
183
184     get lastChild()
185     {
186         var children = this.children;
187
188         if (children && children.length > 0)
189             return children.lastValue;
190
191         return null;
192     }
193
194     get nextSibling()
195     {
196         if (WI.showShadowDOMSetting.value)
197             return this._nextSibling;
198
199         var node = this._nextSibling;
200         while (node) {
201             if (!node._isInShadowTree)
202                 return node;
203             node = node._nextSibling;
204         }
205         return null;
206     }
207
208     get previousSibling()
209     {
210         if (WI.showShadowDOMSetting.value)
211             return this._previousSibling;
212
213         var node = this._previousSibling;
214         while (node) {
215             if (!node._isInShadowTree)
216                 return node;
217             node = node._previousSibling;
218         }
219         return null;
220     }
221
222     get childNodeCount()
223     {
224         var children = this.children;
225         if (children)
226             return children.length;
227
228         if (WI.showShadowDOMSetting.value)
229             return this._childNodeCount + this._shadowRoots.length;
230
231         return this._childNodeCount;
232     }
233
234     set childNodeCount(count)
235     {
236         this._childNodeCount = count;
237     }
238
239     computedRole()
240     {
241         return this._computedRole;
242     }
243
244     contentSecurityPolicyHash()
245     {
246         return this._contentSecurityPolicyHash;
247     }
248
249     hasAttributes()
250     {
251         return this._attributes.length > 0;
252     }
253
254     hasChildNodes()
255     {
256         return this.childNodeCount > 0;
257     }
258
259     hasShadowRoots()
260     {
261         return !!this._shadowRoots.length;
262     }
263
264     isInShadowTree()
265     {
266         return this._isInShadowTree;
267     }
268
269     isInUserAgentShadowTree()
270     {
271         return this._isInShadowTree && this.ancestorShadowRoot().isUserAgentShadowRoot();
272     }
273
274     isCustomElement()
275     {
276         return this._customElementState === WI.DOMNode.CustomElementState.Custom;
277     }
278
279     customElementState()
280     {
281         return this._customElementState;
282     }
283
284     isShadowRoot()
285     {
286         return !!this._shadowRootType;
287     }
288
289     isUserAgentShadowRoot()
290     {
291         return this._shadowRootType === WI.DOMNode.ShadowRootType.UserAgent;
292     }
293
294     ancestorShadowRoot()
295     {
296         if (!this._isInShadowTree)
297             return null;
298
299         let node = this;
300         while (node && !node.isShadowRoot())
301             node = node.parentNode;
302         return node;
303     }
304
305     ancestorShadowHost()
306     {
307         let shadowRoot = this.ancestorShadowRoot();
308         return shadowRoot ? shadowRoot.parentNode : null;
309     }
310
311     isPseudoElement()
312     {
313         return this._pseudoType !== undefined;
314     }
315
316     nodeType()
317     {
318         return this._nodeType;
319     }
320
321     nodeName()
322     {
323         return this._nodeName;
324     }
325
326     nodeNameInCorrectCase()
327     {
328         return this.isXMLNode() ? this.nodeName() : this.nodeName().toLowerCase();
329     }
330
331     setNodeName(name, callback)
332     {
333         DOMAgent.setNodeName(this.id, name, this._makeUndoableCallback(callback));
334     }
335
336     localName()
337     {
338         return this._localName;
339     }
340
341     templateContent()
342     {
343         return this._templateContent || null;
344     }
345
346     pseudoType()
347     {
348         return this._pseudoType;
349     }
350
351     hasPseudoElements()
352     {
353         return this._pseudoElements.size > 0;
354     }
355
356     pseudoElements()
357     {
358         return this._pseudoElements;
359     }
360
361     beforePseudoElement()
362     {
363         return this._pseudoElements.get(WI.DOMNode.PseudoElementType.Before) || null;
364     }
365
366     afterPseudoElement()
367     {
368         return this._pseudoElements.get(WI.DOMNode.PseudoElementType.After) || null;
369     }
370
371     shadowRoots()
372     {
373         return this._shadowRoots;
374     }
375
376     shadowRootType()
377     {
378         return this._shadowRootType;
379     }
380
381     nodeValue()
382     {
383         return this._nodeValue;
384     }
385
386     setNodeValue(value, callback)
387     {
388         DOMAgent.setNodeValue(this.id, value, this._makeUndoableCallback(callback));
389     }
390
391     getAttribute(name)
392     {
393         let attr = this._attributesMap.get(name);
394         return attr ? attr.value : undefined;
395     }
396
397     setAttribute(name, text, callback)
398     {
399         DOMAgent.setAttributesAsText(this.id, text, name, this._makeUndoableCallback(callback));
400     }
401
402     setAttributeValue(name, value, callback)
403     {
404         DOMAgent.setAttributeValue(this.id, name, value, this._makeUndoableCallback(callback));
405     }
406
407     attributes()
408     {
409         return this._attributes;
410     }
411
412     removeAttribute(name, callback)
413     {
414         function mycallback(error, success)
415         {
416             if (!error) {
417                 this._attributesMap.delete(name);
418                 for (var i = 0; i < this._attributes.length; ++i) {
419                     if (this._attributes[i].name === name) {
420                         this._attributes.splice(i, 1);
421                         break;
422                     }
423                 }
424             }
425
426             this._makeUndoableCallback(callback)(error);
427         }
428         DOMAgent.removeAttribute(this.id, name, mycallback.bind(this));
429     }
430
431     toggleClass(className, flag)
432     {
433         if (!className || !className.length)
434             return;
435
436         if (this.isPseudoElement()) {
437             this.parentNode.toggleClass(className, flag);
438             return;
439         }
440
441         if (this.nodeType() !== Node.ELEMENT_NODE)
442             return;
443
444         WI.RemoteObject.resolveNode(this).then((object) => {
445             function inspectedPage_node_toggleClass(className, flag) {
446                 this.classList.toggle(className, flag);
447             }
448
449             object.callFunction(inspectedPage_node_toggleClass, [className, flag]);
450             object.release();
451         });
452     }
453
454     scrollIntoView()
455     {
456         WI.RemoteObject.resolveNode(this).then((object) => {
457             function inspectedPage_node_scrollIntoView() {
458                 this.scrollIntoViewIfNeeded(true);
459             }
460
461             object.callFunction(inspectedPage_node_scrollIntoView);
462             object.release();
463         });
464     }
465
466     getChildNodes(callback)
467     {
468         if (this.children) {
469             if (callback)
470                 callback(this.children);
471             return;
472         }
473
474         function mycallback(error) {
475             if (!error && callback)
476                 callback(this.children);
477         }
478
479         DOMAgent.requestChildNodes(this.id, mycallback.bind(this));
480     }
481
482     getSubtree(depth, callback)
483     {
484         function mycallback(error)
485         {
486             if (callback)
487                 callback(error ? null : this.children);
488         }
489
490         DOMAgent.requestChildNodes(this.id, depth, mycallback.bind(this));
491     }
492
493     getOuterHTML(callback)
494     {
495         DOMAgent.getOuterHTML(this.id, callback);
496     }
497
498     setOuterHTML(html, callback)
499     {
500         DOMAgent.setOuterHTML(this.id, html, this._makeUndoableCallback(callback));
501     }
502
503     insertAdjacentHTML(position, html)
504     {
505         if (this.nodeType() !== Node.ELEMENT_NODE)
506             return;
507
508         // COMPATIBILITY (iOS 11.0): DOM.insertAdjacentHTML did not exist.
509         if (!DOMAgent.insertAdjacentHTML) {
510             WI.RemoteObject.resolveNode(this).then((object) => {
511                 function inspectedPage_node_insertAdjacentHTML(position, html) {
512                     this.insertAdjacentHTML(position, html);
513                 }
514
515                 object.callFunction(inspectedPage_node_insertAdjacentHTML, [position, html]);
516                 object.release();
517             });
518             return;
519         }
520
521         DOMAgent.insertAdjacentHTML(this.id, position, html, this._makeUndoableCallback());
522     }
523
524     removeNode(callback)
525     {
526         DOMAgent.removeNode(this.id, this._makeUndoableCallback(callback));
527     }
528
529     copyNode()
530     {
531         function copy(error, text)
532         {
533             if (!error)
534                 InspectorFrontendHost.copyText(text);
535         }
536         DOMAgent.getOuterHTML(this.id, copy);
537     }
538
539     getEventListeners(callback)
540     {
541         DOMAgent.getEventListenersForNode(this.id, callback);
542     }
543
544     accessibilityProperties(callback)
545     {
546         function accessibilityPropertiesCallback(error, accessibilityProperties)
547         {
548             if (!error && callback && accessibilityProperties) {
549                 callback({
550                     activeDescendantNodeId: accessibilityProperties.activeDescendantNodeId,
551                     busy: accessibilityProperties.busy,
552                     checked: accessibilityProperties.checked,
553                     childNodeIds: accessibilityProperties.childNodeIds,
554                     controlledNodeIds: accessibilityProperties.controlledNodeIds,
555                     current: accessibilityProperties.current,
556                     disabled: accessibilityProperties.disabled,
557                     exists: accessibilityProperties.exists,
558                     expanded: accessibilityProperties.expanded,
559                     flowedNodeIds: accessibilityProperties.flowedNodeIds,
560                     focused: accessibilityProperties.focused,
561                     ignored: accessibilityProperties.ignored,
562                     ignoredByDefault: accessibilityProperties.ignoredByDefault,
563                     invalid: accessibilityProperties.invalid,
564                     isPopupButton: accessibilityProperties.isPopUpButton,
565                     headingLevel: accessibilityProperties.headingLevel,
566                     hierarchyLevel: accessibilityProperties.hierarchyLevel,
567                     hidden: accessibilityProperties.hidden,
568                     label: accessibilityProperties.label,
569                     liveRegionAtomic: accessibilityProperties.liveRegionAtomic,
570                     liveRegionRelevant: accessibilityProperties.liveRegionRelevant,
571                     liveRegionStatus: accessibilityProperties.liveRegionStatus,
572                     mouseEventNodeId: accessibilityProperties.mouseEventNodeId,
573                     nodeId: accessibilityProperties.nodeId,
574                     ownedNodeIds: accessibilityProperties.ownedNodeIds,
575                     parentNodeId: accessibilityProperties.parentNodeId,
576                     pressed: accessibilityProperties.pressed,
577                     readonly: accessibilityProperties.readonly,
578                     required: accessibilityProperties.required,
579                     role: accessibilityProperties.role,
580                     selected: accessibilityProperties.selected,
581                     selectedChildNodeIds: accessibilityProperties.selectedChildNodeIds
582                 });
583             }
584         }
585         DOMAgent.getAccessibilityPropertiesForNode(this.id, accessibilityPropertiesCallback.bind(this));
586     }
587
588     path()
589     {
590         var path = [];
591         var node = this;
592         while (node && "index" in node && node._nodeName.length) {
593             path.push([node.index, node._nodeName]);
594             node = node.parentNode;
595         }
596         path.reverse();
597         return path.join(",");
598     }
599
600     get escapedIdSelector()
601     {
602         let id = this.getAttribute("id");
603         if (!id)
604             return "";
605
606         id = id.trim();
607         if (!id.length)
608             return "";
609
610         id = CSS.escape(id);
611         if (/[\s'"]/.test(id))
612             return `[id="${id}"]`;
613
614         return `#${id}`;
615     }
616
617     get escapedClassSelector()
618     {
619         let classes = this.getAttribute("class");
620         if (!classes)
621             return "";
622
623         classes = classes.trim();
624         if (!classes.length)
625             return "";
626
627         let foundClasses = new Set;
628         return classes.split(/\s+/).reduce((selector, className) => {
629             if (!className.length || foundClasses.has(className))
630                 return selector;
631
632             foundClasses.add(className);
633             return `${selector}.${CSS.escape(className)}`;
634         }, "");
635     }
636
637     get displayName()
638     {
639         return this.nodeNameInCorrectCase() + this.escapedIdSelector + this.escapedClassSelector;
640     }
641
642     appropriateSelectorFor(justSelector)
643     {
644         if (this.isPseudoElement())
645             return this.parentNode.appropriateSelectorFor() + "::" + this._pseudoType;
646
647         let lowerCaseName = this.localName() || this.nodeName().toLowerCase();
648
649         let id = this.escapedIdSelector;
650         if (id.length)
651             return justSelector ? id : lowerCaseName + id;
652
653         let classes = this.escapedClassSelector;
654         if (classes.length)
655             return justSelector ? classes : lowerCaseName + classes;
656
657         if (lowerCaseName === "input" && this.getAttribute("type"))
658             return lowerCaseName + "[type=\"" + this.getAttribute("type") + "\"]";
659
660         return lowerCaseName;
661     }
662
663     isAncestor(node)
664     {
665         if (!node)
666             return false;
667
668         var currentNode = node.parentNode;
669         while (currentNode) {
670             if (this === currentNode)
671                 return true;
672             currentNode = currentNode.parentNode;
673         }
674         return false;
675     }
676
677     isDescendant(descendant)
678     {
679         return descendant !== null && descendant.isAncestor(this);
680     }
681
682     get ownerSVGElement()
683     {
684         if (this._nodeName === "svg")
685             return this;
686
687         if (!this.parentNode)
688             return null;
689
690         return this.parentNode.ownerSVGElement;
691     }
692
693     isSVGElement()
694     {
695         return !!this.ownerSVGElement;
696     }
697
698     _setAttributesPayload(attrs)
699     {
700         this._attributes = [];
701         this._attributesMap = new Map;
702         for (var i = 0; i < attrs.length; i += 2)
703             this._addAttribute(attrs[i], attrs[i + 1]);
704     }
705
706     _insertChild(prev, payload)
707     {
708         var node = new WI.DOMNode(this._domTreeManager, this.ownerDocument, this._isInShadowTree, payload);
709         if (!prev) {
710             if (!this._children) {
711                 // First node
712                 this._children = this._shadowRoots.concat([node]);
713             } else
714                 this._children.unshift(node);
715         } else
716             this._children.splice(this._children.indexOf(prev) + 1, 0, node);
717         this._renumber();
718         return node;
719     }
720
721     _removeChild(node)
722     {
723         // FIXME: Handle removal if this is a shadow root.
724         if (node.isPseudoElement()) {
725             this._pseudoElements.delete(node.pseudoType());
726             node.parentNode = null;
727         } else {
728             this._children.splice(this._children.indexOf(node), 1);
729             node.parentNode = null;
730             this._renumber();
731         }
732     }
733
734     _setChildrenPayload(payloads)
735     {
736         // We set children in the constructor.
737         if (this._contentDocument)
738             return;
739
740         this._children = this._shadowRoots.slice();
741         for (var i = 0; i < payloads.length; ++i) {
742             var node = new WI.DOMNode(this._domTreeManager, this.ownerDocument, this._isInShadowTree, payloads[i]);
743             this._children.push(node);
744         }
745         this._renumber();
746     }
747
748     _renumber()
749     {
750         this._filteredChildrenNeedsUpdating = true;
751
752         var childNodeCount = this._children.length;
753         if (childNodeCount === 0)
754             return;
755
756         for (var i = 0; i < childNodeCount; ++i) {
757             var child = this._children[i];
758             child.index = i;
759             child._nextSibling = i + 1 < childNodeCount ? this._children[i + 1] : null;
760             child._previousSibling = i - 1 >= 0 ? this._children[i - 1] : null;
761             child.parentNode = this;
762         }
763     }
764
765     _addAttribute(name, value)
766     {
767         let attr = {name, value, _node: this};
768         this._attributesMap.set(name, attr);
769         this._attributes.push(attr);
770     }
771
772     _setAttribute(name, value)
773     {
774         let attr = this._attributesMap.get(name);
775         if (attr)
776             attr.value = value;
777         else
778             this._addAttribute(name, value);
779     }
780
781     _removeAttribute(name)
782     {
783         let attr = this._attributesMap.get(name);
784         if (attr) {
785             this._attributes.remove(attr);
786             this._attributesMap.delete(name);
787         }
788     }
789
790     moveTo(targetNode, anchorNode, callback)
791     {
792         DOMAgent.moveTo(this.id, targetNode.id, anchorNode ? anchorNode.id : undefined, this._makeUndoableCallback(callback));
793     }
794
795     isXMLNode()
796     {
797         return !!this.ownerDocument && !!this.ownerDocument.xmlVersion;
798     }
799
800     get enabledPseudoClasses()
801     {
802         return this._enabledPseudoClasses;
803     }
804
805     setPseudoClassEnabled(pseudoClass, enabled)
806     {
807         var pseudoClasses = this._enabledPseudoClasses;
808         if (enabled) {
809             if (pseudoClasses.includes(pseudoClass))
810                 return;
811             pseudoClasses.push(pseudoClass);
812         } else {
813             if (!pseudoClasses.includes(pseudoClass))
814                 return;
815             pseudoClasses.remove(pseudoClass);
816         }
817
818         function changed(error)
819         {
820             if (!error)
821                 this.dispatchEventToListeners(WI.DOMNode.Event.EnabledPseudoClassesChanged);
822         }
823
824         CSSAgent.forcePseudoState(this.id, pseudoClasses, changed.bind(this));
825     }
826
827     _makeUndoableCallback(callback)
828     {
829         return function(error)
830         {
831             if (!error)
832                 DOMAgent.markUndoableState();
833
834             if (callback)
835                 callback.apply(null, arguments);
836         };
837     }
838 };
839
840 WI.DOMNode.Event = {
841     EnabledPseudoClassesChanged: "dom-node-enabled-pseudo-classes-did-change",
842     AttributeModified: "dom-node-attribute-modified",
843     AttributeRemoved: "dom-node-attribute-removed",
844     EventListenersChanged: "dom-node-event-listeners-changed",
845 };
846
847 WI.DOMNode.PseudoElementType = {
848     Before: "before",
849     After: "after",
850 };
851
852 WI.DOMNode.ShadowRootType = {
853     UserAgent: "user-agent",
854     Closed: "closed",
855     Open: "open",
856 };
857
858 WI.DOMNode.CustomElementState = {
859     Builtin: "builtin",
860     Custom: "custom",
861     Waiting: "waiting",
862     Failed: "failed",
863 };