| /* |
| * 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, 2013 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 |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "core/dom/ContainerNode.h" |
| |
| #include "bindings/core/v8/ExceptionState.h" |
| #include "core/dom/ChildFrameDisconnector.h" |
| #include "core/dom/ChildListMutationScope.h" |
| #include "core/dom/ClassCollection.h" |
| #include "core/dom/ElementTraversal.h" |
| #include "core/dom/ExceptionCode.h" |
| #include "core/dom/NameNodeList.h" |
| #include "core/dom/NodeChildRemovalTracker.h" |
| #include "core/dom/NodeComputedStyle.h" |
| #include "core/dom/NodeRareData.h" |
| #include "core/dom/NodeTraversal.h" |
| #include "core/dom/SelectorQuery.h" |
| #include "core/dom/StaticNodeList.h" |
| #include "core/dom/StyleChangeReason.h" |
| #include "core/dom/StyleEngine.h" |
| #include "core/dom/shadow/ElementShadow.h" |
| #include "core/dom/shadow/ShadowRoot.h" |
| #include "core/events/MutationEvent.h" |
| #include "core/frame/FrameView.h" |
| #include "core/html/HTMLCollection.h" |
| #include "core/html/HTMLFrameOwnerElement.h" |
| #include "core/html/HTMLTagCollection.h" |
| #include "core/html/RadioNodeList.h" |
| #include "core/layout/LayoutBlockFlow.h" |
| #include "core/layout/LayoutInline.h" |
| #include "core/layout/LayoutText.h" |
| #include "core/layout/LayoutTheme.h" |
| #include "core/layout/line/InlineTextBox.h" |
| #include "core/layout/line/RootInlineBox.h" |
| #include "core/probe/CoreProbes.h" |
| #include "platform/EventDispatchForbiddenScope.h" |
| #include "platform/ScriptForbiddenScope.h" |
| |
| namespace blink { |
| |
| using namespace HTMLNames; |
| |
| static void DispatchChildInsertionEvents(Node&); |
| static void DispatchChildRemovalEvents(Node&); |
| |
| // This dispatches various events; DOM mutation events, blur events, IFRAME |
| // unload events, etc. |
| static inline void CollectChildrenAndRemoveFromOldParent( |
| Node& node, |
| NodeVector& nodes, |
| ExceptionState& exception_state) { |
| if (node.IsDocumentFragment()) { |
| DocumentFragment& fragment = ToDocumentFragment(node); |
| GetChildNodes(fragment, nodes); |
| fragment.RemoveChildren(); |
| return; |
| } |
| nodes.push_back(&node); |
| if (ContainerNode* old_parent = node.parentNode()) |
| old_parent->RemoveChild(&node, exception_state); |
| } |
| |
| void ContainerNode::ParserTakeAllChildrenFrom(ContainerNode& old_parent) { |
| while (Node* child = old_parent.FirstChild()) { |
| // Explicitly remove since appending can fail, but this loop shouldn't be |
| // infinite. |
| old_parent.ParserRemoveChild(*child); |
| ParserAppendChild(child); |
| } |
| } |
| |
| ContainerNode::~ContainerNode() { |
| DCHECK(NeedsAttach()); |
| } |
| |
| DISABLE_CFI_PERF |
| bool ContainerNode::IsChildTypeAllowed(const Node& child) const { |
| if (!child.IsDocumentFragment()) |
| return ChildTypeAllowed(child.getNodeType()); |
| |
| for (Node* node = ToDocumentFragment(child).FirstChild(); node; |
| node = node->nextSibling()) { |
| if (!ChildTypeAllowed(node->getNodeType())) |
| return false; |
| } |
| return true; |
| } |
| |
| bool ContainerNode::ContainsConsideringHostElements( |
| const Node& new_child) const { |
| if (IsInShadowTree() || GetDocument().IsTemplateDocument()) |
| return new_child.ContainsIncludingHostElements(*this); |
| return new_child.contains(this); |
| } |
| |
| DISABLE_CFI_PERF |
| bool ContainerNode::CheckAcceptChild(const Node* new_child, |
| const Node* old_child, |
| ExceptionState& exception_state) const { |
| // Not mentioned in spec: throw NotFoundError if newChild is null |
| if (!new_child) { |
| exception_state.ThrowDOMException(kNotFoundError, |
| "The new child element is null."); |
| return false; |
| } |
| |
| // Use common case fast path if possible. |
| if ((new_child->IsElementNode() || new_child->IsTextNode()) && |
| IsElementNode()) { |
| DCHECK(IsChildTypeAllowed(*new_child)); |
| if (ContainsConsideringHostElements(*new_child)) { |
| exception_state.ThrowDOMException( |
| kHierarchyRequestError, "The new child element contains the parent."); |
| return false; |
| } |
| return true; |
| } |
| |
| // This should never happen, but also protect release builds from tree |
| // corruption. |
| DCHECK(!new_child->IsPseudoElement()); |
| if (new_child->IsPseudoElement()) { |
| exception_state.ThrowDOMException( |
| kHierarchyRequestError, "The new child element is a pseudo-element."); |
| return false; |
| } |
| |
| return CheckAcceptChildGuaranteedNodeTypes(*new_child, old_child, |
| exception_state); |
| } |
| |
| bool ContainerNode::CheckAcceptChildGuaranteedNodeTypes( |
| const Node& new_child, |
| const Node* old_child, |
| ExceptionState& exception_state) const { |
| if (IsDocumentNode()) |
| return ToDocument(this)->CanAcceptChild(new_child, old_child, |
| exception_state); |
| // Skip containsIncludingHostElements() if !newChild.parentNode() && |
| // isConnected(). |newChild| typically has no parentNode(), and it means |
| // it's !isConnected(). In such case, the contains check for connected |
| // |this| is unnecessary. |
| if (new_child.IsContainerNode() && |
| (new_child.IsDocumentNode() || new_child.parentNode() || |
| !isConnected()) && |
| new_child.ContainsIncludingHostElements(*this)) { |
| exception_state.ThrowDOMException( |
| kHierarchyRequestError, "The new child element contains the parent."); |
| return false; |
| } |
| if (!IsChildTypeAllowed(new_child)) { |
| exception_state.ThrowDOMException( |
| kHierarchyRequestError, |
| "Nodes of type '" + new_child.nodeName() + |
| "' may not be inserted inside nodes of type '" + nodeName() + "'."); |
| return false; |
| } |
| return true; |
| } |
| |
| bool ContainerNode::CollectChildrenAndRemoveFromOldParentWithCheck( |
| const Node* next, |
| const Node* old_child, |
| Node& new_child, |
| NodeVector& new_children, |
| ExceptionState& exception_state) const { |
| CollectChildrenAndRemoveFromOldParent(new_child, new_children, |
| exception_state); |
| if (exception_state.HadException() || new_children.IsEmpty()) |
| return false; |
| |
| // We need this extra check because collectChildrenAndRemoveFromOldParent() |
| // can fire various events. |
| for (const auto& child : new_children) { |
| if (child->parentNode()) { |
| // A new child was added to another parent before adding to this |
| // node. Firefox and Edge don't throw in this case. |
| return false; |
| } |
| if (!CheckAcceptChildGuaranteedNodeTypes(*child, old_child, |
| exception_state)) |
| return false; |
| } |
| if (next && next->parentNode() != this) { |
| exception_state.ThrowDOMException( |
| kNotFoundError, |
| "The node before which the new node is to " |
| "be inserted is not a child of this " |
| "node."); |
| return false; |
| } |
| return true; |
| } |
| |
| template <typename Functor> |
| void ContainerNode::InsertNodeVector(const NodeVector& targets, |
| Node* next, |
| const Functor& mutator) { |
| probe::willInsertDOMNode(this); |
| NodeVector post_insertion_notification_targets; |
| { |
| EventDispatchForbiddenScope assert_no_event_dispatch; |
| ScriptForbiddenScope forbid_script; |
| for (const auto& target_node : targets) { |
| DCHECK(target_node); |
| DCHECK(!target_node->parentNode()); |
| Node& child = *target_node; |
| mutator(*this, child, next); |
| ChildListMutationScope(*this).ChildAdded(child); |
| if (GetDocument().ContainsV1ShadowTree()) |
| child.CheckSlotChangeAfterInserted(); |
| probe::didInsertDOMNode(&child); |
| NotifyNodeInsertedInternal(child, post_insertion_notification_targets); |
| } |
| } |
| Node* unchanged_previous = |
| targets.size() > 0 ? targets[0]->previousSibling() : nullptr; |
| for (const auto& target_node : targets) { |
| ChildrenChanged(ChildrenChange::ForInsertion( |
| *target_node, unchanged_previous, next, kChildrenChangeSourceAPI)); |
| } |
| for (const auto& descendant : post_insertion_notification_targets) { |
| if (descendant->isConnected()) |
| descendant->DidNotifySubtreeInsertionsToDocument(); |
| } |
| for (const auto& target_node : targets) { |
| if (target_node->parentNode() == this) |
| DispatchChildInsertionEvents(*target_node); |
| } |
| DispatchSubtreeModifiedEvent(); |
| } |
| |
| class ContainerNode::AdoptAndInsertBefore { |
| public: |
| inline void operator()(ContainerNode& container, |
| Node& child, |
| Node* next) const { |
| DCHECK(next); |
| DCHECK_EQ(next->parentNode(), &container); |
| container.GetTreeScope().AdoptIfNeeded(child); |
| container.InsertBeforeCommon(*next, child); |
| } |
| }; |
| |
| class ContainerNode::AdoptAndAppendChild { |
| public: |
| inline void operator()(ContainerNode& container, Node& child, Node*) const { |
| container.GetTreeScope().AdoptIfNeeded(child); |
| container.AppendChildCommon(child); |
| } |
| }; |
| |
| Node* ContainerNode::InsertBefore(Node* new_child, |
| Node* ref_child, |
| ExceptionState& exception_state) { |
| // https://dom.spec.whatwg.org/#concept-node-pre-insert |
| |
| // insertBefore(node, 0) is equivalent to appendChild(node) |
| if (!ref_child) |
| return AppendChild(new_child, exception_state); |
| |
| // NotFoundError: Raised if refChild is not a child of this node |
| if (ref_child->parentNode() != this) { |
| exception_state.ThrowDOMException( |
| kNotFoundError, |
| "The node before which the new node is to " |
| "be inserted is not a child of this " |
| "node."); |
| return nullptr; |
| } |
| |
| // 3. If reference child is node, set it to node’s next sibling. |
| if (ref_child == new_child) { |
| ref_child = new_child->nextSibling(); |
| if (!ref_child) |
| return AppendChild(new_child, exception_state); |
| } |
| |
| // Make sure adding the new child is OK. |
| if (!CheckAcceptChild(new_child, 0, exception_state)) |
| return new_child; |
| DCHECK(new_child); |
| |
| NodeVector targets; |
| if (!CollectChildrenAndRemoveFromOldParentWithCheck( |
| ref_child, nullptr, *new_child, targets, exception_state)) |
| return new_child; |
| |
| ChildListMutationScope mutation(*this); |
| InsertNodeVector(targets, ref_child, AdoptAndInsertBefore()); |
| return new_child; |
| } |
| |
| void ContainerNode::InsertBeforeCommon(Node& next_child, Node& new_child) { |
| #if DCHECK_IS_ON() |
| DCHECK(EventDispatchForbiddenScope::IsEventDispatchForbidden()); |
| #endif |
| DCHECK(ScriptForbiddenScope::IsScriptForbidden()); |
| // Use insertBefore if you need to handle reparenting (and want DOM mutation |
| // events). |
| DCHECK(!new_child.parentNode()); |
| DCHECK(!new_child.nextSibling()); |
| DCHECK(!new_child.previousSibling()); |
| DCHECK(!new_child.IsShadowRoot()); |
| |
| Node* prev = next_child.previousSibling(); |
| DCHECK_NE(last_child_, prev); |
| next_child.SetPreviousSibling(&new_child); |
| if (prev) { |
| DCHECK_NE(FirstChild(), next_child); |
| DCHECK_EQ(prev->nextSibling(), next_child); |
| prev->SetNextSibling(&new_child); |
| } else { |
| DCHECK(FirstChild() == next_child); |
| SetFirstChild(&new_child); |
| } |
| new_child.SetParentOrShadowHostNode(this); |
| new_child.SetPreviousSibling(prev); |
| new_child.SetNextSibling(&next_child); |
| } |
| |
| void ContainerNode::AppendChildCommon(Node& child) { |
| #if DCHECK_IS_ON() |
| DCHECK(EventDispatchForbiddenScope::IsEventDispatchForbidden()); |
| #endif |
| DCHECK(ScriptForbiddenScope::IsScriptForbidden()); |
| |
| child.SetParentOrShadowHostNode(this); |
| if (last_child_) { |
| child.SetPreviousSibling(last_child_); |
| last_child_->SetNextSibling(&child); |
| } else { |
| SetFirstChild(&child); |
| } |
| SetLastChild(&child); |
| } |
| |
| bool ContainerNode::CheckParserAcceptChild(const Node& new_child) const { |
| if (!IsDocumentNode()) |
| return true; |
| // TODO(esprehn): Are there other conditions where the parser can create |
| // invalid trees? |
| return ToDocument(*this).CanAcceptChild(new_child, nullptr, |
| IGNORE_EXCEPTION_FOR_TESTING); |
| } |
| |
| void ContainerNode::ParserInsertBefore(Node* new_child, Node& next_child) { |
| DCHECK(new_child); |
| DCHECK_EQ(next_child.parentNode(), this); |
| DCHECK(!new_child->IsDocumentFragment()); |
| DCHECK(!isHTMLTemplateElement(this)); |
| |
| if (next_child.previousSibling() == new_child || |
| &next_child == new_child) // nothing to do |
| return; |
| |
| if (!CheckParserAcceptChild(*new_child)) |
| return; |
| |
| // FIXME: parserRemoveChild can run script which could then insert the |
| // newChild back into the page. Loop until the child is actually removed. |
| // See: fast/parser/execute-script-during-adoption-agency-removal.html |
| while (ContainerNode* parent = new_child->parentNode()) |
| parent->ParserRemoveChild(*new_child); |
| |
| if (next_child.parentNode() != this) |
| return; |
| |
| if (GetDocument() != new_child->GetDocument()) |
| GetDocument().adoptNode(new_child, ASSERT_NO_EXCEPTION); |
| |
| { |
| EventDispatchForbiddenScope assert_no_event_dispatch; |
| ScriptForbiddenScope forbid_script; |
| |
| AdoptAndInsertBefore()(*this, *new_child, &next_child); |
| DCHECK_EQ(new_child->ConnectedSubframeCount(), 0u); |
| ChildListMutationScope(*this).ChildAdded(*new_child); |
| } |
| |
| NotifyNodeInserted(*new_child, kChildrenChangeSourceParser); |
| } |
| |
| Node* ContainerNode::ReplaceChild(Node* new_child, |
| Node* old_child, |
| ExceptionState& exception_state) { |
| // https://dom.spec.whatwg.org/#concept-node-replace |
| |
| if (!old_child) { |
| exception_state.ThrowDOMException(kNotFoundError, |
| "The node to be replaced is null."); |
| return nullptr; |
| } |
| |
| // Make sure replacing the old child with the new is OK. |
| if (!CheckAcceptChild(new_child, old_child, exception_state)) |
| return old_child; |
| |
| // NotFoundError: Raised if oldChild is not a child of this node. |
| if (old_child->parentNode() != this) { |
| exception_state.ThrowDOMException( |
| kNotFoundError, "The node to be replaced is not a child of this node."); |
| return nullptr; |
| } |
| |
| ChildListMutationScope mutation(*this); |
| // 7. Let reference child be child’s next sibling. |
| Node* next = old_child->nextSibling(); |
| // 8. If reference child is node, set it to node’s next sibling. |
| if (next == new_child) |
| next = new_child->nextSibling(); |
| |
| // TODO(tkent): According to the specification, we should remove |newChild| |
| // from its parent here, and create a separated mutation record for it. |
| // Refer to external/wpt/dom/nodes/MutationObserver-childList.html. |
| |
| // 12. If child’s parent is not null, run these substeps: |
| // 1. Set removedNodes to a list solely containing child. |
| // 2. Remove child from its parent with the suppress observers flag set. |
| RemoveChild(old_child, exception_state); |
| if (exception_state.HadException()) |
| return nullptr; |
| |
| // Does this one more time because removeChild() fires a MutationEvent. |
| if (!CheckAcceptChild(new_child, old_child, exception_state)) |
| return old_child; |
| |
| NodeVector targets; |
| if (!CollectChildrenAndRemoveFromOldParentWithCheck( |
| next, old_child, *new_child, targets, exception_state)) |
| return old_child; |
| |
| if (next) |
| InsertNodeVector(targets, next, AdoptAndInsertBefore()); |
| else |
| InsertNodeVector(targets, nullptr, AdoptAndAppendChild()); |
| return old_child; |
| } |
| |
| void ContainerNode::WillRemoveChild(Node& child) { |
| DCHECK_EQ(child.parentNode(), this); |
| ChildListMutationScope(*this).WillRemoveChild(child); |
| child.NotifyMutationObserversNodeWillDetach(); |
| DispatchChildRemovalEvents(child); |
| ChildFrameDisconnector(child).Disconnect(); |
| if (GetDocument() != child.GetDocument()) { |
| // |child| was moved another document by DOM mutation event handler. |
| return; |
| } |
| |
| // |nodeWillBeRemoved()| must be run after |ChildFrameDisconnector|, because |
| // |ChildFrameDisconnector| can run script which may cause state that is to |
| // be invalidated by removing the node. |
| ScriptForbiddenScope script_forbidden_scope; |
| EventDispatchForbiddenScope assert_no_event_dispatch; |
| // e.g. mutation event listener can create a new range. |
| GetDocument().NodeWillBeRemoved(child); |
| } |
| |
| void ContainerNode::WillRemoveChildren() { |
| NodeVector children; |
| GetChildNodes(*this, children); |
| |
| ChildListMutationScope mutation(*this); |
| for (const auto& node : children) { |
| DCHECK(node); |
| Node& child = *node; |
| mutation.WillRemoveChild(child); |
| child.NotifyMutationObserversNodeWillDetach(); |
| DispatchChildRemovalEvents(child); |
| } |
| |
| ChildFrameDisconnector(*this).Disconnect( |
| ChildFrameDisconnector::kDescendantsOnly); |
| } |
| |
| DEFINE_TRACE(ContainerNode) { |
| visitor->Trace(first_child_); |
| visitor->Trace(last_child_); |
| Node::Trace(visitor); |
| } |
| |
| DEFINE_TRACE_WRAPPERS(ContainerNode) { |
| visitor->TraceWrappersWithManualWriteBarrier(first_child_); |
| visitor->TraceWrappersWithManualWriteBarrier(last_child_); |
| Node::TraceWrappers(visitor); |
| } |
| |
| Node* ContainerNode::RemoveChild(Node* old_child, |
| ExceptionState& exception_state) { |
| // NotFoundError: Raised if oldChild is not a child of this node. |
| // FIXME: We should never really get PseudoElements in here, but editing will |
| // sometimes attempt to remove them still. We should fix that and enable this |
| // DCHECK. DCHECK(!oldChild->isPseudoElement()) |
| if (!old_child || old_child->parentNode() != this || |
| old_child->IsPseudoElement()) { |
| exception_state.ThrowDOMException( |
| kNotFoundError, "The node to be removed is not a child of this node."); |
| return nullptr; |
| } |
| |
| Node* child = old_child; |
| |
| GetDocument().RemoveFocusedElementOfSubtree(child); |
| |
| // Events fired when blurring currently focused node might have moved this |
| // child into a different parent. |
| if (child->parentNode() != this) { |
| exception_state.ThrowDOMException( |
| kNotFoundError, |
| "The node to be removed is no longer a " |
| "child of this node. Perhaps it was moved " |
| "in a 'blur' event handler?"); |
| return nullptr; |
| } |
| |
| WillRemoveChild(*child); |
| |
| // Mutation events might have moved this child into a different parent. |
| if (child->parentNode() != this) { |
| exception_state.ThrowDOMException( |
| kNotFoundError, |
| "The node to be removed is no longer a " |
| "child of this node. Perhaps it was moved " |
| "in response to a mutation?"); |
| return nullptr; |
| } |
| |
| { |
| HTMLFrameOwnerElement::UpdateSuspendScope suspend_widget_hierarchy_updates; |
| DocumentOrderedMap::RemoveScope tree_remove_scope; |
| |
| Node* prev = child->previousSibling(); |
| Node* next = child->nextSibling(); |
| RemoveBetween(prev, next, *child); |
| NotifyNodeRemoved(*child); |
| ChildrenChanged(ChildrenChange::ForRemoval(*child, prev, next, |
| kChildrenChangeSourceAPI)); |
| } |
| DispatchSubtreeModifiedEvent(); |
| return child; |
| } |
| |
| void ContainerNode::RemoveBetween(Node* previous_child, |
| Node* next_child, |
| Node& old_child) { |
| EventDispatchForbiddenScope assert_no_event_dispatch; |
| |
| DCHECK_EQ(old_child.parentNode(), this); |
| |
| AttachContext context; |
| context.clear_invalidation = true; |
| if (!old_child.NeedsAttach()) |
| old_child.DetachLayoutTree(context); |
| |
| if (next_child) |
| next_child->SetPreviousSibling(previous_child); |
| if (previous_child) |
| previous_child->SetNextSibling(next_child); |
| if (first_child_ == &old_child) |
| SetFirstChild(next_child); |
| if (last_child_ == &old_child) |
| SetLastChild(previous_child); |
| |
| old_child.SetPreviousSibling(nullptr); |
| old_child.SetNextSibling(nullptr); |
| old_child.SetParentOrShadowHostNode(nullptr); |
| |
| GetDocument().AdoptIfNeeded(old_child); |
| } |
| |
| void ContainerNode::ParserRemoveChild(Node& old_child) { |
| DCHECK_EQ(old_child.parentNode(), this); |
| DCHECK(!old_child.IsDocumentFragment()); |
| |
| // This may cause arbitrary Javascript execution via onunload handlers. |
| if (old_child.ConnectedSubframeCount()) |
| ChildFrameDisconnector(old_child).Disconnect(); |
| |
| if (old_child.parentNode() != this) |
| return; |
| |
| ChildListMutationScope(*this).WillRemoveChild(old_child); |
| old_child.NotifyMutationObserversNodeWillDetach(); |
| |
| HTMLFrameOwnerElement::UpdateSuspendScope suspend_widget_hierarchy_updates; |
| DocumentOrderedMap::RemoveScope tree_remove_scope; |
| |
| Node* prev = old_child.previousSibling(); |
| Node* next = old_child.nextSibling(); |
| RemoveBetween(prev, next, old_child); |
| |
| NotifyNodeRemoved(old_child); |
| ChildrenChanged(ChildrenChange::ForRemoval(old_child, prev, next, |
| kChildrenChangeSourceParser)); |
| } |
| |
| // This differs from other remove functions because it forcibly removes all the |
| // children, regardless of read-only status or event exceptions, e.g. |
| void ContainerNode::RemoveChildren(SubtreeModificationAction action) { |
| if (!first_child_) |
| return; |
| |
| // Do any prep work needed before actually starting to detach |
| // and remove... e.g. stop loading frames, fire unload events. |
| WillRemoveChildren(); |
| |
| { |
| // Removing focus can cause frames to load, either via events (focusout, |
| // blur) or widget updates (e.g., for <embed>). |
| SubframeLoadingDisabler disabler(*this); |
| |
| // Exclude this node when looking for removed focusedElement since only |
| // children will be removed. |
| // This must be later than willRemoveChildren, which might change focus |
| // state of a child. |
| GetDocument().RemoveFocusedElementOfSubtree(this, true); |
| |
| // Removing a node from a selection can cause widget updates. |
| GetDocument().NodeChildrenWillBeRemoved(*this); |
| } |
| |
| { |
| HTMLFrameOwnerElement::UpdateSuspendScope suspend_widget_hierarchy_updates; |
| DocumentOrderedMap::RemoveScope tree_remove_scope; |
| { |
| EventDispatchForbiddenScope assert_no_event_dispatch; |
| ScriptForbiddenScope forbid_script; |
| |
| while (Node* child = first_child_) { |
| RemoveBetween(0, child->nextSibling(), *child); |
| NotifyNodeRemoved(*child); |
| } |
| } |
| |
| ChildrenChange change = {kAllChildrenRemoved, nullptr, nullptr, nullptr, |
| kChildrenChangeSourceAPI}; |
| ChildrenChanged(change); |
| } |
| |
| if (action == kDispatchSubtreeModifiedEvent) |
| DispatchSubtreeModifiedEvent(); |
| } |
| |
| Node* ContainerNode::AppendChild(Node* new_child, |
| ExceptionState& exception_state) { |
| // Make sure adding the new child is ok |
| if (!CheckAcceptChild(new_child, 0, exception_state)) |
| return new_child; |
| DCHECK(new_child); |
| |
| NodeVector targets; |
| if (!CollectChildrenAndRemoveFromOldParentWithCheck( |
| nullptr, nullptr, *new_child, targets, exception_state)) |
| return new_child; |
| |
| ChildListMutationScope mutation(*this); |
| InsertNodeVector(targets, nullptr, AdoptAndAppendChild()); |
| return new_child; |
| } |
| |
| void ContainerNode::ParserAppendChild(Node* new_child) { |
| DCHECK(new_child); |
| DCHECK(!new_child->IsDocumentFragment()); |
| DCHECK(!isHTMLTemplateElement(this)); |
| |
| if (!CheckParserAcceptChild(*new_child)) |
| return; |
| |
| // FIXME: parserRemoveChild can run script which could then insert the |
| // newChild back into the page. Loop until the child is actually removed. |
| // See: fast/parser/execute-script-during-adoption-agency-removal.html |
| while (ContainerNode* parent = new_child->parentNode()) |
| parent->ParserRemoveChild(*new_child); |
| |
| if (GetDocument() != new_child->GetDocument()) |
| GetDocument().adoptNode(new_child, ASSERT_NO_EXCEPTION); |
| |
| { |
| EventDispatchForbiddenScope assert_no_event_dispatch; |
| ScriptForbiddenScope forbid_script; |
| |
| AdoptAndAppendChild()(*this, *new_child, nullptr); |
| DCHECK_EQ(new_child->ConnectedSubframeCount(), 0u); |
| ChildListMutationScope(*this).ChildAdded(*new_child); |
| } |
| |
| NotifyNodeInserted(*new_child, kChildrenChangeSourceParser); |
| } |
| |
| DISABLE_CFI_PERF |
| void ContainerNode::NotifyNodeInserted(Node& root, |
| ChildrenChangeSource source) { |
| #if DCHECK_IS_ON() |
| DCHECK(!EventDispatchForbiddenScope::IsEventDispatchForbidden()); |
| #endif |
| DCHECK(!root.IsShadowRoot()); |
| |
| if (GetDocument().ContainsV1ShadowTree()) |
| root.CheckSlotChangeAfterInserted(); |
| |
| probe::didInsertDOMNode(&root); |
| |
| NodeVector post_insertion_notification_targets; |
| NotifyNodeInsertedInternal(root, post_insertion_notification_targets); |
| |
| ChildrenChanged(ChildrenChange::ForInsertion(root, root.previousSibling(), |
| root.nextSibling(), source)); |
| |
| for (const auto& target_node : post_insertion_notification_targets) { |
| if (target_node->isConnected()) |
| target_node->DidNotifySubtreeInsertionsToDocument(); |
| } |
| } |
| |
| DISABLE_CFI_PERF |
| void ContainerNode::NotifyNodeInsertedInternal( |
| Node& root, |
| NodeVector& post_insertion_notification_targets) { |
| EventDispatchForbiddenScope assert_no_event_dispatch; |
| ScriptForbiddenScope forbid_script; |
| |
| for (Node& node : NodeTraversal::InclusiveDescendantsOf(root)) { |
| // As an optimization we don't notify leaf nodes when when inserting |
| // into detached subtrees that are not in a shadow tree. |
| if (!isConnected() && !IsInShadowTree() && !node.IsContainerNode()) |
| continue; |
| if (Node::kInsertionShouldCallDidNotifySubtreeInsertions == |
| node.InsertedInto(this)) |
| post_insertion_notification_targets.push_back(&node); |
| for (ShadowRoot* shadow_root = node.YoungestShadowRoot(); shadow_root; |
| shadow_root = shadow_root->OlderShadowRoot()) |
| NotifyNodeInsertedInternal(*shadow_root, |
| post_insertion_notification_targets); |
| } |
| } |
| |
| void ContainerNode::NotifyNodeRemoved(Node& root) { |
| ScriptForbiddenScope forbid_script; |
| EventDispatchForbiddenScope assert_no_event_dispatch; |
| |
| for (Node& node : NodeTraversal::InclusiveDescendantsOf(root)) { |
| // As an optimization we skip notifying Text nodes and other leaf nodes |
| // of removal when they're not in the Document tree and not in a shadow root |
| // since the virtual call to removedFrom is not needed. |
| if (!node.IsContainerNode() && !node.IsInTreeScope()) |
| continue; |
| node.RemovedFrom(this); |
| for (ShadowRoot* shadow_root = node.YoungestShadowRoot(); shadow_root; |
| shadow_root = shadow_root->OlderShadowRoot()) |
| NotifyNodeRemoved(*shadow_root); |
| } |
| } |
| |
| DISABLE_CFI_PERF |
| void ContainerNode::AttachLayoutTree(const AttachContext& context) { |
| AttachContext children_context(context); |
| children_context.resolved_style = nullptr; |
| |
| for (Node* child = FirstChild(); child; child = child->nextSibling()) { |
| #if DCHECK_IS_ON() |
| DCHECK(child->NeedsAttach() || |
| ChildAttachedAllowedWhenAttachingChildren(this)); |
| #endif |
| if (child->NeedsAttach()) |
| child->AttachLayoutTree(children_context); |
| } |
| |
| ClearChildNeedsStyleRecalc(); |
| ClearChildNeedsReattachLayoutTree(); |
| Node::AttachLayoutTree(context); |
| } |
| |
| void ContainerNode::DetachLayoutTree(const AttachContext& context) { |
| AttachContext children_context(context); |
| children_context.resolved_style = nullptr; |
| children_context.clear_invalidation = true; |
| |
| for (Node* child = FirstChild(); child; child = child->nextSibling()) |
| child->DetachLayoutTree(children_context); |
| |
| SetChildNeedsStyleRecalc(); |
| Node::DetachLayoutTree(context); |
| } |
| |
| void ContainerNode::ChildrenChanged(const ChildrenChange& change) { |
| GetDocument().IncDOMTreeVersion(); |
| GetDocument().NotifyChangeChildren(*this); |
| InvalidateNodeListCachesInAncestors(); |
| if (change.IsChildInsertion()) { |
| if (!ChildNeedsStyleRecalc()) { |
| SetChildNeedsStyleRecalc(); |
| MarkAncestorsWithChildNeedsStyleRecalc(); |
| } |
| } |
| } |
| |
| void ContainerNode::CloneChildNodes(ContainerNode* clone) { |
| DummyExceptionStateForTesting exception_state; |
| for (Node* n = FirstChild(); n && !exception_state.HadException(); |
| n = n->nextSibling()) |
| clone->AppendChild(n->cloneNode(true), exception_state); |
| } |
| |
| bool ContainerNode::GetUpperLeftCorner(FloatPoint& point) const { |
| if (!GetLayoutObject()) |
| return false; |
| |
| // FIXME: What is this code really trying to do? |
| LayoutObject* o = GetLayoutObject(); |
| if (!o->IsInline() || o->IsAtomicInlineLevel()) { |
| point = o->LocalToAbsolute(FloatPoint(), kUseTransforms); |
| return true; |
| } |
| |
| // Find the next text/image child, to get a position. |
| while (o) { |
| LayoutObject* p = o; |
| if (LayoutObject* o_first_child = o->SlowFirstChild()) { |
| o = o_first_child; |
| } else if (o->NextSibling()) { |
| o = o->NextSibling(); |
| } else { |
| LayoutObject* next = nullptr; |
| while (!next && o->Parent()) { |
| o = o->Parent(); |
| next = o->NextSibling(); |
| } |
| o = next; |
| |
| if (!o) |
| break; |
| } |
| DCHECK(o); |
| |
| if (!o->IsInline() || o->IsAtomicInlineLevel()) { |
| point = o->LocalToAbsolute(FloatPoint(), kUseTransforms); |
| return true; |
| } |
| |
| if (p->GetNode() && p->GetNode() == this && o->IsText() && !o->IsBR() && |
| !ToLayoutText(o)->HasTextBoxes()) { |
| // Do nothing - skip unrendered whitespace that is a child or next sibling |
| // of the anchor. |
| // FIXME: This fails to skip a whitespace sibling when there was also a |
| // whitespace child (because p has moved). |
| } else if ((o->IsText() && !o->IsBR()) || o->IsAtomicInlineLevel()) { |
| point = FloatPoint(); |
| if (o->IsText()) { |
| if (ToLayoutText(o)->FirstTextBox()) |
| point.Move( |
| ToLayoutText(o)->LinesBoundingBox().X(), |
| ToLayoutText(o)->FirstTextBox()->Root().LineTop().ToFloat()); |
| point = o->LocalToAbsolute(point, kUseTransforms); |
| } else { |
| DCHECK(o->IsBox()); |
| LayoutBox* box = ToLayoutBox(o); |
| point.MoveBy(box->Location()); |
| point = o->Container()->LocalToAbsolute(point, kUseTransforms); |
| } |
| 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 && GetDocument().View()) { |
| point = FloatPoint(0, GetDocument().View()->ContentsHeight()); |
| return true; |
| } |
| return false; |
| } |
| |
| static inline LayoutObject* EndOfContinuations(LayoutObject* layout_object) { |
| LayoutObject* prev = nullptr; |
| LayoutObject* cur = layout_object; |
| |
| if (!cur->IsLayoutInline() && !cur->IsLayoutBlockFlow()) |
| return nullptr; |
| |
| while (cur) { |
| prev = cur; |
| if (cur->IsLayoutInline()) |
| cur = ToLayoutInline(cur)->Continuation(); |
| else |
| cur = ToLayoutBlockFlow(cur)->Continuation(); |
| } |
| |
| return prev; |
| } |
| |
| bool ContainerNode::GetLowerRightCorner(FloatPoint& point) const { |
| if (!GetLayoutObject()) |
| return false; |
| |
| LayoutObject* o = GetLayoutObject(); |
| if (!o->IsInline() || o->IsAtomicInlineLevel()) { |
| LayoutBox* box = ToLayoutBox(o); |
| point = o->LocalToAbsolute(FloatPoint(box->Size()), kUseTransforms); |
| return true; |
| } |
| |
| LayoutObject* start_continuation = nullptr; |
| // Find the last text/image child, to get a position. |
| while (o) { |
| if (LayoutObject* o_last_child = o->SlowLastChild()) { |
| o = o_last_child; |
| } else if (o != GetLayoutObject() && o->PreviousSibling()) { |
| o = o->PreviousSibling(); |
| } else { |
| LayoutObject* prev = nullptr; |
| while (!prev) { |
| // Check if the current layoutObject has contiunation and move the |
| // location for finding the layoutObject to the end of continuations if |
| // there is the continuation. Skip to check the contiunation on |
| // contiunations section |
| if (start_continuation == o) { |
| start_continuation = nullptr; |
| } else if (!start_continuation) { |
| if (LayoutObject* continuation = EndOfContinuations(o)) { |
| start_continuation = o; |
| prev = continuation; |
| break; |
| } |
| } |
| // Prevent to overrun out of own layout tree |
| if (o == GetLayoutObject()) { |
| return false; |
| } |
| o = o->Parent(); |
| if (!o) |
| return false; |
| prev = o->PreviousSibling(); |
| } |
| o = prev; |
| } |
| DCHECK(o); |
| if (o->IsText() || o->IsAtomicInlineLevel()) { |
| point = FloatPoint(); |
| if (o->IsText()) { |
| LayoutText* text = ToLayoutText(o); |
| IntRect lines_box = EnclosingIntRect(text->LinesBoundingBox()); |
| if (!lines_box.MaxX() && !lines_box.MaxY()) |
| continue; |
| point.MoveBy(lines_box.MaxXMaxYCorner()); |
| point = o->LocalToAbsolute(point, kUseTransforms); |
| } else { |
| LayoutBox* box = ToLayoutBox(o); |
| point.MoveBy(box->FrameRect().MaxXMaxYCorner()); |
| point = o->Container()->LocalToAbsolute(point, kUseTransforms); |
| } |
| return true; |
| } |
| } |
| return true; |
| } |
| |
| // FIXME: This override is only needed for inline anchors without an |
| // InlineBox and it does not belong in ContainerNode as it reaches into |
| // the layout and line box trees. |
| // https://code.google.com/p/chromium/issues/detail?id=248354 |
| LayoutRect ContainerNode::BoundingBox() const { |
| FloatPoint upper_left, lower_right; |
| bool found_upper_left = GetUpperLeftCorner(upper_left); |
| bool found_lower_right = GetLowerRightCorner(lower_right); |
| |
| // 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 (found_upper_left != found_lower_right) { |
| if (found_upper_left) |
| lower_right = upper_left; |
| else |
| upper_left = lower_right; |
| } |
| |
| FloatSize size = lower_right.ExpandedTo(upper_left) - upper_left; |
| if (std::isnan(size.Width()) || std::isnan(size.Height())) |
| return LayoutRect(); |
| |
| return EnclosingLayoutRect(FloatRect(upper_left, size)); |
| } |
| |
| // This is used by FrameSelection to denote when the active-state of the page |
| // has changed independent of the focused element changing. |
| void ContainerNode::FocusStateChanged() { |
| // If we're just changing the window's active state and the focused node has |
| // no layoutObject we can just ignore the state change. |
| if (!GetLayoutObject()) |
| return; |
| |
| if (GetComputedStyle()->AffectedByFocus()) { |
| StyleChangeType change_type = |
| GetComputedStyle()->HasPseudoStyle(kPseudoIdFirstLetter) |
| ? kSubtreeStyleChange |
| : kLocalStyleChange; |
| SetNeedsStyleRecalc( |
| change_type, |
| StyleChangeReasonForTracing::CreateWithExtraData( |
| StyleChangeReason::kPseudoClass, StyleChangeExtraData::g_focus)); |
| } |
| if (IsElementNode() && ToElement(this)->ChildrenOrSiblingsAffectedByFocus()) |
| ToElement(this)->PseudoStateChanged(CSSSelector::kPseudoFocus); |
| |
| LayoutTheme::GetTheme().ControlStateChanged(*GetLayoutObject(), |
| kFocusControlState); |
| FocusWithinStateChanged(); |
| } |
| |
| void ContainerNode::FocusWithinStateChanged() { |
| if (GetComputedStyle() && GetComputedStyle()->AffectedByFocusWithin()) { |
| StyleChangeType change_type = |
| GetComputedStyle()->HasPseudoStyle(kPseudoIdFirstLetter) |
| ? kSubtreeStyleChange |
| : kLocalStyleChange; |
| SetNeedsStyleRecalc(change_type, |
| StyleChangeReasonForTracing::CreateWithExtraData( |
| StyleChangeReason::kPseudoClass, |
| StyleChangeExtraData::g_focus_within)); |
| } |
| if (IsElementNode() && |
| ToElement(this)->ChildrenOrSiblingsAffectedByFocusWithin()) |
| ToElement(this)->PseudoStateChanged(CSSSelector::kPseudoFocusWithin); |
| } |
| |
| void ContainerNode::SetFocused(bool received, WebFocusType focus_type) { |
| // Recurse up author shadow trees to mark shadow hosts if it matches :focus. |
| // TODO(kochi): Handle UA shadows which marks multiple nodes as focused such |
| // as <input type="date"> the same way as author shadow. |
| if (ShadowRoot* root = ContainingShadowRoot()) { |
| if (root->GetType() != ShadowRootType::kUserAgent) |
| OwnerShadowHost()->SetFocused(received, focus_type); |
| } |
| |
| // If this is an author shadow host and indirectly focused (has focused |
| // element within its shadow root), update focus. |
| if (IsElementNode() && GetDocument().FocusedElement() && |
| GetDocument().FocusedElement() != this) { |
| if (ToElement(this)->AuthorShadowRoot()) |
| received = |
| received && ToElement(this)->AuthorShadowRoot()->delegatesFocus(); |
| } |
| |
| if (IsFocused() == received) |
| return; |
| |
| Node::SetFocused(received, focus_type); |
| |
| FocusStateChanged(); |
| |
| UpdateDistribution(); |
| for (ContainerNode* node = this; node; |
| node = FlatTreeTraversal::Parent(*node)) { |
| node->SetHasFocusWithin(received); |
| node->FocusWithinStateChanged(); |
| } |
| |
| if (GetLayoutObject() || received) |
| return; |
| |
| // If :focus sets display: none, we lose focus but still need to recalc our |
| // style. |
| if (IsElementNode() && ToElement(this)->ChildrenOrSiblingsAffectedByFocus()) |
| ToElement(this)->PseudoStateChanged(CSSSelector::kPseudoFocus); |
| else |
| SetNeedsStyleRecalc( |
| kLocalStyleChange, |
| StyleChangeReasonForTracing::CreateWithExtraData( |
| StyleChangeReason::kPseudoClass, StyleChangeExtraData::g_focus)); |
| |
| if (IsElementNode() && |
| ToElement(this)->ChildrenOrSiblingsAffectedByFocusWithin()) { |
| ToElement(this)->PseudoStateChanged(CSSSelector::kPseudoFocusWithin); |
| } else { |
| SetNeedsStyleRecalc(kLocalStyleChange, |
| StyleChangeReasonForTracing::CreateWithExtraData( |
| StyleChangeReason::kPseudoClass, |
| StyleChangeExtraData::g_focus_within)); |
| } |
| } |
| |
| void ContainerNode::SetActive(bool down) { |
| if (down == IsActive()) |
| return; |
| |
| Node::SetActive(down); |
| |
| if (!GetLayoutObject()) { |
| if (IsElementNode() && |
| ToElement(this)->ChildrenOrSiblingsAffectedByActive()) |
| ToElement(this)->PseudoStateChanged(CSSSelector::kPseudoActive); |
| else |
| SetNeedsStyleRecalc( |
| kLocalStyleChange, |
| StyleChangeReasonForTracing::CreateWithExtraData( |
| StyleChangeReason::kPseudoClass, StyleChangeExtraData::g_active)); |
| return; |
| } |
| |
| if (GetComputedStyle()->AffectedByActive()) { |
| StyleChangeType change_type = |
| GetComputedStyle()->HasPseudoStyle(kPseudoIdFirstLetter) |
| ? kSubtreeStyleChange |
| : kLocalStyleChange; |
| SetNeedsStyleRecalc( |
| change_type, |
| StyleChangeReasonForTracing::CreateWithExtraData( |
| StyleChangeReason::kPseudoClass, StyleChangeExtraData::g_active)); |
| } |
| if (IsElementNode() && ToElement(this)->ChildrenOrSiblingsAffectedByActive()) |
| ToElement(this)->PseudoStateChanged(CSSSelector::kPseudoActive); |
| |
| LayoutTheme::GetTheme().ControlStateChanged(*GetLayoutObject(), |
| kPressedControlState); |
| } |
| |
| void ContainerNode::SetDragged(bool new_value) { |
| if (new_value == IsDragged()) |
| return; |
| |
| Node::SetDragged(new_value); |
| |
| // If :-webkit-drag sets display: none we lose our dragging but still need |
| // to recalc our style. |
| if (!GetLayoutObject()) { |
| if (new_value) |
| return; |
| if (IsElementNode() && ToElement(this)->ChildrenOrSiblingsAffectedByDrag()) |
| ToElement(this)->PseudoStateChanged(CSSSelector::kPseudoDrag); |
| else |
| SetNeedsStyleRecalc( |
| kLocalStyleChange, |
| StyleChangeReasonForTracing::CreateWithExtraData( |
| StyleChangeReason::kPseudoClass, StyleChangeExtraData::g_drag)); |
| return; |
| } |
| |
| if (GetComputedStyle()->AffectedByDrag()) { |
| StyleChangeType change_type = |
| GetComputedStyle()->HasPseudoStyle(kPseudoIdFirstLetter) |
| ? kSubtreeStyleChange |
| : kLocalStyleChange; |
| SetNeedsStyleRecalc( |
| change_type, |
| StyleChangeReasonForTracing::CreateWithExtraData( |
| StyleChangeReason::kPseudoClass, StyleChangeExtraData::g_drag)); |
| } |
| if (IsElementNode() && ToElement(this)->ChildrenOrSiblingsAffectedByDrag()) |
| ToElement(this)->PseudoStateChanged(CSSSelector::kPseudoDrag); |
| } |
| |
| void ContainerNode::SetHovered(bool over) { |
| if (over == IsHovered()) |
| return; |
| |
| Node::SetHovered(over); |
| |
| const ComputedStyle* style = GetComputedStyle(); |
| if (!style || style->AffectedByHover()) { |
| StyleChangeType change_type = kLocalStyleChange; |
| if (style && style->HasPseudoStyle(kPseudoIdFirstLetter)) |
| change_type = kSubtreeStyleChange; |
| SetNeedsStyleRecalc( |
| change_type, |
| StyleChangeReasonForTracing::CreateWithExtraData( |
| StyleChangeReason::kPseudoClass, StyleChangeExtraData::g_hover)); |
| } |
| if (IsElementNode() && ToElement(this)->ChildrenOrSiblingsAffectedByHover()) |
| ToElement(this)->PseudoStateChanged(CSSSelector::kPseudoHover); |
| |
| if (GetLayoutObject()) { |
| LayoutTheme::GetTheme().ControlStateChanged(*GetLayoutObject(), |
| kHoverControlState); |
| } |
| } |
| |
| HTMLCollection* ContainerNode::Children() { |
| return EnsureCachedCollection<HTMLCollection>(kNodeChildren); |
| } |
| |
| unsigned ContainerNode::CountChildren() const { |
| unsigned count = 0; |
| for (Node* node = FirstChild(); node; node = node->nextSibling()) |
| count++; |
| return count; |
| } |
| |
| Element* ContainerNode::QuerySelector(const AtomicString& selectors, |
| ExceptionState& exception_state) { |
| SelectorQuery* selector_query = GetDocument().GetSelectorQueryCache().Add( |
| selectors, GetDocument(), exception_state); |
| if (!selector_query) |
| return nullptr; |
| return selector_query->QueryFirst(*this); |
| } |
| |
| StaticElementList* ContainerNode::QuerySelectorAll( |
| const AtomicString& selectors, |
| ExceptionState& exception_state) { |
| SelectorQuery* selector_query = GetDocument().GetSelectorQueryCache().Add( |
| selectors, GetDocument(), exception_state); |
| if (!selector_query) |
| return nullptr; |
| return selector_query->QueryAll(*this); |
| } |
| |
| static void DispatchChildInsertionEvents(Node& child) { |
| if (child.IsInShadowTree()) |
| return; |
| |
| #if DCHECK_IS_ON() |
| DCHECK(!EventDispatchForbiddenScope::IsEventDispatchForbidden()); |
| #endif |
| |
| Node* c = &child; |
| Document* document = &child.GetDocument(); |
| |
| if (c->parentNode() && |
| document->HasListenerType(Document::DOMNODEINSERTED_LISTENER)) |
| c->DispatchScopedEvent(MutationEvent::Create( |
| EventTypeNames::DOMNodeInserted, true, c->parentNode())); |
| |
| // dispatch the DOMNodeInsertedIntoDocument event to all descendants |
| if (c->isConnected() && document->HasListenerType( |
| Document::DOMNODEINSERTEDINTODOCUMENT_LISTENER)) { |
| for (; c; c = NodeTraversal::Next(*c, &child)) |
| c->DispatchScopedEvent(MutationEvent::Create( |
| EventTypeNames::DOMNodeInsertedIntoDocument, false)); |
| } |
| } |
| |
| static void DispatchChildRemovalEvents(Node& child) { |
| if (child.IsInShadowTree()) { |
| probe::willRemoveDOMNode(&child); |
| return; |
| } |
| |
| #if DCHECK_IS_ON() |
| DCHECK(!EventDispatchForbiddenScope::IsEventDispatchForbidden()); |
| #endif |
| |
| probe::willRemoveDOMNode(&child); |
| |
| Node* c = &child; |
| Document* document = &child.GetDocument(); |
| |
| // Dispatch pre-removal mutation events. |
| if (c->parentNode() && |
| document->HasListenerType(Document::DOMNODEREMOVED_LISTENER)) { |
| NodeChildRemovalTracker scope(child); |
| c->DispatchScopedEvent(MutationEvent::Create(EventTypeNames::DOMNodeRemoved, |
| true, c->parentNode())); |
| } |
| |
| // Dispatch the DOMNodeRemovedFromDocument event to all descendants. |
| if (c->isConnected() && document->HasListenerType( |
| Document::DOMNODEREMOVEDFROMDOCUMENT_LISTENER)) { |
| NodeChildRemovalTracker scope(child); |
| for (; c; c = NodeTraversal::Next(*c, &child)) |
| c->DispatchScopedEvent(MutationEvent::Create( |
| EventTypeNames::DOMNodeRemovedFromDocument, false)); |
| } |
| } |
| |
| bool ContainerNode::HasRestyleFlagInternal(DynamicRestyleFlags mask) const { |
| return RareData()->HasRestyleFlag(mask); |
| } |
| |
| bool ContainerNode::HasRestyleFlagsInternal() const { |
| return RareData()->HasRestyleFlags(); |
| } |
| |
| void ContainerNode::SetRestyleFlag(DynamicRestyleFlags mask) { |
| DCHECK(IsElementNode() || IsShadowRoot()); |
| EnsureRareData().SetRestyleFlag(mask); |
| } |
| |
| void ContainerNode::RecalcDescendantStyles(StyleRecalcChange change) { |
| DCHECK(GetDocument().InStyleRecalc()); |
| DCHECK(change >= kUpdatePseudoElements || ChildNeedsStyleRecalc()); |
| DCHECK(!NeedsStyleRecalc()); |
| |
| StyleResolver& style_resolver = GetDocument().EnsureStyleResolver(); |
| for (Node* child = LastChild(); child; child = child->previousSibling()) { |
| if (child->IsTextNode()) { |
| ToText(child)->RecalcTextStyle(change); |
| } else if (child->IsElementNode()) { |
| Element* element = ToElement(child); |
| if (element->ShouldCallRecalcStyle(change)) |
| element->RecalcStyle(change); |
| else if (element->SupportsStyleSharing()) |
| style_resolver.AddToStyleSharingList(*element); |
| } |
| } |
| } |
| |
| void ContainerNode::RebuildChildrenLayoutTrees(Text*& next_text_sibling) { |
| DCHECK(!NeedsReattachLayoutTree()); |
| |
| // This loop is deliberately backwards because we use insertBefore in the |
| // layout tree, and want to avoid a potentially n^2 loop to find the insertion |
| // point while building the layout tree. Having us start from the last child |
| // and work our way back means in the common case, we'll find the insertion |
| // point in O(1) time. See crbug.com/288225 |
| for (Node* child = LastChild(); child; child = child->previousSibling()) { |
| bool rebuild_child = child->NeedsReattachLayoutTree() || |
| child->ChildNeedsReattachLayoutTree(); |
| if (child->IsTextNode()) { |
| Text* text_node = ToText(child); |
| if (rebuild_child) |
| text_node->RebuildTextLayoutTree(next_text_sibling); |
| next_text_sibling = text_node; |
| } else if (child->IsElementNode()) { |
| Element* element = ToElement(child); |
| if (rebuild_child) |
| element->RebuildLayoutTree(next_text_sibling); |
| if (element->GetLayoutObject()) |
| next_text_sibling = nullptr; |
| } |
| } |
| // This is done in ContainerNode::attachLayoutTree but will never be cleared |
| // if we don't enter ContainerNode::attachLayoutTree so we do it here. |
| ClearChildNeedsStyleRecalc(); |
| ClearChildNeedsReattachLayoutTree(); |
| } |
| |
| void ContainerNode::CheckForSiblingStyleChanges(SiblingCheckType change_type, |
| Element* changed_element, |
| Node* node_before_change, |
| Node* node_after_change) { |
| if (!InActiveDocument() || GetDocument().HasPendingForcedStyleRecalc() || |
| GetStyleChangeType() >= kSubtreeStyleChange) |
| return; |
| |
| if (!HasRestyleFlag(kChildrenAffectedByStructuralRules)) |
| return; |
| |
| Element* element_after_change = |
| !node_after_change || node_after_change->IsElementNode() |
| ? ToElement(node_after_change) |
| : ElementTraversal::NextSibling(*node_after_change); |
| Element* element_before_change = |
| !node_before_change || node_before_change->IsElementNode() |
| ? ToElement(node_before_change) |
| : ElementTraversal::PreviousSibling(*node_before_change); |
| |
| // TODO(rune@opera.com): move this code into StyleEngine and collect the |
| // various invalidation sets into a single InvalidationLists object and |
| // schedule with a single scheduleInvalidationSetsForNode for efficiency. |
| |
| // Forward positional selectors include :nth-child, :nth-of-type, |
| // :first-of-type, and only-of-type. Backward positional selectors include |
| // :nth-last-child, :nth-last-of-type, :last-of-type, and :only-of-type. |
| if ((ChildrenAffectedByForwardPositionalRules() && element_after_change) || |
| (ChildrenAffectedByBackwardPositionalRules() && element_before_change)) { |
| GetDocument().GetStyleEngine().ScheduleNthPseudoInvalidations(*this); |
| } |
| |
| if (ChildrenAffectedByFirstChildRules() && !element_before_change && |
| element_after_change && |
| element_after_change->AffectedByFirstChildRules()) { |
| DCHECK_NE(change_type, kFinishedParsingChildren); |
| element_after_change->PseudoStateChanged(CSSSelector::kPseudoFirstChild); |
| element_after_change->PseudoStateChanged(CSSSelector::kPseudoOnlyChild); |
| } |
| |
| if (ChildrenAffectedByLastChildRules() && !element_after_change && |
| element_before_change && |
| element_before_change->AffectedByLastChildRules()) { |
| element_before_change->PseudoStateChanged(CSSSelector::kPseudoLastChild); |
| element_before_change->PseudoStateChanged(CSSSelector::kPseudoOnlyChild); |
| } |
| |
| // For ~ and + combinators, succeeding siblings may need style invalidation |
| // after an element is inserted or removed. |
| |
| if (!element_after_change) |
| return; |
| |
| if (!ChildrenAffectedByIndirectAdjacentRules() && |
| !ChildrenAffectedByDirectAdjacentRules()) |
| return; |
| |
| if (change_type == kSiblingElementInserted) { |
| GetDocument().GetStyleEngine().ScheduleInvalidationsForInsertedSibling( |
| element_before_change, *changed_element); |
| return; |
| } |
| |
| DCHECK(change_type == kSiblingElementRemoved); |
| GetDocument().GetStyleEngine().ScheduleInvalidationsForRemovedSibling( |
| element_before_change, *changed_element, *element_after_change); |
| } |
| |
| void ContainerNode::InvalidateNodeListCachesInAncestors( |
| const QualifiedName* attr_name, |
| Element* attribute_owner_element) { |
| if (HasRareData() && (!attr_name || IsAttributeNode())) { |
| if (NodeListsNodeData* lists = RareData()->NodeLists()) { |
| if (ChildNodeList* child_node_list = lists->GetChildNodeList(*this)) |
| child_node_list->InvalidateCache(); |
| } |
| } |
| |
| // Modifications to attributes that are not associated with an Element can't |
| // invalidate NodeList caches. |
| if (attr_name && !attribute_owner_element) |
| return; |
| |
| if (!GetDocument().ShouldInvalidateNodeListCaches(attr_name)) |
| return; |
| |
| GetDocument().InvalidateNodeListCaches(attr_name); |
| |
| for (ContainerNode* node = this; node; node = node->parentNode()) { |
| if (NodeListsNodeData* lists = node->NodeLists()) |
| lists->InvalidateCaches(attr_name); |
| } |
| } |
| |
| TagCollection* ContainerNode::getElementsByTagName( |
| const AtomicString& local_name) { |
| if (GetDocument().IsHTMLDocument()) |
| return EnsureCachedCollection<HTMLTagCollection>(kHTMLTagCollectionType, |
| local_name); |
| return EnsureCachedCollection<TagCollection>(kTagCollectionType, local_name); |
| } |
| |
| TagCollection* ContainerNode::getElementsByTagNameNS( |
| const AtomicString& namespace_uri, |
| const AtomicString& local_name) { |
| if (namespace_uri == g_star_atom) |
| return getElementsByTagName(local_name); |
| |
| return EnsureCachedCollection<TagCollection>( |
| kTagCollectionType, namespace_uri.IsEmpty() ? g_null_atom : namespace_uri, |
| local_name); |
| } |
| |
| // Takes an AtomicString in argument because it is common for elements to share |
| // the same name attribute. Therefore, the NameNodeList factory function |
| // expects an AtomicString type. |
| NameNodeList* ContainerNode::getElementsByName( |
| const AtomicString& element_name) { |
| return EnsureCachedCollection<NameNodeList>(kNameNodeListType, element_name); |
| } |
| |
| // Takes an AtomicString in argument because it is common for elements to share |
| // the same set of class names. Therefore, the ClassNodeList factory function |
| // expects an AtomicString type. |
| ClassCollection* ContainerNode::getElementsByClassName( |
| const AtomicString& class_names) { |
| return EnsureCachedCollection<ClassCollection>(kClassCollectionType, |
| class_names); |
| } |
| |
| RadioNodeList* ContainerNode::GetRadioNodeList(const AtomicString& name, |
| bool only_match_img_elements) { |
| DCHECK(isHTMLFormElement(this) || isHTMLFieldSetElement(this)); |
| CollectionType type = |
| only_match_img_elements ? kRadioImgNodeListType : kRadioNodeListType; |
| return EnsureCachedCollection<RadioNodeList>(type, name); |
| } |
| |
| Element* ContainerNode::getElementById(const AtomicString& id) const { |
| if (IsInTreeScope()) { |
| // Fast path if we are in a tree scope: call getElementById() on tree scope |
| // and check if the matching element is in our subtree. |
| Element* element = ContainingTreeScope().GetElementById(id); |
| if (!element) |
| return nullptr; |
| if (element->IsDescendantOf(this)) |
| return element; |
| } |
| |
| // Fall back to traversing our subtree. In case of duplicate ids, the first |
| // element found will be returned. |
| for (Element& element : ElementTraversal::DescendantsOf(*this)) { |
| if (element.GetIdAttribute() == id) |
| return &element; |
| } |
| return nullptr; |
| } |
| |
| NodeListsNodeData& ContainerNode::EnsureNodeLists() { |
| return EnsureRareData().EnsureNodeLists(); |
| } |
| |
| #if DCHECK_IS_ON() |
| bool ChildAttachedAllowedWhenAttachingChildren(ContainerNode* node) { |
| if (node->IsShadowRoot()) |
| return true; |
| |
| if (node->IsInsertionPoint()) |
| return true; |
| |
| if (isHTMLSlotElement(node)) |
| return true; |
| |
| if (node->IsElementNode() && ToElement(node)->Shadow()) |
| return true; |
| |
| return false; |
| } |
| #endif |
| |
| } // namespace blink |