From f3304f0ef866df798877c18fde9da8fc72c358e4 Mon Sep 17 00:00:00 2001 From: David Shin Date: Mon, 15 Jul 2024 18:41:14 +0000 Subject: [PATCH] Bug 1889109: Part 5 - Add scope subject map and use it to early-reject elements that can't be scopes. r=firefox-style-system-reviewers,emilio Differential Revision: https://phabricator.services.mozilla.com/D212729 --- .../style/stylesheets/scope_rule.rs | 117 +++++++++++++++++- servo/components/style/stylist.rs | 16 ++- 2 files changed, 126 insertions(+), 7 deletions(-) diff --git a/servo/components/style/stylesheets/scope_rule.rs b/servo/components/style/stylesheets/scope_rule.rs index 65270b1006a8..c4815ff97344 100644 --- a/servo/components/style/stylesheets/scope_rule.rs +++ b/servo/components/style/stylesheets/scope_rule.rs @@ -15,14 +15,15 @@ use crate::shared_lock::{ }; use crate::str::CssStringWriter; use crate::stylesheets::CssRules; +use crate::simple_buckets_map::SimpleBucketsMap; use cssparser::{Parser, SourceLocation, ToCss}; #[cfg(feature = "gecko")] use malloc_size_of::{ MallocSizeOfOps, MallocUnconditionalShallowSizeOf, MallocUnconditionalSizeOf, }; -use selectors::context::MatchingContext; +use selectors::context::{MatchingContext, QuirksMode}; use selectors::matching::matches_selector; -use selectors::parser::{ParseRelative, Selector, SelectorList}; +use selectors::parser::{Component, ParseRelative, Selector, SelectorList}; use selectors::OpaqueElement; use servo_arc::Arc; use std::fmt::{self, Write}; @@ -206,15 +207,19 @@ pub enum ScopeTarget<'a> { impl<'a> ScopeTarget<'a> { /// Check if the given element is the scope. - pub fn check( + fn check( &self, element: E, scope: Option, + scope_subject_map: &ScopeSubjectMap, context: &mut MatchingContext, ) -> bool { match self { Self::Selector(list) => { context.nest_for_scope_condition(scope, |context| { + if scope_subject_map.early_reject(element, context.quirks_mode()) { + return false; + } for selector in list.slice().iter() { if matches_selector(selector, 0, None, &element, context) { return true; @@ -245,6 +250,7 @@ pub fn collect_scope_roots( context: &mut MatchingContext, target: &ScopeTarget, matches_shadow_host: bool, + scope_subject_map: &ScopeSubjectMap, ) -> Vec where E: TElement, @@ -256,7 +262,7 @@ where if ceiling == Some(p.opaque()) { break; } - if target.check(p, ceiling, context) { + if target.check(p, ceiling, scope_subject_map, context) { result.push(ScopeRootCandidate { root: p.opaque(), proximity: ScopeProximity::new(proximity), @@ -308,3 +314,106 @@ where return false; }) } + +/// A map containing simple selectors in subjects of scope selectors. +/// This allows fast-rejecting scopes before running the full match. +#[derive(Clone, Debug, Default, MallocSizeOf)] +pub struct ScopeSubjectMap { + buckets: SimpleBucketsMap<()>, + any: bool, +} + +impl ScopeSubjectMap { + /// Add the `` of a scope. + pub fn add_bound_start(&mut self, selectors: &SelectorList, quirks_mode: QuirksMode) { + if self.add_selector_list(selectors, quirks_mode) { + self.any = true; + } + } + + fn add_selector_list(&mut self, selectors: &SelectorList, quirks_mode: QuirksMode) -> bool { + let mut is_any = false; + for selector in selectors.slice().iter() { + is_any = is_any || self.add_selector(selector, quirks_mode); + } + is_any + } + + fn add_selector(&mut self, selector: &Selector, quirks_mode: QuirksMode) -> bool { + let mut is_any = true; + let mut iter = selector.iter(); + while let Some(c) = iter.next() { + let component_any = match c { + Component::Class(cls) => { + match self.buckets.classes.try_entry(cls.0.clone(), quirks_mode) { + Ok(e) => { + e.or_insert(()); + false + }, + Err(_) => true, + } + }, + Component::ID(id) => { + match self.buckets.ids.try_entry(id.0.clone(), quirks_mode) { + Ok(e) => { + e.or_insert(()); + false + }, + Err(_) => true, + } + }, + Component::LocalName(local_name) => { + self.buckets.local_names.insert(local_name.lower_name.clone(), ()); + false + }, + Component::Is(ref list) | Component::Where(ref list) => { + self.add_selector_list(list, quirks_mode) + }, + _ => true, + }; + + is_any = is_any && component_any; + } + is_any + } + + /// Shrink the map as much as possible. + pub fn shrink_if_needed(&mut self) { + self.buckets.shrink_if_needed(); + } + + /// Clear the map. + pub fn clear(&mut self) { + self.buckets.clear(); + self.any = false; + } + + /// Could a given element possibly be a scope root? + fn early_reject(&self, element: E, quirks_mode: QuirksMode) -> bool { + if self.any { + return false; + } + + if let Some(id) = element.id() { + if self.buckets.ids.get(id, quirks_mode).is_some() { + return false; + } + } + + let mut found = false; + element.each_class(|cls| { + if self.buckets.classes.get(cls, quirks_mode).is_some() { + found = true; + } + }); + if found { + return false; + } + + if self.buckets.local_names.get(element.local_name()).is_some() { + return false; + } + + true + } +} diff --git a/servo/components/style/stylist.rs b/servo/components/style/stylist.rs index 5eae366804d7..5838f9cf2c67 100644 --- a/servo/components/style/stylist.rs +++ b/servo/components/style/stylist.rs @@ -42,8 +42,7 @@ use crate::stylesheets::import_rule::ImportLayer; use crate::stylesheets::keyframes_rule::KeyframesAnimation; use crate::stylesheets::layer_rule::{LayerName, LayerOrder}; use crate::stylesheets::scope_rule::{ - collect_scope_roots, element_is_outside_of_scope, ImplicitScopeRoot, ScopeRootCandidate, - ScopeTarget, + collect_scope_roots, element_is_outside_of_scope, ImplicitScopeRoot, ScopeRootCandidate, ScopeSubjectMap, ScopeTarget }; #[cfg(feature = "gecko")] use crate::stylesheets::{ @@ -2640,6 +2639,9 @@ pub struct CascadeData { /// The list of scope conditions, indexed by their id. scope_conditions: SmallVec<[ScopeConditionReference; 1]>, + /// Map of unique selectors on scope start selectors' subjects. + scope_subject_map: ScopeSubjectMap, + /// Effective media query results cached from the last rebuild. effective_media_query_results: EffectiveMediaQueryResults, @@ -2699,6 +2701,7 @@ impl CascadeData { layers: smallvec::smallvec![CascadeLayer::root()], container_conditions: smallvec::smallvec![ContainerConditionReference::none()], scope_conditions: smallvec::smallvec![ScopeConditionReference::none()], + scope_subject_map: Default::default(), extra_data: ExtraStyleData::default(), effective_media_query_results: EffectiveMediaQueryResults::new(), rules_source_order: 0, @@ -2996,7 +2999,7 @@ impl CascadeData { }; let potential_scope_roots = if is_outermost_scope { - collect_scope_roots(element, None, context, &root_target, matches_shadow_host) + collect_scope_roots(element, None, context, &root_target, matches_shadow_host, &self.scope_subject_map) } else { let mut result = vec![]; for activation in &outer_scope_roots { @@ -3006,6 +3009,7 @@ impl CascadeData { context, &root_target, matches_shadow_host, + &self.scope_subject_map, ); result.append(&mut this_result); } @@ -3071,6 +3075,7 @@ impl CascadeData { self.mapped_ids.shrink_if_needed(); self.layer_id.shrink_if_needed(); self.selectors_for_cache_revalidation.shrink_if_needed(); + self.scope_subject_map.shrink_if_needed(); } fn compute_layer_order(&mut self) { @@ -3599,6 +3604,10 @@ impl CascadeData { ScopeBoundsWithHashes::new(quirks_mode, start, end) }; + if let Some(selectors) = replaced.start.as_ref() { + self.scope_subject_map.add_bound_start(&selectors.selectors, quirks_mode); + } + self.scope_conditions.push(ScopeConditionReference { parent: containing_rule_state.scope_condition_id, condition: Some(replaced), @@ -3850,6 +3859,7 @@ impl CascadeData { self.nth_of_mapped_ids.clear(); self.selectors_for_cache_revalidation.clear(); self.effective_media_query_results.clear(); + self.scope_subject_map.clear(); } }