From def92d1b8e9d373e2f6f27c366d578d97d8960c6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:34:50 +0200 Subject: Merging upstream version 126.0. Signed-off-by: Daniel Baumann --- servo/components/selectors/matching.rs | 350 +++++++++++++++++++++++---------- 1 file changed, 242 insertions(+), 108 deletions(-) (limited to 'servo/components/selectors/matching.rs') diff --git a/servo/components/selectors/matching.rs b/servo/components/selectors/matching.rs index 763f65d547..282d04064b 100644 --- a/servo/components/selectors/matching.rs +++ b/servo/components/selectors/matching.rs @@ -7,6 +7,7 @@ use crate::attr::{ ParsedAttrSelectorOperation, ParsedCaseSensitivity, }; use crate::bloom::{BloomFilter, BLOOM_HASH_MASK}; +use crate::kleene_value::KleeneValue; use crate::parser::{ AncestorHashes, Combinator, Component, LocalName, NthSelectorData, RelativeSelectorMatchHint, }; @@ -200,12 +201,39 @@ fn may_match(hashes: &AncestorHashes, bf: &BloomFilter) -> bool { /// However since the selector "c1" raises /// NotMatchedAndRestartFromClosestDescendant. So the selector /// "b1 + c1 > b2 ~ " doesn't match and restart matching from "d1". +/// +/// There is also the unknown result, which is used during invalidation when +/// specific selector is being tested for before/after comparison. More specifically, +/// selectors that are too expensive to correctly compute during invalidation may +/// return unknown, as the computation will be thrown away and only to be recomputed +/// during styling. For most cases, the unknown result can be treated as matching. +/// This is because a compound of selectors acts like &&, and unknown && matched +/// == matched and unknown && not-matched == not-matched. However, some selectors, +/// like `:is()`, behave like || i.e. `:is(.a, .b)` == a || b. Treating unknown +/// == matching then causes these selectors to always return matching, which undesired +/// for before/after comparison. Coercing to not-matched doesn't work since each +/// inner selector may have compounds: e.g. Toggling `.foo` in `:is(.foo:has(..))` +/// with coersion to not-matched would result in an invalid before/after comparison +/// of not-matched/not-matched. #[derive(Clone, Copy, Eq, PartialEq)] enum SelectorMatchingResult { Matched, NotMatchedAndRestartFromClosestLaterSibling, NotMatchedAndRestartFromClosestDescendant, NotMatchedGlobally, + Unknown, +} + +impl From for KleeneValue { + fn from(value: SelectorMatchingResult) -> Self { + match value { + SelectorMatchingResult::Matched => KleeneValue::True, + SelectorMatchingResult::Unknown => KleeneValue::Unknown, + SelectorMatchingResult::NotMatchedAndRestartFromClosestLaterSibling | + SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant | + SelectorMatchingResult::NotMatchedGlobally => KleeneValue::False, + } + } } /// Matches a selector, fast-rejecting against a bloom filter. @@ -224,6 +252,28 @@ pub fn matches_selector( element: &E, context: &mut MatchingContext, ) -> bool +where + E: Element, +{ + let result = matches_selector_kleene(selector, offset, hashes, element, context); + if cfg!(debug_assertions) && result == KleeneValue::Unknown { + debug_assert!( + context.matching_for_invalidation_comparison().unwrap_or(false), + "How did we return unknown?" + ); + } + result.to_bool(true) +} + +/// Same as matches_selector, but returns the Kleene value as-is. +#[inline(always)] +pub fn matches_selector_kleene( + selector: &Selector, + offset: usize, + hashes: Option<&AncestorHashes>, + element: &E, + context: &mut MatchingContext, +) -> KleeneValue where E: Element, { @@ -231,7 +281,7 @@ where if let Some(hashes) = hashes { if let Some(filter) = context.bloom_filter { if !may_match(hashes, filter) { - return false; + return KleeneValue::False; } } } @@ -275,6 +325,12 @@ pub fn matches_compound_selector_from( where E: Element, { + debug_assert!( + !context + .matching_for_invalidation_comparison() + .unwrap_or(false), + "CompoundSelectorMatchingResult doesn't support unknown" + ); if cfg!(debug_assertions) && from_offset != 0 { selector.combinator_at_parse_order(from_offset - 1); // This asserts. } @@ -320,7 +376,9 @@ where ); for component in iter { - if !matches_simple_selector(component, element, &mut local_context) { + let result = matches_simple_selector(component, element, &mut local_context); + debug_assert!(result != KleeneValue::Unknown, "Returned unknown in non invalidation context?"); + if !result.to_bool(true) { return CompoundSelectorMatchingResult::NotMatched; } } @@ -341,7 +399,7 @@ fn matches_complex_selector( element: &E, context: &mut MatchingContext, rightmost: SubjectOrPseudoElement, -) -> bool +) -> KleeneValue where E: Element, { @@ -353,7 +411,7 @@ where Component::PseudoElement(ref pseudo) => { if let Some(ref f) = context.pseudo_element_matching_fn { if !f(pseudo) { - return false; + return KleeneValue::False; } } }, @@ -364,12 +422,12 @@ where in a non-pseudo selector {:?}", other ); - return false; + return KleeneValue::False; }, } if !iter.matches_for_stateless_pseudo_element() { - return false; + return KleeneValue::False; } // Advance to the non-pseudo-element part of the selector. @@ -377,9 +435,14 @@ where debug_assert_eq!(next_sequence, Combinator::PseudoElement); } - let result = matches_complex_selector_internal(iter, element, context, rightmost); - - matches!(result, SelectorMatchingResult::Matched) + matches_complex_selector_internal( + iter, + element, + context, + rightmost, + SubjectOrPseudoElement::Yes, + ) + .into() } /// Matches each selector of a list as a complex selector @@ -388,13 +451,16 @@ fn matches_complex_selector_list( element: &E, context: &mut MatchingContext, rightmost: SubjectOrPseudoElement, -) -> bool { - for selector in list { - if matches_complex_selector(selector.iter(), element, context, rightmost) { - return true; - } - } - false +) -> KleeneValue { + KleeneValue::any( + list.iter(), + |selector| matches_complex_selector( + selector.iter(), + element, + context, + rightmost + ) + ) } fn matches_relative_selector( @@ -423,7 +489,8 @@ fn matches_relative_selector( &el, context, rightmost, - ); + ) + .to_bool(true); if !matched && relative_selector.match_hint.is_subtree() { matched = matches_relative_selector_subtree( &relative_selector.selector, @@ -472,6 +539,7 @@ fn matches_relative_selector( ) } else { matches_complex_selector(relative_selector.selector.iter(), &el, context, rightmost) + .to_bool(true) }; if matched { return true; @@ -490,12 +558,6 @@ fn relative_selector_match_early( element: &E, context: &mut MatchingContext, ) -> Option { - if context.matching_for_invalidation() { - // In the context of invalidation, we can't use caching/filtering due to - // now/then matches. DOM structure also may have changed, so just pretend - // that we always match. - return Some(!context.in_negation()); - } // See if we can return a cached result. if let Some(cached) = context .selector_caches @@ -526,18 +588,28 @@ fn match_relative_selectors( element: &E, context: &mut MatchingContext, rightmost: SubjectOrPseudoElement, -) -> bool { +) -> KleeneValue { if context.relative_selector_anchor().is_some() { // FIXME(emilio): This currently can happen with nesting, and it's not fully // correct, arguably. But the ideal solution isn't super-clear either. For now, // cope with it and explicitly reject it at match time. See [1] for discussion. // // [1]: https://github.com/w3c/csswg-drafts/issues/9600 - return false; + return KleeneValue::False; + } + if let Some(may_return_unknown) = context.matching_for_invalidation_comparison() { + // In the context of invalidation, :has is expensive, especially because we + // can't use caching/filtering due to now/then matches. DOM structure also + // may have changed. + return if may_return_unknown { + KleeneValue::Unknown + } else { + KleeneValue::from(!context.in_negation()) + }; } context.nest_for_relative_selector(element.opaque(), |context| { do_match_relative_selectors(selectors, element, context, rightmost) - }) + }).into() } /// Matches a relative selector in a list of relative selectors. @@ -604,7 +676,7 @@ fn matches_relative_selector_subtree( ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR, ); } - if matches_complex_selector(selector.iter(), &el, context, rightmost) { + if matches_complex_selector(selector.iter(), &el, context, rightmost).to_bool(true) { return true; } @@ -643,19 +715,8 @@ fn hover_and_active_quirk_applies( } selector_iter.clone().all(|simple| match *simple { - Component::LocalName(_) | - Component::AttributeInNoNamespaceExists { .. } | - Component::AttributeInNoNamespace { .. } | - Component::AttributeOther(_) | - Component::ID(_) | - Component::Class(_) | - Component::PseudoElement(_) | - Component::Negation(_) | - Component::Empty | - Component::Nth(_) | - Component::NthOf(_) => false, Component::NonTSPseudoClass(ref pseudo_class) => pseudo_class.is_active_or_hover(), - _ => true, + _ => false, }) } @@ -753,6 +814,7 @@ fn matches_complex_selector_internal( element: &E, context: &mut MatchingContext, rightmost: SubjectOrPseudoElement, + first_subject_compound: SubjectOrPseudoElement, ) -> SelectorMatchingResult where E: Element, @@ -762,8 +824,24 @@ where selector_iter, element ); - let matches_compound_selector = - matches_compound_selector(&mut selector_iter, element, context, rightmost); + let matches_compound_selector = { + let result = matches_compound_selector(&mut selector_iter, element, context, rightmost); + // We only care for unknown match in the first subject in compound - in the context of comparison + // invalidation, ancestors/previous sibling being an unknown match doesn't matter - we must + // invalidate to guarantee correctness. + if result == KleeneValue::Unknown && first_subject_compound == SubjectOrPseudoElement::No { + debug_assert!( + context + .matching_for_invalidation_comparison() + .unwrap_or(false), + "How did we return unknown?" + ); + // Coerce the result to matched. + KleeneValue::True + } else { + result + } + }; let combinator = selector_iter.next_sequence(); if combinator.map_or(false, |c| c.is_sibling()) { @@ -772,24 +850,42 @@ where } } - if !matches_compound_selector { + // We don't short circuit unknown here, since the rest of the selector + // to the left of this compound may return false. + if matches_compound_selector == KleeneValue::False { return SelectorMatchingResult::NotMatchedAndRestartFromClosestLaterSibling; } let combinator = match combinator { - None => return SelectorMatchingResult::Matched, + None => { + return match matches_compound_selector { + KleeneValue::True => SelectorMatchingResult::Matched, + KleeneValue::Unknown => SelectorMatchingResult::Unknown, + KleeneValue::False => unreachable!(), + } + }, Some(c) => c, }; - let (candidate_not_found, mut rightmost) = match combinator { - Combinator::NextSibling | Combinator::LaterSibling => { - (SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant, SubjectOrPseudoElement::No) - }, + let (candidate_not_found, rightmost, first_subject_compound) = match combinator { + Combinator::NextSibling | Combinator::LaterSibling => ( + SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant, + SubjectOrPseudoElement::No, + SubjectOrPseudoElement::No, + ), Combinator::Child | Combinator::Descendant | Combinator::SlotAssignment | - Combinator::Part => (SelectorMatchingResult::NotMatchedGlobally, SubjectOrPseudoElement::No), - Combinator::PseudoElement => (SelectorMatchingResult::NotMatchedGlobally, rightmost), + Combinator::Part => ( + SelectorMatchingResult::NotMatchedGlobally, + SubjectOrPseudoElement::No, + SubjectOrPseudoElement::No, + ), + Combinator::PseudoElement => ( + SelectorMatchingResult::NotMatchedGlobally, + rightmost, + first_subject_compound, + ), }; // Stop matching :visited as soon as we find a link, or a combinator for @@ -818,18 +914,27 @@ where &element, context, rightmost, + first_subject_compound, ) }); - if !matches!(combinator, Combinator::PseudoElement) { - rightmost = SubjectOrPseudoElement::No; - } - match (result, combinator) { // Return the status immediately. - (SelectorMatchingResult::Matched, _) | - (SelectorMatchingResult::NotMatchedGlobally, _) | - (_, Combinator::NextSibling) => { + (SelectorMatchingResult::Matched | SelectorMatchingResult::Unknown, _) => { + debug_assert!( + matches_compound_selector.to_bool(true), + "Compound didn't match?" + ); + if result == SelectorMatchingResult::Matched && + matches_compound_selector.to_bool(false) + { + // Matches without question + return result; + } + // Something returned unknown, so return unknown. + return SelectorMatchingResult::Unknown; + }, + (SelectorMatchingResult::NotMatchedGlobally, _) | (_, Combinator::NextSibling) => { return result; }, @@ -921,18 +1026,18 @@ fn matches_host( selector: Option<&Selector>, context: &mut MatchingContext, rightmost: SubjectOrPseudoElement, -) -> bool +) -> KleeneValue where E: Element, { let host = match context.shadow_host() { Some(h) => h, - None => return false, + None => return KleeneValue::False, }; if host != element.opaque() { - return false; + return KleeneValue::False; } - selector.map_or(true, |selector| { + selector.map_or(KleeneValue::True, |selector| { context .nest(|context| matches_complex_selector(selector.iter(), element, context, rightmost)) }) @@ -943,13 +1048,13 @@ fn matches_slotted( selector: &Selector, context: &mut MatchingContext, rightmost: SubjectOrPseudoElement, -) -> bool +) -> KleeneValue where E: Element, { // are never flattened tree slottables. if element.is_html_slot_element() { - return false; + return KleeneValue::False; } context.nest(|context| matches_complex_selector(selector.iter(), element, context, rightmost)) } @@ -994,7 +1099,7 @@ fn matches_compound_selector( element: &E, context: &mut MatchingContext, rightmost: SubjectOrPseudoElement, -) -> bool +) -> KleeneValue where E: Element, { @@ -1008,7 +1113,14 @@ where rightmost, quirks_data, }; - selector_iter.all(|simple| matches_simple_selector(simple, element, &mut local_context)) + KleeneValue::any_false( + selector_iter, + |simple| matches_simple_selector( + simple, + element, + &mut local_context + ) + ) } /// Determines whether the given element matches the given single selector. @@ -1016,13 +1128,13 @@ fn matches_simple_selector( selector: &Component, element: &E, context: &mut LocalMatchingContext, -) -> bool +) -> KleeneValue where E: Element, { debug_assert!(context.shared.is_nested() || !context.shared.in_negation()); let rightmost = context.rightmost; - match *selector { + KleeneValue::from(match *selector { Component::ID(ref id) => { element.has_id(id, context.shared.classes_and_ids_case_sensitivity()) }, @@ -1053,7 +1165,7 @@ where }, Component::Part(ref parts) => matches_part(element, parts, &mut context.shared), Component::Slotted(ref selector) => { - matches_slotted(element, selector, &mut context.shared, rightmost) + return matches_slotted(element, selector, &mut context.shared, rightmost); }, Component::PseudoElement(ref pseudo) => { element.match_pseudo_element(pseudo, context.shared) @@ -1072,7 +1184,7 @@ where !element.is_link() && hover_and_active_quirk_applies(iter, context.shared, context.rightmost) { - return false; + return KleeneValue::False; } } element.match_non_ts_pseudo_class(pc, &mut context.shared) @@ -1085,32 +1197,43 @@ where element.is_empty() }, Component::Host(ref selector) => { - matches_host(element, selector.as_ref(), &mut context.shared, rightmost) + return matches_host(element, selector.as_ref(), &mut context.shared, rightmost); }, Component::ParentSelector | Component::Scope => match context.shared.scope_element { Some(ref scope_element) => element.opaque() == *scope_element, None => element.is_root(), }, Component::Nth(ref nth_data) => { - matches_generic_nth_child(element, context.shared, nth_data, &[], rightmost) + return matches_generic_nth_child(element, context.shared, nth_data, &[], rightmost); }, - Component::NthOf(ref nth_of_data) => context.shared.nest(|context| { - matches_generic_nth_child( + Component::NthOf(ref nth_of_data) => { + return context.shared.nest(|context| { + matches_generic_nth_child( + element, + context, + nth_of_data.nth_data(), + nth_of_data.selectors(), + rightmost, + ) + }) + }, + Component::Is(ref list) | Component::Where(ref list) => { + return context.shared.nest(|context| { + matches_complex_selector_list(list.slice(), element, context, rightmost) + }) + }, + Component::Negation(ref list) => { + return context.shared.nest_for_negation(|context| { + !matches_complex_selector_list(list.slice(), element, context, rightmost) + }) + }, + Component::Has(ref relative_selectors) => { + return match_relative_selectors( + relative_selectors, element, - context, - nth_of_data.nth_data(), - nth_of_data.selectors(), + context.shared, rightmost, - ) - }), - Component::Is(ref list) | Component::Where(ref list) => context.shared.nest(|context| { - matches_complex_selector_list(list.slice(), element, context, rightmost) - }), - Component::Negation(ref list) => context.shared.nest_for_negation(|context| { - !matches_complex_selector_list(list.slice(), element, context, rightmost) - }), - Component::Has(ref relative_selectors) => { - match_relative_selectors(relative_selectors, element, context.shared, rightmost) + ); }, Component::Combinator(_) => unsafe { debug_unreachable!("Shouldn't try to selector-match combinators") @@ -1121,7 +1244,7 @@ where anchor.map_or(true, |a| a == element.opaque()) }, Component::Invalid(..) => false, - } + }) } #[inline(always)] @@ -1163,19 +1286,23 @@ fn matches_generic_nth_child( nth_data: &NthSelectorData, selectors: &[Selector], rightmost: SubjectOrPseudoElement, -) -> bool +) -> KleeneValue where E: Element, { if element.ignores_nth_child_selectors() { - return false; + return KleeneValue::False; } let has_selectors = !selectors.is_empty(); - let selectors_match = - !has_selectors || matches_complex_selector_list(selectors, element, context, rightmost); - if context.matching_for_invalidation() { + let selectors_match = !has_selectors || + matches_complex_selector_list(selectors, element, context, rightmost).to_bool(true); + if let Some(may_return_unknown) = context.matching_for_invalidation_comparison() { // Skip expensive indexing math in invalidation. - return selectors_match && !context.in_negation(); + return if selectors_match && may_return_unknown { + KleeneValue::Unknown + } else { + KleeneValue::from(selectors_match && !context.in_negation()) + }; } let NthSelectorData { ty, a, b, .. } = *nth_data; @@ -1185,18 +1312,23 @@ where !has_selectors, ":only-child and :only-of-type cannot have a selector list!" ); - return matches_generic_nth_child( - element, - context, - &NthSelectorData::first(is_of_type), - selectors, - rightmost, - ) && matches_generic_nth_child( - element, - context, - &NthSelectorData::last(is_of_type), - selectors, - rightmost, + return KleeneValue::from( + matches_generic_nth_child( + element, + context, + &NthSelectorData::first(is_of_type), + selectors, + rightmost, + ) + .to_bool(true) && + matches_generic_nth_child( + element, + context, + &NthSelectorData::last(is_of_type), + selectors, + rightmost, + ) + .to_bool(true), ); } @@ -1223,7 +1355,7 @@ where } if !selectors_match { - return false; + return KleeneValue::False; } // :first/last-child are rather trivial to match, don't bother with the @@ -1234,7 +1366,8 @@ where } else { element.prev_sibling_element() } - .is_none(); + .is_none() + .into(); } // Lookup or compute the index. @@ -1280,6 +1413,7 @@ where None /* a == 0 */ => an == 0, }, } + .into() } #[inline] @@ -1314,7 +1448,7 @@ where let matches = if is_of_type { element.is_same_type(&curr) } else if !selectors.is_empty() { - matches_complex_selector_list(selectors, &curr, context, rightmost) + matches_complex_selector_list(selectors, &curr, context, rightmost).to_bool(true) } else { true }; @@ -1345,7 +1479,7 @@ where let matches = if is_of_type { element.is_same_type(&curr) } else if !selectors.is_empty() { - matches_complex_selector_list(selectors, &curr, context, rightmost) + matches_complex_selector_list(selectors, &curr, context, rightmost).to_bool(true) } else { true }; -- cgit v1.2.3