servo: Merge #20597 - style: Implement the non-functional :host selector (from emilio:host); r=xidorn

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
Source-Repo: https://github.com/servo/servo
Source-Revision: cb754b262747e7cab794411df55588f0f0b30b5e

--HG--
extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear
extra : subtree_revision : 2ebbb2578ff9dbadfe905ae501cd52bd61a9fe9a
This commit is contained in:
Emilio Cobos Álvarez 2018-04-09 06:41:59 -04:00
parent 4d956f9599
commit 2bc468b78a
13 changed files with 241 additions and 136 deletions

View File

@ -314,6 +314,7 @@ fn complex_selector_specificity<Impl>(mut iter: slice::Iter<Component<Impl>>)
Component::FirstChild | Component::LastChild |
Component::OnlyChild | Component::Root |
Component::Empty | Component::Scope |
Component::Host |
Component::NthChild(..) |
Component::NthLastChild(..) |
Component::NthOfType(..) |

View File

@ -6,7 +6,7 @@ use attr::CaseSensitivity;
use bloom::BloomFilter;
use nth_index_cache::NthIndexCache;
use parser::SelectorImpl;
use tree::OpaqueElement;
use tree::{Element, OpaqueElement};
/// What kind of selector matching mode we should use.
///
@ -118,6 +118,9 @@ where
/// 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,
@ -178,6 +181,7 @@ where
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,
@ -186,6 +190,16 @@ where
}
}
/// 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 {
@ -266,4 +280,30 @@ where
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()
}
}

View File

