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 "HTMLFrameOwnerElement.h"
30#include "HTMLTextAreaElement.h"
31#include "InspectorInstrumentation.h"
32#include "ScriptDisallowedScope.h"
33#include "ShadowRoot.h"
34#include "TypedElementDescendantIterator.h"
35
36namespace WebCore {
37
38#if !ASSERT_DISABLED
39ContainerChildRemovalScope* ContainerChildRemovalScope::s_scope = nullptr;
40#endif
41
42enum class TreeScopeChange { Changed, DidNotChange };
43
44static void notifyNodeInsertedIntoDocument(ContainerNode& parentOfInsertedTree, Node& node, TreeScopeChange treeScopeChange, NodeVector& postInsertionNotificationTargets)
45{
46 ASSERT(parentOfInsertedTree.isConnected());
47 ASSERT(!node.isConnected());
48 if (node.insertedIntoAncestor(Node::InsertionType { /* connectedToDocument */ true, treeScopeChange == TreeScopeChange::Changed }, parentOfInsertedTree) == Node::InsertedIntoAncestorResult::NeedsPostInsertionCallback)
49 postInsertionNotificationTargets.append(node);
50
51 if (!is<ContainerNode>(node))
52 return;
53
54 for (RefPtr<Node> child = downcast<ContainerNode>(node).firstChild(); child; child = child->nextSibling()) {
55 RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(node.isConnected() && child->parentNode() == &node);
56 notifyNodeInsertedIntoDocument(parentOfInsertedTree, *child, treeScopeChange, postInsertionNotificationTargets);
57 }
58
59 if (!is<Element>(node))
60 return;
61
62 if (RefPtr<ShadowRoot> root = downcast<Element>(node).shadowRoot()) {
63 RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(node.isConnected() && root->host() == &node);
64 notifyNodeInsertedIntoDocument(parentOfInsertedTree, *root, TreeScopeChange::DidNotChange, postInsertionNotificationTargets);
65 }
66}
67
68static void notifyNodeInsertedIntoTree(ContainerNode& parentOfInsertedTree, Node& node, TreeScopeChange treeScopeChange, NodeVector& postInsertionNotificationTargets)
69{
70 ASSERT(!parentOfInsertedTree.isConnected());
71 ASSERT(!node.isConnected());
72
73 if (node.insertedIntoAncestor(Node::InsertionType { /* connectedToDocument */ false, treeScopeChange == TreeScopeChange::Changed }, parentOfInsertedTree) == Node::InsertedIntoAncestorResult::NeedsPostInsertionCallback)
74 postInsertionNotificationTargets.append(node);
75
76 if (!is<ContainerNode>(node))
77 return;
78
79 for (RefPtr<Node> child = downcast<ContainerNode>(node).firstChild(); child; child = child->nextSibling())
80 notifyNodeInsertedIntoTree(parentOfInsertedTree, *child, treeScopeChange, postInsertionNotificationTargets);
81
82 if (!is<Element>(node))
83 return;
84
85 if (RefPtr<ShadowRoot> root = downcast<Element>(node).shadowRoot())
86 notifyNodeInsertedIntoTree(parentOfInsertedTree, *root, TreeScopeChange::DidNotChange, postInsertionNotificationTargets);
87}
88
89NodeVector notifyChildNodeInserted(ContainerNode& parentOfInsertedTree, Node& node)
90{
91 ASSERT(ScriptDisallowedScope::InMainThread::hasDisallowedScope());
92
93 InspectorInstrumentation::didInsertDOMNode(node.document(), node);
94
95 Ref<Document> protectDocument(node.document());
96 Ref<Node> protectNode(node);
97
98 NodeVector postInsertionNotificationTargets;
99
100 // Tree scope has changed if the container node into which "node" is inserted is in a document or a shadow root.
101 auto treeScopeChange = parentOfInsertedTree.isInTreeScope() ? TreeScopeChange::Changed : TreeScopeChange::DidNotChange;
102 if (parentOfInsertedTree.isConnected())
103 notifyNodeInsertedIntoDocument(parentOfInsertedTree, node, treeScopeChange, postInsertionNotificationTargets);
104 else
105 notifyNodeInsertedIntoTree(parentOfInsertedTree, node, treeScopeChange, postInsertionNotificationTargets);
106
107 return postInsertionNotificationTargets;
108}
109
110static void notifyNodeRemovedFromDocument(ContainerNode& oldParentOfRemovedTree, TreeScopeChange treeScopeChange, Node& node)
111{
112 ASSERT(oldParentOfRemovedTree.isConnected());
113 ASSERT(node.isConnected());
114 node.removedFromAncestor(Node::RemovalType { /* disconnectedFromDocument */ true, treeScopeChange == TreeScopeChange::Changed }, oldParentOfRemovedTree);
115
116 if (!is<ContainerNode>(node))
117 return;
118
119 for (RefPtr<Node> child = downcast<ContainerNode>(node).firstChild(); child; child = child->nextSibling()) {
120 RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(!node.isConnected() && child->parentNode() == &node);
121 notifyNodeRemovedFromDocument(oldParentOfRemovedTree, treeScopeChange, *child.get());
122 }
123
124 if (!is<Element>(node))
125 return;
126
127 if (RefPtr<ShadowRoot> root = downcast<Element>(node).shadowRoot()) {
128 RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(!node.isConnected() && root->host() == &node);
129 notifyNodeRemovedFromDocument(oldParentOfRemovedTree, TreeScopeChange::DidNotChange, *root.get());
130 }
131}
132
133static void notifyNodeRemovedFromTree(ContainerNode& oldParentOfRemovedTree, TreeScopeChange treeScopeChange, Node& node)
134{
135 ASSERT(!oldParentOfRemovedTree.isConnected());
136
137 node.removedFromAncestor(Node::RemovalType { /* disconnectedFromDocument */ false, treeScopeChange == TreeScopeChange::Changed }, oldParentOfRemovedTree);
138
139 if (!is<ContainerNode>(node))
140 return;
141
142 for (RefPtr<Node> child = downcast<ContainerNode>(node).firstChild(); child; child = child->nextSibling())
143 notifyNodeRemovedFromTree(oldParentOfRemovedTree, treeScopeChange, *child);
144
145 if (!is<Element>(node))
146 return;
147
148 if (RefPtr<ShadowRoot> root = downcast<Element>(node).shadowRoot())
149 notifyNodeRemovedFromTree(oldParentOfRemovedTree, TreeScopeChange::DidNotChange, *root);
150}
151
152void notifyChildNodeRemoved(ContainerNode& oldParentOfRemovedTree, Node& child)
153{
154 // Assert that the caller of this function has an instance of ScriptDisallowedScope.
155 ASSERT(!isMainThread() || ScriptDisallowedScope::InMainThread::hasDisallowedScope());
156 ContainerChildRemovalScope removalScope(oldParentOfRemovedTree, child);
157
158 // Tree scope has changed if the container node from which "node" is removed is in a document or a shadow root.
159 auto treeScopeChange = oldParentOfRemovedTree.isInTreeScope() ? TreeScopeChange::Changed : TreeScopeChange::DidNotChange;
160 if (child.isConnected())
161 notifyNodeRemovedFromDocument(oldParentOfRemovedTree, treeScopeChange, child);
162 else
163 notifyNodeRemovedFromTree(oldParentOfRemovedTree, treeScopeChange, child);
164}
165
166void addChildNodesToDeletionQueue(Node*& head, Node*& tail, ContainerNode& container)
167{
168 // We have to tell all children that their parent has died.
169 RefPtr<Node> next = nullptr;
170 for (RefPtr<Node> node = container.firstChild(); node; node = next) {
171 ASSERT(!node->m_deletionHasBegun);
172
173 next = node->nextSibling();
174 node->setNextSibling(nullptr);
175 node->setParentNode(nullptr);
176 container.setFirstChild(next.get());
177 if (next)
178 next->setPreviousSibling(nullptr);
179
180 if (!node->refCount()) {
181#ifndef NDEBUG
182 node->m_deletionHasBegun = true;
183#endif
184 // Add the node to the list of nodes to be deleted.
185 // Reuse the nextSibling pointer for this purpose.
186 if (tail)
187 tail->setNextSibling(node.get());
188 else
189 head = node.get();
190
191 tail = node.get();
192 } else {
193 node->setTreeScopeRecursively(container.document());
194 if (node->isInTreeScope())
195 notifyChildNodeRemoved(container, *node);
196 ASSERT_WITH_SECURITY_IMPLICATION(!node->isInTreeScope());
197 }
198 }
199
200 container.setLastChild(nullptr);
201}
202
203void removeDetachedChildrenInContainer(ContainerNode& container)
204{
205 // List of nodes to be deleted.
206 Node* head = nullptr;
207 Node* tail = nullptr;
208
209 addChildNodesToDeletionQueue(head, tail, container);
210
211 Node* node;
212 Node* next;
213 while ((node = head)) {
214 ASSERT(node->m_deletionHasBegun);
215
216 next = node->nextSibling();
217 node->setNextSibling(nullptr);
218
219 head = next;
220 if (!next)
221 tail = nullptr;
222
223 if (is<ContainerNode>(*node))
224 addChildNodesToDeletionQueue(head, tail, downcast<ContainerNode>(*node));
225
226 delete node;
227 }
228}
229
230#ifndef NDEBUG
231static unsigned assertConnectedSubrameCountIsConsistent(ContainerNode& node)
232{
233 unsigned count = 0;
234
235 if (is<Element>(node)) {
236 if (is<HTMLFrameOwnerElement>(node) && downcast<HTMLFrameOwnerElement>(node).contentFrame())
237 ++count;
238
239 if (ShadowRoot* root = downcast<Element>(node).shadowRoot())
240 count += assertConnectedSubrameCountIsConsistent(*root);
241 }
242
243 for (auto& child : childrenOfType<Element>(node))
244 count += assertConnectedSubrameCountIsConsistent(child);
245
246 // If we undercount there's possibly a security bug since we'd leave frames
247 // in subtrees outside the document.
248 ASSERT(node.connectedSubframeCount() >= count);
249
250 // If we overcount it's safe, but not optimal because it means we'll traverse
251 // through the document in disconnectSubframes looking for frames that have
252 // already been disconnected.
253 ASSERT(node.connectedSubframeCount() == count);
254
255 return count;
256}
257#endif
258
259static void collectFrameOwners(Vector<Ref<HTMLFrameOwnerElement>>& frameOwners, ContainerNode& root)
260{
261 auto elementDescendants = descendantsOfType<Element>(root);
262 auto it = elementDescendants.begin();
263 auto end = elementDescendants.end();
264 while (it != end) {
265 Element& element = *it;
266 if (!element.connectedSubframeCount()) {
267 it.traverseNextSkippingChildren();
268 continue;
269 }
270
271 if (is<HTMLFrameOwnerElement>(element))
272 frameOwners.append(downcast<HTMLFrameOwnerElement>(element));
273
274 if (ShadowRoot* shadowRoot = element.shadowRoot())
275 collectFrameOwners(frameOwners, *shadowRoot);
276 ++it;
277 }
278}
279
280void disconnectSubframes(ContainerNode& root, SubframeDisconnectPolicy policy)
281{
282#ifndef NDEBUG
283 assertConnectedSubrameCountIsConsistent(root);
284#endif
285 ASSERT(root.connectedSubframeCount());
286
287 Vector<Ref<HTMLFrameOwnerElement>> frameOwners;
288
289 if (policy == RootAndDescendants) {
290 if (is<HTMLFrameOwnerElement>(root))
291 frameOwners.append(downcast<HTMLFrameOwnerElement>(root));
292 }
293
294 collectFrameOwners(frameOwners, root);
295
296 if (auto* shadowRoot = root.shadowRoot())
297 collectFrameOwners(frameOwners, *shadowRoot);
298
299 // Must disable frame loading in the subtree so an unload handler cannot
300 // insert more frames and create loaded frames in detached subtrees.
301 SubframeLoadingDisabler disabler(&root);
302
303 bool isFirst = true;
304 for (auto& owner : frameOwners) {
305 // Don't need to traverse up the tree for the first owner since no
306 // script could have moved it.
307 if (isFirst || root.containsIncludingShadowDOM(&owner.get()))
308 owner.get().disconnectContentFrame();
309 isFirst = false;
310 }
311}
312
313}
314