Bug 1879349 - Improve featureless host matching. r=dshin
Remove host-multiple-006 because it's a bit-identical copy of host-multiple-002.html. Add a test for the specific descendant combinator (bug 1950290). Differential Revision: https://phabricator.services.mozilla.com/D240033
This commit is contained in:
@@ -184,9 +184,8 @@ bitflags! {
|
||||
const HAS_SLOTTED = 1 << 1;
|
||||
const HAS_PART = 1 << 2;
|
||||
const HAS_PARENT = 1 << 3;
|
||||
const HAS_NON_FEATURELESS_COMPONENT = 1 << 4;
|
||||
const HAS_HOST = 1 << 5;
|
||||
const HAS_SCOPE = 1 << 6;
|
||||
const HAS_HOST = 1 << 4;
|
||||
const HAS_SCOPE = 1 << 5;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,13 +305,10 @@ where
|
||||
}
|
||||
},
|
||||
Component::LocalName(..) => {
|
||||
flags.insert(SelectorFlags::HAS_NON_FEATURELESS_COMPONENT);
|
||||
specificity.element_selectors += 1
|
||||
},
|
||||
Component::Slotted(ref selector) => {
|
||||
flags.insert(
|
||||
SelectorFlags::HAS_SLOTTED | SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
);
|
||||
flags.insert(SelectorFlags::HAS_SLOTTED);
|
||||
if !for_nesting_parent {
|
||||
specificity.element_selectors += 1;
|
||||
// Note that due to the way ::slotted works we only compete with
|
||||
@@ -331,11 +327,10 @@ where
|
||||
if let Some(ref selector) = *selector {
|
||||
// See: https://github.com/w3c/csswg-drafts/issues/1915
|
||||
*specificity += Specificity::from(selector.specificity());
|
||||
flags.insert(selector.flags() - SelectorFlags::HAS_NON_FEATURELESS_COMPONENT);
|
||||
flags.insert(selector.flags());
|
||||
}
|
||||
},
|
||||
Component::ID(..) => {
|
||||
flags.insert(SelectorFlags::HAS_NON_FEATURELESS_COMPONENT);
|
||||
specificity.id_selectors += 1;
|
||||
},
|
||||
Component::Class(..) |
|
||||
@@ -346,7 +341,6 @@ where
|
||||
Component::Empty |
|
||||
Component::Nth(..) |
|
||||
Component::NonTSPseudoClass(..) => {
|
||||
flags.insert(SelectorFlags::HAS_NON_FEATURELESS_COMPONENT);
|
||||
specificity.class_like_selectors += 1;
|
||||
},
|
||||
Component::Scope | Component::ImplicitScope => {
|
||||
@@ -365,7 +359,7 @@ where
|
||||
specificity.class_like_selectors += 1;
|
||||
let sf = selector_list_specificity_and_flags(nth_of_data.selectors().iter(), for_nesting_parent);
|
||||
*specificity += Specificity::from(sf.specificity);
|
||||
flags.insert(sf.flags | SelectorFlags::HAS_NON_FEATURELESS_COMPONENT);
|
||||
flags.insert(sf.flags);
|
||||
},
|
||||
// https://drafts.csswg.org/selectors/#specificity-rules:
|
||||
//
|
||||
@@ -384,7 +378,7 @@ where
|
||||
Component::Has(ref relative_selectors) => {
|
||||
let sf = relative_selector_list_specificity_and_flags(relative_selectors, for_nesting_parent);
|
||||
*specificity += Specificity::from(sf.specificity);
|
||||
flags.insert(sf.flags | SelectorFlags::HAS_NON_FEATURELESS_COMPONENT);
|
||||
flags.insert(sf.flags);
|
||||
},
|
||||
Component::ExplicitUniversalType |
|
||||
Component::ExplicitAnyNamespace |
|
||||
@@ -394,7 +388,6 @@ where
|
||||
Component::RelativeSelectorAnchor |
|
||||
Component::Invalid(..) => {
|
||||
// Does not affect specificity
|
||||
flags.insert(SelectorFlags::HAS_NON_FEATURELESS_COMPONENT);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +176,9 @@ where
|
||||
/// Whether there are any rules inside @starting-style.
|
||||
pub has_starting_style: bool,
|
||||
|
||||
/// Whether we're currently matching a featureless element.
|
||||
pub featureless: bool,
|
||||
|
||||
/// The current nesting level of selectors that we're matching.
|
||||
nesting_level: usize,
|
||||
|
||||
@@ -253,6 +256,7 @@ where
|
||||
matching_for_invalidation,
|
||||
scope_element: None,
|
||||
current_host: None,
|
||||
featureless: false,
|
||||
nesting_level: 0,
|
||||
in_negation: false,
|
||||
pseudo_element_matching_fn: None,
|
||||
@@ -373,6 +377,31 @@ where
|
||||
self.visited_handling
|
||||
}
|
||||
|
||||
/// Runs F with a different featureless element flag.
|
||||
#[inline]
|
||||
pub fn with_featureless<F, R>(
|
||||
&mut self,
|
||||
featureless: bool,
|
||||
f: F,
|
||||
) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
{
|
||||
let orig = self.featureless;
|
||||
self.featureless = featureless;
|
||||
let result = f(self);
|
||||
self.featureless = orig;
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns whether the currently matching element is acting as a featureless element (e.g.,
|
||||
/// because we've crossed a shadow boundary). This is used to implement the :host selector
|
||||
/// rules properly.
|
||||
#[inline]
|
||||
pub fn featureless(&self) -> bool {
|
||||
self.featureless
|
||||
}
|
||||
|
||||
/// Runs F with a different VisitedHandlingMode.
|
||||
#[inline]
|
||||
pub fn with_visited_handling_mode<F, R>(
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::attr::{
|
||||
use crate::bloom::{BloomFilter, BLOOM_HASH_MASK};
|
||||
use crate::kleene_value::KleeneValue;
|
||||
use crate::parser::{
|
||||
AncestorHashes, Combinator, Component, FeaturelessHostMatches, LocalName, NthSelectorData,
|
||||
AncestorHashes, Combinator, Component, MatchesFeaturelessHost, LocalName, NthSelectorData,
|
||||
RelativeSelectorMatchHint,
|
||||
};
|
||||
use crate::parser::{
|
||||
@@ -376,6 +376,11 @@ where
|
||||
start_offset
|
||||
);
|
||||
|
||||
debug_assert!(
|
||||
!local_context.shared.featureless(),
|
||||
"Invalidating featureless element somehow?"
|
||||
);
|
||||
|
||||
for component in iter {
|
||||
let result = matches_simple_selector(component, element, &mut local_context);
|
||||
debug_assert!(result != KleeneValue::Unknown, "Returned unknown in non invalidation context?");
|
||||
@@ -758,66 +763,47 @@ where
|
||||
Some(current_slot)
|
||||
}
|
||||
|
||||
struct NextElement<E> {
|
||||
next_element: Option<E>,
|
||||
featureless: bool,
|
||||
}
|
||||
|
||||
impl<E> NextElement<E> {
|
||||
#[inline(always)]
|
||||
fn new(next_element: Option<E>, featureless: bool) -> Self {
|
||||
Self { next_element, featureless }
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn next_element_for_combinator<E>(
|
||||
element: &E,
|
||||
combinator: Combinator,
|
||||
selector: &SelectorIter<E::Impl>,
|
||||
context: &MatchingContext<E::Impl>,
|
||||
) -> Option<E>
|
||||
) -> NextElement<E>
|
||||
where
|
||||
E: Element,
|
||||
{
|
||||
match combinator {
|
||||
Combinator::NextSibling | Combinator::LaterSibling => element.prev_sibling_element(),
|
||||
Combinator::NextSibling | Combinator::LaterSibling => NextElement::new(
|
||||
element.prev_sibling_element(),
|
||||
false,
|
||||
),
|
||||
Combinator::Child | Combinator::Descendant => {
|
||||
match element.parent_element() {
|
||||
Some(e) => return Some(e),
|
||||
None => {},
|
||||
if let Some(parent) = element.parent_element() {
|
||||
return NextElement::new(Some(parent), false);
|
||||
}
|
||||
|
||||
if !element.parent_node_is_shadow_root() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/css-scoping/#host-element-in-tree:
|
||||
//
|
||||
// For the purpose of Selectors, a shadow host also appears in
|
||||
// its shadow tree, with the contents of the shadow tree treated
|
||||
// as its children. (In other words, the shadow host is treated as
|
||||
// replacing the shadow root node.)
|
||||
//
|
||||
// and also:
|
||||
//
|
||||
// When considered within its own shadow trees, the shadow host is
|
||||
// featureless. Only the :host, :host(), and :host-context()
|
||||
// pseudo-classes are allowed to match it.
|
||||
//
|
||||
// Since we know that the parent is a shadow root, we necessarily
|
||||
// are in a shadow tree of the host, and the next selector will only
|
||||
// match if the selector is a featureless :host selector.
|
||||
let matches_featureless_host = selector.clone().is_featureless_host_selector();
|
||||
if matches_featureless_host.intersects(FeaturelessHostMatches::FOR_HOST) {
|
||||
// May not match the inner selector, but we can't really call that here.
|
||||
return element.containing_shadow_host()
|
||||
} else if matches_featureless_host.intersects(FeaturelessHostMatches::FOR_SCOPE) {
|
||||
let host = element.containing_shadow_host();
|
||||
// If this element's shadow host matches the `:scope` element, we should
|
||||
// treat the `:scope` selector as featureless.
|
||||
// See https://github.com/w3c/csswg-drafts/issues/9025.
|
||||
if context.scope_element.is_some() &&
|
||||
context.scope_element.clone() == host.clone().map(|e| e.opaque())
|
||||
{
|
||||
return host;
|
||||
}
|
||||
return None;
|
||||
let element = if element.parent_node_is_shadow_root() {
|
||||
element.containing_shadow_host()
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
None
|
||||
};
|
||||
NextElement::new(element, true)
|
||||
},
|
||||
Combinator::Part => host_for_part(element, context),
|
||||
Combinator::SlotAssignment => assigned_slot(element, context),
|
||||
Combinator::PseudoElement => element.pseudo_element_originating_element(),
|
||||
Combinator::Part => NextElement::new(host_for_part(element, context), false),
|
||||
Combinator::SlotAssignment => NextElement::new(assigned_slot(element, context), false),
|
||||
Combinator::PseudoElement => NextElement::new(element.pseudo_element_originating_element(), false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -856,8 +842,13 @@ where
|
||||
};
|
||||
|
||||
let combinator = selector_iter.next_sequence();
|
||||
if combinator.map_or(false, |c| c.is_sibling()) {
|
||||
if context.needs_selector_flags() {
|
||||
if let Some(c) = combinator {
|
||||
if context.featureless() && !c.is_pseudo_element() {
|
||||
// A featureless element shouldn't match any further combinator.
|
||||
// TODO(emilio): Maybe we could avoid the compound matching more eagerly.
|
||||
return SelectorMatchingResult::NotMatchedGlobally;
|
||||
}
|
||||
if c.is_sibling() && context.needs_selector_flags() {
|
||||
element.apply_selector_flags(ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS);
|
||||
}
|
||||
}
|
||||
@@ -914,25 +905,27 @@ where
|
||||
visited_handling = VisitedHandlingMode::AllLinksUnvisited;
|
||||
}
|
||||
|
||||
element = match next_element_for_combinator(&element, combinator, &selector_iter, &context)
|
||||
{
|
||||
let NextElement { next_element, featureless } = next_element_for_combinator(&element, combinator, &context);
|
||||
element = match next_element {
|
||||
None => return candidate_not_found,
|
||||
Some(next_element) => next_element,
|
||||
Some(e) => e,
|
||||
};
|
||||
|
||||
let result = context.with_visited_handling_mode(visited_handling, |context| {
|
||||
matches_complex_selector_internal(
|
||||
selector_iter.clone(),
|
||||
&element,
|
||||
context,
|
||||
rightmost,
|
||||
first_subject_compound,
|
||||
)
|
||||
context.with_featureless(featureless, |context| {
|
||||
matches_complex_selector_internal(
|
||||
selector_iter.clone(),
|
||||
&element,
|
||||
context,
|
||||
rightmost,
|
||||
first_subject_compound,
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
match (result, combinator) {
|
||||
// Return the status immediately.
|
||||
(SelectorMatchingResult::Matched | SelectorMatchingResult::Unknown, _) => {
|
||||
match result {
|
||||
SelectorMatchingResult::Matched | SelectorMatchingResult::Unknown => {
|
||||
// Return the status immediately.
|
||||
debug_assert!(
|
||||
matches_compound_selector.to_bool(true),
|
||||
"Compound didn't match?"
|
||||
@@ -945,36 +938,43 @@ where
|
||||
}
|
||||
// Something returned unknown, so return unknown.
|
||||
return SelectorMatchingResult::Unknown;
|
||||
},
|
||||
(SelectorMatchingResult::NotMatchedGlobally, _) | (_, Combinator::NextSibling) => {
|
||||
return result;
|
||||
},
|
||||
|
||||
// Upgrade the failure status to
|
||||
// NotMatchedAndRestartFromClosestDescendant.
|
||||
(_, Combinator::PseudoElement) | (_, Combinator::Child) => {
|
||||
return SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant;
|
||||
},
|
||||
|
||||
// If the failure status is
|
||||
// NotMatchedAndRestartFromClosestDescendant and combinator is
|
||||
// Combinator::LaterSibling, give up this Combinator::LaterSibling
|
||||
// matching and restart from the closest descendant combinator.
|
||||
(
|
||||
SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant,
|
||||
Combinator::LaterSibling,
|
||||
) => {
|
||||
return result;
|
||||
},
|
||||
|
||||
// The Combinator::Descendant combinator and the status is
|
||||
// NotMatchedAndRestartFromClosestLaterSibling or
|
||||
// NotMatchedAndRestartFromClosestDescendant, or the
|
||||
// Combinator::LaterSibling combinator and the status is
|
||||
// NotMatchedAndRestartFromClosestDescendant, we can continue to
|
||||
// matching on the next candidate element.
|
||||
}
|
||||
SelectorMatchingResult::NotMatchedGlobally => return result,
|
||||
_ => {},
|
||||
}
|
||||
|
||||
if featureless {
|
||||
// A featureless element didn't match the selector, we can stop matching now rather
|
||||
// than looking at following elements for our combinator.
|
||||
return SelectorMatchingResult::NotMatchedGlobally;
|
||||
}
|
||||
|
||||
match combinator {
|
||||
Combinator::NextSibling => return result,
|
||||
Combinator::PseudoElement | Combinator::Child => {
|
||||
// Upgrade the failure status to NotMatchedAndRestartFromClosestDescendant.
|
||||
return SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant;
|
||||
}
|
||||
Combinator::LaterSibling => {
|
||||
// If the failure status is NotMatchedAndRestartFromClosestDescendant and combinator is
|
||||
// LaterSibling, give up this LaterSibling matching and restart from the closest
|
||||
// descendant combinator.
|
||||
if matches!(result, SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// The Descendant combinator and the status is
|
||||
// NotMatchedAndRestartFromClosestLaterSibling or
|
||||
// NotMatchedAndRestartFromClosestDescendant, or the
|
||||
// LaterSibling combinator and the status is
|
||||
// NotMatchedAndRestartFromClosestDescendant, we can continue to
|
||||
// matching on the next candidate element.
|
||||
//
|
||||
// TODO(emilio): is this sound with Combinator::Part and others? It seems we could
|
||||
// early reject if we haven't matched rather than keeping going?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1049,10 +1049,10 @@ where
|
||||
if host != element.opaque() {
|
||||
return KleeneValue::False;
|
||||
}
|
||||
selector.map_or(KleeneValue::True, |selector| {
|
||||
context
|
||||
.nest(|context| matches_complex_selector(selector.iter(), element, context, rightmost))
|
||||
})
|
||||
let Some(selector) = selector else { return KleeneValue::True };
|
||||
context.nest(|context| context.with_featureless(false, |context| {
|
||||
matches_complex_selector(selector.iter(), element, context, rightmost)
|
||||
}))
|
||||
}
|
||||
|
||||
fn matches_slotted<E>(
|
||||
@@ -1104,6 +1104,62 @@ where
|
||||
)
|
||||
}
|
||||
|
||||
/// There are relatively few selectors in a given compound that may match a featureless element.
|
||||
/// Instead of adding a check to every selector that may not match, we handle it here in an out of
|
||||
/// line path.
|
||||
pub(crate) fn compound_matches_featureless_host<Impl: SelectorImpl>(iter: &mut SelectorIter<Impl>, scope_matches_featureless_host: bool) -> MatchesFeaturelessHost {
|
||||
let mut matches = MatchesFeaturelessHost::Only;
|
||||
for component in iter {
|
||||
match component {
|
||||
Component::Scope | Component::ImplicitScope if scope_matches_featureless_host => {},
|
||||
// :host only matches featureless elements.
|
||||
Component::Host(..) => {},
|
||||
// Pseudo-elements are allowed to match as well.
|
||||
Component::PseudoElement(..) => {},
|
||||
// We allow logical pseudo-classes, but we'll fail matching of the inner selectors if
|
||||
// necessary.
|
||||
Component::Is(ref l) | Component::Where(ref l) => {
|
||||
let mut any_yes = false;
|
||||
let mut any_no = false;
|
||||
for selector in l.slice() {
|
||||
match selector.matches_featureless_host(scope_matches_featureless_host) {
|
||||
MatchesFeaturelessHost::Never => {
|
||||
any_no = true;
|
||||
}
|
||||
MatchesFeaturelessHost::Yes => {
|
||||
any_yes = true;
|
||||
any_no = true;
|
||||
}
|
||||
MatchesFeaturelessHost::Only => {
|
||||
any_yes = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !any_yes {
|
||||
return MatchesFeaturelessHost::Never;
|
||||
}
|
||||
if any_no {
|
||||
// Potentially downgrade since we might match non-featureless elements too.
|
||||
matches = MatchesFeaturelessHost::Yes;
|
||||
}
|
||||
},
|
||||
Component::Negation(ref l) => {
|
||||
// For now preserving behavior, see
|
||||
// https://github.com/w3c/csswg-drafts/issues/10179 for existing resolutions that
|
||||
// tweak this behavior.
|
||||
for selector in l.slice() {
|
||||
if selector.matches_featureless_host(scope_matches_featureless_host) != MatchesFeaturelessHost::Only {
|
||||
return MatchesFeaturelessHost::Never;
|
||||
}
|
||||
}
|
||||
},
|
||||
// Other components don't match the host scope.
|
||||
_ => return MatchesFeaturelessHost::Never,
|
||||
}
|
||||
}
|
||||
matches
|
||||
}
|
||||
|
||||
/// Determines whether the given element matches the given compound selector.
|
||||
#[inline]
|
||||
fn matches_compound_selector<E>(
|
||||
@@ -1115,6 +1171,9 @@ fn matches_compound_selector<E>(
|
||||
where
|
||||
E: Element,
|
||||
{
|
||||
if context.featureless() && compound_matches_featureless_host(&mut selector_iter.clone(), /* scope_matches_featureless_host = */ true) == MatchesFeaturelessHost::Never {
|
||||
return KleeneValue::False;
|
||||
}
|
||||
let quirks_data = if context.quirks_mode() == QuirksMode::Quirks {
|
||||
Some(selector_iter.clone())
|
||||
} else {
|
||||
|
||||
@@ -777,27 +777,23 @@ pub fn namespace_empty_string<Impl: SelectorImpl>() -> Impl::NamespaceUrl {
|
||||
|
||||
type SelectorData<Impl> = ThinArc<SpecificityAndFlags, Component<Impl>>;
|
||||
|
||||
bitflags! {
|
||||
/// What kind of selectors potentially matching featureless shawdow host are present.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct FeaturelessHostMatches: u8 {
|
||||
/// This selector matches featureless shadow host via `:host`.
|
||||
const FOR_HOST = 1 << 0;
|
||||
/// This selector matches featureless shadow host via `:scope`.
|
||||
/// Featureless match applies only if we're:
|
||||
/// 1) In a scoping context, AND
|
||||
/// 2) The scope is a shadow host.
|
||||
const FOR_SCOPE = 1 << 1;
|
||||
}
|
||||
/// Whether a selector may match a featureless host element, and whether it may match other
|
||||
/// elements.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum MatchesFeaturelessHost {
|
||||
/// The selector may match a featureless host, but also a non-featureless element.
|
||||
Yes,
|
||||
/// The selector is guaranteed to never match a non-featureless host element.
|
||||
Only,
|
||||
/// The selector never matches a featureless host.
|
||||
Never,
|
||||
}
|
||||
|
||||
impl FeaturelessHostMatches {
|
||||
fn insert_not_empty(&mut self, other: Self) -> bool {
|
||||
if other.is_empty() {
|
||||
return false;
|
||||
}
|
||||
self.insert(other);
|
||||
true
|
||||
impl MatchesFeaturelessHost {
|
||||
/// Whether we may match.
|
||||
#[inline]
|
||||
pub fn may_match(self) -> bool {
|
||||
return !matches!(self, Self::Never)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -942,6 +938,36 @@ impl<Impl: SelectorImpl> Selector<Impl> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether this selector may match a featureless shadow host, with no combinators to the
|
||||
/// left, and optionally has a pseudo-element to the right.
|
||||
#[inline]
|
||||
pub fn matches_featureless_host(&self, scope_matches_featureless_host: bool) -> MatchesFeaturelessHost {
|
||||
let flags = self.flags();
|
||||
if !flags.intersects(SelectorFlags::HAS_HOST | SelectorFlags::HAS_SCOPE) {
|
||||
return MatchesFeaturelessHost::Never;
|
||||
}
|
||||
|
||||
let mut iter = self.iter();
|
||||
if flags.intersects(SelectorFlags::HAS_PSEUDO) {
|
||||
for _ in &mut iter {
|
||||
// Skip over pseudo-elements
|
||||
}
|
||||
match iter.next_sequence() {
|
||||
Some(c) if c.is_pseudo_element() => {},
|
||||
_ => {
|
||||
debug_assert!(false, "Pseudo selector without pseudo combinator?");
|
||||
return MatchesFeaturelessHost::Never;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let compound_matches = crate::matching::compound_matches_featureless_host(&mut iter, scope_matches_featureless_host);
|
||||
if iter.next_sequence().is_some() {
|
||||
return MatchesFeaturelessHost::Never;
|
||||
}
|
||||
return compound_matches;
|
||||
}
|
||||
|
||||
/// Returns an iterator over this selector in matching order (right-to-left).
|
||||
/// When a combinator is reached, the iterator will return None, and
|
||||
/// next_sequence() may be called to continue to the next sequence.
|
||||
@@ -977,25 +1003,6 @@ impl<Impl: SelectorImpl> Selector<Impl> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this selector matches a featureless shadow host, with no combinators to the left, and
|
||||
/// optionally has a pseudo-element to the right.
|
||||
#[inline]
|
||||
pub fn matches_featureless_host_selector_or_pseudo_element(&self) -> FeaturelessHostMatches {
|
||||
let flags = self.flags();
|
||||
|
||||
let mut result = FeaturelessHostMatches::empty();
|
||||
if flags.intersects(SelectorFlags::HAS_NON_FEATURELESS_COMPONENT) {
|
||||
return result;
|
||||
}
|
||||
if flags.intersects(SelectorFlags::HAS_HOST) {
|
||||
result.insert(FeaturelessHostMatches::FOR_HOST);
|
||||
}
|
||||
if flags.intersects(SelectorFlags::HAS_SCOPE) {
|
||||
result.insert(FeaturelessHostMatches::FOR_SCOPE);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns an iterator over this selector in matching order (right-to-left),
|
||||
/// skipping the rightmost |offset| Components.
|
||||
#[inline]
|
||||
@@ -1244,7 +1251,7 @@ impl<Impl: SelectorImpl> Selector<Impl> {
|
||||
parent,
|
||||
&mut specificity,
|
||||
&mut flags,
|
||||
forbidden_flags | SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
forbidden_flags,
|
||||
))),
|
||||
NthOf(ref data) => {
|
||||
let selectors = replace_parent_on_selector_list(
|
||||
@@ -1417,28 +1424,6 @@ impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> {
|
||||
self.next_combinator.take()
|
||||
}
|
||||
|
||||
/// Whether this selector is a featureless selector matching the shadow host, with no
|
||||
/// combinators to the left.
|
||||
#[inline]
|
||||
pub(crate) fn is_featureless_host_selector(&mut self) -> FeaturelessHostMatches {
|
||||
if self.selector_length() == 0 {
|
||||
return FeaturelessHostMatches::empty();
|
||||
}
|
||||
let mut result = FeaturelessHostMatches::empty();
|
||||
while let Some(c) = self.next() {
|
||||
let component_matches = c.matches_featureless_host();
|
||||
if component_matches.is_empty() {
|
||||
return FeaturelessHostMatches::empty();
|
||||
}
|
||||
result.insert(component_matches);
|
||||
}
|
||||
if self.next_sequence().is_some() {
|
||||
FeaturelessHostMatches::empty()
|
||||
} else {
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn matches_for_stateless_pseudo_element(&mut self) -> bool {
|
||||
let first = match self.next() {
|
||||
@@ -2121,32 +2106,6 @@ impl<Impl: SelectorImpl> Component<Impl> {
|
||||
matches!(*self, Component::Host(..))
|
||||
}
|
||||
|
||||
/// Returns if this component can match a featureless shadow host, and if so,
|
||||
/// via which selector.
|
||||
#[inline]
|
||||
pub fn matches_featureless_host(&self) -> FeaturelessHostMatches {
|
||||
match *self {
|
||||
Component::Host(..) => FeaturelessHostMatches::FOR_HOST,
|
||||
Component::Scope | Component::ImplicitScope => FeaturelessHostMatches::FOR_SCOPE,
|
||||
Component::Where(ref l) | Component::Is(ref l) => {
|
||||
debug_assert!(l.len() > 0, "Zero length selector?");
|
||||
// TODO(emilio): For now we require that everything in logical combination can match
|
||||
// the featureless shadow host, because not doing so brings up a fair amount of extra
|
||||
// complexity (we can't make the decision on whether to walk out statically).
|
||||
let mut result = FeaturelessHostMatches::empty();
|
||||
for i in l.slice() {
|
||||
if !result.insert_not_empty(
|
||||
i.matches_featureless_host_selector_or_pseudo_element()
|
||||
) {
|
||||
return FeaturelessHostMatches::empty();
|
||||
}
|
||||
}
|
||||
result
|
||||
},
|
||||
_ => FeaturelessHostMatches::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value as a combinator if applicable, None otherwise.
|
||||
pub fn as_combinator(&self) -> Option<Combinator> {
|
||||
match *self {
|
||||
@@ -4051,7 +4010,7 @@ pub mod tests {
|
||||
lower_name: DummyAtom::from("eeÉ"),
|
||||
})],
|
||||
specificity(0, 0, 1),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -4065,7 +4024,7 @@ pub mod tests {
|
||||
}),
|
||||
],
|
||||
specificity(0, 0, 1),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
// When the default namespace is not set, *| should be elided.
|
||||
@@ -4078,7 +4037,7 @@ pub mod tests {
|
||||
lower_name: DummyAtom::from("e"),
|
||||
})],
|
||||
specificity(0, 0, 1),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
// When the default namespace is set, *| should _not_ be elided (as foo
|
||||
@@ -4099,7 +4058,7 @@ pub mod tests {
|
||||
}),
|
||||
],
|
||||
specificity(0, 0, 1),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -4107,7 +4066,7 @@ pub mod tests {
|
||||
Ok(SelectorList::from_vec(vec![Selector::from_vec(
|
||||
vec![Component::ExplicitUniversalType],
|
||||
specificity(0, 0, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -4118,7 +4077,7 @@ pub mod tests {
|
||||
Component::ExplicitUniversalType,
|
||||
],
|
||||
specificity(0, 0, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -4126,7 +4085,7 @@ pub mod tests {
|
||||
Ok(SelectorList::from_vec(vec![Selector::from_vec(
|
||||
vec![Component::ExplicitUniversalType],
|
||||
specificity(0, 0, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -4140,7 +4099,7 @@ pub mod tests {
|
||||
Component::ExplicitUniversalType,
|
||||
],
|
||||
specificity(0, 0, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -4151,7 +4110,7 @@ pub mod tests {
|
||||
Component::NonTSPseudoClass(PseudoClass::Lang("en-US".to_owned())),
|
||||
],
|
||||
specificity(0, 2, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -4159,7 +4118,7 @@ pub mod tests {
|
||||
Ok(SelectorList::from_vec(vec![Selector::from_vec(
|
||||
vec![Component::ID(DummyAtom::from("bar"))],
|
||||
specificity(1, 0, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -4174,7 +4133,7 @@ pub mod tests {
|
||||
Component::ID(DummyAtom::from("bar")),
|
||||
],
|
||||
specificity(1, 1, 1),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -4190,7 +4149,7 @@ pub mod tests {
|
||||
Component::ID(DummyAtom::from("bar")),
|
||||
],
|
||||
specificity(1, 1, 1),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
// Default namespace does not apply to attribute selectors
|
||||
@@ -4204,7 +4163,7 @@ pub mod tests {
|
||||
local_name_lower: DummyAtom::from("foo"),
|
||||
}],
|
||||
specificity(0, 1, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
assert!(parse_ns("svg|circle", &parser).is_err());
|
||||
@@ -4222,7 +4181,7 @@ pub mod tests {
|
||||
}),
|
||||
],
|
||||
specificity(0, 0, 1),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -4233,7 +4192,7 @@ pub mod tests {
|
||||
Component::ExplicitUniversalType,
|
||||
],
|
||||
specificity(0, 0, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
// Default namespace does not apply to attribute selectors
|
||||
@@ -4252,7 +4211,7 @@ pub mod tests {
|
||||
},
|
||||
],
|
||||
specificity(0, 1, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
// Default namespace does apply to type selectors
|
||||
@@ -4267,7 +4226,7 @@ pub mod tests {
|
||||
}),
|
||||
],
|
||||
specificity(0, 0, 1),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -4278,7 +4237,7 @@ pub mod tests {
|
||||
Component::ExplicitUniversalType,
|
||||
],
|
||||
specificity(0, 0, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -4289,7 +4248,7 @@ pub mod tests {
|
||||
Component::ExplicitUniversalType,
|
||||
],
|
||||
specificity(0, 0, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
// Default namespace applies to universal and type selectors inside :not and :matches,
|
||||
@@ -4302,11 +4261,11 @@ pub mod tests {
|
||||
Component::Negation(SelectorList::from_vec(vec![Selector::from_vec(
|
||||
vec![Component::Class(DummyAtom::from("cl"))],
|
||||
specificity(0, 1, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)])),
|
||||
],
|
||||
specificity(0, 1, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -4320,11 +4279,11 @@ pub mod tests {
|
||||
Component::ExplicitUniversalType,
|
||||
],
|
||||
specificity(0, 0, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]),),
|
||||
],
|
||||
specificity(0, 0, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -4341,11 +4300,11 @@ pub mod tests {
|
||||
}),
|
||||
],
|
||||
specificity(0, 0, 1),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)])),
|
||||
],
|
||||
specificity(0, 0, 1),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -4358,7 +4317,7 @@ pub mod tests {
|
||||
case_sensitivity: ParsedCaseSensitivity::CaseSensitive,
|
||||
}],
|
||||
specificity(0, 1, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
// https://github.com/mozilla/servo/issues/1723
|
||||
@@ -4382,7 +4341,7 @@ pub mod tests {
|
||||
Component::NonTSPseudoClass(PseudoClass::Hover),
|
||||
],
|
||||
specificity(0, 1, 1),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT | SelectorFlags::HAS_PSEUDO,
|
||||
SelectorFlags::HAS_PSEUDO,
|
||||
)]))
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -4395,7 +4354,7 @@ pub mod tests {
|
||||
Component::NonTSPseudoClass(PseudoClass::Hover),
|
||||
],
|
||||
specificity(0, 2, 1),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT | SelectorFlags::HAS_PSEUDO,
|
||||
SelectorFlags::HAS_PSEUDO,
|
||||
)]))
|
||||
);
|
||||
assert!(parse("::before:hover:lang(foo)").is_err());
|
||||
@@ -4419,7 +4378,7 @@ pub mod tests {
|
||||
Component::PseudoElement(PseudoElement::After),
|
||||
],
|
||||
specificity(0, 0, 2),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT | SelectorFlags::HAS_PSEUDO,
|
||||
SelectorFlags::HAS_PSEUDO,
|
||||
)]))
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -4431,7 +4390,7 @@ pub mod tests {
|
||||
Component::Class(DummyAtom::from("ok")),
|
||||
],
|
||||
(1 << 20) + (1 << 10) + (0 << 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
parser.default_ns = None;
|
||||
@@ -4446,11 +4405,11 @@ pub mod tests {
|
||||
Selector::from_vec(
|
||||
vec![Component::ExplicitUniversalType],
|
||||
specificity(0, 0, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)
|
||||
]))],
|
||||
specificity(0, 0, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -4463,11 +4422,11 @@ pub mod tests {
|
||||
Component::ExplicitUniversalType,
|
||||
],
|
||||
specificity(0, 0, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)
|
||||
]))],
|
||||
specificity(0, 0, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
// *| should be elided if there is no default namespace.
|
||||
@@ -4479,11 +4438,11 @@ pub mod tests {
|
||||
Selector::from_vec(
|
||||
vec![Component::ExplicitUniversalType],
|
||||
specificity(0, 0, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)
|
||||
]))],
|
||||
specificity(0, 0, 0),
|
||||
SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::empty(),
|
||||
)]))
|
||||
);
|
||||
|
||||
@@ -4526,7 +4485,7 @@ pub mod tests {
|
||||
Component::Class(DummyAtom::from("bar")),
|
||||
],
|
||||
(1 << 20) + (1 << 10) + (0 << 0),
|
||||
SelectorFlags::HAS_PARENT | SelectorFlags::HAS_NON_FEATURELESS_COMPONENT
|
||||
SelectorFlags::HAS_PARENT
|
||||
)]))
|
||||
);
|
||||
|
||||
@@ -4610,7 +4569,7 @@ pub mod tests {
|
||||
Component::Class(DummyAtom::from("foo")),
|
||||
],
|
||||
specificity(0, 1, 0),
|
||||
SelectorFlags::HAS_SCOPE | SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::HAS_SCOPE,
|
||||
)])
|
||||
);
|
||||
|
||||
@@ -4623,7 +4582,7 @@ pub mod tests {
|
||||
Component::Class(DummyAtom::from("foo")),
|
||||
],
|
||||
specificity(0, 2, 0),
|
||||
SelectorFlags::HAS_SCOPE | SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::HAS_SCOPE
|
||||
)])
|
||||
);
|
||||
|
||||
@@ -4636,7 +4595,7 @@ pub mod tests {
|
||||
Component::Class(DummyAtom::from("foo")),
|
||||
],
|
||||
specificity(0, 1, 0),
|
||||
SelectorFlags::HAS_SCOPE | SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::HAS_SCOPE
|
||||
)])
|
||||
);
|
||||
|
||||
@@ -4651,26 +4610,11 @@ pub mod tests {
|
||||
Component::Class(DummyAtom::from("bar")),
|
||||
],
|
||||
specificity(0, 3, 0),
|
||||
SelectorFlags::HAS_SCOPE | SelectorFlags::HAS_NON_FEATURELESS_COMPONENT,
|
||||
SelectorFlags::HAS_SCOPE
|
||||
)])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_featureless() {
|
||||
let featureless = parse(":host, :scope").unwrap();
|
||||
assert_eq!(featureless.slice().len(), 2);
|
||||
for selector in featureless.slice() {
|
||||
assert!(!selector.flags().intersects(SelectorFlags::HAS_NON_FEATURELESS_COMPONENT));
|
||||
}
|
||||
|
||||
let non_featureless = parse(":host.foo, :scope.foo, :host .foo, :scope .foo").unwrap();
|
||||
assert_eq!(non_featureless.slice().len(), 4);
|
||||
for selector in non_featureless.slice() {
|
||||
assert!(selector.flags().intersects(SelectorFlags::HAS_NON_FEATURELESS_COMPONENT));
|
||||
}
|
||||
}
|
||||
|
||||
struct TestVisitor {
|
||||
seen: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -366,7 +366,10 @@ where
|
||||
let cascade_level = CascadeLevel::AuthorNormal {
|
||||
shadow_cascade_order,
|
||||
};
|
||||
debug_assert!(!collector.context.featureless(), "How?");
|
||||
collector.context.featureless = true;
|
||||
collector.collect_rules_in_map(host_rules, cascade_level, style_data);
|
||||
collector.context.featureless = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ use selectors::matching::{
|
||||
};
|
||||
use selectors::matching::{MatchingForInvalidation, VisitedHandlingMode};
|
||||
use selectors::parser::{
|
||||
AncestorHashes, Combinator, Component, FeaturelessHostMatches, Selector, SelectorIter,
|
||||
AncestorHashes, Combinator, Component, MatchesFeaturelessHost, Selector, SelectorIter,
|
||||
SelectorList,
|
||||
};
|
||||
use selectors::visitor::{SelectorListKind, SelectorVisitor};
|
||||
@@ -2741,6 +2741,14 @@ fn parent_selector_for_scope(parent: Option<&SelectorList<SelectorImpl>>) -> &Se
|
||||
}
|
||||
}
|
||||
|
||||
fn scope_start_matches_shadow_host(start: &SelectorList<SelectorImpl>) -> bool {
|
||||
// TODO(emilio): Should we carry a MatchesFeaturelessHost rather than a bool around?
|
||||
// Pre-existing behavior with multiple selectors matches this tho.
|
||||
start.slice().iter().any(|s| {
|
||||
s.matches_featureless_host(true).may_match()
|
||||
})
|
||||
}
|
||||
|
||||
impl CascadeData {
|
||||
/// Creates an empty `CascadeData`.
|
||||
pub fn new() -> Self {
|
||||
@@ -3034,10 +3042,7 @@ impl CascadeData {
|
||||
}
|
||||
(
|
||||
ScopeTarget::Selector(&start.selectors),
|
||||
start.selectors.slice().iter().any(|s| {
|
||||
!s.matches_featureless_host_selector_or_pseudo_element()
|
||||
.is_empty()
|
||||
}),
|
||||
scope_start_matches_shadow_host(&start.selectors),
|
||||
)
|
||||
} else {
|
||||
let implicit_root = condition_ref.implicit_scope_root;
|
||||
@@ -3362,28 +3367,27 @@ impl CascadeData {
|
||||
vec.try_reserve(1)?;
|
||||
vec.push(rule);
|
||||
} else {
|
||||
let scope_matches_shadow_host = containing_rule_state.scope_matches_shadow_host == ScopeMatchesShadowHost::Yes;
|
||||
let matches_featureless_host_only = match rule.selector.matches_featureless_host(scope_matches_shadow_host) {
|
||||
MatchesFeaturelessHost::Only => true,
|
||||
MatchesFeaturelessHost::Yes => {
|
||||
// We need to insert this in featureless_host_rules but also normal_rules.
|
||||
self.featureless_host_rules
|
||||
.get_or_insert_with(|| Box::new(Default::default()))
|
||||
.for_insertion(pseudo_element)
|
||||
.insert(rule.clone(), quirks_mode)?;
|
||||
false
|
||||
},
|
||||
MatchesFeaturelessHost::Never => false,
|
||||
};
|
||||
|
||||
// NOTE(emilio): It's fine to look at :host and then at
|
||||
// ::slotted(..), since :host::slotted(..) could never
|
||||
// possibly match, as <slot> is not a valid shadow host.
|
||||
// :scope may match featureless shadow host if the scope
|
||||
// root is the shadow root.
|
||||
// See https://github.com/w3c/csswg-drafts/issues/9025
|
||||
let potentially_matches_featureless_host = rule
|
||||
.selector
|
||||
.matches_featureless_host_selector_or_pseudo_element();
|
||||
let matches_featureless_host = if potentially_matches_featureless_host
|
||||
.intersects(FeaturelessHostMatches::FOR_HOST)
|
||||
{
|
||||
true
|
||||
} else if potentially_matches_featureless_host
|
||||
.intersects(FeaturelessHostMatches::FOR_SCOPE)
|
||||
{
|
||||
containing_rule_state.scope_matches_shadow_host ==
|
||||
ScopeMatchesShadowHost::Yes
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let rules = if matches_featureless_host {
|
||||
let rules = if matches_featureless_host_only {
|
||||
self.featureless_host_rules
|
||||
.get_or_insert_with(|| Box::new(Default::default()))
|
||||
} else if rule.selector.is_slotted() {
|
||||
@@ -3685,10 +3689,7 @@ impl CascadeData {
|
||||
let id = ScopeConditionId(self.scope_conditions.len() as u16);
|
||||
let mut matches_shadow_host = false;
|
||||
let implicit_scope_root = if let Some(start) = rule.bounds.start.as_ref() {
|
||||
matches_shadow_host = start.slice().iter().any(|s| {
|
||||
!s.matches_featureless_host_selector_or_pseudo_element()
|
||||
.is_empty()
|
||||
});
|
||||
matches_shadow_host = scope_start_matches_shadow_host(start);
|
||||
// Would be unused, but use the default as fallback.
|
||||
StylistImplicitScopeRoot::default()
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user