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