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 <jo.steven.novaryo@huawei.com>
This commit is contained in:
Jo Steven Novaryo 2025-07-23 16:16:01 +08:00 committed by GitHub
parent d2e5137201
commit f523445fc3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 434 additions and 139 deletions

View file

@ -69,6 +69,7 @@ pub struct Preferences {
/// List of comma-separated backends to be used by wgpu. /// List of comma-separated backends to be used by wgpu.
pub dom_webgpu_wgpu_backend: String, pub dom_webgpu_wgpu_backend: String,
pub dom_abort_controller_enabled: bool, pub dom_abort_controller_enabled: bool,
pub dom_adoptedstylesheet_enabled: bool,
pub dom_async_clipboard_enabled: bool, pub dom_async_clipboard_enabled: bool,
pub dom_bluetooth_enabled: bool, pub dom_bluetooth_enabled: bool,
pub dom_bluetooth_testing_enabled: bool, pub dom_bluetooth_testing_enabled: bool,
@ -247,6 +248,7 @@ impl Preferences {
devtools_server_enabled: false, devtools_server_enabled: false,
devtools_server_port: 0, devtools_server_port: 0,
dom_abort_controller_enabled: false, dom_abort_controller_enabled: false,
dom_adoptedstylesheet_enabled: false,
dom_allow_scripts_to_close_windows: false, dom_allow_scripts_to_close_windows: false,
dom_async_clipboard_enabled: false, dom_async_clipboard_enabled: false,
dom_bluetooth_enabled: false, dom_bluetooth_enabled: false,

View file

@ -120,7 +120,7 @@ impl CSSRuleList {
let parent_stylesheet = self.parent_stylesheet.style_stylesheet(); let parent_stylesheet = self.parent_stylesheet.style_stylesheet();
let owner = self let owner = self
.parent_stylesheet .parent_stylesheet
.get_owner() .owner_node()
.and_then(DomRoot::downcast::<HTMLElement>); .and_then(DomRoot::downcast::<HTMLElement>);
let loader = owner let loader = owner
.as_ref() .as_ref()

View file

@ -127,9 +127,7 @@ impl CSSStyleOwner {
if changed { if changed {
// If this is changed, see also // If this is changed, see also
// CSSStyleRule::SetSelectorText, which does the same thing. // CSSStyleRule::SetSelectorText, which does the same thing.
if let Some(owner) = rule.parent_stylesheet().get_owner() { rule.parent_stylesheet().notify_invalidations();
owner.stylesheet_list_owner().invalidate_stylesheets();
}
} }
result result
}, },

View file

@ -21,7 +21,6 @@ use crate::dom::cssgroupingrule::CSSGroupingRule;
use crate::dom::cssrule::SpecificCSSRule; use crate::dom::cssrule::SpecificCSSRule;
use crate::dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner}; use crate::dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner};
use crate::dom::cssstylesheet::CSSStyleSheet; use crate::dom::cssstylesheet::CSSStyleSheet;
use crate::dom::node::NodeTraits;
use crate::dom::window::Window; use crate::dom::window::Window;
use crate::script_runtime::CanGc; use crate::script_runtime::CanGc;
@ -136,9 +135,9 @@ impl CSSStyleRuleMethods<crate::DomTypeHolder> for CSSStyleRule {
let mut guard = self.cssgroupingrule.shared_lock().write(); let mut guard = self.cssgroupingrule.shared_lock().write();
let stylerule = self.stylerule.write_with(&mut guard); let stylerule = self.stylerule.write_with(&mut guard);
mem::swap(&mut stylerule.selectors, &mut s); mem::swap(&mut stylerule.selectors, &mut s);
if let Some(owner) = self.cssgroupingrule.parent_stylesheet().get_owner() { self.cssgroupingrule
owner.stylesheet_list_owner().invalidate_stylesheets(); .parent_stylesheet()
} .notify_invalidations();
} }
} }
} }

View file

