mirror of
https://github.com/servo/servo.git
synced 2025-06-17 12:54:28 +00:00
selectors: Add parsing support for ::slotted().
Without turning it on yet, of course. The reason why I didn't use the general PseudoElement mechanism is because this pseudo is a bit of its own thing, and I found easier to make ::selectors know about it (because you need to jump to the assigned slot) than the other way around. Also, we need to support ::slotted(..)::before and such, and supporting multiple pseudo-elements like that breaks some other invariants around the SelectorMap, and fixing those would require special-casing slotted a lot more in other parts of the code. Let me know if you think otherwise. I also don't like much the boolean tuple return value, but I plan to do some cleanup in the area in a bit, so it should go away soon, I'd hope.
This commit is contained in:
parent
0fa605d243
commit
7886e033aa
4 changed files with 175 additions and 42 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue