diff --git a/layout/style/test/test_selectors.html b/layout/style/test/test_selectors.html index 2dfba7b37a565..d688139d3c513 100644 --- a/layout/style/test/test_selectors.html +++ b/layout/style/test/test_selectors.html @@ -1299,14 +1299,15 @@ test_balanced_unparseable("::-moz-color-swatch:hover#foo"); test_balanced_unparseable(".foo::after:not(.bar) ~ h3"); - for (let [selector, expected_serialization_when_parseable] of [ - ["::-moz-color-swatch:where(.foo)", "::-moz-color-swatch:where()"], - ["::-moz-color-swatch:is(.foo)", "::-moz-color-swatch:is()"], - ["::-moz-color-swatch:where(p, :hover)", "::-moz-color-swatch:where(:hover)"], - ["::-moz-color-swatch:is(p, :hover)", "::-moz-color-swatch:is(:hover)"], + for (let selector of [ + "::-moz-color-swatch:where(.foo)", + "::-moz-color-swatch:is(.foo)", + "::-moz-color-swatch:where(p, :hover)", + "::-moz-color-swatch:is(p, :hover)", ]) { test_parseable(selector); - should_serialize_to(selector, expected_serialization_when_parseable); + should_serialize_to(selector, selector); + ok(!CSS.supports(`selector(${selector})`), "supports should report false for forging selector parse failure"); } run_deferred_tests(); diff --git a/servo/components/malloc_size_of/lib.rs b/servo/components/malloc_size_of/lib.rs index f3a254fc95b3a..b2b44cad4cd8e 100644 --- a/servo/components/malloc_size_of/lib.rs +++ b/servo/components/malloc_size_of/lib.rs @@ -771,7 +771,8 @@ where Component::ParentSelector | Component::Nth(..) | Component::Host(None) | - Component::RelativeSelectorAnchor => 0, + Component::RelativeSelectorAnchor | + Component::Invalid(..) => 0, } } } diff --git a/servo/components/selectors/builder.rs b/servo/components/selectors/builder.rs index a24b97db16855..e386175101bf7 100644 --- a/servo/components/selectors/builder.rs +++ b/servo/components/selectors/builder.rs @@ -351,7 +351,8 @@ where Component::ExplicitNoNamespace | Component::DefaultNamespace(..) | Component::Namespace(..) | - Component::RelativeSelectorAnchor => { + Component::RelativeSelectorAnchor | + Component::Invalid(..) => { // Does not affect specificity }, } diff --git a/servo/components/selectors/matching.rs b/servo/components/selectors/matching.rs index be1c50f79e9bf..54b448e0da7dc 100644 --- a/servo/components/selectors/matching.rs +++ b/servo/components/selectors/matching.rs @@ -1100,6 +1100,7 @@ where ); anchor.map_or(false, |a| a == element.opaque()) }, + Component::Invalid(..) => false, } } diff --git a/servo/components/selectors/parser.rs b/servo/components/selectors/parser.rs index f9d9584e6d0e1..4a83f1b25d0c2 100644 --- a/servo/components/selectors/parser.rs +++ b/servo/components/selectors/parser.rs @@ -18,7 +18,7 @@ use cssparser::{BasicParseError, BasicParseErrorKind, ParseError, ParseErrorKind use cssparser::{CowRcStr, Delimiter, SourceLocation}; use cssparser::{Parser as CssParser, ToCss, Token}; use precomputed_hash::PrecomputedHash; -use servo_arc::{ThinArc, UniqueArc}; +use servo_arc::{Arc, ThinArc, UniqueArc}; use smallvec::SmallVec; use std::borrow::{Borrow, Cow}; use std::fmt::{self, Debug}; @@ -371,6 +371,7 @@ impl SelectorKey { } /// Whether or not we're using forgiving parsing mode +#[derive(PartialEq)] enum ForgivingParsing { /// Discard the entire selector list upon encountering any invalid selector. /// This is the default behavior for almost all of CSS. @@ -443,34 +444,28 @@ impl SelectorList { P: Parser<'i, Impl = Impl>, { let mut values = SmallVec::new(); + let forgiving = recovery == ForgivingParsing::Yes && parser.allow_forgiving_selectors(); loop { - let selector = input.parse_until_before(Delimiter::Comma, |i| { - parse_selector(parser, i, state, parse_relative) + let selector = input.parse_until_before(Delimiter::Comma, |input| { + let start = input.position(); + let mut selector = parse_selector(parser, input, state, parse_relative); + if forgiving && (selector.is_err() || input.expect_exhausted().is_err()) { + input.expect_no_error_token()?; + selector = Ok(Selector::new_invalid(input.slice_from(start))); + } + selector }); - let was_ok = selector.is_ok(); - match selector { - Ok(selector) => values.push(selector), - Err(err) => match recovery { - ForgivingParsing::No => return Err(err), - ForgivingParsing::Yes => { - if !parser.allow_forgiving_selectors() { - return Err(err); - } - }, - }, - } + debug_assert!(!forgiving || selector.is_ok()); + values.push(selector?); - loop { - match input.next() { - Err(_) => return Ok(SelectorList(values)), - Ok(&Token::Comma) => break, - Ok(_) => { - debug_assert!(!was_ok, "Shouldn't have got a selector if getting here"); - }, - } + match input.next() { + Ok(&Token::Comma) => {}, + Ok(_) => unreachable!(), + Err(_) => break, } } + Ok(SelectorList(values)) } /// Replaces the parent selector in all the items of the selector list. @@ -1045,6 +1040,7 @@ impl Selector { Combinator(..) | Host(None) | Part(..) | + Invalid(..) | RelativeSelectorAnchor => component.clone(), ParentSelector => { specificity += parent_specificity; @@ -1168,6 +1164,65 @@ impl Selector { true } + + /// Parse a selector, without any pseudo-element. + #[inline] + pub fn parse<'i, 't, P>( + parser: &P, + input: &mut CssParser<'i, 't>, + ) -> Result> + where + P: Parser<'i, Impl = Impl>, + { + parse_selector( + parser, + input, + SelectorParsingState::empty(), + ParseRelative::No, + ) + } + + pub fn new_invalid(s: &str) -> Self { + fn check_for_parent(input: &mut CssParser, has_parent: &mut bool) { + while let Ok(t) = input.next() { + match *t { + Token::Function(_) | + Token::ParenthesisBlock | + Token::CurlyBracketBlock | + Token::SquareBracketBlock => { + let _ = input.parse_nested_block(|i| -> Result<(), ParseError<'_, BasicParseError>> { + check_for_parent(i, has_parent); + Ok(()) + }); + }, + Token::Delim('&') => { + *has_parent = true; + } + _ => {}, + } + if *has_parent { + break; + } + } + } + let mut has_parent = false; + { + let mut parser = cssparser::ParserInput::new(s); + let mut parser = CssParser::new(&mut parser); + check_for_parent(&mut parser, &mut has_parent); + } + Self(ThinArc::from_header_and_iter( + SpecificityAndFlags { + specificity: 0, + flags: if has_parent { + SelectorFlags::HAS_PARENT + } else { + SelectorFlags::empty() + }, + }, + std::iter::once(Component::Invalid(Arc::new(String::from(s.trim())))) + )) + } } #[derive(Clone)] @@ -1831,6 +1886,8 @@ pub enum Component { /// /// Same comment as above re. the argument. Has(Box<[RelativeSelector]>), + /// An invalid selector inside :is() / :where(). + Invalid(Arc), /// An implementation-dependent pseudo-element selector. PseudoElement(#[shmem(field_bound)] Impl::PseudoElement), @@ -2369,6 +2426,7 @@ impl ToCss for Component { dest.write_str(")") }, NonTSPseudoClass(ref pseudo) => pseudo.to_css(dest), + Invalid(ref css) => dest.write_str(css), RelativeSelectorAnchor => Ok(()), } } @@ -2519,25 +2577,6 @@ fn try_parse_combinator<'i, 't, P, Impl>(input: &mut CssParser<'i, 't>) -> Resul } } -impl Selector { - /// Parse a selector, without any pseudo-element. - #[inline] - pub fn parse<'i, 't, P>( - parser: &P, - input: &mut CssParser<'i, 't>, - ) -> Result> - where - P: Parser<'i, Impl = Impl>, - { - parse_selector( - parser, - input, - SelectorParsingState::empty(), - ParseRelative::No, - ) - } -} - /// * `Err(())`: Invalid selector, abort /// * `Ok(false)`: Not a type selector, could be something else. `input` was not consumed. /// * `Ok(true)`: Length 0 (`*|*`), 1 (`*|E` or `ns|*`) or 2 (`|E` or `ns|E`) @@ -4202,7 +4241,7 @@ pub mod tests { assert!(parse("foo:where()").is_ok()); assert!(parse("foo:where(div, foo, .bar baz)").is_ok()); - assert!(parse_expected("foo:where(::before)", Some("foo:where()")).is_ok()); + assert!(parse("foo:where(::before)").is_ok()); } #[test] diff --git a/testing/web-platform/meta/css/css-nesting/cssom.html.ini b/testing/web-platform/meta/css/css-nesting/cssom.html.ini deleted file mode 100644 index 14751f5bb48a7..0000000000000 --- a/testing/web-platform/meta/css/css-nesting/cssom.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[cssom.html] - [Simple CSSOM manipulation of subrules 10] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-nesting/nest-containing-forgiving.html.ini b/testing/web-platform/meta/css/css-nesting/nest-containing-forgiving.html.ini deleted file mode 100644 index 5bc1769b4f1ca..0000000000000 --- a/testing/web-platform/meta/css/css-nesting/nest-containing-forgiving.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[nest-containing-forgiving.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-nesting/parsing.html.ini b/testing/web-platform/meta/css/css-nesting/parsing.html.ini deleted file mode 100644 index b3a26082ad15e..0000000000000 --- a/testing/web-platform/meta/css/css-nesting/parsing.html.ini +++ /dev/null @@ -1 +0,0 @@ -[parsing.html] diff --git a/testing/web-platform/meta/css/css-scoping/slotted-invalidation.html.ini b/testing/web-platform/meta/css/css-scoping/slotted-invalidation.html.ini deleted file mode 100644 index e15e183630954..0000000000000 --- a/testing/web-platform/meta/css/css-scoping/slotted-invalidation.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[slotted-invalidation.html] - expected: - if (os == "android") and debug and fission: [OK, TIMEOUT] - if (os == "android") and debug and not fission: [OK, TIMEOUT] diff --git a/testing/web-platform/meta/css/css-scoping/slotted-link.html.ini b/testing/web-platform/meta/css/css-scoping/slotted-link.html.ini deleted file mode 100644 index 36f719091964e..0000000000000 --- a/testing/web-platform/meta/css/css-scoping/slotted-link.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[slotted-link.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] diff --git a/testing/web-platform/meta/css/css-scoping/slotted-matches.html.ini b/testing/web-platform/meta/css/css-scoping/slotted-matches.html.ini deleted file mode 100644 index a7914b8393bea..0000000000000 --- a/testing/web-platform/meta/css/css-scoping/slotted-matches.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[slotted-matches.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] diff --git a/testing/web-platform/meta/css/css-scoping/slotted-nested.html.ini b/testing/web-platform/meta/css/css-scoping/slotted-nested.html.ini deleted file mode 100644 index c63c72152b579..0000000000000 --- a/testing/web-platform/meta/css/css-scoping/slotted-nested.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[slotted-nested.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] diff --git a/testing/web-platform/meta/css/css-scoping/slotted-parsing.html.ini b/testing/web-platform/meta/css/css-scoping/slotted-parsing.html.ini deleted file mode 100644 index 60029274100ee..0000000000000 --- a/testing/web-platform/meta/css/css-scoping/slotted-parsing.html.ini +++ /dev/null @@ -1,15 +0,0 @@ -[slotted-parsing.html] - expected: - if (os == "android") and debug and fission: [OK, TIMEOUT] - if (os == "android") and debug and not fission: [OK, TIMEOUT] - ["::slotted(*):is(:hover)" should be a valid selector] - expected: FAIL - - ["::slotted(*):is(#id)" should be a valid selector] - expected: FAIL - - ["::slotted(*):where(:hover)" should be a valid selector] - expected: FAIL - - ["::slotted(*):where(#id)" should be a valid selector] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-scoping/slotted-slot.html.ini b/testing/web-platform/meta/css/css-scoping/slotted-slot.html.ini deleted file mode 100644 index c504c85f60c91..0000000000000 --- a/testing/web-platform/meta/css/css-scoping/slotted-slot.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[slotted-slot.html] - expected: - if (os == "android") and debug and fission: [OK, TIMEOUT] - if (os == "android") and debug and not fission: [OK, TIMEOUT] diff --git a/testing/web-platform/meta/css/selectors/is-nested.html.ini b/testing/web-platform/meta/css/selectors/is-nested.html.ini deleted file mode 100644 index 333bbb79dbdc7..0000000000000 --- a/testing/web-platform/meta/css/selectors/is-nested.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[is-nested.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] diff --git a/testing/web-platform/meta/css/selectors/is-where-basic.html.ini b/testing/web-platform/meta/css/selectors/is-where-basic.html.ini deleted file mode 100644 index 09dd763bb8ea3..0000000000000 --- a/testing/web-platform/meta/css/selectors/is-where-basic.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[is-where-basic.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] diff --git a/testing/web-platform/meta/css/selectors/is-where-error-recovery.html.ini b/testing/web-platform/meta/css/selectors/is-where-error-recovery.html.ini deleted file mode 100644 index d731f88945926..0000000000000 --- a/testing/web-platform/meta/css/selectors/is-where-error-recovery.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[is-where-error-recovery.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] - [CSS Selectors: :is() and :where() error recovery] - expected: FAIL diff --git a/testing/web-platform/meta/css/selectors/is-where-not.html.ini b/testing/web-platform/meta/css/selectors/is-where-not.html.ini deleted file mode 100644 index 071bf7acd2f1c..0000000000000 --- a/testing/web-platform/meta/css/selectors/is-where-not.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[is-where-not.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] diff --git a/testing/web-platform/meta/css/selectors/is-where-parsing.html.ini b/testing/web-platform/meta/css/selectors/is-where-parsing.html.ini deleted file mode 100644 index e7320dbe60f06..0000000000000 --- a/testing/web-platform/meta/css/selectors/is-where-parsing.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[is-where-parsing.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] - [Pseudo-elements inside] - expected: FAIL diff --git a/testing/web-platform/meta/css/selectors/is-where-pseudo-classes.html.ini b/testing/web-platform/meta/css/selectors/is-where-pseudo-classes.html.ini deleted file mode 100644 index deffa76608c95..0000000000000 --- a/testing/web-platform/meta/css/selectors/is-where-pseudo-classes.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[is-where-pseudo-classes.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] diff --git a/testing/web-platform/meta/css/selectors/is-where-shadow.html.ini b/testing/web-platform/meta/css/selectors/is-where-shadow.html.ini index cbcea64fe1cca..99885484ab114 100644 --- a/testing/web-platform/meta/css/selectors/is-where-shadow.html.ini +++ b/testing/web-platform/meta/css/selectors/is-where-shadow.html.ini @@ -1,5 +1,3 @@ [is-where-shadow.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] [:is() inside :host-context()] expected: FAIL diff --git a/testing/web-platform/tests/css/css-scoping/slotted-parsing.html b/testing/web-platform/tests/css/css-scoping/slotted-parsing.html index bed4dedd56072..e4657b588af5e 100644 --- a/testing/web-platform/tests/css/css-scoping/slotted-parsing.html +++ b/testing/web-platform/tests/css/css-scoping/slotted-parsing.html @@ -27,13 +27,14 @@ test_valid_selector("::slotted([attr]:hover)"); test_valid_selector("::slotted(:not(.a))"); - test_valid_selector("::slotted(*):is()"); - test_valid_selector("::slotted(*):is(:hover)"); - test_valid_selector("::slotted(*):is(#id)"); + test_valid_forgiving_selector("::slotted(*):is()"); + test_valid_forgiving_selector("::slotted(*):is(:hover)"); + test_valid_forgiving_selector("::slotted(*):is(#id)"); - test_valid_selector("::slotted(*):where()"); - test_valid_selector("::slotted(*):where(:hover)"); - test_valid_selector("::slotted(*):where(#id)"); + test_valid_forgiving_selector("::slotted(*):where()"); + test_valid_forgiving_selector("::slotted(*):where(:hover)"); + test_valid_forgiving_selector("::slotted(*):where(#id)"); + test_valid_forgiving_selector("::slotted(*):where(::before)"); // Allow tree-abiding pseudo elements after ::slotted test_valid_selector("::slotted(*)::before"); diff --git a/testing/web-platform/tests/css/selectors/is-where-parsing.html b/testing/web-platform/tests/css/selectors/is-where-parsing.html index 3159ecfe6a65b..6d404dd953b28 100644 --- a/testing/web-platform/tests/css/selectors/is-where-parsing.html +++ b/testing/web-platform/tests/css/selectors/is-where-parsing.html @@ -5,27 +5,20 @@ - +