зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1509418 - Make Invalidation work in terms of a dependency, not a selector. r=heycam,boris
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:
Родитель
9a6a14fb98
Коммит
9c9a799887
|
@ -9,6 +9,7 @@ use crate::context::QuirksMode;
|
|||
use crate::dom::{TDocument, TElement, TNode, TShadowRoot};
|
||||
use crate::invalidation::element::invalidator::{DescendantInvalidationLists, Invalidation};
|
||||
use crate::invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector};
|
||||
use crate::invalidation::element::invalidation_map::Dependency;
|
||||
use crate::Atom;
|
||||
use selectors::attr::CaseSensitivity;
|
||||
use selectors::matching::{self, MatchingContext, MatchingMode};
|
||||
|
@ -130,7 +131,7 @@ where
|
|||
{
|
||||
results: &'a mut Q::Output,
|
||||
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>
|
||||
|
@ -143,6 +144,11 @@ where
|
|||
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(
|
||||
&mut self,
|
||||
element: E,
|
||||
|
@ -171,11 +177,10 @@ where
|
|||
self_invalidations
|
||||
};
|
||||
|
||||
for selector in self.selector_list.0.iter() {
|
||||
for dependency in self.dependencies.iter() {
|
||||
target_vector.push(Invalidation::new(
|
||||
selector,
|
||||
dependency,
|
||||
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 {
|
||||
query_selector_slow::<E, Q>(root, selector_list, results, &mut matching_context);
|
||||
} 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> {
|
||||
results,
|
||||
matching_context,
|
||||
selector_list,
|
||||
dependencies: &dependencies,
|
||||
};
|
||||
|
||||
for node in root.dom_children() {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
use crate::dom::TElement;
|
||||
use crate::element_state::DocumentState;
|
||||
use crate::invalidation::element::invalidation_map::Dependency;
|
||||
use crate::invalidation::element::invalidator::{DescendantInvalidationLists, InvalidationVector};
|
||||
use crate::invalidation::element::invalidator::{Invalidation, InvalidationProcessor};
|
||||
use crate::invalidation::element::state_and_attributes;
|
||||
|
@ -65,6 +66,11 @@ where
|
|||
E: TElement,
|
||||
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(
|
||||
&mut self,
|
||||
_element: E,
|
||||
|
@ -81,10 +87,14 @@ where
|
|||
|
||||
// We pass `None` as a scope, as document state selectors aren't
|
||||
// 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(
|
||||
&dependency.selector,
|
||||
&dependency.dependency,
|
||||
/* scope = */ None,
|
||||
0,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ pub struct Dependency {
|
|||
///
|
||||
/// 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
|
||||
/// dependency pointing to C.
|
||||
/// * One dependency from .bar pointing to C (parent: None)
|
||||
|
@ -88,6 +88,22 @@ pub enum DependencyInvalidationKind {
|
|||
}
|
||||
|
||||
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
|
||||
/// dependency represents.
|
||||
///
|
||||
|
@ -147,14 +163,14 @@ impl SelectorMapEntry for StateDependency {
|
|||
/// The same, but for document state selectors.
|
||||
#[derive(Clone, Debug, MallocSizeOf)]
|
||||
pub struct DocumentStateDependency {
|
||||
/// The selector that is affected. We don't need to track an offset, since
|
||||
/// when it changes it changes for the whole document anyway.
|
||||
/// We track `Dependency` even though we don't need to track an offset,
|
||||
/// since when it changes it changes for the whole document anyway.
|
||||
#[cfg_attr(
|
||||
feature = "gecko",
|
||||
ignore_malloc_size_of = "CssRules have primary refs, we measure there"
|
||||
)]
|
||||
#[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")]
|
||||
pub selector: Selector<SelectorImpl>,
|
||||
pub dependency: Dependency,
|
||||
/// The state this dependency is affected by.
|
||||
pub state: DocumentState,
|
||||
}
|
||||
|
@ -269,7 +285,7 @@ impl InvalidationMap {
|
|||
if !document_state.is_empty() {
|
||||
let dep = DocumentStateDependency {
|
||||
state: document_state,
|
||||
selector: selector.clone(),
|
||||
dependency: Dependency::for_full_selector_invalidation(selector.clone()),
|
||||
};
|
||||
self.document_state_selectors.try_push(dep)?;
|
||||
}
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
|
||||
use crate::context::StackLimitChecker;
|
||||
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::{CompoundSelectorMatchingResult, MatchingContext};
|
||||
use selectors::parser::{Combinator, Component, Selector};
|
||||
use selectors::parser::{Combinator, Component};
|
||||
use selectors::OpaqueElement;
|
||||
use smallvec::SmallVec;
|
||||
use std::fmt;
|
||||
|
@ -34,6 +34,27 @@ where
|
|||
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.
|
||||
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.
|
||||
#[derive(Clone)]
|
||||
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.
|
||||
///
|
||||
/// 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
|
||||
/// of the selector written as parsed / serialized.
|
||||
///
|
||||
/// It is initialized from the offset from `dependency`.
|
||||
offset: usize,
|
||||
/// Whether the invalidation was already matched by any previous sibling or
|
||||
/// ancestor.
|
||||
|
@ -149,16 +176,21 @@ pub struct 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(
|
||||
selector: &'a Selector<SelectorImpl>,
|
||||
dependency: &'a Dependency,
|
||||
scope: Option<OpaqueElement>,
|
||||
offset: usize,
|
||||
) -> 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 {
|
||||
selector,
|
||||
dependency,
|
||||
scope,
|
||||
offset,
|
||||
// + 1 to go past the combinator.
|
||||
offset: dependency.selector.len() + 1 - dependency.selector_offset,
|
||||
matched_by_any_previous: false,
|
||||
}
|
||||
}
|
||||
|
@ -174,7 +206,7 @@ impl<'a> Invalidation<'a> {
|
|||
// for the weird pseudos in <input type="number">.
|
||||
//
|
||||
// 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::Part |
|
||||
Combinator::SlotAssignment |
|
||||
|
@ -188,7 +220,7 @@ impl<'a> Invalidation<'a> {
|
|||
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 => {
|
||||
InvalidationKind::Descendant(DescendantInvalidationKind::Dom)
|
||||
},
|
||||
|
@ -206,7 +238,7 @@ impl<'a> fmt::Debug for Invalidation<'a> {
|
|||
use cssparser::ToCss;
|
||||
|
||||
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(..)) {
|
||||
break;
|
||||
}
|
||||
|
@ -763,201 +795,241 @@ where
|
|||
context.current_host = invalidation.scope;
|
||||
|
||||
matches_compound_selector_from(
|
||||
&invalidation.selector,
|
||||
&invalidation.dependency.selector,
|
||||
invalidation.offset,
|
||||
context,
|
||||
&self.element,
|
||||
)
|
||||
};
|
||||
|
||||
let mut invalidated_self = false;
|
||||
let mut matched = false;
|
||||
match matching_result {
|
||||
let next_invalidation = match matching_result {
|
||||
CompoundSelectorMatchingResult::NotMatched => {
|
||||
return SingleInvalidationResult {
|
||||
invalidated_self: false,
|
||||
matched: false,
|
||||
}
|
||||
},
|
||||
CompoundSelectorMatchingResult::FullyMatched => {
|
||||
debug!(" > Invalidation matched completely");
|
||||
matched = true;
|
||||
invalidated_self = true;
|
||||
// We matched completely. If we're an inner selector now we need
|
||||
// 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 {
|
||||
next_combinator_offset,
|
||||
} => {
|
||||
let next_combinator = invalidation
|
||||
.selector
|
||||
.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,
|
||||
Invalidation {
|
||||
dependency: invalidation.dependency,
|
||||
scope: invalidation.scope,
|
||||
offset: next_combinator_offset + 1,
|
||||
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 {
|
||||
invalidated_self,
|
||||
matched,
|
||||
matched: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,8 +85,8 @@ pub fn check_dependency<E, W>(
|
|||
dependency: &Dependency,
|
||||
element: &E,
|
||||
wrapper: &W,
|
||||
context: &mut MatchingContext<'_, SelectorImpl>,
|
||||
)
|
||||
mut context: &mut MatchingContext<'_, E::Impl>,
|
||||
) -> bool
|
||||
where
|
||||
E: TElement,
|
||||
W: selectors::Element<Impl = E::Impl>,
|
||||
|
@ -166,6 +166,14 @@ where
|
|||
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> {
|
||||
&mut self.matching_context
|
||||
}
|
||||
|
@ -447,7 +455,7 @@ where
|
|||
/// Check whether a dependency should be taken into account.
|
||||
#[inline]
|
||||
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) {
|
||||
|
@ -484,9 +492,8 @@ where
|
|||
debug_assert_ne!(dependency.selector_offset, dependency.selector.len());
|
||||
|
||||
let invalidation = Invalidation::new(
|
||||
&dependency.selector,
|
||||
&dependency,
|
||||
self.matching_context.current_host.clone(),
|
||||
dependency.selector.len() - dependency.selector_offset + 1,
|
||||
);
|
||||
|
||||
match invalidation_kind {
|
||||
|
|
Загрузка…
Ссылка в новой задаче