Bug 1845744: Move selector-specific node flags to a separate field. r=emilio

Upcoming :has invalidation (Bug 1792501) requires 4 more flags, and we're out of space.
This change consumes the remaining 32-bit hole in `nsINode` to migrate selector-specific
node flags.
This has implications on 32-bit platforms, specifically on text nodes.

Differential Revision: https://phabricator.services.mozilla.com/D184718
This commit is contained in:
David Shin 2023-08-01 00:29:56 +00:00
parent 7998255fe4
commit ec30a6eea6
7 changed files with 139 additions and 107 deletions

View file

@ -231,7 +231,7 @@ ASSERT_NODE_SIZE(HTMLParagraphElement, 128, 80);
ASSERT_NODE_SIZE(HTMLPreElement, 128, 80); ASSERT_NODE_SIZE(HTMLPreElement, 128, 80);
ASSERT_NODE_SIZE(HTMLSpanElement, 128, 80); ASSERT_NODE_SIZE(HTMLSpanElement, 128, 80);
ASSERT_NODE_SIZE(HTMLTableCellElement, 128, 80); ASSERT_NODE_SIZE(HTMLTableCellElement, 128, 80);
ASSERT_NODE_SIZE(Text, 120, 64); ASSERT_NODE_SIZE(Text, 120, 80);
#undef ASSERT_NODE_SIZE #undef ASSERT_NODE_SIZE
#undef EXTRA_DOM_NODE_BYTES #undef EXTRA_DOM_NODE_BYTES
@ -3025,6 +3025,8 @@ void Element::List(FILE* out, int32_t aIndent, const nsCString& aPrefix) const {
fprintf(out, " state=[%llx]", fprintf(out, " state=[%llx]",
static_cast<unsigned long long>(State().GetInternalValue())); static_cast<unsigned long long>(State().GetInternalValue()));
fprintf(out, " flags=[%08x]", static_cast<unsigned int>(GetFlags())); fprintf(out, " flags=[%08x]", static_cast<unsigned int>(GetFlags()));
fprintf(out, " selectorflags=[%08x]",
static_cast<unsigned int>(GetSelectorFlags()));
if (IsClosestCommonInclusiveAncestorForRangeInSelection()) { if (IsClosestCommonInclusiveAncestorForRangeInSelection()) {
const LinkedList<AbstractRange>* ranges = const LinkedList<AbstractRange>* ranges =
GetExistingClosestCommonInclusiveAncestorRanges(); GetExistingClosestCommonInclusiveAncestorRanges();

View file

@ -143,74 +143,82 @@ enum : uint32_t {
// Whether the node participates in a shadow tree. // Whether the node participates in a shadow tree.
NODE_IS_IN_SHADOW_TREE = NODE_FLAG_BIT(5), NODE_IS_IN_SHADOW_TREE = NODE_FLAG_BIT(5),
// Node has an :empty or :-moz-only-whitespace selector
NODE_HAS_EMPTY_SELECTOR = NODE_FLAG_BIT(6),
// A child of the node has a selector such that any insertion,
// removal, or appending of children requires restyling the parent, if the
// parent is an element. If the parent is the shadow root, the child's
// siblings are restyled.
NODE_HAS_SLOW_SELECTOR = NODE_FLAG_BIT(7),
// A child of the node has a :first-child, :-moz-first-node,
// :only-child, :last-child or :-moz-last-node selector.
NODE_HAS_EDGE_CHILD_SELECTOR = NODE_FLAG_BIT(8),
// A child of the node has a selector such that any insertion or
// removal of children requires restyling later siblings of that
// element. Additionally (in this manner it is stronger than
// NODE_HAS_SLOW_SELECTOR), if a child's style changes due to any
// other content tree changes (e.g., the child changes to or from
// matching :empty due to a grandchild insertion or removal), the
// child's later siblings must also be restyled.
NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS = NODE_FLAG_BIT(9),
// A child of this node might be matched by :nth-child(.. of <selector>) or
// :nth-last-child(.. of <selector>). If a DOM mutation may have caused the
// selector to either match or no longer match that child, the child's
// siblings are restyled.
NODE_HAS_SLOW_SELECTOR_NTH_OF = NODE_FLAG_BIT(10),
// Set of selector flags that may trigger a restyle as a result of DOM
// mutation.
NODE_RESTYLE_SELECTOR_FLAGS =
NODE_HAS_EMPTY_SELECTOR | NODE_HAS_SLOW_SELECTOR |
NODE_HAS_EDGE_CHILD_SELECTOR | NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS |
NODE_HAS_SLOW_SELECTOR_NTH_OF,
// This node needs to go through frame construction to get a frame (or // This node needs to go through frame construction to get a frame (or
// undisplayed entry). // undisplayed entry).
NODE_NEEDS_FRAME = NODE_FLAG_BIT(11), NODE_NEEDS_FRAME = NODE_FLAG_BIT(6),
// At least one descendant in the flattened tree has NODE_NEEDS_FRAME set. // At least one descendant in the flattened tree has NODE_NEEDS_FRAME set.
// This should be set on every node on the flattened tree path between the // This should be set on every node on the flattened tree path between the
// node(s) with NODE_NEEDS_FRAME and the root content. // node(s) with NODE_NEEDS_FRAME and the root content.
NODE_DESCENDANTS_NEED_FRAMES = NODE_FLAG_BIT(12), NODE_DESCENDANTS_NEED_FRAMES = NODE_FLAG_BIT(7),
// Set if the node has the accesskey attribute set. // Set if the node has the accesskey attribute set.
NODE_HAS_ACCESSKEY = NODE_FLAG_BIT(13), NODE_HAS_ACCESSKEY = NODE_FLAG_BIT(8),
// Set if the node has right-to-left directionality // Set if the node has right-to-left directionality
NODE_HAS_DIRECTION_RTL = NODE_FLAG_BIT(14), NODE_HAS_DIRECTION_RTL = NODE_FLAG_BIT(9),
// Set if the node has left-to-right directionality // Set if the node has left-to-right directionality
NODE_HAS_DIRECTION_LTR = NODE_FLAG_BIT(15), NODE_HAS_DIRECTION_LTR = NODE_FLAG_BIT(10),
NODE_ALL_DIRECTION_FLAGS = NODE_HAS_DIRECTION_LTR | NODE_HAS_DIRECTION_RTL, NODE_ALL_DIRECTION_FLAGS = NODE_HAS_DIRECTION_LTR | NODE_HAS_DIRECTION_RTL,
NODE_HAS_BEEN_IN_UA_WIDGET = NODE_FLAG_BIT(16), NODE_HAS_BEEN_IN_UA_WIDGET = NODE_FLAG_BIT(11),
// Set if the node has a nonce value and a header delivered CSP. // Set if the node has a nonce value and a header delivered CSP.
NODE_HAS_NONCE_AND_HEADER_CSP = NODE_FLAG_BIT(17), NODE_HAS_NONCE_AND_HEADER_CSP = NODE_FLAG_BIT(12),
NODE_KEEPS_DOMARENA = NODE_FLAG_BIT(18), NODE_KEEPS_DOMARENA = NODE_FLAG_BIT(13),
NODE_MAY_HAVE_ELEMENT_CHILDREN = NODE_FLAG_BIT(19), NODE_MAY_HAVE_ELEMENT_CHILDREN = NODE_FLAG_BIT(14),
// Remaining bits are node type specific. // Remaining bits are node type specific.
NODE_TYPE_SPECIFIC_BITS_OFFSET = 20 NODE_TYPE_SPECIFIC_BITS_OFFSET = 15
}; };
// Flags for selectors that persist to the DOM node.
enum class NodeSelectorFlags : uint32_t {
// Node has an :empty or :-moz-only-whitespace selector
HasEmptySelector = 1 << 0,
/// A child of the node has a selector such that any insertion,
/// removal, or appending of children requires restyling the parent, if the
/// parent is an element. If the parent is the shadow root, the child's
/// siblings are restyled.
HasSlowSelector = 1 << 1,
/// A child of the node has a :first-child, :-moz-first-node,
/// :only-child, :last-child or :-moz-last-node selector.
HasEdgeChildSelector = 1 << 2,
/// A child of the node has a selector such that any insertion or
/// removal of children requires restyling later siblings of that
/// element. Additionally (in this manner it is stronger than
/// NODE_HAS_SLOW_SELECTOR), if a child's style changes due to any
/// other content tree changes (e.g., the child changes to or from
/// matching :empty due to a grandchild insertion or removal), the
/// child's later siblings must also be restyled.
HasSlowSelectorLaterSiblings = 1 << 3,
/// A child of this node might be matched by :nth-child(.. of <selector>) or
/// :nth-last-child(.. of <selector>). If a DOM mutation may have caused the
/// selector to either match or no longer match that child, the child's
/// siblings are restyled.
HasSlowSelectorNthOf = 1 << 4,
/// Set of selector flags that may trigger a restyle on DOM append, with
/// restyle on siblings or a single parent (And perhaps their subtrees).
AllSimpleRestyleFlagsForAppend = HasEmptySelector | HasSlowSelector |
HasEdgeChildSelector | HasSlowSelectorNthOf,
/// Set of selector flags that may trigger a restyle as a result of any
/// DOM mutation.
AllSimpleRestyleFlags =
AllSimpleRestyleFlagsForAppend | HasSlowSelectorLaterSiblings,
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(NodeSelectorFlags);
// Make sure we have space for our bits // Make sure we have space for our bits
#define ASSERT_NODE_FLAGS_SPACE(n) \ #define ASSERT_NODE_FLAGS_SPACE(n) \
static_assert(WRAPPER_CACHE_FLAGS_BITS_USED + (n) <= \ static_assert(WRAPPER_CACHE_FLAGS_BITS_USED + (n) <= \
@ -2317,6 +2325,10 @@ class nsINode : public mozilla::dom::EventTarget {
#undef TOUCH_EVENT #undef TOUCH_EVENT
#undef EVENT #undef EVENT
NodeSelectorFlags GetSelectorFlags() const {
return static_cast<NodeSelectorFlags>(mSelectorFlags.Get());
}
protected: protected:
static bool Traverse(nsINode* tmp, nsCycleCollectionTraversalCallback& cb); static bool Traverse(nsINode* tmp, nsCycleCollectionTraversalCallback& cb);
static void Unlink(nsINode* tmp); static void Unlink(nsINode* tmp);
@ -2334,7 +2346,7 @@ class nsINode : public mozilla::dom::EventTarget {
uint32_t mBoolFlags; uint32_t mBoolFlags;
#endif #endif
// NOTE, there are 32 bits left here, at least in 64 bit builds. mozilla::RustCell<uint32_t> mSelectorFlags{0};
uint32_t mChildCount; uint32_t mChildCount;

View file

@ -40,7 +40,10 @@ class nsWindowRoot;
// Both sets of flags are 32 bits. On 64-bit platforms, this can cause two // Both sets of flags are 32 bits. On 64-bit platforms, this can cause two
// wasted 32-bit fields due to alignment requirements. Some compilers are // wasted 32-bit fields due to alignment requirements. Some compilers are
// smart enough to coalesce the fields if we make mBoolFlags the first member // smart enough to coalesce the fields if we make mBoolFlags the first member
// of nsINode, but others (such as MSVC) are not. // of nsINode, but others (such as MSVC, but that's not officially supported
// by us anymore) are not. Also note that this kind of coalascing tends to
// interact poorly with rust's bindgen, see:
// https://github.com/rust-lang/rust-bindgen/issues/380
// //
// So we just store mBoolFlags directly on nsWrapperCache on 64-bit platforms. // So we just store mBoolFlags directly on nsWrapperCache on 64-bit platforms.
// This may waste space for some other nsWrapperCache-derived objects that have // This may waste space for some other nsWrapperCache-derived objects that have

View file

@ -94,17 +94,16 @@ void RestyleManager::ContentAppended(nsIContent* aFirstNewContent) {
} }
} }
#endif #endif
uint32_t selectorFlags = const auto selectorFlags = container->GetSelectorFlags() &
container->GetFlags() & NodeSelectorFlags::AllSimpleRestyleFlagsForAppend;
(NODE_RESTYLE_SELECTOR_FLAGS & ~NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS); if (!selectorFlags) {
if (selectorFlags == 0) {
return; return;
} }
// The container cannot be a document. // The container cannot be a document.
MOZ_ASSERT(container->IsElement() || container->IsShadowRoot()); MOZ_ASSERT(container->IsElement() || container->IsShadowRoot());
if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) { if (selectorFlags & NodeSelectorFlags::HasEmptySelector) {
// see whether we need to restyle the container // see whether we need to restyle the container
bool wasEmpty = true; // :empty or :-moz-only-whitespace bool wasEmpty = true; // :empty or :-moz-only-whitespace
for (nsIContent* cur = container->GetFirstChild(); cur != aFirstNewContent; for (nsIContent* cur = container->GetFirstChild(); cur != aFirstNewContent;
@ -124,7 +123,7 @@ void RestyleManager::ContentAppended(nsIContent* aFirstNewContent) {
} }
} }
if (selectorFlags & NODE_HAS_SLOW_SELECTOR) { if (selectorFlags & NodeSelectorFlags::HasSlowSelector) {
if (container->IsElement()) { if (container->IsElement()) {
PostRestyleEvent(container->AsElement(), RestyleHint::RestyleSubtree(), PostRestyleEvent(container->AsElement(), RestyleHint::RestyleSubtree(),
nsChangeHint(0)); nsChangeHint(0));
@ -136,7 +135,7 @@ void RestyleManager::ContentAppended(nsIContent* aFirstNewContent) {
return; return;
} }
if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) { if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
// restyle the last element child before this node // restyle the last element child before this node
for (nsIContent* cur = aFirstNewContent->GetPreviousSibling(); cur; for (nsIContent* cur = aFirstNewContent->GetPreviousSibling(); cur;
cur = cur->GetPreviousSibling()) { cur = cur->GetPreviousSibling()) {
@ -173,8 +172,8 @@ void RestyleManager::RestyleForEmptyChange(Element* aContainer) {
// In some cases (:empty + E, :empty ~ E), a change in the content of // In some cases (:empty + E, :empty ~ E), a change in the content of
// an element requires restyling its parent's siblings. // an element requires restyling its parent's siblings.
nsIContent* grandparent = aContainer->GetParent(); nsIContent* grandparent = aContainer->GetParent();
if (!grandparent || if (!grandparent || !(grandparent->GetSelectorFlags() &
!(grandparent->GetFlags() & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS)) { NodeSelectorFlags::HasSlowSelectorLaterSiblings)) {
return; return;
} }
RestyleSiblingsStartingWith(aContainer->GetNextSibling()); RestyleSiblingsStartingWith(aContainer->GetNextSibling());
@ -182,7 +181,8 @@ void RestyleManager::RestyleForEmptyChange(Element* aContainer) {
void RestyleManager::MaybeRestyleForEdgeChildChange(nsINode* aContainer, void RestyleManager::MaybeRestyleForEdgeChildChange(nsINode* aContainer,
nsIContent* aChangedChild) { nsIContent* aChangedChild) {
MOZ_ASSERT(aContainer->GetFlags() & NODE_HAS_EDGE_CHILD_SELECTOR); MOZ_ASSERT(aContainer->GetSelectorFlags() &
NodeSelectorFlags::HasEdgeChildSelector);
MOZ_ASSERT(aChangedChild->GetParent() == aContainer); MOZ_ASSERT(aChangedChild->GetParent() == aContainer);
// restyle the previously-first element child if it is after this node // restyle the previously-first element child if it is after this node
bool passedChild = false; bool passedChild = false;
@ -264,9 +264,10 @@ void RestyleManager::CharacterDataChanged(
nsINode* parent = aContent->GetParentNode(); nsINode* parent = aContent->GetParentNode();
MOZ_ASSERT(parent, "How were we notified of a stray node?"); MOZ_ASSERT(parent, "How were we notified of a stray node?");
uint32_t slowSelectorFlags = parent->GetFlags() & NODE_RESTYLE_SELECTOR_FLAGS; const auto slowSelectorFlags =
if (!(slowSelectorFlags & parent->GetSelectorFlags() & NodeSelectorFlags::AllSimpleRestyleFlags;
(NODE_HAS_EMPTY_SELECTOR | NODE_HAS_EDGE_CHILD_SELECTOR))) { if (!(slowSelectorFlags & (NodeSelectorFlags::HasEmptySelector |
NodeSelectorFlags::HasEdgeChildSelector))) {
// Nothing to do, no other slow selector can change as a result of this. // Nothing to do, no other slow selector can change as a result of this.
return; return;
} }
@ -321,7 +322,7 @@ void RestyleManager::CharacterDataChanged(
return; return;
} }
if (slowSelectorFlags & NODE_HAS_EMPTY_SELECTOR) { if (slowSelectorFlags & NodeSelectorFlags::HasEmptySelector) {
if (!HasAnySignificantSibling(parent->AsElement(), aContent)) { if (!HasAnySignificantSibling(parent->AsElement(), aContent)) {
// We used to be empty, restyle the parent. // We used to be empty, restyle the parent.
RestyleForEmptyChange(parent->AsElement()); RestyleForEmptyChange(parent->AsElement());
@ -329,7 +330,7 @@ void RestyleManager::CharacterDataChanged(
} }
} }
if (slowSelectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) { if (slowSelectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
MaybeRestyleForEdgeChildChange(parent, aContent); MaybeRestyleForEdgeChildChange(parent, aContent);
} }
} }
@ -344,8 +345,9 @@ void RestyleManager::RestyleForInsertOrChange(nsIContent* aChild) {
nsINode* container = aChild->GetParentNode(); nsINode* container = aChild->GetParentNode();
MOZ_ASSERT(container); MOZ_ASSERT(container);
uint32_t selectorFlags = container->GetFlags() & NODE_RESTYLE_SELECTOR_FLAGS; const auto selectorFlags =
if (selectorFlags == 0) { container->GetSelectorFlags() & NodeSelectorFlags::AllSimpleRestyleFlags;
if (!selectorFlags) {
return; return;
} }
@ -355,7 +357,8 @@ void RestyleManager::RestyleForInsertOrChange(nsIContent* aChild) {
// The container cannot be a document. // The container cannot be a document.
MOZ_ASSERT(container->IsElement() || container->IsShadowRoot()); MOZ_ASSERT(container->IsElement() || container->IsShadowRoot());
if (selectorFlags & NODE_HAS_EMPTY_SELECTOR && container->IsElement()) { if (selectorFlags & NodeSelectorFlags::HasEmptySelector &&
container->IsElement()) {
// See whether we need to restyle the container due to :empty / // See whether we need to restyle the container due to :empty /
// :-moz-only-whitespace. // :-moz-only-whitespace.
const bool wasEmpty = const bool wasEmpty =
@ -369,7 +372,7 @@ void RestyleManager::RestyleForInsertOrChange(nsIContent* aChild) {
} }
} }
if (selectorFlags & NODE_HAS_SLOW_SELECTOR) { if (selectorFlags & NodeSelectorFlags::HasSlowSelector) {
if (container->IsElement()) { if (container->IsElement()) {
PostRestyleEvent(container->AsElement(), RestyleHint::RestyleSubtree(), PostRestyleEvent(container->AsElement(), RestyleHint::RestyleSubtree(),
nsChangeHint(0)); nsChangeHint(0));
@ -381,12 +384,12 @@ void RestyleManager::RestyleForInsertOrChange(nsIContent* aChild) {
return; return;
} }
if (selectorFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) { if (selectorFlags & NodeSelectorFlags::HasSlowSelectorLaterSiblings) {
// Restyle all later siblings. // Restyle all later siblings.
RestyleSiblingsStartingWith(aChild->GetNextSibling()); RestyleSiblingsStartingWith(aChild->GetNextSibling());
} }
if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) { if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
MaybeRestyleForEdgeChildChange(container, aChild); MaybeRestyleForEdgeChildChange(container, aChild);
} }
} }
@ -408,8 +411,9 @@ void RestyleManager::ContentRemoved(nsIContent* aOldChild,
IncrementUndisplayedRestyleGeneration(); IncrementUndisplayedRestyleGeneration();
} }
uint32_t selectorFlags = container->GetFlags() & NODE_RESTYLE_SELECTOR_FLAGS; const auto selectorFlags =
if (selectorFlags == 0) { container->GetSelectorFlags() & NodeSelectorFlags::AllSimpleRestyleFlags;
if (!selectorFlags) {
return; return;
} }
@ -424,7 +428,8 @@ void RestyleManager::ContentRemoved(nsIContent* aOldChild,
// The container cannot be a document. // The container cannot be a document.
MOZ_ASSERT(container->IsElement() || container->IsShadowRoot()); MOZ_ASSERT(container->IsElement() || container->IsShadowRoot());
if (selectorFlags & NODE_HAS_EMPTY_SELECTOR && container->IsElement()) { if (selectorFlags & NodeSelectorFlags::HasEmptySelector &&
container->IsElement()) {
// see whether we need to restyle the container // see whether we need to restyle the container
bool isEmpty = true; // :empty or :-moz-only-whitespace bool isEmpty = true; // :empty or :-moz-only-whitespace
for (nsIContent* child = container->GetFirstChild(); child; for (nsIContent* child = container->GetFirstChild(); child;
@ -444,7 +449,7 @@ void RestyleManager::ContentRemoved(nsIContent* aOldChild,
} }
} }
if (selectorFlags & NODE_HAS_SLOW_SELECTOR) { if (selectorFlags & NodeSelectorFlags::HasSlowSelector) {
if (container->IsElement()) { if (container->IsElement()) {
PostRestyleEvent(container->AsElement(), RestyleHint::RestyleSubtree(), PostRestyleEvent(container->AsElement(), RestyleHint::RestyleSubtree(),
nsChangeHint(0)); nsChangeHint(0));
@ -456,12 +461,12 @@ void RestyleManager::ContentRemoved(nsIContent* aOldChild,
return; return;
} }
if (selectorFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) { if (selectorFlags & NodeSelectorFlags::HasSlowSelectorLaterSiblings) {
// Restyle all later siblings. // Restyle all later siblings.
RestyleSiblingsStartingWith(aFollowingSibling); RestyleSiblingsStartingWith(aFollowingSibling);
} }
if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) { if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
// restyle the now-first element child if it was after aOldChild // restyle the now-first element child if it was after aOldChild
bool reachedFollowingSibling = false; bool reachedFollowingSibling = false;
for (nsIContent* content = container->GetFirstChild(); content; for (nsIContent* content = container->GetFirstChild(); content;
@ -3355,8 +3360,8 @@ void RestyleManager::MaybeRestyleForNthOfState(ServoStyleSet& aStyleSet,
ElementState aChangedBits) { ElementState aChangedBits) {
const auto* parentNode = aChild->GetParentNode(); const auto* parentNode = aChild->GetParentNode();
MOZ_ASSERT(parentNode); MOZ_ASSERT(parentNode);
const auto parentFlags = parentNode->GetFlags(); const auto parentFlags = parentNode->GetSelectorFlags();
if (!(parentFlags & NODE_HAS_SLOW_SELECTOR_NTH_OF)) { if (!(parentFlags & NodeSelectorFlags::HasSlowSelectorNthOf)) {
return; return;
} }
@ -3535,20 +3540,21 @@ void RestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
} }
} }
void RestyleManager::RestyleSiblings( void RestyleManager::RestyleSiblings(Element* aChild,
Element* aChild, nsBaseContentList::FlagsType aParentFlags) { NodeSelectorFlags aParentFlags) {
const DebugOnly<nsINode*> parentNode = aChild->GetParentNode(); const DebugOnly<nsINode*> parentNode = aChild->GetParentNode();
MOZ_ASSERT(parentNode->IsElement() || parentNode->IsShadowRoot()); MOZ_ASSERT(parentNode->IsElement() || parentNode->IsShadowRoot());
DebugOnly<bool> restyledSiblings = false; DebugOnly<bool> restyledSiblings = false;
// NODE_HAS_SLOW_SELECTOR typically indicates restyling the parent, but since // NodeSelectorFlags::HasSlowSelector typically indicates restyling the
// we know we're restyling for :nth-last-child(.. of <selector>), we can // parent, but since we know we're restyling for :nth-last-child(.. of
// restyle only previous siblings without under-invalidating. // <selector>), we can restyle only previous siblings without
if (aParentFlags & NODE_HAS_SLOW_SELECTOR) { // under-invalidating.
if (aParentFlags & NodeSelectorFlags::HasSlowSelector) {
RestylePreviousSiblings(aChild->GetPreviousSibling()); RestylePreviousSiblings(aChild->GetPreviousSibling());
restyledSiblings = true; restyledSiblings = true;
} }
if (aParentFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) { if (aParentFlags & NodeSelectorFlags::HasSlowSelectorLaterSiblings) {
RestyleSiblingsStartingWith(aChild->GetNextSibling()); RestyleSiblingsStartingWith(aChild->GetNextSibling());
restyledSiblings = true; restyledSiblings = true;
} }
@ -3560,8 +3566,8 @@ void RestyleManager::MaybeRestyleForNthOfAttribute(
Element* aChild, nsAtom* aAttribute, const nsAttrValue* aOldValue) { Element* aChild, nsAtom* aAttribute, const nsAttrValue* aOldValue) {
const auto* parentNode = aChild->GetParentNode(); const auto* parentNode = aChild->GetParentNode();
MOZ_ASSERT(parentNode); MOZ_ASSERT(parentNode);
const auto parentFlags = parentNode->GetFlags(); const auto parentFlags = parentNode->GetSelectorFlags();
if (!(parentFlags & NODE_HAS_SLOW_SELECTOR_NTH_OF)) { if (!(parentFlags & NodeSelectorFlags::HasSlowSelectorNthOf)) {
return; return;
} }
if (!aChild->HasServoData()) { if (!aChild->HasServoData()) {

View file

@ -379,8 +379,7 @@ class RestyleManager {
/** /**
* Restyle an element's previous and/or next siblings. * Restyle an element's previous and/or next siblings.
*/ */
void RestyleSiblings(dom::Element* aChild, void RestyleSiblings(dom::Element* aChild, NodeSelectorFlags aParentFlags);
nsBaseContentList::FlagsType aParentFlags);
/** /**
* Posts restyle hints for siblings of an element and their descendants if the * Posts restyle hints for siblings of an element and their descendants if the

View file

@ -58,6 +58,7 @@ hide-types = [
bitfield-enums = [ bitfield-enums = [
"nsChangeHint", "nsChangeHint",
"mozilla::OriginFlags", "mozilla::OriginFlags",
"NodeSelectorFlags",
] ]
rusty-enums = [ rusty-enums = [
"nsCompatibility", "nsCompatibility",
@ -354,6 +355,7 @@ allowlist-types = [
"mozilla::dom::MediaList", "mozilla::dom::MediaList",
"mozilla::StyleRuleInclusion", "mozilla::StyleRuleInclusion",
"nsStyleTransformMatrix::MatrixTransformOperator", "nsStyleTransformMatrix::MatrixTransformOperator",
"NodeSelectorFlags",
] ]
opaque-types = [ opaque-types = [
"mozilla::StyleThinArc", # https://github.com/rust-lang/rust-bindgen/issues/1557 "mozilla::StyleThinArc", # https://github.com/rust-lang/rust-bindgen/issues/1557

View file

@ -76,6 +76,7 @@ use selectors::matching::{ElementSelectorFlags, MatchingContext};
use selectors::sink::Push; use selectors::sink::Push;
use selectors::{Element, OpaqueElement}; use selectors::{Element, OpaqueElement};
use servo_arc::{Arc, ArcBorrow}; use servo_arc::{Arc, ArcBorrow};
use std::cell::Cell;
use std::fmt; use std::fmt;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::mem; use std::mem;
@ -270,17 +271,9 @@ impl<'ln> GeckoNode<'ln> {
self.flags_atomic().fetch_or(flags, Ordering::Relaxed); self.flags_atomic().fetch_or(flags, Ordering::Relaxed);
} }
#[inline] fn flags_atomic_for(flags: &Cell<u32>) -> &AtomicU32 {
fn flags_atomic(&self) -> &AtomicU32 { const_assert!(std::mem::size_of::<Cell<u32>>() == std::mem::size_of::<AtomicU32>());
use std::cell::Cell; const_assert!(std::mem::align_of::<Cell<u32>>() == std::mem::align_of::<AtomicU32>());
let flags: &Cell<u32> = &(self.0)._base._base_1.mFlags;
#[allow(dead_code)]
fn static_assert() {
let _: [u8; std::mem::size_of::<Cell<u32>>()] = [0u8; std::mem::size_of::<AtomicU32>()];
let _: [u8; std::mem::align_of::<Cell<u32>>()] =
[0u8; std::mem::align_of::<AtomicU32>()];
}
// Rust doesn't provide standalone atomic functions like GCC/clang do // Rust doesn't provide standalone atomic functions like GCC/clang do
// (via the atomic intrinsics) or via std::atomic_ref, but it guarantees // (via the atomic intrinsics) or via std::atomic_ref, but it guarantees
@ -289,11 +282,26 @@ impl<'ln> GeckoNode<'ln> {
unsafe { std::mem::transmute::<&Cell<u32>, &AtomicU32>(flags) } unsafe { std::mem::transmute::<&Cell<u32>, &AtomicU32>(flags) }
} }
#[inline]
fn flags_atomic(&self) -> &AtomicU32 {
Self::flags_atomic_for(&self.0._base._base_1.mFlags)
}
#[inline] #[inline]
fn flags(&self) -> u32 { fn flags(&self) -> u32 {
self.flags_atomic().load(Ordering::Relaxed) self.flags_atomic().load(Ordering::Relaxed)
} }
#[inline]
fn selector_flags_atomic(&self) -> &AtomicU32 {
Self::flags_atomic_for(&self.0.mSelectorFlags)
}
#[inline]
fn set_selector_flags(&self, flags: u32) {
self.selector_flags_atomic().fetch_or(flags, Ordering::Relaxed);
}
#[inline] #[inline]
fn node_info(&self) -> &structs::NodeInfo { fn node_info(&self) -> &structs::NodeInfo {
debug_assert!(!self.0.mNodeInfo.mRawPtr.is_null()); debug_assert!(!self.0.mNodeInfo.mRawPtr.is_null());
@ -859,22 +867,22 @@ impl<'le> GeckoElement<'le> {
/// by Gecko. We could align these and then do this without conditionals, but /// by Gecko. We could align these and then do this without conditionals, but
/// it's probably not worth the trouble. /// it's probably not worth the trouble.
fn selector_flags_to_node_flags(flags: ElementSelectorFlags) -> u32 { fn selector_flags_to_node_flags(flags: ElementSelectorFlags) -> u32 {
use crate::gecko_bindings::structs::*; use crate::gecko_bindings::structs::NodeSelectorFlags;
let mut gecko_flags = 0u32; let mut gecko_flags = 0u32;
if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR) { if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR) {
gecko_flags |= NODE_HAS_SLOW_SELECTOR; gecko_flags |= NodeSelectorFlags::HasSlowSelector.0;
} }
if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS) { if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
gecko_flags |= NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS; gecko_flags |= NodeSelectorFlags::HasSlowSelectorLaterSiblings.0;
} }
if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR_NTH_OF) { if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR_NTH_OF) {
gecko_flags |= NODE_HAS_SLOW_SELECTOR_NTH_OF; gecko_flags |= NodeSelectorFlags::HasSlowSelectorNthOf.0;
} }
if flags.contains(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR) { if flags.contains(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR) {
gecko_flags |= NODE_HAS_EDGE_CHILD_SELECTOR; gecko_flags |= NodeSelectorFlags::HasEdgeChildSelector.0;
} }
if flags.contains(ElementSelectorFlags::HAS_EMPTY_SELECTOR) { if flags.contains(ElementSelectorFlags::HAS_EMPTY_SELECTOR) {
gecko_flags |= NODE_HAS_EMPTY_SELECTOR; gecko_flags |= NodeSelectorFlags::HasEmptySelector.0;
} }
gecko_flags gecko_flags
@ -1810,7 +1818,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
// Handle flags that apply to the element. // Handle flags that apply to the element.
let self_flags = flags.for_self(); let self_flags = flags.for_self();
if !self_flags.is_empty() { if !self_flags.is_empty() {
self.set_flags(selector_flags_to_node_flags(flags)) self.as_node().set_selector_flags(selector_flags_to_node_flags(flags))
} }
// Handle flags that apply to the parent. // Handle flags that apply to the parent.
@ -1818,7 +1826,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
if !parent_flags.is_empty() { if !parent_flags.is_empty() {
if let Some(p) = self.as_node().parent_node() { if let Some(p) = self.as_node().parent_node() {
if p.is_element() || p.is_shadow_root() { if p.is_element() || p.is_shadow_root() {
p.set_flags(selector_flags_to_node_flags(parent_flags)); p.set_selector_flags(selector_flags_to_node_flags(parent_flags));
} }
} }
} }