1#![cfg_attr(crown, allow(crown::jscontext_first_arg))]
6
7use std::io::{Read, Seek, Write};
8use std::sync::atomic::{AtomicUsize, Ordering};
9
10use bytes::{Bytes, BytesMut};
11use crossbeam_channel::Sender;
12use cssparser::SourceLocation;
13use encoding_rs::UTF_8;
14use js::context::JSContext;
15use net_traits::mime_classifier::MimeClassifier;
16use net_traits::request::{CorsSettings, Destination, RequestId};
17use net_traits::{
18 FetchMetadata, FilteredMetadata, LoadContext, Metadata, NetworkError, ReferrerPolicy,
19 ResourceFetchTiming,
20};
21use servo_arc::Arc;
22use servo_base::id::PipelineId;
23use servo_config::pref;
24use servo_url::ServoUrl;
25use style::context::QuirksMode;
26use style::global_style_data::STYLE_THREAD_POOL;
27use style::media_queries::MediaList;
28use style::shared_lock::{Locked, SharedRwLock};
29use style::stylesheets::import_rule::{ImportLayer, ImportSheet, ImportSupportsCondition};
30use style::stylesheets::{
31 ImportRule, Origin, Stylesheet, StylesheetLoader as StyleStylesheetLoader, UrlExtraData,
32};
33use style::values::CssUrl;
34
35use crate::dom::bindings::inheritance::Castable;
36use crate::dom::bindings::refcounted::Trusted;
37use crate::dom::bindings::reflector::DomGlobal;
38use crate::dom::bindings::root::DomRoot;
39use crate::dom::csp::{GlobalCspReporting, Violation};
40use crate::dom::document::Document;
41use crate::dom::element::Element;
42use crate::dom::eventtarget::EventTarget;
43use crate::dom::globalscope::GlobalScope;
44use crate::dom::html::htmlelement::HTMLElement;
45use crate::dom::html::htmllinkelement::{HTMLLinkElement, RequestGenerationId};
46use crate::dom::node::NodeTraits;
47use crate::dom::performance::performanceresourcetiming::InitiatorType;
48use crate::dom::shadowroot::ShadowRoot;
49use crate::dom::window::CSSErrorReporter;
50use crate::event_loop::document_loader::LoadType;
51use crate::fetch::fetch::{RequestWithGlobalScope, create_a_potential_cors_request};
52use crate::fetch::network_listener::{self, FetchResponseListener, ResourceTimingListener};
53use crate::messaging::{CommonScriptMsg, MainThreadScriptMsg};
54use crate::runtime::script_runtime::ScriptThreadEventCategory;
55use crate::tasks::task_source::TaskSourceName;
56use crate::unminify::{
57 BeautifyFileType, create_output_file, create_temp_files, execute_js_beautify,
58};
59
60#[derive(Clone, Copy, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
63pub(crate) struct StylesheetContextId(usize);
64
65impl StylesheetContextId {
66 fn next() -> Self {
67 static NEXT_STYLESHEET_CONTEXT_INDEX: AtomicUsize = AtomicUsize::new(0);
68 Self(NEXT_STYLESHEET_CONTEXT_INDEX.fetch_add(1, Ordering::Relaxed))
69 }
70}
71
72pub(crate) trait StylesheetOwner {
73 fn parser_inserted(&self) -> bool;
76
77 fn potentially_render_blocking(&self) -> bool;
79
80 fn referrer_policy(&self, cx: &mut JSContext) -> ReferrerPolicy;
82
83 fn increment_pending_loads_count(&self);
85
86 fn load_finished(&self, successful: bool) -> Option<bool>;
89
90 fn set_origin_clean(&self, cx: &mut JSContext, origin_clean: bool);
92}
93
94pub(crate) enum StylesheetContextSource {
95 LinkElement,
96 Import(Arc<Locked<ImportRule>>),
97}
98
99struct StylesheetContext {
101 id: StylesheetContextId,
104 element: Trusted<HTMLElement>,
106 source: StylesheetContextSource,
107 media: Arc<Locked<MediaList>>,
108 url: ServoUrl,
109 metadata: Option<Metadata>,
110 data: BytesMut,
112 document: Trusted<Document>,
114 shadow_root: Option<Trusted<ShadowRoot>>,
115 origin_clean: bool,
116 request_generation_id: Option<RequestGenerationId>,
119 is_script_blocking: bool,
121 is_render_blocking: bool,
123}
124
125impl StylesheetContext {
126 fn unminify_css(&mut self, file_url: ServoUrl) {
127 let Some(unminified_dir) = self.document.root().window().unminified_css_dir() else {
128 return;
129 };
130
131 let mut style_content = std::mem::take(&mut self.data).to_vec();
132 if let Some((input, mut output)) = create_temp_files() &&
133 execute_js_beautify(
134 input.path(),
135 output.try_clone().unwrap(),
136 BeautifyFileType::Css,
137 )
138 {
139 output.seek(std::io::SeekFrom::Start(0)).unwrap();
140 output.read_to_end(&mut style_content).unwrap();
141 }
142 match create_output_file(unminified_dir, &file_url, None) {
143 Ok(mut file) => {
144 file.write_all(&style_content).unwrap();
145 },
146 Err(why) => {
147 log::warn!("Could not store script {:?}", why);
148 },
149 }
150
151 self.data = Bytes::copy_from_slice(&style_content)
152 .try_into_mut()
153 .unwrap();
154 }
155
156 fn empty_stylesheet(&self, document: &Document) -> Arc<Stylesheet> {
157 let shared_lock = document.style_shared_author_lock().clone();
158 let quirks_mode = document.quirks_mode();
159
160 Arc::new(Stylesheet::from_bytes(
161 &[],
162 UrlExtraData(self.url.get_arc()),
163 None,
164 None,
165 Origin::Author,
166 self.media.clone(),
167 shared_lock,
168 None,
169 None,
170 quirks_mode,
171 ))
172 }
173
174 fn parse(
175 &self,
176 quirks_mode: QuirksMode,
177 shared_lock: SharedRwLock,
178 css_error_reporter: &CSSErrorReporter,
179 loader: ElementStylesheetLoader<'_>,
180 ) -> Arc<Stylesheet> {
181 let metadata = self
182 .metadata
183 .as_ref()
184 .expect("Should never call parse without metadata.");
185
186 let _span = profile_traits::trace_span!("ParseStylesheet").entered();
187 Arc::new(Stylesheet::from_bytes(
188 &self.data,
189 UrlExtraData(metadata.final_url.get_arc()),
190 metadata.charset.as_deref(),
191 Some(UTF_8),
197 Origin::Author,
198 self.media.clone(),
199 shared_lock,
200 Some(&loader),
201 Some(css_error_reporter),
202 quirks_mode,
203 ))
204 }
205
206 fn contributes_to_the_styling_processing_model(&self, element: &HTMLElement) -> bool {
207 if !element.upcast::<Element>().is_connected() {
208 return false;
209 }
210
211 if !matches!(&self.source, StylesheetContextSource::LinkElement) {
218 return true;
219 }
220 let link = element.downcast::<HTMLLinkElement>().unwrap();
221 self.request_generation_id
222 .is_none_or(|generation| generation == link.get_request_generation_id())
223 }
224
225 fn contributes_a_script_blocking_style_sheet(
227 &self,
228 element: &HTMLElement,
229 owner: &dyn StylesheetOwner,
230 document: &Document,
231 ) -> bool {
232 owner.parser_inserted()
234 && element.downcast::<HTMLLinkElement>().is_none_or(|link|
237 self.contributes_to_the_styling_processing_model(element)
238 && !link.is_effectively_disabled()
240 )
241 && element.media_attribute_matches_media_environment()
243 && *element.owner_document() == *document
245 }
250
251 fn decrement_blockers_and_finish_load(
252 self,
253 document: &Document,
254 cx: &mut js::context::JSContext,
255 ) {
256 if self.is_script_blocking {
257 document.remove_script_blocking_stylesheet(self.id);
258 }
259
260 if self.is_render_blocking {
261 document.decrement_render_blocking_element_count();
262 }
263
264 document.finish_load(LoadType::Stylesheet(self.url), cx);
265 }
266
267 fn do_post_parse_tasks(
268 self,
269 success: bool,
270 stylesheet: Arc<Stylesheet>,
271 cx: &mut js::context::JSContext,
272 ) {
273 let element = self.element.root();
274 let document = self.document.root();
275 let owner = element
276 .upcast::<Element>()
277 .as_stylesheet_owner()
278 .expect("Stylesheet not loaded by <style> or <link> element!");
279
280 match &self.source {
281 StylesheetContextSource::LinkElement => {
283 let link = element
284 .downcast::<HTMLLinkElement>()
285 .expect("Should be HTMLinkElement due to StylesheetContextSource");
286 if self
290 .request_generation_id
291 .is_some_and(|generation| generation != link.get_request_generation_id())
292 {
293 self.decrement_blockers_and_finish_load(&document, cx);
294 return;
295 }
296 if link.is_effectively_disabled() {
300 stylesheet.set_disabled(true);
301 }
302 link.set_stylesheet(cx.no_gc(), stylesheet);
309 },
310 StylesheetContextSource::Import(import_rule) => {
311 let mut guard = document.style_shared_author_lock().write();
312 import_rule.write_with(&mut guard).stylesheet = ImportSheet::Sheet(stylesheet);
313 },
314 }
315
316 if let Some(ref shadow_root) = self.shadow_root {
317 shadow_root.root().invalidate_stylesheets(cx.no_gc());
318 } else {
319 document.invalidate_stylesheets(cx.no_gc());
320 }
321 owner.set_origin_clean(cx, self.origin_clean);
322
323 if let Some(any_failed) = owner.load_finished(success) {
330 let event = match any_failed {
333 true => atom!("error"),
334 false => atom!("load"),
335 };
336 element.upcast::<EventTarget>().fire_event(cx, event);
337 }
338 self.decrement_blockers_and_finish_load(&document, cx);
344 }
345}
346
347impl FetchResponseListener for StylesheetContext {
348 fn process_request_body(&mut self, _: RequestId) {}
349
350 fn process_response(
351 &mut self,
352 _: &mut js::context::JSContext,
353 _: RequestId,
354 metadata: Result<FetchMetadata, NetworkError>,
355 ) {
356 if let Ok(FetchMetadata::Filtered {
357 filtered: FilteredMetadata::Opaque | FilteredMetadata::OpaqueRedirect(_),
358 ..
359 }) = metadata
360 {
361 self.origin_clean = false;
362 }
363
364 self.metadata = metadata.ok().map(|m| match m {
365 FetchMetadata::Unfiltered(m) => m,
366 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
367 });
368 }
369
370 fn process_response_chunk(
371 &mut self,
372 _: &mut js::context::JSContext,
373 _: RequestId,
374 payload: Bytes,
375 ) {
376 self.data.extend_from_slice(&payload);
377 }
378
379 fn process_response_eof(
380 mut self,
381 cx: &mut js::context::JSContext,
382 _: RequestId,
383 status: Result<(), NetworkError>,
384 timing: ResourceFetchTiming,
385 ) {
386 network_listener::submit_timing(cx, &self, &status, &timing);
387
388 let document = self.document.root();
389 let Some(metadata) = self.metadata.as_ref() else {
390 let empty_stylesheet = self.empty_stylesheet(&document);
391 self.do_post_parse_tasks(false, empty_stylesheet, cx);
392 return;
393 };
394
395 let element = self.element.root();
396
397 if element.is::<HTMLLinkElement>() {
399 let is_css = MimeClassifier::is_css(
401 &metadata.resource_content_type_metadata(LoadContext::Style, &self.data),
402 ) || (
403 document.quirks_mode() == QuirksMode::Quirks &&
409 document.origin().immutable().clone() == metadata.final_url.origin()
410 );
411
412 if !is_css {
413 let empty_stylesheet = self.empty_stylesheet(&document);
414 self.do_post_parse_tasks(false, empty_stylesheet, cx);
415 return;
416 }
417
418 if !self.contributes_to_the_styling_processing_model(&element) {
421 self.decrement_blockers_and_finish_load(&document, cx);
423 return;
425 }
426 }
427
428 if metadata.status != http::StatusCode::OK {
429 let empty_stylesheet = self.empty_stylesheet(&document);
430 self.do_post_parse_tasks(false, empty_stylesheet, cx);
431 return;
432 }
433
434 self.unminify_css(metadata.final_url.clone());
435
436 let loader = if pref!(dom_parallel_css_parsing_enabled) {
437 ElementStylesheetLoader::Asynchronous(AsynchronousStylesheetLoader::new(&element))
438 } else {
439 ElementStylesheetLoader::Synchronous { element: &element }
440 };
441 loader.parse(self, &element, &document, cx);
442 }
443
444 fn process_csp_violations(
445 &mut self,
446 cx: &mut js::context::JSContext,
447 _request_id: RequestId,
448 violations: Vec<Violation>,
449 ) {
450 let global = &self.resource_timing_global();
451 global.report_csp_violations(cx, violations, None, None);
452 }
453
454 fn process_content_length(&mut self, _request_id: RequestId, size: usize) {
455 self.data.reserve(size.saturating_sub(self.data.len()));
456 }
457}
458
459impl ResourceTimingListener for StylesheetContext {
460 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
461 let initiator_type = InitiatorType::LocalName(
462 self.element
463 .root()
464 .upcast::<Element>()
465 .local_name()
466 .to_string(),
467 );
468 (initiator_type, self.url.clone())
469 }
470
471 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
472 self.element.root().owner_document().global()
473 }
474}
475
476pub(crate) enum ElementStylesheetLoader<'a> {
477 Synchronous { element: &'a HTMLElement },
478 Asynchronous(AsynchronousStylesheetLoader),
479}
480
481impl<'a> ElementStylesheetLoader<'a> {
482 pub(crate) fn new(element: &'a HTMLElement) -> Self {
483 ElementStylesheetLoader::Synchronous { element }
484 }
485}
486
487impl ElementStylesheetLoader<'_> {
488 pub(crate) fn load_with_element(
489 cx: &mut JSContext,
490 element: &HTMLElement,
491 source: StylesheetContextSource,
492 media: Arc<Locked<MediaList>>,
493 url: ServoUrl,
494 cors_setting: Option<CorsSettings>,
495 integrity_metadata: String,
496 ) {
497 let document = element.owner_document();
498 let shadow_root = element
499 .containing_shadow_root()
500 .map(|shadow_root| Trusted::new(&*shadow_root));
501 let generation = element
502 .downcast::<HTMLLinkElement>()
503 .map(HTMLLinkElement::get_request_generation_id);
504 let mut context = StylesheetContext {
505 id: StylesheetContextId::next(),
506 element: Trusted::new(element),
507 source,
508 media,
509 url: url.clone(),
510 metadata: None,
511 data: BytesMut::new(),
512 document: Trusted::new(&*document),
513 shadow_root,
514 origin_clean: true,
515 request_generation_id: generation,
516 is_script_blocking: false,
517 is_render_blocking: false,
518 };
519
520 let owner = element
521 .upcast::<Element>()
522 .as_stylesheet_owner()
523 .expect("Stylesheet not loaded by <style> or <link> element!");
524 let referrer_policy = owner.referrer_policy(cx);
525 owner.increment_pending_loads_count();
526
527 context.is_script_blocking =
532 context.contributes_a_script_blocking_style_sheet(element, owner, &document);
533 if context.is_script_blocking {
534 document.add_script_blocking_stylesheet(context.id);
535 }
536
537 context.is_render_blocking = element.media_attribute_matches_media_environment() &&
540 owner.potentially_render_blocking() &&
541 document.allows_adding_render_blocking_elements();
542 if context.is_render_blocking {
543 document.increment_render_blocking_element_count();
544 }
545
546 let global = element.global();
548 let request = create_a_potential_cors_request(
549 Some(document.webview_id()),
550 url.clone(),
551 Destination::Style,
552 cors_setting,
553 None,
554 global.get_referrer(),
555 )
556 .with_global_scope(&global)
557 .referrer_policy(referrer_policy)
558 .integrity_metadata(integrity_metadata);
559
560 document.fetch_blocking(LoadType::Stylesheet(url), request, context);
561 }
562
563 fn parse(
564 self,
565 listener: StylesheetContext,
566 element: &HTMLElement,
567 document: &Document,
568 cx: &mut js::context::JSContext,
569 ) {
570 let shared_lock = document.style_shared_author_lock().clone();
571 let quirks_mode = document.quirks_mode();
572 let window = element.owner_window();
573
574 match self {
575 ElementStylesheetLoader::Synchronous { .. } => {
576 let stylesheet =
577 listener.parse(quirks_mode, shared_lock, window.css_error_reporter(), self);
578 listener.do_post_parse_tasks(true, stylesheet, cx);
579 },
580 ElementStylesheetLoader::Asynchronous(asynchronous_loader) => {
581 let css_error_reporter = window.css_error_reporter().clone();
582
583 let parse_stylesheet = move || {
584 let pipeline_id = asynchronous_loader.pipeline_id;
585 let main_thread_sender = asynchronous_loader.main_thread_sender.clone();
586 let loader = ElementStylesheetLoader::Asynchronous(asynchronous_loader);
587 let stylesheet =
588 listener.parse(quirks_mode, shared_lock, &css_error_reporter, loader);
589
590 let task = task!(finish_parsing_of_stylesheet_on_main_thread: move |cx| {
591 listener.do_post_parse_tasks(true, stylesheet, cx);
592 });
593 let _ = main_thread_sender.send(MainThreadScriptMsg::Common(
594 CommonScriptMsg::Task(
595 ScriptThreadEventCategory::StylesheetLoad,
596 Box::new(task),
597 Some(pipeline_id),
598 TaskSourceName::Networking,
599 ),
600 ));
601 };
602
603 let thread_pool = STYLE_THREAD_POOL.pool();
604 if let Some(thread_pool) = thread_pool.as_ref() {
605 thread_pool.spawn(parse_stylesheet);
606 } else {
607 parse_stylesheet();
608 }
609 },
610 };
611 }
612}
613
614impl StyleStylesheetLoader for ElementStylesheetLoader<'_> {
615 fn request_stylesheet(
618 &self,
619 url: CssUrl,
620 source_location: SourceLocation,
621 lock: &SharedRwLock,
622 media: Arc<Locked<MediaList>>,
623 supports: Option<ImportSupportsCondition>,
624 layer: ImportLayer,
625 ) -> Arc<Locked<ImportRule>> {
626 if supports.as_ref().is_some_and(|s| !s.enabled) {
628 return Arc::new(lock.wrap(ImportRule {
629 url,
630 stylesheet: ImportSheet::new_refused(),
631 supports,
632 layer,
633 source_location,
634 }));
635 }
636
637 let resolved_url = match url.url().cloned() {
638 Some(url) => url,
639 None => {
640 return Arc::new(lock.wrap(ImportRule {
641 url,
642 stylesheet: ImportSheet::new_refused(),
643 supports,
644 layer,
645 source_location,
646 }));
647 },
648 };
649
650 let import_rule = Arc::new(lock.wrap(ImportRule {
651 url,
652 stylesheet: ImportSheet::new_pending(),
653 supports,
654 layer,
655 source_location,
656 }));
657
658 let source = StylesheetContextSource::Import(import_rule.clone());
661
662 match self {
663 ElementStylesheetLoader::Synchronous { element } => {
664 #[expect(unsafe_code)]
666 let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
667 Self::load_with_element(
668 &mut cx,
669 element,
670 source,
671 media,
672 resolved_url.into(),
673 None,
674 String::new(),
675 );
676 },
677 ElementStylesheetLoader::Asynchronous(AsynchronousStylesheetLoader {
678 element,
679 main_thread_sender,
680 pipeline_id,
681 }) => {
682 let element = element.clone();
683 let task = task!(load_import_stylesheet_on_main_thread: move || {
684 #[expect(unsafe_code)]
686 let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
687 Self::load_with_element(
688 &mut cx,
689 &element.root(),
690 source,
691 media,
692 resolved_url.into(),
693 None,
694 String::new()
695 );
696 });
697 let _ =
698 main_thread_sender.send(MainThreadScriptMsg::Common(CommonScriptMsg::Task(
699 ScriptThreadEventCategory::StylesheetLoad,
700 Box::new(task),
701 Some(*pipeline_id),
702 TaskSourceName::Networking,
703 )));
704 },
705 }
706
707 import_rule
708 }
709}
710
711pub(crate) struct AsynchronousStylesheetLoader {
712 element: Trusted<HTMLElement>,
713 main_thread_sender: Sender<MainThreadScriptMsg>,
714 pipeline_id: PipelineId,
715}
716
717impl AsynchronousStylesheetLoader {
718 pub(crate) fn new(element: &HTMLElement) -> Self {
719 let window = element.owner_window();
720 Self {
721 element: Trusted::new(element),
722 main_thread_sender: window.main_thread_script_chan().clone(),
723 pipeline_id: window.pipeline_id(),
724 }
725 }
726}