* 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 Apple Inc. All rights reserved.
+ * 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
#include "config.h"
#include "core/dom/ContainerNode.h"
-#include "bindings/v8/ExceptionState.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/ContainerNodeAlgorithms.h"
#include "core/dom/ElementTraversal.h"
#include "core/dom/ExceptionCode.h"
-#include "core/dom/FullscreenElementStack.h"
#include "core/dom/NameNodeList.h"
-#include "core/dom/NoEventDispatchAssertion.h"
#include "core/dom/NodeChildRemovalTracker.h"
#include "core/dom/NodeRareData.h"
#include "core/dom/NodeRenderStyle.h"
#include "core/dom/NodeTraversal.h"
-#include "core/dom/ScriptForbiddenScope.h"
#include "core/dom/SelectorQuery.h"
#include "core/dom/StaticNodeList.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/html/HTMLCollection.h"
#include "core/html/HTMLFrameOwnerElement.h"
+#include "core/html/HTMLTagCollection.h"
#include "core/html/RadioNodeList.h"
#include "core/inspector/InspectorInstrumentation.h"
#include "core/rendering/InlineTextBox.h"
#include "core/rendering/RenderText.h"
#include "core/rendering/RenderTheme.h"
#include "core/rendering/RenderView.h"
+#include "platform/EventDispatchForbiddenScope.h"
+#include "platform/ScriptForbiddenScope.h"
-namespace WebCore {
+namespace blink {
using namespace HTMLNames;
static void dispatchChildInsertionEvents(Node&);
static void dispatchChildRemovalEvents(Node&);
-#ifndef NDEBUG
-unsigned NoEventDispatchAssertion::s_count = 0;
+#if ENABLE(ASSERT)
+unsigned EventDispatchForbiddenScope::s_count = 0;
#endif
static void collectChildrenAndRemoveFromOldParent(Node& node, NodeVector& nodes, ExceptionState& exceptionState)
{
- if (!node.isDocumentFragment()) {
- nodes.append(&node);
- if (ContainerNode* oldParent = node.parentNode())
- oldParent->removeChild(&node, exceptionState);
+ if (node.isDocumentFragment()) {
+ DocumentFragment& fragment = toDocumentFragment(node);
+ getChildNodes(fragment, nodes);
+ fragment.removeChildren();
return;
}
- getChildNodes(node, nodes);
- toContainerNode(node).removeChildren();
+ nodes.append(&node);
+ if (ContainerNode* oldParent = node.parentNode())
+ oldParent->removeChild(&node, exceptionState);
}
#if !ENABLE(OILPAN)
{
ASSERT(!connectedSubframeCount());
ASSERT(needsAttach());
- removeDetachedChildrenInContainer<Node, ContainerNode>(*this);
+ removeDetachedChildrenInContainer(*this);
}
#endif
return true;
}
-void ContainerNode::insertBefore(PassRefPtrWillBeRawPtr<Node> newChild, Node* refChild, ExceptionState& exceptionState)
+PassRefPtrWillBeRawPtr<Node> ContainerNode::insertBefore(PassRefPtrWillBeRawPtr<Node> newChild, Node* refChild, ExceptionState& exceptionState)
{
#if !ENABLE(OILPAN)
// Check that this node is not "floating".
// insertBefore(node, 0) is equivalent to appendChild(node)
if (!refChild) {
- appendChild(newChild, exceptionState);
- return;
+ return appendChild(newChild, exceptionState);
}
// Make sure adding the new child is OK.
- if (!checkAcceptChild(newChild.get(), 0, exceptionState))
- return;
+ if (!checkAcceptChild(newChild.get(), 0, exceptionState)) {
+ if (exceptionState.hadException())
+ return nullptr;
+ return newChild;
+ }
ASSERT(newChild);
// NotFoundError: Raised if refChild is not a child of this node
if (refChild->parentNode() != this) {
exceptionState.throwDOMException(NotFoundError, "The node before which the new node is to be inserted is not a child of this node.");
- return;
+ return nullptr;
}
- if (refChild->previousSibling() == newChild || refChild == newChild) // nothing to do
- return;
+ // nothing to do
+ if (refChild->previousSibling() == newChild || refChild == newChild)
+ return newChild;
RefPtrWillBeRawPtr<Node> next = refChild;
NodeVector targets;
collectChildrenAndRemoveFromOldParent(*newChild, targets, exceptionState);
if (exceptionState.hadException())
- return;
+ return nullptr;
if (targets.isEmpty())
- return;
+ return newChild;
// We need this extra check because collectChildrenAndRemoveFromOldParent() can fire mutation events.
- if (!checkAcceptChildGuaranteedNodeTypes(*newChild, exceptionState))
- return;
+ if (!checkAcceptChildGuaranteedNodeTypes(*newChild, exceptionState)) {
+ if (exceptionState.hadException())
+ return nullptr;
+ return newChild;
+ }
InspectorInstrumentation::willInsertDOMNode(this);
}
dispatchSubtreeModifiedEvent();
+
+ return newChild;
}
void ContainerNode::insertBeforeCommon(Node& nextChild, Node& newChild)
{
- NoEventDispatchAssertion assertNoEventDispatch;
+ EventDispatchForbiddenScope assertNoEventDispatch;
ScriptForbiddenScope forbidScript;
ASSERT(!newChild.parentNode()); // Use insertBefore if you need to handle reparenting (and want DOM mutation events).
notifyNodeInserted(*newChild, ChildrenChangeSourceParser);
}
-void ContainerNode::replaceChild(PassRefPtrWillBeRawPtr<Node> newChild, Node* oldChild, ExceptionState& exceptionState)
+PassRefPtrWillBeRawPtr<Node> ContainerNode::replaceChild(PassRefPtrWillBeRawPtr<Node> newChild, PassRefPtrWillBeRawPtr<Node> oldChild, ExceptionState& exceptionState)
{
#if !ENABLE(OILPAN)
// Check that this node is not "floating".
RefPtrWillBeRawPtr<Node> protect(this);
if (oldChild == newChild) // nothing to do
- return;
+ return oldChild;
if (!oldChild) {
exceptionState.throwDOMException(NotFoundError, "The node to be replaced is null.");
- return;
+ return nullptr;
}
+ RefPtrWillBeRawPtr<Node> child = oldChild;
+
// Make sure replacing the old child with the new is ok
- if (!checkAcceptChild(newChild.get(), oldChild, exceptionState))
- return;
+ if (!checkAcceptChild(newChild.get(), child.get(), exceptionState)) {
+ if (exceptionState.hadException())
+ return nullptr;
+ return child;
+ }
// NotFoundError: Raised if oldChild is not a child of this node.
- if (oldChild->parentNode() != this) {
+ if (child->parentNode() != this) {
exceptionState.throwDOMException(NotFoundError, "The node to be replaced is not a child of this node.");
- return;
+ return nullptr;
}
ChildListMutationScope mutation(*this);
- RefPtrWillBeRawPtr<Node> next = oldChild->nextSibling();
+ RefPtrWillBeRawPtr<Node> next = child->nextSibling();
// Remove the node we're replacing
- RefPtrWillBeRawPtr<Node> protectRemovedChild = oldChild;
- ASSERT_UNUSED(protectRemovedChild, protectRemovedChild);
- removeChild(oldChild, exceptionState);
+ removeChild(child, exceptionState);
if (exceptionState.hadException())
- return;
+ return nullptr;
if (next && (next->previousSibling() == newChild || next == newChild)) // nothing to do
- return;
+ return child;
// Does this one more time because removeChild() fires a MutationEvent.
- if (!checkAcceptChild(newChild.get(), oldChild, exceptionState))
- return;
+ if (!checkAcceptChild(newChild.get(), child.get(), exceptionState)) {
+ if (exceptionState.hadException())
+ return nullptr;
+ return child;
+ }
NodeVector targets;
collectChildrenAndRemoveFromOldParent(*newChild, targets, exceptionState);
if (exceptionState.hadException())
- return;
+ return nullptr;
// Does this yet another check because collectChildrenAndRemoveFromOldParent() fires a MutationEvent.
- if (!checkAcceptChild(newChild.get(), oldChild, exceptionState))
- return;
+ if (!checkAcceptChild(newChild.get(), child.get(), exceptionState)) {
+ if (exceptionState.hadException())
+ return nullptr;
+ return child;
+ }
InspectorInstrumentation::willInsertDOMNode(this);
// Add child before "next".
{
- NoEventDispatchAssertion assertNoEventDispatch;
+ EventDispatchForbiddenScope assertNoEventDispatch;
if (next)
insertBeforeCommon(*next, child);
else
}
dispatchSubtreeModifiedEvent();
+ return child;
}
void ContainerNode::willRemoveChild(Node& child)
ChildFrameDisconnector(*this).disconnect(ChildFrameDisconnector::DescendantsOnly);
}
+#if !ENABLE(OILPAN)
+void ContainerNode::removeDetachedChildrenInContainer(ContainerNode& container)
+{
+ // List of nodes to be deleted.
+ Node* head = 0;
+ Node* tail = 0;
+
+ addChildNodesToDeletionQueue(head, tail, container);
+
+ Node* n;
+ Node* next;
+ while ((n = head) != 0) {
+ ASSERT_WITH_SECURITY_IMPLICATION(n->m_deletionHasBegun);
+
+ next = n->nextSibling();
+ n->setNextSibling(0);
+
+ head = next;
+ if (next == 0)
+ tail = 0;
+
+ if (n->hasChildren())
+ addChildNodesToDeletionQueue(head, tail, toContainerNode(*n));
+
+ delete n;
+ }
+}
+
+void ContainerNode::addChildNodesToDeletionQueue(Node*& head, Node*& tail, ContainerNode& container)
+{
+ // We have to tell all children that their parent has died.
+ Node* next = 0;
+ for (Node* n = container.firstChild(); n; n = next) {
+ ASSERT_WITH_SECURITY_IMPLICATION(!n->m_deletionHasBegun);
+
+ next = n->nextSibling();
+ n->setNextSibling(0);
+ n->setParentOrShadowHostNode(0);
+ container.setFirstChild(next);
+ if (next)
+ next->setPreviousSibling(0);
+
+ if (!n->refCount()) {
+#if ENABLE(SECURITY_ASSERT)
+ n->m_deletionHasBegun = true;
+#endif
+ // Add the node to the list of nodes to be deleted.
+ // Reuse the nextSibling pointer for this purpose.
+ if (tail)
+ tail->setNextSibling(n);
+ else
+ head = n;
+
+ tail = n;
+ } else {
+ RefPtrWillBeRawPtr<Node> protect(n); // removedFromDocument may remove all references to this node.
+ container.document().adoptIfNeeded(*n);
+ if (n->inDocument())
+ container.notifyNodeRemoved(*n);
+ }
+ }
+
+ container.setLastChild(0);
+}
+#endif
+
void ContainerNode::disconnectDescendantFrames()
{
ChildFrameDisconnector(*this).disconnect();
Node::trace(visitor);
}
-void ContainerNode::removeChild(Node* oldChild, ExceptionState& exceptionState)
+PassRefPtrWillBeRawPtr<Node> ContainerNode::removeChild(PassRefPtrWillBeRawPtr<Node> oldChild, ExceptionState& exceptionState)
{
#if !ENABLE(OILPAN)
// Check that this node is not "floating".
// ASSERT(!oldChild->isPseudoElement())
if (!oldChild || oldChild->parentNode() != this || oldChild->isPseudoElement()) {
exceptionState.throwDOMException(NotFoundError, "The node to be removed is not a child of this node.");
- return;
+ return nullptr;
}
RefPtrWillBeRawPtr<Node> child = oldChild;
document().removeFocusedElementOfSubtree(child.get());
- if (FullscreenElementStack* fullscreen = FullscreenElementStack::fromIfExists(document()))
- fullscreen->removeFullScreenElementOfSubtree(child.get());
-
// Events fired when blurring currently focused node might have moved this
// child into a different parent.
if (child->parentNode() != this) {
exceptionState.throwDOMException(NotFoundError, "The node to be removed is no longer a child of this node. Perhaps it was moved in a 'blur' event handler?");
- return;
+ return nullptr;
}
willRemoveChild(*child);
// Mutation events might have moved this child into a different parent.
if (child->parentNode() != this) {
exceptionState.throwDOMException(NotFoundError, "The node to be removed is no longer a child of this node. Perhaps it was moved in response to a mutation?");
- return;
+ return nullptr;
}
{
Node* next = child->nextSibling();
removeBetween(prev, next, *child);
notifyNodeRemoved(*child);
- childrenChanged(false, prev, next, -1);
+ childrenChanged(ChildrenChange::forRemoval(*child, prev, next, ChildrenChangeSourceAPI));
}
dispatchSubtreeModifiedEvent();
+ return child;
}
void ContainerNode::removeBetween(Node* previousChild, Node* nextChild, Node& oldChild)
{
- NoEventDispatchAssertion assertNoEventDispatch;
+ EventDispatchForbiddenScope assertNoEventDispatch;
ASSERT(oldChild.parentNode() == this);
removeBetween(prev, next, oldChild);
- childrenChanged(true, prev, next, -1);
notifyNodeRemoved(oldChild);
+ childrenChanged(ChildrenChange::forRemoval(oldChild, prev, next, ChildrenChangeSourceParser));
}
// this differs from other remove functions because it forcibly removes all the children,
// The container node can be removed from event handlers.
RefPtrWillBeRawPtr<ContainerNode> protect(this);
- if (FullscreenElementStack* fullscreen = FullscreenElementStack::fromIfExists(document()))
- fullscreen->removeFullScreenElementOfSubtree(this, true);
-
// Do any prep work needed before actually starting to detach
// and remove... e.g. stop loading frames, fire unload events.
willRemoveChildren();
HTMLFrameOwnerElement::UpdateSuspendScope suspendWidgetHierarchyUpdates;
{
- NoEventDispatchAssertion assertNoEventDispatch;
+ EventDispatchForbiddenScope assertNoEventDispatch;
ScriptForbiddenScope forbidScript;
removedChildren.reserveInitialCapacity(countChildren());
}
}
- childrenChanged(false, 0, 0, -static_cast<int>(removedChildren.size()));
+ ChildrenChange change = {AllChildrenRemoved, nullptr, nullptr, ChildrenChangeSourceAPI};
+ childrenChanged(change);
}
- dispatchSubtreeModifiedEvent();
+ // We don't fire the DOMSubtreeModified event for Attr Nodes. This matches the behavior
+ // of IE and Firefox. This event is fired synchronously and is a source of trouble for
+ // attributes as the JS callback could alter the attributes and leave us in a bad state.
+ if (!isAttributeNode())
+ dispatchSubtreeModifiedEvent();
}
-void ContainerNode::appendChild(PassRefPtrWillBeRawPtr<Node> newChild, ExceptionState& exceptionState)
+PassRefPtrWillBeRawPtr<Node> ContainerNode::appendChild(PassRefPtrWillBeRawPtr<Node> newChild, ExceptionState& exceptionState)
{
RefPtrWillBeRawPtr<ContainerNode> protect(this);
#endif
// Make sure adding the new child is ok
- if (!checkAcceptChild(newChild.get(), 0, exceptionState))
- return;
+ if (!checkAcceptChild(newChild.get(), 0, exceptionState)) {
+ if (exceptionState.hadException())
+ return nullptr;
+ return newChild;
+ }
ASSERT(newChild);
if (newChild == m_lastChild) // nothing to do
- return;
+ return newChild;
NodeVector targets;
collectChildrenAndRemoveFromOldParent(*newChild, targets, exceptionState);
if (exceptionState.hadException())
- return;
+ return nullptr;
if (targets.isEmpty())
- return;
+ return newChild;
// We need this extra check because collectChildrenAndRemoveFromOldParent() can fire mutation events.
- if (!checkAcceptChildGuaranteedNodeTypes(*newChild, exceptionState))
- return;
+ if (!checkAcceptChildGuaranteedNodeTypes(*newChild, exceptionState)) {
+ if (exceptionState.hadException())
+ return nullptr;
+ return newChild;
+ }
InspectorInstrumentation::willInsertDOMNode(this);
break;
{
- NoEventDispatchAssertion assertNoEventDispatch;
+ EventDispatchForbiddenScope assertNoEventDispatch;
ScriptForbiddenScope forbidScript;
treeScope().adoptIfNeeded(child);
}
dispatchSubtreeModifiedEvent();
+ return newChild;
}
void ContainerNode::parserAppendChild(PassRefPtrWillBeRawPtr<Node> newChild)
document().adoptNode(newChild.get(), ASSERT_NO_EXCEPTION);
{
- NoEventDispatchAssertion assertNoEventDispatch;
+ EventDispatchForbiddenScope assertNoEventDispatch;
ScriptForbiddenScope forbidScript;
treeScope().adoptIfNeeded(*newChild);
void ContainerNode::notifyNodeInserted(Node& root, ChildrenChangeSource source)
{
- ASSERT(!NoEventDispatchAssertion::isEventDispatchForbidden());
+ ASSERT(!EventDispatchForbiddenScope::isEventDispatchForbidden());
+ ASSERT(!root.isShadowRoot());
InspectorInstrumentation::didInsertDOMNode(&root);
NodeVector postInsertionNotificationTargets;
notifyNodeInsertedInternal(root, postInsertionNotificationTargets);
- // ShadowRoots are not real children, we don't need to tell host that it's
- // children changed when one is added.
- // FIXME: We should have a separate code path for ShadowRoot since it only
- // needs to call insertedInto and the rest of this logic is not needed.
- if (!root.isShadowRoot()) {
- childrenChanged(source == ChildrenChangeSourceParser, root.previousSibling(), root.nextSibling(), 1);
- }
+ childrenChanged(ChildrenChange::forInsertion(root, source));
for (size_t i = 0; i < postInsertionNotificationTargets.size(); ++i) {
Node* targetNode = postInsertionNotificationTargets[i].get();
void ContainerNode::notifyNodeInsertedInternal(Node& root, NodeVector& postInsertionNotificationTargets)
{
- NoEventDispatchAssertion assertNoEventDispatch;
+ EventDispatchForbiddenScope assertNoEventDispatch;
ScriptForbiddenScope forbidScript;
for (Node* node = &root; node; node = NodeTraversal::next(*node, &root)) {
void ContainerNode::notifyNodeRemoved(Node& root)
{
ScriptForbiddenScope forbidScript;
- NoEventDispatchAssertion assertNoEventDispatch;
+ EventDispatchForbiddenScope assertNoEventDispatch;
Document& document = root.document();
for (Node* node = &root; node; node = NodeTraversal::next(*node, &root)) {
Node::detach(context);
}
-void ContainerNode::childrenChanged(bool changedByParser, Node*, Node*, int childCountDelta)
+void ContainerNode::childrenChanged(const ChildrenChange& change)
{
document().incDOMTreeVersion();
- if (!changedByParser && childCountDelta)
+ if (!change.byParser && change.type != TextChanged)
document().updateRangesAfterChildrenChanged(this);
invalidateNodeListCachesInAncestors();
- if (childCountDelta > 0 && !childNeedsStyleRecalc()) {
+ if (change.isChildInsertion() && !childNeedsStyleRecalc()) {
setChildNeedsStyleRecalc();
markAncestorsWithChildNeedsStyleRecalc();
}
PassRefPtrWillBeRawPtr<HTMLCollection> ContainerNode::children()
{
- return ensureRareData().ensureNodeLists().addCache<HTMLCollection>(*this, NodeChildren);
+ return ensureCachedCollection<HTMLCollection>(NodeChildren);
}
unsigned ContainerNode::countChildren() const
return count;
}
-Node* ContainerNode::traverseToChildAt(unsigned index) const
-{
- unsigned i;
- Node *n = firstChild();
- for (i = 0; n != 0 && i < index; i++)
- n = n->nextSibling();
- return n;
-}
-
PassRefPtrWillBeRawPtr<Element> ContainerNode::querySelector(const AtomicString& selectors, ExceptionState& exceptionState)
{
if (selectors.isEmpty()) {
return selectorQuery->queryFirst(*this);
}
-PassRefPtrWillBeRawPtr<StaticNodeList> ContainerNode::querySelectorAll(const AtomicString& selectors, ExceptionState& exceptionState)
+PassRefPtrWillBeRawPtr<StaticElementList> ContainerNode::querySelectorAll(const AtomicString& selectors, ExceptionState& exceptionState)
{
if (selectors.isEmpty()) {
exceptionState.throwDOMException(SyntaxError, "The provided selector is empty.");
if (child.isInShadowTree())
return;
- ASSERT(!NoEventDispatchAssertion::isEventDispatchForbidden());
+ ASSERT(!EventDispatchForbiddenScope::isEventDispatchForbidden());
RefPtrWillBeRawPtr<Node> c(child);
RefPtrWillBeRawPtr<Document> document(child.document());
return;
}
- ASSERT(!NoEventDispatchAssertion::isEventDispatchForbidden());
+ ASSERT(!EventDispatchForbiddenScope::isEventDispatchForbidden());
InspectorInstrumentation::willRemoveDOMNode(&child);
}
}
-void ContainerNode::checkForSiblingStyleChanges(bool finishedParsingCallback, Node* beforeChange, Node* afterChange, int childCountDelta)
+void ContainerNode::checkForSiblingStyleChanges(SiblingCheckType changeType, Node* nodeBeforeChange, Node* nodeAfterChange)
{
if (!inActiveDocument() || document().hasPendingForcedStyleRecalc() || styleChangeType() >= SubtreeStyleChange)
return;
// |afterChange| is 0 in the parser callback case, so we won't do any work for the forward case if we don't have to.
// For performance reasons we just mark the parent node as changed, since we don't want to make childrenChanged O(n^2) by crawling all our kids
// here. recalcStyle will then force a walk of the children when it sees that this has happened.
- if (((childrenAffectedByForwardPositionalRules() || childrenAffectedByIndirectAdjacentRules()) && afterChange)
- || (childrenAffectedByBackwardPositionalRules() && beforeChange)) {
+ if (((childrenAffectedByForwardPositionalRules() || childrenAffectedByIndirectAdjacentRules()) && nodeAfterChange)
+ || (childrenAffectedByBackwardPositionalRules() && nodeBeforeChange)) {
setNeedsStyleRecalc(SubtreeStyleChange);
return;
}
// :first-child. In the parser callback case, we don't have to check anything, since we were right the first time.
// In the DOM case, we only need to do something if |afterChange| is not 0.
// |afterChange| is 0 in the parser case, so it works out that we'll skip this block.
- if (childrenAffectedByFirstChildRules() && afterChange) {
- // Find our new first child.
- Element* newFirstChild = ElementTraversal::firstWithin(*this);
- RenderStyle* newFirstChildStyle = newFirstChild ? newFirstChild->renderStyle() : 0;
-
- // Find the first element node following |afterChange|
- Node* firstElementAfterInsertion = afterChange->isElementNode() ? afterChange : ElementTraversal::nextSibling(*afterChange);
- RenderStyle* firstElementAfterInsertionStyle = firstElementAfterInsertion ? firstElementAfterInsertion->renderStyle() : 0;
-
- // This is the insert/append case.
- if (newFirstChild != firstElementAfterInsertion && firstElementAfterInsertionStyle && firstElementAfterInsertionStyle->firstChildState())
- firstElementAfterInsertion->setNeedsStyleRecalc(SubtreeStyleChange);
+ if (childrenAffectedByFirstChildRules() && nodeAfterChange) {
+ ASSERT(changeType != FinishedParsingChildren);
+ // Find our new first child element.
+ Element* firstChildElement = ElementTraversal::firstChild(*this);
+ RenderStyle* firstChildElementStyle = firstChildElement ? firstChildElement->renderStyle() : 0;
+
+ // Find the first element after the change.
+ Element* elementAfterChange = nodeAfterChange->isElementNode() ? toElement(nodeAfterChange) : ElementTraversal::nextSibling(*nodeAfterChange);
+ RenderStyle* elementAfterChangeStyle = elementAfterChange ? elementAfterChange->renderStyle() : 0;
+
+ // This is the element insertion as first child element case.
+ if (firstChildElement != elementAfterChange && elementAfterChangeStyle && elementAfterChangeStyle->firstChildState()) {
+ ASSERT(changeType == SiblingElementInserted);
+ elementAfterChange->setNeedsStyleRecalc(SubtreeStyleChange);
+ }
- // We also have to handle node removal.
- if (childCountDelta < 0 && newFirstChild == firstElementAfterInsertion && newFirstChild && (!newFirstChildStyle || !newFirstChildStyle->firstChildState()))
- newFirstChild->setNeedsStyleRecalc(SubtreeStyleChange);
+ // This is the first child element removal case.
+ if (changeType == SiblingElementRemoved && firstChildElement == elementAfterChange && firstChildElement && (!firstChildElementStyle || !firstChildElementStyle->firstChildState()))
+ firstChildElement->setNeedsStyleRecalc(SubtreeStyleChange);
}
// :last-child. In the parser callback case, we don't have to check anything, since we were right the first time.
// In the DOM case, we only need to do something if |afterChange| is not 0.
- if (childrenAffectedByLastChildRules() && beforeChange) {
- // Find our new last child.
- Node* newLastChild = ElementTraversal::lastChild(*this);
- RenderStyle* newLastChildStyle = newLastChild ? newLastChild->renderStyle() : 0;
-
- // Find the last element node going backwards from |beforeChange|
- Node* lastElementBeforeInsertion = beforeChange->isElementNode() ? beforeChange : ElementTraversal::previousSibling(*beforeChange);
- RenderStyle* lastElementBeforeInsertionStyle = lastElementBeforeInsertion ? lastElementBeforeInsertion->renderStyle() : 0;
-
- if (newLastChild != lastElementBeforeInsertion && lastElementBeforeInsertionStyle && lastElementBeforeInsertionStyle->lastChildState())
- lastElementBeforeInsertion->setNeedsStyleRecalc(SubtreeStyleChange);
+ if (childrenAffectedByLastChildRules() && nodeBeforeChange) {
+ // Find our new last child element.
+ Element* lastChildElement = ElementTraversal::lastChild(*this);
+ RenderStyle* lastChildElementStyle = lastChildElement ? lastChildElement->renderStyle() : 0;
+
+ // Find the last element before the change.
+ Element* elementBeforeChange = nodeBeforeChange->isElementNode() ? toElement(nodeBeforeChange) : ElementTraversal::previousSibling(*nodeBeforeChange);
+ RenderStyle* elementBeforeChangeStyle = elementBeforeChange ? elementBeforeChange->renderStyle() : 0;
+
+ // This is the element insertion as last child element case.
+ if (lastChildElement != elementBeforeChange && elementBeforeChangeStyle && elementBeforeChangeStyle->lastChildState()) {
+ ASSERT(SiblingElementInserted);
+ elementBeforeChange->setNeedsStyleRecalc(SubtreeStyleChange);
+ }
- // We also have to handle node removal. The parser callback case is similar to node removal as well in that we need to change the last child
+ // This is the last child element removal case. The parser callback case is similar to node removal as well in that we need to change the last child
// to match now.
- if ((childCountDelta < 0 || finishedParsingCallback) && newLastChild == lastElementBeforeInsertion && newLastChild && (!newLastChildStyle || !newLastChildStyle->lastChildState()))
- newLastChild->setNeedsStyleRecalc(SubtreeStyleChange);
+ if ((changeType == SiblingElementRemoved || changeType == FinishedParsingChildren) && lastChildElement == elementBeforeChange && lastChildElement && (!lastChildElementStyle || !lastChildElementStyle->lastChildState()))
+ lastChildElement->setNeedsStyleRecalc(SubtreeStyleChange);
}
- // The + selector. We need to invalidate the first element following the insertion point. It is the only possible element
+ // The + selector. We need to invalidate the first element following the change. It is the only possible element
// that could be affected by this DOM change.
- if (childrenAffectedByDirectAdjacentRules() && afterChange) {
- if (Node* firstElementAfterInsertion = afterChange->isElementNode() ? afterChange : ElementTraversal::nextSibling(*afterChange))
- firstElementAfterInsertion->setNeedsStyleRecalc(SubtreeStyleChange);
+ if (childrenAffectedByDirectAdjacentRules() && nodeAfterChange) {
+ if (Element* elementAfterChange = nodeAfterChange->isElementNode() ? toElement(nodeAfterChange) : ElementTraversal::nextSibling(*nodeAfterChange))
+ elementAfterChange->setNeedsStyleRecalc(SubtreeStyleChange);
+ }
+}
+
+void ContainerNode::invalidateNodeListCachesInAncestors(const QualifiedName* attrName, Element* attributeOwnerElement)
+{
+ if (hasRareData() && (!attrName || isAttributeNode())) {
+ if (NodeListsNodeData* lists = rareData()->nodeLists()) {
+ if (ChildNodeList* childNodeList = lists->childNodeList(*this))
+ childNodeList->invalidateCache();
+ }
+ }
+
+ // Modifications to attributes that are not associated with an Element can't invalidate NodeList caches.
+ if (attrName && !attributeOwnerElement)
+ return;
+
+ if (!document().shouldInvalidateNodeListCaches(attrName))
+ return;
+
+ document().invalidateNodeListCaches(attrName);
+
+ for (ContainerNode* node = this; node; node = node->parentNode()) {
+ if (NodeListsNodeData* lists = node->nodeLists())
+ lists->invalidateCaches(attrName);
}
}
return nullptr;
if (document().isHTMLDocument())
- return ensureRareData().ensureNodeLists().addCache<HTMLTagCollection>(*this, HTMLTagCollectionType, localName);
- return ensureRareData().ensureNodeLists().addCache<TagCollection>(*this, TagCollectionType, localName);
+ return ensureCachedCollection<HTMLTagCollection>(HTMLTagCollectionType, localName);
+ return ensureCachedCollection<TagCollection>(TagCollectionType, localName);
}
PassRefPtrWillBeRawPtr<TagCollection> ContainerNode::getElementsByTagNameNS(const AtomicString& namespaceURI, const AtomicString& localName)
if (namespaceURI == starAtom)
return getElementsByTagName(localName);
- return ensureRareData().ensureNodeLists().addCache(*this, namespaceURI.isEmpty() ? nullAtom : namespaceURI, localName);
+ return ensureCachedCollection<TagCollection>(TagCollectionType, namespaceURI.isEmpty() ? nullAtom : namespaceURI, localName);
}
// 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.
PassRefPtrWillBeRawPtr<NameNodeList> ContainerNode::getElementsByName(const AtomicString& elementName)
{
- return ensureRareData().ensureNodeLists().addCache<NameNodeList>(*this, NameNodeListType, elementName);
+ return ensureCachedCollection<NameNodeList>(NameNodeListType, elementName);
}
// 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.
PassRefPtrWillBeRawPtr<ClassCollection> ContainerNode::getElementsByClassName(const AtomicString& classNames)
{
- return ensureRareData().ensureNodeLists().addCache<ClassCollection>(*this, ClassCollectionType, classNames);
+ return ensureCachedCollection<ClassCollection>(ClassCollectionType, classNames);
}
PassRefPtrWillBeRawPtr<RadioNodeList> ContainerNode::radioNodeList(const AtomicString& name, bool onlyMatchImgElements)
{
ASSERT(isHTMLFormElement(this) || isHTMLFieldSetElement(this));
CollectionType type = onlyMatchImgElements ? RadioImgNodeListType : RadioNodeListType;
- return ensureRareData().ensureNodeLists().addCache<RadioNodeList>(*this, type, name);
+ return ensureCachedCollection<RadioNodeList>(type, name);
}
Element* ContainerNode::getElementById(const AtomicString& id) const
return 0;
}
-#ifndef NDEBUG
+NodeListsNodeData& ContainerNode::ensureNodeLists()
+{
+ return ensureRareData().ensureNodeLists();
+}
+
+#if ENABLE(ASSERT)
bool childAttachedAllowedWhenAttachingChildren(ContainerNode* node)
{
if (node->isShadowRoot())
}
#endif
-} // namespace WebCore
+} // namespace blink