style: Keep track of nested dependencies for :where() and :is().

The tricky part of :is() and :where() is that they can have combinators inside,
so something like this is valid:

  foo:is(#bar > .baz) ~ taz

The current invalidation logic is based on the assumption that you can
represent a combinator as a (selector, offset) tuple, which are stored in the
Dependency struct. This assumption breaks with :is() and :where(), so we need
to make them be able to represent a combinator in an "inner" selector.

For this purpose, we add a `parent` dependency. With it, when invalidating
inside the `:is()` we can represent combinators inside as a stack.

The basic idea is that, for the example above, when an id of "bar" is added or
removed, we'd find a dependency like:

    Dependency {
        selector: #bar > .baz,
        offset: 1, // pointing to the `>` combinator
        parent: Some(Dependency {
            selector: foo:is(#bar > .baz) > taz,
            offset: 1, // Pointing to the `~` combinator.
            parent: None,
        })
    }

That way, we'd start matching at the element that changed, towards the right,
and if we find an element that matches .baz, instead of invalidating that
element, we'd look at the parent dependency, then double-check that the whole
left-hand-side of the selector (foo:is(#bar > .baz)) actually changed, and then
keep invalidating to the right using the parent dependency as usual.

This patch only builds the data structure and keeps the code compiling, the
actual invalidation work will come in a following patch.

Differential Revision: https://phabricator.services.mozilla.com/D71421
This commit is contained in:
Emilio Cobos Álvarez 2020-04-23 19:20:03 +00:00
parent cd63d7b272
commit c1bc588c93
2 changed files with 249 additions and 150 deletions

View file

@ -79,6 +79,39 @@ impl<'a, 'b: 'a, E: TElement + 'b> StateAndAttrInvalidationProcessor<'a, 'b, E>
}
}
/// Checks a dependency against a given element and wrapper, to see if something
/// changed.
pub fn check_dependency<E, W>(
dependency: &Dependency,
element: &E,
wrapper: &W,
context: &mut MatchingContext<'_, SelectorImpl>,
)
where
E: TElement,
W: selectors::Element<Impl = E::Impl>,
{
let matches_now = matches_selector(
&dependency.selector,
dependency.selector_offset,
None,
element,
&mut context,
&mut |_, _| {},
);
let matched_then = matches_selector(
&dependency.selector,
dependency.selector_offset,
None,
wrapper,
&mut context,
&mut |_, _| {},
);
matched_then != matches_now
}
/// Whether we should process the descendants of a given element for style
/// invalidation.
pub fn should_process_descendants(data: &ElementData) -> bool {
@ -412,28 +445,9 @@ where
}
/// Check whether a dependency should be taken into account.
#[inline]
fn check_dependency(&mut self, dependency: &Dependency) -> bool {
let element = &self.element;
let wrapper = &self.wrapper;
let matches_now = matches_selector(
&dependency.selector,
dependency.selector_offset,
None,
element,
&mut self.matching_context,
&mut |_, _| {},
);
let matched_then = matches_selector(
&dependency.selector,
dependency.selector_offset,
None,
wrapper,
&mut self.matching_context,
&mut |_, _| {},
);
matched_then != matches_now
check_dependency(&self.element, &self.wrapper, &mut self.matching_context)
}
fn scan_dependency(&mut self, dependency: &'selectors Dependency) {
@ -456,7 +470,13 @@ where
let invalidation_kind = dependency.invalidation_kind();
if matches!(invalidation_kind, DependencyInvalidationKind::Element) {
self.invalidates_self = true;
if let Some(ref parent) = dependency.parent {
// We know something changed in the inner selector, go outwards
// now.
self.scan_dependency(parent);
} else {
self.invalidates_self = true;
}
return;
}