@ -466,15 +466,9 @@ where
// 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.
let all_selectors_could_match = selector.clone().all(|component| {
match *component {
Component::NonTSPseudoClass(ref pc) => pc.is_host(),
_ => false,
}
});
if !all_selectors_could_match {
// are in a shadow tree of the host, and the next selector will only
// match if the selector is a featureless :host selector.
if !selector.clone().is_featureless_host_selector() {
return None;
}
@ -820,6 +814,9 @@ where
flags_setter(element, ElementSelectorFlags::HAS_EMPTY_SELECTOR);
element.is_empty()
}
Component::Host => {
context.shared.shadow_host().map_or(false, |host| host == element.opaque())
}
Component::Scope => {
match context.shared.scope_element {
Some(ref scope_element) => element.opaque() == *scope_element,

View File

@ -42,9 +42,6 @@ pub trait NonTSPseudoClass : Sized + ToCss {
/// Whether this pseudo-class is :active or :hover.
fn is_active_or_hover(&self) -> bool;
/// Whether this pseudo-class is :host.
fn is_host(&self) -> bool;
}
fn to_ascii_lowercase(s: &str) -> Cow<str> {
@ -145,6 +142,11 @@ pub trait Parser<'i> {
false
}
/// Whether to parse the `:host` pseudo-class.
fn parse_host(&self) -> bool {
false
}
/// This function can return an "Err" pseudo-element in order to support CSS2.1
/// pseudo-elements.
fn parse_non_ts_pseudo_class(
@ -513,6 +515,13 @@ impl<Impl: SelectorImpl> Selector<Impl> {
}
}
/// Whether this selector is a featureless :host selector, with no
/// combinators to the left.
#[inline]
pub fn is_featureless_host_selector(&self) -> bool {
self.iter().is_featureless_host_selector()
}
/// Returns an iterator over this selector in matching order (right-to-left),
/// skipping the rightmost |offset| Components.
#[inline]
@ -605,6 +614,14 @@ impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> {
self.next_combinator.take()
}
/// Whether this selector is a featureless host selector, with no
/// combinators to the left.
#[inline]
pub(crate) fn is_featureless_host_selector(&mut self) -> bool {
self.all(|component| matches!(*component, Component::Host)) &&
self.next_sequence().is_none()
}
/// Returns remaining count of the simple selectors and combinators in the Selector.
#[inline]
pub fn selector_length(&self) -> usize {
@ -776,6 +793,10 @@ pub enum Component<Impl: SelectorImpl> {
Root,
Empty,
Scope,
/// The `:host` pseudo-class:
///
/// https://drafts.csswg.org/css-scoping/#host-selector
Host,
NthChild(i32, i32),
NthLastChild(i32, i32),
NthOfType(i32, i32),
@ -1098,6 +1119,7 @@ impl<Impl: SelectorImpl> ToCss for Component<Impl> {
Root => dest.write_str(":root"),
Empty => dest.write_str(":empty"),
Scope => dest.write_str(":scope"),
Host => dest.write_str(":host"),
FirstOfType => dest.write_str(":first-of-type"),
LastOfType => dest.write_str(":last-of-type"),
OnlyOfType => dest.write_str(":only-of-type"),
@ -1947,9 +1969,10 @@ where
"root" => Ok(Component::Root),
"empty" => Ok(Component::Empty),
"scope" => Ok(Component::Scope),
"host" if P::parse_host(parser) => Ok(Component::Host),
"first-of-type" => Ok(Component::FirstOfType),
"last-of-type" => Ok(Component::LastOfType),
"only-of-type" => Ok(Component::OnlyOfType),
"last-of-type" => Ok(Component::LastOfType),
"only-of-type" => Ok(Component::OnlyOfType),
_ => Err(())
}).or_else(|()| {
P::parse_non_ts_pseudo_class(parser, location, name)
@ -1999,11 +2022,6 @@ pub mod tests {
fn is_active_or_hover(&self) -> bool {
matches!(*self, PseudoClass::Active | PseudoClass::Hover)
}
#[inline]
fn is_host(&self) -> bool {
false
}
}
impl ToCss for PseudoClass {

View File

@ -262,8 +262,8 @@ impl ElementData {
let mut non_document_styles = SmallVec::<[_; 3]>::new();
let matches_doc_author_rules =
element.each_applicable_non_document_style_rule_data(|data, quirks_mode| {
non_document_styles.push((data, quirks_mode))
element.each_applicable_non_document_style_rule_data(|data, quirks_mode, host| {
non_document_styles.push((data, quirks_mode, host.map(|h| h.opaque())))
});
let mut processor = StateAndAttrInvalidationProcessor::new(

View File

@ -772,24 +772,37 @@ pub trait TElement
/// Executes the callback for each applicable style rule data which isn't
/// the main document's data (which stores UA / author rules).
///
/// The element passed to the callback is the containing shadow host for the
/// data if it comes from Shadow DOM, None if it comes from XBL.
///
/// Returns whether normal document author rules should apply.
fn each_applicable_non_document_style_rule_data<'a, F>(&self, mut f: F) -> bool
where
Self: 'a,
F: FnMut(&'a CascadeData, QuirksMode),
F: FnMut(&'a CascadeData, QuirksMode, Option<Self>),
{
let mut doc_rules_apply = !self.each_xbl_cascade_data(&mut f);
let mut doc_rules_apply = !self.each_xbl_cascade_data(|data, quirks_mode| {
f(data, quirks_mode, None);
});
if let Some(shadow) = self.containing_shadow() {
doc_rules_apply = false;
f(shadow.style_data(), self.as_node().owner_doc().quirks_mode());
f(
shadow.style_data(),
self.as_node().owner_doc().quirks_mode(),
Some(shadow.host()),
);
}
let mut current = self.assigned_slot();
while let Some(slot) = current {
// Slots can only have assigned nodes when in a shadow tree.
let data = slot.containing_shadow().unwrap().style_data();
f(data, self.as_node().owner_doc().quirks_mode());
let shadow = slot.containing_shadow().unwrap();
f(
shadow.style_data(),
self.as_node().owner_doc().quirks_mode(),
Some(shadow.host()),
);
current = slot.assigned_slot();
}

View File

@ -7,7 +7,7 @@
use Atom;
use context::QuirksMode;
use dom::{TDocument, TElement, TNode};
use dom::{TDocument, TElement, TNode, TShadowRoot};
use invalidation::element::invalidator::{DescendantInvalidationLists, Invalidation};
use invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector};
use selectors::{Element, NthIndexCache, SelectorList};
@ -33,6 +33,7 @@ where
quirks_mode,
);
context.scope_element = Some(element.opaque());
context.current_host = element.containing_shadow_host().map(|e| e.opaque());
matching::matches_selector_list(selector_list, element, &mut context)
}
@ -54,6 +55,7 @@ where
quirks_mode,
);
context.scope_element = Some(element.opaque());
context.current_host = element.containing_shadow_host().map(|e| e.opaque());
let mut current = Some(element);
while let Some(element) = current.take() {
@ -547,6 +549,10 @@ where
let root_element = root.as_element();
matching_context.scope_element = root_element.map(|e| e.opaque());
matching_context.current_host = match root_element {
Some(root) => root.containing_shadow_host().map(|host| host.opaque()),
None => root.as_shadow_root().map(|root| root.host().opaque()),
};
let fast_result = query_selector_fast::<E, Q>(
root,

View File

@ -117,7 +117,8 @@ impl Visit for NonTSPseudoClass {
type Impl = SelectorImpl;
fn visit<V>(&self, visitor: &mut V) -> bool
where V: SelectorVisitor<Impl = Self::Impl>,
where
V: SelectorVisitor<Impl = Self::Impl>,
{
if let NonTSPseudoClass::MozAny(ref selectors) = *self {
for selector in selectors.iter() {
@ -161,8 +162,6 @@ impl NonTSPseudoClass {
match *self {
$(NonTSPseudoClass::$name => check_flag!($flags),)*
$(NonTSPseudoClass::$s_name(..) => check_flag!($s_flags),)*
// TODO(emilio): Maybe -moz-locale-dir shouldn't be
// content-exposed.
NonTSPseudoClass::MozLocaleDir(_) |
NonTSPseudoClass::Dir(_) |
NonTSPseudoClass::MozAny(_) => false,
@ -275,11 +274,6 @@ impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass {
fn is_active_or_hover(&self) -> bool {
matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover)
}
#[inline]
fn is_host(&self) -> bool {
false // TODO(emilio)
}
}
/// The dummy struct we use to implement our selector parsing.
@ -349,6 +343,7 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
type Impl = SelectorImpl;
type Error = StyleParseErrorKind<'i>;
#[inline]
fn parse_slotted(&self) -> bool {
// NOTE(emilio): Slot assignment and such works per-document, but
// getting a document around here is not trivial, and it's not worth
@ -356,6 +351,11 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
unsafe { structs::nsContentUtils_sIsShadowDOMEnabled }
}
#[inline]
fn parse_host(&self) -> bool {
self.parse_slotted()
}
fn pseudo_element_allows_single_colon(name: &str) -> bool {
// FIXME: -moz-tree check should probably be ascii-case-insensitive.
::selectors::parser::is_css2_pseudo_element(name) ||

View File

@ -18,6 +18,7 @@ use invalidation::element::restyle_hints::RestyleHint;
use selector_map::SelectorMap;
use selector_parser::Snapshot;
use selectors::NthIndexCache;
use selectors::OpaqueElement;
use selectors::attr::CaseSensitivity;
use selectors::matching::{MatchingContext, MatchingMode, VisitedHandlingMode};
use selectors::matching::matches_selector;
@ -38,9 +39,8 @@ where
{
element: E,
wrapper: ElementWrapper<'b, E>,
nth_index_cache: Option<&'a mut NthIndexCache>,
snapshot: &'a Snapshot,
quirks_mode: QuirksMode,
matching_context: &'a mut MatchingContext<'b, E::Impl>,
lookup_element: E,
removed_id: Option<&'a WeakAtom>,
added_id: Option<&'a WeakAtom>,
@ -56,7 +56,7 @@ where
/// changes.
pub struct StateAndAttrInvalidationProcessor<'a, 'b: 'a, E: TElement> {
shared_context: &'a SharedStyleContext<'b>,
shadow_rule_datas: &'a [(&'b CascadeData, QuirksMode)],
shadow_rule_datas: &'a [(&'b CascadeData, QuirksMode, Option<OpaqueElement>)],
matches_document_author_rules: bool,
element: E,
data: &'a mut ElementData,
@ -67,7 +67,7 @@ impl<'a, 'b: 'a, E: TElement> StateAndAttrInvalidationProcessor<'a, 'b, E> {
/// Creates a new StateAndAttrInvalidationProcessor.
pub fn new(
shared_context: &'a SharedStyleContext<'b>,
shadow_rule_datas: &'a [(&'b CascadeData, QuirksMode)],
shadow_rule_datas: &'a [(&'b CascadeData, QuirksMode, Option<OpaqueElement>)],
matches_document_author_rules: bool,
element: E,
data: &'a mut ElementData,
@ -237,8 +237,7 @@ where
state_changes,
element,
snapshot: &snapshot,
quirks_mode: self.shared_context.quirks_mode(),
nth_index_cache: self.matching_context.nth_index_cache.as_mut().map(|c| &mut **c),
matching_context: &mut self.matching_context,
removed_id: id_removed,
added_id: id_added,
classes_removed: &classes_removed,
@ -260,11 +259,12 @@ where
}
}
for &(ref data, quirks_mode) in self.shadow_rule_datas {
for &(ref data, quirks_mode, ref host) in self.shadow_rule_datas {
// FIXME(emilio): Replace with assert / remove when we figure
// out what to do with the quirks mode mismatches
// (that is, when bug 1406875 is properly fixed).
collector.quirks_mode = quirks_mode;
collector.matching_context.set_quirks_mode(quirks_mode);
collector.matching_context.current_host = host.clone();
collector.collect_dependencies_in_invalidation_map(data.invalidation_map());
}
@ -333,7 +333,7 @@ where
&mut self,
map: &'selectors InvalidationMap,
) {
let quirks_mode = self.quirks_mode;
let quirks_mode = self.matching_context.quirks_mode();
let removed_id = self.removed_id;
if let Some(ref id) = removed_id {
if let Some(deps) = map.id_to_selector.get(id, quirks_mode) {
@ -386,7 +386,7 @@ where
) {
map.lookup_with_additional(
self.lookup_element,
self.quirks_mode,
self.matching_context.quirks_mode(),
self.removed_id,
self.classes_removed,
|dependency| {
@ -403,7 +403,7 @@ where
) {
map.lookup_with_additional(
self.lookup_element,
self.quirks_mode,
self.matching_context.quirks_mode(),
self.removed_id,
self.classes_removed,
|dependency| {
@ -429,51 +429,29 @@ where
visited_handling_mode: VisitedHandlingMode,
dependency: &Dependency,
) -> bool {
let matches_now = {
let mut context = MatchingContext::new_for_visited(
MatchingMode::Normal,
None,
self.nth_index_cache.as_mut().map(|c| &mut **c),
visited_handling_mode,
self.quirks_mode,
);
let element = &self.element;
let wrapper = &self.wrapper;
self.matching_context.with_visited_handling_mode(visited_handling_mode, |mut context| {
let matches_now = matches_selector(
&dependency.selector,
dependency.selector_offset,
None,
&self.element,
element,
&mut context,
&mut |_, _| {},
);
matches_now
};
let matched_then = {
let mut context = MatchingContext::new_for_visited(
MatchingMode::Normal,
None,
self.nth_index_cache.as_mut().map(|c| &mut **c),
visited_handling_mode,
self.quirks_mode,
);
let matched_then = matches_selector(
&dependency.selector,
dependency.selector_offset,
None,
&self.wrapper,
wrapper,
&mut context,
&mut |_, _| {},
);
matched_then
};
// Check for mismatches in both the match result and also the status
// of whether a relevant link was found.
matched_then != matches_now
matched_then != matches_now
})
}
fn scan_dependency(

View File

@ -311,11 +311,6 @@ pub enum NonTSPseudoClass {
impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass {
type Impl = SelectorImpl;
#[inline]
fn is_host(&self) -> bool {
false
}
#[inline]
fn is_active_or_hover(&self) -> bool {
matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover)

View File

@ -553,6 +553,16 @@ impl<E: TElement> StyleSharingCache<E> {
return;
}
// We can't share style across shadow hosts right now, because they may
// match different :host rules.
//
// TODO(emilio): We could share across the ones that don't have :host
// rules or have the same.
if element.shadow_root().is_some() {
debug!("Failing to insert into the cache: Shadow Host");
return;
}
// If the element has running animations, we can't share style.
//
// This is distinct from the specifies_{animations,transitions} check below,
@ -716,6 +726,11 @@ impl<E: TElement> StyleSharingCache<E> {
return None;
}
if target.element.shadow_root().is_some() {
trace!("Miss: Shadow host");
return None;
}
if target.matches_user_and_author_rules() !=
candidate.element.matches_user_and_author_rules() {
trace!("Miss: User and Author Rules");

View File

@ -591,25 +591,24 @@ impl Stylist {
pub fn any_applicable_rule_data<E, F>(&self, element: E, mut f: F) -> bool
where
E: TElement,
F: FnMut(&CascadeData, QuirksMode) -> bool,
F: FnMut(&CascadeData) -> bool,
{
if f(&self.cascade_data.user_agent.cascade_data, self.quirks_mode()) {
if f(&self.cascade_data.user_agent.cascade_data) {
return true;
}
let mut maybe = false;
let doc_author_rules_apply =
element.each_applicable_non_document_style_rule_data(|data, quirks_mode| {
maybe = maybe || f(&*data, quirks_mode);
element.each_applicable_non_document_style_rule_data(|data, _, _| {
maybe = maybe || f(&*data);
});
if maybe || !doc_author_rules_apply {
return maybe;
}
f(&self.cascade_data.author, self.quirks_mode()) ||
f(&self.cascade_data.user, self.quirks_mode())
f(&self.cascade_data.author) || f(&self.cascade_data.user)
}
/// Computes the style for a given "precomputed" pseudo-element, taking the
@ -1261,6 +1260,21 @@ impl Stylist {
// for !important it should be the other way around. So probably we need
// to add some sort of AuthorScoped cascade level or something.
if matches_author_rules && !only_default_rules {
if let Some(shadow) = rule_hash_target.shadow_root() {
if let Some(map) = shadow.style_data().host_rules(pseudo_element) {
context.with_shadow_host(Some(rule_hash_target), |context| {
map.get_all_matching_rules(
element,
rule_hash_target,
applicable_declarations,
context,
flags_setter,
CascadeLevel::AuthorNormal,
);
});
}
}
// Match slotted rules in reverse order, so that the outer slotted
// rules come before the inner rules (and thus have less priority).
let mut slots = SmallVec::<[_; 3]>::new();
@ -1271,32 +1285,35 @@ impl Stylist {
}
for slot in slots.iter().rev() {
let styles = slot.containing_shadow().unwrap().style_data();
let shadow = slot.containing_shadow().unwrap();
let styles = shadow.style_data();
if let Some(map) = styles.slotted_rules(pseudo_element) {
map.get_all_matching_rules(
element,
rule_hash_target,
applicable_declarations,
context,
flags_setter,
CascadeLevel::AuthorNormal,
);
context.with_shadow_host(Some(shadow.host()), |context| {
map.get_all_matching_rules(
element,
rule_hash_target,
applicable_declarations,
context,
flags_setter,
CascadeLevel::AuthorNormal,
);
});
}
}
// TODO(emilio): We need to look up :host rules if the element is a
// shadow host, when we implement that.
if let Some(containing_shadow) = rule_hash_target.containing_shadow() {
let cascade_data = containing_shadow.style_data();
if let Some(map) = cascade_data.normal_rules(pseudo_element) {
map.get_all_matching_rules(
element,
rule_hash_target,
applicable_declarations,
context,
flags_setter,
CascadeLevel::AuthorNormal,
);
context.with_shadow_host(Some(containing_shadow.host()), |context| {
map.get_all_matching_rules(
element,
rule_hash_target,
applicable_declarations,
context,
flags_setter,
CascadeLevel::AuthorNormal,
);
});
}
match_document_author_rules = false;
@ -1421,7 +1438,7 @@ impl Stylist {
}
let hash = id.get_hash();
self.any_applicable_rule_data(element, |data, _| {
self.any_applicable_rule_data(element, |data| {
data.mapped_ids.might_contain_hash(hash)
})
}
@ -1483,22 +1500,24 @@ impl Stylist {
);
}
element.each_applicable_non_document_style_rule_data(|data, quirks_mode| {
data.selectors_for_cache_revalidation.lookup(
element,
quirks_mode,
|selector_and_hashes| {
results.push(matches_selector(
&selector_and_hashes.selector,
selector_and_hashes.selector_offset,
Some(&selector_and_hashes.hashes),
&element,
&mut matching_context,
flags_setter
));
true
}
);
element.each_applicable_non_document_style_rule_data(|data, quirks_mode, host| {
matching_context.with_shadow_host(host, |matching_context| {
data.selectors_for_cache_revalidation.lookup(
element,
quirks_mode,
|selector_and_hashes| {
results.push(matches_selector(
&selector_and_hashes.selector,
selector_and_hashes.selector_offset,
Some(&selector_and_hashes.hashes),
&element,
matching_context,
flags_setter
));
true
}
);
})
});
results
@ -1929,6 +1948,15 @@ pub struct CascadeData {
/// cascade level.
normal_rules: ElementAndPseudoRules,
/// The `:host` pseudo rules that are the rightmost selector.
///
/// Note that as of right now these can't affect invalidation in any way,
/// until we support the :host(<selector>) notation.
///
/// Also, note that other engines don't accept stuff like :host::before /
/// :host::after, so we don't need to store pseudo rules at all.
host_rules: Option<Box<SelectorMap<Rule>>>,
/// The data coming from ::slotted() pseudo-element rules.
///
/// We need to store them separately because an element needs to match
@ -2005,6 +2033,7 @@ impl CascadeData {
pub fn new() -> Self {
Self {
normal_rules: ElementAndPseudoRules::default(),
host_rules: None,
slotted_rules: None,
invalidation_map: InvalidationMap::new(),
attribute_dependencies: NonCountingBloomFilter::new(),
@ -2091,6 +2120,15 @@ impl CascadeData {
self.normal_rules.rules(pseudo)
}
#[inline]
fn host_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
if pseudo.is_some() {
return None;
}
self.host_rules.as_ref().map(|rules| &**rules)
}
#[inline]
fn slotted_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
self.slotted_rules.as_ref().and_then(|d| d.rules(pseudo))
@ -2224,19 +2262,23 @@ impl CascadeData {
}
}
let rules = if selector.is_slotted() {
self.slotted_rules.get_or_insert_with(|| {
Box::new(Default::default())
})
if selector.is_featureless_host_selector() {
let host_rules =
self.host_rules.get_or_insert_with(|| {
Box::new(Default::default())
});
host_rules.insert(rule, quirks_mode)?;
} else {
&mut self.normal_rules
};
let rules = if selector.is_slotted() {
self.slotted_rules.get_or_insert_with(|| {
Box::new(Default::default())
})
} else {
&mut self.normal_rules
};
rules.insert(
rule,
pseudo_element,
quirks_mode,
)?;
rules.insert(rule, pseudo_element, quirks_mode)?;
}
}
self.rules_source_order += 1;
}

View File

@ -4933,7 +4933,7 @@ pub extern "C" fn Servo_StyleSet_MightHaveAttributeDependency(
unsafe {
Atom::with(local_name, |atom| {
data.stylist.any_applicable_rule_data(element, |data, _| {
data.stylist.any_applicable_rule_data(element, |data| {
data.might_have_attribute_dependency(atom)
})
})
@ -4951,7 +4951,7 @@ pub extern "C" fn Servo_StyleSet_HasStateDependency(
let state = ElementState::from_bits_truncate(state);
let data = PerDocumentStyleData::from_ffi(raw_data).borrow();
data.stylist.any_applicable_rule_data(element, |data, _| {
data.stylist.any_applicable_rule_data(element, |data| {
data.has_state_dependency(state)
})
}