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
This commit is contained in:
Emilio Cobos Álvarez 2018-03-14 10:38:45 -04:00
Родитель 7acf28ebb0
Коммит fb715e3c6d
9 изменённых файлов: 178 добавлений и 51 удалений

Просмотреть файл

@ -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<Self> {
None
}
fn first_child_element(&self) -> Option<ServoLayoutElement<'le>> { fn first_child_element(&self) -> Option<ServoLayoutElement<'le>> {
self.as_node().dom_children().filter_map(|n| n.as_element()).next() self.as_node().dom_children().filter_map(|n| n.as_element()).next()
} }
@ -1199,6 +1207,14 @@ impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> {
None None
} }
fn parent_node_is_shadow_root(&self) -> bool {
false
}
fn containing_shadow_host(&self) -> Option<Self> {
None
}
fn first_child_element(&self) -> Option<Self> { fn first_child_element(&self) -> Option<Self> {
warn!("ServoThreadSafeLayoutElement::first_child_element called"); warn!("ServoThreadSafeLayoutElement::first_child_element called");
None None

Просмотреть файл

@ -2570,6 +2570,14 @@ impl<'a> SelectorsElement for DomRoot<Element> {
self.upcast::<Node>().GetParentElement() self.upcast::<Node>().GetParentElement()
} }
fn parent_node_is_shadow_root(&self) -> bool {
false
}
fn containing_shadow_host(&self) -> Option<Self> {
None
}
fn match_pseudo_element( fn match_pseudo_element(
&self, &self,
_pseudo: &PseudoElement, _pseudo: &PseudoElement,
@ -2578,7 +2586,6 @@ impl<'a> SelectorsElement for DomRoot<Element> {
false false
} }
fn first_child_element(&self) -> Option<DomRoot<Element>> { fn first_child_element(&self) -> Option<DomRoot<Element>> {
self.node.child_elements().next() self.node.child_elements().next()
} }

Просмотреть файл

