Drop unnecessary null check in removeDetachedChildrenInContainer()
[WebKit-https.git] / Source / WebCore / dom / ContainerNodeAlgorithms.cpp
1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  *           (C) 2001 Dirk Mueller (mueller@kde.org)
5  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2015 Apple Inc. All rights reserved.
6  * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
7  * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
8  * Copyright (C) 2012 Google Inc. All rights reserved.
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Library General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Library General Public License for more details.
19  *
20  * You should have received a copy of the GNU Library General Public License
21  * along with this library; see the file COPYING.LIB.  If not, write to
22  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23  * Boston, MA 02110-1301, USA.
24  */
25
26 #include "config.h"
27 #include "ContainerNodeAlgorithms.h"
28
29 #include "CommonVM.h"
30 #include "HTMLFrameOwnerElement.h"
31 #include "InspectorInstrumentation.h"
32 #include "NoEventDispatchAssertion.h"
33 #include "ShadowRoot.h"
34
35 namespace WebCore {
36
37 static void notifyNodeInsertedIntoTree(ContainerNode& insertionPoint, ContainerNode&, NodeVector& postInsertionNotificationTargets);
38 static void notifyNodeInsertedIntoDocument(ContainerNode& insertionPoint, Node&, NodeVector& postInsertionNotificationTargets);
39 static void notifyNodeRemovedFromTree(ContainerNode& insertionPoint, ContainerNode&);
40 static void notifyNodeRemovedFromDocument(ContainerNode& insertionPoint, Node&);
41
42 static void notifyDescendantInsertedIntoDocument(ContainerNode& insertionPoint, ContainerNode& node, NodeVector& postInsertionNotificationTargets)
43 {
44     ChildNodesLazySnapshot snapshot(node);
45     while (RefPtr<Node> child = snapshot.nextNode()) {
46         // If we have been removed from the document during this loop, then
47         // we don't want to tell the rest of our children that they've been
48         // inserted into the document because they haven't.
49         if (node.inDocument() && child->parentNode() == &node)
50             notifyNodeInsertedIntoDocument(insertionPoint, *child, postInsertionNotificationTargets);
51     }
52
53     if (!is<Element>(node))
54         return;
55
56     if (RefPtr<ShadowRoot> root = downcast<Element>(node).shadowRoot()) {
57         if (node.inDocument() && root->host() == &node)
58             notifyNodeInsertedIntoDocument(insertionPoint, *root, postInsertionNotificationTargets);
59     }
60 }
61
62 static void notifyDescendantInsertedIntoTree(ContainerNode& insertionPoint, ContainerNode& node, NodeVector& postInsertionNotificationTargets)
63 {
64     for (Node* child = node.firstChild(); child; child = child->nextSibling()) {
65         if (is<ContainerNode>(*child))
66             notifyNodeInsertedIntoTree(insertionPoint, downcast<ContainerNode>(*child), postInsertionNotificationTargets);
67     }
68
69     if (ShadowRoot* root = node.shadowRoot())
70         notifyNodeInsertedIntoTree(insertionPoint, *root, postInsertionNotificationTargets);
71 }
72
73 void notifyNodeInsertedIntoDocument(ContainerNode& insertionPoint, Node& node, NodeVector& postInsertionNotificationTargets)
74 {
75     ASSERT(insertionPoint.inDocument());
76     if (node.insertedInto(insertionPoint) == Node::InsertionShouldCallFinishedInsertingSubtree)
77         postInsertionNotificationTargets.append(node);
78     if (is<ContainerNode>(node))
79         notifyDescendantInsertedIntoDocument(insertionPoint, downcast<ContainerNode>(node), postInsertionNotificationTargets);
80 }
81
82 void notifyNodeInsertedIntoTree(ContainerNode& insertionPoint, ContainerNode& node, NodeVector& postInsertionNotificationTargets)
83 {
84     NoEventDispatchAssertion assertNoEventDispatch;
85     ASSERT(!insertionPoint.inDocument());
86
87     if (node.insertedInto(insertionPoint) == Node::InsertionShouldCallFinishedInsertingSubtree)
88         postInsertionNotificationTargets.append(node);
89     notifyDescendantInsertedIntoTree(insertionPoint, node, postInsertionNotificationTargets);
90 }
91
92 void notifyChildNodeInserted(ContainerNode& insertionPoint, Node& node, NodeVector& postInsertionNotificationTargets)
93 {
94     ASSERT_WITH_SECURITY_IMPLICATION(!NoEventDispatchAssertion::isEventDispatchForbidden());
95
96     InspectorInstrumentation::didInsertDOMNode(node.document(), node);
97
98     Ref<Document> protectDocument(node.document());
99     Ref<Node> protectNode(node);
100
101     if (insertionPoint.inDocument())
102         notifyNodeInsertedIntoDocument(insertionPoint, node, postInsertionNotificationTargets);
103     else if (is<ContainerNode>(node))
104         notifyNodeInsertedIntoTree(insertionPoint, downcast<ContainerNode>(node), postInsertionNotificationTargets);
105
106     writeBarrierOpaqueRoot([&insertionPoint] () -> void* { return insertionPoint.opaqueRoot(); });
107 }
108
109 void notifyNodeRemovedFromDocument(ContainerNode& insertionPoint, Node& node)
110 {
111     ASSERT(insertionPoint.inDocument());
112     node.removedFrom(insertionPoint);
113
114     if (!is<ContainerNode>(node))
115         return;
116     ChildNodesLazySnapshot snapshot(node);
117     while (RefPtr<Node> child = snapshot.nextNode()) {
118         // If we have been added to the document during this loop, then we
119         // don't want to tell the rest of our children that they've been
120         // removed from the document because they haven't.
121         if (!node.inDocument() && child->parentNode() == &node)
122             notifyNodeRemovedFromDocument(insertionPoint, *child.get());
123     }
124
125     if (!is<Element>(node))
126         return;
127
128     if (node.document().cssTarget() == &node)
129         node.document().setCSSTarget(nullptr);
130
131     if (RefPtr<ShadowRoot> root = downcast<Element>(node).shadowRoot()) {
132         if (!node.inDocument() && root->host() == &node)
133             notifyNodeRemovedFromDocument(insertionPoint, *root.get());
134     }
135 }
136
137 void notifyNodeRemovedFromTree(ContainerNode& insertionPoint, ContainerNode& node)
138 {
139     NoEventDispatchAssertion assertNoEventDispatch;
140     ASSERT(!insertionPoint.inDocument());
141
142     node.removedFrom(insertionPoint);
143
144     for (Node* child = node.firstChild(); child; child = child->nextSibling()) {
145         if (is<ContainerNode>(*child))
146             notifyNodeRemovedFromTree(insertionPoint, downcast<ContainerNode>(*child));
147     }
148
149     if (!is<Element>(node))
150         return;
151
152     if (RefPtr<ShadowRoot> root = downcast<Element>(node).shadowRoot())
153         notifyNodeRemovedFromTree(insertionPoint, *root.get());
154 }
155
156 void notifyChildNodeRemoved(ContainerNode& insertionPoint, Node& child)
157 {
158     writeBarrierOpaqueRoot([&child] () -> void* { return &child; });
159
160     if (!child.inDocument()) {
161         if (is<ContainerNode>(child))
162             notifyNodeRemovedFromTree(insertionPoint, downcast<ContainerNode>(child));
163         return;
164     }
165     notifyNodeRemovedFromDocument(insertionPoint, child);
166     child.document().notifyRemovePendingSheetIfNeeded();
167 }
168
169 void addChildNodesToDeletionQueue(Node*& head, Node*& tail, ContainerNode& container)
170 {
171     // We have to tell all children that their parent has died.
172     Node* next = nullptr;
173     for (auto* node = container.firstChild(); node; node = next) {
174         ASSERT(!node->m_deletionHasBegun);
175
176         next = node->nextSibling();
177         node->setNextSibling(nullptr);
178         node->setParentNode(nullptr);
179         container.setFirstChild(next);
180         if (next)
181             next->setPreviousSibling(nullptr);
182
183         if (!node->refCount()) {
184 #ifndef NDEBUG
185             node->m_deletionHasBegun = true;
186 #endif
187             // Add the node to the list of nodes to be deleted.
188             // Reuse the nextSibling pointer for this purpose.
189             if (tail)
190                 tail->setNextSibling(node);
191             else
192                 head = node;
193
194             tail = node;
195         } else {
196             Ref<Node> protect(*node); // removedFromDocument may remove remove all references to this node.
197             if (Document* containerDocument = container.ownerDocument())
198                 containerDocument->adoptIfNeeded(*node);
199             if (node->isInTreeScope())
200                 notifyChildNodeRemoved(container, *node);
201         }
202     }
203
204     container.setLastChild(nullptr);
205 }
206
207 void removeDetachedChildrenInContainer(ContainerNode& container)
208 {
209     // List of nodes to be deleted.
210     Node* head = nullptr;
211     Node* tail = nullptr;
212
213     addChildNodesToDeletionQueue(head, tail, container);
214
215     Node* node;
216     Node* next;
217     while ((node = head)) {
218         ASSERT(node->m_deletionHasBegun);
219
220         next = node->nextSibling();
221         node->setNextSibling(nullptr);
222
223         head = next;
224         if (!next)
225             tail = nullptr;
226
227         if (is<ContainerNode>(*node))
228             addChildNodesToDeletionQueue(head, tail, downcast<ContainerNode>(*node));
229         
230         delete node;
231     }
232 }
233
234 #ifndef NDEBUG
235 static unsigned assertConnectedSubrameCountIsConsistent(ContainerNode& node)
236 {
237     unsigned count = 0;
238
239     if (is<Element>(node)) {
240         if (is<HTMLFrameOwnerElement>(node) && downcast<HTMLFrameOwnerElement>(node).contentFrame())
241             ++count;
242
243         if (ShadowRoot* root = downcast<Element>(node).shadowRoot())
244             count += assertConnectedSubrameCountIsConsistent(*root);
245     }
246
247     for (auto& child : childrenOfType<Element>(node))
248         count += assertConnectedSubrameCountIsConsistent(child);
249
250     // If we undercount there's possibly a security bug since we'd leave frames
251     // in subtrees outside the document.
252     ASSERT(node.connectedSubframeCount() >= count);
253
254     // If we overcount it's safe, but not optimal because it means we'll traverse
255     // through the document in disconnectSubframes looking for frames that have
256     // already been disconnected.
257     ASSERT(node.connectedSubframeCount() == count);
258
259     return count;
260 }
261 #endif
262
263 static void collectFrameOwners(Vector<Ref<HTMLFrameOwnerElement>>& frameOwners, ContainerNode& root)
264 {
265     auto elementDescendants = descendantsOfType<Element>(root);
266     auto it = elementDescendants.begin();
267     auto end = elementDescendants.end();
268     while (it != end) {
269         Element& element = *it;
270         if (!element.connectedSubframeCount()) {
271             it.traverseNextSkippingChildren();
272             continue;
273         }
274
275         if (is<HTMLFrameOwnerElement>(element))
276             frameOwners.append(downcast<HTMLFrameOwnerElement>(element));
277
278         if (ShadowRoot* shadowRoot = element.shadowRoot())
279             collectFrameOwners(frameOwners, *shadowRoot);
280         ++it;
281     }
282 }
283
284 void disconnectSubframes(ContainerNode& root, SubframeDisconnectPolicy policy)
285 {
286 #ifndef NDEBUG
287     assertConnectedSubrameCountIsConsistent(root);
288 #endif
289     ASSERT(root.connectedSubframeCount());
290
291     Vector<Ref<HTMLFrameOwnerElement>> frameOwners;
292
293     if (policy == RootAndDescendants) {
294         if (is<HTMLFrameOwnerElement>(root))
295             frameOwners.append(downcast<HTMLFrameOwnerElement>(root));
296     }
297
298     collectFrameOwners(frameOwners, root);
299
300     // Must disable frame loading in the subtree so an unload handler cannot
301     // insert more frames and create loaded frames in detached subtrees.
302     SubframeLoadingDisabler disabler(root);
303
304     bool isFirst = true;
305     for (auto& owner : frameOwners) {
306         // Don't need to traverse up the tree for the first owner since no
307         // script could have moved it.
308         if (isFirst || root.containsIncludingShadowDOM(&owner.get()))
309             owner.get().disconnectContentFrame();
310         isFirst = false;
311     }
312 }
313
314 }