mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
style: Make Invalidation work in terms of a dependency, not a selector.
That way we can look at the parent dependency as described in the previous patch. An alternative would be to add a: parent_dependency: Option<&'a Dependency> on construction to `Invalidation`, but this way seems slightly better to avoid growing the struct. It's not even one more indirection because the selector is contained directly in the Dependency struct. Differential Revision: https://phabricator.services.mozilla.com/D71422
This commit is contained in:
parent
c1bc588c93
commit
4b5de772c6
5 changed files with 315 additions and 202 deletions
|
@ -9,6 +9,7 @@ use crate::context::QuirksMode;
|
||||||
use crate::dom::{TDocument, TElement, TNode, TShadowRoot};
|
use crate::dom::{TDocument, TElement, TNode, TShadowRoot};
|
||||||
use crate::invalidation::element::invalidator::{DescendantInvalidationLists, Invalidation};
|
use crate::invalidation::element::invalidator::{DescendantInvalidationLists, Invalidation};
|
||||||
use crate::invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector};
|
use crate::invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector};
|
||||||
|
use crate::invalidation::element::invalidation_map::Dependency;
|
||||||
use crate::Atom;
|
use crate::Atom;
|
||||||
use selectors::attr::CaseSensitivity;
|
use selectors::attr::CaseSensitivity;
|
||||||
use selectors::matching::{self, MatchingContext, MatchingMode};
|
use selectors::matching::{self, MatchingContext, MatchingMode};
|
||||||
|
@ -130,7 +131,7 @@ where
|
||||||
{
|
{
|
||||||
results: &'a mut Q::Output,
|
results: &'a mut Q::Output,
|
||||||
matching_context: MatchingContext<'a, E::Impl>,
|
matching_context: MatchingContext<'a, E::Impl>,
|
||||||
selector_list: &'a SelectorList<E::Impl>,
|
dependencies: &'a [Dependency],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, E, Q> InvalidationProcessor<'a, E> for QuerySelectorProcessor<'a, E, Q>
|
impl<'a, E, Q> InvalidationProcessor<'a, E> for QuerySelectorProcessor<'a, E, Q>
|
||||||
|
@ -143,6 +144,11 @@ where
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_outer_dependency(&mut self, _: &Dependency, _: E) -> bool {
|
||||||
|
debug_assert!(false, "How? We should only have parent-less dependencies here!");
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn collect_invalidations(
|
fn collect_invalidations(
|
||||||
&mut self,
|
&mut self,
|
||||||
element: E,
|
element: E,
|
||||||
|
@ -171,11 +177,10 @@ where
|
||||||
self_invalidations
|
self_invalidations
|
||||||
};
|
};
|
||||||
|
|
||||||
for selector in self.selector_list.0.iter() {
|
for dependency in self.dependencies.iter() {
|
||||||
target_vector.push(Invalidation::new(
|
target_vector.push(Invalidation::new(
|
||||||
selector,
|
dependency,
|
||||||
self.matching_context.current_host.clone(),
|
self.matching_context.current_host.clone(),
|
||||||
0,
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -642,10 +647,13 @@ pub fn query_selector<E, Q>(
|
||||||
if root_element.is_some() || !invalidation_may_be_useful {
|
if root_element.is_some() || !invalidation_may_be_useful {
|
||||||
query_selector_slow::<E, Q>(root, selector_list, results, &mut matching_context);
|
query_selector_slow::<E, Q>(root, selector_list, results, &mut matching_context);
|
||||||
} else {
|
} else {
|
||||||
|
let dependencies = selector_list.0.iter().map(|selector| {
|
||||||
|
Dependency::for_full_selector_invalidation(selector.clone())
|
||||||
|
}).collect::<SmallVec<[_; 5]>>();
|
||||||
let mut processor = QuerySelectorProcessor::<E, Q> {
|
let mut processor = QuerySelectorProcessor::<E, Q> {
|
||||||
results,
|
results,
|
||||||
matching_context,
|
matching_context,
|
||||||
selector_list,
|
dependencies: &dependencies,
|
||||||
};
|
};
|
||||||
|
|
||||||
for node in root.dom_children() {
|
for node in root.dom_children() {
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
use crate::dom::TElement;
|
use crate::dom::TElement;
|
||||||
use crate::element_state::DocumentState;
|
use crate::element_state::DocumentState;
|
||||||
|
use crate::invalidation::element::invalidation_map::Dependency;
|
||||||
use crate::invalidation::element::invalidator::{DescendantInvalidationLists, InvalidationVector};
|
use crate::invalidation::element::invalidator::{DescendantInvalidationLists, InvalidationVector};
|
||||||
use crate::invalidation::element::invalidator::{Invalidation, InvalidationProcessor};
|
use crate::invalidation::element::invalidator::{Invalidation, InvalidationProcessor};
|
||||||
use crate::invalidation::element::state_and_attributes;
|
use crate::invalidation::element::state_and_attributes;
|
||||||
|
@ -65,6 +66,11 @@ where
|
||||||
E: TElement,
|
E: TElement,
|
||||||
I: Iterator<Item = &'a CascadeData>,
|
I: Iterator<Item = &'a CascadeData>,
|
||||||
{
|
{
|
||||||
|
fn check_outer_dependency(&mut self, _: &Dependency, _: E) -> bool {
|
||||||
|
debug_assert!(false, "how, we should only have parent-less dependencies here!");
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn collect_invalidations(
|
fn collect_invalidations(
|
||||||
&mut self,
|
&mut self,
|
||||||
_element: E,
|
_element: E,
|
||||||
|
@ -81,10 +87,14 @@ where
|
||||||
|
|
||||||
// We pass `None` as a scope, as document state selectors aren't
|
// We pass `None` as a scope, as document state selectors aren't
|
||||||
// affected by the current scope.
|
// affected by the current scope.
|
||||||
|
//
|
||||||
|
// FIXME(emilio): We should really pass the relevant host for
|
||||||
|
// self.rules, so that we invalidate correctly if the selector
|
||||||
|
// happens to have something like :host(:-moz-window-inactive)
|
||||||
|
// for example.
|
||||||
self_invalidations.push(Invalidation::new(
|
self_invalidations.push(Invalidation::new(
|
||||||
&dependency.selector,
|
&dependency.dependency,
|
||||||
/* scope = */ None,
|
/* scope = */ None,
|
||||||
0,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ pub struct Dependency {
|
||||||
///
|
///
|
||||||
/// We'd generate:
|
/// We'd generate:
|
||||||
///
|
///
|
||||||
/// * One dependency for .quz (offset: 0, parent: None)
|
/// * One dependency for .qux (offset: 0, parent: None)
|
||||||
/// * One dependency for .baz pointing to B with parent being a
|
/// * One dependency for .baz pointing to B with parent being a
|
||||||
/// dependency pointing to C.
|
/// dependency pointing to C.
|
||||||
/// * One dependency from .bar pointing to C (parent: None)
|
/// * One dependency from .bar pointing to C (parent: None)
|
||||||
|
@ -88,6 +88,22 @@ pub enum DependencyInvalidationKind {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dependency {
|
impl Dependency {
|
||||||
|
/// Creates a dummy dependency to invalidate the whole selector.
|
||||||
|
///
|
||||||
|
/// This is necessary because document state invalidation wants to
|
||||||
|
/// invalidate all elements in the document.
|
||||||
|
///
|
||||||
|
/// The offset is such as that Invalidation::new(self) returns a zero
|
||||||
|
/// offset. That is, it points to a virtual "combinator" outside of the
|
||||||
|
/// selector, so calling combinator() on such a dependency will panic.
|
||||||
|
pub fn for_full_selector_invalidation(selector: Selector<SelectorImpl>) -> Self {
|
||||||
|
Self {
|
||||||
|
selector_offset: selector.len() + 1,
|
||||||
|
selector,
|
||||||
|
parent: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the combinator to the right of the partial selector this
|
/// Returns the combinator to the right of the partial selector this
|
||||||
/// dependency represents.
|
/// dependency represents.
|
||||||
///
|
///
|
||||||
|
@ -147,14 +163,14 @@ impl SelectorMapEntry for StateDependency {
|
||||||
/// The same, but for document state selectors.
|
/// The same, but for document state selectors.
|
||||||
#[derive(Clone, Debug, MallocSizeOf)]
|
#[derive(Clone, Debug, MallocSizeOf)]
|
||||||
pub struct DocumentStateDependency {
|
pub struct DocumentStateDependency {
|
||||||
/// The selector that is affected. We don't need to track an offset, since
|
/// We track `Dependency` even though we don't need to track an offset,
|
||||||
/// when it changes it changes for the whole document anyway.
|
/// since when it changes it changes for the whole document anyway.
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "gecko",
|
feature = "gecko",
|
||||||
ignore_malloc_size_of = "CssRules have primary refs, we measure there"
|
ignore_malloc_size_of = "CssRules have primary refs, we measure there"
|
||||||
)]
|
)]
|
||||||
#[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")]
|
#[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")]
|
||||||
pub selector: Selector<SelectorImpl>,
|
pub dependency: Dependency,
|
||||||
/// The state this dependency is affected by.
|
/// The state this dependency is affected by.
|
||||||
pub state: DocumentState,
|
pub state: DocumentState,
|
||||||
}
|
}
|
||||||
|
@ -269,7 +285,7 @@ impl InvalidationMap {
|
||||||
if !document_state.is_empty() {
|
if !document_state.is_empty() {
|
||||||
let dep = DocumentStateDependency {
|
let dep = DocumentStateDependency {
|
||||||
state: document_state,
|
state: document_state,
|
||||||
selector: selector.clone(),
|
dependency: Dependency::for_full_selector_invalidation(selector.clone()),
|
||||||
};
|
};
|
||||||
self.document_state_selectors.try_push(dep)?;
|
self.document_state_selectors.try_push(dep)?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,10 @@
|
||||||
|
|
||||||
use crate::context::StackLimitChecker;
|
use crate::context::StackLimitChecker;
|
||||||
use crate::dom::{TElement, TNode, TShadowRoot};
|
use crate::dom::{TElement, TNode, TShadowRoot};
|
||||||
use crate::selector_parser::SelectorImpl;
|
use crate::invalidation::element::invalidation_map::{Dependency, DependencyInvalidationKind};
|
||||||
use selectors::matching::matches_compound_selector_from;
|
use selectors::matching::matches_compound_selector_from;
|
||||||
use selectors::matching::{CompoundSelectorMatchingResult, MatchingContext};
|
use selectors::matching::{CompoundSelectorMatchingResult, MatchingContext};
|
||||||
use selectors::parser::{Combinator, Component, Selector};
|
use selectors::parser::{Combinator, Component};
|
||||||
use selectors::OpaqueElement;
|
use selectors::OpaqueElement;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -34,6 +34,27 @@ where
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// When a dependency from a :where or :is selector matches, it may still be
|
||||||
|
/// the case that we don't need to invalidate the full style. Consider the
|
||||||
|
/// case of:
|
||||||
|
///
|
||||||
|
/// div .foo:where(.bar *, .baz) .qux
|
||||||
|
///
|
||||||
|
/// We can get to the `*` part after a .bar class change, but you only need
|
||||||
|
/// to restyle the element if it also matches .foo.
|
||||||
|
///
|
||||||
|
/// Similarly, you only need to restyle .baz if the whole result of matching
|
||||||
|
/// the selector changes.
|
||||||
|
///
|
||||||
|
/// This function is called to check the result of matching the "outer"
|
||||||
|
/// dependency that we generate for the parent of the `:where` selector,
|
||||||
|
/// that is, in the case above it should match
|
||||||
|
/// `div .foo:where(.bar *, .baz)`.
|
||||||
|
///
|
||||||
|
/// Returning true unconditionally here is over-optimistic and may
|
||||||
|
/// over-invalidate.
|
||||||
|
fn check_outer_dependency(&mut self, dependency: &Dependency, element: E) -> bool;
|
||||||
|
|
||||||
/// The matching context that should be used to process invalidations.
|
/// The matching context that should be used to process invalidations.
|
||||||
fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl>;
|
fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl>;
|
||||||
|
|
||||||
|
@ -127,7 +148,11 @@ enum InvalidationKind {
|
||||||
/// relative to a current element we are processing, must be restyled.
|
/// relative to a current element we are processing, must be restyled.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Invalidation<'a> {
|
pub struct Invalidation<'a> {
|
||||||
selector: &'a Selector<SelectorImpl>,
|
/// The dependency that generated this invalidation.
|
||||||
|
///
|
||||||
|
/// Note that the offset inside the dependency is not really useful after
|
||||||
|
/// construction.
|
||||||
|
dependency: &'a Dependency,
|
||||||
/// The right shadow host from where the rule came from, if any.
|
/// The right shadow host from where the rule came from, if any.
|
||||||
///
|
///
|
||||||
/// This is needed to ensure that we match the selector with the right
|
/// This is needed to ensure that we match the selector with the right
|
||||||
|
@ -138,6 +163,8 @@ pub struct Invalidation<'a> {
|
||||||
///
|
///
|
||||||
/// This order is a "parse order" offset, that is, zero is the leftmost part
|
/// This order is a "parse order" offset, that is, zero is the leftmost part
|
||||||
/// of the selector written as parsed / serialized.
|
/// of the selector written as parsed / serialized.
|
||||||
|
///
|
||||||
|
/// It is initialized from the offset from `dependency`.
|
||||||
offset: usize,
|
offset: usize,
|
||||||
/// Whether the invalidation was already matched by any previous sibling or
|
/// Whether the invalidation was already matched by any previous sibling or
|
||||||
/// ancestor.
|
/// ancestor.
|
||||||
|
@ -149,16 +176,21 @@ pub struct Invalidation<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Invalidation<'a> {
|
impl<'a> Invalidation<'a> {
|
||||||
/// Create a new invalidation for a given selector and offset.
|
/// Create a new invalidation for matching a dependency.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
selector: &'a Selector<SelectorImpl>,
|
dependency: &'a Dependency,
|
||||||
scope: Option<OpaqueElement>,
|
scope: Option<OpaqueElement>,
|
||||||
offset: usize,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
debug_assert!(
|
||||||
|
dependency.selector_offset == dependency.selector.len() + 1 ||
|
||||||
|
dependency.invalidation_kind() != DependencyInvalidationKind::Element,
|
||||||
|
"No point to this, if the dependency matched the element we should just invalidate it"
|
||||||
|
);
|
||||||
Self {
|
Self {
|
||||||
selector,
|
dependency,
|
||||||
scope,
|
scope,
|
||||||
offset,
|
// + 1 to go past the combinator.
|
||||||
|
offset: dependency.selector.len() + 1 - dependency.selector_offset,
|
||||||
matched_by_any_previous: false,
|
matched_by_any_previous: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,7 +206,7 @@ impl<'a> Invalidation<'a> {
|
||||||
// for the weird pseudos in <input type="number">.
|
// for the weird pseudos in <input type="number">.
|
||||||
//
|
//
|
||||||
// We should be able to do better here!
|
// We should be able to do better here!
|
||||||
match self.selector.combinator_at_parse_order(self.offset - 1) {
|
match self.dependency.selector.combinator_at_parse_order(self.offset - 1) {
|
||||||
Combinator::Descendant | Combinator::LaterSibling | Combinator::PseudoElement => true,
|
Combinator::Descendant | Combinator::LaterSibling | Combinator::PseudoElement => true,
|
||||||
Combinator::Part |
|
Combinator::Part |
|
||||||
Combinator::SlotAssignment |
|
Combinator::SlotAssignment |
|
||||||
|
@ -188,7 +220,7 @@ impl<'a> Invalidation<'a> {
|
||||||
return InvalidationKind::Descendant(DescendantInvalidationKind::Dom);
|
return InvalidationKind::Descendant(DescendantInvalidationKind::Dom);
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.selector.combinator_at_parse_order(self.offset - 1) {
|
match self.dependency.selector.combinator_at_parse_order(self.offset - 1) {
|
||||||
Combinator::Child | Combinator::Descendant | Combinator::PseudoElement => {
|
Combinator::Child | Combinator::Descendant | Combinator::PseudoElement => {
|
||||||
InvalidationKind::Descendant(DescendantInvalidationKind::Dom)
|
InvalidationKind::Descendant(DescendantInvalidationKind::Dom)
|
||||||
},
|
},
|
||||||
|
@ -206,7 +238,7 @@ impl<'a> fmt::Debug for Invalidation<'a> {
|
||||||
use cssparser::ToCss;
|
use cssparser::ToCss;
|
||||||
|
|
||||||
f.write_str("Invalidation(")?;
|
f.write_str("Invalidation(")?;
|
||||||
for component in self.selector.iter_raw_parse_order_from(self.offset) {
|
for component in self.dependency.selector.iter_raw_parse_order_from(self.offset) {
|
||||||
if matches!(*component, Component::Combinator(..)) {
|
if matches!(*component, Component::Combinator(..)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -763,201 +795,241 @@ where
|
||||||
context.current_host = invalidation.scope;
|
context.current_host = invalidation.scope;
|
||||||
|
|
||||||
matches_compound_selector_from(
|
matches_compound_selector_from(
|
||||||
&invalidation.selector,
|
&invalidation.dependency.selector,
|
||||||
invalidation.offset,
|
invalidation.offset,
|
||||||
context,
|
context,
|
||||||
&self.element,
|
&self.element,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut invalidated_self = false;
|
let next_invalidation = match matching_result {
|
||||||
let mut matched = false;
|
CompoundSelectorMatchingResult::NotMatched => {
|
||||||
match matching_result {
|
return SingleInvalidationResult {
|
||||||
|
invalidated_self: false,
|
||||||
|
matched: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
CompoundSelectorMatchingResult::FullyMatched => {
|
CompoundSelectorMatchingResult::FullyMatched => {
|
||||||
debug!(" > Invalidation matched completely");
|
debug!(" > Invalidation matched completely");
|
||||||
matched = true;
|
// We matched completely. If we're an inner selector now we need
|
||||||
invalidated_self = true;
|
// to go outside our selector and carry on invalidating.
|
||||||
|
let mut cur_dependency = invalidation.dependency;
|
||||||
|
loop {
|
||||||
|
cur_dependency = match cur_dependency.parent {
|
||||||
|
None => return SingleInvalidationResult {
|
||||||
|
invalidated_self: true,
|
||||||
|
matched: true,
|
||||||
|
},
|
||||||
|
Some(ref p) => &**p,
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!(" > Checking outer dependency {:?}", cur_dependency);
|
||||||
|
|
||||||
|
// The inner selector changed, now check if the full
|
||||||
|
// previous part of the selector did, before keeping
|
||||||
|
// checking for descendants.
|
||||||
|
if !self.processor.check_outer_dependency(cur_dependency, self.element) {
|
||||||
|
return SingleInvalidationResult {
|
||||||
|
invalidated_self: false,
|
||||||
|
matched: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cur_dependency.invalidation_kind() == DependencyInvalidationKind::Element {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!(" > Generating invalidation");
|
||||||
|
break Invalidation::new(cur_dependency, invalidation.scope)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
CompoundSelectorMatchingResult::Matched {
|
CompoundSelectorMatchingResult::Matched {
|
||||||
next_combinator_offset,
|
next_combinator_offset,
|
||||||
} => {
|
} => {
|
||||||
let next_combinator = invalidation
|
Invalidation {
|
||||||
.selector
|
dependency: invalidation.dependency,
|
||||||
.combinator_at_parse_order(next_combinator_offset);
|
|
||||||
matched = true;
|
|
||||||
|
|
||||||
if matches!(next_combinator, Combinator::PseudoElement) {
|
|
||||||
// This will usually be the very next component, except for
|
|
||||||
// the fact that we store compound selectors the other way
|
|
||||||
// around, so there could also be state pseudo-classes.
|
|
||||||
let pseudo_selector = invalidation
|
|
||||||
.selector
|
|
||||||
.iter_raw_parse_order_from(next_combinator_offset + 1)
|
|
||||||
.skip_while(|c| matches!(**c, Component::NonTSPseudoClass(..)))
|
|
||||||
.next()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let pseudo = match *pseudo_selector {
|
|
||||||
Component::PseudoElement(ref pseudo) => pseudo,
|
|
||||||
_ => unreachable!(
|
|
||||||
"Someone seriously messed up selector parsing: \
|
|
||||||
{:?} at offset {:?}: {:?}",
|
|
||||||
invalidation.selector, next_combinator_offset, pseudo_selector,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
// FIXME(emilio): This is not ideal, and could not be
|
|
||||||
// accurate if we ever have stateful element-backed eager
|
|
||||||
// pseudos.
|
|
||||||
//
|
|
||||||
// Ideally, we'd just remove element-backed eager pseudos
|
|
||||||
// altogether, given they work fine without it. Only gotcha
|
|
||||||
// is that we wouldn't style them in parallel, which may or
|
|
||||||
// may not be an issue.
|
|
||||||
//
|
|
||||||
// Also, this could be more fine grained now (perhaps a
|
|
||||||
// RESTYLE_PSEUDOS hint?).
|
|
||||||
//
|
|
||||||
// Note that we'll also restyle the pseudo-element because
|
|
||||||
// it would match this invalidation.
|
|
||||||
if self.processor.invalidates_on_eager_pseudo_element() {
|
|
||||||
if pseudo.is_eager() {
|
|
||||||
invalidated_self = true;
|
|
||||||
}
|
|
||||||
// If we start or stop matching some marker rules, and
|
|
||||||
// don't have a marker, then we need to restyle the
|
|
||||||
// element to potentially create one.
|
|
||||||
//
|
|
||||||
// Same caveats as for other eager pseudos apply, this
|
|
||||||
// could be more fine-grained.
|
|
||||||
if pseudo.is_marker() && self.element.marker_pseudo_element().is_none() {
|
|
||||||
invalidated_self = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: ::selection doesn't generate elements, so the
|
|
||||||
// regular invalidation doesn't work for it. We store
|
|
||||||
// the cached selection style holding off the originating
|
|
||||||
// element, so we need to restyle it in order to invalidate
|
|
||||||
// it. This is still not quite correct, since nothing
|
|
||||||
// triggers a repaint necessarily, but matches old Gecko
|
|
||||||
// behavior, and the ::selection implementation needs to
|
|
||||||
// change significantly anyway to implement
|
|
||||||
// https://github.com/w3c/csswg-drafts/issues/2474.
|
|
||||||
if pseudo.is_selection() {
|
|
||||||
invalidated_self = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let next_invalidation = Invalidation {
|
|
||||||
selector: invalidation.selector,
|
|
||||||
scope: invalidation.scope,
|
scope: invalidation.scope,
|
||||||
offset: next_combinator_offset + 1,
|
offset: next_combinator_offset + 1,
|
||||||
matched_by_any_previous: false,
|
matched_by_any_previous: false,
|
||||||
};
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
" > Invalidation matched, next: {:?}, ({:?})",
|
|
||||||
next_invalidation, next_combinator
|
|
||||||
);
|
|
||||||
|
|
||||||
let next_invalidation_kind = next_invalidation.kind();
|
|
||||||
|
|
||||||
// We can skip pushing under some circumstances, and we should
|
|
||||||
// because otherwise the invalidation list could grow
|
|
||||||
// exponentially.
|
|
||||||
//
|
|
||||||
// * First of all, both invalidations need to be of the same
|
|
||||||
// kind. This is because of how we propagate them going to
|
|
||||||
// the right of the tree for sibling invalidations and going
|
|
||||||
// down the tree for children invalidations. A sibling
|
|
||||||
// invalidation that ends up generating a children
|
|
||||||
// invalidation ends up (correctly) in five different lists,
|
|
||||||
// not in the same list five different times.
|
|
||||||
//
|
|
||||||
// * Then, the invalidation needs to be matched by a previous
|
|
||||||
// ancestor/sibling, in order to know that this invalidation
|
|
||||||
// has been generated already.
|
|
||||||
//
|
|
||||||
// * Finally, the new invalidation needs to be
|
|
||||||
// `effective_for_next()`, in order for us to know that it is
|
|
||||||
// still in the list, since we remove the dependencies that
|
|
||||||
// aren't from the lists for our children / siblings.
|
|
||||||
//
|
|
||||||
// To go through an example, let's imagine we are processing a
|
|
||||||
// dom subtree like:
|
|
||||||
//
|
|
||||||
// <div><address><div><div/></div></address></div>
|
|
||||||
//
|
|
||||||
// And an invalidation list with a single invalidation like:
|
|
||||||
//
|
|
||||||
// [div div div]
|
|
||||||
//
|
|
||||||
// When we process the invalidation list for the outer div, we
|
|
||||||
// match it, and generate a `div div` invalidation, so for the
|
|
||||||
// <address> child we have:
|
|
||||||
//
|
|
||||||
// [div div div, div div]
|
|
||||||
//
|
|
||||||
// With the first of them marked as `matched`.
|
|
||||||
//
|
|
||||||
// When we process the <address> child, we don't match any of
|
|
||||||
// them, so both invalidations go untouched to our children.
|
|
||||||
//
|
|
||||||
// When we process the second <div>, we match _both_
|
|
||||||
// invalidations.
|
|
||||||
//
|
|
||||||
// However, when matching the first, we can tell it's been
|
|
||||||
// matched, and not push the corresponding `div div`
|
|
||||||
// invalidation, since we know it's necessarily already on the
|
|
||||||
// list.
|
|
||||||
//
|
|
||||||
// Thus, without skipping the push, we'll arrive to the
|
|
||||||
// innermost <div> with:
|
|
||||||
//
|
|
||||||
// [div div div, div div, div div, div]
|
|
||||||
//
|
|
||||||
// While skipping it, we won't arrive here with duplicating
|
|
||||||
// dependencies:
|
|
||||||
//
|
|
||||||
// [div div div, div div, div]
|
|
||||||
//
|
|
||||||
let can_skip_pushing = next_invalidation_kind == invalidation_kind &&
|
|
||||||
invalidation.matched_by_any_previous &&
|
|
||||||
next_invalidation.effective_for_next();
|
|
||||||
|
|
||||||
if can_skip_pushing {
|
|
||||||
debug!(
|
|
||||||
" > Can avoid push, since the invalidation had \
|
|
||||||
already been matched before"
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
match next_invalidation_kind {
|
|
||||||
InvalidationKind::Descendant(DescendantInvalidationKind::Dom) => {
|
|
||||||
descendant_invalidations
|
|
||||||
.dom_descendants
|
|
||||||
.push(next_invalidation);
|
|
||||||
},
|
|
||||||
InvalidationKind::Descendant(DescendantInvalidationKind::Part) => {
|
|
||||||
descendant_invalidations.parts.push(next_invalidation);
|
|
||||||
},
|
|
||||||
InvalidationKind::Descendant(DescendantInvalidationKind::Slotted) => {
|
|
||||||
descendant_invalidations
|
|
||||||
.slotted_descendants
|
|
||||||
.push(next_invalidation);
|
|
||||||
},
|
|
||||||
InvalidationKind::Sibling => {
|
|
||||||
sibling_invalidations.push(next_invalidation);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CompoundSelectorMatchingResult::NotMatched => {},
|
};
|
||||||
|
|
||||||
|
debug_assert_ne!(
|
||||||
|
next_invalidation.offset,
|
||||||
|
0,
|
||||||
|
"Rightmost selectors shouldn't generate more invalidations",
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut invalidated_self = false;
|
||||||
|
let next_combinator = next_invalidation
|
||||||
|
.dependency
|
||||||
|
.selector
|
||||||
|
.combinator_at_parse_order(next_invalidation.offset - 1);
|
||||||
|
|
||||||
|
if matches!(next_combinator, Combinator::PseudoElement) {
|
||||||
|
// This will usually be the very next component, except for
|
||||||
|
// the fact that we store compound selectors the other way
|
||||||
|
// around, so there could also be state pseudo-classes.
|
||||||
|
let pseudo_selector = next_invalidation
|
||||||
|
.dependency
|
||||||
|
.selector
|
||||||
|
.iter_raw_parse_order_from(next_invalidation.offset)
|
||||||
|
.skip_while(|c| matches!(**c, Component::NonTSPseudoClass(..)))
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let pseudo = match *pseudo_selector {
|
||||||
|
Component::PseudoElement(ref pseudo) => pseudo,
|
||||||
|
_ => unreachable!(
|
||||||
|
"Someone seriously messed up selector parsing: \
|
||||||
|
{:?} at offset {:?}: {:?}",
|
||||||
|
next_invalidation.dependency, next_invalidation.offset, pseudo_selector,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
// FIXME(emilio): This is not ideal, and could not be
|
||||||
|
// accurate if we ever have stateful element-backed eager
|
||||||
|
// pseudos.
|
||||||
|
//
|
||||||
|
// Ideally, we'd just remove element-backed eager pseudos
|
||||||
|
// altogether, given they work fine without it. Only gotcha
|
||||||
|
// is that we wouldn't style them in parallel, which may or
|
||||||
|
// may not be an issue.
|
||||||
|
//
|
||||||
|
// Also, this could be more fine grained now (perhaps a
|
||||||
|
// RESTYLE_PSEUDOS hint?).
|
||||||
|
//
|
||||||
|
// Note that we'll also restyle the pseudo-element because
|
||||||
|
// it would match this invalidation.
|
||||||
|
if self.processor.invalidates_on_eager_pseudo_element() {
|
||||||
|
if pseudo.is_eager() {
|
||||||
|
invalidated_self = true;
|
||||||
|
}
|
||||||
|
// If we start or stop matching some marker rules, and
|
||||||
|
// don't have a marker, then we need to restyle the
|
||||||
|
// element to potentially create one.
|
||||||
|
//
|
||||||
|
// Same caveats as for other eager pseudos apply, this
|
||||||
|
// could be more fine-grained.
|
||||||
|
if pseudo.is_marker() && self.element.marker_pseudo_element().is_none() {
|
||||||
|
invalidated_self = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: ::selection doesn't generate elements, so the
|
||||||
|
// regular invalidation doesn't work for it. We store
|
||||||
|
// the cached selection style holding off the originating
|
||||||
|
// element, so we need to restyle it in order to invalidate
|
||||||
|
// it. This is still not quite correct, since nothing
|
||||||
|
// triggers a repaint necessarily, but matches old Gecko
|
||||||
|
// behavior, and the ::selection implementation needs to
|
||||||
|
// change significantly anyway to implement
|
||||||
|
// https://github.com/w3c/csswg-drafts/issues/2474.
|
||||||
|
if pseudo.is_selection() {
|
||||||
|
invalidated_self = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
" > Invalidation matched, next: {:?}, ({:?})",
|
||||||
|
next_invalidation, next_combinator
|
||||||
|
);
|
||||||
|
|
||||||
|
let next_invalidation_kind = next_invalidation.kind();
|
||||||
|
|
||||||
|
// We can skip pushing under some circumstances, and we should
|
||||||
|
// because otherwise the invalidation list could grow
|
||||||
|
// exponentially.
|
||||||
|
//
|
||||||
|
// * First of all, both invalidations need to be of the same
|
||||||
|
// kind. This is because of how we propagate them going to
|
||||||
|
// the right of the tree for sibling invalidations and going
|
||||||
|
// down the tree for children invalidations. A sibling
|
||||||
|
// invalidation that ends up generating a children
|
||||||
|
// invalidation ends up (correctly) in five different lists,
|
||||||
|
// not in the same list five different times.
|
||||||
|
//
|
||||||
|
// * Then, the invalidation needs to be matched by a previous
|
||||||
|
// ancestor/sibling, in order to know that this invalidation
|
||||||
|
// has been generated already.
|
||||||
|
//
|
||||||
|
// * Finally, the new invalidation needs to be
|
||||||
|
// `effective_for_next()`, in order for us to know that it is
|
||||||
|
// still in the list, since we remove the dependencies that
|
||||||
|
// aren't from the lists for our children / siblings.
|
||||||
|
//
|
||||||
|
// To go through an example, let's imagine we are processing a
|
||||||
|
// dom subtree like:
|
||||||
|
//
|
||||||
|
// <div><address><div><div/></div></address></div>
|
||||||
|
//
|
||||||
|
// And an invalidation list with a single invalidation like:
|
||||||
|
//
|
||||||
|
// [div div div]
|
||||||
|
//
|
||||||
|
// When we process the invalidation list for the outer div, we
|
||||||
|
// match it, and generate a `div div` invalidation, so for the
|
||||||
|
// <address> child we have:
|
||||||
|
//
|
||||||
|
// [div div div, div div]
|
||||||
|
//
|
||||||
|
// With the first of them marked as `matched`.
|
||||||
|
//
|
||||||
|
// When we process the <address> child, we don't match any of
|
||||||
|
// them, so both invalidations go untouched to our children.
|
||||||
|
//
|
||||||
|
// When we process the second <div>, we match _both_
|
||||||
|
// invalidations.
|
||||||
|
//
|
||||||
|
// However, when matching the first, we can tell it's been
|
||||||
|
// matched, and not push the corresponding `div div`
|
||||||
|
// invalidation, since we know it's necessarily already on the
|
||||||
|
// list.
|
||||||
|
//
|
||||||
|
// Thus, without skipping the push, we'll arrive to the
|
||||||
|
// innermost <div> with:
|
||||||
|
//
|
||||||
|
// [div div div, div div, div div, div]
|
||||||
|
//
|
||||||
|
// While skipping it, we won't arrive here with duplicating
|
||||||
|
// dependencies:
|
||||||
|
//
|
||||||
|
// [div div div, div div, div]
|
||||||
|
//
|
||||||
|
let can_skip_pushing = next_invalidation_kind == invalidation_kind &&
|
||||||
|
invalidation.matched_by_any_previous &&
|
||||||
|
next_invalidation.effective_for_next();
|
||||||
|
|
||||||
|
if can_skip_pushing {
|
||||||
|
debug!(
|
||||||
|
" > Can avoid push, since the invalidation had \
|
||||||
|
already been matched before"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
match next_invalidation_kind {
|
||||||
|
InvalidationKind::Descendant(DescendantInvalidationKind::Dom) => {
|
||||||
|
descendant_invalidations
|
||||||
|
.dom_descendants
|
||||||
|
.push(next_invalidation);
|
||||||
|
},
|
||||||
|
InvalidationKind::Descendant(DescendantInvalidationKind::Part) => {
|
||||||
|
descendant_invalidations.parts.push(next_invalidation);
|
||||||
|
},
|
||||||
|
InvalidationKind::Descendant(DescendantInvalidationKind::Slotted) => {
|
||||||
|
descendant_invalidations
|
||||||
|
.slotted_descendants
|
||||||
|
.push(next_invalidation);
|
||||||
|
},
|
||||||
|
InvalidationKind::Sibling => {
|
||||||
|
sibling_invalidations.push(next_invalidation);
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SingleInvalidationResult {
|
SingleInvalidationResult {
|
||||||
invalidated_self,
|
invalidated_self,
|
||||||
matched,
|
matched: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,8 +85,8 @@ pub fn check_dependency<E, W>(
|
||||||
dependency: &Dependency,
|
dependency: &Dependency,
|
||||||
element: &E,
|
element: &E,
|
||||||
wrapper: &W,
|
wrapper: &W,
|
||||||
context: &mut MatchingContext<'_, SelectorImpl>,
|
mut context: &mut MatchingContext<'_, E::Impl>,
|
||||||
)
|
) -> bool
|
||||||
where
|
where
|
||||||
E: TElement,
|
E: TElement,
|
||||||
W: selectors::Element<Impl = E::Impl>,
|
W: selectors::Element<Impl = E::Impl>,
|
||||||
|
@ -166,6 +166,14 @@ where
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_outer_dependency(&mut self, dependency: &Dependency, element: E) -> bool {
|
||||||
|
// We cannot assert about `element` having a snapshot here (in fact it
|
||||||
|
// most likely won't), because it may be an arbitrary descendant or
|
||||||
|
// later-sibling of the element we started invalidating with.
|
||||||
|
let wrapper = ElementWrapper::new(element, &*self.shared_context.snapshot_map);
|
||||||
|
check_dependency(dependency, &element, &wrapper, &mut self.matching_context)
|
||||||
|
}
|
||||||
|
|
||||||
fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl> {
|
fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl> {
|
||||||
&mut self.matching_context
|
&mut self.matching_context
|
||||||
}
|
}
|
||||||
|
@ -447,7 +455,7 @@ where
|
||||||
/// Check whether a dependency should be taken into account.
|
/// Check whether a dependency should be taken into account.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn check_dependency(&mut self, dependency: &Dependency) -> bool {
|
fn check_dependency(&mut self, dependency: &Dependency) -> bool {
|
||||||
check_dependency(&self.element, &self.wrapper, &mut self.matching_context)
|
check_dependency(dependency, &self.element, &self.wrapper, &mut self.matching_context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scan_dependency(&mut self, dependency: &'selectors Dependency) {
|
fn scan_dependency(&mut self, dependency: &'selectors Dependency) {
|
||||||
|
@ -484,9 +492,8 @@ where
|
||||||
debug_assert_ne!(dependency.selector_offset, dependency.selector.len());
|
debug_assert_ne!(dependency.selector_offset, dependency.selector.len());
|
||||||
|
|
||||||
let invalidation = Invalidation::new(
|
let invalidation = Invalidation::new(
|
||||||
&dependency.selector,
|
&dependency,
|
||||||
self.matching_context.current_host.clone(),
|
self.matching_context.current_host.clone(),
|
||||||
dependency.selector.len() - dependency.selector_offset + 1,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
match invalidation_kind {
|
match invalidation_kind {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue