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

View file

@ -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::<HTMLElement>);
let loader = owner
.as_ref()

View file

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

View file

@ -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<crate::DomTypeHolder> 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();
}
}
}

View file

@ -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<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>,
/// The inner Stylo's [Stylesheet].
#[ignore_malloc_size_of = "Arc"]
#[no_trace]
style_stylesheet: Arc<StyleStyleSheet>,
/// <https://drafts.csswg.org/cssom/#concept-css-style-sheet-origin-clean-flag>
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 {
@ -52,15 +72,16 @@ impl CSSStyleSheet {
href: Option<DOMString>,
title: Option<DOMString>,
stylesheet: Arc<StyleStyleSheet>,
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<DOMString>,
title: Option<DOMString>,
stylesheet: Arc<StyleStyleSheet>,
is_constructed: bool,
constructor_document: Option<&Document>,
can_gc: CanGc,
) -> DomRoot<CSSStyleSheet> {
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<DOMString>,
title: Option<DOMString>,
stylesheet: Arc<StyleStyleSheet>,
is_constructed: bool,
constructor_document: Option<&Document>,
can_gc: CanGc,
) -> DomRoot<CSSStyleSheet> {
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<DomRoot<Element>> {
self.owner.get()
pub(crate) fn owner_node(&self) -> Option<DomRoot<Element>> {
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<StyleStyleSheet> {
&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 {
/// <https://drafts.csswg.org/cssom/#concept-css-style-sheet-constructed-flag>
#[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<crate::DomTypeHolder> for CSSStyleSheet {
None, // href
None, // title
stylesheet,
true, // is_constructed
Some(&window.Document()), // constructor_document
can_gc,
)
}
@ -294,7 +348,7 @@ impl CSSStyleSheetMethods<crate::DomTypeHolder> for CSSStyleSheet {
/// <https://drafts.csswg.org/cssom/#synchronously-replace-the-rules-of-a-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<crate::DomTypeHolder> for CSSStyleSheet {
// at the next getter access.
self.rulelist.set(None);
// Notify invalidation to update the styles immediately.
self.notify_invalidations();
Ok(())
}
}

View file

@ -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<Modifiers>,
/// The node that is currently highlighted by the devtools
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)]
@ -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.
///
/// <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.
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();
// 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::<Node>().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::<Node>().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.
///
/// <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),
sheet,
insertion_point,
@ -6719,6 +6766,40 @@ impl DocumentMethods<crate::DomTypeHolder> for Document {
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>>) {

View file

@ -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<Element>),
// TODO(stevennovaryo): This type of enum would not be used until we implement adopted stylesheet
#[allow(dead_code)]
Constructed(Dom<CSSStyleSheet>),
}
@ -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.
///
/// <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.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<Arc<Stylesheet>> {
@ -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);
}

View file

@ -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<Arc<Stylesheet>> {
@ -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);
}

View file

@ -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 {
/// <https://dom.spec.whatwg.org/#shadowroot-delegates-focus>
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 {
@ -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<DomRoot<Element>> {
//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.
///
/// <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.
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;
// 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::<Node>().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::<Node>().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<crate::DomTypeHolder> for ShadowRoot {
// https://dom.spec.whatwg.org/#dom-shadowroot-onslotchange
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 {
@ -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);

View file

@ -51,7 +51,8 @@ impl StyleSheetMethods<crate::DomTypeHolder> for StyleSheet {
// https://drafts.csswg.org/cssom/#dom-stylesheet-ownernode
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

View file

@ -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<Document>),
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_stylesheet(&self, owner: StylesheetSource, sheet: Arc<Stylesheet>) {
pub(crate) fn add_owned_stylesheet(&self, owner_node: &Element, sheet: Arc<Stylesheet>) {
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)
},
}
}

View file

@ -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': {

View file

@ -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<CSSStyleSheet> */ any adoptedStyleSheets;
};