diff --git a/components/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs index 95ef949d2ab..7539fbe36f5 100644 --- a/components/malloc_size_of/lib.rs +++ b/components/malloc_size_of/lib.rs @@ -717,7 +717,8 @@ where Component::Scope | Component::ParentSelector | Component::Nth(..) | - Component::Host(None) => 0, + Component::Host(None) | + Component::RelativeSelectorAnchor => 0, } } } diff --git a/components/selectors/builder.rs b/components/selectors/builder.rs index 7689ba0d478..2c0356ce8ae 100644 --- a/components/selectors/builder.rs +++ b/components/selectors/builder.rs @@ -360,7 +360,8 @@ where Component::ExplicitAnyNamespace | Component::ExplicitNoNamespace | Component::DefaultNamespace(..) | - Component::Namespace(..) => { + Component::Namespace(..) | + Component::RelativeSelectorAnchor => { // Does not affect specificity }, } diff --git a/components/selectors/context.rs b/components/selectors/context.rs index ffb97c94e8b..1bdad188e81 100644 --- a/components/selectors/context.rs +++ b/components/selectors/context.rs @@ -147,6 +147,9 @@ where /// Extra implementation-dependent matching data. pub extra_data: Impl::ExtraMatchingData<'a>, + /// The current element we're anchoring on for evaluating the relative selector. + current_relative_selector_anchor: Option, + quirks_mode: QuirksMode, needs_selector_flags: NeedsSelectorFlags, classes_and_ids_case_sensitivity: CaseSensitivity, @@ -198,6 +201,7 @@ where in_negation: false, pseudo_element_matching_fn: None, extra_data: Default::default(), + current_relative_selector_anchor: None, _impl: ::std::marker::PhantomData, } } @@ -318,4 +322,25 @@ where pub fn shadow_host(&self) -> Option { self.current_host } + + /// Runs F with a deeper nesting level, with the given element as the anchor, + /// for a :has(...) selector, for example. + #[inline] + pub fn nest_for_relative_selector(&mut self, anchor: OpaqueElement, f: F) -> R + where + F: FnOnce(&mut Self) -> R, + { + // TODO(dshin): Nesting should be rejected at parse time. + let original_relative_selector_anchor = self.current_relative_selector_anchor.take(); + self.current_relative_selector_anchor = Some(anchor); + let result = self.nest(f); + self.current_relative_selector_anchor = original_relative_selector_anchor; + result + } + + /// Returns the current anchor element to evaluate the relative selector against. + #[inline] + pub fn relative_selector_anchor(&self) -> Option { + self.current_relative_selector_anchor + } } diff --git a/components/selectors/matching.rs b/components/selectors/matching.rs index 5e8be75eaf0..f3e55f705c2 100644 --- a/components/selectors/matching.rs +++ b/components/selectors/matching.rs @@ -835,10 +835,34 @@ where .nest_for_negation(|context| !list_matches_complex_selector(list, element, context)), Component::Has(ref list) => context .shared - .nest(|context| has_children_matching(list, element, context)), + .nest_for_relative_selector(element.opaque(), |context| { + if cfg!(debug_assertions) { + for selector in list.iter() { + let mut selector_iter = selector.iter_raw_parse_order_from(0); + assert!( + matches!(selector_iter.next().unwrap(), Component::RelativeSelectorAnchor), + "Relative selector does not start with RelativeSelectorAnchor" + ); + assert!( + selector_iter.next().unwrap().is_combinator(), + "Relative combinator does not exist" + ); + } + } + // TODO(dshin): Proper matching for sibling relative combinators. + has_children_matching(list, element, context) + }), Component::Combinator(_) => unsafe { debug_unreachable!("Shouldn't try to selector-match combinators") }, + Component::RelativeSelectorAnchor => { + let anchor = context.shared.relative_selector_anchor(); + debug_assert!( + anchor.is_some(), + "Relative selector outside of relative selector matching?" + ); + anchor.map_or(false, |a| a == element.opaque()) + }, } } diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index 808e06e9c24..1e5333bb71c 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -366,15 +366,24 @@ pub struct SelectorList( #[cfg_attr(feature = "shmem", shmem(field_bound))] pub SmallVec<[Selector; 1]>, ); -/// How to treat invalid selectors in a selector list. -enum ParseErrorRecovery { - /// Discard the entire selector list, this is the default behavior for - /// almost all of CSS. - DiscardList, +/// Whether or not we're using forgiving parsing mode +enum ForgivingParsing { + /// Discard the entire selector list upon encountering any invalid selector. + /// This is the default behavior for almost all of CSS. + No, /// Ignore invalid selectors, potentially creating an empty selector list. /// /// This is the error recovery mode of :is() and :where() - IgnoreInvalidSelector, + Yes, +} + +/// Flag indicating if we're parsing relative selectors. +#[derive(Copy, Clone, PartialEq)] +enum ParseRelative { + /// Expect selectors to start with a combinator, assuming descendant combinator if not present. + Yes, + /// Treat as parse error if any selector begins with a combinator. + No, } impl SelectorList { @@ -393,7 +402,8 @@ impl SelectorList { parser, input, SelectorParsingState::empty(), - ParseErrorRecovery::DiscardList, + ForgivingParsing::No, + ParseRelative::No, ) } @@ -402,23 +412,24 @@ impl SelectorList { parser: &P, input: &mut CssParser<'i, 't>, state: SelectorParsingState, - recovery: ParseErrorRecovery, + recovery: ForgivingParsing, + parse_relative: ParseRelative, ) -> Result> where P: Parser<'i, Impl = Impl>, { let mut values = SmallVec::new(); loop { - let selector = input.parse_until_before(Delimiter::Comma, |input| { - parse_selector(parser, input, state) + let selector = input.parse_until_before(Delimiter::Comma, |i| { + parse_selector(parser, i, state, parse_relative) }); let was_ok = selector.is_ok(); match selector { Ok(selector) => values.push(selector), Err(err) => match recovery { - ParseErrorRecovery::DiscardList => return Err(err), - ParseErrorRecovery::IgnoreInvalidSelector => { + ForgivingParsing::No => return Err(err), + ForgivingParsing::Yes => { if !parser.allow_forgiving_selectors() { return Err(err); } @@ -459,6 +470,7 @@ where parser, input, state | SelectorParsingState::DISALLOW_PSEUDOS | SelectorParsingState::DISALLOW_COMBINATORS, + ParseRelative::No, ) } @@ -905,7 +917,8 @@ impl Selector { PseudoElement(..) | Combinator(..) | Host(None) | - Part(..) => component.clone(), + Part(..) | + RelativeSelectorAnchor => component.clone(), ParentSelector => { specificity += parent_specificity; Is(parent.to_vec().into_boxed_slice()) @@ -1469,6 +1482,12 @@ pub enum Component { PseudoElement(#[cfg_attr(feature = "shmem", shmem(field_bound))] Impl::PseudoElement), Combinator(Combinator), + + /// Used only for relative selectors, which starts with a combinator + /// (With an implied descendant combinator if not specified). + /// + /// https://drafts.csswg.org/csswg-drafts/selectors-4/#typedef-relative-selector + RelativeSelectorAnchor, } impl Component { @@ -1722,6 +1741,14 @@ impl ToCss for Selector { if compound.is_empty() { continue; } + if let Component::RelativeSelectorAnchor = compound.first().unwrap() { + debug_assert!( + compound.len() == 1, + "RelativeLeft should only be a simple selector" + ); + combinators.next().unwrap().to_css_relative(dest)?; + continue; + } // 1. If there is only one simple selector in the compound selectors // which is a universal selector, append the result of @@ -1811,18 +1838,45 @@ impl ToCss for Selector { } } +impl Combinator { + fn to_css_internal(&self, dest: &mut W, prefix_space: bool) -> fmt::Result + where + W: fmt::Write, + { + if matches!( + *self, + Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment + ) { + return Ok(()); + } + if prefix_space { + dest.write_char(' ')?; + } + match *self { + Combinator::Child => dest.write_str("> "), + Combinator::Descendant => Ok(()), + Combinator::NextSibling => dest.write_str("+ "), + Combinator::LaterSibling => dest.write_str("~ "), + Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment => unsafe { + debug_unreachable!("Already handled") + }, + } + } + + fn to_css_relative(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + self.to_css_internal(dest, false) + } +} + impl ToCss for Combinator { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write, { - match *self { - Combinator::Child => dest.write_str(" > "), - Combinator::Descendant => dest.write_str(" "), - Combinator::NextSibling => dest.write_str(" + "), - Combinator::LaterSibling => dest.write_str(" ~ "), - Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment => Ok(()), - } + self.to_css_internal(dest, true) } } @@ -1952,6 +2006,7 @@ impl ToCss for Component { dest.write_str(")") }, NonTSPseudoClass(ref pseudo) => pseudo.to_css(dest), + RelativeSelectorAnchor => Ok(()), } } } @@ -2011,13 +2066,19 @@ fn parse_selector<'i, 't, P, Impl>( parser: &P, input: &mut CssParser<'i, 't>, mut state: SelectorParsingState, + parse_relative: ParseRelative, ) -> Result, ParseError<'i, P::Error>> where P: Parser<'i, Impl = Impl>, Impl: SelectorImpl, { let mut builder = SelectorBuilder::default(); - + if parse_relative == ParseRelative::Yes { + builder.push_simple_selector(Component::RelativeSelectorAnchor); + // Do we see a combinator? If so, push that. Otherwise, push a descendant combinator. + builder + .push_combinator(parse_combinator::(input).unwrap_or(Combinator::Descendant)); + } 'outer_loop: loop { // Parse a sequence of simple selectors. let empty = parse_compound_selector(parser, &mut state, input, &mut builder)?; @@ -2038,37 +2099,11 @@ where break; } - // Parse a combinator. - let combinator; - let mut any_whitespace = false; - loop { - let before_this_token = input.state(); - match input.next_including_whitespace() { - Err(_e) => break 'outer_loop, - Ok(&Token::WhiteSpace(_)) => any_whitespace = true, - Ok(&Token::Delim('>')) => { - combinator = Combinator::Child; - break; - }, - Ok(&Token::Delim('+')) => { - combinator = Combinator::NextSibling; - break; - }, - Ok(&Token::Delim('~')) => { - combinator = Combinator::LaterSibling; - break; - }, - Ok(_) => { - input.reset(&before_this_token); - if any_whitespace { - combinator = Combinator::Descendant; - break; - } else { - break 'outer_loop; - } - }, - } - } + let combinator = if let Ok(c) = parse_combinator::(input) { + c + } else { + break 'outer_loop; + }; if !state.allows_combinators() { return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); @@ -2076,8 +2111,35 @@ where builder.push_combinator(combinator); } + return Ok(Selector(builder.build())); +} - Ok(Selector(builder.build())) +fn parse_combinator<'i, 't, P, Impl>(input: &mut CssParser<'i, 't>) -> Result { + let mut any_whitespace = false; + loop { + let before_this_token = input.state(); + match input.next_including_whitespace() { + Err(_e) => return Err(()), + Ok(&Token::WhiteSpace(_)) => any_whitespace = true, + Ok(&Token::Delim('>')) => { + return Ok(Combinator::Child); + }, + Ok(&Token::Delim('+')) => { + return Ok(Combinator::NextSibling); + }, + Ok(&Token::Delim('~')) => { + return Ok(Combinator::LaterSibling); + }, + Ok(_) => { + input.reset(&before_this_token); + if any_whitespace { + return Ok(Combinator::Descendant); + } else { + return Err(()); + } + }, + } + } } impl Selector { @@ -2090,7 +2152,12 @@ impl Selector { where P: Parser<'i, Impl = Impl>, { - parse_selector(parser, input, SelectorParsingState::empty()) + parse_selector( + parser, + input, + SelectorParsingState::empty(), + ParseRelative::No, + ) } } @@ -2498,7 +2565,8 @@ where state | SelectorParsingState::SKIP_DEFAULT_NAMESPACE | SelectorParsingState::DISALLOW_PSEUDOS, - ParseErrorRecovery::DiscardList, + ForgivingParsing::No, + ParseRelative::No, )?; Ok(Component::Negation(list.0.into_vec().into_boxed_slice())) @@ -2603,7 +2671,7 @@ where Ok(empty) } -fn parse_is_where_has<'i, 't, P, Impl>( +fn parse_is_where<'i, 't, P, Impl>( parser: &P, input: &mut CssParser<'i, 't>, state: SelectorParsingState, @@ -2625,11 +2693,34 @@ where state | SelectorParsingState::SKIP_DEFAULT_NAMESPACE | SelectorParsingState::DISALLOW_PSEUDOS, - ParseErrorRecovery::IgnoreInvalidSelector, + ForgivingParsing::Yes, + ParseRelative::No, )?; Ok(component(inner.0.into_vec().into_boxed_slice())) } +fn parse_has<'i, 't, P, Impl>( + parser: &P, + input: &mut CssParser<'i, 't>, + state: SelectorParsingState, +) -> Result, ParseError<'i, P::Error>> +where + P: Parser<'i, Impl = Impl>, + Impl: SelectorImpl, +{ + debug_assert!(parser.parse_has()); + let inner = SelectorList::parse_with_state( + parser, + input, + state | + SelectorParsingState::SKIP_DEFAULT_NAMESPACE | + SelectorParsingState::DISALLOW_PSEUDOS, + ForgivingParsing::No, + ParseRelative::Yes, + )?; + Ok(Component::Has(inner.0.into_vec().into_boxed_slice())) +} + fn parse_functional_pseudo_class<'i, 't, P, Impl>( parser: &P, input: &mut CssParser<'i, 't>, @@ -2645,9 +2736,9 @@ where "nth-of-type" => return parse_nth_pseudo_class(parser, input, state, NthType::OfType), "nth-last-child" => return parse_nth_pseudo_class(parser, input, state, NthType::LastChild), "nth-last-of-type" => return parse_nth_pseudo_class(parser, input, state, NthType::LastOfType), - "is" if parser.parse_is_and_where() => return parse_is_where_has(parser, input, state, Component::Is), - "where" if parser.parse_is_and_where() => return parse_is_where_has(parser, input, state, Component::Where), - "has" if parser.parse_has() => return parse_is_where_has(parser, input, state, Component::Has), + "is" if parser.parse_is_and_where() => return parse_is_where(parser, input, state, Component::Is), + "where" if parser.parse_is_and_where() => return parse_is_where(parser, input, state, Component::Where), + "has" if parser.parse_has() => return parse_has(parser, input, state), "host" => { if !state.allows_tree_structural_pseudo_classes() { return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); @@ -2661,7 +2752,7 @@ where } if parser.parse_is_and_where() && parser.is_is_alias(&name) { - return parse_is_where_has(parser, input, state, Component::Is); + return parse_is_where(parser, input, state, Component::Is); } if !state.allows_custom_functional_pseudo_classes() { @@ -2707,7 +2798,8 @@ where state | SelectorParsingState::SKIP_DEFAULT_NAMESPACE | SelectorParsingState::DISALLOW_PSEUDOS, - ParseErrorRecovery::DiscardList, + ForgivingParsing::No, + ParseRelative::No, )?; Ok(Component::NthOf(NthOfSelectorData::new( &nth_data,