Skip to content

Commit 5f5eef1

Browse files
rniwaRazvan Caliman
authored andcommitted
Add documentation for inserting and removing a DOM Node.
* docs/Deep Dive/DOM.md
1 parent 3904adb commit 5f5eef1

File tree

1 file changed

+143
-0
lines changed

1 file changed

+143
-0
lines changed

docs/Deep Dive/DOM.md

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,146 @@ Because JavaScript can [open a new window](https://developer.mozilla.org/en-US/d
4747
under user gestures and have [access back to its opener](https://developer.mozilla.org/en-US/docs/Web/API/Window/opener),
4848
multiple web pages across multiple tabs might be able to communicate with one another via JavaScript API
4949
such as [postMessage](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage).
50+
51+
## Node’s Type and State flags
52+
53+
Each node has a set of [`TypeFlag`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/Node.h#L596),
54+
which are set at construction time and immutable, and a set of [`StateFlag`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/Node.h#L617),
55+
which can be set or unset throughout [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h)’s lifetime.
56+
Node also makes use of [`EventTargetFlag`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/EventTarget.h#L188)
57+
for indicating ownership and relationship with other objects.
58+
For example, [`TypeFlag::IsElement`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/Node.h#L600C9-L600C18)
59+
is set whenever a [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h)
60+
is a subclass of [`Element`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Element.h).
61+
[`StateFlag::IsParsingChildren`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/Node.h#L620C9-L620C26)
62+
is set whenever a [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h)
63+
is in the state of its child nodes being [parsed](https://html.spec.whatwg.org/multipage/parsing.html).
64+
[`EventTargetFlag::IsConnected`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/EventTarget.h#L193)
65+
is set whenever a [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h) is [connected](https://dom.spec.whatwg.org/#connected).
66+
These flags are updated by each subclass of [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h) throughout its lifetime.
67+
Note that these flags are set or unset within a specific function.
68+
For example, [`EventTargetFlag::IsConnected`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/EventTarget.h#L193)
69+
is set in [`Node::insertedIntoAncestor`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/Node.cpp#L1474).
70+
It means that any code which runs prior to [`Node::insertedIntoAncestor`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/Node.cpp#L1474)
71+
running on a given `Node` will observe an outdated value of
72+
[`EventTargetFlag::IsConnected`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/EventTarget.h#L193).
73+
74+
## Insertion and Removal of DOM Nodes
75+
76+
In order to construct a [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model) tree,
77+
we create a DOM [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h)
78+
and [insert](https://github.com/WebKit/WebKit/blob/0a9ebe9a13e511c2848b7ed3dfd887be266d42bb/Source/WebCore/dom/ContainerNode.cpp#L279)
79+
it into a [`ContainerNode`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/ContainerNode.h)
80+
such as [`Document`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Document.h)
81+
and [`Element`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Element.cpp).
82+
An insertion of a node starts with a [validation](https://github.com/WebKit/WebKit/blob/0a9ebe9a13e511c2848b7ed3dfd887be266d42bb/Source/WebCore/dom/ContainerNode.cpp#L477),
83+
then [removal of the node from its old parent](https://github.com/WebKit/WebKit/blob/0a9ebe9a13e511c2848b7ed3dfd887be266d42bb/Source/WebCore/dom/ContainerNode.cpp#L329)
84+
if there is any. Either of these two steps can synchronously execute JavaScript via [mutation events](https://developer.mozilla.org/en-US/docs/Web/API/MutationEvent)
85+
and therefore can synchronously mutate tree’s state.
86+
Because of that, we need to [check the validity again](https://github.com/WebKit/WebKit/blob/0a9ebe9a13e511c2848b7ed3dfd887be266d42bb/Source/WebCore/dom/ContainerNode.cpp#L866)
87+
before proceeding with the insertion.
88+
89+
An actual insertion of a DOM [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h)
90+
is implemented using [`executeNodeInsertionWithScriptAssertion`](https://github.com/WebKit/WebKit/blob/0a9ebe9a13e511c2848b7ed3dfd887be266d42bb/Source/WebCore/dom/ContainerNode.cpp#L279)
91+
or [`executeParserNodeInsertionIntoIsolatedTreeWithoutNotifyingParent`](https://github.com/WebKit/WebKit/blob/0a9ebe9a13e511c2848b7ed3dfd887be266d42bb/Source/WebCore/dom/ContainerNode.cpp#L310).
92+
To start off, these functions instantiate a [RAII](https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization)-style
93+
object [`ScriptDisallowedScope`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/ScriptDisallowedScope.h),
94+
which forbids JavaScript execution during its lifetime, do the insertion,
95+
then [notify the child and its descendant](https://github.com/WebKit/WebKit/blob/b7bd89ba227d492f2eeefca628afea8480f556d9/Source/WebCore/dom/ContainerNodeAlgorithms.cpp#L97)
96+
with [`insertedIntoAncestor`](https://github.com/WebKit/WebKit/blob/b7bd89ba227d492f2eeefca628afea8480f556d9/Source/WebCore/dom/Node.h#L474).
97+
Note that [`insertedIntoAncestor`](https://github.com/WebKit/WebKit/blob/b7bd89ba227d492f2eeefca628afea8480f556d9/Source/WebCore/dom/Node.h#L474)
98+
can be called when a given [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h)
99+
[becomes connected](https://html.spec.whatwg.org/multipage/infrastructure.html#becomes-connected)
100+
to a [`Document`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Document.h), or it’s inserted into a disconnected subtree.
101+
It’s not correct to assume that `this` [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h)
102+
is always [connected](https://dom.spec.whatwg.org/#connected) to a
103+
[`Document`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Document.h) in `insertedIntoAncestor`.
104+
To run code only when a [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h)
105+
[becomes connected](https://html.spec.whatwg.org/multipage/infrastructure.html#becomes-connected) to a document,
106+
check [`InsertionType`](https://github.com/WebKit/WebKit/blob/b7bd89ba227d492f2eeefca628afea8480f556d9/Source/WebCore/dom/Node.h#L468)’s
107+
[`connectedToDocument`](https://github.com/WebKit/WebKit/blob/b7bd89ba227d492f2eeefca628afea8480f556d9/Source/WebCore/dom/Node.h#L469) boolean.
108+
It’s also not necessarily true that this [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h)’s immediate parent node changed.
109+
It could be this [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h)’s ancestor that got inserted into a new parent.
110+
To run code only when this [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h)’s immediate parent had changed,
111+
check if node’s parent node matches [`parentOfInsertedTree`](https://github.com/WebKit/WebKit/blob/b7bd89ba227d492f2eeefca628afea8480f556d9/Source/WebCore/dom/Node.h#L474).
112+
There are cases in which code must run whenever its [`TreeScope`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/TreeScope.h)
113+
([`ShadowRoot`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/ShadowRoot.h)
114+
or [`Document`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Document.h)) had changed.
115+
In this case, check [`InsertionType`](https://github.com/WebKit/WebKit/blob/b7bd89ba227d492f2eeefca628afea8480f556d9/Source/WebCore/dom/Node.h#L468)’s
116+
[`treeScopeChanged`](https://github.com/WebKit/WebKit/blob/b7bd89ba227d492f2eeefca628afea8480f556d9/Source/WebCore/dom/Node.h#L470) boolean.
117+
In all cases, it’s vital that no code invoked by `insertedIntoAncestor` attempts to execute JavaScript synchronously, for example, by dispatching an event.
118+
Doing so will result in a [release assert](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/bindings/js/ScriptController.cpp#L794) (i.e. crash).
119+
If an element must dispatch events or otherwise execute arbitrary author JavaScript,
120+
return [`NeedsPostInsertionCallback`](https://github.com/WebKit/WebKit/blob/b7bd89ba227d492f2eeefca628afea8480f556d9/Source/WebCore/dom/Node.h#L466) from `insertedIntoAncestor`.
121+
This will result in a call to [`didFinishInsertingNode`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h#L475C18-L475C40)
122+
which unlike `insertedIntoAncestor` allows script execution (it gets called only after
123+
[`ScriptDisallowedScope`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/ScriptDisallowedScope.h) has been out of scope).
124+
But note that the tree’s state may have been mutated by other scripts between when `insertedIntoAncestor` is called and by when `didFinishInsertingNode` is called
125+
so it’s not safe to assume any tree state condition which was true during `insertedIntoAncestor` to be true in `didFinishInsertingNode`.
126+
It’s also not safe to leave Node in an inconsistent state at the end of `insertedIntoAncestor`
127+
because JavaScript may invoke any API on such a Node between `insertedIntoAncestor` and `didFinishInsertingNode`.
128+
After invoking `insertedIntoAncestor`, these functions invoke
129+
[`childrenChanged`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/ContainerNode.h#L111) on the new parent.
130+
This function has the first opportunity to execute any JavaScript in response to a child node being inserted.
131+
[`HTMLScriptElement`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/html/HTMLScriptElement.h),
132+
for example, may execute its script in [its `childrenChanged`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/ScriptElement.cpp#L92).
133+
Finally, the functions will invoke [`didFinishInsertingNode`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/Node.h#L475)
134+
on [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h)s which returned
135+
[`NeedsPostInsertionCallback`](https://github.com/WebKit/WebKit/blob/b7bd89ba227d492f2eeefca628afea8480f556d9/Source/WebCore/dom/Node.h#L466)
136+
from its `insertedIntoAncestor` and [trigger mutation events](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/ContainerNode.cpp#L1056)
137+
such as `DOMNodeInsertedEvent`.
138+
139+
The removal of a DOM [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h) from its parent is implemented using
140+
[`ContainerNode::removeAllChildrenWithScriptAssertion`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/ContainerNode.cpp#L89)
141+
and [`ContainerNode::removeChildWithScriptAssertion`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/ContainerNode.cpp#L180).
142+
These functions first [dispatch mutation events](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/ContainerNode.cpp#L1076)
143+
and check if child’s parent is still the same container node.
144+
If it’s not, we stop and exit early. Next, they [disconnect any subframes](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/ContainerNodeAlgorithms.cpp#L263)
145+
in the subtree to be removed. These functions then instantiate a [RAII](https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization)-style object
146+
[`ScriptDisallowedScope`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/ScriptDisallowedScope.h),
147+
which forbids JavaScript execution during its lifetime like the insertion counterparts,
148+
and [notify](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/Document.cpp#L5828)
149+
[`Document`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Document.h) of the node’s removal so that objects such as
150+
[`NodeIterator`](https://developer.mozilla.org/en-US/docs/Web/API/NodeIterator) and [`Range`](https://developer.mozilla.org/en-US/docs/Web/API/Range) can be updated.
151+
The functions will then do the removal and notify the child and its descendant with
152+
[`removedFromAncestor`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/ContainerNodeAlgorithms.cpp#L177).
153+
Note that [`removedFromAncestor`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/ContainerNodeAlgorithms.cpp#L177)
154+
can be called when a given [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h)
155+
[becomes disconnected](https://html.spec.whatwg.org/multipage/infrastructure.html#becomes-disconnected)
156+
from a [`Document`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Document.h), or it’s removed from an already disconnected subtree.
157+
It’s not correct to assume that `this` [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h)
158+
used to be [connected](https://dom.spec.whatwg.org/#connected) to a [`Document`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Document.h) in `removedFromAncestor`.
159+
To run code only when a [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h)
160+
[becomes disconnected](https://html.spec.whatwg.org/multipage/infrastructure.html#becomes-disconnected) from a document,
161+
check [`RemovalType`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/Node.h#L477)’s
162+
[`disconnectedFromDocument`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/Node.h#L478) boolean.
163+
It’s also not necessarily true that this [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h)’s immediate parent node changed.
164+
It could be this [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h)’s ancestor that got removed from its old parent.
165+
To run code only when this [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h)’s immediate parent had changed,
166+
check if node’s parent node is `nullptr`.
167+
To run code whenever its [`TreeScope`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/TreeScope.h)
168+
([`ShadowRoot`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/ShadowRoot.h)
169+
or [`Document`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Document.h)) had changed,
170+
check [`RemovalType`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/Node.h#L477)’s
171+
[`treeScopeChanged`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/Node.h#L479C14-L479C30) boolean.
172+
In all cases, it’s vital that no code invoked by
173+
[`removedFromAncestor`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/ContainerNodeAlgorithms.cpp#L177)
174+
attempts to execute JavaScript synchronously, for example, by dispatching an event.
175+
Doing so will result in a [release assert](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/bindings/js/ScriptController.cpp#L794) (i.e. crash).
176+
If an element must dispatch events or otherwise execute arbitrary author JavaScript,
177+
[queue a task](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/EventLoop.h#L206) to do so.
178+
After invoking `removedFromAncestor`, these functions invoke
179+
[`childrenChanged`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/ContainerNode.h#L111) on the old parent.
180+
181+
Additionally, certain [`StateFlag`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/Node.h#L617) and
182+
[`EventTargetFlag`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/EventTarget.h#L188)
183+
might be outdated in `insertedIntoAncestor` and `removedFromAncestor`.
184+
For example, [`EventTargetFlag::IsConnected`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/EventTarget.h#L193)
185+
flag is not set or unset until [`Node::insertedIntoAncestor`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/Node.cpp#L1474)
186+
or [`Node::removedFromAncestor`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/Node.cpp#L1486) is called.
187+
Accessing other node’s states and member functions are even trickier.
188+
Because `insertedIntoAncestor` or `removedFromAncestor` may not have been called on such nodes,
189+
functions like [`getElementById`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/TreeScope.h#L83)
190+
and [`rootNode`](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/dom/ContainerNode.h#L209)
191+
will return wrong results for those nodes.
192+
Code which runs inside these functions must carefully [avoid these pitfalls](https://github.com/WebKit/WebKit/blob/5ee1e908b6ed778eca6b6a72997648b10d4bcbf4/Source/WebCore/html/FormAssociatedElement.cpp#L62).

0 commit comments

Comments
 (0)