Bug 1432850: Look at the snapshots when invalidating due to stylesheet changes. r?bz draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Thu, 25 Jan 2018 02:53:49 +0100
changeset 738043 f7119005531e17d84f52f34de7d1eb9158e5cbd1
parent 737989 fed48bc65af8cfb60ccf3d855cde590bd61fa42e
child 747607 7a7cc2bbc1bd54ca231e507f2fc3f698878892fe
push id96828
push userbmo:emilio@crisal.io
push dateThu, 25 Jan 2018 16:32:33 +0000
reviewersbz
bugs1432850, 37468
milestone60.0a1
Bug 1432850: Look at the snapshots when invalidating due to stylesheet changes. r?bz The selectorText test happens to pass right now because well, we don't implement the setter yet[1], but would fail if we implemented an specific invalidation in the way I'd have done it yesterday. [1]: https://bugzilla.mozilla.org/show_bug.cgi?id=37468 MozReview-Commit-ID: DrMTgLzQcnk
layout/style/ServoBindingList.h
layout/style/ServoStyleSet.cpp
servo/components/style/gecko/data.rs
servo/components/style/gecko/snapshot.rs
servo/components/style/invalidation/element/element_wrapper.rs
servo/components/style/invalidation/stylesheets.rs
servo/components/style/stylesheet_set.rs
servo/components/style/stylist.rs
servo/ports/geckolib/glue.rs
testing/web-platform/meta/MANIFEST.json
testing/web-platform/tests/css/selectors/invalidation/selectorText-dynamic-001.html
testing/web-platform/tests/css/selectors/invalidation/sheet-going-away-001.html
testing/web-platform/tests/css/selectors/invalidation/sheet-going-away-002-ref.html
testing/web-platform/tests/css/selectors/invalidation/sheet-going-away-002.html
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -104,18 +104,20 @@ SERVO_BINDING_FUNC(Servo_StyleSet_Prepen
                    const mozilla::ServoStyleSheet* gecko_sheet)
 SERVO_BINDING_FUNC(Servo_StyleSet_RemoveStyleSheet, void,
                    RawServoStyleSetBorrowed set,
                    const mozilla::ServoStyleSheet* gecko_sheet)
 SERVO_BINDING_FUNC(Servo_StyleSet_InsertStyleSheetBefore, void,
                    RawServoStyleSetBorrowed set,
                    const mozilla::ServoStyleSheet* gecko_sheet,
                    const mozilla::ServoStyleSheet* before)
