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 title: Option<DOMString>,
93 stylesheet: Arc<StyleStyleSheet>,
94 constructor_document: Option<&Document>,
95 ) -> CSSStyleSheet {
96 CSSStyleSheet {
97 stylesheet: StyleSheet::new_inherited(type_, href, title),
98 owner_node: MutNullableDom::new(owner),
99 rule_list: MutNullableDom::new(None),
100 style_shared_lock: stylesheet.shared_lock.clone(),
101 style_stylesheet: DomRefCell::new(stylesheet),
102 origin_clean: Cell::new(true),
103 constructor_document: constructor_document.map(Dom::from_ref),
104 adopters: Default::default(),
105 disallow_modification: Cell::new(false),
106 stylesheet_base_url: Default::default(),
107 }
108 }
109
110 #[allow(clippy::too_many_arguments)]
111 pub(crate) fn new(
112 cx: &mut JSContext,
113 window: &Window,
114 owner: Option<&Element>,
115 type_: DOMString,
116 href: Option<DOMString>,
117 title: Option<DOMString>,
118 stylesheet: Arc<StyleStyleSheet>,
119 constructor_document: Option<&Document>,
120 ) -> DomRoot<CSSStyleSheet> {
121 reflect_dom_object(
122 cx,
123 Box::new(CSSStyleSheet::new_inherited(
124 owner,
125 type_,
126 href,
127 title,
128 stylesheet,
129 constructor_document,
130 )),
131 window,
132 )
133 }
134
135 #[allow(clippy::too_many_arguments)]
136 fn new_with_proto(
137 cx: &mut JSContext,
138 window: &Window,
139 proto: Option<HandleObject>,
140 owner: Option<&Element>,
141 type_: DOMString,
142 href: Option<DOMString>,
143 title: Option<DOMString>,
144 stylesheet: Arc<StyleStyleSheet>,
145 constructor_document: Option<&Document>,
146 ) -> DomRoot<CSSStyleSheet> {
147 reflect_dom_object_with_proto(
148 cx,
149 Box::new(CSSStyleSheet::new_inherited(
150 owner,
151 type_,
152 href,
153 title,
154 stylesheet,
155 constructor_document,
156 )),
157 window,
158 proto,
159 )
160 }
161
162 pub(crate) fn rulelist(&self, cx: &mut JSContext) -> DomRoot<CSSRuleList> {
163 self.rule_list.or_init(|| {
164 let sheet = self.style_stylesheet.borrow();
165 let guard = sheet.shared_lock.read();
166 let rules = sheet.contents(&guard).rules.clone();
167 CSSRuleList::new(
168 cx,
169 self.global().as_window(),
170 None,
171 self,
172 RulesSource::Rules(rules),
173 )
174 })
175 }
176
177 pub(crate) fn disabled(&self) -> bool {
178 self.style_stylesheet.borrow().disabled()
179 }
180
181 pub(crate) fn href(&self) -> Option<DOMString> {
182 self.upcast::<StyleSheet>().GetHref()
183 }
184
185 pub(crate) fn title(&self) -> DOMString {
186 self.upcast::<StyleSheet>().GetTitle().unwrap_or_default()
187 }
188
189 pub(crate) fn get_rule_count(&self) -> u32 {
190 let sheet = self.style_stylesheet.borrow();
191 let guard = sheet.shared_lock.read();
192 sheet.contents(&guard).rules.read_with(&guard).0.len() as u32
193 }
194
195 pub(crate) fn origin(&self) -> Origin {
196 let guard = self.style_shared_lock.read();
197 self.style_stylesheet()
198 .clone()
199 .contents
200 .read_with(&guard)
201 .origin
202 }
203
204 pub(crate) fn owner_node(&self) -> Option<DomRoot<Element>> {
205 self.owner_node.get()
206 }
207
208 pub(crate) fn set_disabled(&self, no_gc: &NoGC, disabled: bool) {
209 if self.style_stylesheet.borrow().set_disabled(disabled) {
210 self.notify_invalidations(no_gc);
211 }
212 }
213
214 pub(crate) fn set_owner_node(&self, value: Option<&Element>) {
215 self.owner_node.set(value);
216 }
217
218 pub(crate) fn shared_lock(&self) -> &SharedRwLock {
219 &self.style_shared_lock
220 }
221
222 pub(crate) fn style_stylesheet(&self) -> Ref<'_, Arc<StyleStyleSheet>> {
223 self.style_stylesheet.borrow()
224 }
225
226 pub(crate) fn set_origin_clean(&self, origin_clean: bool) {
227 self.origin_clean.set(origin_clean);
228 }
229
230 pub(crate) fn medialist(&self, cx: &mut JSContext) -> DomRoot<MediaList> {
231 MediaList::new(
232 cx,
233 self.global().as_window(),
234 self,
235 self.style_stylesheet().media.clone(),
236 )
237 }
238
239 #[inline]
241 pub(crate) fn is_constructed(&self) -> bool {
242 self.constructor_document.is_some()
243 }
244
245 pub(crate) fn constructor_document_matches(&self, other_doc: &Document) -> bool {
246 match &self.constructor_document {
247 Some(doc) => *doc == other_doc,
248 None => false,
249 }
250 }
251
252 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
255 pub(crate) fn add_adopter(&self, owner: StyleSheetListOwner) {
256 debug_assert!(self.is_constructed());
257 self.adopters.borrow_mut().push(owner);
258 }
259
260 pub(crate) fn remove_adopter(&self, owner: &StyleSheetListOwner) {
261 let adopters = &mut *self.adopters.borrow_mut();
262 if let Some(index) = adopters.iter().position(|o| o == owner) {
263 adopters.swap_remove(index);
264 }
265 }
266
267 pub(crate) fn will_modify(&self, no_gc: &NoGC) {
268 let Some(node) = self.owner_node.get() else {
269 return;
270 };
271
272 let Some(node) = node.downcast::<HTMLStyleElement>() else {
273 return;
274 };
275
276 node.will_modify_stylesheet(no_gc);
277 }
278
279 pub(crate) fn update_style_stylesheet(
280 &self,
281 style_stylesheet: &Arc<StyleStyleSheet>,
282 guard: &SharedRwLockReadGuard,
283 ) {
284 *self.style_stylesheet.borrow_mut() = style_stylesheet.clone();
291 if let Some(rulelist) = self.rule_list.get() {
292 let rules = style_stylesheet.contents(guard).rules.clone();
293 rulelist.update_rules(RulesSource::Rules(rules), guard);
294 }
295 }
296
297 pub(crate) fn notify_invalidations(&self, no_gc: &NoGC) {
299 if let Some(owner) = self.owner_node() {
300 owner.stylesheet_list_owner().invalidate_stylesheets(no_gc);
301 }
302 for adopter in self.adopters.borrow().iter() {
303 adopter.invalidate_stylesheets(no_gc);
304 }
305 }
306
307 pub(crate) fn disallow_modification(&self) -> bool {
309 self.disallow_modification.get()
310 }
311
312 fn do_replace_sync(&self, no_gc: &NoGC, text: USVString) {
314 let global = self.global();
316 let window = global.as_window();
317
318 self.will_modify(no_gc);
319
320 let _span = profile_traits::trace_span!("ParseStylesheet").entered();
321 let sheet = self.style_stylesheet();
322 let stylesheet_base_url = self
323 .stylesheet_base_url
324 .borrow()
325 .clone()
326 .unwrap_or(window.get_url());
327
328 let new_contents = StylesheetContents::from_str(
329 &text,
330 UrlExtraData(stylesheet_base_url.get_arc()),
331 Origin::Author,
332 &self.style_shared_lock,
333 None,
334 Some(window.css_error_reporter()),
335 window.Document().quirks_mode(),
336 AllowImportRules::No, None,
338 );
339
340 {
341 let mut write_guard = self.style_shared_lock.write();
342 *sheet.contents.write_with(&mut write_guard) = new_contents;
343 }
344
345 self.rule_list.set(None);
349
350 self.notify_invalidations(no_gc);
352 }
353}
354
355impl CSSStyleSheetMethods<crate::DomTypeHolder> for CSSStyleSheet {
356 fn Constructor(
358 cx: &mut JSContext,
359 window: &Window,
360 proto: Option<HandleObject>,
361 options: &CSSStyleSheetInit,
362 ) -> DomRoot<Self> {
363 let doc = window.Document();
364 let shared_lock = doc.style_shared_author_lock().clone();
365 let media = Arc::new(shared_lock.wrap(match &options.media {
366 MediaListOrString::MediaList(media_list) => media_list.clone_media_list(),
367 MediaListOrString::String(str) => MediaList::parse_media_list(&str.str(), window),
368 }));
369
370 let stylesheet = Arc::new(StyleStyleSheet::from_str(
371 "",
372 UrlExtraData(window.get_url().get_arc()),
373 Origin::Author,
374 media,
375 shared_lock,
376 None,
377 Some(window.css_error_reporter()),
378 doc.quirks_mode(),
379 AllowImportRules::No,
380 ));
381 if options.disabled {
382 stylesheet.set_disabled(true);
383 }
384
385 let sheet = Self::new_with_proto(
388 cx,
389 window,
390 proto,
391 None, DOMString::from_static("text/css"),
393 None, None, stylesheet,
396 Some(&window.Document()), );
398
399 let base_url = options
400 .baseURL
401 .as_ref()
402 .and_then(|url| ServoUrl::parse(&url.str()).ok());
403 *sheet.stylesheet_base_url.safe_borrow_mut(cx) = base_url;
405
406 sheet
408 }
409
410 fn GetCssRules(&self, cx: &mut JSContext) -> Fallible<DomRoot<CSSRuleList>> {
412 if !self.origin_clean.get() {
414 return Err(Error::Security(Some(
415 "Not allowed to access cross-origin style sheet".to_string(),
416 )));
417 }
418 Ok(self.rulelist(cx))
419 }
420
421 fn InsertRule(&self, cx: &mut JSContext, rule: DOMString, index: u32) -> Fallible<u32> {
423 if !self.origin_clean.get() {
425 return Err(Error::Security(Some(
426 "Not allowed to access cross-origin style sheet".to_string(),
427 )));
428 }
429
430 if self.disallow_modification() {
432 return Err(Error::NotAllowed(Some(
433 "This method can only be called on modifiable style sheets".to_string(),
434 )));
435 }
436
437 self.rulelist(cx).insert_rule(cx, &rule, index)
438 }
439
440 fn DeleteRule(&self, cx: &mut JSContext, index: u32) -> ErrorResult {
442 if !self.origin_clean.get() {
444 return Err(Error::Security(Some(
445 "Not allowed to access cross-origin style sheet".to_string(),
446 )));
447 }
448
449 if self.disallow_modification() {
451 return Err(Error::NotAllowed(Some(
452 "This method can only be called on modifiable style sheets".to_string(),
453 )));
454 }
455 self.rulelist(cx).remove_rule(cx, index)
456 }
457
458 fn GetRules(&self, cx: &mut JSContext) -> Fallible<DomRoot<CSSRuleList>> {
460 self.GetCssRules(cx)
461 }
462
463 fn RemoveRule(&self, cx: &mut JSContext, index: u32) -> ErrorResult {
465 self.DeleteRule(cx, index)
466 }
467
468 fn AddRule(
470 &self,
471 cx: &mut js::context::JSContext,
472 selector: DOMString,
473 block: DOMString,
474 optional_index: Option<u32>,
475 ) -> Fallible<i32> {
476 let mut rule = selector;
479
480 if block.is_empty() {
484 rule.push_str(" { }");
485 } else {
486 rule.push_str(" { ");
487 rule.push_str(&block.str());
488 rule.push_str(" }");
489 };
490
491 let index = optional_index.unwrap_or_else(|| self.rulelist(cx).Length());
493
494 self.InsertRule(cx, rule, index)?;
496
497 Ok(-1)
499 }
500
501 fn Replace(&self, cx: &mut CurrentRealm, text: USVString) -> Fallible<RootedPromise> {
503 let promise = Promise::new_in_realm_rooted(cx);
505
506 if !self.is_constructed() {
509 return Err(Error::NotAllowed(Some(
510 "This method can only be called on constructed style sheets".to_string(),
511 )));
512 }
513 if self.disallow_modification() {
514 return Err(Error::NotAllowed(Some(
515 "This method can only be called on modifiable style sheets".to_string(),
516 )));
517 }
518
519 self.disallow_modification.set(true);
521
522 let trusted_sheet = Trusted::new(self);
524 let trusted_promise = TrustedPromise::from(&promise);
525
526 self.global()
527 .task_manager()
528 .dom_manipulation_task_source()
529 .queue(task!(cssstylesheet_replace: move |cx| {
530 let sheet = trusted_sheet.root();
531
532 sheet.do_replace_sync(cx.no_gc(), text);
534
535 sheet.disallow_modification.set(false);
537
538 trusted_promise.root(cx).resolve_native(cx, &sheet);
540 }));
541
542 Ok(promise)
543 }
544
545 fn ReplaceSync(&self, no_gc: &NoGC, text: USVString) -> Result<(), Error> {
547 if !self.is_constructed() || self.disallow_modification() {
550 return Err(Error::NotAllowed(Some(
551 "This method can only be called on constructed style sheets".to_string(),
552 )));
553 }
554 if self.disallow_modification() {
555 return Err(Error::NotAllowed(Some(
556 "This method can only be called on modifiable style sheets".to_string(),
557 )));
558 }
559 self.do_replace_sync(no_gc, text);
560 Ok(())
561 }
562}