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