blob: 3de58c2b8b065a3bc08c1e1f237b27374c12ecc8 [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/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