1use std::io::{Read, Seek, Write};
6
7use crossbeam_channel::Sender;
8use cssparser::SourceLocation;
9use encoding_rs::UTF_8;
10use js::context::JSContext;
11use net_traits::mime_classifier::MimeClassifier;
12use net_traits::request::{CorsSettings, Destination, RequestId};
13use net_traits::{
14 FetchMetadata, FilteredMetadata, LoadContext, Metadata, NetworkError, ReferrerPolicy,
15 ResourceFetchTiming,
16};
17use servo_arc::Arc;
18use servo_base::id::PipelineId;
19use servo_config::pref;
20use servo_url::ServoUrl;
21use style::context::QuirksMode;
22use style::global_style_data::STYLE_THREAD_POOL;
23use style::media_queries::MediaList;
24use style::shared_lock::{Locked, SharedRwLock};
25use style::stylesheets::import_rule::{ImportLayer, ImportSheet, ImportSupportsCondition};
26use style::stylesheets::{
27 ImportRule, Origin, Stylesheet, StylesheetLoader as StyleStylesheetLoader, UrlExtraData,
28};
29use style::values::CssUrl;
30
31use crate::document_loader::LoadType;
32use crate::dom::bindings::inheritance::Castable;
33use crate::dom::bindings::refcounted::Trusted;
34use crate::dom::bindings::reflector::DomGlobal;
35use crate::dom::bindings::root::DomRoot;
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;
46use crate::dom::window::CSSErrorReporter;
47use crate::fetch::{RequestWithGlobalScope, create_a_potential_cors_request};
48use crate::messaging::{CommonScriptMsg, MainThreadScriptMsg};
49use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
50use crate::script_runtime::ScriptThreadEventCategory;
51use crate::task_source::TaskSourceName;
52use crate::unminify::{
53 BeautifyFileType, create_output_file, create_temp_files, execute_js_beautify,
54};
55
56pub(crate) trait StylesheetOwner {
57 fn parser_inserted(&self) -> bool;
60
61 fn potentially_render_blocking(&self) -> bool;
63
64 fn referrer_policy(&self, cx: &mut JSContext) -> ReferrerPolicy;
66
67 fn increment_pending_loads_count(&self);
69
70 fn load_finished(&self, successful: bool) -> Option<bool>;
73
74 fn set_origin_clean(&self, origin_clean: bool);
76}
77
78pub(crate) enum StylesheetContextSource {
79 LinkElement,
80 Import(Arc<Locked<ImportRule>>),
81}
82
83struct StylesheetContext {
85 element: Trusted<HTMLElement>,
87 source: StylesheetContextSource,
88 media: Arc<Locked<MediaList>>,
89 url: ServoUrl,
90 metadata: Option<Metadata>,
91 data: Vec<u8>,
93 document: Trusted<Document>,
95 shadow_root: Option<Trusted<ShadowRoot>>,
96 origin_clean: bool,
97 request_generation_id: Option<RequestGenerationId>,
100 is_script_blocking: bool,
102 is_render_blocking: bool,
104}
105
106impl StylesheetContext {
107 fn unminify_css(&mut self, file_url: ServoUrl) {
108 let Some(unminified_dir) = self.document.root().window().unminified_css_dir() else {
109 return;
110 };
111
112 let mut style_content = std::mem::take(&mut self.data);
113 if let Some((input, mut output)) = create_temp_files() &&
114 execute_js_beautify(
115 input.path(),
116 output.try_clone().unwrap(),
117 BeautifyFileType::Css,
118 )
119 {
120 output.seek(std::io::SeekFrom::Start(0)).unwrap();
121 output.read_to_end(&mut style_content).unwrap();
122 }
123 match create_output_file(unminified_dir, &file_url, None) {
124 Ok(mut file) => {
125 file.write_all(&style_content).unwrap();
126 },
127 Err(why) => {
128 log::warn!("Could not store script {:?}", why);
129 },
130 }
131
132 self.data = style_content;
133 }
134
135 fn empty_stylesheet(&self, document: &Document) -> Arc<Stylesheet> {
136 let shared_lock = document.style_shared_author_lock().clone();
137 let quirks_mode = document.quirks_mode();
138
139 Arc::new(Stylesheet::from_bytes(
140 &[],
141 UrlExtraData(self.url.get_arc()),
142 None,
143 None,
144 Origin::Author,
145 self.media.clone(),
146 shared_lock,
147 None,
148 None,
149 quirks_mode,
150 ))
151 }
152
153 fn parse(
154 &self,
155 quirks_mode: QuirksMode,
156 shared_lock: SharedRwLock,
157 css_error_reporter: &CSSErrorReporter,
158 loader: ElementStylesheetLoader<'_>,
159 ) -> Arc<Stylesheet> {
160 let metadata = self
161 .metadata
162 .as_ref()
163 .expect("Should never call parse without metadata.");
164
165 let _span = profile_traits::trace_span!("ParseStylesheet").entered();
166 Arc::new(Stylesheet::from_bytes(
167 &self.data,
168 UrlExtraData(metadata.final_url.get_arc()),
169 metadata.charset.as_deref(),
170 Some(UTF_8),
176 Origin::Author,
177 self.media.clone(),
178 shared_lock,
179 Some(&loader),
180 Some(css_error_reporter),
181 quirks_mode,
182 ))
183 }
184
185 fn contributes_to_the_styling_processing_model(&self, element: &HTMLElement) -> bool {
186 if !element.upcast::<Element>().is_connected() {
187 return false;
188 }
189
190 if !matches!(&self.source, StylesheetContextSource::LinkElement) {
197 return true;
198 }
199 let link = element.downcast::<HTMLLinkElement>().unwrap();
200 self.request_generation_id
201 .is_none_or(|generation| generation == link.get_request_generation_id())
202 }
203
204 fn contributes_a_script_blocking_style_sheet(
206 &self,
207 element: &HTMLElement,
208 owner: &dyn StylesheetOwner,
209 document: &Document,
210 ) -> bool {
211 owner.parser_inserted()
213 && element.downcast::<HTMLLinkElement>().is_none_or(|link|
216 self.contributes_to_the_styling_processing_model(element)
217 && !link.is_effectively_disabled()
219 )
220 && element.media_attribute_matches_media_environment()
222 && *element.owner_document() == *document
224 }
229
230 fn decrement_blockers_and_finish_load(
231 self,
232 document: &Document,
233 cx: &mut js::context::JSContext,
234 ) {
235 if self.is_script_blocking {
236 document.decrement_script_blocking_stylesheet_count();
237 }
238
239 if self.is_render_blocking {
240 document.decrement_render_blocking_element_count();
241 }
242
243 document.finish_load(LoadType::Stylesheet(self.url), cx);
244 }
245
246 fn do_post_parse_tasks(
247 self,
248 success: bool,
249 stylesheet: Arc<Stylesheet>,
250 cx: &mut js::context::JSContext,
251 ) {
252 let element = self.element.root();
253 let document = self.document.root();
254 let owner = element
255 .upcast::<Element>()
256 .as_stylesheet_owner()
257 .expect("Stylesheet not loaded by <style> or <link> element!");
258
259 match &self.source {
260 StylesheetContextSource::LinkElement => {
262 let link = element
263 .downcast::<HTMLLinkElement>()
264 .expect("Should be HTMLinkElement due to StylesheetContextSource");
265 if self
269 .request_generation_id
270 .is_some_and(|generation| generation != link.get_request_generation_id())
271 {
272 self.decrement_blockers_and_finish_load(&document, cx);
273 return;
274 }
275 if link.is_effectively_disabled() {
279 stylesheet.set_disabled(true);
280 }
281 link.set_stylesheet(stylesheet);
288 },
289 StylesheetContextSource::Import(import_rule) => {
290 let window = element.owner_window();
292 let document_context = window.web_font_context();
293
294 document.load_web_fonts_from_stylesheet(&stylesheet, &document_context);
297
298 let mut guard = document.style_shared_author_lock().write();
299 import_rule.write_with(&mut guard).stylesheet = ImportSheet::Sheet(stylesheet);
300 },
301 }
302
303 if let Some(ref shadow_root) = self.shadow_root {
304 shadow_root.root().invalidate_stylesheets();
305 } else {
306 document.invalidate_stylesheets();
307 }
308 owner.set_origin_clean(self.origin_clean);
309
310 if let Some(any_failed) = owner.load_finished(success) {
317 let event = match any_failed {
320 true => atom!("error"),
321 false => atom!("load"),
322 };
323 element.upcast::<EventTarget>().fire_event(cx, event);
324 }
325 self.decrement_blockers_and_finish_load(&document, cx);
331 }
332}
333
334impl FetchResponseListener for StylesheetContext {
335 fn process_request_body(&mut self, _: RequestId) {}
336
337 fn process_response(
338 &mut self,
339 _: &mut js::context::JSContext,
340 _: RequestId,
341 metadata: Result<FetchMetadata, NetworkError>,
342 ) {
343 if let Ok(FetchMetadata::Filtered {
344 filtered: FilteredMetadata::Opaque | FilteredMetadata::OpaqueRedirect(_),
345 ..
346 }) = metadata
347 {
348 self.origin_clean = false;
349 }
350
351 self.metadata = metadata.ok().map(|m| match m {
352 FetchMetadata::Unfiltered(m) => m,
353 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
354 });
355 }
356
357 fn process_response_chunk(
358 &mut self,
359 _: &mut js::context::JSContext,
360 _: RequestId,
361 mut payload: Vec<u8>,
362 ) {
363 self.data.append(&mut payload);
364 }
365
366 fn process_response_eof(
367 mut self,
368 cx: &mut js::context::JSContext,
369 _: RequestId,
370 status: Result<(), NetworkError>,
371 timing: ResourceFetchTiming,
372 ) {
373 network_listener::submit_timing(cx, &self, &status, &timing);
374
375 let document = self.document.root();
376 let Some(metadata) = self.metadata.as_ref() else {
377 let empty_stylesheet = self.empty_stylesheet(&document);
378 self.do_post_parse_tasks(false, empty_stylesheet, cx);
379 return;
380 };
381
382 let element = self.element.root();
383
384 if element.downcast::<HTMLLinkElement>().is_some() {
386 let is_css = MimeClassifier::is_css(
388 &metadata.resource_content_type_metadata(LoadContext::Style, &self.data),
389 ) || (
390 document.quirks_mode() == QuirksMode::Quirks &&
396 document.origin().immutable().clone() == metadata.final_url.origin()
397 );
398
399 if !is_css {
400 let empty_stylesheet = self.empty_stylesheet(&document);
401 self.do_post_parse_tasks(false, empty_stylesheet, cx);
402 return;
403 }
404
405 if !self.contributes_to_the_styling_processing_model(&element) {
408 self.decrement_blockers_and_finish_load(&document, cx);
410 return;
412 }
413 }
414
415 if metadata.status != http::StatusCode::OK {
416 let empty_stylesheet = self.empty_stylesheet(&document);
417 self.do_post_parse_tasks(false, empty_stylesheet, cx);
418 return;
419 }
420
421 self.unminify_css(metadata.final_url.clone());
422
423 let loader = if pref!(dom_parallel_css_parsing_enabled) {
424 ElementStylesheetLoader::Asynchronous(AsynchronousStylesheetLoader::new(&element))
425 } else {
426 ElementStylesheetLoader::Synchronous { element: &element }
427 };
428 loader.parse(self, &element, &document, cx);
429 }
430
431 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
432 let global = &self.resource_timing_global();
433 global.report_csp_violations(violations, None, None);
434 }
435}
436
437impl ResourceTimingListener for StylesheetContext {
438 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
439 let initiator_type = InitiatorType::LocalName(
440 self.element
441 .root()
442 .upcast::<Element>()
443 .local_name()
444 .to_string(),
445 );
446 (initiator_type, self.url.clone())
447 }
448
449 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
450 self.element.root().owner_document().global()
451 }
452}
453
454pub(crate) enum ElementStylesheetLoader<'a> {
455 Synchronous { element: &'a HTMLElement },
456 Asynchronous(AsynchronousStylesheetLoader),
457}
458
459impl<'a> ElementStylesheetLoader<'a> {
460 pub(crate) fn new(element: &'a HTMLElement) -> Self {
461 ElementStylesheetLoader::Synchronous { element }
462 }
463}
464
465impl ElementStylesheetLoader<'_> {
466 pub(crate) fn load_with_element(
467 cx: &mut JSContext,
468 element: &HTMLElement,
469 source: StylesheetContextSource,
470 media: Arc<Locked<MediaList>>,
471 url: ServoUrl,
472 cors_setting: Option<CorsSettings>,
473 integrity_metadata: String,
474 ) {
475 let document = element.owner_document();
476 let shadow_root = element
477 .containing_shadow_root()
478 .map(|shadow_root| Trusted::new(&*shadow_root));
479 let generation = element
480 .downcast::<HTMLLinkElement>()
481 .map(HTMLLinkElement::get_request_generation_id);
482 let mut context = StylesheetContext {
483 element: Trusted::new(element),
484 source,
485 media,
486 url: url.clone(),
487 metadata: None,
488 data: vec![],
489 document: Trusted::new(&*document),
490 shadow_root,
491 origin_clean: true,
492 request_generation_id: generation,
493 is_script_blocking: false,
494 is_render_blocking: false,
495 };
496
497 let owner = element
498 .upcast::<Element>()
499 .as_stylesheet_owner()
500 .expect("Stylesheet not loaded by <style> or <link> element!");
501 let referrer_policy = owner.referrer_policy(cx);
502 owner.increment_pending_loads_count();
503
504 context.is_script_blocking =
509 context.contributes_a_script_blocking_style_sheet(element, owner, &document);
510 if context.is_script_blocking {
511 document.increment_script_blocking_stylesheet_count();
512 }
513
514 context.is_render_blocking = element.media_attribute_matches_media_environment() &&
517 owner.potentially_render_blocking() &&
518 document.allows_adding_render_blocking_elements();
519 if context.is_render_blocking {
520 document.increment_render_blocking_element_count();
521 }
522
523 let global = element.global();
525 let request = create_a_potential_cors_request(
526 Some(document.webview_id()),
527 url.clone(),
528 Destination::Style,
529 cors_setting,
530 None,
531 global.get_referrer(),
532 )
533 .with_global_scope(&global)
534 .referrer_policy(referrer_policy)
535 .integrity_metadata(integrity_metadata);
536
537 document.fetch(LoadType::Stylesheet(url), request, context);
538 }
539
540 fn parse(
541 self,
542 listener: StylesheetContext,
543 element: &HTMLElement,
544 document: &Document,
545 cx: &mut js::context::JSContext,
546 ) {
547 let shared_lock = document.style_shared_author_lock().clone();
548 let quirks_mode = document.quirks_mode();
549 let window = element.owner_window();
550
551 match self {
552 ElementStylesheetLoader::Synchronous { .. } => {
553 let stylesheet =
554 listener.parse(quirks_mode, shared_lock, window.css_error_reporter(), self);
555 listener.do_post_parse_tasks(true, stylesheet, cx);
556 },
557 ElementStylesheetLoader::Asynchronous(asynchronous_loader) => {
558 let css_error_reporter = window.css_error_reporter().clone();
559
560 let parse_stylesheet = move || {
561 let pipeline_id = asynchronous_loader.pipeline_id;
562 let main_thread_sender = asynchronous_loader.main_thread_sender.clone();
563 let loader = ElementStylesheetLoader::Asynchronous(asynchronous_loader);
564 let stylesheet =
565 listener.parse(quirks_mode, shared_lock, &css_error_reporter, loader);
566
567 let task = task!(finish_parsing_of_stylesheet_on_main_thread: move |cx| {
568 listener.do_post_parse_tasks(true, stylesheet, cx);
569 });
570 let _ = main_thread_sender.send(MainThreadScriptMsg::Common(
571 CommonScriptMsg::Task(
572 ScriptThreadEventCategory::StylesheetLoad,
573 Box::new(task),
574 Some(pipeline_id),
575 TaskSourceName::Networking,
576 ),
577 ));
578 };
579
580 let thread_pool = STYLE_THREAD_POOL.pool();
581 if let Some(thread_pool) = thread_pool.as_ref() {
582 thread_pool.spawn(parse_stylesheet);
583 } else {
584 parse_stylesheet();
585 }
586 },
587 };
588 }
589}
590
591impl StyleStylesheetLoader for ElementStylesheetLoader<'_> {
592 fn request_stylesheet(
595 &self,
596 url: CssUrl,
597 source_location: SourceLocation,
598 lock: &SharedRwLock,
599 media: Arc<Locked<MediaList>>,
600 supports: Option<ImportSupportsCondition>,
601 layer: ImportLayer,
602 ) -> Arc<Locked<ImportRule>> {
603 if supports.as_ref().is_some_and(|s| !s.enabled) {
605 return Arc::new(lock.wrap(ImportRule {
606 url,
607 stylesheet: ImportSheet::new_refused(),
608 supports,
609 layer,
610 source_location,
611 }));
612 }
613
614 let resolved_url = match url.url().cloned() {
615 Some(url) => url,
616 None => {
617 return Arc::new(lock.wrap(ImportRule {
618 url,
619 stylesheet: ImportSheet::new_refused(),
620 supports,
621 layer,
622 source_location,
623 }));
624 },
625 };
626
627 let import_rule = Arc::new(lock.wrap(ImportRule {
628 url,
629 stylesheet: ImportSheet::new_pending(),
630 supports,
631 layer,
632 source_location,
633 }));
634
635 let source = StylesheetContextSource::Import(import_rule.clone());
638
639 match self {
640 ElementStylesheetLoader::Synchronous { element } => {
641 #[expect(unsafe_code)]
643 let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
644 Self::load_with_element(
645 &mut cx,
646 element,
647 source,
648 media,
649 resolved_url.into(),
650 None,
651 "".to_owned(),
652 );
653 },
654 ElementStylesheetLoader::Asynchronous(AsynchronousStylesheetLoader {
655 element,
656 main_thread_sender,
657 pipeline_id,
658 }) => {
659 let element = element.clone();
660 let task = task!(load_import_stylesheet_on_main_thread: move || {
661 #[expect(unsafe_code)]
663 let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
664 Self::load_with_element(
665 &mut cx,
666 &element.root(),
667 source,
668 media,
669 resolved_url.into(),
670 None,
671 "".to_owned()
672 );
673 });
674 let _ =
675 main_thread_sender.send(MainThreadScriptMsg::Common(CommonScriptMsg::Task(
676 ScriptThreadEventCategory::StylesheetLoad,
677 Box::new(task),
678 Some(*pipeline_id),
679 TaskSourceName::Networking,
680 )));
681 },
682 }
683
684 import_rule
685 }
686}
687
688pub(crate) struct AsynchronousStylesheetLoader {
689 element: Trusted<HTMLElement>,
690 main_thread_sender: Sender<MainThreadScriptMsg>,
691 pipeline_id: PipelineId,
692}
693
694impl AsynchronousStylesheetLoader {
695 pub(crate) fn new(element: &HTMLElement) -> Self {
696 let window = element.owner_window();
697 Self {
698 element: Trusted::new(element),
699 main_thread_sender: window.main_thread_script_chan().clone(),
700 pipeline_id: window.pipeline_id(),
701 }
702 }
703}