Log Message
Avoid tree traversals to look for form and canvas elements https://bugs.webkit.org/show_bug.cgi?id=222159 <rdar://problem/74680265>
Reviewed by Simon Fraser. This patch introduces a fast path for checking if an element has a canvas or a form as an ancestor using new node flags, InclusiveAncestorStateForForm and InclusiveAncestorStateForCanvas. We update these flags in Element::insertedIntoAncestor and Element::removedFromAncestor based on the same node flags set on its new parent (clear if there is no parent). HTMLFormElement and HTMLCanvasElement always add their respective flag (but never clear others) inside their constructors and after calling Element's insertedIntoAncestor and removedFromAncestor. Because notifyChildNodeInserted and notifyChildNodeRemoved traverse the newly inserted or newly removed nodes in tree order, all its descendants will carry over the flag. For convenience and code readability, we use OptionSet<Node::AncestorState> to represent the node tree ancestory state of these elements. No new tests since there should be no behavioral differences. * dom/ContainerNodeAlgorithms.cpp: (WebCore::notifyNodeInsertedIntoDocument): (WebCore::notifyNodeInsertedIntoTree): (WebCore::notifyChildNodeInserted): (WebCore::notifyNodeRemovedFromDocument): (WebCore::notifyNodeRemovedFromTree): (WebCore::notifyChildNodeRemoved): * dom/Element.cpp: (WebCore::Element::isFocusable const): Optimized this function by avoiding the ancestor tree walk when inclusiveAncestorStates() does not contain AncestorState::Canvas. (WebCore::Element::insertedIntoAncestor): Clear or set new node flags based on the ancestor states. (WebCore::Element::removedFromAncestor): Ditto. * dom/Node.h: (WebCore::Node::delegatesFocusToShadowRoot const): Added. (WebCore::Node::setDelegatesFocusToShadowRoot): Added. (WebCore::Node::inclusiveAncestorStates const): Added. (WebCore::Node::setInclusiveAncestorStates): Added. (WebCore::Node::addInclusiveAncestorState): Added. Used by HTMLCanvasElement and HTMLFormElement to set the respective node flags. * html/HTMLAnchorElement.cpp: (WebCore::HTMLAnchorElement::isKeyboardFocusable const): Added the same optimization as Element. * html/HTMLCanvasElement.cpp: (WebCore::HTMLCanvasElement::HTMLCanvasElement): Set InclusiveAncestorStateForCanvas. (WebCore::HTMLCanvasElement::insertedIntoAncestor): Ditto. Note that we must do this after calling Element::insertedIntoAncestor as it can unset this flag (this is the point of this code). Also note that Element::insertedIntoAncestor will always return InsertedIntoAncestorResult::Done. (WebCore::HTMLCanvasElement::removedFromAncestor): Ditto for removal. * html/HTMLFormElement.cpp: (WebCore::HTMLFormElement::HTMLFormElement): Ditto for InclusiveAncestorStateForForm. (WebCore::HTMLFormElement::insertedIntoAncestor): Ditto. (WebCore::HTMLFormElement::removedFromAncestor): Ditto. (WebCore::HTMLFormElement::findClosestFormAncestorSlowCase): Renamed from findClosestFormAncestor. Optimized this function a bit by avoiding the tree walk when the starting element itself is a form since InclusiveAncestorStateForForm is always set on the element in that case. * html/HTMLFormElement.h: (WebCore::HTMLFormElement::findClosestFormAncestor): Added the optimized version inlined here. It exits early with nullptr if InclusiveAncestorStateForForm is not set. * html/HTMLImageElement.cpp: (WebCore::HTMLImageElement::insertedIntoAncestor): Call Element::insertedIntoAncestor before calling findClosestFormAncestor to find the form element to associate this image element with as it now relies on InclusiveAncestorStateForForm flag which is updated in Element::insertedIntoAncestor.
Modified Paths
- trunk/Source/WebCore/ChangeLog
- trunk/Source/WebCore/dom/ContainerNodeAlgorithms.cpp
- trunk/Source/WebCore/dom/Element.cpp
- trunk/Source/WebCore/dom/Node.h
- trunk/Source/WebCore/html/HTMLAnchorElement.cpp
- trunk/Source/WebCore/html/HTMLCanvasElement.cpp
- trunk/Source/WebCore/html/HTMLFormElement.cpp
- trunk/Source/WebCore/html/HTMLFormElement.h
- trunk/Source/WebCore/html/HTMLImageElement.cpp
Diff
Modified: trunk/Source/WebCore/ChangeLog (273478 => 273479)
--- trunk/Source/WebCore/ChangeLog 2021-02-25 08:31:26 UTC (rev 273478)
+++ trunk/Source/WebCore/ChangeLog 2021-02-25 09:02:56 UTC (rev 273479)
@@ -1,3 +1,68 @@
+2021-02-25 Ryosuke Niwa <[email protected]>
+
+ Avoid tree traversals to look for form and canvas elements
+ https://bugs.webkit.org/show_bug.cgi?id=222159
+ <rdar://problem/74680265>
+
+ Reviewed by Simon Fraser.
+
+ This patch introduces a fast path for checking if an element has a canvas or a form as an ancestor
+ using new node flags, InclusiveAncestorStateForForm and InclusiveAncestorStateForCanvas.
+ We update these flags in Element::insertedIntoAncestor and Element::removedFromAncestor based on
+ the same node flags set on its new parent (clear if there is no parent).
+
+ HTMLFormElement and HTMLCanvasElement always add their respective flag (but never clear others)
+ inside their constructors and after calling Element's insertedIntoAncestor and removedFromAncestor.
+ Because notifyChildNodeInserted and notifyChildNodeRemoved traverse the newly inserted or newly
+ removed nodes in tree order, all its descendants will carry over the flag.
+
+ For convenience and code readability, we use OptionSet<Node::AncestorState> to represent
+ the node tree ancestory state of these elements.
+
+ No new tests since there should be no behavioral differences.
+
+ * dom/ContainerNodeAlgorithms.cpp:
+ (WebCore::notifyNodeInsertedIntoDocument):
+ (WebCore::notifyNodeInsertedIntoTree):
+ (WebCore::notifyChildNodeInserted):
+ (WebCore::notifyNodeRemovedFromDocument):
+ (WebCore::notifyNodeRemovedFromTree):
+ (WebCore::notifyChildNodeRemoved):
+ * dom/Element.cpp:
+ (WebCore::Element::isFocusable const): Optimized this function by avoiding the ancestor tree walk
+ when inclusiveAncestorStates() does not contain AncestorState::Canvas.
+ (WebCore::Element::insertedIntoAncestor): Clear or set new node flags based on the ancestor states.
+ (WebCore::Element::removedFromAncestor): Ditto.
+ * dom/Node.h:
+ (WebCore::Node::delegatesFocusToShadowRoot const): Added.
+ (WebCore::Node::setDelegatesFocusToShadowRoot): Added.
+ (WebCore::Node::inclusiveAncestorStates const): Added.
+ (WebCore::Node::setInclusiveAncestorStates): Added.
+ (WebCore::Node::addInclusiveAncestorState): Added. Used by HTMLCanvasElement and HTMLFormElement
+ to set the respective node flags.
+ * html/HTMLAnchorElement.cpp:
+ (WebCore::HTMLAnchorElement::isKeyboardFocusable const): Added the same optimization as Element.
+ * html/HTMLCanvasElement.cpp:
+ (WebCore::HTMLCanvasElement::HTMLCanvasElement): Set InclusiveAncestorStateForCanvas.
+ (WebCore::HTMLCanvasElement::insertedIntoAncestor): Ditto. Note that we must do this after calling
+ Element::insertedIntoAncestor as it can unset this flag (this is the point of this code).
+ Also note that Element::insertedIntoAncestor will always return InsertedIntoAncestorResult::Done.
+ (WebCore::HTMLCanvasElement::removedFromAncestor): Ditto for removal.
+ * html/HTMLFormElement.cpp:
+ (WebCore::HTMLFormElement::HTMLFormElement): Ditto for InclusiveAncestorStateForForm.
+ (WebCore::HTMLFormElement::insertedIntoAncestor): Ditto.
+ (WebCore::HTMLFormElement::removedFromAncestor): Ditto.
+ (WebCore::HTMLFormElement::findClosestFormAncestorSlowCase): Renamed from findClosestFormAncestor.
+ Optimized this function a bit by avoiding the tree walk when the starting element itself is a form
+ since InclusiveAncestorStateForForm is always set on the element in that case.
+ * html/HTMLFormElement.h:
+ (WebCore::HTMLFormElement::findClosestFormAncestor): Added the optimized version inlined here.
+ It exits early with nullptr if InclusiveAncestorStateForForm is not set.
+ * html/HTMLImageElement.cpp:
+ (WebCore::HTMLImageElement::insertedIntoAncestor): Call Element::insertedIntoAncestor before calling
+ findClosestFormAncestor to find the form element to associate this image element with as it now
+ relies on InclusiveAncestorStateForForm flag which is updated in Element::insertedIntoAncestor.
+
2021-02-25 Antoine Quint <[email protected]>
border-image-outset doesn't handle float values
Modified: trunk/Source/WebCore/dom/ContainerNodeAlgorithms.cpp (273478 => 273479)
--- trunk/Source/WebCore/dom/ContainerNodeAlgorithms.cpp 2021-02-25 08:31:26 UTC (rev 273478)
+++ trunk/Source/WebCore/dom/ContainerNodeAlgorithms.cpp 2021-02-25 09:02:56 UTC (rev 273479)
@@ -41,19 +41,20 @@
enum class TreeScopeChange { Changed, DidNotChange };
-static void notifyNodeInsertedIntoDocument(ContainerNode& parentOfInsertedTree, Node& node, TreeScopeChange treeScopeChange, NodeVector& postInsertionNotificationTargets)
+static void notifyNodeInsertedIntoDocument(ContainerNode& parentOfInsertedTree, Node& node, TreeScopeChange treeScopeChange, OptionSet<Node::AncestorState> ancestorStates, NodeVector& postInsertionNotificationTargets)
{
ASSERT(parentOfInsertedTree.isConnected());
ASSERT(!node.isConnected());
- if (node.insertedIntoAncestor(Node::InsertionType { /* connectedToDocument */ true, treeScopeChange == TreeScopeChange::Changed }, parentOfInsertedTree) == Node::InsertedIntoAncestorResult::NeedsPostInsertionCallback)
+ if (node.insertedIntoAncestor(Node::InsertionType { /* connectedToDocument */ true, treeScopeChange == TreeScopeChange::Changed, ancestorStates }, parentOfInsertedTree) == Node::InsertedIntoAncestorResult::NeedsPostInsertionCallback)
postInsertionNotificationTargets.append(node);
if (!is<ContainerNode>(node))
return;
+ auto ancestorStatesOfChildNodes = node.inclusiveAncestorStates();
for (RefPtr<Node> child = downcast<ContainerNode>(node).firstChild(); child; child = child->nextSibling()) {
RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(node.isConnected() && child->parentNode() == &node);
- notifyNodeInsertedIntoDocument(parentOfInsertedTree, *child, treeScopeChange, postInsertionNotificationTargets);
+ notifyNodeInsertedIntoDocument(parentOfInsertedTree, *child, treeScopeChange, ancestorStatesOfChildNodes, postInsertionNotificationTargets);
}
if (!is<Element>(node))
@@ -61,29 +62,30 @@
if (RefPtr<ShadowRoot> root = downcast<Element>(node).shadowRoot()) {
RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(node.isConnected() && root->host() == &node);
- notifyNodeInsertedIntoDocument(parentOfInsertedTree, *root, TreeScopeChange::DidNotChange, postInsertionNotificationTargets);
+ notifyNodeInsertedIntoDocument(parentOfInsertedTree, *root, TreeScopeChange::DidNotChange, { }, postInsertionNotificationTargets);
}
}
-static void notifyNodeInsertedIntoTree(ContainerNode& parentOfInsertedTree, Node& node, TreeScopeChange treeScopeChange, NodeVector& postInsertionNotificationTargets)
+static void notifyNodeInsertedIntoTree(ContainerNode& parentOfInsertedTree, Node& node, TreeScopeChange treeScopeChange, OptionSet<Node::AncestorState> ancestorStates, NodeVector& postInsertionNotificationTargets)
{
ASSERT(!parentOfInsertedTree.isConnected());
ASSERT(!node.isConnected());
- if (node.insertedIntoAncestor(Node::InsertionType { /* connectedToDocument */ false, treeScopeChange == TreeScopeChange::Changed }, parentOfInsertedTree) == Node::InsertedIntoAncestorResult::NeedsPostInsertionCallback)
+ if (node.insertedIntoAncestor(Node::InsertionType { /* connectedToDocument */ false, treeScopeChange == TreeScopeChange::Changed, ancestorStates }, parentOfInsertedTree) == Node::InsertedIntoAncestorResult::NeedsPostInsertionCallback)
postInsertionNotificationTargets.append(node);
if (!is<ContainerNode>(node))
return;
+ auto ancestorStatesOfChildNodes = node.inclusiveAncestorStates();
for (RefPtr<Node> child = downcast<ContainerNode>(node).firstChild(); child; child = child->nextSibling())
- notifyNodeInsertedIntoTree(parentOfInsertedTree, *child, treeScopeChange, postInsertionNotificationTargets);
+ notifyNodeInsertedIntoTree(parentOfInsertedTree, *child, treeScopeChange, ancestorStatesOfChildNodes, postInsertionNotificationTargets);
if (!is<Element>(node))
return;
if (RefPtr<ShadowRoot> root = downcast<Element>(node).shadowRoot())
- notifyNodeInsertedIntoTree(parentOfInsertedTree, *root, TreeScopeChange::DidNotChange, postInsertionNotificationTargets);
+ notifyNodeInsertedIntoTree(parentOfInsertedTree, *root, TreeScopeChange::DidNotChange, { }, postInsertionNotificationTargets);
}
NodeVector notifyChildNodeInserted(ContainerNode& parentOfInsertedTree, Node& node)
@@ -100,9 +102,9 @@
// Tree scope has changed if the container node into which "node" is inserted is in a document or a shadow root.
auto treeScopeChange = parentOfInsertedTree.isInTreeScope() ? TreeScopeChange::Changed : TreeScopeChange::DidNotChange;
if (parentOfInsertedTree.isConnected())
- notifyNodeInsertedIntoDocument(parentOfInsertedTree, node, treeScopeChange, postInsertionNotificationTargets);
+ notifyNodeInsertedIntoDocument(parentOfInsertedTree, node, treeScopeChange, parentOfInsertedTree.inclusiveAncestorStates(), postInsertionNotificationTargets);
else
- notifyNodeInsertedIntoTree(parentOfInsertedTree, node, treeScopeChange, postInsertionNotificationTargets);
+ notifyNodeInsertedIntoTree(parentOfInsertedTree, node, treeScopeChange, parentOfInsertedTree.inclusiveAncestorStates(), postInsertionNotificationTargets);
return postInsertionNotificationTargets;
}
@@ -119,19 +121,20 @@
currentObservability = newStatus;
}
-static RemovedSubtreeObservability notifyNodeRemovedFromDocument(ContainerNode& oldParentOfRemovedTree, TreeScopeChange treeScopeChange, Node& node)
+static RemovedSubtreeObservability notifyNodeRemovedFromDocument(ContainerNode& oldParentOfRemovedTree, TreeScopeChange treeScopeChange, OptionSet<Node::AncestorState> ancestorStates, Node& node)
{
ASSERT(oldParentOfRemovedTree.isConnected());
ASSERT(node.isConnected());
- node.removedFromAncestor(Node::RemovalType { /* disconnectedFromDocument */ true, treeScopeChange == TreeScopeChange::Changed }, oldParentOfRemovedTree);
+ node.removedFromAncestor(Node::RemovalType { /* disconnectedFromDocument */ true, treeScopeChange == TreeScopeChange::Changed, ancestorStates }, oldParentOfRemovedTree);
auto observability = observabilityOfRemovedNode(node);
if (!is<ContainerNode>(node))
return observability;
+ auto ancestorStatesOfChildNodes = node.inclusiveAncestorStates();
for (RefPtr<Node> child = downcast<ContainerNode>(node).firstChild(); child; child = child->nextSibling()) {
RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(!node.isConnected() && child->parentNode() == &node);
- updateObservability(observability, notifyNodeRemovedFromDocument(oldParentOfRemovedTree, treeScopeChange, *child.get()));
+ updateObservability(observability, notifyNodeRemovedFromDocument(oldParentOfRemovedTree, treeScopeChange, ancestorStatesOfChildNodes, *child.get()));
}
if (!is<Element>(node))
@@ -139,29 +142,30 @@
if (RefPtr<ShadowRoot> root = downcast<Element>(node).shadowRoot()) {
RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(!node.isConnected() && root->host() == &node);
- updateObservability(observability, notifyNodeRemovedFromDocument(oldParentOfRemovedTree, TreeScopeChange::DidNotChange, *root.get()));
+ updateObservability(observability, notifyNodeRemovedFromDocument(oldParentOfRemovedTree, TreeScopeChange::DidNotChange, { }, *root.get()));
}
return observability;
}
-static RemovedSubtreeObservability notifyNodeRemovedFromTree(ContainerNode& oldParentOfRemovedTree, TreeScopeChange treeScopeChange, Node& node)
+static RemovedSubtreeObservability notifyNodeRemovedFromTree(ContainerNode& oldParentOfRemovedTree, TreeScopeChange treeScopeChange, OptionSet<Node::AncestorState> ancestorStates, Node& node)
{
ASSERT(!oldParentOfRemovedTree.isConnected());
- node.removedFromAncestor(Node::RemovalType { /* disconnectedFromDocument */ false, treeScopeChange == TreeScopeChange::Changed }, oldParentOfRemovedTree);
+ node.removedFromAncestor(Node::RemovalType { /* disconnectedFromDocument */ false, treeScopeChange == TreeScopeChange::Changed, ancestorStates }, oldParentOfRemovedTree);
auto observability = observabilityOfRemovedNode(node);
if (!is<ContainerNode>(node))
return observability;
+ auto ancestorStatesOfChildNodes = node.inclusiveAncestorStates();
for (RefPtr<Node> child = downcast<ContainerNode>(node).firstChild(); child; child = child->nextSibling())
- updateObservability(observability, notifyNodeRemovedFromTree(oldParentOfRemovedTree, treeScopeChange, *child));
+ updateObservability(observability, notifyNodeRemovedFromTree(oldParentOfRemovedTree, treeScopeChange, ancestorStatesOfChildNodes, *child));
if (!is<Element>(node))
return observability;
if (RefPtr<ShadowRoot> root = downcast<Element>(node).shadowRoot())
- updateObservability(observability, notifyNodeRemovedFromTree(oldParentOfRemovedTree, TreeScopeChange::DidNotChange, *root));
+ updateObservability(observability, notifyNodeRemovedFromTree(oldParentOfRemovedTree, TreeScopeChange::DidNotChange, { }, *root));
return observability;
}
@@ -175,8 +179,8 @@
// Tree scope has changed if the container node from which "node" is removed is in a document or a shadow root.
auto treeScopeChange = oldParentOfRemovedTree.isInTreeScope() ? TreeScopeChange::Changed : TreeScopeChange::DidNotChange;
if (child.isConnected())
- return notifyNodeRemovedFromDocument(oldParentOfRemovedTree, treeScopeChange, child);
- return notifyNodeRemovedFromTree(oldParentOfRemovedTree, treeScopeChange, child);
+ return notifyNodeRemovedFromDocument(oldParentOfRemovedTree, treeScopeChange, { }, child);
+ return notifyNodeRemovedFromTree(oldParentOfRemovedTree, treeScopeChange, { }, child);
}
void addChildNodesToDeletionQueue(Node*& head, Node*& tail, ContainerNode& container)
Modified: trunk/Source/WebCore/dom/Element.cpp (273478 => 273479)
--- trunk/Source/WebCore/dom/Element.cpp 2021-02-25 08:31:26 UTC (rev 273478)
+++ trunk/Source/WebCore/dom/Element.cpp 2021-02-25 09:02:56 UTC (rev 273479)
@@ -636,7 +636,10 @@
if (!isConnected() || !supportsFocus())
return false;
- if (!renderer()) {
+ bool hasCanvasAsInclusiveAncestor = inclusiveAncestorStates().contains(AncestorState::Canvas);
+ ASSERT(hasCanvasAsInclusiveAncestor == !!ancestorsOfType<HTMLCanvasElement>(*this).first()
+ || (hasCanvasAsInclusiveAncestor && is<HTMLCanvasElement>(*this)));
+ if (!renderer() && hasCanvasAsInclusiveAncestor) {
// Elements in canvas fallback content are not rendered, but they are allowed to be
// focusable as long as their canvas is displayed and visible.
if (auto* canvas = ancestorsOfType<HTMLCanvasElement>(*this).first())
@@ -2151,6 +2154,8 @@
{
ContainerNode::insertedIntoAncestor(insertionType, parentOfInsertedTree);
+ setInclusiveAncestorStates(insertionType.ancestorStates);
+
#if ENABLE(FULLSCREEN_API)
if (containsFullScreenElement() && parentElement() && !parentElement()->containsFullScreenElement())
setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true);
@@ -2208,6 +2213,8 @@
void Element::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree)
{
+ setInclusiveAncestorStates(removalType.ancestorStates);
+
#if ENABLE(FULLSCREEN_API)
if (containsFullScreenElement())
setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false);
Modified: trunk/Source/WebCore/dom/Node.h (273478 => 273479)
--- trunk/Source/WebCore/dom/Node.h 2021-02-25 08:31:26 UTC (rev 273478)
+++ trunk/Source/WebCore/dom/Node.h 2021-02-25 09:02:56 UTC (rev 273479)
@@ -406,9 +406,14 @@
Done,
NeedsPostInsertionCallback,
};
+ enum class AncestorState : uint8_t {
+ Form = 1 << 0,
+ Canvas = 1 << 1,
+ };
struct InsertionType {
bool connectedToDocument { false };
bool treeScopeChanged { false };
+ OptionSet<AncestorState> ancestorStates;
};
// Called *after* this node or its ancestor is inserted into a new parent (may or may not be a part of document) by scripts or parser.
// insertedInto **MUST NOT** invoke scripts. Return NeedsPostInsertionCallback and implement didFinishInsertingNode instead to run scripts.
@@ -418,6 +423,7 @@
struct RemovalType {
bool disconnectedFromDocument { false };
bool treeScopeChanged { false };
+ OptionSet<AncestorState> ancestorStates;
};
virtual void removedFromAncestor(RemovalType, ContainerNode& oldParentOfRemovedTree);
@@ -495,6 +501,8 @@
void updateAncestorConnectedSubframeCountForRemoval() const;
void updateAncestorConnectedSubframeCountForInsertion() const;
+ OptionSet<AncestorState> inclusiveAncestorStates() const;
+
#if ENABLE(JIT)
static ptrdiff_t nodeFlagsMemoryOffset() { return OBJECT_OFFSETOF(Node, m_nodeFlags); }
static ptrdiff_t rareDataMemoryOffset() { return OBJECT_OFFSETOF(Node, m_rareDataWithBitfields); }
@@ -551,7 +559,9 @@
IsComputedStyleInvalidFlag = 1 << 26,
DelegatesFocusToShadowRoot = 1 << 27,
- // Bits 28-31 are free.
+ InclusiveAncestorStateForForm = 1 << 28,
+ InclusiveAncestorStateForCanvas = 1 << 29,
+ // Bits 30-31 are free.
};
enum class TabIndexState : uint8_t {
@@ -593,6 +603,9 @@
void setDelegatesFocusToShadowRoot() { setNodeFlag(NodeFlag::DelegatesFocusToShadowRoot); }
+ void setInclusiveAncestorStates(OptionSet<AncestorState>);
+ void addInclusiveAncestorState(AncestorState);
+
constexpr static auto DefaultNodeFlags = OptionSet<NodeFlag>(NodeFlag::IsParsingChildrenFinished);
constexpr static auto CreateOther = DefaultNodeFlags;
constexpr static auto CreateCharacterData = DefaultNodeFlags | NodeFlag::IsCharacterData;
@@ -890,6 +903,44 @@
moveTreeToNewScope(*this, *m_treeScope, newTreeScope);
}
+ALWAYS_INLINE OptionSet<Node::AncestorState> Node::inclusiveAncestorStates() const
+{
+ ASSERT(isElementNode() || isTreeScope() || isDocumentFragment()); // Only Element supports ancestor states for expediency.
+ OptionSet<Node::AncestorState> states;
+ if (hasNodeFlag(NodeFlag::InclusiveAncestorStateForForm))
+ states.add(AncestorState::Form);
+ if (hasNodeFlag(NodeFlag::InclusiveAncestorStateForCanvas))
+ states.add(AncestorState::Canvas);
+ return states;
+}
+
+ALWAYS_INLINE void Node::setInclusiveAncestorStates(OptionSet<AncestorState> states)
+{
+ ASSERT(isElementNode());
+ if (states.contains(AncestorState::Form))
+ setNodeFlag(NodeFlag::InclusiveAncestorStateForForm);
+ else
+ clearNodeFlag(NodeFlag::InclusiveAncestorStateForForm);
+
+ if (states.contains(AncestorState::Canvas))
+ setNodeFlag(NodeFlag::InclusiveAncestorStateForCanvas);
+ else
+ clearNodeFlag(NodeFlag::InclusiveAncestorStateForCanvas);
+}
+
+inline void Node::addInclusiveAncestorState(AncestorState state)
+{
+ ASSERT(isHTMLElement());
+ switch (state) {
+ case AncestorState::Form:
+ setNodeFlag(NodeFlag::InclusiveAncestorStateForForm);
+ break;
+ case AncestorState::Canvas:
+ setNodeFlag(NodeFlag::InclusiveAncestorStateForCanvas);
+ break;
+ }
+}
+
inline constexpr PartialOrdering PartialOrdering::less(Type::Less);
inline constexpr PartialOrdering PartialOrdering::equivalent(Type::Equivalent);
inline constexpr PartialOrdering PartialOrdering::greater(Type::Greater);
Modified: trunk/Source/WebCore/html/HTMLAnchorElement.cpp (273478 => 273479)
--- trunk/Source/WebCore/html/HTMLAnchorElement.cpp 2021-02-25 08:31:26 UTC (rev 273478)
+++ trunk/Source/WebCore/html/HTMLAnchorElement.cpp 2021-02-25 09:02:56 UTC (rev 273479)
@@ -148,7 +148,8 @@
if (!document().frame()->eventHandler().tabsToLinks(event))
return false;
- if (!renderer() && ancestorsOfType<HTMLCanvasElement>(*this).first())
+ ASSERT(inclusiveAncestorStates().contains(AncestorState::Canvas) == !!ancestorsOfType<HTMLCanvasElement>(*this).first());
+ if (!renderer() && inclusiveAncestorStates().contains(AncestorState::Canvas))
return true;
return hasNonEmptyBox(renderBoxModelObject());
Modified: trunk/Source/WebCore/html/HTMLCanvasElement.cpp (273478 => 273479)
--- trunk/Source/WebCore/html/HTMLCanvasElement.cpp 2021-02-25 08:31:26 UTC (rev 273478)
+++ trunk/Source/WebCore/html/HTMLCanvasElement.cpp 2021-02-25 09:02:56 UTC (rev 273479)
@@ -128,6 +128,7 @@
, CanvasBase(IntSize(defaultWidth, defaultHeight))
, ActiveDOMObject(document)
{
+ addInclusiveAncestorState(AncestorState::Canvas);
ASSERT(hasTagName(canvasTag));
}
@@ -1049,7 +1050,10 @@
document.canvasChanged(*this, FloatRect { });
}
- return HTMLElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
+ HTMLElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
+ addInclusiveAncestorState(AncestorState::Canvas);
+
+ return InsertedIntoAncestorResult::Done;
}
void HTMLCanvasElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree)
@@ -1060,6 +1064,7 @@
}
HTMLElement::removedFromAncestor(removalType, oldParentOfRemovedTree);
+ addInclusiveAncestorState(AncestorState::Canvas);
}
bool HTMLCanvasElement::needsPreparationForDisplay()
Modified: trunk/Source/WebCore/html/HTMLFormElement.cpp (273478 => 273479)
--- trunk/Source/WebCore/html/HTMLFormElement.cpp 2021-02-25 08:31:26 UTC (rev 273478)
+++ trunk/Source/WebCore/html/HTMLFormElement.cpp 2021-02-25 09:02:56 UTC (rev 273479)
@@ -66,6 +66,7 @@
HTMLFormElement::HTMLFormElement(const QualifiedName& tagName, Document& document)
: HTMLElement(tagName, document)
{
+ addInclusiveAncestorState(AncestorState::Form);
ASSERT(hasTagName(formTag));
}
@@ -135,6 +136,7 @@
Node::InsertedIntoAncestorResult HTMLFormElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree)
{
HTMLElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
+ addInclusiveAncestorState(AncestorState::Form);
if (insertionType.connectedToDocument)
document().didAssociateFormControl(*this);
return InsertedIntoAncestorResult::Done;
@@ -147,6 +149,7 @@
for (auto& associatedElement : associatedElements)
associatedElement->formOwnerRemovedFromTree(root);
HTMLElement::removedFromAncestor(removalType, oldParentOfRemovedTree);
+ addInclusiveAncestorState(AncestorState::Form);
}
unsigned HTMLFormElement::length() const
@@ -892,8 +895,15 @@
HTMLElement::copyNonAttributePropertiesFromElement(source);
}
-HTMLFormElement* HTMLFormElement::findClosestFormAncestor(const Element& startElement)
+HTMLFormElement* HTMLFormElement::findClosestFormAncestorSlowCase(const Element& startElement)
{
+ if (UNLIKELY(is<HTMLFormElement>(startElement))) {
+ auto* parentElement = startElement.parentElement();
+ if (!parentElement || !parentElement->inclusiveAncestorStates().contains(AncestorState::Form)) {
+ ASSERT(!ancestorsOfType<HTMLFormElement>(startElement).first());
+ return nullptr;
+ }
+ }
return const_cast<HTMLFormElement*>(ancestorsOfType<HTMLFormElement>(startElement).first());
}
Modified: trunk/Source/WebCore/html/HTMLFormElement.h (273478 => 273479)
--- trunk/Source/WebCore/html/HTMLFormElement.h 2021-02-25 08:31:26 UTC (rev 273478)
+++ trunk/Source/WebCore/html/HTMLFormElement.h 2021-02-25 09:02:56 UTC (rev 273479)
@@ -26,6 +26,7 @@
#include "FormState.h"
#include "FormSubmission.h"
#include "HTMLElement.h"
+#include "HTMLNames.h"
#include "RadioButtonGroups.h"
#include <memory>
#include <wtf/IsoMalloc.h>
@@ -121,7 +122,16 @@
StringPairVector textFieldValues() const;
- static HTMLFormElement* findClosestFormAncestor(const Element&);
+ static HTMLFormElement* findClosestFormAncestor(const Element& element)
+ {
+ if (!element.inclusiveAncestorStates().contains(AncestorState::Form)) {
+ ASSERT(!findClosestFormAncestorSlowCase(element));
+ return nullptr;
+ }
+ auto* result = findClosestFormAncestorSlowCase(element);
+ ASSERT(result || element.tagQName() == HTMLNames::formTag);
+ return result;
+ }
private:
HTMLFormElement(const QualifiedName&, Document&);
@@ -164,6 +174,8 @@
void resetAssociatedFormControlElements();
+ static HTMLFormElement* findClosestFormAncestorSlowCase(const Element&);
+
FormSubmission::Attributes m_attributes;
HashMap<AtomString, WeakPtr<HTMLElement>> m_pastNamesMap;
Modified: trunk/Source/WebCore/html/HTMLImageElement.cpp (273478 => 273479)
--- trunk/Source/WebCore/html/HTMLImageElement.cpp 2021-02-25 08:31:26 UTC (rev 273478)
+++ trunk/Source/WebCore/html/HTMLImageElement.cpp 2021-02-25 09:02:56 UTC (rev 273479)
@@ -368,6 +368,10 @@
m_form = nullptr;
}
+ // Insert needs to complete first, before we start updating the loader. Loader dispatches events which could result
+ // in callbacks back to this node.
+ Node::InsertedIntoAncestorResult insertNotificationRequest = HTMLElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
+
if (!m_form) {
if (auto* newForm = HTMLFormElement::findClosestFormAncestor(*this)) {
m_form = makeWeakPtr(newForm);
@@ -375,10 +379,6 @@
}
}
- // Insert needs to complete first, before we start updating the loader. Loader dispatches events which could result
- // in callbacks back to this node.
- Node::InsertedIntoAncestorResult insertNotificationRequest = HTMLElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
-
if (insertionType.treeScopeChanged && !m_parsedUsemap.isNull())
treeScope().addImageElementByUsemap(*m_parsedUsemap.impl(), *this);
_______________________________________________ webkit-changes mailing list [email protected] https://lists.webkit.org/mailman/listinfo/webkit-changes
