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, 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::script_runtime::CanGc;
47use crate::test::TrustedPromise;
48
49#[dom_struct]
50pub(crate) struct CSSStyleSheet {
51 stylesheet: StyleSheet,
52
53 owner_node: MutNullableDom<Element>,
55
56 rule_list: MutNullableDom<CSSRuleList>,
58
59 #[ignore_malloc_size_of = "Stylo"]
61 #[no_trace]
62 style_stylesheet: DomRefCell<Arc<StyleStyleSheet>>,
63
64 #[no_trace]
67 style_shared_lock: SharedRwLock,
68
69 origin_clean: Cell<bool>,
71
72 constructor_document: Option<Dom<Document>>,
76
77 disallow_modification: Cell<bool>,
79
80 adopters: DomRefCell<Vec<StyleSheetListOwner>>,
83}
84
85impl CSSStyleSheet {
86 fn new_inherited(
87 owner: Option<&Element>,
88 type_: DOMString,
89 href: Option<DOMString>,
90 title: Option<DOMString>,
91 stylesheet: Arc<StyleStyleSheet>,
92 constructor_document: Option<&Document>,
93 ) -> CSSStyleSheet {
94 CSSStyleSheet {
95 stylesheet: StyleSheet::new_inherited(type_, href, title),
96 owner_node: MutNullableDom::new(owner),
97 rule_list: MutNullableDom::new(None),
98 style_shared_lock: stylesheet.shared_lock.clone(),
99 style_stylesheet: DomRefCell::new(stylesheet),
100 origin_clean: Cell::new(true),
101 constructor_document: constructor_document.map(Dom::from_ref),
102 adopters: Default::default(),
103 disallow_modification: Cell::new(false),
104 }
105 }
106
107 #[allow(clippy::too_many_arguments)]
108 pub(crate) fn new(
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 can_gc: CanGc,
117 ) -> DomRoot<CSSStyleSheet> {
118 reflect_dom_object(
119 Box::new(CSSStyleSheet::new_inherited(
120 owner,
121 type_,
122 href,
123 title,
124 stylesheet,
125 constructor_document,
126 )),
127 window,
128 can_gc,
129 )
130 }
131
132 #[allow(clippy::too_many_arguments)]
133 fn new_with_proto(
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 can_gc: CanGc,
143 ) -> DomRoot<CSSStyleSheet> {
144 reflect_dom_object_with_proto(
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 can_gc,
156 )
157 }
158
159 pub(crate) fn rulelist(&self, cx: &mut JSContext) -> DomRoot<CSSRuleList> {
160 self.rule_list.or_init(|| {
161 let sheet = self.style_stylesheet.borrow();
162 let guard = sheet.shared_lock.read();
163 let rules = sheet.contents(&guard).rules.clone();
164 CSSRuleList::new(
165 cx,
166 self.global().as_window(),
167 self,
168 RulesSource::Rules(rules),
169 )
170 })
171 }
172
173 pub(crate) fn disabled(&self) -> bool {
174 self.style_stylesheet.borrow().disabled()
175 }
176
177 pub(crate) fn href(&self) -> Option<DOMString> {
178 self.upcast::<StyleSheet>().GetHref()
179 }
180
181 pub(crate) fn title(&self) -> DOMString {
182 self.upcast::<StyleSheet>().GetTitle().unwrap_or_default()
183 }
184
185 pub(crate) fn get_rule_count(&self) -> u32 {
186 let sheet = self.style_stylesheet.borrow();
187 let guard = sheet.shared_lock.read();
188 sheet.contents(&guard).rules.read_with(&guard).0.len() as u32
189 }
190
191 pub(crate) fn origin(&self) -> Origin {
192 let guard = self.style_shared_lock.read();
193 self.style_stylesheet()
194 .clone()
195 .contents
196 .read_with(&guard)
197 .origin
198 }
199
200 pub(crate) fn owner_node(&self) -> Option<DomRoot<Element>> {
201 self.owner_node.get()
202 }
203
204 pub(crate) fn set_disabled(&self, disabled: bool) {
205 if self.style_stylesheet.borrow().set_disabled(disabled) {
206 self.notify_invalidations();
207 }
208 }
209
210 pub(crate) fn set_owner_node(&self, value: Option<&Element>) {
211 self.owner_node.set(value);
212 }
213
214 pub(crate) fn shared_lock(&self) -> &SharedRwLock {
215 &self.style_shared_lock
216 }
217
218 pub(crate) fn style_stylesheet(&self) -> Ref<'_, Arc<StyleStyleSheet>> {
219 self.style_stylesheet.borrow()
220 }
221
222 pub(crate) fn set_origin_clean(&self, origin_clean: bool) {
223 self.origin_clean.set(origin_clean);
224 }
225
226 pub(crate) fn medialist(&self, cx: &mut JSContext) -> DomRoot<MediaList> {
227 MediaList::new(
228 cx,
229 self.global().as_window(),
230 self,
231 self.style_stylesheet().media.clone(),
232 )
233 }
234
235 #[inline]
237 pub(crate) fn is_constructed(&self) -> bool {
238 self.constructor_document.is_some()
239 }
240
241 pub(crate) fn constructor_document_matches(&self, other_doc: &Document) -> bool {
242 match &self.constructor_document {
243 Some(doc) => *doc == other_doc,
244 None => false,
245 }
246 }
247
248 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
251 pub(crate) fn add_adopter(&self, owner: StyleSheetListOwner) {
252 debug_assert!(self.is_constructed());
253 self.adopters.borrow_mut().push(owner);
254 }
255
256 pub(crate) fn remove_adopter(&self, owner: &StyleSheetListOwner) {
257 let adopters = &mut *self.adopters.borrow_mut();
258 if let Some(index) = adopters.iter().position(|o| o == owner) {
259 adopters.swap_remove(index);
260 }
261 }
262
263 pub(crate) fn will_modify(&self) {
264 let Some(node) = self.owner_node.get() else {
265 return;
266 };
267
268 let Some(node) = node.downcast::<HTMLStyleElement>() else {
269 return;
270 };
271
272 node.will_modify_stylesheet();
273 }
274
275 pub(crate) fn update_style_stylesheet(
276 &self,
277 style_stylesheet: &Arc<StyleStyleSheet>,
278 guard: &SharedRwLockReadGuard,
279 ) {
280 *self.style_stylesheet.borrow_mut() = style_stylesheet.clone();
287 if let Some(rulelist) = self.rule_list.get() {
288 let rules = style_stylesheet.contents(guard).rules.clone();
289 rulelist.update_rules(RulesSource::Rules(rules), guard);
290 }
291 }
292
293 pub(crate) fn notify_invalidations(&self) {
295 if let Some(owner) = self.owner_node() {
296 owner.stylesheet_list_owner().invalidate_stylesheets();
297 }
298 for adopter in self.adopters.borrow().iter() {
299 adopter.invalidate_stylesheets();
300 }
301 }
302
303 pub(crate) fn disallow_modification(&self) -> bool {
305 self.disallow_modification.get()
306 }
307
308 fn do_replace_sync(&self, text: USVString) {
310 let global = self.global();
312 let window = global.as_window();
313
314 self.will_modify();
315
316 let _span = profile_traits::trace_span!("ParseStylesheet").entered();
317 let sheet = self.style_stylesheet();
318 let new_contents = StylesheetContents::from_str(
319 &text,
320 UrlExtraData(window.get_url().get_arc()),
321 Origin::Author,
322 &self.style_shared_lock,
323 None,
324 Some(window.css_error_reporter()),
325 window.Document().quirks_mode(),
326 AllowImportRules::No, None,
328 );
329
330 {
331 let mut write_guard = self.style_shared_lock.write();
332 *sheet.contents.write_with(&mut write_guard) = new_contents;
333 }
334
335 self.rule_list.set(None);
339
340 self.notify_invalidations();
342 }
343}
344
345impl CSSStyleSheetMethods<crate::DomTypeHolder> for CSSStyleSheet {
346 fn Constructor(
348 window: &Window,
349 proto: Option<HandleObject>,
350 can_gc: CanGc,
351 options: &CSSStyleSheetInit,
352 ) -> DomRoot<Self> {
353 let doc = window.Document();
354 let shared_lock = doc.style_shared_author_lock().clone();
355 let media = Arc::new(shared_lock.wrap(match &options.media {
356 Some(media) => match media {
357 MediaListOrString::MediaList(media_list) => media_list.clone_media_list(),
358 MediaListOrString::String(str) => MediaList::parse_media_list(&str.str(), window),
359 },
360 None => StyleMediaList::empty(),
361 }));
362 let stylesheet = Arc::new(StyleStyleSheet::from_str(
363 "",
364 UrlExtraData(window.get_url().get_arc()),
365 Origin::Author,
366 media,
367 shared_lock,
368 None,
369 Some(window.css_error_reporter()),
370 doc.quirks_mode(),
371 AllowImportRules::No,
372 ));
373 if options.disabled {
374 stylesheet.set_disabled(true);
375 }
376 Self::new_with_proto(
377 window,
378 proto,
379 None, "text/css".into(),
381 None, None, stylesheet,
384 Some(&window.Document()), can_gc,
386 )
387 }
388
389 fn GetCssRules(&self, cx: &mut JSContext) -> Fallible<DomRoot<CSSRuleList>> {
391 if !self.origin_clean.get() {
393 return Err(Error::Security(Some(
394 "Not allowed to access cross-origin style sheet".to_string(),
395 )));
396 }
397 Ok(self.rulelist(cx))
398 }
399
400 fn InsertRule(&self, cx: &mut JSContext, rule: DOMString, index: u32) -> Fallible<u32> {
402 if !self.origin_clean.get() {
404 return Err(Error::Security(Some(
405 "Not allowed to access cross-origin style sheet".to_string(),
406 )));
407 }
408
409 if self.disallow_modification() {
411 return Err(Error::NotAllowed(Some(
412 "This method can only be called on modifiable style sheets".to_string(),
413 )));
414 }
415
416 self.rulelist(cx)
417 .insert_rule(cx, &rule, index, CssRuleTypes::default(), None)
418 }
419
420 fn DeleteRule(&self, cx: &mut JSContext, index: u32) -> ErrorResult {
422 if !self.origin_clean.get() {
424 return Err(Error::Security(Some(
425 "Not allowed to access cross-origin style sheet".to_string(),
426 )));
427 }
428
429 if self.disallow_modification() {
431 return Err(Error::NotAllowed(Some(
432 "This method can only be called on modifiable style sheets".to_string(),
433 )));
434 }
435 self.rulelist(cx).remove_rule(index)
436 }
437
438 fn GetRules(&self, cx: &mut JSContext) -> Fallible<DomRoot<CSSRuleList>> {
440 self.GetCssRules(cx)
441 }
442
443 fn RemoveRule(&self, cx: &mut JSContext, index: u32) -> ErrorResult {
445 self.DeleteRule(cx, index)
446 }
447
448 fn AddRule(
450 &self,
451 cx: &mut js::context::JSContext,
452 selector: DOMString,
453 block: DOMString,
454 optional_index: Option<u32>,
455 ) -> Fallible<i32> {
456 let mut rule = selector;
459
460 if block.is_empty() {
464 rule.push_str(" { }");
465 } else {
466 rule.push_str(" { ");
467 rule.push_str(&block.str());
468 rule.push_str(" }");
469 };
470
471 let index = optional_index.unwrap_or_else(|| self.rulelist(cx).Length());
473
474 self.InsertRule(cx, rule, index)?;
476
477 Ok(-1)
479 }
480
481 fn Replace(&self, cx: &mut CurrentRealm, text: USVString) -> Fallible<Rc<Promise>> {
483 let promise = Promise::new_in_realm(cx);
485
486 if !self.is_constructed() {
489 return Err(Error::NotAllowed(Some(
490 "This method can only be called on constructed style sheets".to_string(),
491 )));
492 }
493 if self.disallow_modification() {
494 return Err(Error::NotAllowed(Some(
495 "This method can only be called on modifiable style sheets".to_string(),
496 )));
497 }
498
499 self.disallow_modification.set(true);
501
502 let trusted_sheet = Trusted::new(self);
504 let trusted_promise = TrustedPromise::new(promise.clone());
505
506 self.global()
507 .task_manager()
508 .dom_manipulation_task_source()
509 .queue(task!(cssstylesheet_replace: move |cx| {
510 let sheet = trusted_sheet.root();
511
512 sheet.do_replace_sync(text);
514
515 sheet.disallow_modification.set(false);
517
518 trusted_promise.root().resolve_native(&sheet, CanGc::from_cx(cx));
520 }));
521
522 Ok(promise)
523 }
524
525 fn ReplaceSync(&self, text: USVString) -> Result<(), Error> {
527 if !self.is_constructed() || self.disallow_modification() {
530 return Err(Error::NotAllowed(Some(
531 "This method can only be called on constructed style sheets".to_string(),
532 )));
533 }
534 if self.disallow_modification() {
535 return Err(Error::NotAllowed(Some(
536 "This method can only be called on modifiable style sheets".to_string(),
537 )));
538 }
539 self.do_replace_sync(text);
540 Ok(())
541 }
542}