--- 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>