mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 13:21:05 +00:00
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:
parent
4d956f9599
commit
2bc468b78a
@ -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(..) |
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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(
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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) ||
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user