-SERVO_BINDING_FUNC(Servo_StyleSet_FlushStyleSheets, void, RawServoStyleSetBorrowed set,
-                   RawGeckoElementBorrowedOrNull doc_elem)
+SERVO_BINDING_FUNC(Servo_StyleSet_FlushStyleSheets, void,
+                   RawServoStyleSetBorrowed set,
+                   RawGeckoElementBorrowedOrNull doc_elem,
+                   const mozilla::ServoElementSnapshotTable* snapshots)
 SERVO_BINDING_FUNC(Servo_StyleSet_NoteStyleSheetsChanged, void,
                    RawServoStyleSetBorrowed set,
                    bool author_style_disabled,
                    mozilla::OriginFlags changed_origins)
 SERVO_BINDING_FUNC(Servo_StyleSet_GetKeyframesForName, bool,
                    RawServoStyleSetBorrowed set,
                    nsAtom* name,
                    nsTimingFunctionBorrowed timing_function,
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -1430,17 +1430,21 @@ ServoStyleSet::UpdateStylist()
 {
   MOZ_ASSERT(StylistNeedsUpdate());
 
   if (mStylistState & StylistState::StyleSheetsDirty) {
     // There's no need to compute invalidations and such for an XBL styleset,
     // since they are loaded and unloaded synchronously, and they don't have to
     // deal with dynamic content changes.
     Element* root = IsMaster() ? mDocument->GetRootElement() : nullptr;
-    Servo_StyleSet_FlushStyleSheets(mRawSet.get(), root);
+    const ServoElementSnapshotTable* snapshots = nullptr;
+    if (nsPresContext* pc = GetPresContext()) {
+      snapshots = &pc->RestyleManager()->AsServo()->Snapshots();
+    }
+    Servo_StyleSet_FlushStyleSheets(mRawSet.get(), root, snapshots);
   }
 
   if (MOZ_UNLIKELY(mStylistState & StylistState::XBLStyleSheetsDirty)) {
     MOZ_ASSERT(IsMaster(), "Only master styleset can mark XBL stylesets dirty!");
     MOZ_ASSERT(GetPresContext(), "How did they get dirty?");
     mDocument->BindingManager()->UpdateBoundContentBindingsForServo(GetPresContext());
   }
 
--- a/servo/components/style/gecko/data.rs
+++ b/servo/components/style/gecko/data.rs
@@ -10,16 +10,17 @@ use dom::TElement;
 use gecko_bindings::bindings::{self, RawServoStyleSet};
 use gecko_bindings::structs::{RawGeckoPresContextOwned, ServoStyleSetSizes, ServoStyleSheet};
 use gecko_bindings::structs::{StyleSheetInfo, ServoStyleSheetInner, nsIDocument, self};
 use gecko_bindings::sugar::ownership::{HasArcFFI, HasBoxFFI, HasFFI, HasSimpleFFI};
 use invalidation::media_queries::{MediaListKey, ToMediaListKey};
 use malloc_size_of::MallocSizeOfOps;
 use media_queries::{Device, MediaList};
 use properties::ComputedValues;
+use selector_parser::SnapshotMap;
 use servo_arc::Arc;
 use shared_lock::{Locked, StylesheetGuards, SharedRwLockReadGuard};
 use std::sync::atomic::{AtomicUsize, Ordering};
 use stylesheets::{StylesheetContents, StylesheetInDocument};
 use stylist::Stylist;
 
 /// Little wrapper to a Gecko style sheet.
 #[derive(Debug, Eq, PartialEq)]
@@ -197,23 +198,25 @@ impl Drop for PerDocumentStyleDataImpl {
 }
 
 impl PerDocumentStyleDataImpl {
     /// Recreate the style data if the stylesheets have changed.
     pub fn flush_stylesheets<E>(
         &mut self,
         guard: &SharedRwLockReadGuard,
         document_element: Option<E>,
+        snapshots: Option<&SnapshotMap>,
     ) -> bool
     where
         E: TElement,
     {
         self.stylist.flush(
             &StylesheetGuards::same(guard),
             document_element,
+            snapshots,
         )
     }
 
     /// Returns whether private browsing is enabled.
     fn is_private_browsing_enabled(&self) -> bool {
         let doc =
             self.stylist.device().pres_context().mDocument.raw::<nsIDocument>();
         unsafe { bindings::Gecko_IsPrivateBrowsingEnabled(doc) }
--- a/servo/components/style/gecko/snapshot.rs
+++ b/servo/components/style/gecko/snapshot.rs
@@ -188,17 +188,18 @@ impl ElementSnapshot for GeckoElementSna
         snapshot_helpers::has_class(self.as_ptr(),
                                     name,
                                     case_sensitivity,
                                     bindings::Gecko_SnapshotClassOrClassList)
     }
 
     #[inline]
     fn each_class<F>(&self, callback: F)
-        where F: FnMut(&Atom)
+    where
+        F: FnMut(&Atom)
     {
         if !self.has_any(Flags::MaybeClass) {
             return;
         }
 
         snapshot_helpers::each_class(self.as_ptr(),
                                      callback,
                                      bindings::Gecko_SnapshotClassOrClassList)
--- a/servo/components/style/invalidation/element/element_wrapper.rs
+++ b/servo/components/style/invalidation/element/element_wrapper.rs
@@ -66,17 +66,18 @@ pub struct ElementWrapper<'a, E>
     where E: TElement,
 {
     element: E,
     cached_snapshot: Cell<Option<&'a Snapshot>>,
     snapshot_map: &'a SnapshotMap,
 }
 
 impl<'a, E> ElementWrapper<'a, E>
-    where E: TElement,
+where
+    E: TElement,
 {
     /// Trivially constructs an `ElementWrapper`.
     pub fn new(el: E, snapshot_map: &'a SnapshotMap) -> Self {
         ElementWrapper {
             element: el,
             cached_snapshot: Cell::new(None),
             snapshot_map: snapshot_map,
         }
--- a/servo/components/style/invalidation/stylesheets.rs
+++ b/servo/components/style/invalidation/stylesheets.rs
@@ -3,22 +3,24 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! A collection of invalidations due to changes in which stylesheets affect a
 //! document.
 
 #![deny(unsafe_code)]
 
 use Atom;
+use CaseSensitivityExt;
 use LocalName as SelectorLocalName;
 use dom::{TElement, TNode};
 use fnv::FnvHashSet;
+use invalidation::element::element_wrapper::{ElementSnapshot, ElementWrapper};
 use invalidation::element::restyle_hints::RestyleHint;
 use media_queries::Device;
-use selector_parser::SelectorImpl;
+use selector_parser::{SelectorImpl, Snapshot, SnapshotMap};
 use selectors::attr::CaseSensitivity;
 use selectors::parser::{Component, LocalName, Selector};
 use shared_lock::SharedRwLockReadGuard;
 use stylesheets::{CssRule, StylesheetInDocument};
 
 /// A style sheet invalidation represents a kind of element or subtree that may
 /// need to be restyled. Whether it represents a whole subtree or just a single
 /// element is determined by whether the invalidation is stored in the
@@ -38,41 +40,62 @@ impl Invalidation {
     fn is_id(&self) -> bool {
         matches!(*self, Invalidation::ID(..))
     }
 
     fn is_id_or_class(&self) -> bool {
         matches!(*self, Invalidation::ID(..) | Invalidation::Class(..))
     }
 
-    fn matches<E>(&self, element: E) -> bool
-        where E: TElement,
+    fn matches<E>(&self, element: E, snapshot: Option<&Snapshot>) -> bool
+    where
+        E: TElement,
     {
+        // FIXME This should look at the quirks mode of the document to
+        // determine case sensitivity.
+        //
+        // FIXME(emilio): Actually write a test and fix this.
+        let case_sensitivity = CaseSensitivity::CaseSensitive;
         match *self {
             Invalidation::Class(ref class) => {
-                // FIXME This should look at the quirks mode of the document to
-                // determine case sensitivity.
-                element.has_class(class, CaseSensitivity::CaseSensitive)
+                if element.has_class(class, case_sensitivity) {
+                    return true;
+                }
+
+                if let Some(snapshot) = snapshot {
+                    if snapshot.has_class(class, case_sensitivity) {
+                        return true;
+                    }
+                }
             }
             Invalidation::ID(ref id) => {
-                match element.get_id() {
-                    // FIXME This should look at the quirks mode of the document
-                    // to determine case sensitivity.
-                    Some(element_id) => element_id == *id,
-                    None => false,
+                if let Some(ref element_id) = element.get_id() {
+                    if case_sensitivity.eq_atom(element_id, id) {
+                        return true;
+                    }
+                }
+
+                if let Some(snapshot) = snapshot {
+                    if let Some(ref old_id) = snapshot.id_attr() {
+                        if case_sensitivity.eq_atom(old_id, id) {
+                            return true;
+                        }
+                    }
                 }
             }
             Invalidation::LocalName { ref name, ref lower_name } => {
                 // This could look at the quirks mode of the document, instead
                 // of testing against both names, but it's probably not worth
                 // it.
                 let local_name = element.get_local_name();
-                *local_name == **name || *local_name == **lower_name
+                return *local_name == **name || *local_name == **lower_name
             }
         }
+
+        false
     }
 }
 
 /// A set of invalidations due to stylesheet additions.
 ///
 /// TODO(emilio): We might be able to do the same analysis for media query
 /// changes too (or even selector changes?).
 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
@@ -140,37 +163,51 @@ impl StylesheetInvalidationSet {
         debug!(" > resulting self invalidations: {:?}", self.invalid_elements);
         debug!(" > fully_invalid: {}", self.fully_invalid);
     }
 
     /// Clears the invalidation set, invalidating elements as needed if
     /// `document_element` is provided.
     ///
     /// Returns true if any invalidations ocurred.
-    pub fn flush<E>(&mut self, document_element: Option<E>) -> bool
-        where E: TElement,
+    pub fn flush<E>(
+        &mut self,
+        document_element: Option<E>,
+        snapshots: Option<&SnapshotMap>,
+    ) -> bool
+    where
+        E: TElement,
     {
+        debug!("Stylist::flush({:?}, snapshots: {})", document_element, snapshots.is_some());
         let have_invalidations = match document_element {
-            Some(e) => self.process_invalidations(e),
+            Some(e) => self.process_invalidations(e, snapshots),
             None => false,
         };
         self.clear();
         have_invalidations
     }
 
     /// Clears the invalidation set without processing.
     pub fn clear(&mut self) {
         self.invalid_scopes.clear();
         self.invalid_elements.clear();
         self.fully_invalid = false;
     }
 
-    fn process_invalidations<E>(&self, element: E) -> bool
-        where E: TElement,
+    fn process_invalidations<E>(&self, element: E, snapshots: Option<&SnapshotMap>) -> bool
+    where
+        E: TElement,
     {
+        debug!(
+           "Stylist::process_invalidations({:?}, {:?}, {:?})",
+           element,
+           self.invalid_scopes,
+           self.invalid_elements,
+        );
+
         {
             let mut data = match element.mutate_data() {
                 Some(data) => data,
                 None => return false,
             };
 
             if self.fully_invalid {
                 debug!("process_invalidations: fully_invalid({:?})",
@@ -180,57 +217,65 @@ impl StylesheetInvalidationSet {
             }
         }
 
         if self.invalid_scopes.is_empty() && self.invalid_elements.is_empty() {
             debug!("process_invalidations: empty invalidation set");
             return false;
         }
 
-        self.process_invalidations_in_subtree(element)
+        self.process_invalidations_in_subtree(element, snapshots)
     }
 
     /// Process style invalidations in a given subtree. This traverses the
     /// subtree looking for elements that match the invalidations in
     /// invalid_scopes and invalid_elements.
     ///
     /// Returns whether it invalidated at least one element's style.
     #[allow(unsafe_code)]
-    fn process_invalidations_in_subtree<E>(&self, element: E) -> bool
-        where E: TElement,
+    fn process_invalidations_in_subtree<E>(
+        &self,
+        element: E,
+        snapshots: Option<&SnapshotMap>,
+    ) -> bool
+    where
+        E: TElement,
     {
+        debug!("process_invalidations_in_subtree({:?})", element);
         let mut data = match element.mutate_data() {
             Some(data) => data,
             None => return false,
         };
 
         if !data.has_styles() {
             return false;
         }
 
         if data.hint.contains_subtree() {
             debug!("process_invalidations_in_subtree: {:?} was already invalid",
                    element);
             return false;
         }
 
+        let element_wrapper = snapshots.map(|s| ElementWrapper::new(element, s));
+        let snapshot = element_wrapper.as_ref().and_then(|e| e.snapshot());
         for invalidation in &self.invalid_scopes {
-            if invalidation.matches(element) {
+            if invalidation.matches(element, snapshot) {
                 debug!("process_invalidations_in_subtree: {:?} matched subtree {:?}",
                        element, invalidation);
                 data.hint.insert(RestyleHint::restyle_subtree());
                 return true;
             }
         }
 
         let mut self_invalid = false;
 
         if !data.hint.contains(RestyleHint::RESTYLE_SELF) {
             for invalidation in &self.invalid_elements {
-                if invalidation.matches(element) {
+                if invalidation.matches(element, snapshot) {
                     debug!("process_invalidations_in_subtree: {:?} matched self {:?}",
                            element, invalidation);
                     data.hint.insert(RestyleHint::RESTYLE_SELF);
                     self_invalid = true;
                     break;
                 }
             }
         }
@@ -238,17 +283,18 @@ impl StylesheetInvalidationSet {
         let mut any_children_invalid = false;
 
         for child in element.traversal_children() {
             let child = match child.as_element() {
                 Some(e) => e,
                 None => continue,
             };
 
-            any_children_invalid |= self.process_invalidations_in_subtree(child);
+            any_children_invalid |=
+                self.process_invalidations_in_subtree(child, snapshots);
         }
 
         if any_children_invalid {
             debug!("Children of {:?} changed, setting dirty descendants",
                    element);
             unsafe { element.set_dirty_descendants() }
         }
 
--- a/servo/components/style/stylesheet_set.rs
+++ b/servo/components/style/stylesheet_set.rs
@@ -2,16 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! A centralized set of stylesheets for a document.
 
 use dom::TElement;
 use invalidation::stylesheets::StylesheetInvalidationSet;
 use media_queries::Device;
+use selector_parser::SnapshotMap;
 use shared_lock::SharedRwLockReadGuard;
 use std::slice;
 use stylesheets::{Origin, OriginSet, OriginSetIterator, PerOrigin, StylesheetInDocument};
 
 /// Entry for a StylesheetSet.
 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
 struct StylesheetSetEntry<S>
 where
@@ -497,25 +498,28 @@ where
         !self.origins_dirty.is_empty()
     }
 
     /// Flush the current set, unmarking it as dirty, and returns a
     /// `StylesheetFlusher` in order to rebuild the stylist.
     pub fn flush<'a, E>(
         &'a mut self,
         document_element: Option<E>,
+        snapshots: Option<&SnapshotMap>,
     ) -> StylesheetFlusher<'a, S>
     where
         E: TElement,
     {
         use std::mem;
 
         debug!("StylesheetSet::flush");
 
-        let had_invalidations = self.invalidations.flush(document_element);
+        let had_invalidations =
+            self.invalidations.flush(document_element, snapshots);
+
         let origins_dirty =
             mem::replace(&mut self.origins_dirty, OriginSet::empty());
 
         let mut origin_data_validity = PerOrigin::<OriginValidity>::default();
         for origin in origins_dirty.iter() {
             let collection = self.collections.borrow_mut_for_origin(&origin);
             *origin_data_validity.borrow_mut_for_origin(&origin) =
                 mem::replace(&mut collection.data_validity, OriginValidity::Valid);
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -20,17 +20,17 @@ use malloc_size_of::{MallocShallowSizeOf
 #[cfg(feature = "gecko")]
 use malloc_size_of::MallocUnconditionalShallowSizeOf;
 use media_queries::Device;
 use properties::{self, CascadeFlags, ComputedValues};
 use properties::{AnimationRules, PropertyDeclarationBlock};
 use rule_cache::{RuleCache, RuleCacheConditions};
 use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource};
 use selector_map::{PrecomputedHashMap, SelectorMap, SelectorMapEntry};
-use selector_parser::{SelectorImpl, PerPseudoElementMap, PseudoElement};
+use selector_parser::{SelectorImpl, SnapshotMap, PerPseudoElementMap, PseudoElement};
 use selectors::NthIndexCache;
 use selectors::attr::{CaseSensitivity, NamespaceConstraint};
 use selectors::bloom::{BloomFilter, NonCountingBloomFilter};
 use selectors::matching::{ElementSelectorFlags, matches_selector, MatchingContext, MatchingMode};
 use selectors::matching::VisitedHandlingMode;
 use selectors::parser::{AncestorHashes, Combinator, Component, Selector};
 use selectors::parser::{SelectorIter, Visit};
 use selectors::visitor::SelectorVisitor;
@@ -499,16 +499,17 @@ impl Stylist {
     }
 
     /// Flush the list of stylesheets if they changed, ensuring the stylist is
     /// up-to-date.
     pub fn flush<E>(
         &mut self,
         guards: &StylesheetGuards,
         document_element: Option<E>,
+        snapshots: Option<&SnapshotMap>,
     ) -> bool
     where
         E: TElement,
     {
         if !self.stylesheets.has_changed() {
             return false;
         }
 
@@ -543,17 +544,17 @@ impl Stylist {
                     self.quirks_mode,
                 );
 
             if let Some(ref constraints) = self.viewport_constraints {
                 self.device.account_for_viewport_rule(constraints);
             }
         }
 
-        let flusher = self.stylesheets.flush(document_element);
+        let flusher = self.stylesheets.flush(document_element, snapshots);
 
         let had_invalidations = flusher.had_invalidations();
 
         self.cascade_data.rebuild(
             &self.device,
             self.quirks_mode,
             flusher,
             guards,
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -1214,31 +1214,33 @@ pub extern "C" fn Servo_StyleSet_RemoveS
     let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
     let data = &mut *data;
     let guard = global_style_data.shared_lock.read();
     let sheet = unsafe { GeckoStyleSheet::new(sheet) };
     data.stylist.remove_stylesheet(sheet, &guard);
 }
 
 #[no_mangle]
-pub extern "C" fn Servo_StyleSet_FlushStyleSheets(
+pub unsafe extern "C" fn Servo_StyleSet_FlushStyleSheets(
     raw_data: RawServoStyleSetBorrowed,
     doc_element: RawGeckoElementBorrowedOrNull,
+    snapshots: *const ServoElementSnapshotTable,
 ) {
     let global_style_data = &*GLOBAL_STYLE_DATA;
     let guard = global_style_data.shared_lock.read();
     let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
     let doc_element = doc_element.map(GeckoElement);
-    let have_invalidations = data.flush_stylesheets(&guard, doc_element);
+
+    let have_invalidations =
+        data.flush_stylesheets(&guard, doc_element, snapshots.as_ref());
+
     if have_invalidations && doc_element.is_some() {
-        // The invalidation machinery propagates the bits up, but we still
-        // need to tell the gecko restyle root machinery about it.
-        unsafe {
-            bindings::Gecko_NoteDirtySubtreeForInvalidation(doc_element.unwrap().0);
-        }
+        // The invalidation machinery propagates the bits up, but we still need
+        // to tell the Gecko restyle root machinery about it.
+        bindings::Gecko_NoteDirtySubtreeForInvalidation(doc_element.unwrap().0);
     }
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_StyleSet_NoteStyleSheetsChanged(
     raw_data: RawServoStyleSetBorrowed,
     author_style_disabled: bool,
     changed_origins: OriginFlags,
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -158794,16 +158794,28 @@
       [
        "/css/selectors/focus-within-shadow-001-ref.html",
        "=="
       ]
      ],
      {}
     ]
    ],
+   "css/selectors/invalidation/sheet-going-away-002.html": [
+    [
+     "/css/selectors/invalidation/sheet-going-away-002.html",
+     [
+      [
+       "/css/selectors/invalidation/sheet-going-away-002-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/selectors/of-type-selectors.xhtml": [
     [
      "/css/selectors/of-type-selectors.xhtml",
      [
       [
        "/css/selectors/of-type-selectors-ref.xhtml",
        "=="
       ]
@@ -259870,16 +259882,21 @@
      {}
     ]
    ],
    "css/selectors/i18n/README": [
     [
      {}
     ]
    ],
+   "css/selectors/invalidation/sheet-going-away-002-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/selectors/of-type-selectors-ref.xhtml": [
     [
      {}
     ]
    ],
    "css/selectors/selector-placeholder-shown-type-change-001-ref.html": [
     [
      {}
@@ -310285,16 +310302,28 @@
     ]
    ],
    "css/selectors/invalidation/any-link-pseudo.html": [
     [
      "/css/selectors/invalidation/any-link-pseudo.html",
      {}
     ]
    ],
+   "css/selectors/invalidation/selectorText-dynamic-001.html": [
+    [
+     "/css/selectors/invalidation/selectorText-dynamic-001.html",
+     {}
+    ]
+   ],
+   "css/selectors/invalidation/sheet-going-away-001.html": [
+    [
+     "/css/selectors/invalidation/sheet-going-away-001.html",
+     {}
+    ]
+   ],
    "css/selectors/missing-right-token.html": [
     [
      "/css/selectors/missing-right-token.html",
      {}
     ]
    ],
    "custom-elements/CustomElementRegistry.html": [
     [
@@ -520924,16 +520953,32 @@
   "css/selectors/i18n/css3-selectors-lang-056.html": [
    "394ad01c928f8a15796bc6c29cdbc5e2dc37fd52",
    "testharness"
   ],
   "css/selectors/invalidation/any-link-pseudo.html": [
    "9593a7d2dddc79525edb801748a28b1a5a1837c7",
    "testharness"
   ],
+  "css/selectors/invalidation/selectorText-dynamic-001.html": [
+   "ab36d3d2b9f8e3b610f60a4b8859c95bf239eb43",
+   "testharness"
+  ],
+  "css/selectors/invalidation/sheet-going-away-001.html": [
+   "ac8bac5fe4663c70de2ee449fd0cf432d7a82eff",
+   "testharness"
+  ],
+  "css/selectors/invalidation/sheet-going-away-002-ref.html": [
+   "5616ec15bb322f49c4b28761df2cfb40fdafc226",
+   "support"
+  ],
+  "css/selectors/invalidation/sheet-going-away-002.html": [
+   "d23ac4f08cb0c70d06bfac4c4be121a8de8af48f",
+   "reftest"
+  ],
   "css/selectors/missing-right-token.html": [
    "d961e801f7df57161cd8c7b5a4b26ae24013c3e9",
    "testharness"
   ],
   "css/selectors/of-type-selectors-ref.xhtml": [
    "59f848418882c75898c422a9600c14ffab64c3d9",
    "support"
   ],
@@ -583601,17 +583646,17 @@
    "817011a8cdff7cfd7e445fb8ecb84e5d91f03993",
    "wdspec"
   ],
   "webdriver/tests/get_window_rect.py": [
    "c9139c16aa950c734c776887d6a762b867790812",
    "wdspec"
   ],
   "webdriver/tests/interaction/element_clear.py": [
-   "222a472b70c38e9178bdb64cc13a99053169a831",
+   "46f145bac93316b5f93d565e9e2389499771ff24",
    "wdspec"
   ],
   "webdriver/tests/interaction/send_keys_content_editable.py": [
    "9c071e60e1203cf31120f20874b5f38ba41dacc3",
    "wdspec"
   ],
   "webdriver/tests/interface.html": [
    "6625887cfa7f461dc428c11861fce71c47bef57d",
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors/invalidation/selectorText-dynamic-001.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: invalidation of class changes when the selector in a rule has changed</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/selectors-4/">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1432850">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  body { background: green; }
+  .red { background: red; }
+</style>
+<body class="red">
+Should have a green background.
+<script>
+test(() => {
+	document.body.offsetTop;
+	assert_equals(getComputedStyle(document.body).backgroundColor, "rgb(255, 0, 0)");
+	document.body.className = "";
+  document.styleSheets[0].cssRules[1].selectorText = ".bar";
+	assert_equals(getComputedStyle(document.body).backgroundColor, "rgb(0, 128, 0)");
+}, "Style should be recomputed correctly when the selector it depends on changes");
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors/invalidation/sheet-going-away-001.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: invalidation of class changes when the sheet the style depends on goes away</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/selectors-4/">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1432850">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  body { background: green; }
+</style>
+<style id="style">
+  .red { background: red; }
+</style>
+<body class="red">
+Should have a green background.
+<script>
+test(() => {
+	document.body.offsetTop;
+	assert_equals(getComputedStyle(document.body).backgroundColor, "rgb(255, 0, 0)");
+	document.body.className = "";
+	style.remove();
+	assert_equals(getComputedStyle(document.body).backgroundColor, "rgb(0, 128, 0)");
+}, "Style should be recomputed correctly when the stylesheet it depends on goes away");
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors/invalidation/sheet-going-away-002-ref.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<p style="color: green">
+  Should be green.
+</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors/invalidation/sheet-going-away-002.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: invalidation of class changes when the sheet the style depends on goes away</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/selectors-4/">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1432850">
+<link rel="match" href="sheet-going-away-002-ref.html">
+<style>
+  p { color: green; }
+</style>
+<style id="style">
+  .red p { color: red; }
+</style>
+<body class="red">
+<p>
+  Should be green.
+</p>
+<script>
+document.body.offsetTop;
+document.body.className = "";
+style.remove();
+</script>
+</body>