diff --git a/components/selectors/builder.rs b/components/selectors/builder.rs index b3d61b34706..2220477b553 100644 --- a/components/selectors/builder.rs +++ b/components/selectors/builder.rs @@ -98,13 +98,21 @@ impl SelectorBuilder { /// Consumes the builder, producing a Selector. #[inline(always)] - pub fn build(&mut self, parsed_pseudo: bool) -> ThinArc> { + pub fn build( + &mut self, + parsed_pseudo: bool, + parsed_slotted: bool, + ) -> ThinArc> { // Compute the specificity and flags. let mut spec = SpecificityAndFlags(specificity(self.simple_selectors.iter())); if parsed_pseudo { spec.0 |= HAS_PSEUDO_BIT; } + if parsed_slotted { + spec.0 |= HAS_SLOTTED_BIT; + } + self.build_with_specificity_and_flags(spec) } @@ -188,18 +196,28 @@ fn split_from_end(s: &[T], at: usize) -> (&[T], &[T]) { } pub const HAS_PSEUDO_BIT: u32 = 1 << 30; +pub const HAS_SLOTTED_BIT: u32 = 1 << 31; +/// We use ten bits for each specificity kind (id, class, element), and the two +/// high bits for the pseudo and slotted flags. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct SpecificityAndFlags(pub u32); impl SpecificityAndFlags { + #[inline] pub fn specificity(&self) -> u32 { - self.0 & !HAS_PSEUDO_BIT + self.0 & !(HAS_PSEUDO_BIT | HAS_SLOTTED_BIT) } + #[inline] pub fn has_pseudo_element(&self) -> bool { (self.0 & HAS_PSEUDO_BIT) != 0 } + + #[inline] + pub fn is_slotted(&self) -> bool { + (self.0 & HAS_SLOTTED_BIT) != 0 + } } const MAX_10BIT: u32 = (1u32 << 10) - 1; diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index 3584cb4eb25..a6902d105c7 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -445,14 +445,22 @@ pub fn namespace_empty_string() -> Impl::NamespaceUrl { pub struct Selector(ThinArc>); impl Selector { + #[inline] pub fn specificity(&self) -> u32 { self.0.header.header.specificity() } + #[inline] pub fn has_pseudo_element(&self) -> bool { self.0.header.header.has_pseudo_element() } + #[inline] + pub fn is_slotted(&self) -> bool { + self.0.header.header.is_slotted() + } + + #[inline] pub fn pseudo_element(&self) -> Option<&Impl::PseudoElement> { if !self.has_pseudo_element() { return None @@ -1219,7 +1227,7 @@ where builder.push_combinator(combinator); } - Ok(Selector(builder.build(has_pseudo_element))) + Ok(Selector(builder.build(has_pseudo_element, slotted))) } impl Selector { diff --git a/components/style/selector_map.rs b/components/style/selector_map.rs index c7c41c6de44..59d53cda2de 100644 --- a/components/style/selector_map.rs +++ b/components/style/selector_map.rs @@ -106,6 +106,13 @@ pub struct SelectorMap { pub count: usize, } +impl Default for SelectorMap { + #[inline] + fn default() -> Self { + Self::new() + } +} + // FIXME(Manishearth) the 'static bound can be removed when // our HashMap fork (hashglobe) is able to use NonZero, // or when stdlib gets fallible collections diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 180c421ba3a..f4d7a2c8dd9 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -1232,7 +1232,7 @@ impl Stylist { rule_hash_target.matches_user_and_author_rules(); // Step 1: Normal user-agent rules. - if let Some(map) = self.cascade_data.user_agent.cascade_data.borrow_for_pseudo(pseudo_element) { + if let Some(map) = self.cascade_data.user_agent.cascade_data.rules.borrow_for_pseudo(pseudo_element) { map.get_all_matching_rules( element, &rule_hash_target, @@ -1270,7 +1270,7 @@ impl Stylist { // Which may be more what you would probably expect. if matches_user_and_author_rules { // Step 3a: User normal rules. - if let Some(map) = self.cascade_data.user.borrow_for_pseudo(pseudo_element) { + if let Some(map) = self.cascade_data.user.rules.borrow_for_pseudo(pseudo_element) { map.get_all_matching_rules( element, &rule_hash_target, @@ -1281,15 +1281,15 @@ impl Stylist { CascadeLevel::UserNormal, ); } - } else { - debug!("skipping user rules"); } // Step 3b: XBL rules. + // + // TODO(emilio): Ensure cascade order is correct for Shadow DOM. let cut_off_inheritance = element.each_xbl_stylist(|stylist| { // ServoStyleSet::CreateXBLServoStyleSet() loads XBL style sheets // under eAuthorSheetFeatures level. - if let Some(map) = stylist.cascade_data.author.borrow_for_pseudo(pseudo_element) { + if let Some(map) = stylist.cascade_data.author.rules.borrow_for_pseudo(pseudo_element) { // NOTE(emilio): This is needed because the XBL stylist may // think it has a different quirks mode than the document. // @@ -1320,7 +1320,7 @@ impl Stylist { !cut_off_inheritance { // Step 3c: Author normal rules. - if let Some(map) = self.cascade_data.author.borrow_for_pseudo(pseudo_element) { + if let Some(map) = self.cascade_data.author.rules.borrow_for_pseudo(pseudo_element) { map.get_all_matching_rules( element, &rule_hash_target, @@ -1331,8 +1331,31 @@ impl Stylist { CascadeLevel::AuthorNormal ); } - } else { - debug!("skipping author normal rules"); + + let mut current = rule_hash_target.assigned_slot(); + while let Some(slot) = current { + // TODO(emilio): Ensure this is the expected cascade order, + // maybe we need to merge the sorting with the above + // get_all_matching_rules. + slot.each_xbl_stylist(|stylist| { + let slotted_rules = + stylist.cascade_data.author.slotted_rules.as_ref(); + if let Some(map) = slotted_rules.and_then(|r| r.borrow_for_pseudo(pseudo_element)) { + map.get_all_matching_rules( + element, + &rule_hash_target, + applicable_declarations, + context, + self.quirks_mode, + flags_setter, + CascadeLevel::AuthorNormal + ); + } + }); + + current = slot.assigned_slot(); + } + } if !only_default_rules { @@ -1368,8 +1391,6 @@ impl Stylist { ) ); } - } else { - debug!("skipping style attr and SMIL & animation rules"); } // @@ -1388,8 +1409,6 @@ impl Stylist { ) ); } - } else { - debug!("skipping transition rules"); } } @@ -1841,14 +1860,9 @@ impl<'a> SelectorVisitor for StylistSelectorVisitor<'a> { } } -/// Data resulting from performing the CSS cascade that is specific to a given -/// origin. -/// -/// FIXME(emilio): Consider renaming and splitting in `CascadeData` and -/// `InvalidationData`? That'd make `clear_cascade_data()` clearer. -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -#[derive(Debug)] -struct CascadeData { +/// A set of rules for element and pseudo-elements. +#[derive(Debug, Default)] +struct ElementAndPseudoRules { /// Rules from stylesheets at this `CascadeData`'s origin. element_map: SelectorMap, @@ -1859,6 +1873,76 @@ struct CascadeData { /// Figure out a good way to do a `PerNonAnonBox` and `PerAnonBox` (for /// `precomputed_values_for_pseudo`) without duplicating a lot of code. pseudos_map: PerPseudoElementMap>>, +} + +impl ElementAndPseudoRules { + #[inline(always)] + fn insert( + &mut self, + rule: Rule, + pseudo_element: Option<&PseudoElement>, + quirks_mode: QuirksMode, + ) -> Result<(), FailedAllocationError> { + debug_assert!(pseudo_element.map_or(true, |pseudo| !pseudo.is_precomputed())); + + let map = match pseudo_element { + None => &mut self.element_map, + Some(pseudo) => { + self.pseudos_map.get_or_insert_with( + &pseudo.canonical(), + || Box::new(SelectorMap::new()) + ) + } + }; + + map.insert(rule, quirks_mode) + } + + fn clear(&mut self) { + self.element_map.clear(); + self.pseudos_map.clear(); + } + + #[inline] + fn borrow_for_pseudo(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap> { + match pseudo { + Some(pseudo) => self.pseudos_map.get(&pseudo.canonical()).map(|p| &**p), + None => Some(&self.element_map), + } + } + + /// Measures heap usage. + #[cfg(feature = "gecko")] + fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) { + sizes.mElementAndPseudosMaps += self.element_map.size_of(ops); + + for elem in self.pseudos_map.iter() { + if let Some(ref elem) = *elem { + sizes.mElementAndPseudosMaps += as MallocSizeOf>::size_of(elem, ops); + } + } + } +} + +/// Data resulting from performing the CSS cascade that is specific to a given +/// origin. +/// +/// FIXME(emilio): Consider renaming and splitting in `CascadeData` and +/// `InvalidationData`? That'd make `clear_cascade_data()` clearer. +#[cfg_attr(feature = "servo", derive(MallocSizeOf))] +#[derive(Debug)] +struct CascadeData { + /// The rules at this cascade level. + rules: ElementAndPseudoRules, + + /// ::slotted() pseudo-element rules. + /// + /// We need to store them separately because an element needs to match scoped + /// pseudo-element rules in different style scopes. + /// + /// In particular, you need to go through all the style data in all the + /// containing style scopes starting from the closest assigned slot. + slotted_rules: Option>, /// A map with all the animations at this `CascadeData`'s origin, indexed /// by name. @@ -1925,8 +2009,8 @@ struct CascadeData { impl CascadeData { fn new() -> Self { Self { - element_map: SelectorMap::new(), - pseudos_map: PerPseudoElementMap::default(), + rules: ElementAndPseudoRules::default(), + slotted_rules: None, animations: Default::default(), extra_data: ExtraStyleData::default(), invalidation_map: InvalidationMap::new(), @@ -2015,8 +2099,10 @@ impl CascadeData { for selector in &style_rule.selectors.0 { self.num_selectors += 1; - let map = match selector.pseudo_element() { - Some(pseudo) if pseudo.is_precomputed() => { + let pseudo_element = selector.pseudo_element(); + + if let Some(pseudo) = pseudo_element { + if pseudo.is_precomputed() { debug_assert!(selector.is_universal()); debug_assert!(matches!(origin, Origin::UserAgent)); @@ -2030,15 +2116,9 @@ impl CascadeData { CascadeLevel::UANormal, selector.specificity() )); - continue; } - None => &mut self.element_map, - Some(pseudo) => { - self.pseudos_map - .get_or_insert_with(&pseudo.canonical(), || Box::new(SelectorMap::new())) - } - }; + } let hashes = AncestorHashes::new(&selector, quirks_mode); @@ -2050,7 +2130,15 @@ impl CascadeData { self.rules_source_order ); - map.insert(rule, quirks_mode)?; + if selector.is_slotted() { + if self.slotted_rules.is_none() { + self.slotted_rules = Some(Default::default()); + } + let slotted_rules = self.slotted_rules.as_mut().unwrap(); + slotted_rules.insert(rule, pseudo_element, quirks_mode)?; + } else { + self.rules.insert(rule, pseudo_element, quirks_mode)?; + } if rebuild_kind.should_rebuild_invalidation() { self.invalidation_map @@ -2223,18 +2311,10 @@ impl CascadeData { true } - #[inline] - fn borrow_for_pseudo(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap> { - match pseudo { - Some(pseudo) => self.pseudos_map.get(&pseudo.canonical()).map(|p| &**p), - None => Some(&self.element_map), - } - } - /// Clears the cascade data, but not the invalidation data. fn clear_cascade_data(&mut self) { - self.element_map.clear(); - self.pseudos_map.clear(); + self.rules.clear(); + self.slotted_rules = None; self.animations.clear(); self.extra_data.clear(); self.rules_source_order = 0; @@ -2257,12 +2337,9 @@ impl CascadeData { /// Measures heap usage. #[cfg(feature = "gecko")] pub fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) { - sizes.mElementAndPseudosMaps += self.element_map.size_of(ops); - - for elem in self.pseudos_map.iter() { - if let Some(ref elem) = *elem { - sizes.mElementAndPseudosMaps += as MallocSizeOf>::size_of(elem, ops); - } + self.rules.add_size_of(ops, sizes); + if let Some(ref slotted_rules) = self.slotted_rules { + slotted_rules.add_size_of(ops, sizes); } sizes.mOther += self.animations.size_of(ops);