diff --git a/components/selectors/matching.rs b/components/selectors/matching.rs index 49b7f3dbd32d..8993a5ea0031 100644 --- a/components/selectors/matching.rs +++ b/components/selectors/matching.rs @@ -8,6 +8,7 @@ use crate::nth_index_cache::NthIndexCacheInner; use crate::parser::{AncestorHashes, Combinator, Component, LocalName}; use crate::parser::{NonTSPseudoClass, Selector, SelectorImpl, SelectorIter, SelectorList}; use crate::tree::Element; +use smallvec::SmallVec; use std::borrow::Borrow; use std::iter; @@ -667,7 +668,41 @@ where match *selector { Component::Combinator(_) => unreachable!(), - Component::Part(ref parts) => parts.iter().all(|part| element.is_part(part)), + Component::Part(ref parts) => { + let mut hosts = SmallVec::<[E; 4]>::new(); + + let mut host = match element.containing_shadow_host() { + Some(h) => h, + None => return false, + }; + + loop { + let outer_host = host.containing_shadow_host(); + if outer_host.as_ref().map(|h| h.opaque()) == context.shared.current_host { + break; + } + let outer_host = match outer_host { + Some(h) => h, + None => return false, + }; + // TODO(emilio): if worth it, we could early return if + // host doesn't have the exportparts attribute. + hosts.push(host); + host = outer_host; + } + + // Translate the part into the right scope. + parts.iter().all(|part| { + let mut part = part.clone(); + for host in hosts.iter().rev() { + part = match host.imported_part(&part) { + Some(p) => p, + None => return false, + }; + } + element.is_part(&part) + }) + }, Component::Slotted(ref selector) => { // are never flattened tree slottables. !element.is_html_slot_element() && diff --git a/components/selectors/tree.rs b/components/selectors/tree.rs index f892abd4e944..d6198c5a5f53 100644 --- a/components/selectors/tree.rs +++ b/components/selectors/tree.rs @@ -117,6 +117,20 @@ pub trait Element: Sized + Clone + Debug { case_sensitivity: CaseSensitivity, ) -> bool; + /// Returns the mapping from the `exportparts` attribute in the regular + /// direction, that is, inner-tree -> outer-tree. + fn exported_part( + &self, + name: &::PartName, + ) -> Option<::PartName>; + + /// Returns the mapping from the `exportparts` attribute in the reverse + /// direction, that is, in an outer-tree -> inner-tree direction. + fn imported_part( + &self, + name: &::PartName, + ) -> Option<::PartName>; + fn is_part(&self, name: &::PartName) -> bool; /// Returns whether this element matches `:empty`. diff --git a/components/style/dom_apis.rs b/components/style/dom_apis.rs index e43c861271ee..8f764dc8958d 100644 --- a/components/style/dom_apis.rs +++ b/components/style/dom_apis.rs @@ -172,7 +172,11 @@ where }; for selector in self.selector_list.0.iter() { - target_vector.push(Invalidation::new(selector, 0)) + target_vector.push(Invalidation::new( + selector, + self.matching_context.current_host.clone(), + 0, + )) } false diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index bd110801c056..2d1236ce698a 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -2216,6 +2216,28 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { snapshot_helpers::has_class_or_part(name, CaseSensitivity::CaseSensitive, attr) } + #[inline] + fn imported_part(&self, name: &Atom) -> Option { + let imported = unsafe { + bindings::Gecko_Element_ImportedPart(self.0, name.as_ptr()) + }; + if imported.is_null() { + return None; + } + Some(unsafe { Atom::from_raw(imported) }) + } + + #[inline] + fn exported_part(&self, name: &Atom) -> Option { + let exported = unsafe { + bindings::Gecko_Element_ExportedPart(self.0, name.as_ptr()) + }; + if exported.is_null() { + return None; + } + Some(unsafe { Atom::from_raw(exported) }) + } + #[inline(always)] fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool { let attr = match self.get_class_attr() { diff --git a/components/style/invalidation/element/document_state.rs b/components/style/invalidation/element/document_state.rs index 4e29e91eb0e9..7fc51338b1ed 100644 --- a/components/style/invalidation/element/document_state.rs +++ b/components/style/invalidation/element/document_state.rs @@ -79,7 +79,13 @@ where continue; } - self_invalidations.push(Invalidation::new(&dependency.selector, 0)); + // We pass `None` as a scope, as document state selectors aren't + // affected by the current scope. + self_invalidations.push(Invalidation::new( + &dependency.selector, + /* scope = */ None, + 0, + )); } } diff --git a/components/style/invalidation/element/element_wrapper.rs b/components/style/invalidation/element/element_wrapper.rs index b8e29095d240..e17e42a31c05 100644 --- a/components/style/invalidation/element/element_wrapper.rs +++ b/components/style/invalidation/element/element_wrapper.rs @@ -365,6 +365,16 @@ where } } + fn exported_part(&self, name: &Atom) -> Option { + // FIXME(emilio): Implement for proper invalidation. + self.element.exported_part(name) + } + + fn imported_part(&self, name: &Atom) -> Option { + // FIXME(emilio): Implement for proper invalidation. + self.element.imported_part(name) + } + fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool { match self.snapshot() { Some(snapshot) if snapshot.has_attrs() => snapshot.has_class(name, case_sensitivity), diff --git a/components/style/invalidation/element/invalidator.rs b/components/style/invalidation/element/invalidator.rs index 3487d7b9353a..9e8309396cdd 100644 --- a/components/style/invalidation/element/invalidator.rs +++ b/components/style/invalidation/element/invalidator.rs @@ -8,6 +8,7 @@ use crate::context::StackLimitChecker; use crate::dom::{TElement, TNode, TShadowRoot}; use crate::selector_parser::SelectorImpl; +use selectors::OpaqueElement; use selectors::matching::matches_compound_selector_from; use selectors::matching::{CompoundSelectorMatchingResult, MatchingContext}; use selectors::parser::{Combinator, Component, Selector}; @@ -127,6 +128,8 @@ enum InvalidationKind { #[derive(Clone)] pub struct Invalidation<'a> { selector: &'a Selector, + /// The right shadow host from where the rule came from, if any. + scope: Option, /// The offset of the selector pointing to a compound selector. /// /// This order is a "parse order" offset, that is, zero is the leftmost part @@ -143,9 +146,14 @@ pub struct Invalidation<'a> { impl<'a> Invalidation<'a> { /// Create a new invalidation for a given selector and offset. - pub fn new(selector: &'a Selector, offset: usize) -> Self { + pub fn new( + selector: &'a Selector, + scope: Option, + offset: usize, + ) -> Self { Self { selector, + scope, offset, matched_by_any_previous: false, } @@ -483,6 +491,9 @@ where let mut any = false; let mut sibling_invalidations = InvalidationVector::new(); + + // FIXME(emilio): We also need to invalidate parts in descendant shadow + // hosts that have exportparts attributes. for element in shadow.parts() { any |= self.invalidate_child( *element, @@ -721,12 +732,17 @@ where self.element, invalidation, invalidation_kind ); - let matching_result = matches_compound_selector_from( - &invalidation.selector, - invalidation.offset, - self.processor.matching_context(), - &self.element, - ); + let matching_result = { + let mut context = self.processor.matching_context(); + context.current_host = invalidation.scope; + + matches_compound_selector_from( + &invalidation.selector, + invalidation.offset, + context, + &self.element, + ) + }; let mut invalidated_self = false; let mut matched = false; @@ -809,6 +825,7 @@ where let next_invalidation = Invalidation { selector: invalidation.selector, + scope: invalidation.scope, offset: next_combinator_offset + 1, matched_by_any_previous: false, }; diff --git a/components/style/invalidation/element/state_and_attributes.rs b/components/style/invalidation/element/state_and_attributes.rs index cb3a5525e398..e678413218ce 100644 --- a/components/style/invalidation/element/state_and_attributes.rs +++ b/components/style/invalidation/element/state_and_attributes.rs @@ -457,6 +457,7 @@ where let invalidation = Invalidation::new( &dependency.selector, + self.matching_context.current_host.clone(), dependency.selector.len() - dependency.selector_offset + 1, ); diff --git a/components/style/rule_collector.rs b/components/style/rule_collector.rs index 031a92ca7286..c0197572e0a6 100644 --- a/components/style/rule_collector.rs +++ b/components/style/rule_collector.rs @@ -4,6 +4,7 @@ //! Collects a series of applicable rules for a given element. +use crate::Atom; use crate::applicable_declarations::{ApplicableDeclarationBlock, ApplicableDeclarationList}; use crate::dom::{TElement, TNode, TShadowRoot}; use crate::properties::{AnimationRules, PropertyDeclarationBlock}; @@ -328,52 +329,75 @@ where return; } - let shadow = match self.rule_hash_target.containing_shadow() { + let mut inner_shadow = match self.rule_hash_target.containing_shadow() { Some(s) => s, None => return, }; - let host = shadow.host(); - let containing_shadow = host.containing_shadow(); - let part_rules = match containing_shadow { - Some(shadow) => shadow - .style_data() - .and_then(|data| data.part_rules(self.pseudo_element)), - None => self - .stylist - .cascade_data() - .borrow_for_origin(Origin::Author) - .part_rules(self.pseudo_element), - }; + let mut shadow_cascade_order = ShadowCascadeOrder::for_innermost_containing_tree(); + + let mut parts = SmallVec::<[Atom; 3]>::new(); + self.rule_hash_target.each_part(|p| parts.push(p.clone())); - // TODO(emilio): Cascade order will need to increment for each tree when - // we implement forwarding. - let shadow_cascade_order = ShadowCascadeOrder::for_innermost_containing_tree(); - if let Some(part_rules) = part_rules { - let containing_host = containing_shadow.map(|s| s.host()); - let element = self.element; - let rule_hash_target = self.rule_hash_target; - let rules = &mut self.rules; - let flags_setter = &mut self.flags_setter; - let cascade_level = CascadeLevel::AuthorNormal { - shadow_cascade_order, + loop { + if parts.is_empty() { + return; + } + + let outer_shadow = inner_shadow.host().containing_shadow(); + let part_rules = match outer_shadow { + Some(shadow) => shadow + .style_data() + .and_then(|data| data.part_rules(self.pseudo_element)), + None => self + .stylist + .cascade_data() + .borrow_for_origin(Origin::Author) + .part_rules(self.pseudo_element), }; - let start = rules.len(); - self.context.with_shadow_host(containing_host, |context| { - rule_hash_target.each_part(|p| { - if let Some(part_rules) = part_rules.get(p) { - SelectorMap::get_matching_rules( - element, - &part_rules, - rules, - context, - flags_setter, - cascade_level, - ); + + if let Some(part_rules) = part_rules { + let containing_host = outer_shadow.map(|s| s.host()); + let element = self.element; + let rules = &mut self.rules; + let flags_setter = &mut self.flags_setter; + let cascade_level = CascadeLevel::AuthorNormal { + shadow_cascade_order, + }; + let start = rules.len(); + self.context.with_shadow_host(containing_host, |context| { + for p in &parts { + if let Some(part_rules) = part_rules.get(p) { + SelectorMap::get_matching_rules( + element, + &part_rules, + rules, + context, + flags_setter, + cascade_level, + ); + } } }); + sort_rules_from(rules, start); + shadow_cascade_order.inc(); + } + + let inner_shadow_host = inner_shadow.host(); + + inner_shadow = match outer_shadow { + Some(s) => s, + None => break, // Nowhere to export to. + }; + + parts.retain(|part| { + let exported_part = match inner_shadow_host.exported_part(part) { + Some(part) => part, + None => return false, + }; + std::mem::replace(part, exported_part); + true }); - sort_rules_from(rules, start); } }