From db5db13559ec3ddfadb9e86fe87f6896b80ed0a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 7 Apr 2018 19:46:00 +0200 Subject: [PATCH] style: Implement the non-functional :host selector. Kinda tricky because :host only matches rules on the shadow root where the rules come from. So we need to be careful during invalidation and style sharing. I didn't use the non_ts_pseudo_class_list bits because as soon as we implement the :host(..) bits we're going to need to special-case it anyway. The general schema is the following: * Rightmost featureless :host selectors are handled inserting them in the host_rules hashmap. Note that we only insert featureless stuff there. We could insert all of them and just filter during matching, but that's slightly annoying. * The other selectors, like non-featureless :host or what not, are added to the normal cascade data. This is harmless, since the shadow host rules are never matched against the host, so we know they'll just never match, and avoids adding more special-cases. * Featureless :host selectors to the left of a combinator are handled during matching, in the special-case of next_element_for_combinator in selectors. This prevents this from being more invasive, and keeps the usual fast path slim, but it's a bit hard to match the spec and the implementation. We could keep a copy of the SelectorIter instead in the matching context to make the handling of featureless-ness explicit in match_non_ts_pseudo_class, but we'd still need the special-case anyway, so I'm not fond of it. * We take advantage of one thing that makes this sound. As you may have noticed, if you had `root` element which is a ShadowRoot, and you matched something like `div:host` against it, using a MatchingContext with current_host == root, we'd incorrectly report a match. But this is impossible due to the following constraints: * Shadow root rules aren't matched against the host during styling (except these featureless selectors). * DOM APIs' current_host needs to be the _containing_ host, not the element itself if you're a Shadow host. Bug: 992245 Reviewed-by: xidorn MozReview-Commit-ID: KayYNfTXb5h --- components/selectors/builder.rs | 1 + components/selectors/context.rs | 42 ++++- components/selectors/matching.rs | 15 +- components/selectors/parser.rs | 38 +++-- components/style/data.rs | 4 +- components/style/dom.rs | 23 ++- components/style/dom_apis.rs | 8 +- components/style/gecko/selector_parser.rs | 16 +- .../element/state_and_attributes.rs | 58 +++---- components/style/servo/selector_parser.rs | 5 - components/style/sharing/mod.rs | 15 ++ components/style/stylist.rs | 148 +++++++++++------- ports/geckolib/glue.rs | 4 +- 13 files changed, 241 insertions(+), 136 deletions(-) diff --git a/components/selectors/builder.rs b/components/selectors/builder.rs index 2220477b553..b81c729eea5 100644 --- a/components/selectors/builder.rs +++ b/components/selectors/builder.rs @@ -314,6 +314,7 @@ fn complex_selector_specificity(mut iter: slice::Iter>) Component::FirstChild | Component::LastChild | Component::OnlyChild | Component::Root | Component::Empty | Component::Scope | + Component::Host | Component::NthChild(..) | Component::NthLastChild(..) | Component::NthOfType(..) | diff --git a/components/selectors/context.rs b/components/selectors/context.rs index 257a29afc44..b3b147f6e0e 100644 --- a/components/selectors/context.rs +++ b/components/selectors/context.rs @@ -6,7 +6,7 @@ use attr::CaseSensitivity; use bloom::BloomFilter; use nth_index_cache::NthIndexCache; use parser::SelectorImpl; -use tree::OpaqueElement; +use tree::{Element, OpaqueElement}; /// What kind of selector matching mode we should use. /// @@ -118,6 +118,9 @@ where /// See https://drafts.csswg.org/selectors-4/#scope-pseudo pub scope_element: Option, + /// The current shadow host we're collecting :host rules for. + pub current_host: Option, + /// Controls how matching for links is handled. visited_handling: VisitedHandlingMode, @@ -178,6 +181,7 @@ where quirks_mode, classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(), scope_element: None, + current_host: None, nesting_level: 0, in_negation: false, pseudo_element_matching_fn: None, @@ -186,6 +190,16 @@ where } } + /// Override the quirks mode we're matching against. + /// + /// FIXME(emilio): This is a hack for XBL quirks-mode mismatches. + #[inline] + pub fn set_quirks_mode(&mut self, quirks_mode: QuirksMode) { + self.quirks_mode = quirks_mode; + self.classes_and_ids_case_sensitivity = + quirks_mode.classes_and_ids_case_sensitivity(); + } + /// Whether we're matching a nested selector. #[inline] pub fn is_nested(&self) -> bool { @@ -266,4 +280,30 @@ where self.visited_handling = original_handling_mode; result } + + /// Runs F with a given shadow host which is the root of the tree whose + /// rules we're matching. + #[inline] + pub fn with_shadow_host( + &mut self, + host: Option, + f: F, + ) -> R + where + E: Element, + F: FnOnce(&mut Self) -> R, + { + let original_host = self.current_host.take(); + self.current_host = host.map(|h| h.opaque()); + let result = f(self); + self.current_host = original_host; + result + } + + /// Returns the current shadow host whose shadow root we're matching rules + /// against. + #[inline] + pub fn shadow_host(&self) -> Option { + self.current_host.clone() + } } diff --git a/components/selectors/matching.rs b/components/selectors/matching.rs index 7244868285b..527d1224e6b 100644 --- a/components/selectors/matching.rs +++ b/components/selectors/matching.rs @@ -466,15 +466,9 @@ where // pseudo-classes are allowed to match it. // // Since we know that the parent is a shadow root, we necessarily - // are in a shadow tree of the host. - let all_selectors_could_match = selector.clone().all(|component| { - match *component { - Component::NonTSPseudoClass(ref pc) => pc.is_host(), - _ => false, - } - }); - - if !all_selectors_could_match { + // are in a shadow tree of the host, and the next selector will only + // match if the selector is a featureless :host selector. + if !selector.clone().is_featureless_host_selector() { return None; } @@ -820,6 +814,9 @@ where flags_setter(element, ElementSelectorFlags::HAS_EMPTY_SELECTOR); element.is_empty() } + Component::Host => { + context.shared.shadow_host().map_or(false, |host| host == element.opaque()) + } Component::Scope => { match context.shared.scope_element { Some(ref scope_element) => element.opaque() == *scope_element, diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index 9f33683b950..dfcae65a9cc 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -42,9 +42,6 @@ pub trait NonTSPseudoClass : Sized + ToCss { /// Whether this pseudo-class is :active or :hover. fn is_active_or_hover(&self) -> bool; - - /// Whether this pseudo-class is :host. - fn is_host(&self) -> bool; } fn to_ascii_lowercase(s: &str) -> Cow { @@ -145,6 +142,11 @@ pub trait Parser<'i> { false } + /// Whether to parse the `:host` pseudo-class. + fn parse_host(&self) -> bool { + false + } + /// This function can return an "Err" pseudo-element in order to support CSS2.1 /// pseudo-elements. fn parse_non_ts_pseudo_class( @@ -513,6 +515,13 @@ impl Selector { } } + /// Whether this selector is a featureless :host selector, with no + /// combinators to the left. + #[inline] + pub fn is_featureless_host_selector(&self) -> bool { + self.iter().is_featureless_host_selector() + } + /// Returns an iterator over this selector in matching order (right-to-left), /// skipping the rightmost |offset| Components. #[inline] @@ -605,6 +614,14 @@ impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> { self.next_combinator.take() } + /// Whether this selector is a featureless host selector, with no + /// combinators to the left. + #[inline] + pub(crate) fn is_featureless_host_selector(&mut self) -> bool { + self.all(|component| matches!(*component, Component::Host)) && + self.next_sequence().is_none() + } + /// Returns remaining count of the simple selectors and combinators in the Selector. #[inline] pub fn selector_length(&self) -> usize { @@ -776,6 +793,10 @@ pub enum Component { Root, Empty, Scope, + /// The `:host` pseudo-class: + /// + /// https://drafts.csswg.org/css-scoping/#host-selector + Host, NthChild(i32, i32), NthLastChild(i32, i32), NthOfType(i32, i32), @@ -1098,6 +1119,7 @@ impl ToCss for Component { Root => dest.write_str(":root"), Empty => dest.write_str(":empty"), Scope => dest.write_str(":scope"), + Host => dest.write_str(":host"), FirstOfType => dest.write_str(":first-of-type"), LastOfType => dest.write_str(":last-of-type"), OnlyOfType => dest.write_str(":only-of-type"), @@ -1947,9 +1969,10 @@ where "root" => Ok(Component::Root), "empty" => Ok(Component::Empty), "scope" => Ok(Component::Scope), + "host" if P::parse_host(parser) => Ok(Component::Host), "first-of-type" => Ok(Component::FirstOfType), - "last-of-type" => Ok(Component::LastOfType), - "only-of-type" => Ok(Component::OnlyOfType), + "last-of-type" => Ok(Component::LastOfType), + "only-of-type" => Ok(Component::OnlyOfType), _ => Err(()) }).or_else(|()| { P::parse_non_ts_pseudo_class(parser, location, name) @@ -1999,11 +2022,6 @@ pub mod tests { fn is_active_or_hover(&self) -> bool { matches!(*self, PseudoClass::Active | PseudoClass::Hover) } - - #[inline] - fn is_host(&self) -> bool { - false - } } impl ToCss for PseudoClass { diff --git a/components/style/data.rs b/components/style/data.rs index e1af10308ad..3cc56b19f0c 100644 --- a/components/style/data.rs +++ b/components/style/data.rs @@ -262,8 +262,8 @@ impl ElementData { let mut non_document_styles = SmallVec::<[_; 3]>::new(); let matches_doc_author_rules = - element.each_applicable_non_document_style_rule_data(|data, quirks_mode| { - non_document_styles.push((data, quirks_mode)) + element.each_applicable_non_document_style_rule_data(|data, quirks_mode, host| { + non_document_styles.push((data, quirks_mode, host.map(|h| h.opaque()))) }); let mut processor = StateAndAttrInvalidationProcessor::new( diff --git a/components/style/dom.rs b/components/style/dom.rs index 93c587257a6..7e63fd8ba50 100644 --- a/components/style/dom.rs +++ b/components/style/dom.rs @@ -772,24 +772,37 @@ pub trait TElement /// Executes the callback for each applicable style rule data which isn't /// the main document's data (which stores UA / author rules). /// + /// The element passed to the callback is the containing shadow host for the + /// data if it comes from Shadow DOM, None if it comes from XBL. + /// /// Returns whether normal document author rules should apply. fn each_applicable_non_document_style_rule_data<'a, F>(&self, mut f: F) -> bool where Self: 'a, - F: FnMut(&'a CascadeData, QuirksMode), + F: FnMut(&'a CascadeData, QuirksMode, Option), { - let mut doc_rules_apply = !self.each_xbl_cascade_data(&mut f); + let mut doc_rules_apply = !self.each_xbl_cascade_data(|data, quirks_mode| { + f(data, quirks_mode, None); + }); if let Some(shadow) = self.containing_shadow() { doc_rules_apply = false; - f(shadow.style_data(), self.as_node().owner_doc().quirks_mode()); + f( + shadow.style_data(), + self.as_node().owner_doc().quirks_mode(), + Some(shadow.host()), + ); } let mut current = self.assigned_slot(); while let Some(slot) = current { // Slots can only have assigned nodes when in a shadow tree. - let data = slot.containing_shadow().unwrap().style_data(); - f(data, self.as_node().owner_doc().quirks_mode()); + let shadow = slot.containing_shadow().unwrap(); + f( + shadow.style_data(), + self.as_node().owner_doc().quirks_mode(), + Some(shadow.host()), + ); current = slot.assigned_slot(); } diff --git a/components/style/dom_apis.rs b/components/style/dom_apis.rs index 1a775853f90..e73f651a6f4 100644 --- a/components/style/dom_apis.rs +++ b/components/style/dom_apis.rs @@ -7,7 +7,7 @@ use Atom; use context::QuirksMode; -use dom::{TDocument, TElement, TNode}; +use dom::{TDocument, TElement, TNode, TShadowRoot}; use invalidation::element::invalidator::{DescendantInvalidationLists, Invalidation}; use invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector}; use selectors::{Element, NthIndexCache, SelectorList}; @@ -33,6 +33,7 @@ where quirks_mode, ); context.scope_element = Some(element.opaque()); + context.current_host = element.containing_shadow_host().map(|e| e.opaque()); matching::matches_selector_list(selector_list, element, &mut context) } @@ -54,6 +55,7 @@ where quirks_mode, ); context.scope_element = Some(element.opaque()); + context.current_host = element.containing_shadow_host().map(|e| e.opaque()); let mut current = Some(element); while let Some(element) = current.take() { @@ -547,6 +549,10 @@ where let root_element = root.as_element(); matching_context.scope_element = root_element.map(|e| e.opaque()); + matching_context.current_host = match root_element { + Some(root) => root.containing_shadow_host().map(|host| host.opaque()), + None => root.as_shadow_root().map(|root| root.host().opaque()), + }; let fast_result = query_selector_fast::( root, diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index d7a9e0af425..41f33269d7d 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -117,7 +117,8 @@ impl Visit for NonTSPseudoClass { type Impl = SelectorImpl; fn visit(&self, visitor: &mut V) -> bool - where V: SelectorVisitor, + where + V: SelectorVisitor, { if let NonTSPseudoClass::MozAny(ref selectors) = *self { for selector in selectors.iter() { @@ -161,8 +162,6 @@ impl NonTSPseudoClass { match *self { $(NonTSPseudoClass::$name => check_flag!($flags),)* $(NonTSPseudoClass::$s_name(..) => check_flag!($s_flags),)* - // TODO(emilio): Maybe -moz-locale-dir shouldn't be - // content-exposed. NonTSPseudoClass::MozLocaleDir(_) | NonTSPseudoClass::Dir(_) | NonTSPseudoClass::MozAny(_) => false, @@ -275,11 +274,6 @@ impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass { fn is_active_or_hover(&self) -> bool { matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover) } - - #[inline] - fn is_host(&self) -> bool { - false // TODO(emilio) - } } /// The dummy struct we use to implement our selector parsing. @@ -349,6 +343,7 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { type Impl = SelectorImpl; type Error = StyleParseErrorKind<'i>; + #[inline] fn parse_slotted(&self) -> bool { // NOTE(emilio): Slot assignment and such works per-document, but // getting a document around here is not trivial, and it's not worth @@ -356,6 +351,11 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { unsafe { structs::nsContentUtils_sIsShadowDOMEnabled } } + #[inline] + fn parse_host(&self) -> bool { + self.parse_slotted() + } + fn pseudo_element_allows_single_colon(name: &str) -> bool { // FIXME: -moz-tree check should probably be ascii-case-insensitive. ::selectors::parser::is_css2_pseudo_element(name) || diff --git a/components/style/invalidation/element/state_and_attributes.rs b/components/style/invalidation/element/state_and_attributes.rs index 0c0bba24e89..c133243abfd 100644 --- a/components/style/invalidation/element/state_and_attributes.rs +++ b/components/style/invalidation/element/state_and_attributes.rs @@ -18,6 +18,7 @@ use invalidation::element::restyle_hints::RestyleHint; use selector_map::SelectorMap; use selector_parser::Snapshot; use selectors::NthIndexCache; +use selectors::OpaqueElement; use selectors::attr::CaseSensitivity; use selectors::matching::{MatchingContext, MatchingMode, VisitedHandlingMode}; use selectors::matching::matches_selector; @@ -38,9 +39,8 @@ where { element: E, wrapper: ElementWrapper<'b, E>, - nth_index_cache: Option<&'a mut NthIndexCache>, snapshot: &'a Snapshot, - quirks_mode: QuirksMode, + matching_context: &'a mut MatchingContext<'b, E::Impl>, lookup_element: E, removed_id: Option<&'a WeakAtom>, added_id: Option<&'a WeakAtom>, @@ -56,7 +56,7 @@ where /// changes. pub struct StateAndAttrInvalidationProcessor<'a, 'b: 'a, E: TElement> { shared_context: &'a SharedStyleContext<'b>, - shadow_rule_datas: &'a [(&'b CascadeData, QuirksMode)], + shadow_rule_datas: &'a [(&'b CascadeData, QuirksMode, Option)], matches_document_author_rules: bool, element: E, data: &'a mut ElementData, @@ -67,7 +67,7 @@ impl<'a, 'b: 'a, E: TElement> StateAndAttrInvalidationProcessor<'a, 'b, E> { /// Creates a new StateAndAttrInvalidationProcessor. pub fn new( shared_context: &'a SharedStyleContext<'b>, - shadow_rule_datas: &'a [(&'b CascadeData, QuirksMode)], + shadow_rule_datas: &'a [(&'b CascadeData, QuirksMode, Option)], matches_document_author_rules: bool, element: E, data: &'a mut ElementData, @@ -237,8 +237,7 @@ where state_changes, element, snapshot: &snapshot, - quirks_mode: self.shared_context.quirks_mode(), - nth_index_cache: self.matching_context.nth_index_cache.as_mut().map(|c| &mut **c), + matching_context: &mut self.matching_context, removed_id: id_removed, added_id: id_added, classes_removed: &classes_removed, @@ -260,11 +259,12 @@ where } } - for &(ref data, quirks_mode) in self.shadow_rule_datas { + for &(ref data, quirks_mode, ref host) in self.shadow_rule_datas { // FIXME(emilio): Replace with assert / remove when we figure // out what to do with the quirks mode mismatches // (that is, when bug 1406875 is properly fixed). - collector.quirks_mode = quirks_mode; + collector.matching_context.set_quirks_mode(quirks_mode); + collector.matching_context.current_host = host.clone(); collector.collect_dependencies_in_invalidation_map(data.invalidation_map()); } @@ -333,7 +333,7 @@ where &mut self, map: &'selectors InvalidationMap, ) { - let quirks_mode = self.quirks_mode; + let quirks_mode = self.matching_context.quirks_mode(); let removed_id = self.removed_id; if let Some(ref id) = removed_id { if let Some(deps) = map.id_to_selector.get(id, quirks_mode) { @@ -386,7 +386,7 @@ where ) { map.lookup_with_additional( self.lookup_element, - self.quirks_mode, + self.matching_context.quirks_mode(), self.removed_id, self.classes_removed, |dependency| { @@ -403,7 +403,7 @@ where ) { map.lookup_with_additional( self.lookup_element, - self.quirks_mode, + self.matching_context.quirks_mode(), self.removed_id, self.classes_removed, |dependency| { @@ -429,51 +429,29 @@ where visited_handling_mode: VisitedHandlingMode, dependency: &Dependency, ) -> bool { - let matches_now = { - let mut context = MatchingContext::new_for_visited( - MatchingMode::Normal, - None, - self.nth_index_cache.as_mut().map(|c| &mut **c), - visited_handling_mode, - self.quirks_mode, - ); - + let element = &self.element; + let wrapper = &self.wrapper; + self.matching_context.with_visited_handling_mode(visited_handling_mode, |mut context| { let matches_now = matches_selector( &dependency.selector, dependency.selector_offset, None, - &self.element, + element, &mut context, &mut |_, _| {}, ); - matches_now - }; - - let matched_then = { - let mut context = MatchingContext::new_for_visited( - MatchingMode::Normal, - None, - self.nth_index_cache.as_mut().map(|c| &mut **c), - visited_handling_mode, - self.quirks_mode, - ); - let matched_then = matches_selector( &dependency.selector, dependency.selector_offset, None, - &self.wrapper, + wrapper, &mut context, &mut |_, _| {}, ); - matched_then - }; - - // Check for mismatches in both the match result and also the status - // of whether a relevant link was found. - matched_then != matches_now + matched_then != matches_now + }) } fn scan_dependency( diff --git a/components/style/servo/selector_parser.rs b/components/style/servo/selector_parser.rs index 91dce27a7d3..dde2c329f9c 100644 --- a/components/style/servo/selector_parser.rs +++ b/components/style/servo/selector_parser.rs @@ -311,11 +311,6 @@ pub enum NonTSPseudoClass { impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass { type Impl = SelectorImpl; - #[inline] - fn is_host(&self) -> bool { - false - } - #[inline] fn is_active_or_hover(&self) -> bool { matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover) diff --git a/components/style/sharing/mod.rs b/components/style/sharing/mod.rs index ae35d734b91..cf8345b5b47 100644 --- a/components/style/sharing/mod.rs +++ b/components/style/sharing/mod.rs @@ -553,6 +553,16 @@ impl StyleSharingCache { return; } + // We can't share style across shadow hosts right now, because they may + // match different :host rules. + // + // TODO(emilio): We could share across the ones that don't have :host + // rules or have the same. + if element.shadow_root().is_some() { + debug!("Failing to insert into the cache: Shadow Host"); + return; + } + // If the element has running animations, we can't share style. // // This is distinct from the specifies_{animations,transitions} check below, @@ -716,6 +726,11 @@ impl StyleSharingCache { return None; } + if target.element.shadow_root().is_some() { + trace!("Miss: Shadow host"); + return None; + } + if target.matches_user_and_author_rules() != candidate.element.matches_user_and_author_rules() { trace!("Miss: User and Author Rules"); diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 59461d152a2..686ce6a6b6e 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -591,25 +591,24 @@ impl Stylist { pub fn any_applicable_rule_data(&self, element: E, mut f: F) -> bool where E: TElement, - F: FnMut(&CascadeData, QuirksMode) -> bool, + F: FnMut(&CascadeData) -> bool, { - if f(&self.cascade_data.user_agent.cascade_data, self.quirks_mode()) { + if f(&self.cascade_data.user_agent.cascade_data) { return true; } let mut maybe = false; let doc_author_rules_apply = - element.each_applicable_non_document_style_rule_data(|data, quirks_mode| { - maybe = maybe || f(&*data, quirks_mode); + element.each_applicable_non_document_style_rule_data(|data, _, _| { + maybe = maybe || f(&*data); }); if maybe || !doc_author_rules_apply { return maybe; } - f(&self.cascade_data.author, self.quirks_mode()) || - f(&self.cascade_data.user, self.quirks_mode()) + f(&self.cascade_data.author) || f(&self.cascade_data.user) } /// Computes the style for a given "precomputed" pseudo-element, taking the @@ -1261,6 +1260,21 @@ impl Stylist { // for !important it should be the other way around. So probably we need // to add some sort of AuthorScoped cascade level or something. if matches_author_rules && !only_default_rules { + if let Some(shadow) = rule_hash_target.shadow_root() { + if let Some(map) = shadow.style_data().host_rules(pseudo_element) { + context.with_shadow_host(Some(rule_hash_target), |context| { + map.get_all_matching_rules( + element, + rule_hash_target, + applicable_declarations, + context, + flags_setter, + CascadeLevel::AuthorNormal, + ); + }); + } + } + // Match slotted rules in reverse order, so that the outer slotted // rules come before the inner rules (and thus have less priority). let mut slots = SmallVec::<[_; 3]>::new(); @@ -1271,32 +1285,35 @@ impl Stylist { } for slot in slots.iter().rev() { - let styles = slot.containing_shadow().unwrap().style_data(); + let shadow = slot.containing_shadow().unwrap(); + let styles = shadow.style_data(); if let Some(map) = styles.slotted_rules(pseudo_element) { - map.get_all_matching_rules( - element, - rule_hash_target, - applicable_declarations, - context, - flags_setter, - CascadeLevel::AuthorNormal, - ); + context.with_shadow_host(Some(shadow.host()), |context| { + map.get_all_matching_rules( + element, + rule_hash_target, + applicable_declarations, + context, + flags_setter, + CascadeLevel::AuthorNormal, + ); + }); } } - // TODO(emilio): We need to look up :host rules if the element is a - // shadow host, when we implement that. if let Some(containing_shadow) = rule_hash_target.containing_shadow() { let cascade_data = containing_shadow.style_data(); if let Some(map) = cascade_data.normal_rules(pseudo_element) { - map.get_all_matching_rules( - element, - rule_hash_target, - applicable_declarations, - context, - flags_setter, - CascadeLevel::AuthorNormal, - ); + context.with_shadow_host(Some(containing_shadow.host()), |context| { + map.get_all_matching_rules( + element, + rule_hash_target, + applicable_declarations, + context, + flags_setter, + CascadeLevel::AuthorNormal, + ); + }); } match_document_author_rules = false; @@ -1421,7 +1438,7 @@ impl Stylist { } let hash = id.get_hash(); - self.any_applicable_rule_data(element, |data, _| { + self.any_applicable_rule_data(element, |data| { data.mapped_ids.might_contain_hash(hash) }) } @@ -1483,22 +1500,24 @@ impl Stylist { ); } - element.each_applicable_non_document_style_rule_data(|data, quirks_mode| { - data.selectors_for_cache_revalidation.lookup( - element, - quirks_mode, - |selector_and_hashes| { - results.push(matches_selector( - &selector_and_hashes.selector, - selector_and_hashes.selector_offset, - Some(&selector_and_hashes.hashes), - &element, - &mut matching_context, - flags_setter - )); - true - } - ); + element.each_applicable_non_document_style_rule_data(|data, quirks_mode, host| { + matching_context.with_shadow_host(host, |matching_context| { + data.selectors_for_cache_revalidation.lookup( + element, + quirks_mode, + |selector_and_hashes| { + results.push(matches_selector( + &selector_and_hashes.selector, + selector_and_hashes.selector_offset, + Some(&selector_and_hashes.hashes), + &element, + matching_context, + flags_setter + )); + true + } + ); + }) }); results @@ -1929,6 +1948,15 @@ pub struct CascadeData { /// cascade level. normal_rules: ElementAndPseudoRules, + /// The `:host` pseudo rules that are the rightmost selector. + /// + /// Note that as of right now these can't affect invalidation in any way, + /// until we support the :host() notation. + /// + /// Also, note that other engines don't accept stuff like :host::before / + /// :host::after, so we don't need to store pseudo rules at all. + host_rules: Option>>, + /// The data coming from ::slotted() pseudo-element rules. /// /// We need to store them separately because an element needs to match @@ -2005,6 +2033,7 @@ impl CascadeData { pub fn new() -> Self { Self { normal_rules: ElementAndPseudoRules::default(), + host_rules: None, slotted_rules: None, invalidation_map: InvalidationMap::new(), attribute_dependencies: NonCountingBloomFilter::new(), @@ -2091,6 +2120,15 @@ impl CascadeData { self.normal_rules.rules(pseudo) } + #[inline] + fn host_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap> { + if pseudo.is_some() { + return None; + } + + self.host_rules.as_ref().map(|rules| &**rules) + } + #[inline] fn slotted_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap> { self.slotted_rules.as_ref().and_then(|d| d.rules(pseudo)) @@ -2224,19 +2262,23 @@ impl CascadeData { } } - let rules = if selector.is_slotted() { - self.slotted_rules.get_or_insert_with(|| { - Box::new(Default::default()) - }) + if selector.is_featureless_host_selector() { + let host_rules = + self.host_rules.get_or_insert_with(|| { + Box::new(Default::default()) + }); + host_rules.insert(rule, quirks_mode)?; } else { - &mut self.normal_rules - }; + let rules = if selector.is_slotted() { + self.slotted_rules.get_or_insert_with(|| { + Box::new(Default::default()) + }) + } else { + &mut self.normal_rules + }; - rules.insert( - rule, - pseudo_element, - quirks_mode, - )?; + rules.insert(rule, pseudo_element, quirks_mode)?; + } } self.rules_source_order += 1; } diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index 41905bfe55e..ce51c3b5d74 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -4914,7 +4914,7 @@ pub extern "C" fn Servo_StyleSet_MightHaveAttributeDependency( unsafe { Atom::with(local_name, |atom| { - data.stylist.any_applicable_rule_data(element, |data, _| { + data.stylist.any_applicable_rule_data(element, |data| { data.might_have_attribute_dependency(atom) }) }) @@ -4932,7 +4932,7 @@ pub extern "C" fn Servo_StyleSet_HasStateDependency( let state = ElementState::from_bits_truncate(state); let data = PerDocumentStyleData::from_ffi(raw_data).borrow(); - data.stylist.any_applicable_rule_data(element, |data, _| { + data.stylist.any_applicable_rule_data(element, |data| { data.has_state_dependency(state) }) }