@ -6,6 +6,7 @@ use std::cell::Cell;
use dom_struct::dom_struct; use dom_struct::dom_struct;
use js::rust::HandleObject; use js::rust::HandleObject;
use script_bindings::root::Dom;
use servo_arc::Arc; use servo_arc::Arc;
use style::media_queries::MediaList as StyleMediaList; use style::media_queries::MediaList as StyleMediaList;
use style::shared_lock::SharedRwLock; use style::shared_lock::SharedRwLock;
@ -13,6 +14,7 @@ use style::stylesheets::{
AllowImportRules, CssRuleTypes, Origin, Stylesheet as StyleStyleSheet, UrlExtraData, AllowImportRules, CssRuleTypes, Origin, Stylesheet as StyleStyleSheet, UrlExtraData,
}; };
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::CSSStyleSheetBinding::{ use crate::dom::bindings::codegen::Bindings::CSSStyleSheetBinding::{
CSSStyleSheetInit, CSSStyleSheetMethods, CSSStyleSheetInit, CSSStyleSheetMethods,
}; };
@ -26,23 +28,41 @@ use crate::dom::bindings::reflector::{
use crate::dom::bindings::root::{DomRoot, MutNullableDom}; use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::cssrulelist::{CSSRuleList, RulesSource}; use crate::dom::cssrulelist::{CSSRuleList, RulesSource};
use crate::dom::document::Document;
use crate::dom::element::Element; use crate::dom::element::Element;
use crate::dom::medialist::MediaList; use crate::dom::medialist::MediaList;
use crate::dom::node::NodeTraits; use crate::dom::node::NodeTraits;
use crate::dom::stylesheet::StyleSheet; use crate::dom::stylesheet::StyleSheet;
use crate::dom::stylesheetlist::StyleSheetListOwner;
use crate::dom::window::Window; use crate::dom::window::Window;
use crate::script_runtime::CanGc; use crate::script_runtime::CanGc;
#[dom_struct] #[dom_struct]
pub(crate) struct CSSStyleSheet { pub(crate) struct CSSStyleSheet {
stylesheet: StyleSheet, stylesheet: StyleSheet,
owner: MutNullableDom<Element>,
/// <https://drafts.csswg.org/cssom/#concept-css-style-sheet-owner-node>
owner_node: MutNullableDom<Element>,
/// <https://drafts.csswg.org/cssom/#ref-for-concept-css-style-sheet-css-rules>
rulelist: MutNullableDom<CSSRuleList>, rulelist: MutNullableDom<CSSRuleList>,
/// The inner Stylo's [Stylesheet].
#[ignore_malloc_size_of = "Arc"] #[ignore_malloc_size_of = "Arc"]
#[no_trace] #[no_trace]
style_stylesheet: Arc<StyleStyleSheet>, style_stylesheet: Arc<StyleStyleSheet>,
/// <https://drafts.csswg.org/cssom/#concept-css-style-sheet-origin-clean-flag>
origin_clean: Cell<bool>, origin_clean: Cell<bool>,
is_constructed: bool,
/// In which [Document] that this stylesheet was constructed.
///
/// <https://drafts.csswg.org/cssom/#concept-css-style-sheet-constructor-document>
constructor_document: Option<Dom<Document>>,
/// Documents or shadow DOMs thats adopt this stylesheet, they will be notified whenever
/// the stylesheet is modified.
adopters: DomRefCell<Vec<StyleSheetListOwner>>,
} }
impl CSSStyleSheet { impl CSSStyleSheet {
@ -52,15 +72,16 @@ impl CSSStyleSheet {
href: Option<DOMString>, href: Option<DOMString>,
title: Option<DOMString>, title: Option<DOMString>,
stylesheet: Arc<StyleStyleSheet>, stylesheet: Arc<StyleStyleSheet>,
is_constructed: bool, constructor_document: Option<&Document>,
) -> CSSStyleSheet { ) -> CSSStyleSheet {
CSSStyleSheet { CSSStyleSheet {
stylesheet: StyleSheet::new_inherited(type_, href, title), stylesheet: StyleSheet::new_inherited(type_, href, title),
owner: MutNullableDom::new(owner), owner_node: MutNullableDom::new(owner),
rulelist: MutNullableDom::new(None), rulelist: MutNullableDom::new(None),
style_stylesheet: stylesheet, style_stylesheet: stylesheet,
origin_clean: Cell::new(true), 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<DOMString>, href: Option<DOMString>,
title: Option<DOMString>, title: Option<DOMString>,
stylesheet: Arc<StyleStyleSheet>, stylesheet: Arc<StyleStyleSheet>,
is_constructed: bool, constructor_document: Option<&Document>,
can_gc: CanGc, can_gc: CanGc,
) -> DomRoot<CSSStyleSheet> { ) -> DomRoot<CSSStyleSheet> {
reflect_dom_object( reflect_dom_object(
@ -83,7 +104,7 @@ impl CSSStyleSheet {
href, href,
title, title,
stylesheet, stylesheet,
is_constructed, constructor_document,
)), )),
window, window,
can_gc, can_gc,
@ -100,7 +121,7 @@ impl CSSStyleSheet {
href: Option<DOMString>, href: Option<DOMString>,
title: Option<DOMString>, title: Option<DOMString>,
stylesheet: Arc<StyleStyleSheet>, stylesheet: Arc<StyleStyleSheet>,
is_constructed: bool, constructor_document: Option<&Document>,
can_gc: CanGc, can_gc: CanGc,
) -> DomRoot<CSSStyleSheet> { ) -> DomRoot<CSSStyleSheet> {
reflect_dom_object_with_proto( reflect_dom_object_with_proto(
@ -110,7 +131,7 @@ impl CSSStyleSheet {
href, href,
title, title,
stylesheet, stylesheet,
is_constructed, constructor_document,
)), )),
window, window,
proto, proto,
@ -134,21 +155,18 @@ impl CSSStyleSheet {
self.style_stylesheet.disabled() self.style_stylesheet.disabled()
} }
pub(crate) fn get_owner(&self) -> Option<DomRoot<Element>> { pub(crate) fn owner_node(&self) -> Option<DomRoot<Element>> {
self.owner.get() self.owner_node.get()
} }
pub(crate) fn set_disabled(&self, disabled: bool) { pub(crate) fn set_disabled(&self, disabled: bool) {
if self.style_stylesheet.set_disabled(disabled) && self.get_owner().is_some() { if self.style_stylesheet.set_disabled(disabled) {
self.get_owner() self.notify_invalidations();
.unwrap()
.stylesheet_list_owner()
.invalidate_stylesheets();
} }
} }
pub(crate) fn set_owner(&self, value: Option<&Element>) { pub(crate) fn set_owner_node(&self, value: Option<&Element>) {
self.owner.set(value); self.owner_node.set(value);
} }
pub(crate) fn shared_lock(&self) -> &SharedRwLock { pub(crate) fn shared_lock(&self) -> &SharedRwLock {
@ -159,6 +177,10 @@ impl CSSStyleSheet {
&self.style_stylesheet &self.style_stylesheet
} }
pub(crate) fn style_stylesheet_arc(&self) -> &Arc<StyleStyleSheet> {
&self.style_stylesheet
}
pub(crate) fn set_origin_clean(&self, origin_clean: bool) { pub(crate) fn set_origin_clean(&self, origin_clean: bool) {
self.origin_clean.set(origin_clean); self.origin_clean.set(origin_clean);
} }
@ -175,7 +197,39 @@ impl CSSStyleSheet {
/// <https://drafts.csswg.org/cssom/#concept-css-style-sheet-constructed-flag> /// <https://drafts.csswg.org/cssom/#concept-css-style-sheet-constructed-flag>
#[inline] #[inline]
pub(crate) fn is_constructed(&self) -> bool { 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<crate::DomTypeHolder> for CSSStyleSheet {
None, // href None, // href
None, // title None, // title
stylesheet, stylesheet,
true, // is_constructed Some(&window.Document()), // constructor_document
can_gc, can_gc,
) )
} }
@ -294,7 +348,7 @@ impl CSSStyleSheetMethods<crate::DomTypeHolder> for CSSStyleSheet {
/// <https://drafts.csswg.org/cssom/#synchronously-replace-the-rules-of-a-cssstylesheet> /// <https://drafts.csswg.org/cssom/#synchronously-replace-the-rules-of-a-cssstylesheet>
fn ReplaceSync(&self, text: USVString) -> Result<(), Error> { fn ReplaceSync(&self, text: USVString) -> Result<(), Error> {
// Step 1. If the constructed flag is not set throw a NotAllowedError // Step 1. If the constructed flag is not set throw a NotAllowedError
if !self.is_constructed { if !self.is_constructed() {
return Err(Error::NotAllowed); return Err(Error::NotAllowed);
} }
@ -316,6 +370,9 @@ impl CSSStyleSheetMethods<crate::DomTypeHolder> for CSSStyleSheet {
// at the next getter access. // at the next getter access.
self.rulelist.set(None); self.rulelist.set(None);
// Notify invalidation to update the styles immediately.
self.notify_invalidations();
Ok(()) Ok(())
} }
} }

View file

@ -40,7 +40,7 @@ use fnv::FnvHashMap;
use html5ever::{LocalName, Namespace, QualName, local_name, ns}; use html5ever::{LocalName, Namespace, QualName, local_name, ns};
use hyper_serde::Serde; use hyper_serde::Serde;
use ipc_channel::ipc; use ipc_channel::ipc;
use js::rust::{HandleObject, HandleValue}; use js::rust::{HandleObject, HandleValue, MutableHandleValue};
use keyboard_types::{Code, Key, KeyState, Modifiers}; use keyboard_types::{Code, Key, KeyState, Modifiers};
use layout_api::{ use layout_api::{
PendingRestyle, ReflowGoal, RestyleReason, TrustedNodeAddress, node_id_from_scroll_id, 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 profile_traits::time::TimerMetadataFrameType;
use regex::bytes::Regex; use regex::bytes::Regex;
use script_bindings::interfaces::DocumentHelpers; use script_bindings::interfaces::DocumentHelpers;
use script_bindings::script_runtime::JSContext;
use script_traits::{ConstellationInputEvent, DocumentActivity, ProgressiveWebMetricType}; use script_traits::{ConstellationInputEvent, DocumentActivity, ProgressiveWebMetricType};
use servo_arc::Arc; use servo_arc::Arc;
use servo_config::pref; 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, 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::error::{Error, ErrorInfo, ErrorResult, Fallible};
use crate::dom::bindings::frozenarray::CachedFrozenArray;
use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
use crate::dom::bindings::num::Finite; use crate::dom::bindings::num::Finite;
use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
@ -564,6 +566,12 @@ pub(crate) struct Document {
active_keyboard_modifiers: Cell<Modifiers>, active_keyboard_modifiers: Cell<Modifiers>,
/// The node that is currently highlighted by the devtools /// The node that is currently highlighted by the devtools
highlighted_dom_node: MutNullableDom<Node>, highlighted_dom_node: MutNullableDom<Node>,
/// The constructed stylesheet that is adopted by this [Document].
/// <https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets>
adopted_stylesheets: DomRefCell<Vec<Dom<CSSStyleSheet>>>,
/// Cached frozen array of [`Self::adopted_stylesheets`]
#[ignore_malloc_size_of = "mozjs"]
adopted_stylesheets_frozen_types: CachedFrozenArray,
} }
#[allow(non_snake_case)] #[allow(non_snake_case)]
@ -4253,6 +4261,8 @@ impl Document {
intersection_observers: Default::default(), intersection_observers: Default::default(),
active_keyboard_modifiers: Cell::new(Modifiers::empty()), active_keyboard_modifiers: Cell::new(Modifiers::empty()),
highlighted_dom_node: Default::default(), 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()) .and_then(|s| s.owner.get_cssom_object())
} }
/// Add a stylesheet owned by `owner` to the list of document sheets, in the /// Add a stylesheet owned by `owner_node` to the list of document sheets, in the
/// correct tree position. /// correct tree position. Additionally, ensure that the owned stylesheet is inserted
/// before any constructed stylesheet.
///
/// <https://drafts.csswg.org/cssom/#documentorshadowroot-final-css-style-sheets>
#[cfg_attr(crown, allow(crown::unrooted_must_root))] // Owner needs to be rooted already necessarily. #[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<Stylesheet>) { pub(crate) fn add_owned_stylesheet(&self, owner_node: &Element, sheet: Arc<Stylesheet>) {
let stylesheets = &mut *self.stylesheets.borrow_mut(); let stylesheets = &mut *self.stylesheets.borrow_mut();
// TODO(stevennovayo): support constructed stylesheet for adopted stylesheet and its ordering // FIXME(stevennovaryo): This is almost identical with the one in ShadowRoot::add_stylesheet.
let insertion_point = match &owner { let insertion_point = stylesheets
StylesheetSource::Element(owner_elem) => stylesheets .iter()
.iter() .map(|(sheet, _origin)| sheet)
.map(|(sheet, _origin)| sheet) .find(|sheet_in_doc| {
.find(|sheet_in_doc| match sheet_in_doc.owner { match &sheet_in_doc.owner {
StylesheetSource::Element(ref other_elem) => { StylesheetSource::Element(other_node) => {
owner_elem.upcast::<Node>().is_before(other_elem.upcast()) owner_node.upcast::<Node>().is_before(other_node.upcast())
}, },
StylesheetSource::Constructed(_) => unreachable!(), // Non-constructed stylesheet should be ordered before the
}) // constructed ones.
.cloned(), StylesheetSource::Constructed(_) => true,
StylesheetSource::Constructed(_) => unreachable!(), }
}; })
.cloned();
if self.has_browsing_context() { if self.has_browsing_context() {
self.window.layout_mut().add_stylesheet( self.window.layout_mut().add_stylesheet(
@ -4927,7 +4941,40 @@ impl Document {
} }
DocumentOrShadowRoot::add_stylesheet( 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.
///
/// <https://drafts.csswg.org/cssom/#documentorshadowroot-final-css-style-sheets>
#[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), StylesheetSetRef::Document(stylesheets),
sheet, sheet,
insertion_point, insertion_point,
@ -6719,6 +6766,40 @@ impl DocumentMethods<crate::DomTypeHolder> for Document {
can_gc, can_gc,
) )
} }
/// <https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets>
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,
);
}
/// <https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets>
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<Option<CrossProcessInstant>>) { fn update_with_current_instant(marker: &Cell<Option<CrossProcessInstant>>) {

View file

@ -2,12 +2,17 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::collections::HashSet;
use std::fmt; use std::fmt;
use embedder_traits::UntrustedNodeAddress; use embedder_traits::UntrustedNodeAddress;
use euclid::default::Point2D; use euclid::default::Point2D;
use js::rust::HandleValue;
use layout_api::{NodesFromPointQueryType, QueryMsg}; use layout_api::{NodesFromPointQueryType, QueryMsg};
use script_bindings::error::{Error, ErrorResult};
use script_bindings::script_runtime::JSContext;
use servo_arc::Arc; use servo_arc::Arc;
use servo_config::pref;
use style::invalidation::media_queries::{MediaListKey, ToMediaListKey}; use style::invalidation::media_queries::{MediaListKey, ToMediaListKey};
use style::media_queries::MediaList; use style::media_queries::MediaList;
use style::shared_lock::{SharedRwLock as StyleSharedRwLock, SharedRwLockReadGuard}; 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::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRootMethods; 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::inheritance::Castable;
use crate::dom::bindings::num::Finite; use crate::dom::bindings::num::Finite;
use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::root::{Dom, DomRoot};
@ -26,6 +32,7 @@ use crate::dom::element::Element;
use crate::dom::htmlelement::HTMLElement; use crate::dom::htmlelement::HTMLElement;
use crate::dom::node::{self, Node, VecPreOrderInsertionHelper}; use crate::dom::node::{self, Node, VecPreOrderInsertionHelper};
use crate::dom::shadowroot::ShadowRoot; use crate::dom::shadowroot::ShadowRoot;
use crate::dom::stylesheetlist::StyleSheetListOwner;
use crate::dom::types::CSSStyleSheet; use crate::dom::types::CSSStyleSheet;
use crate::dom::window::Window; use crate::dom::window::Window;
use crate::script_runtime::CanGc; use crate::script_runtime::CanGc;
@ -37,8 +44,6 @@ use crate::stylesheet_set::StylesheetSetRef;
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub(crate) enum StylesheetSource { pub(crate) enum StylesheetSource {
Element(Dom<Element>), Element(Dom<Element>),
// TODO(stevennovaryo): This type of enum would not be used until we implement adopted stylesheet
#[allow(dead_code)]
Constructed(Dom<CSSStyleSheet>), Constructed(Dom<CSSStyleSheet>),
} }
@ -56,6 +61,10 @@ impl StylesheetSource {
StylesheetSource::Constructed(ss) => ss.is_constructed(), StylesheetSource::Constructed(ss) => ss.is_constructed(),
} }
} }
pub(crate) fn is_constructed(&self) -> bool {
matches!(self, StylesheetSource::Constructed(_))
}
} }
#[derive(Clone, JSTraceable, MallocSizeOf)] #[derive(Clone, JSTraceable, MallocSizeOf)]
@ -285,6 +294,10 @@ impl DocumentOrShadowRoot {
) { ) {
debug_assert!(owner.is_a_valid_owner(), "Wat"); debug_assert!(owner.is_a_valid_owner(), "Wat");
if owner.is_constructed() && !pref!(dom_adoptedstylesheet_enabled) {
return;
}
let sheet = ServoStylesheetInDocument { sheet, owner }; let sheet = ServoStylesheetInDocument { sheet, owner };
let guard = style_shared_lock.read(); let guard = style_shared_lock.read();
@ -345,4 +358,111 @@ impl DocumentOrShadowRoot {
let elements = id_map.entry(id.clone()).or_default(); let elements = id_map.entry(id.clone()).or_default();
elements.insert_pre_order(element, &root); 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.
///
/// <https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets>
// 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<Dom<CSSStyleSheet>>,
incoming_stylesheets: &[Dom<CSSStyleSheet>],
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 values constructed flag is not set, or its constructor document is not equal
// > to this DocumentOrShadowRoots 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<Dom<CSSStyleSheet>>,
incoming_value: HandleValue,
owner: &StyleSheetListOwner,
) -> ErrorResult {
let maybe_stylesheets =
Vec::<DomRoot<CSSStyleSheet>>::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(),
)),
}
}
} }

