commit 7ab4d7f6dba998ac72e13394dd4ff8c0054674dc
parent 7ece99560bbf651a4b77e740ccc1bb6affd659c6
Author: Diego Escalante <descalante@mozilla.com>
Date: Wed, 22 Oct 2025 18:49:44 +0000
Bug 1966704 - Add support for @scope invalidations with relative selectors. r=dshin,firefox-style-system-reviewers
Also implement drive-by fix for NegationScopeVisitor.
Differential Revision: https://phabricator.services.mozilla.com/D268952
Diffstat:
2 files changed, 53 insertions(+), 16 deletions(-)
diff --git a/servo/components/style/invalidation/element/invalidator.rs b/servo/components/style/invalidation/element/invalidator.rs
@@ -440,7 +440,8 @@ impl NegationScopeVisitor {
DependencyInvalidationKind::Normal(..)
)
{
- return dependency.selector.visit(&mut self);
+ dependency.selector.visit(&mut self);
+ return self.found_scope_in_negation;
}
let nested_visitor = Self {
diff --git a/servo/components/style/invalidation/element/relative_selector.rs b/servo/components/style/invalidation/element/relative_selector.rs
@@ -12,11 +12,12 @@ use crate::invalidation::element::element_wrapper::ElementWrapper;
use crate::invalidation::element::invalidation_map::{
AdditionalRelativeSelectorInvalidationMap, Dependency, DependencyInvalidationKind,
InvalidationMap, NormalDependencyInvalidationKind, RelativeDependencyInvalidationKind,
- TSStateForInvalidation,
+ ScopeDependencyInvalidationKind, TSStateForInvalidation,
};
use crate::invalidation::element::invalidator::{
- DescendantInvalidationLists, Invalidation, InvalidationProcessor, InvalidationResult,
- InvalidationVector, SiblingTraversalMap, TreeStyleInvalidator,
+ note_scope_dependency_force_at_subject, DescendantInvalidationLists, Invalidation,
+ InvalidationProcessor, InvalidationResult, InvalidationVector, SiblingTraversalMap,
+ TreeStyleInvalidator,
};
use crate::invalidation::element::restyle_hints::RestyleHint;
use crate::invalidation::element::state_and_attributes::{
@@ -35,7 +36,7 @@ use selectors::matching::{
};
use selectors::parser::SelectorKey;
use selectors::OpaqueElement;
-use smallvec::SmallVec;
+use smallvec::{smallvec, SmallVec};
use std::ops::DerefMut;
/// Kind of DOM mutation this relative selector invalidation is being carried out in.
@@ -1010,9 +1011,17 @@ where
);
if let Some(x) = outer_dependency.next.as_ref() {
- if !Self::is_subject(&x.as_ref().slice()[0]) {
- // Not subject in outer selector.
- return false;
+ // We only care to ensure that we're the subject in the outermost selector of the current
+ // selector - Crossing over a scope invalidation would mean moving into a selector inside
+ // the current scope block
+ if matches!(
+ outer_dependency.invalidation_kind(),
+ DependencyInvalidationKind::Normal(..)
+ ) {
+ if !Self::is_subject(&x.as_ref().slice()[0]) {
+ // Not subject in outer selector.
+ return false;
+ }
}
}
outer_dependency
@@ -1086,8 +1095,10 @@ where
// e.g. Element under consideration can only be the anchor to `:has` in
// `.foo .bar ~ .baz:has()`, iff it matches `.foo .bar ~ .baz`.
let invalidated_self = {
- let mut d = self.dependency;
- loop {
+ let mut invalidated = false;
+ let mut dependencies_to_invalidate: SmallVec<[&Dependency; 1]> =
+ smallvec![self.dependency];
+ while let Some(d) = dependencies_to_invalidate.pop() {
debug_assert!(
matches!(
d.invalidation_kind(),
@@ -1097,7 +1108,7 @@ where
"Unexpected dependency kind"
);
if !dependency_may_be_relevant(d, &element, false) {
- break false;
+ continue;
}
if !matches_selector(
&d.selector,
@@ -1106,30 +1117,55 @@ where
&element,
self.matching_context(),
) {
- break false;
+ continue;
}
+
let invalidation_kind = d.invalidation_kind();
+
+ if let DependencyInvalidationKind::Scope(scope_kind) = invalidation_kind {
+ if d.selector_offset == 0 {
+ if scope_kind == ScopeDependencyInvalidationKind::ScopeEnd {
+ let invalidations = note_scope_dependency_force_at_subject(
+ d,
+ self.matching_context.current_host.clone(),
+ self.matching_context.scope_element,
+ false,
+ );
+ descendant_invalidations
+ .dom_descendants
+ .extend(invalidations);
+
+ invalidated |= true;
+ } else if let Some(ref next) = d.next {
+ dependencies_to_invalidate.extend(next.as_ref().slice());
+ }
+ continue;
+ }
+ }
+
if matches!(
invalidation_kind,
DependencyInvalidationKind::Normal(NormalDependencyInvalidationKind::Element)
- | DependencyInvalidationKind::Scope(_)
) {
if let Some(ref deps) = d.next {
- d = &deps.as_ref().slice()[0];
+ // Normal dependencies should only have one next
+ dependencies_to_invalidate.push(&deps.as_ref().slice()[0]);
continue;
}
- break true;
+ invalidated |= true;
+ continue;
}
debug_assert_ne!(d.selector_offset, 0);
debug_assert_ne!(d.selector_offset, d.selector.len());
let invalidation = Invalidation::new(&d, self.host, None);
- break push_invalidation(
+ invalidated |= push_invalidation(
invalidation,
d.invalidation_kind(),
descendant_invalidations,
sibling_invalidations,
);
}
+ invalidated
};
if invalidated_self {