diff --git a/components/script/dom/cssstylesheet.rs b/components/script/dom/cssstylesheet.rs index 4aaefd2e09d..0e1110deff9 100644 --- a/components/script/dom/cssstylesheet.rs +++ b/components/script/dom/cssstylesheet.rs @@ -3,9 +3,11 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cell::Cell; +use std::rc::Rc; use dom_struct::dom_struct; use js::rust::HandleObject; +use script_bindings::realms::InRealm; use script_bindings::root::Dom; use servo_arc::Arc; use style::media_queries::MediaList as StyleMediaList; @@ -22,6 +24,7 @@ use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use crate::dom::bindings::codegen::GenericBindings::CSSRuleListBinding::CSSRuleList_Binding::CSSRuleListMethods; use crate::dom::bindings::codegen::UnionTypes::MediaListOrString; use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::{ DomGlobal, reflect_dom_object, reflect_dom_object_with_proto, }; @@ -34,8 +37,10 @@ use crate::dom::medialist::MediaList; use crate::dom::node::NodeTraits; use crate::dom::stylesheet::StyleSheet; use crate::dom::stylesheetlist::StyleSheetListOwner; +use crate::dom::types::Promise; use crate::dom::window::Window; use crate::script_runtime::CanGc; +use crate::test::TrustedPromise; #[dom_struct] pub(crate) struct CSSStyleSheet { @@ -60,6 +65,9 @@ pub(crate) struct CSSStyleSheet { /// constructor_document: Option>, + /// + disallow_modification: Cell, + /// Documents or shadow DOMs thats adopt this stylesheet, they will be notified whenever /// the stylesheet is modified. adopters: DomRefCell>, @@ -82,6 +90,7 @@ impl CSSStyleSheet { origin_clean: Cell::new(true), constructor_document: constructor_document.map(Dom::from_ref), adopters: Default::default(), + disallow_modification: Cell::new(false), } } @@ -231,6 +240,11 @@ impl CSSStyleSheet { adopter.invalidate_stylesheets(); } } + + /// + pub(crate) fn disallow_modification(&self) -> bool { + self.disallow_modification.get() + } } impl CSSStyleSheetMethods for CSSStyleSheet { @@ -287,18 +301,31 @@ impl CSSStyleSheetMethods for CSSStyleSheet { /// fn InsertRule(&self, rule: DOMString, index: u32, can_gc: CanGc) -> Fallible { + // Step 1. If the origin-clean flag is unset, throw a SecurityError exception. if !self.origin_clean.get() { return Err(Error::Security); } + + // Step 2. If the disallow modification flag is set, throw a NotAllowedError DOMException. + if self.disallow_modification() { + return Err(Error::NotAllowed); + } + self.rulelist(can_gc) .insert_rule(&rule, index, CssRuleTypes::default(), None, can_gc) } /// fn DeleteRule(&self, index: u32, can_gc: CanGc) -> ErrorResult { + // Step 1. If the origin-clean flag is unset, throw a SecurityError exception. if !self.origin_clean.get() { return Err(Error::Security); } + + // Step 2. If the disallow modification flag is set, throw a NotAllowedError DOMException. + if self.disallow_modification() { + return Err(Error::NotAllowed); + } self.rulelist(can_gc).remove_rule(index) } @@ -345,10 +372,68 @@ impl CSSStyleSheetMethods for CSSStyleSheet { Ok(-1) } - /// + /// + fn Replace(&self, text: USVString, comp: InRealm, can_gc: CanGc) -> Fallible> { + // Step 1. Let promise be a promise. + let promise = Promise::new_in_current_realm(comp, can_gc); + + // Step 2. If the constructed flag is not set, or the disallow modification flag is set, + // reject promise with a NotAllowedError DOMException and return promise. + if !self.is_constructed() || self.disallow_modification() { + return Err(Error::NotAllowed); + } + + // Step 3. Set the disallow modification flag. + self.disallow_modification.set(true); + + // Step 4. In parallel, do these steps: + let trusted_sheet = Trusted::new(self); + let trusted_promise = TrustedPromise::new(promise.clone()); + + self.global() + .task_manager() + .dom_manipulation_task_source() + .queue(task!(cssstylesheet_replace: move || { + let sheet = trusted_sheet.root(); + + // Step 4.1. Let rules be the result of running parse a stylesheet’s contents from text. + // Step 4.2. If rules contains one or more @import rules, remove those rules from rules. + // We are disallowing import rules in parsing + let global = sheet.global(); + let window = global.as_window(); + + StyleStyleSheet::update_from_str( + &sheet.style_stylesheet, + &text, + UrlExtraData(window.get_url().get_arc()), + None, + window.css_error_reporter(), + AllowImportRules::No, + ); + + // Step 4.3. Set sheet’s CSS rules to rules. + // We reset our rule list, which will be initialized properly + // at the next getter access. + sheet.rulelist.set(None); + + // Notify invalidation to update the styles immediately. + sheet.notify_invalidations(); + + // Step 4.4. Unset sheet’s disallow modification flag. + sheet.disallow_modification.set(false); + + // Step 4.5. Resolve promise with sheet. + trusted_promise.root().resolve_native(&sheet, CanGc::note()); + })); + + Ok(promise) + } + + /// fn ReplaceSync(&self, text: USVString) -> Result<(), Error> { - // Step 1. If the constructed flag is not set throw a NotAllowedError - if !self.is_constructed() { + // Step 1. If the constructed flag is not set, or the disallow modification flag is set, + // throw a NotAllowedError DOMException. + if !self.is_constructed() || self.disallow_modification() { return Err(Error::NotAllowed); } diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index 4823eab15f6..87486fcab45 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -137,7 +137,8 @@ DOMInterfaces = { }, 'CSSStyleSheet': { - 'canGc': ['AddRule', 'DeleteRule', 'GetCssRules', 'GetRules', 'InsertRule', 'RemoveRule'], + 'inRealms': ['Replace'], + 'canGc': ['AddRule', 'DeleteRule', 'GetCssRules', 'GetRules', 'InsertRule', 'RemoveRule', 'Replace'], }, 'Crypto': { diff --git a/components/script_bindings/webidls/CSSStyleSheet.webidl b/components/script_bindings/webidls/CSSStyleSheet.webidl index 302e7433300..1778eee2713 100644 --- a/components/script_bindings/webidls/CSSStyleSheet.webidl +++ b/components/script_bindings/webidls/CSSStyleSheet.webidl @@ -11,6 +11,8 @@ interface CSSStyleSheet : StyleSheet { [Throws, SameObject] readonly attribute CSSRuleList cssRules; [Throws] unsigned long insertRule(DOMString rule, optional unsigned long index = 0); [Throws] undefined deleteRule(unsigned long index); + + [Throws] Promise replace(USVString text); [Throws] undefined replaceSync(USVString text); }; diff --git a/tests/wpt/meta/css/cssom/CSSStyleSheet-constructable.html.ini b/tests/wpt/meta/css/cssom/CSSStyleSheet-constructable.html.ini index 2c23dd00540..44a4e8eb4e6 100644 --- a/tests/wpt/meta/css/cssom/CSSStyleSheet-constructable.html.ini +++ b/tests/wpt/meta/css/cssom/CSSStyleSheet-constructable.html.ini @@ -5,9 +5,6 @@ [CSSStyleSheet.replace produces Promise] expected: FAIL - [Constructed style sheets can be applied on document] - expected: FAIL - [Constructed style sheets can be applied on shadow root] expected: FAIL @@ -44,9 +41,6 @@ [CSSStyleSheet.replace ignores @import rule but still loads other rules] expected: FAIL - [CSSStyleSheet.replace does not reject on failed imports] - expected: FAIL - [Adopting a shadow host will empty adoptedStyleSheets if adopting to a different document] expected: FAIL diff --git a/tests/wpt/meta/css/cssom/idlharness.html.ini b/tests/wpt/meta/css/cssom/idlharness.html.ini index 498f103e531..744f5ec50bb 100644 --- a/tests/wpt/meta/css/cssom/idlharness.html.ini +++ b/tests/wpt/meta/css/cssom/idlharness.html.ini @@ -386,15 +386,6 @@ [MathMLElement interface: attribute style] expected: FAIL - [CSSStyleSheet interface: operation replace(USVString)] - expected: FAIL - - [CSSStyleSheet interface: sheet must inherit property "replace(USVString)" with the proper type] - expected: FAIL - - [CSSStyleSheet interface: calling replace(USVString) on sheet with too few arguments must throw TypeError] - expected: FAIL - [CSSImportRule interface: attribute supportsText] expected: FAIL