script: Implement CSSStyleSheet constructor (#36521)

https://drafts.csswg.org/cssom/#dom-cssstylesheet-cssstylesheet

Testing: covered by WPT
This is part of #36162

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Oriol Brufau 2025-04-15 07:05:13 -07:00 committed by GitHub
parent 372fd04b23
commit 10f6f50c61
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 127 additions and 38 deletions

View file

@ -125,6 +125,11 @@ impl CSSRuleList {
let loader = owner
.as_ref()
.map(|element| StylesheetLoader::for_element(element));
let allow_import_rules = if self.parent_stylesheet.is_constructed() {
AllowImportRules::No
} else {
AllowImportRules::Yes
};
let new_rule = css_rules
.insert_rule(
&parent_stylesheet.shared_lock,
@ -134,7 +139,7 @@ impl CSSRuleList {
containing_rule_types,
parse_relative_rule_type,
loader.as_ref().map(|l| l as &dyn StyleStylesheetLoader),
AllowImportRules::Yes,
allow_import_rules,
)
.map_err(Convert::convert)?;

View file

@ -5,14 +5,24 @@
use std::cell::Cell;
use dom_struct::dom_struct;
use js::rust::HandleObject;
use servo_arc::Arc;
use style::media_queries::MediaList as StyleMediaList;
use style::shared_lock::SharedRwLock;
use style::stylesheets::{CssRuleTypes, Stylesheet as StyleStyleSheet};
use style::stylesheets::{
AllowImportRules, CssRuleTypes, Origin, Stylesheet as StyleStyleSheet, UrlExtraData,
};
use crate::dom::bindings::codegen::Bindings::CSSStyleSheetBinding::CSSStyleSheetMethods;
use crate::dom::bindings::codegen::Bindings::CSSStyleSheetBinding::{
CSSStyleSheetInit, CSSStyleSheetMethods,
};
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::reflector::{DomGlobal, reflect_dom_object};
use crate::dom::bindings::reflector::{
DomGlobal, reflect_dom_object, reflect_dom_object_with_proto,
};
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::cssrulelist::{CSSRuleList, RulesSource};
@ -32,44 +42,82 @@ pub(crate) struct CSSStyleSheet {
#[no_trace]
style_stylesheet: Arc<StyleStyleSheet>,
origin_clean: Cell<bool>,
is_constructed: bool,
}
impl CSSStyleSheet {
fn new_inherited(
owner: &Element,
owner: Option<&Element>,
type_: DOMString,
href: Option<DOMString>,
title: Option<DOMString>,
stylesheet: Arc<StyleStyleSheet>,
is_constructed: bool,
) -> CSSStyleSheet {
CSSStyleSheet {
stylesheet: StyleSheet::new_inherited(type_, href, title),
owner: MutNullableDom::new(Some(owner)),
owner: MutNullableDom::new(owner),
rulelist: MutNullableDom::new(None),
style_stylesheet: stylesheet,
origin_clean: Cell::new(true),
is_constructed,
}
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
window: &Window,
owner: &Element,
owner: Option<&Element>,
type_: DOMString,
href: Option<DOMString>,
title: Option<DOMString>,
stylesheet: Arc<StyleStyleSheet>,
is_constructed: bool,
can_gc: CanGc,
) -> DomRoot<CSSStyleSheet> {
reflect_dom_object(
Box::new(CSSStyleSheet::new_inherited(
owner, type_, href, title, stylesheet,
owner,
type_,
href,
title,
stylesheet,
is_constructed,
)),
window,
can_gc,
)
}
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
#[allow(clippy::too_many_arguments)]
fn new_with_proto(
window: &Window,
proto: Option<HandleObject>,
owner: Option<&Element>,
type_: DOMString,
href: Option<DOMString>,
title: Option<DOMString>,
stylesheet: Arc<StyleStyleSheet>,
is_constructed: bool,
can_gc: CanGc,
) -> DomRoot<CSSStyleSheet> {
reflect_dom_object_with_proto(
Box::new(CSSStyleSheet::new_inherited(
owner,
type_,
href,
title,
stylesheet,
is_constructed,
)),
window,
proto,
can_gc,
)
}
fn rulelist(&self, can_gc: CanGc) -> DomRoot<CSSRuleList> {
self.rulelist.or_init(|| {
let rules = self.style_stylesheet.contents.rules.clone();
@ -123,9 +171,58 @@ impl CSSStyleSheet {
can_gc,
)
}
/// <https://drafts.csswg.org/cssom/#concept-css-style-sheet-constructed-flag>
#[inline]
pub(crate) fn is_constructed(&self) -> bool {
self.is_constructed
}
}
impl CSSStyleSheetMethods<crate::DomTypeHolder> for CSSStyleSheet {
/// <https://drafts.csswg.org/cssom/#dom-cssstylesheet-cssstylesheet>
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
options: &CSSStyleSheetInit,
) -> DomRoot<Self> {
let doc = window.Document();
let shared_lock = doc.style_shared_lock().clone();
let media = Arc::new(shared_lock.wrap(match &options.media {
Some(media) => match media {
MediaListOrString::MediaList(media_list) => media_list.clone_media_list(),
MediaListOrString::String(str) => MediaList::parse_media_list(str, window),
},
None => StyleMediaList::empty(),
}));
let stylesheet = Arc::new(StyleStyleSheet::from_str(
"",
UrlExtraData(window.get_url().get_arc()),
Origin::Author,
media,
shared_lock,
None,
window.css_error_reporter(),
doc.quirks_mode(),
AllowImportRules::No,
));
if options.disabled {
stylesheet.set_disabled(true);
}
Self::new_with_proto(
window,
proto,
None, // owner
"text/css".into(),
None, // href
None, // title
stylesheet,
true, // is_constructed
can_gc,
)
}
/// <https://drafts.csswg.org/cssom/#dom-cssstylesheet-cssrules>
fn GetCssRules(&self, can_gc: CanGc) -> Fallible<DomRoot<CSSRuleList>> {
if !self.origin_clean.get() {

View file

@ -182,11 +182,12 @@ impl HTMLLinkElement {
self.cssom_stylesheet.or_init(|| {
CSSStyleSheet::new(
&self.owner_window(),
self.upcast::<Element>(),
Some(self.upcast::<Element>()),
"text/css".into(),
None, // todo handle location
None, // todo handle title
sheet,
false, // is_constructed
can_gc,
)
})

View file

@ -151,11 +151,12 @@ impl HTMLStyleElement {
self.cssom_stylesheet.or_init(|| {
CSSStyleSheet::new(
&self.owner_window(),
self.upcast::<Element>(),
Some(self.upcast::<Element>()),
"text/css".into(),
None, // todo handle location
None, // todo handle title
sheet,
false, // is_constructed
CanGc::note(),
)
})

View file

@ -106,6 +106,11 @@ impl MediaList {
);
MediaQuery::parse(&context, &mut parser)
}
pub(crate) fn clone_media_list(&self) -> StyleMediaList {
let guard = self.shared_lock().read();
self.media_queries.read_with(&guard).clone()
}
}
impl MediaListMethods<crate::DomTypeHolder> for MediaList {

View file

@ -5,12 +5,20 @@
// https://drafts.csswg.org/cssom/#the-cssstylesheet-interface
[Exposed=Window]
interface CSSStyleSheet : StyleSheet {
constructor(optional CSSStyleSheetInit options = {});
// readonly attribute CSSRule? ownerRule;
[Throws, SameObject] readonly attribute CSSRuleList cssRules;
[Throws] unsigned long insertRule(DOMString rule, optional unsigned long index = 0);
[Throws] undefined deleteRule(unsigned long index);
};
dictionary CSSStyleSheetInit {
// DOMString baseURL = null;
(MediaList or DOMString) media;
boolean disabled = false;
};
// https://drafts.csswg.org/cssom/#legacy-css-style-sheet-members
partial interface CSSStyleSheet {
[Throws, SameObject] readonly attribute CSSRuleList rules;

View file

@ -26,9 +26,6 @@
[Attempting to insert a CSSNestedDeclaration rule into top-level @media rule]
expected: FAIL
[Attempting to insert a CSSNestedDeclaration rule into a stylesheet]
expected: FAIL
[Attempting to insert a CSSNestedDeclaration rule, empty block]
expected: FAIL

View file

@ -2,12 +2,8 @@
[@import rules are not parsed in CSSStyleSheet.replaceSync]
expected: FAIL
[Inserting an @import rule through insertRule on a constructed stylesheet throws an exception]
expected: FAIL
[@import rules are not parsed in CSSStyleSheet.replace]
expected: FAIL
[@import rules should not trigger any loads.]
expected: FAIL

View file

@ -5,9 +5,6 @@
[new CSSStyleSheet produces empty CSSStyleSheet]
expected: FAIL
[title can be set in the CSSStyleSheet constructor]
expected: FAIL
[CSSStyleSheet.replace produces Promise<CSSStyleSheet>]
expected: FAIL
@ -56,9 +53,6 @@
[Adopted sheets are ordered after non-adopted sheets in the document]
expected: FAIL
[Inserting an @import rule through insertRule on a constructed stylesheet throws an exception]
expected: FAIL
[CSSStyleSheet.replaceSync should not trigger any loads from @import rules]
expected: FAIL
@ -74,21 +68,12 @@
[CSSStyleSheet.replace does not reject on failed imports]
expected: FAIL
[Cloning a shadow host will not clone shadow root, and also adoptedStyleSheets]
expected: FAIL
[Importing a shadow host will not copy shadow root, and also adoptedStyleSheets]
expected: FAIL
[Adopting a shadow host will empty adoptedStyleSheets if adopting to a different document]
expected: FAIL
[Adopting a shadow host's ancestor will empty adoptedStyleSheets if adopting to a different document]
expected: FAIL
[Forcing a style update after adding an adopted stylesheet on a disconnected shadow root should not crash.]
expected: FAIL
[Modifying an adopted stylesheet on a disconnected shadow root should not crash.]
expected: FAIL

View file

@ -1,3 +0,0 @@
[CSSStyleSheet-modify-after-removal.html]
[Modify constructed sheet from removed iframe]
expected: FAIL

View file

@ -1,3 +0,0 @@
[insertRule-across-context.html]
[The constructor of inserted rule object must be from iframe for new CSSStyleSheet()]
expected: FAIL