| 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 | |