1use std::cell::{Cell, Ref};
6
7use dom_struct::dom_struct;
8use js::context::{JSContext, NoGC};
9use js::realm::CurrentRealm;
10use js::rust::HandleObject;
11use script_bindings::cell::DomRefCell;
12use script_bindings::codegen::GenericBindings::StyleSheetBinding::StyleSheetMethods;
13use script_bindings::inheritance::Castable;
14use script_bindings::reflector::{reflect_dom_object, reflect_dom_object_with_proto};
15use script_bindings::root::Dom;
16use servo_arc::Arc;
17use servo_url::ServoUrl;
18use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard};
19use style::stylesheets::{
20 AllowImportRules, Origin, Stylesheet as StyleStyleSheet, StylesheetContents,
21 StylesheetInDocument, UrlExtraData,
22};
23
24use super::cssrulelist::{CSSRuleList, RulesSource};
25use super::stylesheet::StyleSheet;
26use super::stylesheetlist::StyleSheetListOwner;
27use crate::dom::bindings::codegen::Bindings::CSSStyleSheetBinding::{
28 CSSStyleSheetInit, CSSStyleSheetMethods,
29};
30use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
31use crate::dom::bindings::codegen::GenericBindings::CSSRuleListBinding::CSSRuleList_Binding::CSSRuleListMethods;
32use crate::dom::bindings::codegen::UnionTypes::MediaListOrString;
33use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
34use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
35use crate::dom::bindings::reflector::DomGlobal;
36use crate::dom::bindings::root::{DomRoot, MutNullableDom};
37use crate::dom::bindings::str::{DOMString, USVString};
38use crate::dom::document::Document;
39use crate::dom::element::Element;
40use crate::dom::html::htmlstyleelement::HTMLStyleElement;
41use crate::dom::medialist::MediaList;
42use crate::dom::node::NodeTraits;
43use crate::dom::promise::RootedPromise;
44use crate::dom::types::Promise;
45use crate::dom::window::Window;
46
47#[dom_struct]
48pub(crate) struct CSSStyleSheet {
49 stylesheet: StyleSheet,
50
51 owner_node: MutNullableDom<Element>,
53
54 rule_list: MutNullableDom<CSSRuleList>,
56
57 #[ignore_malloc_size_of = "Stylo"]
59 #[no_trace]
60 style_stylesheet: DomRefCell<Arc<StyleStyleSheet>>,
61
62 #[no_trace]
65 style_shared_lock: SharedRwLock,
66
67 constructor_document: Option<Dom<Document>>,
71
72 disallow_modification: Cell<bool>,
74
75 origin_clean: Cell<bool>,
77
78 #[no_trace]
80 stylesheet_base_url: DomRefCell<Option<ServoUrl>>,
81
82 adopters: DomRefCell<Vec<StyleSheetListOwner>>,
85}
86
87impl CSSStyleSheet {
88 fn new_inherited(
89 owner: Option<&Element>,
90 type_: DOMString,
91 href: Option<DOMString>,
92 stylesheet: Arc<StyleStyleSheet>,
93 constructor_document: Option<&Document>,
94 ) -> CSSStyleSheet {
95 CSSStyleSheet {
96 stylesheet: StyleSheet::new_inherited(type_, href),
97 owner_node: MutNullableDom::new(owner),
98 rule_list: MutNullableDom::new(None),
99 style_shared_lock: stylesheet.shared_lock.clone(),
100 style_stylesheet: DomRefCell::new(stylesheet),
101 origin_clean: Cell::new(true),
102 constructor_document: constructor_document.map(Dom::from_ref),
103 adopters: Default::default(),
104 disallow_modification: Cell::new(false),
105 stylesheet_base_url: Default::default(),
106 }
107 }
108
109 pub(crate) fn new(
110 cx: &mut JSContext,
111 window: &Window,
112 owner: Option<&Element>,
113 type_: DOMString,
114 href: Option<DOMString>,
115 stylesheet: Arc<StyleStyleSheet>,
116 constructor_document: Option<&Document>,
117 ) -> DomRoot<CSSStyleSheet> {
118 reflect_dom_object(
119 cx,
120 Box::new(CSSStyleSheet::new_inherited(
121 owner,
122 type_,
123 href,
124 stylesheet,
125 constructor_document,
126 )),
127 window,
128 )
129 }
130
131 #[expect(clippy::too_many_arguments)]
132 fn new_with_proto(
133 cx: &mut JSContext,
134 window: &Window,
135 proto: Option<HandleObject>,
136 owner: Option<&Element>,
137 type_: DOMString,
138 href: Option<DOMString>,
139 stylesheet: Arc<StyleStyleSheet>,
140 constructor_document: Option<&Document>,
141 ) -> DomRoot<CSSStyleSheet> {
142 reflect_dom_object_with_proto(
143 cx,
144 Box::new(CSSStyleSheet::new_inherited(
145 owner,
146 type_,
147 href,
148 stylesheet,
149 constructor_document,
150 )),
151 window,
152 proto,
153 )
154 }
155
156 pub(crate) fn rulelist(&self, cx: &mut JSContext) -> DomRoot<CSSRuleList> {
157 self.rule_list.or_init(|| {
158 let sheet = self.style_stylesheet.borrow();
159 let guard = sheet.shared_lock.read();
160 let rules = sheet.contents(&guard).rules.clone();
161 CSSRuleList::new(
162 cx,
163 self.global().as_window(),
164 None,
165 self,
166 RulesSource::Rules(rules),
167 )
168 })
169 }
170
171 pub(crate) fn disabled(&self) -> bool {
172 self.style_stylesheet.borrow().disabled()
173 }
174
175 pub(crate) fn href(&self) -> Option<DOMString> {
176 self.upcast::<StyleSheet>().GetHref()
177 }
178
179 pub(crate) fn title(&self) -> DOMString {
180 self.upcast::<StyleSheet>().GetTitle().unwrap_or_default()
181 }
182
183 pub(crate) fn get_rule_count(&self) -> u32 {
184 let sheet = self.style_stylesheet.borrow();
185 let guard = sheet.shared_lock.read();
186 sheet.contents(&guard).rules.read_with(&guard).0.len() as u32
187 }
188
189 pub(crate) fn origin(&self) -> Origin {
190 let guard = self.style_shared_lock.read();
191 self.style_stylesheet()
192 .clone()
193 .contents
194 .read_with(&guard)
195 .origin
196 }
197
198 pub(crate) fn owner_node(&self) -> Option<DomRoot<Element>> {
199 self.owner_node.get()
200 }
201
202 pub(crate) fn set_disabled(&self, no_gc: &NoGC, disabled: bool) {
203 if self.style_stylesheet.borrow().set_disabled(disabled) {
204 self.notify_invalidations(no_gc);
205 }
206 }
207
208 pub(crate) fn set_owner_node(&self, value: Option<&Element>) {
209 self.owner_node.set(value);
210 }
211
212 pub(crate) fn shared_lock(&self) -> &SharedRwLock {
213 &self.style_shared_lock
214 }
215
216 pub(crate) fn style_stylesheet(&self) -> Ref<'_, Arc<StyleStyleSheet>> {
217 self.style_stylesheet.borrow()
218 }
219
220 pub(crate) fn set_origin_clean(&self, origin_clean: bool) {
221 self.origin_clean.set(origin_clean);
222 }
223
224 pub(crate) fn medialist(&self, cx: &mut JSContext) -> DomRoot<MediaList> {
225 MediaList::new(
226 cx,
227 self.global().as_window(),
228 self,
229 self.style_stylesheet().media.clone(),
230 )
231 }
232
233 #[inline]
235 pub(crate) fn is_constructed(&self) -> bool {
236 self.constructor_document.is_some()
237 }
238
239 pub(crate) fn constructor_document_matches(&self, other_doc: &Document) -> bool {
240 match &self.constructor_document {
241 Some(doc) => *doc == other_doc,
242 None => false,
243 }
244 }
245
246 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
249 pub(crate) fn add_adopter(&self, owner: StyleSheetListOwner) {
250 debug_assert!(self.is_constructed());
251 self.adopters.borrow_mut().push(owner);
252 }
253
254 pub(crate) fn remove_adopter(&self, owner: &StyleSheetListOwner) {
255 let adopters = &mut *self.adopters.borrow_mut();
256 if let Some(index) = adopters.iter().position(|o| o == owner) {
257 adopters.swap_remove(index);
258 }
259 }
260
261 pub(crate) fn will_modify(&self, no_gc: &NoGC) {
262 let Some(node) = self.owner_node.get() else {
263 return;
264 };
265
266 let Some(node) = node.downcast::<HTMLStyleElement>() else {
267 return;
268 };
269
270 node.will_modify_stylesheet(no_gc);
271 }
272
273 pub(crate) fn update_style_stylesheet(
274 &self,
275 style_stylesheet: &Arc<StyleStyleSheet>,
276 guard: &SharedRwLockReadGuard,
277 ) {
278 *self.style_stylesheet.borrow_mut() = style_stylesheet.clone();
285 if let Some(rulelist) = self.rule_list.get() {
286 let rules = style_stylesheet.contents(guard).rules.clone();
287 rulelist.update_rules(RulesSource::Rules(rules), guard);
288 }
289 }
290
291 pub(crate) fn notify_invalidations(&self, no_gc: &NoGC) {
293 if let Some(owner) = self.owner_node() {
294 owner.stylesheet_list_owner().invalidate_stylesheets(no_gc);
295 }
296 for adopter in self.adopters.borrow().iter() {
297 adopter.invalidate_stylesheets(no_gc);
298 }
299 }
300
301 pub(crate) fn disallow_modification(&self) -> bool {
303 self.disallow_modification.get()
304 }
305
306 fn do_replace_sync(&self, no_gc: &NoGC, text: USVString) {
308 let global = self.global();
310 let window = global.as_window();
311
312 self.will_modify(no_gc);
313
314 let _span = profile_traits::trace_span!("ParseStylesheet").entered();
315 let sheet = self.style_stylesheet();
316 let stylesheet_base_url = self
317 .stylesheet_base_url
318 .borrow()
319 .clone()
320 .unwrap_or(window.get_url());
321
322 let new_contents = StylesheetContents::from_str(
323 &text,
324 UrlExtraData(stylesheet_base_url.get_arc()),
325 Origin::Author,
326 &self.style_shared_lock,
327 None,
328 Some(window.css_error_reporter()),
329 window.Document().quirks_mode(),
330 AllowImportRules::No, None,
332 );
333
334 {
335 let mut write_guard = self.style_shared_lock.write();
336 *sheet.contents.write_with(&mut write_guard) = new_contents;
337 }
338
339 self.rule_list.set(None);
343
344 self.notify_invalidations(no_gc);
346 }
347}
348
349impl CSSStyleSheetMethods<crate::DomTypeHolder> for CSSStyleSheet {
350 fn Constructor(
352 cx: &mut JSContext,
353 window: &Window,
354 proto: Option<HandleObject>,
355 options: &CSSStyleSheetInit,
356 ) -> DomRoot<Self> {
357 let doc = window.Document();
358 let shared_lock = doc.style_shared_author_lock().clone();
359 let media = Arc::new(shared_lock.wrap(match &options.media {
360 MediaListOrString::MediaList(media_list) => media_list.clone_media_list(),
361 MediaListOrString::String(str) => MediaList::parse_media_list(&str.str(), window),
362 }));
363
364 let stylesheet = Arc::new(StyleStyleSheet::from_str(
365 "",
366 UrlExtraData(window.get_url().get_arc()),
367 Origin::Author,
368 media,
369 shared_lock,
370 None,
371 Some(window.css_error_reporter()),
372 doc.quirks_mode(),
373 AllowImportRules::No,
374 ));
375 if options.disabled {
376 stylesheet.set_disabled(true);
377 }
378
379 let sheet = Self::new_with_proto(
382 cx,
383 window,
384 proto,
385 None, DOMString::from_static("text/css"),
387 None, stylesheet,
389 Some(&window.Document()), );
391
392 let base_url = options
393 .baseURL
394 .as_ref()
395 .and_then(|url| ServoUrl::parse(&url.str()).ok());
396 *sheet.stylesheet_base_url.safe_borrow_mut(cx) = base_url;
398
399 sheet
401 }
402
403 fn GetCssRules(&self, cx: &mut JSContext) -> Fallible<DomRoot<CSSRuleList>> {
405 if !self.origin_clean.get() {
407 return Err(Error::Security(Some(
408 "Not allowed to access cross-origin style sheet".to_string(),
409 )));
410 }
411 Ok(self.rulelist(cx))
412 }
413
414 fn InsertRule(&self, cx: &mut JSContext, rule: DOMString, index: u32) -> Fallible<u32> {
416 if !self.origin_clean.get() {
418 return Err(Error::Security(Some(
419 "Not allowed to access cross-origin style sheet".to_string(),
420 )));
421 }
422
423 if self.disallow_modification() {
425 return Err(Error::NotAllowed(Some(
426 "This method can only be called on modifiable style sheets".to_string(),
427 )));
428 }
429
430 self.rulelist(cx).insert_rule(cx, &rule, index)
431 }
432
433 fn DeleteRule(&self, cx: &mut JSContext, index: u32) -> ErrorResult {
435 if !self.origin_clean.get() {
437 return Err(Error::Security(Some(
438 "Not allowed to access cross-origin style sheet".to_string(),
439 )));
440 }
441
442 if self.disallow_modification() {
444 return Err(Error::NotAllowed(Some(
445 "This method can only be called on modifiable style sheets".to_string(),
446 )));
447 }
448 self.rulelist(cx).remove_rule(cx, index)
449 }
450
451 fn GetRules(&self, cx: &mut JSContext) -> Fallible<DomRoot<CSSRuleList>> {
453 self.GetCssRules(cx)
454 }
455
456 fn RemoveRule(&self, cx: &mut JSContext, index: u32) -> ErrorResult {
458 self.DeleteRule(cx, index)
459 }
460
461 fn AddRule(
463 &self,
464 cx: &mut js::context::JSContext,
465 selector: DOMString,
466 block: DOMString,
467 optional_index: Option<u32>,
468 ) -> Fallible<i32> {
469 let mut rule = selector;
472
473 if block.is_empty() {
477 rule.push_str(" { }");
478 } else {
479 rule.push_str(" { ");
480 rule.push_str(&block.str());
481 rule.push_str(" }");
482 };
483
484 let index = optional_index.unwrap_or_else(|| self.rulelist(cx).Length());
486
487 self.InsertRule(cx, rule, index)?;
489
490 Ok(-1)
492 }
493
494 fn Replace(&self, cx: &mut CurrentRealm, text: USVString) -> Fallible<RootedPromise> {
496 let promise = Promise::new_in_realm(cx);
498
499 if !self.is_constructed() {
502 return Err(Error::NotAllowed(Some(
503 "This method can only be called on constructed style sheets".to_string(),
504 )));
505 }
506 if self.disallow_modification() {
507 return Err(Error::NotAllowed(Some(
508 "This method can only be called on modifiable style sheets".to_string(),
509 )));
510 }
511
512 self.disallow_modification.set(true);
514
515 let trusted_sheet = Trusted::new(self);
517 let trusted_promise = TrustedPromise::from(&promise);
518
519 self.global()
520 .task_manager()
521 .dom_manipulation_task_source()
522 .queue(task!(cssstylesheet_replace: move |cx| {
523 let sheet = trusted_sheet.root();
524
525 sheet.do_replace_sync(cx.no_gc(), text);
527
528 sheet.disallow_modification.set(false);
530
531 trusted_promise.root(cx).resolve_native(cx, &sheet);
533 }));
534
535 Ok(promise)
536 }
537
538 fn ReplaceSync(&self, no_gc: &NoGC, text: USVString) -> Result<(), Error> {
540 if !self.is_constructed() || self.disallow_modification() {
543 return Err(Error::NotAllowed(Some(
544 "This method can only be called on constructed style sheets".to_string(),
545 )));
546 }
547 if self.disallow_modification() {
548 return Err(Error::NotAllowed(Some(
549 "This method can only be called on modifiable style sheets".to_string(),
550 )));
551 }
552 self.do_replace_sync(no_gc, text);
553 Ok(())
554 }
555}