tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit aa8f9a6919c757c4e89b683fecfd24ed6e91ae6c
parent 9cc6b78dc079e599d2f5f2ab9b4824045f4b3027
Author: Diego Escalante <descalante@mozilla.com>
Date:   Fri, 24 Oct 2025 19:34:37 +0000

Bug 1986743 - Add special invalidation processing for ImplicitScope dependencies. r=dshin,firefox-style-system-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D269264

Diffstat:
Mservo/components/style/invalidation/element/invalidator.rs | 134++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mservo/components/style/invalidation/element/state_and_attributes.rs | 21+++++++++++++++++++++
2 files changed, 110 insertions(+), 45 deletions(-)

diff --git a/servo/components/style/invalidation/element/invalidator.rs b/servo/components/style/invalidation/element/invalidator.rs @@ -289,7 +289,7 @@ pub struct Invalidation<'a> { /// This is useful for overriding invalidations we would otherwise skip. /// e.g @scope(.a){:not(:scope)} where we would need the :not(:scope) /// invalidation to traverse down for all children of the scope root - always_effective_for_next: bool, + always_effective_for_next_descendant: bool, } impl<'a> Invalidation<'a> { @@ -314,7 +314,7 @@ impl<'a> Invalidation<'a> { // + 1 to go past the combinator. offset: dependency.selector.len() + 1 - dependency.selector_offset, matched_by_any_previous: false, - always_effective_for_next: false, + always_effective_for_next_descendant: false, } } @@ -342,7 +342,31 @@ impl<'a> Invalidation<'a> { scope, offset: dependency.selector.len() - compound_offset, matched_by_any_previous: false, - always_effective_for_next: true, + always_effective_for_next_descendant: true, + } + } + + /// Create a new invalidation for matching a dependency that should always check + /// its next descendants. It tends to overinvalidate less than new_subject_invalidation + /// but it should also be avoided whenever possible. Specifically used when crossing + /// into implicit scope invalidation. + pub fn new_always_effective_for_next_descendant( + dependency: &'a Dependency, + host: Option<OpaqueElement>, + scope: Option<OpaqueElement>, + ) -> Self { + if dependency.selector_offset == 0 { + return Self::new_subject_invalidation(dependency, host, scope); + } + + Self { + dependency, + host, + scope, + // + 1 to go past the combinator. + offset: dependency.selector.len() + 1 - dependency.selector_offset, + matched_by_any_previous: false, + always_effective_for_next_descendant: true, } } @@ -359,7 +383,7 @@ impl<'a> Invalidation<'a> { /// Whether this invalidation is effective for the next sibling or /// descendant after us. fn effective_for_next(&self) -> bool { - if self.offset == 0 || self.always_effective_for_next { + if self.offset == 0 || self.always_effective_for_next_descendant { return true; } @@ -1082,6 +1106,21 @@ where if let DependencyInvalidationKind::Scope(scope_kind) = dependency.invalidation_kind() { + if scope_kind == ScopeDependencyInvalidationKind::ImplicitScope { + if let Some(ref deps) = dependency.next { + for dep in deps.as_ref().slice() { + let invalidation = + Invalidation::new_always_effective_for_next_descendant( + dep, + invalidation.host, + invalidation.scope, + ); + next_invalidations.push(invalidation); + } + } + continue; + } + let force_add = any_next_has_scope_in_negation(dependency); if scope_kind == ScopeDependencyInvalidationKind::ScopeEnd || force_add { let invalidations = note_scope_dependency_force_at_subject( @@ -1221,56 +1260,61 @@ where scope: invalidation.scope, offset: next_combinator_offset + 1, matched_by_any_previous: false, - always_effective_for_next: false, + always_effective_for_next_descendant: invalidation + .always_effective_for_next_descendant, }], ), }; for next_invalidation in next_invalidations { - debug_assert_ne!( - next_invalidation.offset, 0, - "Rightmost selectors shouldn't generate more invalidations", - ); + let next_invalidation_kind = if next_invalidation.always_effective_for_next_descendant { + InvalidationKind::Descendant(DescendantInvalidationKind::Dom) + } else { + debug_assert_ne!( + next_invalidation.offset, 0, + "Rightmost selectors shouldn't generate more invalidations", + ); - let next_combinator = next_invalidation - .dependency - .selector - .combinator_at_parse_order(next_invalidation.offset - 1); + let next_combinator = next_invalidation + .dependency + .selector + .combinator_at_parse_order(next_invalidation.offset - 1); - if matches!(next_combinator, Combinator::PseudoElement) - && self.processor.invalidates_on_pseudo_element() - { - // We need to invalidate the element whenever pseudos change, for - // two reasons: - // - // * Eager pseudo styles are stored as part of the originating - // element's computed style. - // - // * Lazy pseudo-styles might be cached on the originating - // element's pseudo-style cache. - // - // This could be more fine-grained (perhaps with a RESTYLE_PSEUDOS - // hint?). - // - // Note that we'll also restyle the pseudo-element because it would - // match this invalidation. - // - // FIXME: For non-element-backed pseudos this is still not quite - // correct. For example for ::selection even though we invalidate - // the style properly there's nothing that triggers a repaint - // necessarily. Though this matches old Gecko behavior, and the - // ::selection implementation needs to change significantly anyway - // to implement https://github.com/w3c/csswg-drafts/issues/2474 for - // example. - result.invalidated_self = true; - } + if matches!(next_combinator, Combinator::PseudoElement) + && self.processor.invalidates_on_pseudo_element() + { + // We need to invalidate the element whenever pseudos change, for + // two reasons: + // + // * Eager pseudo styles are stored as part of the originating + // element's computed style. + // + // * Lazy pseudo-styles might be cached on the originating + // element's pseudo-style cache. + // + // This could be more fine-grained (perhaps with a RESTYLE_PSEUDOS + // hint?). + // + // Note that we'll also restyle the pseudo-element because it would + // match this invalidation. + // + // FIXME: For non-element-backed pseudos this is still not quite + // correct. For example for ::selection even though we invalidate + // the style properly there's nothing that triggers a repaint + // necessarily. Though this matches old Gecko behavior, and the + // ::selection implementation needs to change significantly anyway + // to implement https://github.com/w3c/csswg-drafts/issues/2474 for + // example. + result.invalidated_self = true; + } - debug!( - " > Invalidation matched, next: {:?}, ({:?})", - next_invalidation, next_combinator - ); + debug!( + " > Invalidation matched, next: {:?}, ({:?})", + next_invalidation, next_combinator + ); - let next_invalidation_kind = next_invalidation.kind(); + next_invalidation.kind() + }; // We can skip pushing under some circumstances, and we should // because otherwise the invalidation list could grow diff --git a/servo/components/style/invalidation/element/state_and_attributes.rs b/servo/components/style/invalidation/element/state_and_attributes.rs @@ -586,6 +586,27 @@ where } if let DependencyInvalidationKind::Scope(scope_kind) = invalidation_kind { + if scope_kind == ScopeDependencyInvalidationKind::ImplicitScope { + if let Some(ref next) = dependency.next { + // When we reach an implicit scope dependency, we know there's an + // element matching that implicit scope somewhere in the descendant. + // We need to go find it so that we can continue the invalidation from + // its next dependencies. + for dep in next.as_ref().slice() { + let invalidation = Invalidation::new_always_effective_for_next_descendant( + dep, + self.matching_context.current_host.clone(), + self.matching_context.scope_element, + ); + + self.descendant_invalidations + .dom_descendants + .push(invalidation); + } + return; + } + } + if dependency.selector_offset == 0 { let force_add = any_next_has_scope_in_negation(dependency); if scope_kind == ScopeDependencyInvalidationKind::ScopeEnd || force_add {