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 | |
36 | namespace WebCore { |
37 | |
38 | #if !ASSERT_DISABLED |
39 | ContainerChildRemovalScope* ContainerChildRemovalScope::s_scope = nullptr; |
40 | #endif |
41 | |
42 | enum class TreeScopeChange { Changed, DidNotChange }; |
43 | |
44 | static 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 | |
68 | static 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 | |
89 | NodeVector 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 | |
110 | static 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 | |
133 | static 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 | |
152 | void 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 | |
166 | void 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 | |
203 | void 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 |
231 | static 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 | |
259 | static 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 | |
280 | void 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 | |