Bug 1368240: Properly handle invalidation of eager pseudo-elements with the new setup. r?heycam
MozReview-Commit-ID: EkzDVhC3GPH
--- a/layout/reftests/generated-content/reftest.list
+++ b/layout/reftests/generated-content/reftest.list
@@ -12,8 +12,9 @@ fuzzy-if(OSX==1010,1,10) == dynamic-tabl
== dynamic-table-cell-indent.html dynamic-table-cell-indent-ref.html
fuzzy-if(OSX==1010,1,10) == floated-01.html floated-01-ref.html
fuzzy-if(OSX==1010,1,10) == images-01.html images-01-ref.html
fuzzy-if(OSX==1010,1,10) == positioned-01.html positioned-01-ref.html
fuzzy-if(OSX==1010,1,10) == quotes-001.xml quotes-001-ref.xml
fuzzy-if(OSX==1010,1,10) == table-ignoring-whitespace-01.html table-ignoring-whitespace-01-ref.html
fuzzy-if(OSX==1010,1,10) == table-parts-01.html table-parts-01-ref.html
== before-style-sharing.html before-style-sharing-ref.html
+== transitive-style-invalidation.html transitive-style-invalidation-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/generated-content/transitive-style-invalidation-ref.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<style>
+.foo > div::before {
+ display: block;
+ width: 100px;
+ height: 100px;
+ background: green;
+ content: "";
+}
+</style>
+<div class="foo">
+ <div>
+ </div>
+</div>
+<script>
+onload = function() {
+ document.querySelector('.foo').className = 'foo bar';
+}
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/generated-content/transitive-style-invalidation.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<style>
+.foo > div::before {
+ display: block;
+ width: 100px;
+ height: 100px;
+ background: red;
+ content: "";
+}
+.foo.bar > div::before {
+ background: green;
+}
+</style>
+<div class="foo">
+ <div>
+ </div>
+</div>
+<script>
+onload = function() {
+ document.querySelector('.foo').className = 'foo bar';
+}
+</script>
--- a/servo/components/style/invalidation/element/invalidator.rs
+++ b/servo/components/style/invalidation/element/invalidator.rs
@@ -414,40 +414,76 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator
invalidation.offset,
&mut context,
&self.element
);
let mut invalidated_self = false;
match matching_result {
CompoundSelectorMatchingResult::Matched { next_combinator_offset: 0 } => {
- if let Some(ref mut data) = self.data {
- data.ensure_restyle().hint.insert(RESTYLE_SELF.into());
- invalidated_self = true;
- }
+ debug!(" > Invalidation matched completely");
+ invalidated_self = true;
}
CompoundSelectorMatchingResult::Matched { next_combinator_offset } => {
let next_combinator =
invalidation.selector.combinator_at(next_combinator_offset);
+ if matches!(next_combinator, Combinator::PseudoElement) {
+ let pseudo_selector =
+ invalidation.selector
+ .iter_raw_rev_from(next_combinator_offset - 1)
+ .next()
+ .unwrap();
+ let pseudo = match *pseudo_selector {
+ Component::PseudoElement(ref pseudo) => pseudo,
+ _ => unreachable!("Someone seriously messed up selector parsing"),
+ };
+
+ // 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 pseudo.is_eager() {
+ invalidated_self = true;
+ }
+ }
+
+
+ let next_invalidation = Invalidation {
+ selector: invalidation.selector.clone(),
+ offset: next_combinator_offset,
+ };
+
+ debug!(" > Invalidation matched, next: {:?}, ({:?})",
+ next_invalidation, next_combinator);
if next_combinator.is_ancestor() {
- descendant_invalidations.push(Invalidation {
- selector: invalidation.selector.clone(),
- offset: next_combinator_offset,
- })
+ descendant_invalidations.push(next_invalidation);
} else {
- sibling_invalidations.push(Invalidation {
- selector: invalidation.selector.clone(),
- offset: next_combinator_offset,
- })
+ sibling_invalidations.push(next_invalidation);
}
}
CompoundSelectorMatchingResult::NotMatched => {}
}
+ if invalidated_self {
+ if let Some(ref mut data) = self.data {
+ data.ensure_restyle().hint.insert(RESTYLE_SELF.into());
+ }
+ }
+
// TODO(emilio): For pseudo-elements this should be mostly false, except
// for the weird pseudos in <input type="number">.
//
// We should be able to do better here!
let effective_for_next =
match invalidation.selector.combinator_at(invalidation.offset) {
Combinator::NextSibling |
Combinator::Child => false,