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