blob: e6a8ee2d238b81cd774fb6bbd9ffcda15ef09bf9 [file] [log] [blame]
/*
* 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/css/SelectorQuery.h"
#include "core/css/StyleChangeReason.h"
#include "core/css/StyleEngine.h"
#include "core/dom/ChildFrameDisconnector.h"
#include "core/dom/ChildListMutationScope.h"
#include "core/dom/ClassCollection.h"
#include "core/dom/ElementShadow.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/ShadowRoot.h"
#include "core/dom/StaticNodeList.h"
#include "core/dom/WhitespaceAttacher.h"
#include "core/events/MutationEvent.h"
#include "core/frame/LocalFrameView.h"
#include "core/html/HTMLCollection.h"
#include "core/html/HTMLFrameOwnerElement.h"
#include "core/html/HTMLTagCollection.h"
#include "core/html/forms/RadioNodeList.h"
#include "core/layout/LayoutBlockFlow.h"
#include "core/layout/LayoutInline.h"
#include "core/layout/LayoutText.h"
#include "core/layout/line/InlineTextBox.h"
#include "core/layout/line/RootInlineBox.h"
#include "core/probe/CoreProbes.h"
#include "platform/EventDispatchForbiddenScope.h"
#include "platform/bindings/RuntimeCallStats.h"
#include "platform/bindings/ScriptForbiddenScope.h"
#include "platform/bindings/V8PerIsolateData.h"
namespace blink {
using namespace HTMLNames;
static void DispatchChildInsertionEvents(Node&);
static void DispatchChildRemovalEvents(Node&);
namespace {
// This class is helpful to detect necessity of
// RecheckNodeInsertionStructuralPrereq() after removeChild*() inside
// InsertBefore(), AppendChild(), and ReplaceChild().
//
// After removeChild*(), we can detect necessity of
// RecheckNodeInsertionStructuralPrereq() by
// - DOM tree version of |node_document_| was increased by at most one.
// - If |node| and |parent| are in different documents, Document for
// |parent| must not be changed.
class DOMTreeMutationDetector {
STACK_ALLOCATED();
public:
DOMTreeMutationDetector(const Node& node, const Node& parent)
: node_document_(node.GetDocument()),
parent_document_(parent.GetDocument()),
parent_(parent),
original_node_document_version_(node_document_->DomTreeVersion()),
original_parent_document_version_(parent_document_->DomTreeVersion()) {}
bool HadAtMostOneDOMMutation() {
if (node_document_->DomTreeVersion() > original_node_document_version_ + 1)
return false;
if (parent_document_ != parent_->GetDocument())
return false;
if (node_document_ == parent_document_)
return true;
return parent_document_->DomTreeVersion() ==
original_parent_document_version_;
}
private:
const Member<Document> node_document_;
const Member<Document> parent_document_;
const Member<const Node> parent_;
const uint64_t original_node_document_version_;
const uint64_t original_parent_document_version_;
};
inline bool CheckReferenceChildParent(const Node& parent,
const Node* next,
const Node* old_child,
ExceptionState& exception_state) {
if (next && next->parentNode() != &parent) {
exception_state.ThrowDOMException(kNotFoundError,
"The node before which the new node is "
"to be inserted is not a child of this "
"node.");
return false;
}
if (old_child && old_child->parentNode() != &parent) {
exception_state.ThrowDOMException(
kNotFoundError, "The node to be replaced is not a child of this node.");
return false;
}
return true;
}
} // namespace
// This dispatches various events; DOM mutation events, blur events, IFRAME
// unload events, etc.
// Returns true if DOM mutation should be proceeded.
static inline bool CollectChildrenAndRemoveFromOldParent(
Node& node,
NodeVector& nodes,
ExceptionState& exception_state) {
if (node.IsDocumentFragment()) {
DocumentFragment& fragment = ToDocumentFragment(node);
GetChildNodes(fragment, nodes);
fragment.RemoveChildren();
return !nodes.IsEmpty();
}
nodes.push_back(&node);
if (ContainerNode* old_parent = node.parentNode())
old_parent->RemoveChild(&node, exception_state);
return !exception_state.HadException() && !nodes.IsEmpty();
}
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;
}
// Returns true if |new_child| contains this node. In that case,
// |exception_state| has an exception.
// https://dom.spec.whatwg.org/#concept-tree-host-including-inclusive-ancestor
bool ContainerNode::IsHostIncludingInclusiveAncestorOfThis(
const Node& new_child,
ExceptionState& exception_state) const {
// Non-ContainerNode can contain nothing.
if (!new_child.IsContainerNode())
return false;
bool child_contains_parent = false;
if (IsInShadowTree() || GetDocument().IsTemplateDocument()) {
child_contains_parent = new_child.ContainsIncludingHostElements(*this);
} else {
const Node& root = TreeRoot();
if (root.IsDocumentFragment() &&
ToDocumentFragment(root).IsTemplateContent()) {
child_contains_parent = new_child.ContainsIncludingHostElements(*this);
} else {
child_contains_parent = new_child.contains(this);
}
}
if (child_contains_parent) {
exception_state.ThrowDOMException(
kHierarchyRequestError, "The new child element contains the parent.");
}
return child_contains_parent;
}
// EnsurePreInsertionValidity() is an implementation of step 2 to 6 of
// https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity and
// https://dom.spec.whatwg.org/#concept-node-replace .
DISABLE_CFI_PERF
bool ContainerNode::EnsurePreInsertionValidity(
const Node& new_child,
const Node* next,
const Node* old_child,
ExceptionState& exception_state) const {
DCHECK(!(next && old_child));
// Use common case fast path if possible.
if ((new_child.IsElementNode() || new_child.IsTextNode()) &&
IsElementNode()) {
DCHECK(IsChildTypeAllowed(new_child));
// 2. If node is a host-including inclusive ancestor of parent, throw a
// HierarchyRequestError.
if (IsHostIncludingInclusiveAncestorOfThis(new_child, exception_state))
return false;
// 3. If child is not null and its parent is not parent, then throw a
// NotFoundError.
return CheckReferenceChildParent(*this, next, old_child, exception_state);
}
// 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;
}
if (IsDocumentNode()) {
// Step 2 is unnecessary. No one can have a Document child.
// Step 3:
if (!CheckReferenceChildParent(*this, next, old_child, exception_state))
return false;
// Step 4-6.
return ToDocument(this)->CanAcceptChild(new_child, next, old_child,
exception_state);
}
// 2. If node is a host-including inclusive ancestor of parent, throw a
// HierarchyRequestError.
if (IsHostIncludingInclusiveAncestorOfThis(new_child, exception_state))
return false;
// 3. If child is not null and its parent is not parent, then throw a
// NotFoundError.
if (!CheckReferenceChildParent(*this, next, old_child, exception_state))
return false;
// 4. If node is not a DocumentFragment, DocumentType, Element, Text,
// ProcessingInstruction, or Comment node, throw a HierarchyRequestError.
// 5. If either node is a Text node and parent is a document, or node is a
// doctype and parent is not a document, throw a HierarchyRequestError.
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;
}
// Step 6 is unnecessary for non-Document nodes.
return true;
}
// We need this extra structural check because prior DOM mutation operations
// dispatched synchronous events, and their handlers might modified DOM trees.
bool ContainerNode::RecheckNodeInsertionStructuralPrereq(
const NodeVector& new_children,
const Node* next,
ExceptionState& exception_state) {
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 (IsDocumentNode()) {
// For Document, no need to check host-including inclusive ancestor
// because a Document node can't be a child of other nodes.
// However, status of existing doctype or root element might be changed
// and we need to check it again.
if (!ToDocument(this)->CanAcceptChild(*child, next, nullptr,
exception_state))
return false;
} else {
if (IsHostIncludingInclusiveAncestorOfThis(*child, exception_state))
return false;
}
}
return CheckReferenceChildParent(*this, next, nullptr, exception_state);
}
template <typename Functor>
void ContainerNode::InsertNodeVector(
const NodeVector& targets,
Node* next,
const Functor& mutator,
NodeVector* post_insertion_notification_targets) {
DCHECK(post_insertion_notification_targets);
probe::willInsertDOMNode(this);
{
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);
}
}
}
void ContainerNode::DidInsertNodeVector(
const NodeVector& targets,
Node* next,
const NodeVector& 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) {
DCHECK(new_child);
// https://dom.spec.whatwg.org/#concept-node-pre-insert
// insertBefore(node, null) is equivalent to appendChild(node)
if (!ref_child)
return AppendChild(new_child, exception_state);
// 1. Ensure pre-insertion validity of node into parent before child.
if (!EnsurePreInsertionValidity(*new_child, ref_child, nullptr,
exception_state))
return new_child;
// 2. Let reference child be child.
// 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);
}
// 4. Adopt node into parent’s node document.
NodeVector targets;
DOMTreeMutationDetector detector(*new_child, *this);
if (!CollectChildrenAndRemoveFromOldParent(*new_child, targets,
exception_state))
return new_child;
if (!detector.HadAtMostOneDOMMutation()) {
if (!RecheckNodeInsertionStructuralPrereq(targets, ref_child,
exception_state))
return new_child;
}
// 5. Insert node into parent before reference child.
NodeVector post_insertion_notification_targets;
{
ChildListMutationScope mutation(*this);
InsertNodeVector(targets, ref_child, AdoptAndInsertBefore(),
&post_insertion_notification_targets);
}
DidInsertNodeVector(targets, ref_child, post_insertion_notification_targets);
return new_child;
}
Node* ContainerNode::InsertBefore(Node* new_child, Node* ref_child) {
return InsertBefore(new_child, ref_child, ASSERT_NO_EXCEPTION);
}
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, 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) {
DCHECK(new_child);
// https://dom.spec.whatwg.org/#concept-node-replace
if (!old_child) {
exception_state.ThrowDOMException(kNotFoundError,
"The node to be replaced is null.");
return nullptr;
}
// Step 2 to 6.
if (!EnsurePreInsertionValidity(*new_child, nullptr, old_child,
exception_state))
return old_child;
// 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();
bool needs_recheck = false;
// 10. Adopt node into parent’s node document.
// TODO(tkent): Actually we do only RemoveChild() as a part of 'adopt'
// operation.
//
// Though the following CollectChildrenAndRemoveFromOldParent() also calls
// RemoveChild(), we'd like to call RemoveChild() here to make a separated
// MutationRecord.
if (ContainerNode* new_child_parent = new_child->parentNode()) {
DOMTreeMutationDetector detector(*new_child, *this);
new_child_parent->RemoveChild(new_child, exception_state);
if (exception_state.HadException())
return nullptr;
if (!detector.HadAtMostOneDOMMutation())
needs_recheck = true;
}
NodeVector targets;
NodeVector post_insertion_notification_targets;
{
// 9. Let previousSibling be child’s previous sibling.
// 11. Let removedNodes be the empty list.
// 15. Queue a mutation record of "childList" for target parent with
// addedNodes nodes, removedNodes removedNodes, nextSibling reference child,
// and previousSibling previousSibling.
ChildListMutationScope mutation(*this);
// 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.
if (ContainerNode* old_child_parent = old_child->parentNode()) {
DOMTreeMutationDetector detector(*old_child, *this);
old_child_parent->RemoveChild(old_child, exception_state);
if (exception_state.HadException())
return nullptr;
if (!detector.HadAtMostOneDOMMutation())
needs_recheck = true;
}
// 13. Let nodes be node’s children if node is a DocumentFragment node, and
// a list containing solely node otherwise.
DOMTreeMutationDetector detector(*new_child, *this);
if (!CollectChildrenAndRemoveFromOldParent(*new_child, targets,
exception_state))
return old_child;
if (!detector.HadAtMostOneDOMMutation() || needs_recheck) {
if (!RecheckNodeInsertionStructuralPrereq(targets, next, exception_state))
return old_child;
}
// 10. Adopt node into parent’s node document.
// 14. Insert node into parent before reference child with the suppress
// observers flag set.
if (next) {
InsertNodeVector(targets, next, AdoptAndInsertBefore(),
&post_insertion_notification_targets);
} else {
InsertNodeVector(targets, nullptr, AdoptAndAppendChild(),
&post_insertion_notification_targets);
}
}
DidInsertNodeVector(targets, next, post_insertion_notification_targets);
// 16. Return child.
return old_child;
}
Node* ContainerNode::ReplaceChild(Node* new_child, Node* old_child) {
return ReplaceChild(new_child, old_child, ASSERT_NO_EXCEPTION);
}
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);
}
void ContainerNode::Trace(blink::Visitor* visitor) {
visitor->Trace(first_child_);
visitor->Trace(last_child_);
Node::Trace(visitor);
}
void ContainerNode::TraceWrappers(const ScriptWrappableVisitor* visitor) const {
visitor->TraceWrappers(first_child_);
visitor->TraceWrappers(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::PluginDisposeSuspendScope suspend_plugin_dispose;
TreeOrderedMap::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;
}
Node* ContainerNode::RemoveChild(Node* old_child) {
return RemoveChild(old_child, ASSERT_NO_EXCEPTION);
}
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::PluginDisposeSuspendScope suspend_plugin_dispose;
TreeOrderedMap::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::PluginDisposeSuspendScope suspend_plugin_dispose;
TreeOrderedMap::RemoveScope tree_remove_scope;
{
EventDispatchForbiddenScope assert_no_event_dispatch;
ScriptForbiddenScope forbid_script;
while (Node* child = first_child_) {
RemoveBetween(nullptr, 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) {
DCHECK(new_child);
// Make sure adding the new child is ok
if (!EnsurePreInsertionValidity(*new_child, nullptr, nullptr,
exception_state))
return new_child;
NodeVector targets;
DOMTreeMutationDetector detector(*new_child, *this);
if (!CollectChildrenAndRemoveFromOldParent(*new_child, targets,
exception_state))
return new_child;
if (!detector.HadAtMostOneDOMMutation()) {
if (!RecheckNodeInsertionStructuralPrereq(targets, nullptr,
exception_state))
return new_child;
}
NodeVector post_insertion_notification_targets;
{
ChildListMutationScope mutation(*this);
InsertNodeVector(targets, nullptr, AdoptAndAppendChild(),
&post_insertion_notification_targets);
}
DidInsertNodeVector(targets, nullptr, post_insertion_notification_targets);
return new_child;
}
Node* ContainerNode::AppendChild(Node* new_child) {
return AppendChild(new_child, ASSERT_NO_EXCEPTION);
}
void ContainerNode::ParserAppendChild(Node* new_child) {
DCHECK(new_child);
DCHECK(!new_child->IsDocumentFragment());
DCHECK(!IsHTMLTemplateElement(this));
RUNTIME_CALL_TIMER_SCOPE(V8PerIsolateData::MainThreadIsolate(),
RuntimeCallStats::CounterId::kParserAppendChild);
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(AttachContext& context) {
for (Node* child = firstChild(); child; child = child->nextSibling()) {
#if DCHECK_IS_ON()
DCHECK(child->NeedsAttach() ||
ChildAttachedAllowedWhenAttachingChildren(this));
#endif
if (child->NeedsAttach())
child->AttachLayoutTree(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(nullptr, nullptr, &change);
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;
}
DCHECK(CanUseInlineBox(*o));
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;
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);
GetLayoutObject()->InvalidateIfControlStateChanged(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();
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::SetHasFocusWithinUpToAncestor(bool flag, Node* ancestor) {
for (ContainerNode* node = this; node && node != ancestor;
node = FlatTreeTraversal::Parent(*node)) {
node->SetHasFocusWithin(flag);
node->FocusWithinStateChanged();
}
}
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);
GetLayoutObject()->InvalidateIfControlStateChanged(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 (LayoutObject* o = GetLayoutObject())
o->InvalidateIfControlStateChanged(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);
}
Element* ContainerNode::QuerySelector(const AtomicString& selectors) {
return QuerySelector(selectors, ASSERT_NO_EXCEPTION);
}
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);
}
StaticElementList* ContainerNode::QuerySelectorAll(
const AtomicString& selectors) {
return QuerySelectorAll(selectors, ASSERT_NO_EXCEPTION);
}
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::kDOMNodeInsertedListener))
c->DispatchScopedEvent(MutationEvent::Create(
EventTypeNames::DOMNodeInserted, true, c->parentNode()));
// dispatch the DOMNodeInsertedIntoDocument event to all descendants
if (c->isConnected() && document->HasListenerType(
Document::kDOMNodeInsertedIntoDocumentListener)) {
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::kDOMNodeRemovedListener)) {
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::kDOMNodeRemovedFromDocumentListener)) {
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());
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);
}
}
}
void ContainerNode::RebuildLayoutTreeForChild(
Node* child,
WhitespaceAttacher& whitespace_attacher) {
if (child->IsTextNode()) {
Text* text_node = ToText(child);
if (child->NeedsReattachLayoutTree())
text_node->RebuildTextLayoutTree(whitespace_attacher);
else
whitespace_attacher.DidVisitText(text_node);
return;
}
if (!child->IsElementNode())
return;
Element* element = ToElement(child);
if (element->NeedsRebuildLayoutTree(whitespace_attacher))
element->RebuildLayoutTree(whitespace_attacher);
else
whitespace_attacher.DidVisitElement(element);
}
void ContainerNode::RebuildNonDistributedChildren() {
// Non-distributed children are:
// 1. Children of shadow hosts which are not slotted (v1) or distributed to an
// insertion point (v0).
// 2. Children of <slot> (v1) and <content> (v0) elements which are not used
// as fallback content when no nodes are slotted/distributed.
//
// These children will not take part in the flat tree, but we need to walk
// them in order to clear dirtiness flags during layout tree rebuild. We
// need to use a separate WhitespaceAttacher so that DidVisitText does not
// mess up the WhitespaceAttacher for the layout tree rebuild of the nodes
// which take part in the flat tree.
WhitespaceAttacher whitespace_attacher;
for (Node* child = lastChild(); child; child = child->previousSibling())
RebuildLayoutTreeForChild(child, whitespace_attacher);
ClearChildNeedsStyleRecalc();
ClearChildNeedsReattachLayoutTree();
}
void ContainerNode::RebuildChildrenLayoutTrees(
WhitespaceAttacher& whitespace_attacher) {
DCHECK(!NeedsReattachLayoutTree());
if (IsActiveSlotOrActiveV0InsertionPoint()) {
if (auto* slot = ToHTMLSlotElementOrNull(this)) {
slot->RebuildDistributedChildrenLayoutTrees(whitespace_attacher);
} else {
ToV0InsertionPoint(this)->RebuildDistributedChildrenLayoutTrees(
whitespace_attacher);
}
RebuildNonDistributedChildren();
return;
}
// 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())
RebuildLayoutTreeForChild(child, whitespace_attacher);
// 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,
const ChildrenChange* change) {
if (HasRareData() && (!attr_name || IsAttributeNode())) {
if (NodeListsNodeData* lists = RareData()->NodeLists()) {
if (ChildNodeList* child_node_list = lists->GetChildNodeList(*this)) {
if (change) {
child_node_list->ChildrenChanged(*change);
} else {
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);
}
}
HTMLCollection* ContainerNode::getElementsByTagName(
const AtomicString& qualified_name) {
DCHECK(!qualified_name.IsNull());
if (GetDocument().IsHTMLDocument()) {
return EnsureCachedCollection<HTMLTagCollection>(kHTMLTagCollectionType,
qualified_name);
}
return EnsureCachedCollection<TagCollection>(kTagCollectionType,
qualified_name);
}
HTMLCollection* ContainerNode::getElementsByTagNameNS(
const AtomicString& namespace_uri,
const AtomicString& local_name) {
return EnsureCachedCollection<TagCollectionNS>(
kTagCollectionNSType,
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->IsV0InsertionPoint())
return true;
if (IsHTMLSlotElement(node))
return true;
if (node->IsElementNode() && ToElement(node)->Shadow())
return true;
return false;
}
#endif
} // namespace blink