mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 05:41:12 +00:00
Bug 1860373: Don't collapse invalidations from selectors shared by nesting. r=emilio
Differential Revision: https://phabricator.services.mozilla.com/D192085
This commit is contained in:
parent
52091ebc4b
commit
82b8c1dc6a
@ -270,11 +270,9 @@ struct RelativeSelectorInvalidation<'a> {
|
|||||||
dependency: &'a Dependency,
|
dependency: &'a Dependency,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Hash, Eq, PartialEq)]
|
|
||||||
struct InvalidationKey(SelectorKey, DependencyInvalidationKind);
|
|
||||||
|
|
||||||
type ElementDependencies<'a> = SmallVec<[(Option<OpaqueElement>, &'a Dependency); 1]>;
|
type ElementDependencies<'a> = SmallVec<[(Option<OpaqueElement>, &'a Dependency); 1]>;
|
||||||
type Dependencies<'a, E> = SmallVec<[(E, ElementDependencies<'a>); 1]>;
|
type Dependencies<'a, E> = SmallVec<[(E, ElementDependencies<'a>); 1]>;
|
||||||
|
type AlreadyInvalidated<'a, E> = SmallVec<[(E, Option<OpaqueElement>, &'a Dependency); 2]>;
|
||||||
|
|
||||||
/// Interface for collecting relative selector dependencies.
|
/// Interface for collecting relative selector dependencies.
|
||||||
pub struct RelativeSelectorDependencyCollector<'a, E>
|
pub struct RelativeSelectorDependencyCollector<'a, E>
|
||||||
@ -285,8 +283,7 @@ where
|
|||||||
/// a relative selector invalidation.
|
/// a relative selector invalidation.
|
||||||
dependencies: FxHashMap<E, ElementDependencies<'a>>,
|
dependencies: FxHashMap<E, ElementDependencies<'a>>,
|
||||||
/// Dependencies that created an invalidation right away.
|
/// Dependencies that created an invalidation right away.
|
||||||
/// Maps an invalidation into the affected element, and its scope & dependency.
|
invalidations: AlreadyInvalidated<'a, E>,
|
||||||
invalidations: FxHashMap<InvalidationKey, (E, Option<OpaqueElement>, &'a Dependency)>,
|
|
||||||
/// The top element in the subtree being invalidated.
|
/// The top element in the subtree being invalidated.
|
||||||
top: E,
|
top: E,
|
||||||
/// Optional context that will be used to try and skip invalidations
|
/// Optional context that will be used to try and skip invalidations
|
||||||
@ -313,6 +310,25 @@ impl<'a, E: TElement + 'a> Default for ToInvalidate<'a, E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dependency_selectors_match(a: &Dependency, b: &Dependency) -> bool {
|
||||||
|
if a.invalidation_kind() != b.invalidation_kind() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if SelectorKey::new(&a.selector) != SelectorKey::new(&b.selector) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let mut a_parent = a.parent.as_ref();
|
||||||
|
let mut b_parent = b.parent.as_ref();
|
||||||
|
while let (Some(a_p), Some(b_p)) = (a_parent, b_parent) {
|
||||||
|
if SelectorKey::new(&a_p.selector) != SelectorKey::new(&b_p.selector) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
a_parent = a_p.parent.as_ref();
|
||||||
|
b_parent = b_p.parent.as_ref();
|
||||||
|
}
|
||||||
|
a_parent.is_none() && b_parent.is_none()
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, E> RelativeSelectorDependencyCollector<'a, E>
|
impl<'a, E> RelativeSelectorDependencyCollector<'a, E>
|
||||||
where
|
where
|
||||||
E: TElement,
|
E: TElement,
|
||||||
@ -320,7 +336,7 @@ where
|
|||||||
fn new(top: E, optimization_context: Option<OptimizationContext<'a, E>>) -> Self {
|
fn new(top: E, optimization_context: Option<OptimizationContext<'a, E>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
dependencies: FxHashMap::default(),
|
dependencies: FxHashMap::default(),
|
||||||
invalidations: FxHashMap::default(),
|
invalidations: AlreadyInvalidated::default(),
|
||||||
top,
|
top,
|
||||||
optimization_context,
|
optimization_context,
|
||||||
}
|
}
|
||||||
@ -328,21 +344,21 @@ where
|
|||||||
|
|
||||||
fn insert_invalidation(
|
fn insert_invalidation(
|
||||||
&mut self,
|
&mut self,
|
||||||
key: InvalidationKey,
|
|
||||||
element: E,
|
element: E,
|
||||||
dependency: &'a Dependency,
|
dependency: &'a Dependency,
|
||||||
host: Option<OpaqueElement>,
|
host: Option<OpaqueElement>,
|
||||||
) {
|
) {
|
||||||
self.invalidations
|
match self.invalidations.iter_mut().find(|(_, _, d)| dependency_selectors_match(dependency, d)) {
|
||||||
.entry(key)
|
Some((e, h, d)) => {
|
||||||
.and_modify(|(e, h, d)| {
|
|
||||||
// Just keep one.
|
// Just keep one.
|
||||||
if d.selector_offset <= dependency.selector_offset {
|
if d.selector_offset > dependency.selector_offset {
|
||||||
return;
|
(*e, *h, *d) = (element, host, dependency);
|
||||||
}
|
}
|
||||||
(*e, *h, *d) = (element, host, dependency);
|
},
|
||||||
})
|
None => {
|
||||||
.or_insert_with(|| (element, host, dependency));
|
self.invalidations.push((element, host, dependency));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add this dependency, if it is unique (i.e. Different outer dependency or same outer dependency
|
/// Add this dependency, if it is unique (i.e. Different outer dependency or same outer dependency
|
||||||
@ -377,10 +393,6 @@ where
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.insert_invalidation(
|
self.insert_invalidation(
|
||||||
InvalidationKey(
|
|
||||||
SelectorKey::new(&dependency.selector),
|
|
||||||
dependency.invalidation_kind(),
|
|
||||||
),
|
|
||||||
element,
|
element,
|
||||||
dependency,
|
dependency,
|
||||||
host,
|
host,
|
||||||
@ -392,8 +404,8 @@ where
|
|||||||
/// Get the dependencies in a list format.
|
/// Get the dependencies in a list format.
|
||||||
fn get(self) -> ToInvalidate<'a, E> {
|
fn get(self) -> ToInvalidate<'a, E> {
|
||||||
let mut result = ToInvalidate::default();
|
let mut result = ToInvalidate::default();
|
||||||
for (key, (element, host, relative_dependency)) in self.invalidations {
|
for (element, host, dependency) in self.invalidations {
|
||||||
match key.1 {
|
match dependency.invalidation_kind() {
|
||||||
DependencyInvalidationKind::Normal(_) => {
|
DependencyInvalidationKind::Normal(_) => {
|
||||||
unreachable!("Inner selector in invalidation?")
|
unreachable!("Inner selector in invalidation?")
|
||||||
},
|
},
|
||||||
@ -403,12 +415,12 @@ where
|
|||||||
element != self.top,
|
element != self.top,
|
||||||
element,
|
element,
|
||||||
host,
|
host,
|
||||||
relative_dependency,
|
dependency,
|
||||||
) {
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let dependency = relative_dependency.parent.as_ref().unwrap();
|
let dependency = dependency.parent.as_ref().unwrap();
|
||||||
result.invalidations.push(RelativeSelectorInvalidation {
|
result.invalidations.push(RelativeSelectorInvalidation {
|
||||||
kind,
|
kind,
|
||||||
host,
|
host,
|
||||||
|
@ -0,0 +1,105 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>:has() invalidation with nesting where the selector is shared</title>
|
||||||
|
<link rel="author" title="David Shin" href="mailto:dshin@mozilla.com">
|
||||||
|
<script src="/resources/testharness.js"></script>
|
||||||
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
|
||||||
|
<style>
|
||||||
|
div, main { color: grey }
|
||||||
|
#outer1:has(.test) {
|
||||||
|
& #subject1_1 {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
& + #subject1_2 {
|
||||||
|
color: orangered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#outer2:has(.test) {
|
||||||
|
& .ancestor {
|
||||||
|
& #subject2_1 {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
& + #subject2_2 {
|
||||||
|
color: lightgreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#outer3:is(:has(.test) .outer) {
|
||||||
|
& #subject3_1 {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
& + #subject3_2 {
|
||||||
|
color: skyblue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<main id="main">
|
||||||
|
<div>
|
||||||
|
<div id="outer1">
|
||||||
|
<div id="trigger1"></div>
|
||||||
|
<div id="subject1_1"></div>
|
||||||
|
</div>
|
||||||
|
<div id="subject1_2"></div>
|
||||||
|
</div>
|
||||||
|
<div id="outer2">
|
||||||
|
<div id="trigger2"></div>
|
||||||
|
<div class="ancestor">
|
||||||
|
<div id="subject2_1"></div>
|
||||||
|
</div>
|
||||||
|
<div id="subject2_2"></div>
|
||||||
|
</div>
|
||||||
|
<div id="trigger3">
|
||||||
|
<div id="outer3" class="outer">
|
||||||
|
<div id="subject3_1"></div>
|
||||||
|
</div>
|
||||||
|
<div id="subject3_2"></div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<script>
|
||||||
|
const grey = 'rgb(128, 128, 128)';
|
||||||
|
const red = 'rgb(255, 0, 0)';
|
||||||
|
const orangered = 'rgb(255, 69, 0)';
|
||||||
|
const green = 'rgb(0, 128, 0)';
|
||||||
|
const lightgreen = 'rgb(144, 238, 144)';
|
||||||
|
const blue = 'rgb(0, 0, 255)';
|
||||||
|
const skyblue = 'rgb(135, 206, 235)';
|
||||||
|
|
||||||
|
const colors = {
|
||||||
|
red: {
|
||||||
|
descendant: red,
|
||||||
|
sibling: orangered,
|
||||||
|
},
|
||||||
|
green: {
|
||||||
|
descendant: green,
|
||||||
|
sibling: lightgreen,
|
||||||
|
},
|
||||||
|
blue: {
|
||||||
|
descendant: blue,
|
||||||
|
sibling: skyblue,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function testColor(testName, element, color) {
|
||||||
|
test(function() {
|
||||||
|
assert_equals(getComputedStyle(element).color, color);
|
||||||
|
}, testName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testClassChange(trigger, targetDescendant, targetSibling, expected)
|
||||||
|
{
|
||||||
|
trigger.classList.add('test');
|
||||||
|
testColor(`add .test to ${trigger.id} - check ${targetDescendant.id}`, targetDescendant, colors[expected].descendant);
|
||||||
|
testColor(`add .test to ${trigger.id} - check ${targetSibling.id}`, targetSibling, colors[expected].sibling);
|
||||||
|
trigger.classList.remove('test');
|
||||||
|
testColor(`remove .test from ${trigger.id} - check ${targetDescendant.id}`, targetDescendant, grey);
|
||||||
|
testColor(`remove .test from ${trigger.id} - check ${targetSibling.id}`, targetSibling, grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
testClassChange(trigger1, subject1_1, subject1_2, 'red');
|
||||||
|
testClassChange(trigger2, subject2_1, subject2_2, 'green');
|
||||||
|
testClassChange(trigger3, subject3_1, subject3_2, 'blue');
|
||||||
|
|
||||||
|
</script>
|
Loading…
Reference in New Issue
Block a user