From fb715e3c6d5d88cda7f10da2411b21cc1bd0b2c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Wed, 14 Mar 2018 10:38:45 -0400 Subject: [PATCH] servo: Merge #20243 - style: add infrastructure to match the :host selector (from emilio:host-selector-on-the-way); r=SimonSapin Source-Repo: https://github.com/servo/servo Source-Revision: 148beb4ea5f8f1680e694ac48045a632da58269c --HG-- extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear extra : subtree_revision : e9d1c9e4dd54d3c9d5bd1f0a2805858456981598 --- servo/components/layout_thread/dom_wrapper.rs | 16 ++++++ servo/components/script/dom/element.rs | 9 +++- servo/components/selectors/matching.rs | 50 ++++++++++++++++--- servo/components/selectors/parser.rs | 43 +++++++++++----- servo/components/selectors/tree.rs | 6 +++ .../components/style/gecko/selector_parser.rs | 20 +++++--- servo/components/style/gecko/wrapper.rs | 13 ++++- .../invalidation/element/element_wrapper.rs | 29 +++++++---- .../components/style/servo/selector_parser.rs | 43 ++++++++++------ 9 files changed, 178 insertions(+), 51 deletions(-) diff --git a/servo/components/layout_thread/dom_wrapper.rs b/servo/components/layout_thread/dom_wrapper.rs index 5c5fc4be0526..55bacd037cbd 100644 --- a/servo/components/layout_thread/dom_wrapper.rs +++ b/servo/components/layout_thread/dom_wrapper.rs @@ -659,6 +659,14 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> { } } + fn parent_node_is_shadow_root(&self) -> bool { + false + } + + fn containing_shadow_host(&self) -> Option { + None + } + fn first_child_element(&self) -> Option> { self.as_node().dom_children().filter_map(|n| n.as_element()).next() } @@ -1199,6 +1207,14 @@ impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> { None } + fn parent_node_is_shadow_root(&self) -> bool { + false + } + + fn containing_shadow_host(&self) -> Option { + None + } + fn first_child_element(&self) -> Option { warn!("ServoThreadSafeLayoutElement::first_child_element called"); None diff --git a/servo/components/script/dom/element.rs b/servo/components/script/dom/element.rs index 5060492160ba..a53446fd4a37 100644 --- a/servo/components/script/dom/element.rs +++ b/servo/components/script/dom/element.rs @@ -2570,6 +2570,14 @@ impl<'a> SelectorsElement for DomRoot { self.upcast::().GetParentElement() } + fn parent_node_is_shadow_root(&self) -> bool { + false + } + + fn containing_shadow_host(&self) -> Option { + None + } + fn match_pseudo_element( &self, _pseudo: &PseudoElement, @@ -2578,7 +2586,6 @@ impl<'a> SelectorsElement for DomRoot { false } - fn first_child_element(&self) -> Option> { self.node.child_elements().next() } diff --git a/servo/components/selectors/matching.rs b/servo/components/selectors/matching.rs index 6cb9acf59fea..7244868285b8 100644 --- a/servo/components/selectors/matching.rs +++ b/servo/components/selectors/matching.rs @@ -6,7 +6,7 @@ use attr::{ParsedAttrSelectorOperation, AttrSelectorOperation, NamespaceConstrai use bloom::{BLOOM_HASH_MASK, BloomFilter}; use nth_index_cache::NthIndexCacheInner; use parser::{AncestorHashes, Combinator, Component, LocalName}; -use parser::{Selector, SelectorImpl, SelectorIter, SelectorList}; +use parser::{Selector, SelectorImpl, SelectorIter, SelectorList, NonTSPseudoClass}; use std::borrow::Borrow; use std::iter; use tree::Element; @@ -404,7 +404,7 @@ fn matches_hover_and_active_quirk( Component::LastOfType | Component::OnlyOfType => false, Component::NonTSPseudoClass(ref pseudo_class) => { - Impl::is_active_or_hover(pseudo_class) + pseudo_class.is_active_or_hover() }, _ => true, } @@ -427,6 +427,7 @@ enum Rightmost { fn next_element_for_combinator( element: &E, combinator: Combinator, + selector: &SelectorIter, ) -> Option where E: Element, @@ -442,7 +443,42 @@ where return None; } - element.parent_element() + match element.parent_element() { + Some(e) => return Some(e), + None => {} + } + + if !element.parent_node_is_shadow_root() { + return None; + } + + // https://drafts.csswg.org/css-scoping/#host-element-in-tree: + // + // For the purpose of Selectors, a shadow host also appears in + // its shadow tree, with the contents of the shadow tree treated + // as its children. (In other words, the shadow host is treated as + // replacing the shadow root node.) + // + // and also: + // + // When considered within its own shadow trees, the shadow host is + // featureless. Only the :host, :host(), and :host-context() + // 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 { + return None; + } + + element.containing_shadow_host() } Combinator::SlotAssignment => { debug_assert!(element.assigned_slot().map_or(true, |s| s.is_html_slot_element())); @@ -502,7 +538,8 @@ where } }; - let mut next_element = next_element_for_combinator(element, combinator); + let mut next_element = + next_element_for_combinator(element, combinator, &selector_iter); // Stop matching :visited as soon as we find a link, or a combinator for // something that isn't an ancestor. @@ -565,7 +602,8 @@ where visited_handling = VisitedHandlingMode::AllLinksUnvisited; } - next_element = next_element_for_combinator(&element, combinator); + next_element = + next_element_for_combinator(&element, combinator, &selector_iter); } } @@ -753,7 +791,7 @@ where Component::NonTSPseudoClass(ref pc) => { if context.matches_hover_and_active_quirk == MatchesHoverAndActiveQuirk::Yes && !context.shared.is_nested() && - E::Impl::is_active_or_hover(pc) && + pc.is_active_or_hover() && !element.is_link() { return false; diff --git a/servo/components/selectors/parser.rs b/servo/components/selectors/parser.rs index 91981562366e..5fd73ce8d735 100644 --- a/servo/components/selectors/parser.rs +++ b/servo/components/selectors/parser.rs @@ -36,6 +36,18 @@ pub trait PseudoElement : Sized + ToCss { } } +/// A trait that represents a pseudo-class. +pub trait NonTSPseudoClass : Sized + ToCss { + /// The `SelectorImpl` this pseudo-element is used for. + type Impl: SelectorImpl; + + /// 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 { if let Some(first_uppercase) = s.bytes().position(|byte| byte >= b'A' && byte <= b'Z') { let mut string = s.to_owned(); @@ -96,14 +108,10 @@ macro_rules! with_all_bounds { /// non tree-structural pseudo-classes /// (see: https://drafts.csswg.org/selectors/#structural-pseudos) - type NonTSPseudoClass: $($CommonBounds)* + Sized + ToCss; + type NonTSPseudoClass: $($CommonBounds)* + NonTSPseudoClass; /// pseudo-elements type PseudoElement: $($CommonBounds)* + PseudoElement; - - /// Returns whether the given pseudo class is :active or :hover. - #[inline] - fn is_active_or_hover(pseudo_class: &Self::NonTSPseudoClass) -> bool; } } } @@ -356,7 +364,10 @@ impl Visit for Selector where Impl::NonTSPseudoClass: } } -impl Visit for Component where Impl::NonTSPseudoClass: Visit { +impl Visit for Component +where + Impl::NonTSPseudoClass: Visit +{ type Impl = Impl; fn visit(&self, visitor: &mut V) -> bool @@ -1981,6 +1992,20 @@ pub mod tests { } } + impl parser::NonTSPseudoClass for PseudoClass { + type Impl = DummySelectorImpl; + + #[inline] + fn is_active_or_hover(&self) -> bool { + matches!(*self, PseudoClass::Active | PseudoClass::Hover) + } + + #[inline] + fn is_host(&self) -> bool { + false + } + } + impl ToCss for PseudoClass { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { @@ -2045,12 +2070,6 @@ pub mod tests { type BorrowedNamespaceUrl = DummyAtom; type NonTSPseudoClass = PseudoClass; type PseudoElement = PseudoElement; - - #[inline] - fn is_active_or_hover(pseudo_class: &Self::NonTSPseudoClass) -> bool { - matches!(*pseudo_class, PseudoClass::Active | - PseudoClass::Hover) - } } #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] diff --git a/servo/components/selectors/tree.rs b/servo/components/selectors/tree.rs index c9baeb1316d4..00799f92edd0 100644 --- a/servo/components/selectors/tree.rs +++ b/servo/components/selectors/tree.rs @@ -31,6 +31,12 @@ pub trait Element: Sized + Clone + Debug { fn parent_element(&self) -> Option; + /// Whether the parent node of this element is a shadow root. + fn parent_node_is_shadow_root(&self) -> bool; + + /// The host of the containing shadow root, if any. + fn containing_shadow_host(&self) -> Option; + /// The parent of a given pseudo-element, after matching a pseudo-element /// selector. /// diff --git a/servo/components/style/gecko/selector_parser.rs b/servo/components/style/gecko/selector_parser.rs index 434d2fef414b..b6e99b2083f1 100644 --- a/servo/components/style/gecko/selector_parser.rs +++ b/servo/components/style/gecko/selector_parser.rs @@ -274,6 +274,20 @@ impl NonTSPseudoClass { } } +impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass { + type Impl = SelectorImpl; + + #[inline] + 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. #[derive(Clone, Debug, Eq, PartialEq)] pub struct SelectorImpl; @@ -291,12 +305,6 @@ impl ::selectors::SelectorImpl for SelectorImpl { type PseudoElement = PseudoElement; type NonTSPseudoClass = NonTSPseudoClass; - - #[inline] - fn is_active_or_hover(pseudo_class: &Self::NonTSPseudoClass) -> bool { - matches!(*pseudo_class, NonTSPseudoClass::Active | - NonTSPseudoClass::Hover) - } } impl<'a> SelectorParser<'a> { diff --git a/servo/components/style/gecko/wrapper.rs b/servo/components/style/gecko/wrapper.rs index e774b1cfb336..db3188bd0bf4 100644 --- a/servo/components/style/gecko/wrapper.rs +++ b/servo/components/style/gecko/wrapper.rs @@ -1822,12 +1822,21 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { #[inline] fn parent_element(&self) -> Option { - // FIXME(emilio): This will need to jump across if the parent node is a - // shadow root to get the shadow host. let parent_node = self.as_node().parent_node(); parent_node.and_then(|n| n.as_element()) } + #[inline] + fn parent_node_is_shadow_root(&self) -> bool { + self.as_node().parent_node().map_or(false, |p| p.is_shadow_root()) + } + + #[inline] + fn containing_shadow_host(&self) -> Option { + let shadow = self.containing_shadow()?; + Some(shadow.host()) + } + #[inline] fn pseudo_element_originating_element(&self) -> Option { debug_assert!(self.implemented_pseudo_element().is_some()); diff --git a/servo/components/style/invalidation/element/element_wrapper.rs b/servo/components/style/invalidation/element/element_wrapper.rs index 5483c2756972..29da4d7cb2fd 100644 --- a/servo/components/style/invalidation/element/element_wrapper.rs +++ b/servo/components/style/invalidation/element/element_wrapper.rs @@ -271,28 +271,37 @@ where } fn parent_element(&self) -> Option { - self.element.parent_element() - .map(|e| ElementWrapper::new(e, self.snapshot_map)) + let parent = self.element.parent_element()?; + Some(Self::new(parent, self.snapshot_map)) + } + + fn parent_node_is_shadow_root(&self) -> bool { + self.element.parent_node_is_shadow_root() + } + + fn containing_shadow_host(&self) -> Option { + let host = self.element.containing_shadow_host()?; + Some(Self::new(host, self.snapshot_map)) } fn first_child_element(&self) -> Option { - self.element.first_child_element() - .map(|e| ElementWrapper::new(e, self.snapshot_map)) + let child = self.element.first_child_element()?; + Some(Self::new(child, self.snapshot_map)) } fn last_child_element(&self) -> Option { - self.element.last_child_element() - .map(|e| ElementWrapper::new(e, self.snapshot_map)) + let child = self.element.last_child_element()?; + Some(Self::new(child, self.snapshot_map)) } fn prev_sibling_element(&self) -> Option { - self.element.prev_sibling_element() - .map(|e| ElementWrapper::new(e, self.snapshot_map)) + let sibling = self.element.prev_sibling_element()?; + Some(Self::new(sibling, self.snapshot_map)) } fn next_sibling_element(&self) -> Option { - self.element.next_sibling_element() - .map(|e| ElementWrapper::new(e, self.snapshot_map)) + let sibling = self.element.next_sibling_element()?; + Some(Self::new(sibling, self.snapshot_map)) } #[inline] diff --git a/servo/components/style/servo/selector_parser.rs b/servo/components/style/servo/selector_parser.rs index 08d3401cebd3..dd2e4f20c9f8 100644 --- a/servo/components/style/servo/selector_parser.rs +++ b/servo/components/style/servo/selector_parser.rs @@ -308,6 +308,20 @@ pub enum NonTSPseudoClass { Visited, } +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) + } +} + impl ToCss for NonTSPseudoClass { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { use self::NonTSPseudoClass::*; @@ -423,20 +437,17 @@ impl ::selectors::SelectorImpl for SelectorImpl { type NamespaceUrl = Namespace; type BorrowedLocalName = LocalName; type BorrowedNamespaceUrl = Namespace; - - #[inline] - fn is_active_or_hover(pseudo_class: &Self::NonTSPseudoClass) -> bool { - matches!(*pseudo_class, NonTSPseudoClass::Active | - NonTSPseudoClass::Hover) - } } impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { type Impl = SelectorImpl; type Error = StyleParseErrorKind<'i>; - fn parse_non_ts_pseudo_class(&self, location: SourceLocation, name: CowRcStr<'i>) - -> Result> { + fn parse_non_ts_pseudo_class( + &self, + location: SourceLocation, + name: CowRcStr<'i>, + ) -> Result> { use self::NonTSPseudoClass::*; let pseudo_class = match_ignore_ascii_case! { &name, "active" => Active, @@ -468,10 +479,11 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { Ok(pseudo_class) } - fn parse_non_ts_functional_pseudo_class<'t>(&self, - name: CowRcStr<'i>, - parser: &mut CssParser<'i, 't>) - -> Result> { + fn parse_non_ts_functional_pseudo_class<'t>( + &self, + name: CowRcStr<'i>, + parser: &mut CssParser<'i, 't>, + ) -> Result> { use self::NonTSPseudoClass::*; let pseudo_class = match_ignore_ascii_case!{ &name, "lang" => { @@ -489,8 +501,11 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { Ok(pseudo_class) } - fn parse_pseudo_element(&self, location: SourceLocation, name: CowRcStr<'i>) - -> Result> { + fn parse_pseudo_element( + &self, + location: SourceLocation, + name: CowRcStr<'i>, + ) -> Result> { use self::PseudoElement::*; let pseudo_element = match_ignore_ascii_case! { &name, "before" => Before,