diff options
author | Emilio Cobos Álvarez <emilio@crisal.io> | 2020-10-29 18:03:54 +0000 |
---|---|---|
committer | Emilio Cobos Álvarez <emilio@crisal.io> | 2021-02-26 16:44:05 +0100 |
commit | 191d5226a38ca926d1f27551431e00e8728f1825 (patch) | |
tree | 439df20df1d593d8ce7a5dcc669e34171e744a37 /components/selectors/parser.rs | |
parent | 7e6d70f3149ba917d8a972f8ee6a53c50fd1215f (diff) | |
download | servo-191d5226a38ca926d1f27551431e00e8728f1825.tar.gz servo-191d5226a38ca926d1f27551431e00e8728f1825.zip |
style: Implement complex :not().
This fixes the failures in bug 1671573 and just works thanks to the
invalidation improvements I did for :is / :where.
Added a couple tests for invalidation which is the tricky bit. 001 is a
very straight-forward test, 002 is the :is test but with :is() replaced
by double-:not().
This also fixes default namespaces inside :is() / :where(), which are
supposed to get ignored, but aren't. Added tests for that and for the
pre-existing :not() behavior which Chrome doesn't quite get right.
Differential Revision: https://phabricator.services.mozilla.com/D94142
Diffstat (limited to 'components/selectors/parser.rs')
-rw-r--r-- | components/selectors/parser.rs | 247 |
1 files changed, 97 insertions, 150 deletions
diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index 4416461f46e..4492369037a 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -21,7 +21,6 @@ use std::borrow::{Borrow, Cow}; use std::fmt::{self, Debug, Display, Write}; use std::iter::Rev; use std::slice; -use thin_slice::ThinBoxedSlice; /// A trait that represents a pseudo-element. pub trait PseudoElement: Sized + ToCss { @@ -76,9 +75,10 @@ fn to_ascii_lowercase(s: &str) -> Cow<str> { bitflags! { /// Flags that indicate at which point of parsing a selector are we. struct SelectorParsingState: u8 { - /// Whether we're inside a negation. If we're inside a negation, we're - /// not allowed to add another negation or such, for example. - const INSIDE_NEGATION = 1 << 0; + /// Whether we should avoid adding default namespaces to selectors that + /// aren't type or universal selectors. + const SKIP_DEFAULT_NAMESPACE = 1 << 0; + /// Whether we've parsed a ::slotted() pseudo-element already. /// /// If so, then we can only parse a subset of pseudo-elements, and @@ -162,7 +162,6 @@ pub enum SelectorParseErrorKind<'i> { NoQualifiedNameInAttributeSelector(Token<'i>), EmptySelector, DanglingCombinator, - NonSimpleSelectorInNegation, NonCompoundSelector, NonPseudoElementAfterSlotted, InvalidPseudoElementAfterSlotted, @@ -180,7 +179,6 @@ pub enum SelectorParseErrorKind<'i> { InvalidQualNameInAttr(Token<'i>), ExplicitNamespaceUnexpectedToken(Token<'i>), ClassNeedsIdent(Token<'i>), - EmptyNegation, } macro_rules! with_all_bounds { @@ -1036,16 +1034,7 @@ pub enum Component<Impl: SelectorImpl> { AttributeOther(Box<AttrSelectorWithOptionalNamespace<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. - Negation(ThinBoxedSlice<Component<Impl>>), + Negation(Box<[Selector<Impl>]>), FirstChild, LastChild, OnlyChild, @@ -1121,10 +1110,9 @@ impl<Impl: SelectorImpl> Component<Impl> { pub fn maybe_allowed_after_pseudo_element(&self) -> bool { match *self { Component::NonTSPseudoClass(..) => true, - Component::Negation(ref components) => components - .iter() - .all(|c| c.maybe_allowed_after_pseudo_element()), - Component::Is(ref selectors) | Component::Where(ref selectors) => { + Component::Negation(ref selectors) | + Component::Is(ref selectors) | + Component::Where(ref selectors) => { selectors.iter().all(|selector| { selector .iter_raw_match_order() @@ -1148,9 +1136,13 @@ impl<Impl: SelectorImpl> Component<Impl> { *self ); match *self { - Component::Negation(ref components) => !components - .iter() - .all(|c| c.matches_for_stateless_pseudo_element()), + Component::Negation(ref selectors) => { + !selectors.iter().all(|selector| { + selector + .iter_raw_match_order() + .all(|c| c.matches_for_stateless_pseudo_element()) + }) + }, Component::Is(ref selectors) | Component::Where(ref selectors) => { selectors.iter().any(|selector| { selector @@ -1182,14 +1174,6 @@ impl<Impl: SelectorImpl> Component<Impl> { return false; } }, - Negation(ref negated) => { - for component in negated.iter() { - if !component.visit(visitor) { - return false; - } - } - }, - AttributeInNoNamespaceExists { ref local_name, ref local_name_lower, @@ -1239,7 +1223,7 @@ impl<Impl: SelectorImpl> Component<Impl> { } }, - Is(ref list) | Where(ref list) => { + Negation(ref list) | Is(ref list) | Where(ref list) => { if !visitor.visit_selector_list(&list) { return false; } @@ -2152,44 +2136,14 @@ where P: Parser<'i, Impl = Impl>, Impl: SelectorImpl, { - let state = state | SelectorParsingState::INSIDE_NEGATION; - - // We use a sequence because a type selector may be represented as two Components. - let mut sequence = SmallVec::<[Component<Impl>; 2]>::new(); - - input.skip_whitespace(); - - // Get exactly one simple selector. The parse logic in the caller will verify - // that there are no trailing tokens after we're done. - let is_type_sel = match parse_type_selector(parser, input, state, &mut sequence) { - Ok(result) => result, - Err(ParseError { - kind: ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput), - .. - }) => return Err(input.new_custom_error(SelectorParseErrorKind::EmptyNegation)), - Err(e) => return Err(e.into()), - }; - if !is_type_sel { - match parse_one_simple_selector(parser, input, state)? { - Some(SimpleSelectorParseResult::SimpleSelector(s)) => { - sequence.push(s); - }, - None => { - return Err(input.new_custom_error(SelectorParseErrorKind::EmptyNegation)); - }, - Some(SimpleSelectorParseResult::PseudoElement(_)) | - Some(SimpleSelectorParseResult::PartPseudo(_)) | - Some(SimpleSelectorParseResult::SlottedPseudo(_)) => { - let e = SelectorParseErrorKind::NonSimpleSelectorInNegation; - return Err(input.new_custom_error(e)); - }, - } - } + let list = SelectorList::parse_with_state( + parser, + input, + state | SelectorParsingState::SKIP_DEFAULT_NAMESPACE | SelectorParsingState::DISALLOW_PSEUDOS, + ParseErrorRecovery::DiscardList + )?; - // Success. - Ok(Component::Negation( - sequence.into_vec().into_boxed_slice().into(), - )) + Ok(Component::Negation(list.0.into_vec().into_boxed_slice())) } /// simple_selector_sequence @@ -2225,7 +2179,8 @@ where if let Some(url) = parser.default_namespace() { // If there was no explicit type selector, but there is a // default namespace, there is an implicit "<defaultns>|*" type - // selector. Except for :host, where we ignore it. + // selector. Except for :host() or :not() / :is() / :where(), + // where we ignore it. // // https://drafts.csswg.org/css-scoping/#host-element-in-tree: // @@ -2240,10 +2195,22 @@ where // given selector is allowed to match a featureless element, // it must do so while ignoring the default namespace. // - if !matches!( - result, - SimpleSelectorParseResult::SimpleSelector(Component::Host(..)) - ) { + // https://drafts.csswg.org/selectors-4/#matches + // + // Default namespace declarations do not affect the compound + // selector representing the subject of any selector within + // a :is() pseudo-class, unless that compound selector + // contains an explicit universal selector or type selector. + // + // (Similar quotes for :where() / :not()) + // + let ignore_default_ns = + state.intersects(SelectorParsingState::SKIP_DEFAULT_NAMESPACE) || + matches!( + result, + SimpleSelectorParseResult::SimpleSelector(Component::Host(..)) + ); + if !ignore_default_ns { builder.push_simple_selector(Component::DefaultNamespace(url)); } } @@ -2297,7 +2264,7 @@ where let inner = SelectorList::parse_with_state( parser, input, - state | SelectorParsingState::DISALLOW_PSEUDOS, + state | SelectorParsingState::SKIP_DEFAULT_NAMESPACE | SelectorParsingState::DISALLOW_PSEUDOS, parser.is_and_where_error_recovery(), )?; Ok(component(inner.0.into_vec().into_boxed_slice())) @@ -2327,11 +2294,6 @@ where return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input, state)?))); }, "not" => { - if state.intersects(SelectorParsingState::INSIDE_NEGATION) { - return Err(input.new_custom_error( - SelectorParseErrorKind::UnexpectedIdent("not".into()) - )); - } return parse_negation(parser, input, state) }, _ => {} @@ -3080,9 +3042,13 @@ pub mod tests { vec![ Component::DefaultNamespace(MATHML.into()), Component::Negation( - vec![Component::Class(DummyAtom::from("cl"))] - .into_boxed_slice() - .into(), + vec![ + Selector::from_vec( + vec![Component::Class(DummyAtom::from("cl"))], + specificity(0, 1, 0), + Default::default(), + ) + ].into_boxed_slice() ), ], specificity(0, 1, 0), @@ -3096,11 +3062,16 @@ pub mod tests { Component::DefaultNamespace(MATHML.into()), Component::Negation( vec![ - Component::DefaultNamespace(MATHML.into()), - Component::ExplicitUniversalType, + Selector::from_vec( + vec![ + Component::DefaultNamespace(MATHML.into()), + Component::ExplicitUniversalType, + ], + specificity(0, 0, 0), + Default::default(), + ) ] - .into_boxed_slice() - .into(), + .into_boxed_slice(), ), ], specificity(0, 0, 0), @@ -3114,14 +3085,19 @@ pub mod tests { Component::DefaultNamespace(MATHML.into()), Component::Negation( vec![ - Component::DefaultNamespace(MATHML.into()), - Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - }), + Selector::from_vec( + vec![ + Component::DefaultNamespace(MATHML.into()), + Component::LocalName(LocalName { + name: DummyAtom::from("e"), + lower_name: DummyAtom::from("e"), + }), + ], + specificity(0, 0, 1), + Default::default(), + ), ] .into_boxed_slice() - .into(), ), ], specificity(0, 0, 1), @@ -3216,47 +3192,23 @@ pub mod tests { )])) ); parser.default_ns = None; - assert!(parse(":not(#provel.old)").is_err()); - assert!(parse(":not(#provel > old)").is_err()); + assert!(parse(":not(#provel.old)").is_ok()); + assert!(parse(":not(#provel > old)").is_ok()); assert!(parse("table[rules]:not([rules=\"none\"]):not([rules=\"\"])").is_ok()); - assert_eq!( - parse(":not(#provel)"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::Negation( - vec![Component::ID(DummyAtom::from("provel"))] - .into_boxed_slice() - .into(), - )], - specificity(1, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse_ns(":not(svg|circle)", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::Negation( - vec![ - Component::Namespace(DummyAtom("svg".into()), SVG.into()), - Component::LocalName(LocalName { - name: DummyAtom::from("circle"), - lower_name: DummyAtom::from("circle"), - }), - ] - .into_boxed_slice() - .into(), - )], - specificity(0, 0, 1), - Default::default(), - )])) - ); // https://github.com/servo/servo/issues/16017 assert_eq!( parse_ns(":not(*)", &parser), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![Component::Negation( - vec![Component::ExplicitUniversalType] - .into_boxed_slice() - .into(), + vec![ + Selector::from_vec( + vec![ + Component::ExplicitUniversalType + ], + specificity(0, 0, 0), + Default::default(), + ) + ].into_boxed_slice() )], specificity(0, 0, 0), Default::default(), @@ -3267,11 +3219,16 @@ pub mod tests { Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![Component::Negation( vec![ - Component::ExplicitNoNamespace, - Component::ExplicitUniversalType, + Selector::from_vec( + vec![ + Component::ExplicitNoNamespace, + Component::ExplicitUniversalType, + ], + specificity(0, 0, 0), + Default::default(), + ) ] - .into_boxed_slice() - .into(), + .into_boxed_slice(), )], specificity(0, 0, 0), Default::default(), @@ -3283,25 +3240,15 @@ pub mod tests { parse_ns_expected(":not(*|*)", &parser, Some(":not(*)")), Ok(SelectorList::from_vec(vec![Selector::from_vec( vec![Component::Negation( - vec![Component::ExplicitUniversalType] - .into_boxed_slice() - .into(), - )], - specificity(0, 0, 0), - Default::default(), - )])) - ); - - assert_eq!( - parse_ns(":not(svg|*)", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::Negation( vec![ - Component::Namespace(DummyAtom("svg".into()), SVG.into()), - Component::ExplicitUniversalType, - ] - .into_boxed_slice() - .into(), + Selector::from_vec( + vec![ + Component::ExplicitUniversalType + ], + specificity(0, 0, 0), + Default::default() + ) + ].into_boxed_slice() )], specificity(0, 0, 0), Default::default(), |