From f523445fc30d7d9fd9633dc113c98be51d6ae68a Mon Sep 17 00:00:00 2001 From: Jo Steven Novaryo <65610990+stevennovaryo@users.noreply.github.com> Date: Wed, 23 Jul 2025 16:16:01 +0800 Subject: [PATCH] script: Implement `DocumentOrShadowDOM.adoptedStylesheet` with `FrozenArray` (#38163) Spec: https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets Implement `DocumentOrShadowDOM.adoptedStylesheet`. Due to `ObservableArray` being a massive issue on its own, it will be as it was a `FrozenArray` at first. This approach is similar to how Gecko implement adopted stylesheet. See https://phabricator.services.mozilla.com/D144547#change-IXyOzxxFn8sU. All of the changes will be gated behind a preference `dom_adoptedstylesheet_enabled`. Adopted stylesheet is implemented by adding the setter and getter of it. While the getter works like a normal attribute getter, the setter need to consider the inner working of document and shadow root StylesheetSet, specifically the ordering and the invalidations. Particularly for setter, we will clear all of the adopted stylesheet within the StylesheetSet and readd them. Possible optimization exist, but the focus should be directed to implementing `ObservableArray`. More context about the implementations https://hackmd.io/vtJAn4UyS_O0Idvk5dCO_w. Testing: Existing WPT Coverage Fixes: https://github.com/servo/servo/issues/37561 --------- Signed-off-by: Jo Steven Novaryo --- components/config/prefs.rs | 2 + components/script/dom/cssrulelist.rs | 2 +- components/script/dom/cssstyledeclaration.rs | 4 +- components/script/dom/cssstylerule.rs | 7 +- components/script/dom/cssstylesheet.rs | 99 +++++++++++--- components/script/dom/document.rs | 117 ++++++++++++++--- components/script/dom/documentorshadowroot.rs | 124 +++++++++++++++++- components/script/dom/htmllinkelement.rs | 7 +- components/script/dom/htmlstyleelement.rs | 7 +- components/script/dom/shadowroot.rs | 109 ++++++++++++--- components/script/dom/stylesheet.rs | 3 +- components/script/dom/stylesheetlist.rs | 22 +++- .../script_bindings/codegen/Bindings.conf | 4 +- .../webidls/DocumentOrShadowRoot.webidl | 6 + resources/wpt-prefs.json | 1 + tests/wpt/meta/__dir__.ini | 1 + .../layer-replaceSync-clears-stale.html.ini | 3 - ...SSStyleSheet-constructable-concat.html.ini | 4 - ...eSheet-constructable-invalidation.html.ini | 9 -- .../CSSStyleSheet-constructable.html.ini | 15 --- ...tylesheets-modify-array-and-sheet.html.ini | 9 -- ...doptedstylesheets-observablearray.html.ini | 3 - tests/wpt/meta/css/cssom/idlharness.html.ini | 12 -- ...e-array-no-leak-of-internals.window.js.ini | 3 - 24 files changed, 434 insertions(+), 139 deletions(-) delete mode 100644 tests/wpt/meta/css/css-cascade/layer-replaceSync-clears-stale.html.ini delete mode 100644 tests/wpt/meta/css/cssom/CSSStyleSheet-constructable-concat.html.ini delete mode 100644 tests/wpt/meta/css/cssom/CSSStyleSheet-constructable-invalidation.html.ini delete mode 100644 tests/wpt/meta/css/cssom/adoptedstylesheets-modify-array-and-sheet.html.ini delete mode 100644 tests/wpt/meta/webidl/ecmascript-binding/observable-array-no-leak-of-internals.window.js.ini diff --git a/components/config/prefs.rs b/components/config/prefs.rs index b408a6b3e47..0e095ef58c0 100644 --- a/components/config/prefs.rs +++ b/components/config/prefs.rs @@ -69,6 +69,7 @@ pub struct Preferences { /// List of comma-separated backends to be used by wgpu. pub dom_webgpu_wgpu_backend: String, pub dom_abort_controller_enabled: bool, + pub dom_adoptedstylesheet_enabled: bool, pub dom_async_clipboard_enabled: bool, pub dom_bluetooth_enabled: bool, pub dom_bluetooth_testing_enabled: bool, @@ -247,6 +248,7 @@ impl Preferences { devtools_server_enabled: false, devtools_server_port: 0, dom_abort_controller_enabled: false, + dom_adoptedstylesheet_enabled: false, dom_allow_scripts_to_close_windows: false, dom_async_clipboard_enabled: false, dom_bluetooth_enabled: false, diff --git a/components/script/dom/cssrulelist.rs b/components/script/dom/cssrulelist.rs index 08f194d5521..0a222f743ba 100644 --- a/components/script/dom/cssrulelist.rs +++ b/components/script/dom/cssrulelist.rs @@ -120,7 +120,7 @@ impl CSSRuleList { let parent_stylesheet = self.parent_stylesheet.style_stylesheet(); let owner = self .parent_stylesheet - .get_owner() + .owner_node() .and_then(DomRoot::downcast::); let loader = owner .as_ref() diff --git a/components/script/dom/cssstyledeclaration.rs b/components/script/dom/cssstyledeclaration.rs index 8060636f17f..de4b87cb2ca 100644 --- a/components/script/dom/cssstyledeclaration.rs +++ b/components/script/dom/cssstyledeclaration.rs @@ -127,9 +127,7 @@ impl CSSStyleOwner { if changed { // If this is changed, see also // CSSStyleRule::SetSelectorText, which does the same thing. - if let Some(owner) = rule.parent_stylesheet().get_owner() { - owner.stylesheet_list_owner().invalidate_stylesheets(); - } + rule.parent_stylesheet().notify_invalidations(); } result }, diff --git a/components/script/dom/cssstylerule.rs b/components/script/dom/cssstylerule.rs index 2e655fb59cd..a94d0b5c6ed 100644 --- a/components/script/dom/cssstylerule.rs +++ b/components/script/dom/cssstylerule.rs @@ -21,7 +21,6 @@ use crate::dom::cssgroupingrule::CSSGroupingRule; use crate::dom::cssrule::SpecificCSSRule; use crate::dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner}; use crate::dom::cssstylesheet::CSSStyleSheet; -use crate::dom::node::NodeTraits; use crate::dom::window::Window; use crate::script_runtime::CanGc; @@ -136,9 +135,9 @@ impl CSSStyleRuleMethods for CSSStyleRule { let mut guard = self.cssgroupingrule.shared_lock().write(); let stylerule = self.stylerule.write_with(&mut guard); mem::swap(&mut stylerule.selectors, &mut s); - if let Some(owner) = self.cssgroupingrule.parent_stylesheet().get_owner() { - owner.stylesheet_list_owner().invalidate_stylesheets(); - } + self.cssgroupingrule + .parent_stylesheet() + .notify_invalidations(); } } } diff --git a/components/script/dom/cssstylesheet.rs b/components/script/dom/cssstylesheet.rs index a367c9943de..4aaefd2e09d 100644 --- a/components/script/dom/cssstylesheet.rs +++ b/components/script/dom/cssstylesheet.rs @@ -6,6 +6,7 @@ use std::cell::Cell; use dom_struct::dom_struct; use js::rust::HandleObject; +use script_bindings::root::Dom; use servo_arc::Arc; use style::media_queries::MediaList as StyleMediaList; use style::shared_lock::SharedRwLock; @@ -13,6 +14,7 @@ use style::stylesheets::{ AllowImportRules, CssRuleTypes, Origin, Stylesheet as StyleStyleSheet, UrlExtraData, }; +use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::CSSStyleSheetBinding::{ CSSStyleSheetInit, CSSStyleSheetMethods, }; @@ -26,23 +28,41 @@ use crate::dom::bindings::reflector::{ use crate::dom::bindings::root::{DomRoot, MutNullableDom}; use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::cssrulelist::{CSSRuleList, RulesSource}; +use crate::dom::document::Document; use crate::dom::element::Element; use crate::dom::medialist::MediaList; use crate::dom::node::NodeTraits; use crate::dom::stylesheet::StyleSheet; +use crate::dom::stylesheetlist::StyleSheetListOwner; use crate::dom::window::Window; use crate::script_runtime::CanGc; #[dom_struct] pub(crate) struct CSSStyleSheet { stylesheet: StyleSheet, - owner: MutNullableDom, + + /// + owner_node: MutNullableDom, + + /// rulelist: MutNullableDom, + + /// The inner Stylo's [Stylesheet]. #[ignore_malloc_size_of = "Arc"] #[no_trace] style_stylesheet: Arc, + + /// origin_clean: Cell, - is_constructed: bool, + + /// In which [Document] that this stylesheet was constructed. + /// + /// + constructor_document: Option>, + + /// Documents or shadow DOMs thats adopt this stylesheet, they will be notified whenever + /// the stylesheet is modified. + adopters: DomRefCell>, } impl CSSStyleSheet { @@ -52,15 +72,16 @@ impl CSSStyleSheet { href: Option, title: Option, stylesheet: Arc, - is_constructed: bool, + constructor_document: Option<&Document>, ) -> CSSStyleSheet { CSSStyleSheet { stylesheet: StyleSheet::new_inherited(type_, href, title), - owner: MutNullableDom::new(owner), + owner_node: MutNullableDom::new(owner), rulelist: MutNullableDom::new(None), style_stylesheet: stylesheet, origin_clean: Cell::new(true), - is_constructed, + constructor_document: constructor_document.map(Dom::from_ref), + adopters: Default::default(), } } @@ -73,7 +94,7 @@ impl CSSStyleSheet { href: Option, title: Option, stylesheet: Arc, - is_constructed: bool, + constructor_document: Option<&Document>, can_gc: CanGc, ) -> DomRoot { reflect_dom_object( @@ -83,7 +104,7 @@ impl CSSStyleSheet { href, title, stylesheet, - is_constructed, + constructor_document, )), window, can_gc, @@ -100,7 +121,7 @@ impl CSSStyleSheet { href: Option, title: Option, stylesheet: Arc, - is_constructed: bool, + constructor_document: Option<&Document>, can_gc: CanGc, ) -> DomRoot { reflect_dom_object_with_proto( @@ -110,7 +131,7 @@ impl CSSStyleSheet { href, title, stylesheet, - is_constructed, + constructor_document, )), window, proto, @@ -134,21 +155,18 @@ impl CSSStyleSheet { self.style_stylesheet.disabled() } - pub(crate) fn get_owner(&self) -> Option> { - self.owner.get() + pub(crate) fn owner_node(&self) -> Option> { + self.owner_node.get() } pub(crate) fn set_disabled(&self, disabled: bool) { - if self.style_stylesheet.set_disabled(disabled) && self.get_owner().is_some() { - self.get_owner() - .unwrap() - .stylesheet_list_owner() - .invalidate_stylesheets(); + if self.style_stylesheet.set_disabled(disabled) { + self.notify_invalidations(); } } - pub(crate) fn set_owner(&self, value: Option<&Element>) { - self.owner.set(value); + pub(crate) fn set_owner_node(&self, value: Option<&Element>) { + self.owner_node.set(value); } pub(crate) fn shared_lock(&self) -> &SharedRwLock { @@ -159,6 +177,10 @@ impl CSSStyleSheet { &self.style_stylesheet } + pub(crate) fn style_stylesheet_arc(&self) -> &Arc { + &self.style_stylesheet + } + pub(crate) fn set_origin_clean(&self, origin_clean: bool) { self.origin_clean.set(origin_clean); } @@ -175,7 +197,39 @@ impl CSSStyleSheet { /// #[inline] pub(crate) fn is_constructed(&self) -> bool { - self.is_constructed + self.constructor_document.is_some() + } + + pub(crate) fn constructor_document_matches(&self, other_doc: &Document) -> bool { + match &self.constructor_document { + Some(doc) => *doc == other_doc, + None => false, + } + } + + /// Add a [StyleSheetListOwner] as an adopter to be notified whenever this stylesheet is + /// modified. + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + pub(crate) fn add_adopter(&self, owner: StyleSheetListOwner) { + debug_assert!(self.is_constructed()); + self.adopters.borrow_mut().push(owner); + } + + pub(crate) fn remove_adopter(&self, owner: &StyleSheetListOwner) { + let adopters = &mut *self.adopters.borrow_mut(); + if let Some(index) = adopters.iter().position(|o| o == owner) { + adopters.swap_remove(index); + } + } + + /// Invalidate all stylesheet set this stylesheet is a part on. + pub(crate) fn notify_invalidations(&self) { + if let Some(owner) = self.owner_node() { + owner.stylesheet_list_owner().invalidate_stylesheets(); + } + for adopter in self.adopters.borrow().iter() { + adopter.invalidate_stylesheets(); + } } } @@ -218,7 +272,7 @@ impl CSSStyleSheetMethods for CSSStyleSheet { None, // href None, // title stylesheet, - true, // is_constructed + Some(&window.Document()), // constructor_document can_gc, ) } @@ -294,7 +348,7 @@ impl CSSStyleSheetMethods for CSSStyleSheet { /// fn ReplaceSync(&self, text: USVString) -> Result<(), Error> { // Step 1. If the constructed flag is not set throw a NotAllowedError - if !self.is_constructed { + if !self.is_constructed() { return Err(Error::NotAllowed); } @@ -316,6 +370,9 @@ impl CSSStyleSheetMethods for CSSStyleSheet { // at the next getter access. self.rulelist.set(None); + // Notify invalidation to update the styles immediately. + self.notify_invalidations(); + Ok(()) } } diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 6a956c6ec78..6c7ad1deb3c 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -40,7 +40,7 @@ use fnv::FnvHashMap; use html5ever::{LocalName, Namespace, QualName, local_name, ns}; use hyper_serde::Serde; use ipc_channel::ipc; -use js::rust::{HandleObject, HandleValue}; +use js::rust::{HandleObject, HandleValue, MutableHandleValue}; use keyboard_types::{Code, Key, KeyState, Modifiers}; use layout_api::{ PendingRestyle, ReflowGoal, RestyleReason, TrustedNodeAddress, node_id_from_scroll_id, @@ -58,6 +58,7 @@ use profile_traits::ipc as profile_ipc; use profile_traits::time::TimerMetadataFrameType; use regex::bytes::Regex; use script_bindings::interfaces::DocumentHelpers; +use script_bindings::script_runtime::JSContext; use script_traits::{ConstellationInputEvent, DocumentActivity, ProgressiveWebMetricType}; use servo_arc::Arc; use servo_config::pref; @@ -114,6 +115,7 @@ use crate::dom::bindings::domname::{ self, is_valid_attribute_local_name, is_valid_element_local_name, namespace_from_domstring, }; use crate::dom::bindings::error::{Error, ErrorInfo, ErrorResult, Fallible}; +use crate::dom::bindings::frozenarray::CachedFrozenArray; use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; use crate::dom::bindings::num::Finite; use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; @@ -564,6 +566,12 @@ pub(crate) struct Document { active_keyboard_modifiers: Cell, /// The node that is currently highlighted by the devtools highlighted_dom_node: MutNullableDom, + /// The constructed stylesheet that is adopted by this [Document]. + /// + adopted_stylesheets: DomRefCell>>, + /// Cached frozen array of [`Self::adopted_stylesheets`] + #[ignore_malloc_size_of = "mozjs"] + adopted_stylesheets_frozen_types: CachedFrozenArray, } #[allow(non_snake_case)] @@ -4253,6 +4261,8 @@ impl Document { intersection_observers: Default::default(), active_keyboard_modifiers: Cell::new(Modifiers::empty()), highlighted_dom_node: Default::default(), + adopted_stylesheets: Default::default(), + adopted_stylesheets_frozen_types: CachedFrozenArray::new(), } } @@ -4898,26 +4908,30 @@ impl Document { .and_then(|s| s.owner.get_cssom_object()) } - /// Add a stylesheet owned by `owner` to the list of document sheets, in the - /// correct tree position. + /// Add a stylesheet owned by `owner_node` to the list of document sheets, in the + /// correct tree position. Additionally, ensure that the owned stylesheet is inserted + /// before any constructed stylesheet. + /// + /// #[cfg_attr(crown, allow(crown::unrooted_must_root))] // Owner needs to be rooted already necessarily. - pub(crate) fn add_stylesheet(&self, owner: StylesheetSource, sheet: Arc) { + pub(crate) fn add_owned_stylesheet(&self, owner_node: &Element, sheet: Arc) { let stylesheets = &mut *self.stylesheets.borrow_mut(); - // TODO(stevennovayo): support constructed stylesheet for adopted stylesheet and its ordering - let insertion_point = match &owner { - StylesheetSource::Element(owner_elem) => stylesheets - .iter() - .map(|(sheet, _origin)| sheet) - .find(|sheet_in_doc| match sheet_in_doc.owner { - StylesheetSource::Element(ref other_elem) => { - owner_elem.upcast::().is_before(other_elem.upcast()) + // FIXME(stevennovaryo): This is almost identical with the one in ShadowRoot::add_stylesheet. + let insertion_point = stylesheets + .iter() + .map(|(sheet, _origin)| sheet) + .find(|sheet_in_doc| { + match &sheet_in_doc.owner { + StylesheetSource::Element(other_node) => { + owner_node.upcast::().is_before(other_node.upcast()) }, - StylesheetSource::Constructed(_) => unreachable!(), - }) - .cloned(), - StylesheetSource::Constructed(_) => unreachable!(), - }; + // Non-constructed stylesheet should be ordered before the + // constructed ones. + StylesheetSource::Constructed(_) => true, + } + }) + .cloned(); if self.has_browsing_context() { self.window.layout_mut().add_stylesheet( @@ -4927,7 +4941,40 @@ impl Document { } DocumentOrShadowRoot::add_stylesheet( - owner, + StylesheetSource::Element(Dom::from_ref(owner_node)), + StylesheetSetRef::Document(stylesheets), + sheet, + insertion_point, + self.style_shared_lock(), + ); + } + + /// Append a constructed stylesheet to the back of document stylesheet set. Because + /// it would be the last element, we therefore would not mess with the ordering. + /// + /// + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + pub(crate) fn append_constructed_stylesheet(&self, cssom_stylesheet: &CSSStyleSheet) { + debug_assert!(cssom_stylesheet.is_constructed()); + + let stylesheets = &mut *self.stylesheets.borrow_mut(); + let sheet = cssom_stylesheet.style_stylesheet_arc().clone(); + + let insertion_point = stylesheets + .iter() + .last() + .map(|(sheet, _origin)| sheet) + .cloned(); + + if self.has_browsing_context() { + self.window.layout_mut().add_stylesheet( + sheet.clone(), + insertion_point.as_ref().map(|s| s.sheet.clone()), + ); + } + + DocumentOrShadowRoot::add_stylesheet( + StylesheetSource::Constructed(Dom::from_ref(cssom_stylesheet)), StylesheetSetRef::Document(stylesheets), sheet, insertion_point, @@ -6719,6 +6766,40 @@ impl DocumentMethods for Document { can_gc, ) } + + /// + fn AdoptedStyleSheets(&self, context: JSContext, can_gc: CanGc, retval: MutableHandleValue) { + self.adopted_stylesheets_frozen_types.get_or_init( + || { + self.adopted_stylesheets + .borrow() + .clone() + .iter() + .map(|sheet| sheet.as_rooted()) + .collect() + }, + context, + retval, + can_gc, + ); + } + + /// + fn SetAdoptedStyleSheets(&self, context: JSContext, val: HandleValue) -> ErrorResult { + let result = DocumentOrShadowRoot::set_adopted_stylesheet_from_jsval( + context, + self.adopted_stylesheets.borrow_mut().as_mut(), + val, + &StyleSheetListOwner::Document(Dom::from_ref(self)), + ); + + // If update is successful, clear the FrozenArray cache. + if result.is_ok() { + self.adopted_stylesheets_frozen_types.clear() + } + + result + } } fn update_with_current_instant(marker: &Cell>) { diff --git a/components/script/dom/documentorshadowroot.rs b/components/script/dom/documentorshadowroot.rs index 0173be600c4..1de7f574944 100644 --- a/components/script/dom/documentorshadowroot.rs +++ b/components/script/dom/documentorshadowroot.rs @@ -2,12 +2,17 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use std::collections::HashSet; use std::fmt; use embedder_traits::UntrustedNodeAddress; use euclid::default::Point2D; +use js::rust::HandleValue; use layout_api::{NodesFromPointQueryType, QueryMsg}; +use script_bindings::error::{Error, ErrorResult}; +use script_bindings::script_runtime::JSContext; use servo_arc::Arc; +use servo_config::pref; use style::invalidation::media_queries::{MediaListKey, ToMediaListKey}; use style::media_queries::MediaList; use style::shared_lock::{SharedRwLock as StyleSharedRwLock, SharedRwLockReadGuard}; @@ -19,6 +24,7 @@ use super::bindings::trace::HashMapTracedValues; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods; use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRootMethods; +use crate::dom::bindings::conversions::{ConversionResult, SafeFromJSValConvertible}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::num::Finite; use crate::dom::bindings::root::{Dom, DomRoot}; @@ -26,6 +32,7 @@ use crate::dom::element::Element; use crate::dom::htmlelement::HTMLElement; use crate::dom::node::{self, Node, VecPreOrderInsertionHelper}; use crate::dom::shadowroot::ShadowRoot; +use crate::dom::stylesheetlist::StyleSheetListOwner; use crate::dom::types::CSSStyleSheet; use crate::dom::window::Window; use crate::script_runtime::CanGc; @@ -37,8 +44,6 @@ use crate::stylesheet_set::StylesheetSetRef; #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] pub(crate) enum StylesheetSource { Element(Dom), - // TODO(stevennovaryo): This type of enum would not be used until we implement adopted stylesheet - #[allow(dead_code)] Constructed(Dom), } @@ -56,6 +61,10 @@ impl StylesheetSource { StylesheetSource::Constructed(ss) => ss.is_constructed(), } } + + pub(crate) fn is_constructed(&self) -> bool { + matches!(self, StylesheetSource::Constructed(_)) + } } #[derive(Clone, JSTraceable, MallocSizeOf)] @@ -285,6 +294,10 @@ impl DocumentOrShadowRoot { ) { debug_assert!(owner.is_a_valid_owner(), "Wat"); + if owner.is_constructed() && !pref!(dom_adoptedstylesheet_enabled) { + return; + } + let sheet = ServoStylesheetInDocument { sheet, owner }; let guard = style_shared_lock.read(); @@ -345,4 +358,111 @@ impl DocumentOrShadowRoot { let elements = id_map.entry(id.clone()).or_default(); elements.insert_pre_order(element, &root); } + + /// Inner part of adopted stylesheet. We are setting it by, assuming it is a FrozenArray + /// instead of an ObservableArray. Thus, it would have a completely different workflow + /// compared to the spec. The workflow here is actually following Gecko's implementation + /// of AdoptedStylesheet before the implementation of ObservableArray. + /// + /// The main purpose from this function is to set the `&mut adopted_stylesheet` to match + /// `incoming_stylesheet` and update the corresponding Styleset in a Document or a ShadowRoot. + /// In case of duplicates, the setter will respect the last duplicates. + /// + /// + // TODO: Handle duplicated adoptedstylesheet correctly, Stylo is preventing duplicates inside a + // Stylesheet Set. But this is not ideal. https://bugzilla.mozilla.org/show_bug.cgi?id=1978755 + fn set_adopted_stylesheet( + adopted_stylesheets: &mut Vec>, + incoming_stylesheets: &[Dom], + owner: &StyleSheetListOwner, + ) -> ErrorResult { + if !pref!(dom_adoptedstylesheet_enabled) { + return Ok(()); + } + + let owner_doc = match owner { + StyleSheetListOwner::Document(doc) => doc, + StyleSheetListOwner::ShadowRoot(root) => root.owner_doc(), + }; + + for sheet in incoming_stylesheets.iter() { + // > If value’s constructed flag is not set, or its constructor document is not equal + // > to this DocumentOrShadowRoot’s node document, throw a "NotAllowedError" DOMException. + if !sheet.constructor_document_matches(owner_doc) { + return Err(Error::NotAllowed); + } + } + + // The set to check for the duplicates when removing the old stylesheets. + let mut stylesheet_remove_set = HashSet::with_capacity(adopted_stylesheets.len()); + + // Remove the old stylesheets from the StyleSet. This workflow is limited by utilities + // Stylo StyleSet given to us. + // TODO(stevennovaryo): we could optimize this by maintaining the longest common prefix + // but we should consider the implementation of ObservableArray as well. + for sheet_to_remove in adopted_stylesheets.iter() { + // Check for duplicates, only proceed with the removal if the stylesheet is not removed yet. + if stylesheet_remove_set.insert(sheet_to_remove) { + owner.remove_stylesheet( + StylesheetSource::Constructed(sheet_to_remove.clone()), + sheet_to_remove.style_stylesheet_arc(), + ); + sheet_to_remove.remove_adopter(owner); + } + } + + // The set to check for the duplicates when adding a new stylesheet. + let mut stylesheet_add_set = HashSet::with_capacity(incoming_stylesheets.len()); + + // Readd all stylesheet to the StyleSet. This workflow is limited by the utilities + // Stylo StyleSet given to us. + for sheet in incoming_stylesheets.iter() { + // Check for duplicates. + if !stylesheet_add_set.insert(sheet) { + // The idea is that this case is rare, so we pay the price of removing the + // old sheet from the styles and append it later rather than the other way + // around. + owner.remove_stylesheet( + StylesheetSource::Constructed(sheet.clone()), + sheet.style_stylesheet_arc(), + ); + } else { + sheet.add_adopter(owner.clone()); + } + + owner.append_constructed_stylesheet(sheet); + } + + *adopted_stylesheets = incoming_stylesheets.to_vec(); + + Ok(()) + } + + /// Set adoptedStylesheet given a js value by converting and passing the converted + /// values to the inner [DocumentOrShadowRoot::set_adopted_stylesheet]. + pub(crate) fn set_adopted_stylesheet_from_jsval( + context: JSContext, + adopted_stylesheets: &mut Vec>, + incoming_value: HandleValue, + owner: &StyleSheetListOwner, + ) -> ErrorResult { + let maybe_stylesheets = + Vec::>::safe_from_jsval(context, incoming_value, ()); + + match maybe_stylesheets { + Ok(ConversionResult::Success(stylesheets)) => { + rooted_vec!(let stylesheets <- stylesheets.to_owned().iter().map(|s| s.as_traced())); + + DocumentOrShadowRoot::set_adopted_stylesheet( + adopted_stylesheets, + &stylesheets, + owner, + ) + }, + Ok(ConversionResult::Failure(msg)) => Err(Error::Type(msg.to_string())), + Err(_) => Err(Error::Type( + "The provided value is not a sequence of 'CSSStylesheet'.".to_owned(), + )), + } + } } diff --git a/components/script/dom/htmllinkelement.rs b/components/script/dom/htmllinkelement.rs index e6b11ae79ce..77c33b2ab2c 100644 --- a/components/script/dom/htmllinkelement.rs +++ b/components/script/dom/htmllinkelement.rs @@ -185,8 +185,7 @@ impl HTMLLinkElement { } *self.stylesheet.borrow_mut() = Some(s.clone()); self.clean_stylesheet_ownership(); - stylesheets_owner - .add_stylesheet(StylesheetSource::Element(Dom::from_ref(self.upcast())), s); + stylesheets_owner.add_owned_stylesheet(self.upcast(), s); } pub(crate) fn get_stylesheet(&self) -> Option> { @@ -203,7 +202,7 @@ impl HTMLLinkElement { None, // todo handle location None, // todo handle title sheet, - false, // is_constructed + None, // constructor_document can_gc, ) }) @@ -222,7 +221,7 @@ impl HTMLLinkElement { fn clean_stylesheet_ownership(&self) { if let Some(cssom_stylesheet) = self.cssom_stylesheet.get() { - cssom_stylesheet.set_owner(None); + cssom_stylesheet.set_owner_node(None); } self.cssom_stylesheet.set(None); } diff --git a/components/script/dom/htmlstyleelement.rs b/components/script/dom/htmlstyleelement.rs index bedc76d6b59..333b43d8bbe 100644 --- a/components/script/dom/htmlstyleelement.rs +++ b/components/script/dom/htmlstyleelement.rs @@ -160,8 +160,7 @@ impl HTMLStyleElement { } *self.stylesheet.borrow_mut() = Some(s.clone()); self.clean_stylesheet_ownership(); - stylesheets_owner - .add_stylesheet(StylesheetSource::Element(Dom::from_ref(self.upcast())), s); + stylesheets_owner.add_owned_stylesheet(self.upcast(), s); } pub(crate) fn get_stylesheet(&self) -> Option> { @@ -178,7 +177,7 @@ impl HTMLStyleElement { None, // todo handle location None, // todo handle title sheet, - false, // is_constructed + None, // constructor_document CanGc::note(), ) }) @@ -187,7 +186,7 @@ impl HTMLStyleElement { fn clean_stylesheet_ownership(&self) { if let Some(cssom_stylesheet) = self.cssom_stylesheet.get() { - cssom_stylesheet.set_owner(None); + cssom_stylesheet.set_owner_node(None); } self.cssom_stylesheet.set(None); } diff --git a/components/script/dom/shadowroot.rs b/components/script/dom/shadowroot.rs index 3d58cc8073c..7ab11d9e1d1 100644 --- a/components/script/dom/shadowroot.rs +++ b/components/script/dom/shadowroot.rs @@ -8,6 +8,9 @@ use std::collections::hash_map::Entry; use dom_struct::dom_struct; use html5ever::serialize::TraversalScope; +use js::rust::{HandleValue, MutableHandleValue}; +use script_bindings::error::ErrorResult; +use script_bindings::script_runtime::JSContext; use servo_arc::Arc; use style::author_styles::AuthorStyles; use style::dom::TElement; @@ -24,6 +27,7 @@ use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Bindi use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{ ShadowRootMode, SlotAssignmentMode, }; +use crate::dom::bindings::frozenarray::CachedFrozenArray; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::num::Finite; use crate::dom::bindings::reflector::reflect_dom_object; @@ -92,6 +96,14 @@ pub(crate) struct ShadowRoot { /// delegates_focus: Cell, + + /// The constructed stylesheet that is adopted by this [ShadowRoot]. + /// + adopted_stylesheets: DomRefCell>>, + + /// Cached frozen array of [`Self::adopted_stylesheets`] + #[ignore_malloc_size_of = "mozjs"] + adopted_stylesheets_frozen_types: CachedFrozenArray, } impl ShadowRoot { @@ -129,6 +141,8 @@ impl ShadowRoot { declarative: Cell::new(false), serializable: Cell::new(false), delegates_focus: Cell::new(false), + adopted_stylesheets: Default::default(), + adopted_stylesheets_frozen_types: CachedFrozenArray::new(), } } @@ -163,6 +177,10 @@ impl ShadowRoot { self.host.set(None); } + pub(crate) fn owner_doc(&self) -> &Document { + &self.document + } + pub(crate) fn get_focused_element(&self) -> Option> { //XXX get retargeted focused element None @@ -180,28 +198,51 @@ impl ShadowRoot { .and_then(|s| s.owner.get_cssom_object()) } - /// Add a stylesheet owned by `owner` to the list of shadow root sheets, in the - /// correct tree position. + /// Add a stylesheet owned by `owner_node` to the list of shadow root sheets, in the + /// correct tree position. Additionally, ensure that owned stylesheet is inserted before + /// any constructed stylesheet. + /// + /// #[cfg_attr(crown, allow(crown::unrooted_must_root))] // Owner needs to be rooted already necessarily. - pub(crate) fn add_stylesheet(&self, owner: StylesheetSource, sheet: Arc) { + pub(crate) fn add_owned_stylesheet(&self, owner_node: &Element, sheet: Arc) { let stylesheets = &mut self.author_styles.borrow_mut().stylesheets; - // TODO(stevennovayo): support constructed stylesheet for adopted stylesheet and its ordering - let insertion_point = match &owner { - StylesheetSource::Element(owner_elem) => stylesheets - .iter() - .find(|sheet_in_shadow| match sheet_in_shadow.owner { - StylesheetSource::Element(ref other_elem) => { - owner_elem.upcast::().is_before(other_elem.upcast()) + // FIXME(stevennovaryo): This is almost identical with the one in Document::add_stylesheet. + let insertion_point = stylesheets + .iter() + .find(|sheet_in_shadow| { + match &sheet_in_shadow.owner { + StylesheetSource::Element(other_node) => { + owner_node.upcast::().is_before(other_node.upcast()) }, - StylesheetSource::Constructed(_) => unreachable!(), - }) - .cloned(), - StylesheetSource::Constructed(_) => unreachable!(), - }; + // Non-constructed stylesheet should be ordered before the + // constructed ones. + StylesheetSource::Constructed(_) => true, + } + }) + .cloned(); DocumentOrShadowRoot::add_stylesheet( - owner, + StylesheetSource::Element(Dom::from_ref(owner_node)), + StylesheetSetRef::Author(stylesheets), + sheet, + insertion_point, + self.document.style_shared_lock(), + ); + } + + /// Append a constructed stylesheet to the back of shadow root stylesheet set. + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + pub(crate) fn append_constructed_stylesheet(&self, cssom_stylesheet: &CSSStyleSheet) { + debug_assert!(cssom_stylesheet.is_constructed()); + + let stylesheets = &mut self.author_styles.borrow_mut().stylesheets; + let sheet = cssom_stylesheet.style_stylesheet_arc().clone(); + + let insertion_point = stylesheets.iter().last().cloned(); + + DocumentOrShadowRoot::add_stylesheet( + StylesheetSource::Constructed(Dom::from_ref(cssom_stylesheet)), StylesheetSetRef::Author(stylesheets), sheet, insertion_point, @@ -473,6 +514,40 @@ impl ShadowRootMethods for ShadowRoot { // https://dom.spec.whatwg.org/#dom-shadowroot-onslotchange event_handler!(onslotchange, GetOnslotchange, SetOnslotchange); + + /// + fn AdoptedStyleSheets(&self, context: JSContext, can_gc: CanGc, retval: MutableHandleValue) { + self.adopted_stylesheets_frozen_types.get_or_init( + || { + self.adopted_stylesheets + .borrow() + .clone() + .iter() + .map(|sheet| sheet.as_rooted()) + .collect() + }, + context, + retval, + can_gc, + ); + } + + /// + fn SetAdoptedStyleSheets(&self, context: JSContext, val: HandleValue) -> ErrorResult { + let result = DocumentOrShadowRoot::set_adopted_stylesheet_from_jsval( + context, + self.adopted_stylesheets.borrow_mut().as_mut(), + val, + &StyleSheetListOwner::ShadowRoot(Dom::from_ref(self)), + ); + + // If update is successful, clear the FrozenArray cache. + if result.is_ok() { + self.adopted_stylesheets_frozen_types.clear(); + } + + result + } } impl VirtualMethods for ShadowRoot { @@ -485,6 +560,8 @@ impl VirtualMethods for ShadowRoot { s.bind_to_tree(context, can_gc); } + // TODO(stevennovaryo): Handle adoptedStylesheet to deal with different + // constructor document. if context.tree_connected { let document = self.owner_document(); document.register_shadow_root(self); diff --git a/components/script/dom/stylesheet.rs b/components/script/dom/stylesheet.rs index 8bb6faa0c90..58c466201a2 100644 --- a/components/script/dom/stylesheet.rs +++ b/components/script/dom/stylesheet.rs @@ -51,7 +51,8 @@ impl StyleSheetMethods for StyleSheet { // https://drafts.csswg.org/cssom/#dom-stylesheet-ownernode fn GetOwnerNode(&self) -> Option> { - self.downcast::().and_then(|s| s.get_owner()) + self.downcast::() + .and_then(|s| s.owner_node()) } // https://drafts.csswg.org/cssom/#dom-stylesheet-media diff --git a/components/script/dom/stylesheetlist.rs b/components/script/dom/stylesheetlist.rs index ab9452cad5f..c7b254e4541 100644 --- a/components/script/dom/stylesheetlist.rs +++ b/components/script/dom/stylesheetlist.rs @@ -12,13 +12,14 @@ use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::cssstylesheet::CSSStyleSheet; use crate::dom::document::Document; use crate::dom::documentorshadowroot::StylesheetSource; +use crate::dom::element::Element; use crate::dom::shadowroot::ShadowRoot; use crate::dom::stylesheet::StyleSheet; use crate::dom::window::Window; use crate::script_runtime::CanGc; #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] -#[derive(JSTraceable, MallocSizeOf)] +#[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)] pub(crate) enum StyleSheetListOwner { Document(Dom), ShadowRoot(Dom), @@ -39,12 +40,23 @@ impl StyleSheetListOwner { } } - #[cfg_attr(crown, allow(crown::unrooted_must_root))] // Owner needs to be rooted already necessarily. - pub(crate) fn add_stylesheet(&self, owner: StylesheetSource, sheet: Arc) { + pub(crate) fn add_owned_stylesheet(&self, owner_node: &Element, sheet: Arc) { match *self { - StyleSheetListOwner::Document(ref doc) => doc.add_stylesheet(owner, sheet), + StyleSheetListOwner::Document(ref doc) => doc.add_owned_stylesheet(owner_node, sheet), StyleSheetListOwner::ShadowRoot(ref shadow_root) => { - shadow_root.add_stylesheet(owner, sheet) + shadow_root.add_owned_stylesheet(owner_node, sheet) + }, + } + } + + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + pub(crate) fn append_constructed_stylesheet(&self, cssom_stylesheet: &CSSStyleSheet) { + match *self { + StyleSheetListOwner::Document(ref doc) => { + doc.append_constructed_stylesheet(cssom_stylesheet) + }, + StyleSheetListOwner::ShadowRoot(ref shadow_root) => { + shadow_root.append_constructed_stylesheet(cssom_stylesheet) }, } } diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index af1b0435d6d..4823eab15f6 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -167,7 +167,7 @@ DOMInterfaces = { 'Document': { 'additionalTraits': ["crate::interfaces::DocumentHelpers"], - 'canGc': ['Close', 'CreateElement', 'CreateElementNS', 'ImportNode', 'SetTitle', 'Write', 'Writeln', 'CreateEvent', 'CreateRange', 'Open', 'Open_', 'CreateComment', 'CreateAttribute', 'CreateAttributeNS', 'CreateDocumentFragment', 'CreateTextNode', 'CreateCDATASection', 'CreateProcessingInstruction', 'Prepend', 'Append', 'ReplaceChildren', 'SetBgColor', 'SetFgColor', 'Fonts', 'ElementFromPoint', 'ElementsFromPoint', 'GetScrollingElement', 'ExitFullscreen', 'CreateExpression', 'CreateNSResolver', 'Evaluate', 'StyleSheets', 'Implementation', 'GetElementsByTagName', 'GetElementsByTagNameNS', 'GetElementsByClassName', 'AdoptNode', 'CreateNodeIterator', 'SetBody', 'GetElementsByName', 'Images', 'Embeds', 'Plugins', 'Links', 'Forms', 'Scripts', 'Anchors', 'Applets', 'Children', 'GetSelection', 'NamedGetter'], + 'canGc': ['Close', 'CreateElement', 'CreateElementNS', 'ImportNode', 'SetTitle', 'Write', 'Writeln', 'CreateEvent', 'CreateRange', 'Open', 'Open_', 'CreateComment', 'CreateAttribute', 'CreateAttributeNS', 'CreateDocumentFragment', 'CreateTextNode', 'CreateCDATASection', 'CreateProcessingInstruction', 'Prepend', 'Append', 'ReplaceChildren', 'SetBgColor', 'SetFgColor', 'Fonts', 'ElementFromPoint', 'ElementsFromPoint', 'GetScrollingElement', 'ExitFullscreen', 'CreateExpression', 'CreateNSResolver', 'Evaluate', 'StyleSheets', 'Implementation', 'GetElementsByTagName', 'GetElementsByTagNameNS', 'GetElementsByClassName', 'AdoptNode', 'CreateNodeIterator', 'SetBody', 'GetElementsByName', 'Images', 'Embeds', 'Plugins', 'Links', 'Forms', 'Scripts', 'Anchors', 'Applets', 'Children', 'GetSelection', 'NamedGetter', 'AdoptedStyleSheets'], }, 'DissimilarOriginWindow': { @@ -586,7 +586,7 @@ DOMInterfaces = { }, 'ShadowRoot': { - 'canGc': ['SetHTMLUnsafe', 'ElementFromPoint', 'ElementsFromPoint', 'SetInnerHTML', 'GetHTML', 'InnerHTML'], + 'canGc': ['SetHTMLUnsafe', 'ElementFromPoint', 'ElementsFromPoint', 'SetInnerHTML', 'GetHTML', 'InnerHTML', 'AdoptedStyleSheets'], }, 'StaticRange': { diff --git a/components/script_bindings/webidls/DocumentOrShadowRoot.webidl b/components/script_bindings/webidls/DocumentOrShadowRoot.webidl index c833299482b..aaaa4c2aac1 100644 --- a/components/script_bindings/webidls/DocumentOrShadowRoot.webidl +++ b/components/script_bindings/webidls/DocumentOrShadowRoot.webidl @@ -15,3 +15,9 @@ interface mixin DocumentOrShadowRoot { readonly attribute Element? activeElement; readonly attribute StyleSheetList styleSheets; }; + +partial interface mixin DocumentOrShadowRoot { + // TODO(37902): Use ObservableArray Array when available + [Pref="dom_adoptedstylesheet_enabled", SetterThrows] + attribute /* ObservableArray */ any adoptedStyleSheets; +}; diff --git a/resources/wpt-prefs.json b/resources/wpt-prefs.json index 70e7ab1d269..c5f6fb02d24 100644 --- a/resources/wpt-prefs.json +++ b/resources/wpt-prefs.json @@ -1,4 +1,5 @@ { + "dom_adoptedstylesheet_enabled": true, "dom_webxr_test": true, "gfx_text_antialiasing_enabled": false, "dom_testutils_enabled": true diff --git a/tests/wpt/meta/__dir__.ini b/tests/wpt/meta/__dir__.ini index 25bff179bc5..53afe574633 100644 --- a/tests/wpt/meta/__dir__.ini +++ b/tests/wpt/meta/__dir__.ini @@ -1,4 +1,5 @@ prefs: [ + "dom_adoptedstylesheet_enabled:true", "dom_indexeddb_enabled:true", "dom_serviceworker_enabled:true", "dom_testutils_enabled:true", diff --git a/tests/wpt/meta/css/css-cascade/layer-replaceSync-clears-stale.html.ini b/tests/wpt/meta/css/css-cascade/layer-replaceSync-clears-stale.html.ini deleted file mode 100644 index f2e99de4ba0..00000000000 --- a/tests/wpt/meta/css/css-cascade/layer-replaceSync-clears-stale.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[layer-replaceSync-clears-stale.html] - [replaceSync clears stale layer statements] - expected: FAIL diff --git a/tests/wpt/meta/css/cssom/CSSStyleSheet-constructable-concat.html.ini b/tests/wpt/meta/css/cssom/CSSStyleSheet-constructable-concat.html.ini deleted file mode 100644 index c4c419ff449..00000000000 --- a/tests/wpt/meta/css/cssom/CSSStyleSheet-constructable-concat.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[CSSStyleSheet-constructable-concat.html] - expected: TIMEOUT - [adoptedStyleSheets should allow .concat on empty starting values] - expected: FAIL diff --git a/tests/wpt/meta/css/cssom/CSSStyleSheet-constructable-invalidation.html.ini b/tests/wpt/meta/css/cssom/CSSStyleSheet-constructable-invalidation.html.ini deleted file mode 100644 index fc908daeaab..00000000000 --- a/tests/wpt/meta/css/cssom/CSSStyleSheet-constructable-invalidation.html.ini +++ /dev/null @@ -1,9 +0,0 @@ -[CSSStyleSheet-constructable-invalidation.html] - [mutating constructed CSSStyleSheet applied to root invalidates styles] - expected: FAIL - - [mutating constructed CSSStyleSheet applied to shadowdom invalidates styles] - expected: FAIL - - [mutating dependent constructed CSSStyleSheet applied to shadowdom invalidates styles] - expected: FAIL diff --git a/tests/wpt/meta/css/cssom/CSSStyleSheet-constructable.html.ini b/tests/wpt/meta/css/cssom/CSSStyleSheet-constructable.html.ini index b8d885c47bd..2c23dd00540 100644 --- a/tests/wpt/meta/css/cssom/CSSStyleSheet-constructable.html.ini +++ b/tests/wpt/meta/css/cssom/CSSStyleSheet-constructable.html.ini @@ -1,7 +1,4 @@ [CSSStyleSheet-constructable.html] - [document.adoptedStyleSheets should initially have length 0.] - expected: FAIL - [new CSSStyleSheet produces empty CSSStyleSheet] expected: FAIL @@ -17,12 +14,6 @@ [Re-attaching shadow host with adopted stylesheets work] expected: FAIL - [Attaching a shadow root that already has adopted stylesheets work] - expected: FAIL - - [Re-attaching shadow host and updating attributes work] - expected: FAIL - [Changes to constructed stylesheets through CSSOM is reflected] expected: FAIL @@ -35,12 +26,6 @@ [Stylesheet constructed on iframe cannot be used in the main Document] expected: FAIL - [Adding non-constructed stylesheet to AdoptedStyleSheets is not allowed when the owner document of the stylesheet is in the same document tree as the AdoptedStyleSheets] - expected: FAIL - - [Adding non-constructed stylesheet to AdoptedStyleSheets is not allowed when the owner document of the stylesheet and the AdoptedStyleSheets are in different document trees] - expected: FAIL - [CSSStyleSheet.replaceSync correctly updates the style of its adopters synchronously] expected: FAIL diff --git a/tests/wpt/meta/css/cssom/adoptedstylesheets-modify-array-and-sheet.html.ini b/tests/wpt/meta/css/cssom/adoptedstylesheets-modify-array-and-sheet.html.ini deleted file mode 100644 index f3585fe7abf..00000000000 --- a/tests/wpt/meta/css/cssom/adoptedstylesheets-modify-array-and-sheet.html.ini +++ /dev/null @@ -1,9 +0,0 @@ -[adoptedstylesheets-modify-array-and-sheet.html] - [Add the two sheets. Text should be red.] - expected: FAIL - - [Flip the two sheet. Still red.] - expected: FAIL - - [Modify the color declaration. Should now be green.] - expected: FAIL diff --git a/tests/wpt/meta/css/cssom/adoptedstylesheets-observablearray.html.ini b/tests/wpt/meta/css/cssom/adoptedstylesheets-observablearray.html.ini index 3425edabdcf..0d2f5123197 100644 --- a/tests/wpt/meta/css/cssom/adoptedstylesheets-observablearray.html.ini +++ b/tests/wpt/meta/css/cssom/adoptedstylesheets-observablearray.html.ini @@ -4,6 +4,3 @@ [shadowRoot.adoptedStyleSheets should allow mutation in-place] expected: FAIL - - [adoptedStyleSheets should return true for isArray()] - expected: FAIL diff --git a/tests/wpt/meta/css/cssom/idlharness.html.ini b/tests/wpt/meta/css/cssom/idlharness.html.ini index 2578ca63706..498f103e531 100644 --- a/tests/wpt/meta/css/cssom/idlharness.html.ini +++ b/tests/wpt/meta/css/cssom/idlharness.html.ini @@ -395,18 +395,6 @@ [CSSStyleSheet interface: calling replace(USVString) on sheet with too few arguments must throw TypeError] expected: FAIL - [Document interface: attribute adoptedStyleSheets] - expected: FAIL - - [Document interface: document must inherit property "adoptedStyleSheets" with the proper type] - expected: FAIL - - [Document interface: new Document() must inherit property "adoptedStyleSheets" with the proper type] - expected: FAIL - - [ShadowRoot interface: attribute adoptedStyleSheets] - expected: FAIL - [CSSImportRule interface: attribute supportsText] expected: FAIL diff --git a/tests/wpt/meta/webidl/ecmascript-binding/observable-array-no-leak-of-internals.window.js.ini b/tests/wpt/meta/webidl/ecmascript-binding/observable-array-no-leak-of-internals.window.js.ini deleted file mode 100644 index d37c885d391..00000000000 --- a/tests/wpt/meta/webidl/ecmascript-binding/observable-array-no-leak-of-internals.window.js.ini +++ /dev/null @@ -1,3 +0,0 @@ -[observable-array-no-leak-of-internals.window.html] - [ObservableArray's internals won't leak] - expected: FAIL