@ -6,7 +6,7 @@ use attr::{ParsedAttrSelectorOperation, AttrSelectorOperation, NamespaceConstrai
use bloom::{BLOOM_HASH_MASK, BloomFilter}; use bloom::{BLOOM_HASH_MASK, BloomFilter};
use nth_index_cache::NthIndexCacheInner; use nth_index_cache::NthIndexCacheInner;
use parser::{AncestorHashes, Combinator, Component, LocalName}; 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::borrow::Borrow;
use std::iter; use std::iter;
use tree::Element; use tree::Element;
@ -404,7 +404,7 @@ fn matches_hover_and_active_quirk<Impl: SelectorImpl>(
Component::LastOfType | Component::LastOfType |
Component::OnlyOfType => false, Component::OnlyOfType => false,
Component::NonTSPseudoClass(ref pseudo_class) => { Component::NonTSPseudoClass(ref pseudo_class) => {
Impl::is_active_or_hover(pseudo_class) pseudo_class.is_active_or_hover()
}, },
_ => true, _ => true,
} }
@ -427,6 +427,7 @@ enum Rightmost {
fn next_element_for_combinator<E>( fn next_element_for_combinator<E>(
element: &E, element: &E,
combinator: Combinator, combinator: Combinator,
selector: &SelectorIter<E::Impl>,
) -> Option<E> ) -> Option<E>
where where
E: Element, E: Element,
@ -442,7 +443,42 @@ where
return None; 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 => { Combinator::SlotAssignment => {
debug_assert!(element.assigned_slot().map_or(true, |s| s.is_html_slot_element())); 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 // Stop matching :visited as soon as we find a link, or a combinator for
// something that isn't an ancestor. // something that isn't an ancestor.
@ -565,7 +602,8 @@ where
visited_handling = VisitedHandlingMode::AllLinksUnvisited; 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) => { Component::NonTSPseudoClass(ref pc) => {
if context.matches_hover_and_active_quirk == MatchesHoverAndActiveQuirk::Yes && if context.matches_hover_and_active_quirk == MatchesHoverAndActiveQuirk::Yes &&
!context.shared.is_nested() && !context.shared.is_nested() &&
E::Impl::is_active_or_hover(pc) && pc.is_active_or_hover() &&
!element.is_link() !element.is_link()
{ {
return false; return false;

Просмотреть файл

@ -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<str> { fn to_ascii_lowercase(s: &str) -> Cow<str> {
if let Some(first_uppercase) = s.bytes().position(|byte| byte >= b'A' && byte <= b'Z') { if let Some(first_uppercase) = s.bytes().position(|byte| byte >= b'A' && byte <= b'Z') {
let mut string = s.to_owned(); let mut string = s.to_owned();
@ -96,14 +108,10 @@ macro_rules! with_all_bounds {
/// non tree-structural pseudo-classes /// non tree-structural pseudo-classes
/// (see: https://drafts.csswg.org/selectors/#structural-pseudos) /// (see: https://drafts.csswg.org/selectors/#structural-pseudos)
type NonTSPseudoClass: $($CommonBounds)* + Sized + ToCss; type NonTSPseudoClass: $($CommonBounds)* + NonTSPseudoClass<Impl = Self>;
/// pseudo-elements /// pseudo-elements
type PseudoElement: $($CommonBounds)* + PseudoElement<Impl = Self>; type PseudoElement: $($CommonBounds)* + PseudoElement<Impl = Self>;
/// 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<Impl: SelectorImpl> Visit for Selector<Impl> where Impl::NonTSPseudoClass:
} }
} }
impl<Impl: SelectorImpl> Visit for Component<Impl> where Impl::NonTSPseudoClass: Visit<Impl=Impl> { impl<Impl: SelectorImpl> Visit for Component<Impl>
where
Impl::NonTSPseudoClass: Visit<Impl=Impl>
{
type Impl = Impl; type Impl = Impl;
fn visit<V>(&self, visitor: &mut V) -> bool fn visit<V>(&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 { impl ToCss for PseudoClass {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self { match *self {
@ -2045,12 +2070,6 @@ pub mod tests {
type BorrowedNamespaceUrl = DummyAtom; type BorrowedNamespaceUrl = DummyAtom;
type NonTSPseudoClass = PseudoClass; type NonTSPseudoClass = PseudoClass;
type PseudoElement = PseudoElement; 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)] #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]

Просмотреть файл

@ -31,6 +31,12 @@ pub trait Element: Sized + Clone + Debug {
fn parent_element(&self) -> Option<Self>; fn parent_element(&self) -> Option<Self>;
/// 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<Self>;
/// The parent of a given pseudo-element, after matching a pseudo-element /// The parent of a given pseudo-element, after matching a pseudo-element
/// selector. /// selector.
/// ///

Просмотреть файл

@ -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. /// The dummy struct we use to implement our selector parsing.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct SelectorImpl; pub struct SelectorImpl;
@ -291,12 +305,6 @@ impl ::selectors::SelectorImpl for SelectorImpl {
type PseudoElement = PseudoElement; type PseudoElement = PseudoElement;
type NonTSPseudoClass = NonTSPseudoClass; 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> { impl<'a> SelectorParser<'a> {

Просмотреть файл

@ -1822,12 +1822,21 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
#[inline] #[inline]
fn parent_element(&self) -> Option<Self> { fn parent_element(&self) -> Option<Self> {
// 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(); let parent_node = self.as_node().parent_node();
parent_node.and_then(|n| n.as_element()) 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<Self> {
let shadow = self.containing_shadow()?;
Some(shadow.host())
}
#[inline] #[inline]
fn pseudo_element_originating_element(&self) -> Option<Self> { fn pseudo_element_originating_element(&self) -> Option<Self> {
debug_assert!(self.implemented_pseudo_element().is_some()); debug_assert!(self.implemented_pseudo_element().is_some());

Просмотреть файл

@ -271,28 +271,37 @@ where
} }
fn parent_element(&self) -> Option<Self> { fn parent_element(&self) -> Option<Self> {
self.element.parent_element() let parent = self.element.parent_element()?;
.map(|e| ElementWrapper::new(e, self.snapshot_map)) 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<Self> {
let host = self.element.containing_shadow_host()?;
Some(Self::new(host, self.snapshot_map))
} }
fn first_child_element(&self) -> Option<Self> { fn first_child_element(&self) -> Option<Self> {
self.element.first_child_element() let child = self.element.first_child_element()?;
.map(|e| ElementWrapper::new(e, self.snapshot_map)) Some(Self::new(child, self.snapshot_map))
} }
fn last_child_element(&self) -> Option<Self> { fn last_child_element(&self) -> Option<Self> {
self.element.last_child_element() let child = self.element.last_child_element()?;
.map(|e| ElementWrapper::new(e, self.snapshot_map)) Some(Self::new(child, self.snapshot_map))
} }
fn prev_sibling_element(&self) -> Option<Self> { fn prev_sibling_element(&self) -> Option<Self> {
self.element.prev_sibling_element() let sibling = self.element.prev_sibling_element()?;
.map(|e| ElementWrapper::new(e, self.snapshot_map)) Some(Self::new(sibling, self.snapshot_map))
} }
fn next_sibling_element(&self) -> Option<Self> { fn next_sibling_element(&self) -> Option<Self> {
self.element.next_sibling_element() let sibling = self.element.next_sibling_element()?;
.map(|e| ElementWrapper::new(e, self.snapshot_map)) Some(Self::new(sibling, self.snapshot_map))
} }
#[inline] #[inline]

Просмотреть файл

@ -308,6 +308,20 @@ pub enum NonTSPseudoClass {
Visited, 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 { impl ToCss for NonTSPseudoClass {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
use self::NonTSPseudoClass::*; use self::NonTSPseudoClass::*;
@ -423,20 +437,17 @@ impl ::selectors::SelectorImpl for SelectorImpl {
type NamespaceUrl = Namespace; type NamespaceUrl = Namespace;
type BorrowedLocalName = LocalName; type BorrowedLocalName = LocalName;
type BorrowedNamespaceUrl = Namespace; 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> { impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
type Impl = SelectorImpl; type Impl = SelectorImpl;
type Error = StyleParseErrorKind<'i>; type Error = StyleParseErrorKind<'i>;
fn parse_non_ts_pseudo_class(&self, location: SourceLocation, name: CowRcStr<'i>) fn parse_non_ts_pseudo_class(
-> Result<NonTSPseudoClass, ParseError<'i>> { &self,
location: SourceLocation,
name: CowRcStr<'i>,
) -> Result<NonTSPseudoClass, ParseError<'i>> {
use self::NonTSPseudoClass::*; use self::NonTSPseudoClass::*;
let pseudo_class = match_ignore_ascii_case! { &name, let pseudo_class = match_ignore_ascii_case! { &name,
"active" => Active, "active" => Active,
@ -468,10 +479,11 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
Ok(pseudo_class) Ok(pseudo_class)
} }
fn parse_non_ts_functional_pseudo_class<'t>(&self, fn parse_non_ts_functional_pseudo_class<'t>(
name: CowRcStr<'i>, &self,
parser: &mut CssParser<'i, 't>) name: CowRcStr<'i>,
-> Result<NonTSPseudoClass, ParseError<'i>> { parser: &mut CssParser<'i, 't>,
) -> Result<NonTSPseudoClass, ParseError<'i>> {
use self::NonTSPseudoClass::*; use self::NonTSPseudoClass::*;
let pseudo_class = match_ignore_ascii_case!{ &name, let pseudo_class = match_ignore_ascii_case!{ &name,
"lang" => { "lang" => {
@ -489,8 +501,11 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
Ok(pseudo_class) Ok(pseudo_class)
} }
fn parse_pseudo_element(&self, location: SourceLocation, name: CowRcStr<'i>) fn parse_pseudo_element(
-> Result<PseudoElement, ParseError<'i>> { &self,
location: SourceLocation,
name: CowRcStr<'i>,
) -> Result<PseudoElement, ParseError<'i>> {
use self::PseudoElement::*; use self::PseudoElement::*;
let pseudo_element = match_ignore_ascii_case! { &name, let pseudo_element = match_ignore_ascii_case! { &name,
"before" => Before, "before" => Before,