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-2018 Apple Inc. All rights reserved. |
6 | * |
7 | * This library is free software; you can redistribute it and/or |
8 | * modify it under the terms of the GNU Library General Public |
9 | * License as published by the Free Software Foundation; either |
10 | * version 2 of the License, or (at your option) any later version. |
11 | * |
12 | * This library is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | * Library General Public License for more details. |
16 | * |
17 | * You should have received a copy of the GNU Library General Public License |
18 | * along with this library; see the file COPYING.LIB. If not, write to |
19 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
20 | * Boston, MA 02110-1301, USA. |
21 | */ |
22 | |
23 | #include "config.h" |
24 | #include "ContainerNode.h" |
25 | |
26 | #include "AXObjectCache.h" |
27 | #include "AllDescendantsCollection.h" |
28 | #include "ChildListMutationScope.h" |
29 | #include "ClassCollection.h" |
30 | #include "CommonVM.h" |
31 | #include "ContainerNodeAlgorithms.h" |
32 | #include "Editor.h" |
33 | #include "EventNames.h" |
34 | #include "FloatRect.h" |
35 | #include "FrameView.h" |
36 | #include "GenericCachedHTMLCollection.h" |
37 | #include "HTMLFormControlsCollection.h" |
38 | #include "HTMLOptionsCollection.h" |
39 | #include "HTMLSlotElement.h" |
40 | #include "HTMLTableRowsCollection.h" |
41 | #include "InlineTextBox.h" |
42 | #include "InspectorInstrumentation.h" |
43 | #include "JSNode.h" |
44 | #include "LabelsNodeList.h" |
45 | #include "MutationEvent.h" |
46 | #include "NameNodeList.h" |
47 | #include "NodeRareData.h" |
48 | #include "NodeRenderStyle.h" |
49 | #include "RadioNodeList.h" |
50 | #include "RenderBox.h" |
51 | #include "RenderTheme.h" |
52 | #include "RenderTreeUpdater.h" |
53 | #include "RenderWidget.h" |
54 | #include "RootInlineBox.h" |
55 | #include "RuntimeEnabledFeatures.h" |
56 | #include "SVGDocumentExtensions.h" |
57 | #include "SVGElement.h" |
58 | #include "SVGNames.h" |
59 | #include "SVGUseElement.h" |
60 | #include "ScriptDisallowedScope.h" |
61 | #include "SelectorQuery.h" |
62 | #include "SlotAssignment.h" |
63 | #include "TemplateContentDocumentFragment.h" |
64 | #include <algorithm> |
65 | #include <wtf/IsoMallocInlines.h> |
66 | #include <wtf/Variant.h> |
67 | |
68 | namespace WebCore { |
69 | |
70 | WTF_MAKE_ISO_ALLOCATED_IMPL(ContainerNode); |
71 | |
72 | static void dispatchChildInsertionEvents(Node&); |
73 | static void dispatchChildRemovalEvents(Ref<Node>&); |
74 | |
75 | ChildNodesLazySnapshot* ChildNodesLazySnapshot::latestSnapshot; |
76 | |
77 | unsigned ScriptDisallowedScope::s_count = 0; |
78 | #if !ASSERT_DISABLED |
79 | ScriptDisallowedScope::EventAllowedScope* ScriptDisallowedScope::EventAllowedScope::s_currentScope = nullptr; |
80 | #endif |
81 | |
82 | ALWAYS_INLINE NodeVector ContainerNode::removeAllChildrenWithScriptAssertion(ChildChangeSource source, DeferChildrenChanged deferChildrenChanged) |
83 | { |
84 | auto children = collectChildNodes(*this); |
85 | |
86 | if (source == ContainerNode::ChildChangeSource::API) { |
87 | ChildListMutationScope mutation(*this); |
88 | for (auto& child : children) { |
89 | mutation.willRemoveChild(child.get()); |
90 | child->notifyMutationObserversNodeWillDetach(); |
91 | dispatchChildRemovalEvents(child); |
92 | } |
93 | } else { |
94 | ASSERT(source == ContainerNode::ChildChangeSource::Parser); |
95 | ScriptDisallowedScope::InMainThread scriptDisallowedScope; |
96 | if (UNLIKELY(document().hasMutationObserversOfType(MutationObserver::ChildList))) { |
97 | ChildListMutationScope mutation(*this); |
98 | for (auto& child : children) |
99 | mutation.willRemoveChild(child.get()); |
100 | } |
101 | } |
102 | |
103 | disconnectSubframesIfNeeded(*this, DescendantsOnly); |
104 | |
105 | WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates; |
106 | ScriptDisallowedScope::InMainThread scriptDisallowedScope; |
107 | |
108 | if (UNLIKELY(isShadowRoot() || isInShadowTree())) |
109 | containingShadowRoot()->willRemoveAllChildren(*this); |
110 | |
111 | document().nodeChildrenWillBeRemoved(*this); |
112 | |
113 | while (RefPtr<Node> child = m_firstChild) { |
114 | removeBetween(nullptr, child->nextSibling(), *child); |
115 | notifyChildNodeRemoved(*this, *child); |
116 | } |
117 | |
118 | if (deferChildrenChanged == DeferChildrenChanged::No) |
119 | childrenChanged(ContainerNode::ChildChange { ContainerNode::AllChildrenRemoved, nullptr, nullptr, source }); |
120 | |
121 | return children; |
122 | } |
123 | |
124 | ALWAYS_INLINE bool ContainerNode::removeNodeWithScriptAssertion(Node& childToRemove, ChildChangeSource source) |
125 | { |
126 | Ref<Node> protectedChildToRemove(childToRemove); |
127 | ASSERT_WITH_SECURITY_IMPLICATION(childToRemove.parentNode() == this); |
128 | { |
129 | ScriptDisallowedScope::InMainThread scriptDisallowedScope; |
130 | ChildListMutationScope(*this).willRemoveChild(childToRemove); |
131 | } |
132 | |
133 | ASSERT_WITH_SECURITY_IMPLICATION(ScriptDisallowedScope::InMainThread::isEventDispatchAllowedInSubtree(childToRemove)); |
134 | if (source == ContainerNode::ChildChangeSource::API) { |
135 | childToRemove.notifyMutationObserversNodeWillDetach(); |
136 | dispatchChildRemovalEvents(protectedChildToRemove); |
137 | if (childToRemove.parentNode() != this) |
138 | return false; |
139 | } |
140 | |
141 | if (source == ContainerNode::ChildChangeSource::Parser) { |
142 | // FIXME: Merge these two code paths. It's a bug in the parser not to update connectedSubframeCount in time. |
143 | disconnectSubframesIfNeeded(*this, DescendantsOnly); |
144 | } else { |
145 | if (is<ContainerNode>(childToRemove)) |
146 | disconnectSubframesIfNeeded(downcast<ContainerNode>(childToRemove), RootAndDescendants); |
147 | } |
148 | |
149 | if (childToRemove.parentNode() != this) |
150 | return false; |
151 | |
152 | ChildChange change; |
153 | { |
154 | WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates; |
155 | ScriptDisallowedScope::InMainThread scriptDisallowedScope; |
156 | |
157 | if (UNLIKELY(isShadowRoot() || isInShadowTree())) |
158 | containingShadowRoot()->resolveSlotsBeforeNodeInsertionOrRemoval(); |
159 | |
160 | document().nodeWillBeRemoved(childToRemove); |
161 | |
162 | ASSERT_WITH_SECURITY_IMPLICATION(childToRemove.parentNode() == this); |
163 | ASSERT(!childToRemove.isDocumentFragment()); |
164 | |
165 | RefPtr<Node> previousSibling = childToRemove.previousSibling(); |
166 | RefPtr<Node> nextSibling = childToRemove.nextSibling(); |
167 | removeBetween(previousSibling.get(), nextSibling.get(), childToRemove); |
168 | notifyChildNodeRemoved(*this, childToRemove); |
169 | |
170 | change.type = is<Element>(childToRemove) ? ElementRemoved : (is<Text>(childToRemove) ? TextRemoved : NonContentsChildRemoved); |
171 | change.previousSiblingElement = (!previousSibling || is<Element>(*previousSibling)) ? downcast<Element>(previousSibling.get()) : ElementTraversal::previousSibling(*previousSibling); |
172 | change.nextSiblingElement = (!nextSibling || is<Element>(*nextSibling)) ? downcast<Element>(nextSibling.get()) : ElementTraversal::nextSibling(*nextSibling); |
173 | change.source = source; |
174 | } |
175 | |
176 | // FIXME: Move childrenChanged into ScriptDisallowedScope block. |
177 | childrenChanged(change); |
178 | |
179 | return true; |
180 | } |
181 | |
182 | enum class ReplacedAllChildren { No, Yes }; |
183 | |
184 | template<typename DOMInsertionWork> |
185 | static ALWAYS_INLINE void executeNodeInsertionWithScriptAssertion(ContainerNode& containerNode, Node& child, |
186 | ContainerNode::ChildChangeSource source, ReplacedAllChildren replacedAllChildren, DOMInsertionWork doNodeInsertion) |
187 | { |
188 | NodeVector postInsertionNotificationTargets; |
189 | { |
190 | ScriptDisallowedScope::InMainThread scriptDisallowedScope; |
191 | |
192 | if (UNLIKELY(containerNode.isShadowRoot() || containerNode.isInShadowTree())) |
193 | containerNode.containingShadowRoot()->resolveSlotsBeforeNodeInsertionOrRemoval(); |
194 | |
195 | doNodeInsertion(); |
196 | ChildListMutationScope(containerNode).childAdded(child); |
197 | postInsertionNotificationTargets = notifyChildNodeInserted(containerNode, child); |
198 | } |
199 | |
200 | // FIXME: Move childrenChanged into ScriptDisallowedScope block. |
201 | if (replacedAllChildren == ReplacedAllChildren::Yes) |
202 | containerNode.childrenChanged(ContainerNode::ChildChange { ContainerNode::AllChildrenReplaced, nullptr, nullptr, source }); |
203 | else { |
204 | containerNode.childrenChanged(ContainerNode::ChildChange { |
205 | child.isElementNode() ? ContainerNode::ElementInserted : (child.isTextNode() ? ContainerNode::TextInserted : ContainerNode::NonContentsChildInserted), |
206 | ElementTraversal::previousSibling(child), |
207 | ElementTraversal::nextSibling(child), |
208 | source |
209 | }); |
210 | } |
211 | |
212 | ASSERT(ScriptDisallowedScope::InMainThread::isEventDispatchAllowedInSubtree(child)); |
213 | for (auto& target : postInsertionNotificationTargets) |
214 | target->didFinishInsertingNode(); |
215 | |
216 | if (source == ContainerNode::ChildChangeSource::API) |
217 | dispatchChildInsertionEvents(child); |
218 | } |
219 | |
220 | static ExceptionOr<void> collectChildrenAndRemoveFromOldParent(Node& node, NodeVector& nodes) |
221 | { |
222 | if (!is<DocumentFragment>(node)) { |
223 | nodes.append(node); |
224 | auto* oldParent = node.parentNode(); |
225 | if (!oldParent) |
226 | return { }; |
227 | return oldParent->removeChild(node); |
228 | } |
229 | |
230 | nodes = collectChildNodes(node); |
231 | downcast<DocumentFragment>(node).removeChildren(); |
232 | return { }; |
233 | } |
234 | |
235 | // FIXME: This function must get a new name. |
236 | // It removes all children, not just a category called "detached children". |
237 | // So this name is terribly confusing. |
238 | void ContainerNode::removeDetachedChildren() |
239 | { |
240 | if (connectedSubframeCount()) { |
241 | for (Node* child = firstChild(); child; child = child->nextSibling()) |
242 | child->updateAncestorConnectedSubframeCountForRemoval(); |
243 | } |
244 | // FIXME: We should be able to ASSERT(!attached()) here: https://bugs.webkit.org/show_bug.cgi?id=107801 |
245 | ScriptDisallowedScope::InMainThread scriptDisallowedScope; |
246 | removeDetachedChildrenInContainer(*this); |
247 | } |
248 | |
249 | static inline void destroyRenderTreeIfNeeded(Node& child) |
250 | { |
251 | bool isElement = is<Element>(child); |
252 | auto hasDisplayContents = isElement && downcast<Element>(child).hasDisplayContents(); |
253 | if (!child.renderer() && !hasDisplayContents) |
254 | return; |
255 | if (isElement) |
256 | RenderTreeUpdater::tearDownRenderers(downcast<Element>(child)); |
257 | else if (is<Text>(child)) |
258 | RenderTreeUpdater::tearDownRenderer(downcast<Text>(child)); |
259 | } |
260 | |
261 | void ContainerNode::takeAllChildrenFrom(ContainerNode* oldParent) |
262 | { |
263 | ASSERT(oldParent); |
264 | |
265 | auto children = oldParent->removeAllChildrenWithScriptAssertion(ChildChangeSource::Parser); |
266 | |
267 | // FIXME: assert that we don't dispatch events here since this container node is still disconnected. |
268 | for (auto& child : children) { |
269 | RELEASE_ASSERT(!child->parentNode() && &child->treeScope() == &treeScope()); |
270 | ASSERT(!ensurePreInsertionValidity(child, nullptr).hasException()); |
271 | child->setTreeScopeRecursively(treeScope()); |
272 | parserAppendChild(child); |
273 | } |
274 | } |
275 | |
276 | ContainerNode::~ContainerNode() |
277 | { |
278 | if (!isDocumentNode()) |
279 | willBeDeletedFrom(document()); |
280 | removeDetachedChildren(); |
281 | } |
282 | |
283 | static inline bool isChildTypeAllowed(ContainerNode& newParent, Node& child) |
284 | { |
285 | if (!child.isDocumentFragment()) |
286 | return newParent.childTypeAllowed(child.nodeType()); |
287 | |
288 | for (Node* node = child.firstChild(); node; node = node->nextSibling()) { |
289 | if (!newParent.childTypeAllowed(node->nodeType())) |
290 | return false; |
291 | } |
292 | return true; |
293 | } |
294 | |
295 | static bool containsIncludingHostElements(const Node& possibleAncestor, const Node& node) |
296 | { |
297 | const Node* currentNode = &node; |
298 | do { |
299 | if (currentNode == &possibleAncestor) |
300 | return true; |
301 | const ContainerNode* parent = currentNode->parentNode(); |
302 | if (!parent) { |
303 | if (is<ShadowRoot>(currentNode)) |
304 | parent = downcast<ShadowRoot>(currentNode)->host(); |
305 | else if (is<DocumentFragment>(*currentNode) && downcast<DocumentFragment>(*currentNode).isTemplateContent()) |
306 | parent = static_cast<const TemplateContentDocumentFragment*>(currentNode)->host(); |
307 | } |
308 | currentNode = parent; |
309 | } while (currentNode); |
310 | |
311 | return false; |
312 | } |
313 | |
314 | static inline ExceptionOr<void> checkAcceptChild(ContainerNode& newParent, Node& newChild, const Node* refChild, Document::AcceptChildOperation operation) |
315 | { |
316 | if (containsIncludingHostElements(newChild, newParent)) |
317 | return Exception { HierarchyRequestError }; |
318 | |
319 | // Use common case fast path if possible. |
320 | if ((newChild.isElementNode() || newChild.isTextNode()) && newParent.isElementNode()) { |
321 | ASSERT(!newParent.isDocumentTypeNode()); |
322 | ASSERT(isChildTypeAllowed(newParent, newChild)); |
323 | if (operation == Document::AcceptChildOperation::InsertOrAdd && refChild && refChild->parentNode() != &newParent) |
324 | return Exception { NotFoundError }; |
325 | return { }; |
326 | } |
327 | |
328 | // This should never happen, but also protect release builds from tree corruption. |
329 | ASSERT(!newChild.isPseudoElement()); |
330 | if (newChild.isPseudoElement()) |
331 | return Exception { HierarchyRequestError }; |
332 | |
333 | if (operation == Document::AcceptChildOperation::InsertOrAdd && refChild && refChild->parentNode() != &newParent) |
334 | return Exception { NotFoundError }; |
335 | |
336 | if (is<Document>(newParent)) { |
337 | if (!downcast<Document>(newParent).canAcceptChild(newChild, refChild, operation)) |
338 | return Exception { HierarchyRequestError }; |
339 | } else if (!isChildTypeAllowed(newParent, newChild)) |
340 | return Exception { HierarchyRequestError }; |
341 | |
342 | return { }; |
343 | } |
344 | |
345 | static inline ExceptionOr<void> checkAcceptChildGuaranteedNodeTypes(ContainerNode& newParent, Node& newChild) |
346 | { |
347 | ASSERT(!newParent.isDocumentTypeNode()); |
348 | ASSERT(isChildTypeAllowed(newParent, newChild)); |
349 | if (containsIncludingHostElements(newChild, newParent)) |
350 | return Exception { HierarchyRequestError }; |
351 | return { }; |
352 | } |
353 | |
354 | // https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity |
355 | ExceptionOr<void> ContainerNode::ensurePreInsertionValidity(Node& newChild, Node* refChild) |
356 | { |
357 | return checkAcceptChild(*this, newChild, refChild, Document::AcceptChildOperation::InsertOrAdd); |
358 | } |
359 | |
360 | // https://dom.spec.whatwg.org/#concept-node-replace |
361 | static inline ExceptionOr<void> checkPreReplacementValidity(ContainerNode& newParent, Node& newChild, Node& oldChild) |
362 | { |
363 | return checkAcceptChild(newParent, newChild, &oldChild, Document::AcceptChildOperation::Replace); |
364 | } |
365 | |
366 | ExceptionOr<void> ContainerNode::insertBefore(Node& newChild, Node* refChild) |
367 | { |
368 | // Check that this node is not "floating". |
369 | // If it is, it can be deleted as a side effect of sending mutation events. |
370 | ASSERT(refCount() || parentOrShadowHostNode()); |
371 | |
372 | // Make sure adding the new child is OK. |
373 | auto validityCheckResult = ensurePreInsertionValidity(newChild, refChild); |
374 | if (validityCheckResult.hasException()) |
375 | return validityCheckResult.releaseException(); |
376 | |
377 | if (refChild == &newChild) |
378 | refChild = newChild.nextSibling(); |
379 | |
380 | // insertBefore(node, null) is equivalent to appendChild(node) |
381 | if (!refChild) |
382 | return appendChildWithoutPreInsertionValidityCheck(newChild); |
383 | |
384 | Ref<ContainerNode> protectedThis(*this); |
385 | Ref<Node> next(*refChild); |
386 | |
387 | NodeVector targets; |
388 | auto removeResult = collectChildrenAndRemoveFromOldParent(newChild, targets); |
389 | if (removeResult.hasException()) |
390 | return removeResult.releaseException(); |
391 | if (targets.isEmpty()) |
392 | return { }; |
393 | |
394 | // We need this extra check because collectChildrenAndRemoveFromOldParent() can fire mutation events. |
395 | for (auto& child : targets) { |
396 | auto checkAcceptResult = checkAcceptChildGuaranteedNodeTypes(*this, child); |
397 | if (checkAcceptResult.hasException()) |
398 | return checkAcceptResult.releaseException(); |
399 | } |
400 | |
401 | InspectorInstrumentation::willInsertDOMNode(document(), *this); |
402 | |
403 | ChildListMutationScope mutation(*this); |
404 | for (auto& child : targets) { |
405 | // Due to arbitrary code running in response to a DOM mutation event it's |
406 | // possible that "next" is no longer a child of "this". |
407 | // It's also possible that "child" has been inserted elsewhere. |
408 | // In either of those cases, we'll just stop. |
409 | if (next->parentNode() != this) |
410 | break; |
411 | if (child->parentNode()) |
412 | break; |
413 | |
414 | executeNodeInsertionWithScriptAssertion(*this, child.get(), ChildChangeSource::API, ReplacedAllChildren::No, [&] { |
415 | child->setTreeScopeRecursively(treeScope()); |
416 | insertBeforeCommon(next, child); |
417 | }); |
418 | } |
419 | |
420 | dispatchSubtreeModifiedEvent(); |
421 | return { }; |
422 | } |
423 | |
424 | void ContainerNode::insertBeforeCommon(Node& nextChild, Node& newChild) |
425 | { |
426 | ScriptDisallowedScope::InMainThread scriptDisallowedScope; |
427 | |
428 | ASSERT(!newChild.parentNode()); // Use insertBefore if you need to handle reparenting (and want DOM mutation events). |
429 | ASSERT(!newChild.nextSibling()); |
430 | ASSERT(!newChild.previousSibling()); |
431 | ASSERT(!newChild.isShadowRoot()); |
432 | |
433 | Node* prev = nextChild.previousSibling(); |
434 | ASSERT(m_lastChild != prev); |
435 | nextChild.setPreviousSibling(&newChild); |
436 | if (prev) { |
437 | ASSERT(m_firstChild != &nextChild); |
438 | ASSERT(prev->nextSibling() == &nextChild); |
439 | prev->setNextSibling(&newChild); |
440 | } else { |
441 | ASSERT(m_firstChild == &nextChild); |
442 | m_firstChild = &newChild; |
443 | } |
444 | newChild.setParentNode(this); |
445 | newChild.setPreviousSibling(prev); |
446 | newChild.setNextSibling(&nextChild); |
447 | } |
448 | |
449 | void ContainerNode::appendChildCommon(Node& child) |
450 | { |
451 | ScriptDisallowedScope::InMainThread scriptDisallowedScope; |
452 | |
453 | child.setParentNode(this); |
454 | |
455 | if (m_lastChild) { |
456 | child.setPreviousSibling(m_lastChild); |
457 | m_lastChild->setNextSibling(&child); |
458 | } else |
459 | m_firstChild = &child; |
460 | |
461 | m_lastChild = &child; |
462 | } |
463 | |
464 | void ContainerNode::parserInsertBefore(Node& newChild, Node& nextChild) |
465 | { |
466 | ASSERT(nextChild.parentNode() == this); |
467 | ASSERT(!newChild.isDocumentFragment()); |
468 | ASSERT(!hasTagName(HTMLNames::templateTag)); |
469 | |
470 | if (nextChild.previousSibling() == &newChild || &nextChild == &newChild) // nothing to do |
471 | return; |
472 | |
473 | executeNodeInsertionWithScriptAssertion(*this, newChild, ChildChangeSource::Parser, ReplacedAllChildren::No, [&] { |
474 | if (&document() != &newChild.document()) |
475 | document().adoptNode(newChild); |
476 | |
477 | insertBeforeCommon(nextChild, newChild); |
478 | |
479 | newChild.updateAncestorConnectedSubframeCountForInsertion(); |
480 | }); |
481 | } |
482 | |
483 | ExceptionOr<void> ContainerNode::replaceChild(Node& newChild, Node& oldChild) |
484 | { |
485 | // Check that this node is not "floating". |
486 | // If it is, it can be deleted as a side effect of sending mutation events. |
487 | ASSERT(refCount() || parentOrShadowHostNode()); |
488 | |
489 | Ref<ContainerNode> protectedThis(*this); |
490 | |
491 | // Make sure replacing the old child with the new is ok |
492 | auto validityResult = checkPreReplacementValidity(*this, newChild, oldChild); |
493 | if (validityResult.hasException()) |
494 | return validityResult.releaseException(); |
495 | |
496 | // NotFoundError: Raised if oldChild is not a child of this node. |
497 | if (oldChild.parentNode() != this) |
498 | return Exception { NotFoundError }; |
499 | |
500 | RefPtr<Node> refChild = oldChild.nextSibling(); |
501 | if (refChild.get() == &newChild) |
502 | refChild = refChild->nextSibling(); |
503 | |
504 | NodeVector targets; |
505 | { |
506 | ChildListMutationScope mutation(*this); |
507 | auto collectResult = collectChildrenAndRemoveFromOldParent(newChild, targets); |
508 | if (collectResult.hasException()) |
509 | return collectResult.releaseException(); |
510 | } |
511 | |
512 | // Do this one more time because collectChildrenAndRemoveFromOldParent() fires a MutationEvent. |
513 | for (auto& child : targets) { |
514 | validityResult = checkPreReplacementValidity(*this, child, oldChild); |
515 | if (validityResult.hasException()) |
516 | return validityResult.releaseException(); |
517 | } |
518 | |
519 | // Remove the node we're replacing. |
520 | Ref<Node> protectOldChild(oldChild); |
521 | |
522 | ChildListMutationScope mutation(*this); |
523 | |
524 | // If oldChild == newChild then oldChild no longer has a parent at this point. |
525 | if (oldChild.parentNode()) { |
526 | auto removeResult = removeChild(oldChild); |
527 | if (removeResult.hasException()) |
528 | return removeResult.releaseException(); |
529 | |
530 | // Does this one more time because removeChild() fires a MutationEvent. |
531 | for (auto& child : targets) { |
532 | validityResult = checkPreReplacementValidity(*this, child, oldChild); |
533 | if (validityResult.hasException()) |
534 | return validityResult.releaseException(); |
535 | } |
536 | } |
537 | |
538 | InspectorInstrumentation::willInsertDOMNode(document(), *this); |
539 | |
540 | // Add the new child(ren). |
541 | for (auto& child : targets) { |
542 | // Due to arbitrary code running in response to a DOM mutation event it's |
543 | // possible that "refChild" is no longer a child of "this". |
544 | // It's also possible that "child" has been inserted elsewhere. |
545 | // In either of those cases, we'll just stop. |
546 | if (refChild && refChild->parentNode() != this) |
547 | break; |
548 | if (child->parentNode()) |
549 | break; |
550 | |
551 | executeNodeInsertionWithScriptAssertion(*this, child.get(), ChildChangeSource::API, ReplacedAllChildren::No, [&] { |
552 | child->setTreeScopeRecursively(treeScope()); |
553 | if (refChild) |
554 | insertBeforeCommon(*refChild, child.get()); |
555 | else |
556 | appendChildCommon(child); |
557 | }); |
558 | } |
559 | |
560 | dispatchSubtreeModifiedEvent(); |
561 | return { }; |
562 | } |
563 | |
564 | void ContainerNode::disconnectDescendantFrames() |
565 | { |
566 | disconnectSubframesIfNeeded(*this, RootAndDescendants); |
567 | } |
568 | |
569 | ExceptionOr<void> ContainerNode::removeChild(Node& oldChild) |
570 | { |
571 | // Check that this node is not "floating". |
572 | // If it is, it can be deleted as a side effect of sending mutation events. |
573 | ASSERT(refCount() || parentOrShadowHostNode()); |
574 | |
575 | Ref<ContainerNode> protectedThis(*this); |
576 | |
577 | // NotFoundError: Raised if oldChild is not a child of this node. |
578 | if (oldChild.parentNode() != this) |
579 | return Exception { NotFoundError }; |
580 | |
581 | if (!removeNodeWithScriptAssertion(oldChild, ChildChangeSource::API)) |
582 | return Exception { NotFoundError }; |
583 | |
584 | rebuildSVGExtensionsElementsIfNecessary(); |
585 | dispatchSubtreeModifiedEvent(); |
586 | |
587 | return { }; |
588 | } |
589 | |
590 | void ContainerNode::removeBetween(Node* previousChild, Node* nextChild, Node& oldChild) |
591 | { |
592 | InspectorInstrumentation::didRemoveDOMNode(oldChild.document(), oldChild); |
593 | |
594 | ScriptDisallowedScope::InMainThread scriptDisallowedScope; |
595 | |
596 | ASSERT(oldChild.parentNode() == this); |
597 | |
598 | destroyRenderTreeIfNeeded(oldChild); |
599 | |
600 | if (nextChild) { |
601 | nextChild->setPreviousSibling(previousChild); |
602 | oldChild.setNextSibling(nullptr); |
603 | } else { |
604 | ASSERT(m_lastChild == &oldChild); |
605 | m_lastChild = previousChild; |
606 | } |
607 | if (previousChild) { |
608 | previousChild->setNextSibling(nextChild); |
609 | oldChild.setPreviousSibling(nullptr); |
610 | } else { |
611 | ASSERT(m_firstChild == &oldChild); |
612 | m_firstChild = nextChild; |
613 | } |
614 | |
615 | ASSERT(m_firstChild != &oldChild); |
616 | ASSERT(m_lastChild != &oldChild); |
617 | ASSERT(!oldChild.previousSibling()); |
618 | ASSERT(!oldChild.nextSibling()); |
619 | oldChild.setParentNode(nullptr); |
620 | |
621 | oldChild.setTreeScopeRecursively(document()); |
622 | } |
623 | |
624 | void ContainerNode::parserRemoveChild(Node& oldChild) |
625 | { |
626 | removeNodeWithScriptAssertion(oldChild, ChildChangeSource::Parser); |
627 | } |
628 | |
629 | // https://dom.spec.whatwg.org/#concept-node-replace-all |
630 | void ContainerNode::replaceAllChildren(std::nullptr_t) |
631 | { |
632 | ChildListMutationScope mutation(*this); |
633 | removeChildren(); |
634 | } |
635 | |
636 | // https://dom.spec.whatwg.org/#concept-node-replace-all |
637 | void ContainerNode::replaceAllChildren(Ref<Node>&& node) |
638 | { |
639 | // This function assumes the input node is not a DocumentFragment and is parentless to decrease complexity. |
640 | ASSERT(!is<DocumentFragment>(node)); |
641 | ASSERT(!node->parentNode()); |
642 | |
643 | if (!hasChildNodes()) { |
644 | // appendChildWithoutPreInsertionValidityCheck() can only throw when node has a parent and we already asserted it doesn't. |
645 | auto result = appendChildWithoutPreInsertionValidityCheck(node); |
646 | ASSERT_UNUSED(result, !result.hasException()); |
647 | return; |
648 | } |
649 | |
650 | Ref<ContainerNode> protectedThis(*this); |
651 | ChildListMutationScope mutation(*this); |
652 | removeAllChildrenWithScriptAssertion(ChildChangeSource::API, DeferChildrenChanged::Yes); |
653 | |
654 | executeNodeInsertionWithScriptAssertion(*this, node.get(), ChildChangeSource::API, ReplacedAllChildren::Yes, [&] { |
655 | ASSERT(!ensurePreInsertionValidity(node, nullptr).hasException()); |
656 | InspectorInstrumentation::willInsertDOMNode(document(), *this); |
657 | node->setTreeScopeRecursively(treeScope()); |
658 | appendChildCommon(node); |
659 | }); |
660 | |
661 | rebuildSVGExtensionsElementsIfNecessary(); |
662 | dispatchSubtreeModifiedEvent(); |
663 | } |
664 | |
665 | inline void ContainerNode::rebuildSVGExtensionsElementsIfNecessary() |
666 | { |
667 | if (document().svgExtensions() && !is<SVGUseElement>(shadowHost())) |
668 | document().accessSVGExtensions().rebuildElements(); |
669 | } |
670 | |
671 | // this differs from other remove functions because it forcibly removes all the children, |
672 | // regardless of read-only status or event exceptions, e.g. |
673 | void ContainerNode::removeChildren() |
674 | { |
675 | if (!m_firstChild) |
676 | return; |
677 | |
678 | Ref<ContainerNode> protectedThis(*this); |
679 | removeAllChildrenWithScriptAssertion(ChildChangeSource::API); |
680 | |
681 | rebuildSVGExtensionsElementsIfNecessary(); |
682 | dispatchSubtreeModifiedEvent(); |
683 | } |
684 | |
685 | ExceptionOr<void> ContainerNode::appendChild(Node& newChild) |
686 | { |
687 | // Check that this node is not "floating". |
688 | // If it is, it can be deleted as a side effect of sending mutation events. |
689 | ASSERT(refCount() || parentOrShadowHostNode()); |
690 | |
691 | // Make sure adding the new child is ok |
692 | auto validityCheckResult = ensurePreInsertionValidity(newChild, nullptr); |
693 | if (validityCheckResult.hasException()) |
694 | return validityCheckResult.releaseException(); |
695 | |
696 | return appendChildWithoutPreInsertionValidityCheck(newChild); |
697 | } |
698 | |
699 | ExceptionOr<void> ContainerNode::appendChildWithoutPreInsertionValidityCheck(Node& newChild) |
700 | { |
701 | Ref<ContainerNode> protectedThis(*this); |
702 | |
703 | NodeVector targets; |
704 | auto removeResult = collectChildrenAndRemoveFromOldParent(newChild, targets); |
705 | if (removeResult.hasException()) |
706 | return removeResult.releaseException(); |
707 | |
708 | if (targets.isEmpty()) |
709 | return { }; |
710 | |
711 | // We need this extra check because collectChildrenAndRemoveFromOldParent() can fire mutation events. |
712 | for (auto& child : targets) { |
713 | auto nodeTypeResult = checkAcceptChildGuaranteedNodeTypes(*this, child); |
714 | if (nodeTypeResult.hasException()) |
715 | return nodeTypeResult.releaseException(); |
716 | } |
717 | |
718 | InspectorInstrumentation::willInsertDOMNode(document(), *this); |
719 | |
720 | // Now actually add the child(ren) |
721 | ChildListMutationScope mutation(*this); |
722 | for (auto& child : targets) { |
723 | // If the child has a parent again, just stop what we're doing, because |
724 | // that means someone is doing something with DOM mutation -- can't re-parent |
725 | // a child that already has a parent. |
726 | if (child->parentNode()) |
727 | break; |
728 | |
729 | // Append child to the end of the list |
730 | executeNodeInsertionWithScriptAssertion(*this, child.get(), ChildChangeSource::API, ReplacedAllChildren::No, [&] { |
731 | child->setTreeScopeRecursively(treeScope()); |
732 | appendChildCommon(child); |
733 | }); |
734 | } |
735 | |
736 | dispatchSubtreeModifiedEvent(); |
737 | return { }; |
738 | } |
739 | |
740 | void ContainerNode::parserAppendChild(Node& newChild) |
741 | { |
742 | ASSERT(!newChild.parentNode()); // Use appendChild if you need to handle reparenting (and want DOM mutation events). |
743 | ASSERT(!newChild.isDocumentFragment()); |
744 | ASSERT(!hasTagName(HTMLNames::templateTag)); |
745 | |
746 | executeNodeInsertionWithScriptAssertion(*this, newChild, ChildChangeSource::Parser, ReplacedAllChildren::No, [&] { |
747 | if (&document() != &newChild.document()) |
748 | document().adoptNode(newChild); |
749 | |
750 | appendChildCommon(newChild); |
751 | newChild.setTreeScopeRecursively(treeScope()); |
752 | newChild.updateAncestorConnectedSubframeCountForInsertion(); |
753 | }); |
754 | } |
755 | |
756 | static bool affectsElements(const ContainerNode::ChildChange& change) |
757 | { |
758 | switch (change.type) { |
759 | case ContainerNode::ElementInserted: |
760 | case ContainerNode::ElementRemoved: |
761 | case ContainerNode::AllChildrenRemoved: |
762 | case ContainerNode::AllChildrenReplaced: |
763 | return true; |
764 | case ContainerNode::TextInserted: |
765 | case ContainerNode::TextRemoved: |
766 | case ContainerNode::TextChanged: |
767 | case ContainerNode::NonContentsChildInserted: |
768 | case ContainerNode::NonContentsChildRemoved: |
769 | return false; |
770 | } |
771 | ASSERT_NOT_REACHED(); |
772 | return false; |
773 | } |
774 | |
775 | void ContainerNode::childrenChanged(const ChildChange& change) |
776 | { |
777 | document().incDOMTreeVersion(); |
778 | |
779 | if (affectsElements(change)) |
780 | document().invalidateAccessKeyCache(); |
781 | |
782 | // FIXME: Unclear why it's always safe to skip this when parser is adding children. |
783 | // FIXME: Seems like it's equally safe to skip for TextInserted and TextRemoved as for TextChanged. |
784 | // FIXME: Should use switch for change type so we remember to update when adding new types. |
785 | if (change.source == ChildChangeSource::API && change.type != TextChanged) |
786 | document().updateRangesAfterChildrenChanged(*this); |
787 | |
788 | invalidateNodeListAndCollectionCachesInAncestors(); |
789 | } |
790 | |
791 | void ContainerNode::cloneChildNodes(ContainerNode& clone) |
792 | { |
793 | Document& targetDocument = clone.document(); |
794 | for (Node* child = firstChild(); child; child = child->nextSibling()) { |
795 | auto clonedChild = child->cloneNodeInternal(targetDocument, CloningOperation::SelfWithTemplateContent); |
796 | if (!clone.appendChild(clonedChild).hasException() && is<ContainerNode>(*child)) |
797 | downcast<ContainerNode>(*child).cloneChildNodes(downcast<ContainerNode>(clonedChild.get())); |
798 | } |
799 | } |
800 | |
801 | unsigned ContainerNode::countChildNodes() const |
802 | { |
803 | unsigned count = 0; |
804 | for (Node* child = firstChild(); child; child = child->nextSibling()) |
805 | ++count; |
806 | return count; |
807 | } |
808 | |
809 | Node* ContainerNode::traverseToChildAt(unsigned index) const |
810 | { |
811 | Node* child = firstChild(); |
812 | for (; child && index > 0; --index) |
813 | child = child->nextSibling(); |
814 | return child; |
815 | } |
816 | |
817 | static void dispatchChildInsertionEvents(Node& child) |
818 | { |
819 | if (child.isInShadowTree()) |
820 | return; |
821 | |
822 | ASSERT_WITH_SECURITY_IMPLICATION(ScriptDisallowedScope::InMainThread::isEventDispatchAllowedInSubtree(child)); |
823 | |
824 | RefPtr<Node> c = &child; |
825 | Ref<Document> document(child.document()); |
826 | |
827 | if (c->parentNode() && document->hasListenerType(Document::DOMNODEINSERTED_LISTENER)) |
828 | c->dispatchScopedEvent(MutationEvent::create(eventNames().DOMNodeInsertedEvent, Event::CanBubble::Yes, c->parentNode())); |
829 | |
830 | // dispatch the DOMNodeInsertedIntoDocument event to all descendants |
831 | if (c->isConnected() && document->hasListenerType(Document::DOMNODEINSERTEDINTODOCUMENT_LISTENER)) { |
832 | for (; c; c = NodeTraversal::next(*c, &child)) |
833 | c->dispatchScopedEvent(MutationEvent::create(eventNames().DOMNodeInsertedIntoDocumentEvent, Event::CanBubble::No)); |
834 | } |
835 | } |
836 | |
837 | static void dispatchChildRemovalEvents(Ref<Node>& child) |
838 | { |
839 | ASSERT_WITH_SECURITY_IMPLICATION(ScriptDisallowedScope::InMainThread::isEventDispatchAllowedInSubtree(child)); |
840 | InspectorInstrumentation::willRemoveDOMNode(child->document(), child.get()); |
841 | |
842 | if (child->isInShadowTree()) |
843 | return; |
844 | |
845 | // FIXME: This doesn't belong in dispatchChildRemovalEvents. |
846 | // FIXME: Nodes removed from a shadow tree should also be kept alive. |
847 | willCreatePossiblyOrphanedTreeByRemoval(child.ptr()); |
848 | |
849 | Ref<Document> document = child->document(); |
850 | |
851 | // dispatch pre-removal mutation events |
852 | if (child->parentNode() && document->hasListenerType(Document::DOMNODEREMOVED_LISTENER)) |
853 | child->dispatchScopedEvent(MutationEvent::create(eventNames().DOMNodeRemovedEvent, Event::CanBubble::Yes, child->parentNode())); |
854 | |
855 | // dispatch the DOMNodeRemovedFromDocument event to all descendants |
856 | if (child->isConnected() && document->hasListenerType(Document::DOMNODEREMOVEDFROMDOCUMENT_LISTENER)) { |
857 | for (RefPtr<Node> currentNode = child.copyRef(); currentNode; currentNode = NodeTraversal::next(*currentNode, child.ptr())) |
858 | currentNode->dispatchScopedEvent(MutationEvent::create(eventNames().DOMNodeRemovedFromDocumentEvent, Event::CanBubble::No)); |
859 | } |
860 | } |
861 | |
862 | ExceptionOr<Element*> ContainerNode::querySelector(const String& selectors) |
863 | { |
864 | auto query = document().selectorQueryForString(selectors); |
865 | if (query.hasException()) |
866 | return query.releaseException(); |
867 | return query.releaseReturnValue().queryFirst(*this); |
868 | } |
869 | |
870 | ExceptionOr<Ref<NodeList>> ContainerNode::querySelectorAll(const String& selectors) |
871 | { |
872 | auto query = document().selectorQueryForString(selectors); |
873 | if (query.hasException()) |
874 | return query.releaseException(); |
875 | return query.releaseReturnValue().queryAll(*this); |
876 | } |
877 | |
878 | Ref<HTMLCollection> ContainerNode::getElementsByTagName(const AtomicString& qualifiedName) |
879 | { |
880 | ASSERT(!qualifiedName.isNull()); |
881 | |
882 | if (qualifiedName == starAtom()) |
883 | return ensureRareData().ensureNodeLists().addCachedCollection<AllDescendantsCollection>(*this, AllDescendants); |
884 | |
885 | if (document().isHTMLDocument()) |
886 | return ensureRareData().ensureNodeLists().addCachedCollection<HTMLTagCollection>(*this, ByHTMLTag, qualifiedName); |
887 | return ensureRareData().ensureNodeLists().addCachedCollection<TagCollection>(*this, ByTag, qualifiedName); |
888 | } |
889 | |
890 | Ref<HTMLCollection> ContainerNode::getElementsByTagNameNS(const AtomicString& namespaceURI, const AtomicString& localName) |
891 | { |
892 | ASSERT(!localName.isNull()); |
893 | return ensureRareData().ensureNodeLists().addCachedTagCollectionNS(*this, namespaceURI.isEmpty() ? nullAtom() : namespaceURI, localName); |
894 | } |
895 | |
896 | Ref<NodeList> ContainerNode::getElementsByName(const String& elementName) |
897 | { |
898 | return ensureRareData().ensureNodeLists().addCacheWithAtomicName<NameNodeList>(*this, elementName); |
899 | } |
900 | |
901 | Ref<HTMLCollection> ContainerNode::getElementsByClassName(const AtomicString& classNames) |
902 | { |
903 | return ensureRareData().ensureNodeLists().addCachedCollection<ClassCollection>(*this, ByClass, classNames); |
904 | } |
905 | |
906 | Ref<RadioNodeList> ContainerNode::radioNodeList(const AtomicString& name) |
907 | { |
908 | ASSERT(hasTagName(HTMLNames::formTag) || hasTagName(HTMLNames::fieldsetTag)); |
909 | return ensureRareData().ensureNodeLists().addCacheWithAtomicName<RadioNodeList>(*this, name); |
910 | } |
911 | |
912 | Ref<HTMLCollection> ContainerNode::children() |
913 | { |
914 | return ensureRareData().ensureNodeLists().addCachedCollection<GenericCachedHTMLCollection<CollectionTypeTraits<NodeChildren>::traversalType>>(*this, NodeChildren); |
915 | } |
916 | |
917 | Element* ContainerNode::firstElementChild() const |
918 | { |
919 | return ElementTraversal::firstChild(*this); |
920 | } |
921 | |
922 | Element* ContainerNode::lastElementChild() const |
923 | { |
924 | return ElementTraversal::lastChild(*this); |
925 | } |
926 | |
927 | unsigned ContainerNode::childElementCount() const |
928 | { |
929 | auto children = childrenOfType<Element>(*this); |
930 | return std::distance(children.begin(), children.end()); |
931 | } |
932 | |
933 | ExceptionOr<void> ContainerNode::append(Vector<NodeOrString>&& vector) |
934 | { |
935 | auto result = convertNodesOrStringsIntoNode(WTFMove(vector)); |
936 | if (result.hasException()) |
937 | return result.releaseException(); |
938 | |
939 | auto node = result.releaseReturnValue(); |
940 | if (!node) |
941 | return { }; |
942 | |
943 | return appendChild(*node); |
944 | } |
945 | |
946 | ExceptionOr<void> ContainerNode::prepend(Vector<NodeOrString>&& vector) |
947 | { |
948 | auto result = convertNodesOrStringsIntoNode(WTFMove(vector)); |
949 | if (result.hasException()) |
950 | return result.releaseException(); |
951 | |
952 | auto node = result.releaseReturnValue(); |
953 | if (!node) |
954 | return { }; |
955 | |
956 | return insertBefore(*node, firstChild()); |
957 | } |
958 | |
959 | HTMLCollection* ContainerNode::cachedHTMLCollection(CollectionType type) |
960 | { |
961 | return hasRareData() && rareData()->nodeLists() ? rareData()->nodeLists()->cachedCollection<HTMLCollection>(type) : nullptr; |
962 | } |
963 | |
964 | } // namespace WebCore |
965 | |