View file

@ -185,8 +185,7 @@ impl HTMLLinkElement {
} }
*self.stylesheet.borrow_mut() = Some(s.clone()); *self.stylesheet.borrow_mut() = Some(s.clone());
self.clean_stylesheet_ownership(); self.clean_stylesheet_ownership();
stylesheets_owner stylesheets_owner.add_owned_stylesheet(self.upcast(), s);
.add_stylesheet(StylesheetSource::Element(Dom::from_ref(self.upcast())), s);
} }
pub(crate) fn get_stylesheet(&self) -> Option<Arc<Stylesheet>> { pub(crate) fn get_stylesheet(&self) -> Option<Arc<Stylesheet>> {
@ -203,7 +202,7 @@ impl HTMLLinkElement {
None, // todo handle location None, // todo handle location
None, // todo handle title None, // todo handle title
sheet, sheet,
false, // is_constructed None, // constructor_document
can_gc, can_gc,
) )
}) })
@ -222,7 +221,7 @@ impl HTMLLinkElement {
fn clean_stylesheet_ownership(&self) { fn clean_stylesheet_ownership(&self) {
if let Some(cssom_stylesheet) = self.cssom_stylesheet.get() { 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); self.cssom_stylesheet.set(None);
} }

View file

@ -160,8 +160,7 @@ impl HTMLStyleElement {
} }
*self.stylesheet.borrow_mut() = Some(s.clone()); *self.stylesheet.borrow_mut() = Some(s.clone());
self.clean_stylesheet_ownership(); self.clean_stylesheet_ownership();
stylesheets_owner stylesheets_owner.add_owned_stylesheet(self.upcast(), s);
.add_stylesheet(StylesheetSource::Element(Dom::from_ref(self.upcast())), s);
} }
pub(crate) fn get_stylesheet(&self) -> Option<Arc<Stylesheet>> { pub(crate) fn get_stylesheet(&self) -> Option<Arc<Stylesheet>> {
@ -178,7 +177,7 @@ impl HTMLStyleElement {
None, // todo handle location None, // todo handle location
None, // todo handle title None, // todo handle title
sheet, sheet,
false, // is_constructed None, // constructor_document
CanGc::note(), CanGc::note(),
) )
}) })
@ -187,7 +186,7 @@ impl HTMLStyleElement {
fn clean_stylesheet_ownership(&self) { fn clean_stylesheet_ownership(&self) {
if let Some(cssom_stylesheet) = self.cssom_stylesheet.get() { 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); self.cssom_stylesheet.set(None);
} }

View file

@ -8,6 +8,9 @@ use std::collections::hash_map::Entry;
use dom_struct::dom_struct; use dom_struct::dom_struct;
use html5ever::serialize::TraversalScope; 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 servo_arc::Arc;
use style::author_styles::AuthorStyles; use style::author_styles::AuthorStyles;
use style::dom::TElement; use style::dom::TElement;
@ -24,6 +27,7 @@ use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Bindi
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{ use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{
ShadowRootMode, SlotAssignmentMode, ShadowRootMode, SlotAssignmentMode,
}; };
use crate::dom::bindings::frozenarray::CachedFrozenArray;
use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::num::Finite; use crate::dom::bindings::num::Finite;
use crate::dom::bindings::reflector::reflect_dom_object; use crate::dom::bindings::reflector::reflect_dom_object;
@ -92,6 +96,14 @@ pub(crate) struct ShadowRoot {
/// <https://dom.spec.whatwg.org/#shadowroot-delegates-focus> /// <https://dom.spec.whatwg.org/#shadowroot-delegates-focus>
delegates_focus: Cell<bool>, delegates_focus: Cell<bool>,
/// The constructed stylesheet that is adopted by this [ShadowRoot].
/// <https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets>
adopted_stylesheets: DomRefCell<Vec<Dom<CSSStyleSheet>>>,
/// Cached frozen array of [`Self::adopted_stylesheets`]
#[ignore_malloc_size_of = "mozjs"]
adopted_stylesheets_frozen_types: CachedFrozenArray,
} }
impl ShadowRoot { impl ShadowRoot {
@ -129,6 +141,8 @@ impl ShadowRoot {
declarative: Cell::new(false), declarative: Cell::new(false),
serializable: Cell::new(false), serializable: Cell::new(false),
delegates_focus: 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); self.host.set(None);
} }
pub(crate) fn owner_doc(&self) -> &Document {
&self.document
}
pub(crate) fn get_focused_element(&self) -> Option<DomRoot<Element>> { pub(crate) fn get_focused_element(&self) -> Option<DomRoot<Element>> {
//XXX get retargeted focused element //XXX get retargeted focused element
None None
@ -180,28 +198,51 @@ impl ShadowRoot {
.and_then(|s| s.owner.get_cssom_object()) .and_then(|s| s.owner.get_cssom_object())
} }
/// Add a stylesheet owned by `owner` to the list of shadow root sheets, in the /// Add a stylesheet owned by `owner_node` to the list of shadow root sheets, in the
/// correct tree position. /// correct tree position. Additionally, ensure that owned stylesheet is inserted before
/// any constructed stylesheet.
///
/// <https://drafts.csswg.org/cssom/#documentorshadowroot-final-css-style-sheets>
#[cfg_attr(crown, allow(crown::unrooted_must_root))] // Owner needs to be rooted already necessarily. #[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<Stylesheet>) { pub(crate) fn add_owned_stylesheet(&self, owner_node: &Element, sheet: Arc<Stylesheet>) {
let stylesheets = &mut self.author_styles.borrow_mut().stylesheets; let stylesheets = &mut self.author_styles.borrow_mut().stylesheets;
// TODO(stevennovayo): support constructed stylesheet for adopted stylesheet and its ordering // FIXME(stevennovaryo): This is almost identical with the one in Document::add_stylesheet.
let insertion_point = match &owner { let insertion_point = stylesheets
StylesheetSource::Element(owner_elem) => stylesheets .iter()
.iter() .find(|sheet_in_shadow| {
.find(|sheet_in_shadow| match sheet_in_shadow.owner { match &sheet_in_shadow.owner {
StylesheetSource::Element(ref other_elem) => { StylesheetSource::Element(other_node) => {
owner_elem.upcast::<Node>().is_before(other_elem.upcast()) owner_node.upcast::<Node>().is_before(other_node.upcast())
}, },
StylesheetSource::Constructed(_) => unreachable!(), // Non-constructed stylesheet should be ordered before the
}) // constructed ones.
.cloned(), StylesheetSource::Constructed(_) => true,
StylesheetSource::Constructed(_) => unreachable!(), }
}; })
.cloned();
DocumentOrShadowRoot::add_stylesheet( 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), StylesheetSetRef::Author(stylesheets),
sheet, sheet,
insertion_point, insertion_point,
@ -473,6 +514,40 @@ impl ShadowRootMethods<crate::DomTypeHolder> for ShadowRoot {
// https://dom.spec.whatwg.org/#dom-shadowroot-onslotchange // https://dom.spec.whatwg.org/#dom-shadowroot-onslotchange
event_handler!(onslotchange, GetOnslotchange, SetOnslotchange); event_handler!(onslotchange, GetOnslotchange, SetOnslotchange);
/// <https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets>
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,
);
}
/// <https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets>
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 { impl VirtualMethods for ShadowRoot {
@ -485,6 +560,8 @@ impl VirtualMethods for ShadowRoot {
s.bind_to_tree(context, can_gc); s.bind_to_tree(context, can_gc);
} }
// TODO(stevennovaryo): Handle adoptedStylesheet to deal with different
// constructor document.
if context.tree_connected { if context.tree_connected {
let document = self.owner_document(); let document = self.owner_document();
document.register_shadow_root(self); document.register_shadow_root(self);

View file

@ -51,7 +51,8 @@ impl StyleSheetMethods<crate::DomTypeHolder> for StyleSheet {
// https://drafts.csswg.org/cssom/#dom-stylesheet-ownernode // https://drafts.csswg.org/cssom/#dom-stylesheet-ownernode
fn GetOwnerNode(&self) -> Option<DomRoot<Element>> { fn GetOwnerNode(&self) -> Option<DomRoot<Element>> {
self.downcast::<CSSStyleSheet>().and_then(|s| s.get_owner()) self.downcast::<CSSStyleSheet>()
.and_then(|s| s.owner_node())
} }
// https://drafts.csswg.org/cssom/#dom-stylesheet-media // https://drafts.csswg.org/cssom/#dom-stylesheet-media

View file

@ -12,13 +12,14 @@ use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::cssstylesheet::CSSStyleSheet; use crate::dom::cssstylesheet::CSSStyleSheet;
use crate::dom::document::Document; use crate::dom::document::Document;
use crate::dom::documentorshadowroot::StylesheetSource; use crate::dom::documentorshadowroot::StylesheetSource;
use crate::dom::element::Element;
use crate::dom::shadowroot::ShadowRoot; use crate::dom::shadowroot::ShadowRoot;
use crate::dom::stylesheet::StyleSheet; use crate::dom::stylesheet::StyleSheet;
use crate::dom::window::Window; use crate::dom::window::Window;
use crate::script_runtime::CanGc; use crate::script_runtime::CanGc;
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
#[derive(JSTraceable, MallocSizeOf)] #[derive(Clone, JSTraceable, MallocSizeOf, PartialEq)]
pub(crate) enum StyleSheetListOwner { pub(crate) enum StyleSheetListOwner {
Document(Dom<Document>), Document(Dom<Document>),
ShadowRoot(Dom<ShadowRoot>), ShadowRoot(Dom<ShadowRoot>),
@ -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_owned_stylesheet(&self, owner_node: &Element, sheet: Arc<Stylesheet>) {
pub(crate) fn add_stylesheet(&self, owner: StylesheetSource, sheet: Arc<Stylesheet>) {
match *self { 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) => { 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)
}, },
} }
} }

View file

@ -167,7 +167,7 @@ DOMInterfaces = {
'Document': { 'Document': {
'additionalTraits': ["crate::interfaces::DocumentHelpers"], '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': { 'DissimilarOriginWindow': {
@ -586,7 +586,7 @@ DOMInterfaces = {
}, },
'ShadowRoot': { 'ShadowRoot': {
'canGc': ['SetHTMLUnsafe', 'ElementFromPoint', 'ElementsFromPoint', 'SetInnerHTML', 'GetHTML', 'InnerHTML'], 'canGc': ['SetHTMLUnsafe', 'ElementFromPoint', 'ElementsFromPoint', 'SetInnerHTML', 'GetHTML', 'InnerHTML', 'AdoptedStyleSheets'],
}, },
'StaticRange': { 'StaticRange': {

View file

@ -15,3 +15,9 @@ interface mixin DocumentOrShadowRoot {
readonly attribute Element? activeElement; readonly attribute Element? activeElement;
readonly attribute StyleSheetList styleSheets; readonly attribute StyleSheetList styleSheets;
}; };
partial interface mixin DocumentOrShadowRoot {
// TODO(37902): Use ObservableArray Array when available
[Pref="dom_adoptedstylesheet_enabled", SetterThrows]
attribute /* ObservableArray<CSSStyleSheet> */ any adoptedStyleSheets;
};

View file

@ -1,4 +1,5 @@
{ {
"dom_adoptedstylesheet_enabled": true,
"dom_webxr_test": true, "dom_webxr_test": true,
"gfx_text_antialiasing_enabled": false, "gfx_text_antialiasing_enabled": false,
"dom_testutils_enabled": true "dom_testutils_enabled": true

View file

@ -1,4 +1,5 @@
prefs: [ prefs: [
"dom_adoptedstylesheet_enabled:true",
"dom_indexeddb_enabled:true", "dom_indexeddb_enabled:true",
"dom_serviceworker_enabled:true", "dom_serviceworker_enabled:true",
"dom_testutils_enabled:true", "dom_testutils_enabled:true",

View file

@ -1,3 +0,0 @@
[layer-replaceSync-clears-stale.html]
[replaceSync clears stale layer statements]
expected: FAIL

View file

@ -1,4 +0,0 @@
[CSSStyleSheet-constructable-concat.html]
expected: TIMEOUT
[adoptedStyleSheets should allow .concat on empty starting values]
expected: FAIL

View file

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

View file

@ -1,7 +1,4 @@
[CSSStyleSheet-constructable.html] [CSSStyleSheet-constructable.html]
[document.adoptedStyleSheets should initially have length 0.]
expected: FAIL
[new CSSStyleSheet produces empty CSSStyleSheet] [new CSSStyleSheet produces empty CSSStyleSheet]
expected: FAIL expected: FAIL
@ -17,12 +14,6 @@
[Re-attaching shadow host with adopted stylesheets work] [Re-attaching shadow host with adopted stylesheets work]
expected: FAIL 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] [Changes to constructed stylesheets through CSSOM is reflected]
expected: FAIL expected: FAIL
@ -35,12 +26,6 @@
[Stylesheet constructed on iframe cannot be used in the main Document] [Stylesheet constructed on iframe cannot be used in the main Document]
expected: FAIL 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] [CSSStyleSheet.replaceSync correctly updates the style of its adopters synchronously]
expected: FAIL expected: FAIL

View file

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

View file

@ -4,6 +4,3 @@
[shadowRoot.adoptedStyleSheets should allow mutation in-place] [shadowRoot.adoptedStyleSheets should allow mutation in-place]
expected: FAIL expected: FAIL
[adoptedStyleSheets should return true for isArray()]
expected: FAIL

View file

@ -395,18 +395,6 @@
[CSSStyleSheet interface: calling replace(USVString) on sheet with too few arguments must throw TypeError] [CSSStyleSheet interface: calling replace(USVString) on sheet with too few arguments must throw TypeError]
expected: FAIL 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] [CSSImportRule interface: attribute supportsText]
expected: FAIL expected: FAIL

View file

@ -1,3 +0,0 @@
[observable-array-no-leak-of-internals.window.html]
[ObservableArray's internals won't leak]
expected: FAIL