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