mirror of
https://github.com/servo/servo.git
synced 2025-07-02 04:53:39 +01:00
Kinda tricky because :host only matches rules on the shadow root where the rules come from. So we need to be careful during invalidation and style sharing. I didn't use the non_ts_pseudo_class_list bits because as soon as we implement the :host(..) bits we're going to need to special-case it anyway. The general schema is the following: * Rightmost featureless :host selectors are handled inserting them in the host_rules hashmap. Note that we only insert featureless stuff there. We could insert all of them and just filter during matching, but that's slightly annoying. * The other selectors, like non-featureless :host or what not, are added to the normal cascade data. This is harmless, since the shadow host rules are never matched against the host, so we know they'll just never match, and avoids adding more special-cases. * Featureless :host selectors to the left of a combinator are handled during matching, in the special-case of next_element_for_combinator in selectors. This prevents this from being more invasive, and keeps the usual fast path slim, but it's a bit hard to match the spec and the implementation. We could keep a copy of the SelectorIter instead in the matching context to make the handling of featureless-ness explicit in match_non_ts_pseudo_class, but we'd still need the special-case anyway, so I'm not fond of it. * We take advantage of one thing that makes this sound. As you may have noticed, if you had `root` element which is a ShadowRoot, and you matched something like `div:host` against it, using a MatchingContext with current_host == root, we'd incorrectly report a match. But this is impossible due to the following constraints: * Shadow root rules aren't matched against the host during styling (except these featureless selectors). * DOM APIs' current_host needs to be the _containing_ host, not the element itself if you're a Shadow host. Bug: 992245 Reviewed-by: xidorn MozReview-Commit-ID: KayYNfTXb5h
309 lines
9.5 KiB
Rust
309 lines
9.5 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
use attr::CaseSensitivity;
|
|
use bloom::BloomFilter;
|
|
use nth_index_cache::NthIndexCache;
|
|
use parser::SelectorImpl;
|
|
use tree::{Element, OpaqueElement};
|
|
|
|
/// What kind of selector matching mode we should use.
|
|
///
|
|
/// There are two modes of selector matching. The difference is only noticeable
|
|
/// in presence of pseudo-elements.
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub enum MatchingMode {
|
|
/// Don't ignore any pseudo-element selectors.
|
|
Normal,
|
|
|
|
/// Ignores any stateless pseudo-element selectors in the rightmost sequence
|
|
/// of simple selectors.
|
|
///
|
|
/// This is useful, for example, to match against ::before when you aren't a
|
|
/// pseudo-element yourself.
|
|
///
|
|
/// For example, in presence of `::before:hover`, it would never match, but
|
|
/// `::before` would be ignored as in "matching".
|
|
///
|
|
/// It's required for all the selectors you match using this mode to have a
|
|
/// pseudo-element.
|
|
ForStatelessPseudoElement,
|
|
}
|
|
|
|
/// The mode to use when matching unvisited and visited links.
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub enum VisitedHandlingMode {
|
|
/// All links are matched as if they are unvisted.
|
|
AllLinksUnvisited,
|
|
/// All links are matched as if they are visited and unvisited (both :link
|
|
/// and :visited match).
|
|
///
|
|
/// This is intended to be used from invalidation code, to be conservative
|
|
/// about whether we need to restyle a link.
|
|
AllLinksVisitedAndUnvisited,
|
|
/// A element's "relevant link" is the element being matched if it is a link
|
|
/// or the nearest ancestor link. The relevant link is matched as though it
|
|
/// is visited, and all other links are matched as if they are unvisited.
|
|
RelevantLinkVisited,
|
|
}
|
|
|
|
impl VisitedHandlingMode {
|
|
#[inline]
|
|
pub fn matches_visited(&self) -> bool {
|
|
matches!(
|
|
*self,
|
|
VisitedHandlingMode::RelevantLinkVisited |
|
|
VisitedHandlingMode::AllLinksVisitedAndUnvisited
|
|
)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn matches_unvisited(&self) -> bool {
|
|
matches!(
|
|
*self,
|
|
VisitedHandlingMode::AllLinksUnvisited |
|
|
VisitedHandlingMode::AllLinksVisitedAndUnvisited
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Which quirks mode is this document in.
|
|
///
|
|
/// See: https://quirks.spec.whatwg.org/
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
pub enum QuirksMode {
|
|
/// Quirks mode.
|
|
Quirks,
|
|
/// Limited quirks mode.
|
|
LimitedQuirks,
|
|
/// No quirks mode.
|
|
NoQuirks,
|
|
}
|
|
|
|
impl QuirksMode {
|
|
#[inline]
|
|
pub fn classes_and_ids_case_sensitivity(self) -> CaseSensitivity {
|
|
match self {
|
|
QuirksMode::NoQuirks |
|
|
QuirksMode::LimitedQuirks => CaseSensitivity::CaseSensitive,
|
|
QuirksMode::Quirks => CaseSensitivity::AsciiCaseInsensitive,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Data associated with the matching process for a element. This context is
|
|
/// used across many selectors for an element, so it's not appropriate for
|
|
/// transient data that applies to only a single selector.
|
|
pub struct MatchingContext<'a, Impl>
|
|
where
|
|
Impl: SelectorImpl,
|
|
{
|
|
/// Input with the matching mode we should use when matching selectors.
|
|
matching_mode: MatchingMode,
|
|
/// Input with the bloom filter used to fast-reject selectors.
|
|
pub bloom_filter: Option<&'a BloomFilter>,
|
|
/// An optional cache to speed up nth-index-like selectors.
|
|
pub nth_index_cache: Option<&'a mut NthIndexCache>,
|
|
/// The element which is going to match :scope pseudo-class. It can be
|
|
/// either one :scope element, or the scoping element.
|
|
///
|
|
/// Note that, although in theory there can be multiple :scope elements,
|
|
/// in current specs, at most one is specified, and when there is one,
|
|
/// scoping element is not relevant anymore, so we use a single field for
|
|
/// them.
|
|
///
|
|
/// When this is None, :scope will match the root element.
|
|
///
|
|
/// See https://drafts.csswg.org/selectors-4/#scope-pseudo
|
|
pub scope_element: Option<OpaqueElement>,
|
|
|
|
/// The current shadow host we're collecting :host rules for.
|
|
pub current_host: Option<OpaqueElement>,
|
|
|
|
/// Controls how matching for links is handled.
|
|
visited_handling: VisitedHandlingMode,
|
|
|
|
/// The current nesting level of selectors that we're matching.
|
|
///
|
|
/// FIXME(emilio): Consider putting the mutable stuff in a Cell, then make
|
|
/// MatchingContext immutable again.
|
|
nesting_level: usize,
|
|
|
|
/// Whether we're inside a negation or not.
|
|
in_negation: bool,
|
|
|
|
/// An optional hook function for checking whether a pseudo-element
|
|
/// should match when matching_mode is ForStatelessPseudoElement.
|
|
pub pseudo_element_matching_fn: Option<&'a Fn(&Impl::PseudoElement) -> bool>,
|
|
|
|
/// Extra implementation-dependent matching data.
|
|
pub extra_data: Impl::ExtraMatchingData,
|
|
|
|
quirks_mode: QuirksMode,
|
|
classes_and_ids_case_sensitivity: CaseSensitivity,
|
|
_impl: ::std::marker::PhantomData<Impl>,
|
|
}
|
|
|
|
impl<'a, Impl> MatchingContext<'a, Impl>
|
|
where
|
|
Impl: SelectorImpl,
|
|
{
|
|
/// Constructs a new `MatchingContext`.
|
|
pub fn new(
|
|
matching_mode: MatchingMode,
|
|
bloom_filter: Option<&'a BloomFilter>,
|
|
nth_index_cache: Option<&'a mut NthIndexCache>,
|
|
quirks_mode: QuirksMode,
|
|
) -> Self {
|
|
Self::new_for_visited(
|
|
matching_mode,
|
|
bloom_filter,
|
|
nth_index_cache,
|
|
VisitedHandlingMode::AllLinksUnvisited,
|
|
quirks_mode
|
|
)
|
|
}
|
|
|
|
/// Constructs a new `MatchingContext` for use in visited matching.
|
|
pub fn new_for_visited(
|
|
matching_mode: MatchingMode,
|
|
bloom_filter: Option<&'a BloomFilter>,
|
|
nth_index_cache: Option<&'a mut NthIndexCache>,
|
|
visited_handling: VisitedHandlingMode,
|
|
quirks_mode: QuirksMode,
|
|
) -> Self {
|
|
Self {
|
|
matching_mode,
|
|
bloom_filter,
|
|
visited_handling,
|
|
nth_index_cache,
|
|
quirks_mode,
|
|
classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(),
|
|
scope_element: None,
|
|
current_host: None,
|
|
nesting_level: 0,
|
|
in_negation: false,
|
|
pseudo_element_matching_fn: None,
|
|
extra_data: Default::default(),
|
|
_impl: ::std::marker::PhantomData,
|
|
}
|
|
}
|
|
|
|
/// Override the quirks mode we're matching against.
|
|
///
|
|
/// FIXME(emilio): This is a hack for XBL quirks-mode mismatches.
|
|
#[inline]
|
|
pub fn set_quirks_mode(&mut self, quirks_mode: QuirksMode) {
|
|
self.quirks_mode = quirks_mode;
|
|
self.classes_and_ids_case_sensitivity =
|
|
quirks_mode.classes_and_ids_case_sensitivity();
|
|
}
|
|
|
|
/// Whether we're matching a nested selector.
|
|
#[inline]
|
|
pub fn is_nested(&self) -> bool {
|
|
self.nesting_level != 0
|
|
}
|
|
|
|
/// Whether we're matching inside a :not(..) selector.
|
|
#[inline]
|
|
pub fn in_negation(&self) -> bool {
|
|
self.in_negation
|
|
}
|
|
|
|
/// The quirks mode of the document.
|
|
#[inline]
|
|
pub fn quirks_mode(&self) -> QuirksMode {
|
|
self.quirks_mode
|
|
}
|
|
|
|
/// The matching-mode for this selector-matching operation.
|
|
#[inline]
|
|
pub fn matching_mode(&self) -> MatchingMode {
|
|
self.matching_mode
|
|
}
|
|
|
|
/// The case-sensitivity for class and ID selectors
|
|
#[inline]
|
|
pub fn classes_and_ids_case_sensitivity(&self) -> CaseSensitivity {
|
|
self.classes_and_ids_case_sensitivity
|
|
}
|
|
|
|
/// Runs F with a deeper nesting level.
|
|
#[inline]
|
|
pub fn nest<F, R>(&mut self, f: F) -> R
|
|
where
|
|
F: FnOnce(&mut Self) -> R,
|
|
{
|
|
self.nesting_level += 1;
|
|
let result = f(self);
|
|
self.nesting_level -= 1;
|
|
result
|
|
}
|
|
|
|
/// Runs F with a deeper nesting level, and marking ourselves in a negation,
|
|
/// for a :not(..) selector, for example.
|
|
#[inline]
|
|
pub fn nest_for_negation<F, R>(&mut self, f: F) -> R
|
|
where
|
|
F: FnOnce(&mut Self) -> R,
|
|
{
|
|
debug_assert!(
|
|
!self.in_negation,
|
|
"Someone messed up parsing?"
|
|
);
|
|
self.in_negation = true;
|
|
let result = self.nest(f);
|
|
self.in_negation = false;
|
|
result
|
|
}
|
|
|
|
#[inline]
|
|
pub fn visited_handling(&self) -> VisitedHandlingMode {
|
|
self.visited_handling
|
|
}
|
|
|
|
/// Runs F with a different VisitedHandlingMode.
|
|
#[inline]
|
|
pub fn with_visited_handling_mode<F, R>(
|
|
&mut self,
|
|
handling_mode: VisitedHandlingMode,
|
|
f: F,
|
|
) -> R
|
|
where
|
|
F: FnOnce(&mut Self) -> R,
|
|
{
|
|
let original_handling_mode = self.visited_handling;
|
|
self.visited_handling = handling_mode;
|
|
let result = f(self);
|
|
self.visited_handling = original_handling_mode;
|
|
result
|
|
}
|
|
|
|
/// Runs F with a given shadow host which is the root of the tree whose
|
|
/// rules we're matching.
|
|
#[inline]
|
|
pub fn with_shadow_host<F, E, R>(
|
|
&mut self,
|
|
host: Option<E>,
|
|
f: F,
|
|
) -> R
|
|
where
|
|
E: Element,
|
|
F: FnOnce(&mut Self) -> R,
|
|
{
|
|
let original_host = self.current_host.take();
|
|
self.current_host = host.map(|h| h.opaque());
|
|
let result = f(self);
|
|
self.current_host = original_host;
|
|
result
|
|
}
|
|
|
|
/// Returns the current shadow host whose shadow root we're matching rules
|
|
/// against.
|
|
#[inline]
|
|
pub fn shadow_host(&self) -> Option<OpaqueElement> {
|
|
self.current_host.clone()
|
|
}
|
|
}
|