aboutsummaryrefslogtreecommitdiffstats
path: root/components/selectors/parser.rs
diff options
context:
space:
mode:
authorEmilio Cobos Álvarez <emilio@crisal.io>2020-10-29 18:03:54 +0000
committerEmilio Cobos Álvarez <emilio@crisal.io>2021-02-26 16:44:05 +0100
commit191d5226a38ca926d1f27551431e00e8728f1825 (patch)
tree439df20df1d593d8ce7a5dcc669e34171e744a37 /components/selectors/parser.rs
parent7e6d70f3149ba917d8a972f8ee6a53c50fd1215f (diff)
downloadservo-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.rs247
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(),