diff options
Diffstat (limited to 'Source/WebCore/dom/ContainerNode.cpp')
-rw-r--r-- | Source/WebCore/dom/ContainerNode.cpp | 1125 |
1 files changed, 485 insertions, 640 deletions
diff --git a/Source/WebCore/dom/ContainerNode.cpp b/Source/WebCore/dom/ContainerNode.cpp index 44a4c3a14..e9ed30dd7 100644 --- a/Source/WebCore/dom/ContainerNode.cpp +++ b/Source/WebCore/dom/ContainerNode.cpp @@ -2,7 +2,7 @@ * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2001 Dirk Mueller (mueller@kde.org) - * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. All rights reserved. + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2016 Apple Inc. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -24,71 +24,71 @@ #include "ContainerNode.h" #include "AXObjectCache.h" +#include "AllDescendantsCollection.h" #include "ChildListMutationScope.h" -#include "Chrome.h" -#include "ChromeClient.h" -#include "ClassNodeList.h" +#include "ClassCollection.h" +#include "CommonVM.h" #include "ContainerNodeAlgorithms.h" #include "Editor.h" +#include "EventNames.h" #include "FloatRect.h" #include "FrameView.h" +#include "GenericCachedHTMLCollection.h" +#include "HTMLFormControlsCollection.h" +#include "HTMLOptionsCollection.h" +#include "HTMLSlotElement.h" +#include "HTMLTableRowsCollection.h" #include "InlineTextBox.h" -#include "InsertionPoint.h" -#include "JSLazyEventListener.h" +#include "InspectorInstrumentation.h" #include "JSNode.h" #include "LabelsNodeList.h" -#include "LoaderStrategy.h" -#include "MemoryCache.h" #include "MutationEvent.h" #include "NameNodeList.h" +#include "NoEventDispatchAssertion.h" #include "NodeRareData.h" #include "NodeRenderStyle.h" -#include "PlatformStrategies.h" #include "RadioNodeList.h" #include "RenderBox.h" #include "RenderTheme.h" +#include "RenderTreeUpdater.h" #include "RenderWidget.h" -#include "ResourceLoadScheduler.h" #include "RootInlineBox.h" +#include "RuntimeEnabledFeatures.h" +#include "SVGDocumentExtensions.h" +#include "SVGElement.h" +#include "SVGNames.h" +#include "SVGUseElement.h" #include "SelectorQuery.h" #include "TemplateContentDocumentFragment.h" -#include <wtf/CurrentTime.h> - -#if ENABLE(DELETION_UI) -#include "DeleteButtonController.h" -#endif +#include <algorithm> +#include <wtf/Variant.h> namespace WebCore { static void dispatchChildInsertionEvents(Node&); static void dispatchChildRemovalEvents(Node&); -typedef std::pair<RefPtr<Node>, unsigned> CallbackParameters; -typedef std::pair<NodeCallback, CallbackParameters> CallbackInfo; -typedef Vector<CallbackInfo> NodeCallbackQueue; - -static NodeCallbackQueue* s_postAttachCallbackQueue; - -static size_t s_attachDepth; -static bool s_shouldReEnableMemoryCacheCallsAfterAttach; +ChildNodesLazySnapshot* ChildNodesLazySnapshot::latestSnapshot; -ChildNodesLazySnapshot* ChildNodesLazySnapshot::latestSnapshot = 0; - -#ifndef NDEBUG +#if !ASSERT_DISABLED unsigned NoEventDispatchAssertion::s_count = 0; +unsigned NoEventDispatchAssertion::DisableAssertionsInScope::s_existingCount = 0; +NoEventDispatchAssertion::EventAllowedScope* NoEventDispatchAssertion::EventAllowedScope::s_currentScope = nullptr; #endif -static void collectChildrenAndRemoveFromOldParent(Node& node, NodeVector& nodes, ExceptionCode& ec) +static ExceptionOr<void> collectChildrenAndRemoveFromOldParent(Node& node, NodeVector& nodes) { - if (!node.isDocumentFragment()) { + if (!is<DocumentFragment>(node)) { nodes.append(node); - if (ContainerNode* oldParent = node.parentNode()) - oldParent->removeChild(&node, ec); - return; + auto* oldParent = node.parentNode(); + if (!oldParent) + return { }; + return oldParent->removeChild(node); } getChildNodes(node, nodes); - toContainerNode(node).removeChildren(); + downcast<DocumentFragment>(node).removeChildren(); + return { }; } // FIXME: This function must get a new name. @@ -101,18 +101,21 @@ void ContainerNode::removeDetachedChildren() child->updateAncestorConnectedSubframeCountForRemoval(); } // FIXME: We should be able to ASSERT(!attached()) here: https://bugs.webkit.org/show_bug.cgi?id=107801 - removeDetachedChildrenInContainer<Node, ContainerNode>(*this); + removeDetachedChildrenInContainer(*this); } static inline void destroyRenderTreeIfNeeded(Node& child) { + bool isElement = is<Element>(child); + auto hasDisplayContents = isElement && downcast<Element>(child).hasDisplayContents(); + auto isNamedFlowElement = isElement && downcast<Element>(child).isNamedFlowContentElement(); // FIXME: Get rid of the named flow test. - if (!child.renderer() && !child.inNamedFlow()) + if (!child.renderer() && !hasDisplayContents && !isNamedFlowElement) return; - if (child.isElementNode()) - Style::detachRenderTree(toElement(child)); - else if (child.isTextNode()) - Style::detachTextRenderer(toText(child)); + if (isElement) + RenderTreeUpdater::tearDownRenderers(downcast<Element>(child)); + else if (is<Text>(child)) + RenderTreeUpdater::tearDownRenderer(downcast<Text>(child)); } void ContainerNode::takeAllChildrenFrom(ContainerNode* oldParent) @@ -124,44 +127,48 @@ void ContainerNode::takeAllChildrenFrom(ContainerNode* oldParent) if (oldParent->document().hasMutationObserversOfType(MutationObserver::ChildList)) { ChildListMutationScope mutation(*oldParent); - for (unsigned i = 0; i < children.size(); ++i) - mutation.willRemoveChild(children[i].get()); + for (auto& child : children) + mutation.willRemoveChild(child); } - // FIXME: We need to do notifyMutationObserversNodeWillDetach() for each child, - // probably inside removeDetachedChildrenInContainer. - - oldParent->removeDetachedChildren(); + disconnectSubframesIfNeeded(*oldParent, DescendantsOnly); + { + NoEventDispatchAssertion assertNoEventDispatch; - for (unsigned i = 0; i < children.size(); ++i) { - Node& child = children[i].get(); + oldParent->document().nodeChildrenWillBeRemoved(*oldParent); - destroyRenderTreeIfNeeded(child); + WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates; + while (RefPtr<Node> child = oldParent->m_firstChild) { + oldParent->removeBetween(nullptr, child->nextSibling(), *child); + notifyChildNodeRemoved(*oldParent, *child); + } + ChildChange change = { AllChildrenRemoved, nullptr, nullptr, ChildChangeSourceParser }; + childrenChanged(change); + } - // FIXME: We need a no mutation event version of adoptNode. - RefPtr<Node> adoptedChild = document().adoptNode(&children[i].get(), ASSERT_NO_EXCEPTION); - parserAppendChild(adoptedChild.get()); - // FIXME: Together with adoptNode above, the tree scope might get updated recursively twice - // (if the document changed or oldParent was in a shadow tree, AND *this is in a shadow tree). - // Can we do better? - treeScope().adoptIfNeeded(adoptedChild.get()); + // FIXME: assert that we don't dispatch events here since this container node is still disconnected. + for (auto& child : children) { + RELEASE_ASSERT(!child->parentNode() && &child->treeScope() == &treeScope()); + ASSERT(!ensurePreInsertionValidity(child, nullptr).hasException()); + treeScope().adoptIfNeeded(child); + parserAppendChild(child); } } ContainerNode::~ContainerNode() { - if (Document* document = documentInternal()) - willBeDeletedFrom(document); + if (!isDocumentNode()) + willBeDeletedFrom(document()); removeDetachedChildren(); } -static inline bool isChildTypeAllowed(ContainerNode* newParent, Node* child) +static inline bool isChildTypeAllowed(ContainerNode& newParent, Node& child) { - if (!child->isDocumentFragment()) - return newParent->childTypeAllowed(child->nodeType()); + if (!child.isDocumentFragment()) + return newParent.childTypeAllowed(child.nodeType()); - for (Node* node = child->firstChild(); node; node = node->nextSibling()) { - if (!newParent->childTypeAllowed(node->nodeType())) + for (Node* node = child.firstChild(); node; node = node->nextSibling()) { + if (!newParent.childTypeAllowed(node->nodeType())) return false; } return true; @@ -169,152 +176,129 @@ static inline bool isChildTypeAllowed(ContainerNode* newParent, Node* child) static inline bool isInTemplateContent(const Node* node) { -#if ENABLE(TEMPLATE_ELEMENT) Document& document = node->document(); return &document == document.templateDocument(); -#else - UNUSED_PARAM(node); - return false; -#endif } -static inline bool containsConsideringHostElements(const Node* newChild, const Node* newParent) +static inline bool containsConsideringHostElements(const Node& newChild, const Node& newParent) { - return (newParent->isInShadowTree() || isInTemplateContent(newParent)) - ? newChild->containsIncludingHostElements(newParent) - : newChild->contains(newParent); + return (newParent.isInShadowTree() || isInTemplateContent(&newParent)) + ? newChild.containsIncludingHostElements(&newParent) + : newChild.contains(&newParent); } -static inline ExceptionCode checkAcceptChild(ContainerNode* newParent, Node* newChild, Node* oldChild) +static inline ExceptionOr<void> checkAcceptChild(ContainerNode& newParent, Node& newChild, const Node* refChild, Document::AcceptChildOperation operation) { - // Not mentioned in spec: throw NOT_FOUND_ERR if newChild is null - if (!newChild) - return NOT_FOUND_ERR; - // Use common case fast path if possible. - if ((newChild->isElementNode() || newChild->isTextNode()) && newParent->isElementNode()) { - ASSERT(!newParent->isReadOnlyNode()); - ASSERT(!newParent->isDocumentTypeNode()); + if ((newChild.isElementNode() || newChild.isTextNode()) && newParent.isElementNode()) { + ASSERT(!newParent.isDocumentTypeNode()); ASSERT(isChildTypeAllowed(newParent, newChild)); if (containsConsideringHostElements(newChild, newParent)) - return HIERARCHY_REQUEST_ERR; - return 0; + return Exception { HIERARCHY_REQUEST_ERR }; + if (operation == Document::AcceptChildOperation::InsertOrAdd && refChild && refChild->parentNode() != &newParent) + return Exception { NOT_FOUND_ERR }; + return { }; } // This should never happen, but also protect release builds from tree corruption. - ASSERT(!newChild->isPseudoElement()); - if (newChild->isPseudoElement()) - return HIERARCHY_REQUEST_ERR; + ASSERT(!newChild.isPseudoElement()); + if (newChild.isPseudoElement()) + return Exception { HIERARCHY_REQUEST_ERR }; - if (newParent->isReadOnlyNode()) - return NO_MODIFICATION_ALLOWED_ERR; if (containsConsideringHostElements(newChild, newParent)) - return HIERARCHY_REQUEST_ERR; + return Exception { HIERARCHY_REQUEST_ERR }; + + if (operation == Document::AcceptChildOperation::InsertOrAdd && refChild && refChild->parentNode() != &newParent) + return Exception { NOT_FOUND_ERR }; - if (oldChild && newParent->isDocumentNode()) { - if (!toDocument(newParent)->canReplaceChild(newChild, oldChild)) - return HIERARCHY_REQUEST_ERR; + if (is<Document>(newParent)) { + if (!downcast<Document>(newParent).canAcceptChild(newChild, refChild, operation)) + return Exception { HIERARCHY_REQUEST_ERR }; } else if (!isChildTypeAllowed(newParent, newChild)) - return HIERARCHY_REQUEST_ERR; + return Exception { HIERARCHY_REQUEST_ERR }; - return 0; + return { }; } -static inline bool checkAcceptChildGuaranteedNodeTypes(ContainerNode* newParent, Node* newChild, ExceptionCode& ec) +static inline ExceptionOr<void> checkAcceptChildGuaranteedNodeTypes(ContainerNode& newParent, Node& newChild) { - ASSERT(!newParent->isReadOnlyNode()); - ASSERT(!newParent->isDocumentTypeNode()); + ASSERT(!newParent.isDocumentTypeNode()); ASSERT(isChildTypeAllowed(newParent, newChild)); - if (newChild->contains(newParent)) { - ec = HIERARCHY_REQUEST_ERR; - return false; - } - - return true; + if (newChild.contains(&newParent)) + return Exception { HIERARCHY_REQUEST_ERR }; + return { }; } -static inline bool checkAddChild(ContainerNode* newParent, Node* newChild, ExceptionCode& ec) +// https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity +ExceptionOr<void> ContainerNode::ensurePreInsertionValidity(Node& newChild, Node* refChild) { - ec = checkAcceptChild(newParent, newChild, 0); - if (ec) - return false; - - return true; + return checkAcceptChild(*this, newChild, refChild, Document::AcceptChildOperation::InsertOrAdd); } -static inline bool checkReplaceChild(ContainerNode* newParent, Node* newChild, Node* oldChild, ExceptionCode& ec) +// https://dom.spec.whatwg.org/#concept-node-replace +static inline ExceptionOr<void> checkPreReplacementValidity(ContainerNode& newParent, Node& newChild, Node& oldChild) { - ec = checkAcceptChild(newParent, newChild, oldChild); - if (ec) - return false; - - return true; + return checkAcceptChild(newParent, newChild, &oldChild, Document::AcceptChildOperation::Replace); } -bool ContainerNode::insertBefore(PassRefPtr<Node> newChild, Node* refChild, ExceptionCode& ec) +ExceptionOr<void> ContainerNode::insertBefore(Node& newChild, Node* refChild) { // Check that this node is not "floating". // If it is, it can be deleted as a side effect of sending mutation events. ASSERT(refCount() || parentOrShadowHostNode()); - Ref<ContainerNode> protect(*this); - - ec = 0; - - // insertBefore(node, 0) is equivalent to appendChild(node) - if (!refChild) - return appendChild(newChild, ec); - // Make sure adding the new child is OK. - if (!checkAddChild(this, newChild.get(), ec)) - return false; + auto validityCheckResult = ensurePreInsertionValidity(newChild, refChild); + if (validityCheckResult.hasException()) + return validityCheckResult.releaseException(); - // NOT_FOUND_ERR: Raised if refChild is not a child of this node - if (refChild->parentNode() != this) { - ec = NOT_FOUND_ERR; - return false; - } + if (refChild == &newChild) + refChild = newChild.nextSibling(); - if (refChild->previousSibling() == newChild || refChild == newChild) // nothing to do - return true; + // insertBefore(node, null) is equivalent to appendChild(node) + if (!refChild) + return appendChildWithoutPreInsertionValidityCheck(newChild); + Ref<ContainerNode> protectedThis(*this); Ref<Node> next(*refChild); NodeVector targets; - collectChildrenAndRemoveFromOldParent(*newChild.get(), targets, ec); - if (ec) - return false; + auto removeResult = collectChildrenAndRemoveFromOldParent(newChild, targets); + if (removeResult.hasException()) + return removeResult.releaseException(); if (targets.isEmpty()) - return true; + return { }; // We need this extra check because collectChildrenAndRemoveFromOldParent() can fire mutation events. - if (!checkAcceptChildGuaranteedNodeTypes(this, newChild.get(), ec)) - return false; + auto checkAcceptResult = checkAcceptChildGuaranteedNodeTypes(*this, newChild); + if (checkAcceptResult.hasException()) + return checkAcceptResult.releaseException(); - InspectorInstrumentation::willInsertDOMNode(&document(), this); + InspectorInstrumentation::willInsertDOMNode(document(), *this); ChildListMutationScope mutation(*this); - for (auto it = targets.begin(), end = targets.end(); it != end; ++it) { - Node& child = it->get(); - + for (auto& child : targets) { // Due to arbitrary code running in response to a DOM mutation event it's // possible that "next" is no longer a child of "this". // It's also possible that "child" has been inserted elsewhere. // In either of those cases, we'll just stop. if (next->parentNode() != this) break; - if (child.parentNode()) + if (child->parentNode()) break; - treeScope().adoptIfNeeded(&child); + { + NoEventDispatchAssertion assertNoEventDispatch; - insertBeforeCommon(next.get(), child); + treeScope().adoptIfNeeded(child); + insertBeforeCommon(next, child); + } updateTreeAfterInsertion(child); } dispatchSubtreeModifiedEvent(); - return true; + return { }; } void ContainerNode::insertBeforeCommon(Node& nextChild, Node& newChild) @@ -342,145 +326,161 @@ void ContainerNode::insertBeforeCommon(Node& nextChild, Node& newChild) newChild.setNextSibling(&nextChild); } -void ContainerNode::notifyChildInserted(Node& child, ChildChangeSource source) +void ContainerNode::appendChildCommon(Node& child) { - ChildChange change; - change.type = child.isElementNode() ? ElementInserted : child.isTextNode() ? TextInserted : NonContentsChildChanged; - change.previousSiblingElement = ElementTraversal::previousSibling(&child); - change.nextSiblingElement = ElementTraversal::nextSibling(&child); - change.source = source; + NoEventDispatchAssertion assertNoEventDispatch; + + child.setParentNode(this); + + if (m_lastChild) { + child.setPreviousSibling(m_lastChild); + m_lastChild->setNextSibling(&child); + } else + m_firstChild = &child; + + m_lastChild = &child; +} + +inline auto ContainerNode::changeForChildInsertion(Node& child, ChildChangeSource source, ReplacedAllChildren replacedAllChildren) -> ChildChange +{ + if (replacedAllChildren == ReplacedAllChildren::Yes) + return { AllChildrenReplaced, nullptr, nullptr, source }; + + return { + child.isElementNode() ? ElementInserted : child.isTextNode() ? TextInserted : NonContentsChildInserted, + ElementTraversal::previousSibling(child), + ElementTraversal::nextSibling(child), + source + }; +} + +void ContainerNode::notifyChildInserted(Node& child, const ChildChange& change) +{ + ChildListMutationScope(*this).childAdded(child); + + NodeVector postInsertionNotificationTargets; + notifyChildNodeInserted(*this, child, postInsertionNotificationTargets); childrenChanged(change); + + for (auto& target : postInsertionNotificationTargets) + target->finishedInsertingSubtree(); } void ContainerNode::notifyChildRemoved(Node& child, Node* previousSibling, Node* nextSibling, ChildChangeSource source) { + NoEventDispatchAssertion assertNoEventDispatch; + notifyChildNodeRemoved(*this, child); + ChildChange change; - change.type = child.isElementNode() ? ElementRemoved : child.isTextNode() ? TextRemoved : NonContentsChildChanged; - change.previousSiblingElement = (!previousSibling || previousSibling->isElementNode()) ? toElement(previousSibling) : ElementTraversal::previousSibling(previousSibling); - change.nextSiblingElement = (!nextSibling || nextSibling->isElementNode()) ? toElement(nextSibling) : ElementTraversal::nextSibling(nextSibling); + change.type = is<Element>(child) ? ElementRemoved : is<Text>(child) ? TextRemoved : NonContentsChildRemoved; + change.previousSiblingElement = (!previousSibling || is<Element>(*previousSibling)) ? downcast<Element>(previousSibling) : ElementTraversal::previousSibling(*previousSibling); + change.nextSiblingElement = (!nextSibling || is<Element>(*nextSibling)) ? downcast<Element>(nextSibling) : ElementTraversal::nextSibling(*nextSibling); change.source = source; childrenChanged(change); } -void ContainerNode::parserInsertBefore(PassRefPtr<Node> newChild, Node* nextChild) +void ContainerNode::parserInsertBefore(Node& newChild, Node& nextChild) { - ASSERT(newChild); - ASSERT(nextChild); - ASSERT(nextChild->parentNode() == this); - ASSERT(!newChild->isDocumentFragment()); -#if ENABLE(TEMPLATE_ELEMENT) + ASSERT(nextChild.parentNode() == this); + ASSERT(!newChild.isDocumentFragment()); ASSERT(!hasTagName(HTMLNames::templateTag)); -#endif - if (nextChild->previousSibling() == newChild || nextChild == newChild) // nothing to do + if (nextChild.previousSibling() == &newChild || &nextChild == &newChild) // nothing to do return; - if (&document() != &newChild->document()) - document().adoptNode(newChild.get(), ASSERT_NO_EXCEPTION); - - insertBeforeCommon(*nextChild, *newChild.get()); + if (&document() != &newChild.document()) + document().adoptNode(newChild); - newChild->updateAncestorConnectedSubframeCountForInsertion(); + insertBeforeCommon(nextChild, newChild); - ChildListMutationScope(*this).childAdded(*newChild); + newChild.updateAncestorConnectedSubframeCountForInsertion(); - notifyChildInserted(*newChild, ChildChangeSourceParser); - - ChildNodeInsertionNotifier(*this).notify(*newChild); - - newChild->setNeedsStyleRecalc(ReconstructRenderTree); + notifyChildInserted(newChild, changeForChildInsertion(newChild, ChildChangeSourceParser)); } -bool ContainerNode::replaceChild(PassRefPtr<Node> newChild, Node* oldChild, ExceptionCode& ec) +ExceptionOr<void> ContainerNode::replaceChild(Node& newChild, Node& oldChild) { // Check that this node is not "floating". // If it is, it can be deleted as a side effect of sending mutation events. ASSERT(refCount() || parentOrShadowHostNode()); - Ref<ContainerNode> protect(*this); - - ec = 0; - - if (oldChild == newChild) // nothing to do - return true; - - if (!oldChild) { - ec = NOT_FOUND_ERR; - return false; - } + Ref<ContainerNode> protectedThis(*this); // Make sure replacing the old child with the new is ok - if (!checkReplaceChild(this, newChild.get(), oldChild, ec)) - return false; + auto validityResult = checkPreReplacementValidity(*this, newChild, oldChild); + if (validityResult.hasException()) + return validityResult.releaseException(); // NOT_FOUND_ERR: Raised if oldChild is not a child of this node. - if (oldChild->parentNode() != this) { - ec = NOT_FOUND_ERR; - return false; - } + if (oldChild.parentNode() != this) + return Exception { NOT_FOUND_ERR }; - ChildListMutationScope mutation(*this); - - RefPtr<Node> next = oldChild->nextSibling(); + RefPtr<Node> refChild = oldChild.nextSibling(); + if (refChild.get() == &newChild) + refChild = refChild->nextSibling(); - // Remove the node we're replacing - Ref<Node> removedChild(*oldChild); - removeChild(oldChild, ec); - if (ec) - return false; + NodeVector targets; + { + ChildListMutationScope mutation(*this); + auto collectResult = collectChildrenAndRemoveFromOldParent(newChild, targets); + if (collectResult.hasException()) + return collectResult.releaseException(); + } - if (next && (next->previousSibling() == newChild || next == newChild)) // nothing to do - return true; + // Do this one more time because collectChildrenAndRemoveFromOldParent() fires a MutationEvent. + validityResult = checkPreReplacementValidity(*this, newChild, oldChild); + if (validityResult.hasException()) + return validityResult.releaseException(); - // Does this one more time because removeChild() fires a MutationEvent. - if (!checkReplaceChild(this, newChild.get(), oldChild, ec)) - return false; + // Remove the node we're replacing. + Ref<Node> protectOldChild(oldChild); - NodeVector targets; - collectChildrenAndRemoveFromOldParent(*newChild.get(), targets, ec); - if (ec) - return false; + ChildListMutationScope mutation(*this); - // Does this yet another check because collectChildrenAndRemoveFromOldParent() fires a MutationEvent. - if (!checkReplaceChild(this, newChild.get(), oldChild, ec)) - return false; + // If oldChild == newChild then oldChild no longer has a parent at this point. + if (oldChild.parentNode()) { + auto removeResult = removeChild(oldChild); + if (removeResult.hasException()) + return removeResult.releaseException(); - InspectorInstrumentation::willInsertDOMNode(&document(), this); + // Does this one more time because removeChild() fires a MutationEvent. + validityResult = checkPreReplacementValidity(*this, newChild, oldChild); + if (validityResult.hasException()) + return validityResult.releaseException(); + } - // Add the new child(ren) - for (auto it = targets.begin(), end = targets.end(); it != end; ++it) { - Node& child = it->get(); + InspectorInstrumentation::willInsertDOMNode(document(), *this); + // Add the new child(ren). + for (auto& child : targets) { // Due to arbitrary code running in response to a DOM mutation event it's - // possible that "next" is no longer a child of "this". + // possible that "refChild" is no longer a child of "this". // It's also possible that "child" has been inserted elsewhere. // In either of those cases, we'll just stop. - if (next && next->parentNode() != this) + if (refChild && refChild->parentNode() != this) break; - if (child.parentNode()) + if (child->parentNode()) break; - treeScope().adoptIfNeeded(&child); - - // Add child before "next". { NoEventDispatchAssertion assertNoEventDispatch; - if (next) - insertBeforeCommon(*next, child); + treeScope().adoptIfNeeded(child); + if (refChild) + insertBeforeCommon(*refChild, child.get()); else - appendChildToContainer(&child, *this); + appendChildCommon(child); } - updateTreeAfterInsertion(child); + updateTreeAfterInsertion(child.get()); } dispatchSubtreeModifiedEvent(); - return true; + return { }; } -void ContainerNode::willRemoveChild(Node& child) +static void willRemoveChild(ContainerNode& container, Node& child) { ASSERT(child.parentNode()); @@ -488,12 +488,11 @@ void ContainerNode::willRemoveChild(Node& child) child.notifyMutationObserversNodeWillDetach(); dispatchChildRemovalEvents(child); - if (child.parentNode() != this) + if (child.parentNode() != &container) return; - child.document().nodeWillBeRemoved(&child); // e.g. mutation event listener can create a new range. - if (child.isContainerNode()) - disconnectSubframesIfNeeded(toContainerNode(child), RootAndDescendants); + if (is<ContainerNode>(child)) + disconnectSubframesIfNeeded(downcast<ContainerNode>(child), RootAndDescendants); } static void willRemoveChildren(ContainerNode& container) @@ -502,17 +501,14 @@ static void willRemoveChildren(ContainerNode& container) getChildNodes(container, children); ChildListMutationScope mutation(container); - for (auto it = children.begin(); it != children.end(); ++it) { - Node& child = it->get(); - mutation.willRemoveChild(child); - child.notifyMutationObserversNodeWillDetach(); + for (auto& child : children) { + mutation.willRemoveChild(child.get()); + child->notifyMutationObserversNodeWillDetach(); // fire removed from document mutation events. - dispatchChildRemovalEvents(child); + dispatchChildRemovalEvents(child.get()); } - container.document().nodeChildrenWillBeRemoved(container); - disconnectSubframesIfNeeded(container, DescendantsOnly); } @@ -521,70 +517,48 @@ void ContainerNode::disconnectDescendantFrames() disconnectSubframesIfNeeded(*this, RootAndDescendants); } -bool ContainerNode::removeChild(Node* oldChild, ExceptionCode& ec) +ExceptionOr<void> ContainerNode::removeChild(Node& oldChild) { // Check that this node is not "floating". // If it is, it can be deleted as a side effect of sending mutation events. ASSERT(refCount() || parentOrShadowHostNode()); - Ref<ContainerNode> protect(*this); - - ec = 0; - - // NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly. - if (isReadOnlyNode()) { - ec = NO_MODIFICATION_ALLOWED_ERR; - return false; - } + Ref<ContainerNode> protectedThis(*this); // NOT_FOUND_ERR: Raised if oldChild is not a child of this node. - if (!oldChild || oldChild->parentNode() != this) { - ec = NOT_FOUND_ERR; - return false; - } - - Ref<Node> child(*oldChild); - - document().removeFocusedNodeOfSubtree(&child.get()); + if (oldChild.parentNode() != this) + return Exception { NOT_FOUND_ERR }; -#if ENABLE(FULLSCREEN_API) - document().removeFullScreenElementOfSubtree(&child.get()); -#endif - - // Events fired when blurring currently focused node might have moved this - // child into a different parent. - if (child->parentNode() != this) { - ec = NOT_FOUND_ERR; - return false; - } + Ref<Node> child(oldChild); - willRemoveChild(child.get()); + willRemoveChild(*this, child); - // Mutation events might have moved this child into a different parent. - if (child->parentNode() != this) { - ec = NOT_FOUND_ERR; - return false; - } + // Mutation events in willRemoveChild might have moved this child into a different parent. + if (child->parentNode() != this) + return Exception { NOT_FOUND_ERR }; { WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates; + NoEventDispatchAssertion assertNoEventDispatch; + + document().nodeWillBeRemoved(child); Node* prev = child->previousSibling(); Node* next = child->nextSibling(); - removeBetween(prev, next, child.get()); + removeBetween(prev, next, child); - notifyChildRemoved(child.get(), prev, next, ChildChangeSourceAPI); - - ChildNodeRemovalNotifier(*this).notify(child.get()); + notifyChildRemoved(child, prev, next, ChildChangeSourceAPI); } + + rebuildSVGExtensionsElementsIfNecessary(); dispatchSubtreeModifiedEvent(); - return true; + return { }; } void ContainerNode::removeBetween(Node* previousChild, Node* nextChild, Node& oldChild) { - InspectorInstrumentation::didRemoveDOMNode(&oldChild.document(), &oldChild); + InspectorInstrumentation::didRemoveDOMNode(oldChild.document(), oldChild); NoEventDispatchAssertion assertNoEventDispatch; @@ -592,40 +566,116 @@ void ContainerNode::removeBetween(Node* previousChild, Node* nextChild, Node& ol destroyRenderTreeIfNeeded(oldChild); - if (nextChild) + if (nextChild) { nextChild->setPreviousSibling(previousChild); - if (previousChild) + oldChild.setNextSibling(nullptr); + } else { + ASSERT(m_lastChild == &oldChild); + m_lastChild = previousChild; + } + if (previousChild) { previousChild->setNextSibling(nextChild); - if (m_firstChild == &oldChild) + oldChild.setPreviousSibling(nullptr); + } else { + ASSERT(m_firstChild == &oldChild); m_firstChild = nextChild; - if (m_lastChild == &oldChild) - m_lastChild = previousChild; + } - oldChild.setPreviousSibling(0); - oldChild.setNextSibling(0); - oldChild.setParentNode(0); + ASSERT(m_firstChild != &oldChild); + ASSERT(m_lastChild != &oldChild); + ASSERT(!oldChild.previousSibling()); + ASSERT(!oldChild.nextSibling()); + oldChild.setParentNode(nullptr); - document().adoptIfNeeded(&oldChild); + document().adoptIfNeeded(oldChild); } void ContainerNode::parserRemoveChild(Node& oldChild) { - ASSERT(oldChild.parentNode() == this); - ASSERT(!oldChild.isDocumentFragment()); + disconnectSubframesIfNeeded(*this, DescendantsOnly); + if (oldChild.parentNode() != this) + return; - Node* prev = oldChild.previousSibling(); - Node* next = oldChild.nextSibling(); + { + NoEventDispatchAssertion assertNoEventDispatch; - oldChild.updateAncestorConnectedSubframeCountForRemoval(); + document().nodeChildrenWillBeRemoved(*this); - ChildListMutationScope(*this).willRemoveChild(oldChild); - oldChild.notifyMutationObserversNodeWillDetach(); + ASSERT(oldChild.parentNode() == this); + ASSERT(!oldChild.isDocumentFragment()); - removeBetween(prev, next, oldChild); + Node* prev = oldChild.previousSibling(); + Node* next = oldChild.nextSibling(); - notifyChildRemoved(oldChild, prev, next, ChildChangeSourceParser); + ChildListMutationScope(*this).willRemoveChild(oldChild); + oldChild.notifyMutationObserversNodeWillDetach(); - ChildNodeRemovalNotifier(*this).notify(oldChild); + removeBetween(prev, next, oldChild); + + notifyChildRemoved(oldChild, prev, next, ChildChangeSourceParser); + } +} + +// https://dom.spec.whatwg.org/#concept-node-replace-all +void ContainerNode::replaceAllChildren(std::nullptr_t) +{ + ChildListMutationScope mutation(*this); + removeChildren(); +} + +// https://dom.spec.whatwg.org/#concept-node-replace-all +void ContainerNode::replaceAllChildren(Ref<Node>&& node) +{ + // This function assumes the input node is not a DocumentFragment and is parentless to decrease complexity. + ASSERT(!is<DocumentFragment>(node)); + ASSERT(!node->parentNode()); + + if (!hasChildNodes()) { + // appendChildWithoutPreInsertionValidityCheck() can only throw when node has a parent and we already asserted it doesn't. + auto result = appendChildWithoutPreInsertionValidityCheck(node); + ASSERT_UNUSED(result, !result.hasException()); + return; + } + + Ref<ContainerNode> protectedThis(*this); + ChildListMutationScope mutation(*this); + + // If node is not null, adopt node into parent's node document. + treeScope().adoptIfNeeded(node); + + // Remove all parent's children, in tree order. + willRemoveChildren(*this); + + { + WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates; + { + NoEventDispatchAssertion assertNoEventDispatch; + + document().nodeChildrenWillBeRemoved(*this); + + while (RefPtr<Node> child = m_firstChild) { + removeBetween(nullptr, child->nextSibling(), *child); + notifyChildNodeRemoved(*this, *child); + } + + // If node is not null, insert node into parent before null. + ASSERT(!ensurePreInsertionValidity(node, nullptr).hasException()); + InspectorInstrumentation::willInsertDOMNode(document(), *this); + + appendChildCommon(node); + } + + updateTreeAfterInsertion(node, ReplacedAllChildren::Yes); + } + + rebuildSVGExtensionsElementsIfNecessary(); + dispatchSubtreeModifiedEvent(); +} + +inline void ContainerNode::rebuildSVGExtensionsElementsIfNecessary() +{ + if (document().svgExtensions() && !is<SVGUseElement>(shadowHost())) + document().accessSVGExtensions().rebuildElements(); } // this differs from other remove functions because it forcibly removes all the children, @@ -636,187 +686,106 @@ void ContainerNode::removeChildren() return; // The container node can be removed from event handlers. - Ref<ContainerNode> protect(*this); - - // exclude this node when looking for removed focusedNode since only children will be removed - document().removeFocusedNodeOfSubtree(this, true); - -#if ENABLE(FULLSCREEN_API) - document().removeFullScreenElementOfSubtree(this, true); -#endif + Ref<ContainerNode> protectedThis(*this); // Do any prep work needed before actually starting to detach // and remove... e.g. stop loading frames, fire unload events. willRemoveChildren(*this); - NodeVector removedChildren; { WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates; - { - NoEventDispatchAssertion assertNoEventDispatch; - removedChildren.reserveInitialCapacity(childNodeCount()); - while (RefPtr<Node> n = m_firstChild) { - removedChildren.append(*m_firstChild); - removeBetween(0, m_firstChild->nextSibling(), *m_firstChild); - } + NoEventDispatchAssertion assertNoEventDispatch; + + document().nodeChildrenWillBeRemoved(*this); + + while (RefPtr<Node> child = m_firstChild) { + removeBetween(0, child->nextSibling(), *child); + notifyChildNodeRemoved(*this, *child); } ChildChange change = { AllChildrenRemoved, nullptr, nullptr, ChildChangeSourceAPI }; childrenChanged(change); - - for (size_t i = 0; i < removedChildren.size(); ++i) - ChildNodeRemovalNotifier(*this).notify(removedChildren[i].get()); } + rebuildSVGExtensionsElementsIfNecessary(); dispatchSubtreeModifiedEvent(); } -bool ContainerNode::appendChild(PassRefPtr<Node> newChild, ExceptionCode& ec) +ExceptionOr<void> ContainerNode::appendChild(Node& newChild) { - Ref<ContainerNode> protect(*this); - // Check that this node is not "floating". // If it is, it can be deleted as a side effect of sending mutation events. ASSERT(refCount() || parentOrShadowHostNode()); - ec = 0; - // Make sure adding the new child is ok - if (!checkAddChild(this, newChild.get(), ec)) - return false; + auto validityCheckResult = ensurePreInsertionValidity(newChild, nullptr); + if (validityCheckResult.hasException()) + return validityCheckResult.releaseException(); + + return appendChildWithoutPreInsertionValidityCheck(newChild); +} - if (newChild == m_lastChild) // nothing to do - return newChild; +ExceptionOr<void> ContainerNode::appendChildWithoutPreInsertionValidityCheck(Node& newChild) +{ + Ref<ContainerNode> protectedThis(*this); NodeVector targets; - collectChildrenAndRemoveFromOldParent(*newChild.get(), targets, ec); - if (ec) - return false; + auto removeResult = collectChildrenAndRemoveFromOldParent(newChild, targets); + if (removeResult.hasException()) + return removeResult.releaseException(); if (targets.isEmpty()) - return true; + return { }; // We need this extra check because collectChildrenAndRemoveFromOldParent() can fire mutation events. - if (!checkAcceptChildGuaranteedNodeTypes(this, newChild.get(), ec)) - return false; + auto nodeTypeResult = checkAcceptChildGuaranteedNodeTypes(*this, newChild); + if (nodeTypeResult.hasException()) + return nodeTypeResult.releaseException(); - InspectorInstrumentation::willInsertDOMNode(&document(), this); + InspectorInstrumentation::willInsertDOMNode(document(), *this); // Now actually add the child(ren) ChildListMutationScope mutation(*this); - for (auto it = targets.begin(), end = targets.end(); it != end; ++it) { - Node& child = it->get(); - + for (auto& child : targets) { // If the child has a parent again, just stop what we're doing, because // that means someone is doing something with DOM mutation -- can't re-parent // a child that already has a parent. - if (child.parentNode()) + if (child->parentNode()) break; - treeScope().adoptIfNeeded(&child); - // Append child to the end of the list { NoEventDispatchAssertion assertNoEventDispatch; - appendChildToContainer(&child, *this); + treeScope().adoptIfNeeded(child); + appendChildCommon(child); } - updateTreeAfterInsertion(child); + updateTreeAfterInsertion(child.get()); } dispatchSubtreeModifiedEvent(); - return true; + return { }; } -void ContainerNode::parserAppendChild(PassRefPtr<Node> newChild) +void ContainerNode::parserAppendChild(Node& newChild) { - ASSERT(newChild); - ASSERT(!newChild->parentNode()); // Use appendChild if you need to handle reparenting (and want DOM mutation events). - ASSERT(!newChild->isDocumentFragment()); -#if ENABLE(TEMPLATE_ELEMENT) + ASSERT(!newChild.parentNode()); // Use appendChild if you need to handle reparenting (and want DOM mutation events). + ASSERT(!newChild.isDocumentFragment()); ASSERT(!hasTagName(HTMLNames::templateTag)); -#endif - - if (&document() != &newChild->document()) - document().adoptNode(newChild.get(), ASSERT_NO_EXCEPTION); { NoEventDispatchAssertion assertNoEventDispatch; - // FIXME: This method should take a PassRefPtr. - appendChildToContainer(newChild.get(), *this); - treeScope().adoptIfNeeded(newChild.get()); - } - - newChild->updateAncestorConnectedSubframeCountForInsertion(); - - ChildListMutationScope(*this).childAdded(*newChild); - - notifyChildInserted(*newChild, ChildChangeSourceParser); - - ChildNodeInsertionNotifier(*this).notify(*newChild); - newChild->setNeedsStyleRecalc(ReconstructRenderTree); -} - -void ContainerNode::suspendPostAttachCallbacks(Document& document) -{ - if (!s_attachDepth) { - ASSERT(!s_shouldReEnableMemoryCacheCallsAfterAttach); - if (Page* page = document.page()) { - // FIXME: How can this call be specific to one Page, while the - // s_attachDepth is a global? Doesn't make sense. - if (page->areMemoryCacheClientCallsEnabled()) { - page->setMemoryCacheClientCallsEnabled(false); - s_shouldReEnableMemoryCacheCallsAfterAttach = true; - } - } - platformStrategies()->loaderStrategy()->resourceLoadScheduler()->suspendPendingRequests(); - } - ++s_attachDepth; -} + if (&document() != &newChild.document()) + document().adoptNode(newChild); -void ContainerNode::resumePostAttachCallbacks(Document& document) -{ - if (s_attachDepth == 1) { - Ref<Document> protect(document); - - if (s_postAttachCallbackQueue) - dispatchPostAttachCallbacks(); - if (s_shouldReEnableMemoryCacheCallsAfterAttach) { - s_shouldReEnableMemoryCacheCallsAfterAttach = false; - if (Page* page = document.page()) - page->setMemoryCacheClientCallsEnabled(true); - } - platformStrategies()->loaderStrategy()->resourceLoadScheduler()->resumePendingRequests(); + appendChildCommon(newChild); + treeScope().adoptIfNeeded(newChild); } - --s_attachDepth; -} -void ContainerNode::queuePostAttachCallback(NodeCallback callback, Node& node, unsigned callbackData) -{ - if (!s_postAttachCallbackQueue) - s_postAttachCallbackQueue = new NodeCallbackQueue; - - s_postAttachCallbackQueue->append(CallbackInfo(callback, CallbackParameters(&node, callbackData))); -} + newChild.updateAncestorConnectedSubframeCountForInsertion(); -bool ContainerNode::postAttachCallbacksAreSuspended() -{ - return s_attachDepth; -} - -void ContainerNode::dispatchPostAttachCallbacks() -{ - // We recalculate size() each time through the loop because a callback - // can add more callbacks to the end of the queue. - for (size_t i = 0; i < s_postAttachCallbackQueue->size(); ++i) { - const CallbackInfo& info = (*s_postAttachCallbackQueue)[i]; - NodeCallback callback = info.first; - CallbackParameters params = info.second; - - callback(*params.first, params.second); - } - s_postAttachCallbackQueue->clear(); + notifyChildInserted(newChild, changeForChildInsertion(newChild, ChildChangeSourceParser)); } void ContainerNode::childrenChanged(const ChildChange& change) @@ -827,182 +796,30 @@ void ContainerNode::childrenChanged(const ChildChange& change) invalidateNodeListAndCollectionCachesInAncestors(); } -inline static void cloneChildNodesAvoidingDeleteButton(ContainerNode* parent, ContainerNode* clonedParent, HTMLElement* deleteButtonContainerElement) -{ - ExceptionCode ec = 0; - for (Node* child = parent->firstChild(); child && !ec; child = child->nextSibling()) { - -#if ENABLE(DELETION_UI) - if (child == deleteButtonContainerElement) - continue; -#else - UNUSED_PARAM(deleteButtonContainerElement); -#endif - - RefPtr<Node> clonedChild = child->cloneNode(false); - clonedParent->appendChild(clonedChild, ec); - - if (!ec && child->isContainerNode()) - cloneChildNodesAvoidingDeleteButton(toContainerNode(child), toContainerNode(clonedChild.get()), deleteButtonContainerElement); - } -} - -void ContainerNode::cloneChildNodes(ContainerNode *clone) -{ -#if ENABLE(DELETION_UI) - HTMLElement* deleteButtonContainerElement = 0; - if (Frame* frame = document().frame()) - deleteButtonContainerElement = frame->editor().deleteButtonController().containerElement(); - cloneChildNodesAvoidingDeleteButton(this, clone, deleteButtonContainerElement); -#else - cloneChildNodesAvoidingDeleteButton(this, clone, 0); -#endif -} - -bool ContainerNode::getUpperLeftCorner(FloatPoint& point) const -{ - if (!renderer()) - return false; - // What is this code really trying to do? - RenderObject* o = renderer(); - RenderObject* p = o; - - if (!o->isInline() || o->isReplaced()) { - point = o->localToAbsolute(FloatPoint(), UseTransforms); - return true; - } - - // find the next text/image child, to get a position - while (o) { - p = o; - if (RenderObject* child = o->firstChildSlow()) - o = child; - else if (o->nextSibling()) - o = o->nextSibling(); - else { - RenderObject* next = 0; - while (!next && o->parent()) { - o = o->parent(); - next = o->nextSibling(); - } - o = next; - - if (!o) - break; - } - ASSERT(o); - - if (!o->isInline() || o->isReplaced()) { - point = o->localToAbsolute(FloatPoint(), UseTransforms); - return true; - } - - if (p->node() && p->node() == this && o->isText() && !toRenderText(o)->firstTextBox()) { - // do nothing - skip unrendered whitespace that is a child or next sibling of the anchor - } else if (o->isText() || o->isReplaced()) { - point = FloatPoint(); - if (o->isText() && toRenderText(o)->firstTextBox()) { - point.move(toRenderText(o)->linesBoundingBox().x(), toRenderText(o)->firstTextBox()->root().lineTop()); - } else if (o->isBox()) { - RenderBox* box = toRenderBox(o); - point.moveBy(box->location()); - } - point = o->container()->localToAbsolute(point, UseTransforms); - return true; - } - } - - // If the target doesn't have any children or siblings that could be used to calculate the scroll position, we must be - // at the end of the document. Scroll to the bottom. FIXME: who said anything about scrolling? - if (!o && document().view()) { - point = FloatPoint(0, document().view()->contentsHeight()); - return true; - } - return false; -} - -bool ContainerNode::getLowerRightCorner(FloatPoint& point) const +void ContainerNode::cloneChildNodes(ContainerNode& clone) { - if (!renderer()) - return false; - - RenderObject* o = renderer(); - if (!o->isInline() || o->isReplaced()) { - RenderBox* box = toRenderBox(o); - point = o->localToAbsolute(LayoutPoint(box->size()), UseTransforms); - return true; + Document& targetDocument = clone.document(); + for (Node* child = firstChild(); child; child = child->nextSibling()) { + auto clonedChild = child->cloneNodeInternal(targetDocument, CloningOperation::SelfWithTemplateContent); + if (!clone.appendChild(clonedChild).hasException() && is<ContainerNode>(*child)) + downcast<ContainerNode>(*child).cloneChildNodes(downcast<ContainerNode>(clonedChild.get())); } - - // find the last text/image child, to get a position - while (o) { - if (RenderObject* child = o->lastChildSlow()) - o = child; - else if (o->previousSibling()) - o = o->previousSibling(); - else { - RenderObject* prev = 0; - while (!prev) { - o = o->parent(); - if (!o) - return false; - prev = o->previousSibling(); - } - o = prev; - } - ASSERT(o); - if (o->isText() || o->isReplaced()) { - point = FloatPoint(); - if (o->isText()) { - RenderText* text = toRenderText(o); - IntRect linesBox = text->linesBoundingBox(); - if (!linesBox.maxX() && !linesBox.maxY()) - continue; - point.moveBy(linesBox.maxXMaxYCorner()); - } else { - RenderBox* box = toRenderBox(o); - point.moveBy(box->frameRect().maxXMaxYCorner()); - } - point = o->container()->localToAbsolute(point, UseTransforms); - return true; - } - } - return true; -} - -LayoutRect ContainerNode::boundingBox() const -{ - FloatPoint upperLeft, lowerRight; - bool foundUpperLeft = getUpperLeftCorner(upperLeft); - bool foundLowerRight = getLowerRightCorner(lowerRight); - - // If we've found one corner, but not the other, - // then we should just return a point at the corner that we did find. - if (foundUpperLeft != foundLowerRight) { - if (foundUpperLeft) - lowerRight = upperLeft; - else - upperLeft = lowerRight; - } - - return enclosingLayoutRect(FloatRect(upperLeft, lowerRight.expandedTo(upperLeft) - upperLeft)); } -unsigned ContainerNode::childNodeCount() const +unsigned ContainerNode::countChildNodes() const { unsigned count = 0; - Node *n; - for (n = firstChild(); n; n = n->nextSibling()) - count++; + for (Node* child = firstChild(); child; child = child->nextSibling()) + ++count; return count; } -Node *ContainerNode::childNode(unsigned index) const +Node* ContainerNode::traverseToChildAt(unsigned index) const { - unsigned i; - Node *n = firstChild(); - for (i = 0; n != 0 && i < index; i++) - n = n->nextSibling(); - return n; + Node* child = firstChild(); + for (; child && index > 0; --index) + child = child->nextSibling(); + return child; } static void dispatchChildInsertionEvents(Node& child) @@ -1010,7 +827,7 @@ static void dispatchChildInsertionEvents(Node& child) if (child.isInShadowTree()) return; - ASSERT(!NoEventDispatchAssertion::isEventDispatchForbidden()); + ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventDispatchAllowedInSubtree(child)); RefPtr<Node> c = &child; Ref<Document> document(child.document()); @@ -1019,8 +836,8 @@ static void dispatchChildInsertionEvents(Node& child) c->dispatchScopedEvent(MutationEvent::create(eventNames().DOMNodeInsertedEvent, true, c->parentNode())); // dispatch the DOMNodeInsertedIntoDocument event to all descendants - if (c->inDocument() && document->hasListenerType(Document::DOMNODEINSERTEDINTODOCUMENT_LISTENER)) { - for (; c; c = NodeTraversal::next(c.get(), &child)) + if (c->isConnected() && document->hasListenerType(Document::DOMNODEINSERTEDINTODOCUMENT_LISTENER)) { + for (; c; c = NodeTraversal::next(*c, &child)) c->dispatchScopedEvent(MutationEvent::create(eventNames().DOMNodeInsertedIntoDocumentEvent, false)); } } @@ -1028,14 +845,14 @@ static void dispatchChildInsertionEvents(Node& child) static void dispatchChildRemovalEvents(Node& child) { if (child.isInShadowTree()) { - InspectorInstrumentation::willRemoveDOMNode(&child.document(), &child); + InspectorInstrumentation::willRemoveDOMNode(child.document(), child); return; } - ASSERT(!NoEventDispatchAssertion::isEventDispatchForbidden()); + ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventDispatchAllowedInSubtree(child)); willCreatePossiblyOrphanedTreeByRemoval(&child); - InspectorInstrumentation::willRemoveDOMNode(&child.document(), &child); + InspectorInstrumentation::willRemoveDOMNode(child.document(), child); RefPtr<Node> c = &child; Ref<Document> document(child.document()); @@ -1045,93 +862,121 @@ static void dispatchChildRemovalEvents(Node& child) c->dispatchScopedEvent(MutationEvent::create(eventNames().DOMNodeRemovedEvent, true, c->parentNode())); // dispatch the DOMNodeRemovedFromDocument event to all descendants - if (c->inDocument() && document->hasListenerType(Document::DOMNODEREMOVEDFROMDOCUMENT_LISTENER)) { - for (; c; c = NodeTraversal::next(c.get(), &child)) + if (c->isConnected() && document->hasListenerType(Document::DOMNODEREMOVEDFROMDOCUMENT_LISTENER)) { + for (; c; c = NodeTraversal::next(*c, &child)) c->dispatchScopedEvent(MutationEvent::create(eventNames().DOMNodeRemovedFromDocumentEvent, false)); } } -void ContainerNode::updateTreeAfterInsertion(Node& child) +void ContainerNode::updateTreeAfterInsertion(Node& child, ReplacedAllChildren replacedAllChildren) { ASSERT(child.refCount()); - ChildListMutationScope(*this).childAdded(child); - - notifyChildInserted(child, ChildChangeSourceAPI); - - ChildNodeInsertionNotifier(*this).notify(child); - - child.setNeedsStyleRecalc(ReconstructRenderTree); + notifyChildInserted(child, changeForChildInsertion(child, ChildChangeSourceAPI, replacedAllChildren)); dispatchChildInsertionEvents(child); } -void ContainerNode::setAttributeEventListener(const AtomicString& eventType, const QualifiedName& attributeName, const AtomicString& attributeValue) +ExceptionOr<Element*> ContainerNode::querySelector(const String& selectors) { - setAttributeEventListener(eventType, JSLazyEventListener::createForNode(*this, attributeName, attributeValue)); + auto query = document().selectorQueryForString(selectors); + if (query.hasException()) + return query.releaseException(); + return query.releaseReturnValue().queryFirst(*this); } -Element* ContainerNode::querySelector(const AtomicString& selectors, ExceptionCode& ec) +ExceptionOr<Ref<NodeList>> ContainerNode::querySelectorAll(const String& selectors) { - if (selectors.isEmpty()) { - ec = SYNTAX_ERR; - return nullptr; - } + auto query = document().selectorQueryForString(selectors); + if (query.hasException()) + return query.releaseException(); + return query.releaseReturnValue().queryAll(*this); +} - SelectorQuery* selectorQuery = document().selectorQueryCache().add(selectors, document(), ec); - if (!selectorQuery) - return nullptr; - return selectorQuery->queryFirst(*this); +Ref<HTMLCollection> ContainerNode::getElementsByTagName(const AtomicString& qualifiedName) +{ + ASSERT(!qualifiedName.isNull()); + + if (qualifiedName == starAtom) + return ensureRareData().ensureNodeLists().addCachedCollection<AllDescendantsCollection>(*this, AllDescendants); + + if (document().isHTMLDocument()) + return ensureRareData().ensureNodeLists().addCachedCollection<HTMLTagCollection>(*this, ByHTMLTag, qualifiedName); + return ensureRareData().ensureNodeLists().addCachedCollection<TagCollection>(*this, ByTag, qualifiedName); } -RefPtr<NodeList> ContainerNode::querySelectorAll(const AtomicString& selectors, ExceptionCode& ec) +Ref<HTMLCollection> ContainerNode::getElementsByTagNameNS(const AtomicString& namespaceURI, const AtomicString& localName) { - if (selectors.isEmpty()) { - ec = SYNTAX_ERR; - return nullptr; - } + ASSERT(!localName.isNull()); + return ensureRareData().ensureNodeLists().addCachedTagCollectionNS(*this, namespaceURI.isEmpty() ? nullAtom : namespaceURI, localName); +} + +Ref<NodeList> ContainerNode::getElementsByName(const String& elementName) +{ + return ensureRareData().ensureNodeLists().addCacheWithAtomicName<NameNodeList>(*this, elementName); +} - SelectorQuery* selectorQuery = document().selectorQueryCache().add(selectors, document(), ec); - if (!selectorQuery) - return nullptr; - return selectorQuery->queryAll(*this); +Ref<HTMLCollection> ContainerNode::getElementsByClassName(const AtomicString& classNames) +{ + return ensureRareData().ensureNodeLists().addCachedCollection<ClassCollection>(*this, ByClass, classNames); } -PassRefPtr<NodeList> ContainerNode::getElementsByTagName(const AtomicString& localName) +Ref<RadioNodeList> ContainerNode::radioNodeList(const AtomicString& name) { - if (localName.isNull()) - return 0; + ASSERT(hasTagName(HTMLNames::formTag) || hasTagName(HTMLNames::fieldsetTag)); + return ensureRareData().ensureNodeLists().addCacheWithAtomicName<RadioNodeList>(*this, name); +} - if (document().isHTMLDocument()) - return ensureRareData().ensureNodeLists().addCacheWithAtomicName<HTMLTagNodeList>(*this, LiveNodeList::HTMLTagNodeListType, localName); - return ensureRareData().ensureNodeLists().addCacheWithAtomicName<TagNodeList>(*this, LiveNodeList::TagNodeListType, localName); +Ref<HTMLCollection> ContainerNode::children() +{ + return ensureRareData().ensureNodeLists().addCachedCollection<GenericCachedHTMLCollection<CollectionTypeTraits<NodeChildren>::traversalType>>(*this, NodeChildren); } -PassRefPtr<NodeList> ContainerNode::getElementsByTagNameNS(const AtomicString& namespaceURI, const AtomicString& localName) +Element* ContainerNode::firstElementChild() const { - if (localName.isNull()) - return 0; + return ElementTraversal::firstChild(*this); +} - if (namespaceURI == starAtom) - return getElementsByTagName(localName); +Element* ContainerNode::lastElementChild() const +{ + return ElementTraversal::lastChild(*this); +} - return ensureRareData().ensureNodeLists().addCacheWithQualifiedName(*this, namespaceURI.isEmpty() ? nullAtom : namespaceURI, localName); +unsigned ContainerNode::childElementCount() const +{ + auto children = childrenOfType<Element>(*this); + return std::distance(children.begin(), children.end()); } -PassRefPtr<NodeList> ContainerNode::getElementsByName(const String& elementName) +ExceptionOr<void> ContainerNode::append(Vector<NodeOrString>&& vector) { - return ensureRareData().ensureNodeLists().addCacheWithAtomicName<NameNodeList>(*this, LiveNodeList::NameNodeListType, elementName); + auto result = convertNodesOrStringsIntoNode(WTFMove(vector)); + if (result.hasException()) + return result.releaseException(); + + auto node = result.releaseReturnValue(); + if (!node) + return { }; + + return appendChild(*node); } -PassRefPtr<NodeList> ContainerNode::getElementsByClassName(const String& classNames) +ExceptionOr<void> ContainerNode::prepend(Vector<NodeOrString>&& vector) { - return ensureRareData().ensureNodeLists().addCacheWithName<ClassNodeList>(*this, LiveNodeList::ClassNodeListType, classNames); + auto result = convertNodesOrStringsIntoNode(WTFMove(vector)); + if (result.hasException()) + return result.releaseException(); + + auto node = result.releaseReturnValue(); + if (!node) + return { }; + + return insertBefore(*node, firstChild()); } -PassRefPtr<RadioNodeList> ContainerNode::radioNodeList(const AtomicString& name) +HTMLCollection* ContainerNode::cachedHTMLCollection(CollectionType type) { - ASSERT(hasTagName(HTMLNames::formTag) || hasTagName(HTMLNames::fieldsetTag)); - return ensureRareData().ensureNodeLists().addCacheWithAtomicName<RadioNodeList>(*this, LiveNodeList::RadioNodeListType, name); + return hasRareData() && rareData()->nodeLists() ? rareData()->nodeLists()->cachedCollection<HTMLCollection>(type) : nullptr; } } // namespace WebCore |