aboutsummaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
Diffstat (limited to 'components')
-rw-r--r--components/selectors/builder.rs17
-rw-r--r--components/selectors/matching.rs19
-rw-r--r--components/selectors/parser.rs174
-rw-r--r--components/selectors/tree.rs7
4 files changed, 175 insertions, 42 deletions
diff --git a/components/selectors/builder.rs b/components/selectors/builder.rs
index c996a6a6491..b3d61b34706 100644
--- a/components/selectors/builder.rs
+++ b/components/selectors/builder.rs
@@ -264,12 +264,23 @@ fn complex_selector_specificity<Impl>(mut iter: slice::Iter<Component<Impl>>)
-> Specificity
where Impl: SelectorImpl
{
- fn simple_selector_specificity<Impl>(simple_selector: &Component<Impl>,
- specificity: &mut Specificity)
- where Impl: SelectorImpl
+ fn simple_selector_specificity<Impl>(
+ simple_selector: &Component<Impl>,
+ specificity: &mut Specificity,
+ )
+ where
+ Impl: SelectorImpl
{
match *simple_selector {
Component::Combinator(..) => unreachable!(),
+ // FIXME(emilio): Spec doesn't define any particular specificity for
+ // ::slotted(), so apply the general rule for pseudos per:
+ //
+ // https://github.com/w3c/csswg-drafts/issues/1915
+ //
+ // Though other engines compute it dynamically, so maybe we should
+ // do that instead, eventually.
+ Component::Slotted(..) |
Component::PseudoElement(..) |
Component::LocalName(..) => {
specificity.element_selectors += 1
diff --git a/components/selectors/matching.rs b/components/selectors/matching.rs
index a2d85e8986d..d49d24ee075 100644
--- a/components/selectors/matching.rs
+++ b/components/selectors/matching.rs
@@ -393,6 +393,9 @@ where
element.parent_element()
}
+ Combinator::SlotAssignment => {
+ element.assigned_slot()
+ }
Combinator::PseudoElement => {
element.pseudo_element_originating_element()
}
@@ -453,6 +456,7 @@ where
}
Combinator::Child |
Combinator::Descendant |
+ Combinator::SlotAssignment |
Combinator::PseudoElement => {
SelectorMatchingResult::NotMatchedGlobally
}
@@ -541,6 +545,21 @@ where
{
match *selector {
Component::Combinator(_) => unreachable!(),
+ Component::Slotted(ref selectors) => {
+ context.shared.nesting_level += 1;
+ let result =
+ element.assigned_slot().is_some() &&
+ selectors.iter().any(|s| {
+ matches_complex_selector(
+ s.iter(),
+ element,
+ context.shared,
+ flags_setter,
+ )
+ });
+ context.shared.nesting_level -= 1;
+ result
+ }
Component::PseudoElement(ref pseudo) => {
element.match_pseudo_element(pseudo, context.shared)
}
diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs
index cf84648760b..91417a8cf62 100644
--- a/components/selectors/parser.rs
+++ b/components/selectors/parser.rs
@@ -132,6 +132,11 @@ pub trait Parser<'i> {
is_css2_pseudo_element(name)
}
+ /// Whether to parse the `::slotted()` pseudo-element.
+ fn parse_slotted(&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(
@@ -353,6 +358,13 @@ impl<Impl: SelectorImpl> SelectorMethods for Component<Impl> {
}
match *self {
+ Slotted(ref selectors) => {
+ for selector in selectors.iter() {
+ if !selector.visit(visitor) {
+ return false;
+ }
+ }
+ }
Negation(ref negated) => {
for component in negated.iter() {
if !component.visit(visitor) {
@@ -658,15 +670,20 @@ pub enum Combinator {
/// combinator for this, we will need to fix up the way hashes are computed
/// for revalidation selectors.
PseudoElement,
+ /// Another combinator used for ::slotted(), which represent the jump from
+ /// a node to its assigned slot.
+ SlotAssignment,
}
impl Combinator {
/// Returns true if this combinator is a child or descendant combinator.
#[inline]
pub fn is_ancestor(&self) -> bool {
- matches!(*self, Combinator::Child |
- Combinator::Descendant |
- Combinator::PseudoElement)
+ matches!(*self,
+ Combinator::Child |
+ Combinator::Descendant |
+ Combinator::PseudoElement |
+ Combinator::SlotAssignment)
}
/// Returns true if this combinator is a pseudo-element combinator.
@@ -716,16 +733,16 @@ pub enum Component<Impl: SelectorImpl> {
// Use a Box in the less common cases with more data to keep size_of::<Component>() small.
AttributeOther(Box<AttrSelectorWithNamespace<Impl>>),
- // Pseudo-classes
- //
- // CSS3 Negation only takes a simple simple selector, but we still need to
- // treat it as a compound selector because it might be a type selector which
- // we represent as a namespace and a localname.
- //
- // Note: if/when we upgrade this to CSS4, which supports combinators, we
- // need to think about how this should interact with visit_complex_selector,
- // and what the consumers of those APIs should do about the presence of
- // combinators in negation.
+ /// Pseudo-classes
+ ///
+ /// CSS3 Negation only takes a simple simple selector, but we still need to
+ /// treat it as a compound selector because it might be a type selector
+ /// which we represent as a namespace and a localname.
+ ///
+ /// Note: if/when we upgrade this to CSS4, which supports combinators, we
+ /// need to think about how this should interact with
+ /// visit_complex_selector, and what the consumers of those APIs should do
+ /// about the presence of combinators in negation.
Negation(Box<[Component<Impl>]>),
FirstChild, LastChild, OnlyChild,
Root,
@@ -739,6 +756,13 @@ pub enum Component<Impl: SelectorImpl> {
LastOfType,
OnlyOfType,
NonTSPseudoClass(Impl::NonTSPseudoClass),
+ /// The ::slotted() pseudo-element (which isn't actually a pseudo-element,
+ /// and probably should be a pseudo-class):
+ ///
+ /// https://drafts.csswg.org/css-scoping/#slotted-pseudo
+ ///
+ /// The selectors here are compound selectors, that is, no combinators.
+ Slotted(Box<[Selector<Impl>]>),
PseudoElement(Impl::PseudoElement),
}
@@ -868,13 +892,15 @@ impl<Impl: SelectorImpl> ToCss for Selector<Impl> {
let mut perform_step_2 = true;
if first_non_namespace == compound.len() - 1 {
match (combinators.peek(), &compound[first_non_namespace]) {
- // We have to be careful here, because if there is a pseudo
- // element "combinator" there isn't really just the one
- // simple selector. Technically this compound selector
- // contains the pseudo element selector as
- // well--Combinator::PseudoElement doesn't exist in the
+ // We have to be careful here, because if there is a
+ // pseudo element "combinator" there isn't really just
+ // the one simple selector. Technically this compound
+ // selector contains the pseudo element selector as well
+ // -- Combinator::PseudoElement, just like
+ // Combinator::SlotAssignment, don't exist in the
// spec.
- (Some(&&Component::Combinator(Combinator::PseudoElement)), _) => (),
+ (Some(&&Component::Combinator(Combinator::PseudoElement)), _) |
+ (Some(&&Component::Combinator(Combinator::SlotAssignment)), _) => (),
(_, &Component::ExplicitUniversalType) => {
// Iterate over everything so we serialize the namespace
// too.
@@ -942,6 +968,7 @@ impl ToCss for Combinator {
Combinator::NextSibling => dest.write_str(" + "),
Combinator::LaterSibling => dest.write_str(" ~ "),
Combinator::PseudoElement => Ok(()),
+ Combinator::SlotAssignment => Ok(()),
}
}
}
@@ -970,6 +997,16 @@ impl<Impl: SelectorImpl> ToCss for Component<Impl> {
Combinator(ref c) => {
c.to_css(dest)
}
+ Slotted(ref selectors) => {
+ dest.write_str("::slotted(")?;
+ let mut iter = selectors.iter();
+ iter.next().expect("At least one selector").to_css(dest)?;
+ for other in iter {
+ dest.write_str(", ")?;
+ other.to_css(dest)?;
+ }
+ dest.write_char(')')
+ }
PseudoElement(ref p) => {
p.to_css(dest)
}
@@ -1120,10 +1157,14 @@ where
let mut builder = SelectorBuilder::default();
let mut has_pseudo_element;
+ let mut slotted;
'outer_loop: loop {
// Parse a sequence of simple selectors.
- has_pseudo_element = match parse_compound_selector(parser, input, &mut builder)? {
- Some(has_pseudo_element) => has_pseudo_element,
+ match parse_compound_selector(parser, input, &mut builder)? {
+ Some((has_pseudo, slot)) => {
+ has_pseudo_element = has_pseudo;
+ slotted = slot;
+ }
None => {
return Err(input.new_custom_error(if builder.has_combinators() {
SelectorParseErrorKind::DanglingCombinator
@@ -1132,7 +1173,8 @@ where
}))
}
};
- if has_pseudo_element {
+
+ if has_pseudo_element || slotted {
break;
}
@@ -1263,6 +1305,7 @@ where
enum SimpleSelectorParseResult<Impl: SelectorImpl> {
SimpleSelector(Component<Impl>),
PseudoElement(Impl::PseudoElement),
+ SlottedPseudo(Box<[Selector<Impl>]>),
}
#[derive(Debug)]
@@ -1575,7 +1618,8 @@ where
None => {
return Err(input.new_custom_error(SelectorParseErrorKind::EmptyNegation));
},
- Some(SimpleSelectorParseResult::PseudoElement(_)) => {
+ Some(SimpleSelectorParseResult::PseudoElement(_)) |
+ Some(SimpleSelectorParseResult::SlottedPseudo(_)) => {
return Err(input.new_custom_error(SelectorParseErrorKind::NonSimpleSelectorInNegation));
}
}
@@ -1592,12 +1636,13 @@ where
/// `Err(())` means invalid selector.
/// `Ok(None)` is an empty selector
///
-/// The boolean represent whether a pseudo-element has been parsed.
+/// The booleans represent whether a pseudo-element has been parsed, and whether
+/// ::slotted() has been parsed, respectively.
fn parse_compound_selector<'i, 't, P, Impl>(
parser: &P,
input: &mut CssParser<'i, 't>,
builder: &mut SelectorBuilder<Impl>,
-) -> Result<Option<bool>, ParseError<'i, P::Error>>
+) -> Result<Option<(bool, bool)>, ParseError<'i, P::Error>>
where
P: Parser<'i, Impl=Impl>,
Impl: SelectorImpl,
@@ -1605,6 +1650,7 @@ where
input.skip_whitespace();
let mut empty = true;
+ let mut slot = false;
if !parse_type_selector(parser, input, builder)? {
if let Some(url) = parser.default_namespace() {
// If there was no explicit type selector, but there is a
@@ -1618,13 +1664,18 @@ where
let mut pseudo = false;
loop {
- match parse_one_simple_selector(parser, input, /* inside_negation = */ false)? {
- None => break,
- Some(SimpleSelectorParseResult::SimpleSelector(s)) => {
+ let parse_result =
+ match parse_one_simple_selector(parser, input, /* inside_negation = */ false)? {
+ None => break,
+ Some(result) => result,
+ };
+
+ match parse_result {
+ SimpleSelectorParseResult::SimpleSelector(s) => {
builder.push_simple_selector(s);
empty = false
}
- Some(SimpleSelectorParseResult::PseudoElement(p)) => {
+ SimpleSelectorParseResult::PseudoElement(p) => {
// Try to parse state to its right. There are only 3 allowable
// state selectors that can go on pseudo-elements.
let mut state_selectors = SmallVec::<[Component<Impl>; 3]>::new();
@@ -1673,13 +1724,25 @@ where
empty = false;
break
}
+ SimpleSelectorParseResult::SlottedPseudo(selectors) => {
+ empty = false;
+ slot = true;
+ if !builder.is_empty() {
+ builder.push_combinator(Combinator::SlotAssignment);
+ }
+ builder.push_simple_selector(Component::Slotted(selectors));
+ // FIXME(emilio): ::slotted() should support ::before and
+ // ::after after it, so we shouldn't break, but we shouldn't
+ // push more type selectors either.
+ break;
+ }
}
}
if empty {
// An empty selector is invalid.
Ok(None)
} else {
- Ok(Some(pseudo))
+ Ok(Some((pseudo, slot)))
}
}
@@ -1790,14 +1853,33 @@ where
let is_pseudo_element = !is_single_colon ||
P::pseudo_element_allows_single_colon(&name);
if is_pseudo_element {
- let pseudo_element = if is_functional {
- input.parse_nested_block(|input| {
- P::parse_functional_pseudo_element(parser, name, input)
- })?
+ let parse_result = if is_functional {
+ if P::parse_slotted(parser) && name.eq_ignore_ascii_case("slotted") {
+ SimpleSelectorParseResult::SlottedPseudo(
+ input.parse_nested_block(|input| {
+ parse_compound_selector_list(
+ parser,
+ input,
+ )
+ })?
+ )
+ } else {
+ SimpleSelectorParseResult::PseudoElement(
+ input.parse_nested_block(|input| {
+ P::parse_functional_pseudo_element(
+ parser,
+ name,
+ input,
+ )
+ })?
+ )
+ }
} else {
- P::parse_pseudo_element(parser, location, name)?
+ SimpleSelectorParseResult::PseudoElement(
+ P::parse_pseudo_element(parser, location, name)?
+ )
};
- Ok(Some(SimpleSelectorParseResult::PseudoElement(pseudo_element)))
+ Ok(Some(parse_result))
} else {
let pseudo_class = if is_functional {
input.parse_nested_block(|input| {
@@ -1979,6 +2061,10 @@ pub mod tests {
type Impl = DummySelectorImpl;
type Error = SelectorParseErrorKind<'i>;
+ fn parse_slotted(&self) -> bool {
+ true
+ }
+
fn parse_non_ts_pseudo_class(
&self,
location: SourceLocation,
@@ -2085,9 +2171,9 @@ pub mod tests {
#[test]
fn test_parsing() {
- assert!(parse("").is_err()) ;
- assert!(parse(":lang(4)").is_err()) ;
- assert!(parse(":lang(en US)").is_err()) ;
+ assert!(parse("").is_err());
+ assert!(parse(":lang(4)").is_err());
+ assert!(parse(":lang(en US)").is_err());
assert_eq!(parse("EeÉ"), Ok(SelectorList::from_vec(vec!(
Selector::from_vec(vec!(
Component::LocalName(LocalName {
@@ -2394,6 +2480,16 @@ pub mod tests {
].into_boxed_slice()
)), specificity(0, 0, 0))
))));
+
+ assert!(parse("::slotted()").is_err());
+ assert!(parse("::slotted(div)").is_ok());
+ assert!(parse("::slotted(div).foo").is_err());
+ assert!(parse("::slotted(div + bar)").is_err());
+ assert!(parse("::slotted(div) + foo").is_err());
+ assert!(parse("div ::slotted(div)").is_ok());
+ assert!(parse("div + slot::slotted(div)").is_ok());
+ assert!(parse("div + slot::slotted(div.foo)").is_ok());
+ assert!(parse("div + slot::slotted(.foo, bar, .baz)").is_ok());
}
#[test]
diff --git a/components/selectors/tree.rs b/components/selectors/tree.rs
index d050c23a470..7a83feb83d0 100644
--- a/components/selectors/tree.rs
+++ b/components/selectors/tree.rs
@@ -84,6 +84,13 @@ pub trait Element: Sized + Clone + Debug {
/// Whether this element is a `link`.
fn is_link(&self) -> bool;
+ /// Returns the assigned <slot> element this element is assigned to.
+ ///
+ /// Necessary for the `::slotted` pseudo-class.
+ fn assigned_slot(&self) -> Option<Self> {
+ None
+ }
+
fn has_id(&self,
id: &<Self::Impl as SelectorImpl>::Identifier,
case_sensitivity: CaseSensitivity)