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(cx, stylesheet);
288 },
289 StylesheetContextSource::Import(import_rule) => {
290 document.load_web_fonts_from_stylesheet(cx, &stylesheet);
293
294 let mut guard = document.style_shared_author_lock().write();
295 import_rule.write_with(&mut guard).stylesheet = ImportSheet::Sheet(stylesheet);
296 },
297 }
298
299 if let Some(ref shadow_root) = self.shadow_root {
300 shadow_root.root().invalidate_stylesheets();
301 } else {
302 document.invalidate_stylesheets();
303 }
304 owner.set_origin_clean(self.origin_clean);
305
306 if let Some(any_failed) = owner.load_finished(success) {
313 let event = match any_failed {
316 true => atom!("error"),
317 false => atom!("load"),
318 };
319 element.upcast::<EventTarget>().fire_event(cx, event);
320 }
321 self.decrement_blockers_and_finish_load(&document, cx);
327 }
328}
329
330impl FetchResponseListener for StylesheetContext {
331 fn process_request_body(&mut self, _: RequestId) {}
332
333 fn process_response(
334 &mut self,
335 _: &mut js::context::JSContext,
336 _: RequestId,
337 metadata: Result<FetchMetadata, NetworkError>,
338 ) {
339 if let Ok(FetchMetadata::Filtered {
340 filtered: FilteredMetadata::Opaque | FilteredMetadata::OpaqueRedirect(_),
341 ..
342 }) = metadata
343 {
344 self.origin_clean = false;
345 }
346
347 self.metadata = metadata.ok().map(|m| match m {
348 FetchMetadata::Unfiltered(m) => m,
349 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
350 });
351 }
352
353 fn process_response_chunk(
354 &mut self,
355 _: &mut js::context::JSContext,
356 _: RequestId,
357 mut payload: Vec<u8>,
358 ) {
359 self.data.append(&mut payload);
360 }
361
362 fn process_response_eof(
363 mut self,
364 cx: &mut js::context::JSContext,
365 _: RequestId,
366 status: Result<(), NetworkError>,
367 timing: ResourceFetchTiming,
368 ) {
369 network_listener::submit_timing(cx, &self, &status, &timing);
370
371 let document = self.document.root();
372 let Some(metadata) = self.metadata.as_ref() else {
373 let empty_stylesheet = self.empty_stylesheet(&document);
374 self.do_post_parse_tasks(false, empty_stylesheet, cx);
375 return;
376 };
377
378 let element = self.element.root();
379
380 if element.is::<HTMLLinkElement>() {
382 let is_css = MimeClassifier::is_css(
384 &metadata.resource_content_type_metadata(LoadContext::Style, &self.data),
385 ) || (
386 document.quirks_mode() == QuirksMode::Quirks &&
392 document.origin().immutable().clone() == metadata.final_url.origin()
393 );
394
395 if !is_css {
396 let empty_stylesheet = self.empty_stylesheet(&document);
397 self.do_post_parse_tasks(false, empty_stylesheet, cx);
398 return;
399 }
400
401 if !self.contributes_to_the_styling_processing_model(&element) {
404 self.decrement_blockers_and_finish_load(&document, cx);
406 return;
408 }
409 }
410
411 if metadata.status != http::StatusCode::OK {
412 let empty_stylesheet = self.empty_stylesheet(&document);
413 self.do_post_parse_tasks(false, empty_stylesheet, cx);
414 return;
415 }
416
417 self.unminify_css(metadata.final_url.clone());
418
419 let loader = if pref!(dom_parallel_css_parsing_enabled) {
420 ElementStylesheetLoader::Asynchronous(AsynchronousStylesheetLoader::new(&element))
421 } else {
422 ElementStylesheetLoader::Synchronous { element: &element }
423 };
424 loader.parse(self, &element, &document, cx);
425 }
426
427 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
428 let global = &self.resource_timing_global();
429 global.report_csp_violations(violations, None, None);
430 }
431}
432
433impl ResourceTimingListener for StylesheetContext {
434 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
435 let initiator_type = InitiatorType::LocalName(
436 self.element
437 .root()
438 .upcast::<Element>()
439 .local_name()
440 .to_string(),
441 );
442 (initiator_type, self.url.clone())
443 }
444
445 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
446 self.element.root().owner_document().global()
447 }
448}
449
450pub(crate) enum ElementStylesheetLoader<'a> {
451 Synchronous { element: &'a HTMLElement },
452 Asynchronous(AsynchronousStylesheetLoader),
453}
454
455impl<'a> ElementStylesheetLoader<'a> {
456 pub(crate) fn new(element: &'a HTMLElement) -> Self {
457 ElementStylesheetLoader::Synchronous { element }
458 }
459}
460
461impl ElementStylesheetLoader<'_> {
462 pub(crate) fn load_with_element(
463 cx: &mut JSContext,
464 element: &HTMLElement,
465 source: StylesheetContextSource,
466 media: Arc<Locked<MediaList>>,
467 url: ServoUrl,
468 cors_setting: Option<CorsSettings>,
469 integrity_metadata: String,
470 ) {
471 let document = element.owner_document();
472 let shadow_root = element
473 .containing_shadow_root()
474 .map(|shadow_root| Trusted::new(&*shadow_root));
475 let generation = element
476 .downcast::<HTMLLinkElement>()
477 .map(HTMLLinkElement::get_request_generation_id);
478 let mut context = StylesheetContext {
479 element: Trusted::new(element),
480 source,
481 media,
482 url: url.clone(),
483 metadata: None,
484 data: vec![],
485 document: Trusted::new(&*document),
486 shadow_root,
487 origin_clean: true,
488 request_generation_id: generation,
489 is_script_blocking: false,
490 is_render_blocking: false,
491 };
492
493 let owner = element
494 .upcast::<Element>()
495 .as_stylesheet_owner()
496 .expect("Stylesheet not loaded by <style> or <link> element!");
497 let referrer_policy = owner.referrer_policy(cx);
498 owner.increment_pending_loads_count();
499
500 context.is_script_blocking =
505 context.contributes_a_script_blocking_style_sheet(element, owner, &document);
506 if context.is_script_blocking {
507 document.increment_script_blocking_stylesheet_count();
508 }
509
510 context.is_render_blocking = element.media_attribute_matches_media_environment() &&
513 owner.potentially_render_blocking() &&
514 document.allows_adding_render_blocking_elements();
515 if context.is_render_blocking {
516 document.increment_render_blocking_element_count();
517 }
518
519 let global = element.global();
521 let request = create_a_potential_cors_request(
522 Some(document.webview_id()),
523 url.clone(),
524 Destination::Style,
525 cors_setting,
526 None,
527 global.get_referrer(),
528 )
529 .with_global_scope(&global)
530 .referrer_policy(referrer_policy)
531 .integrity_metadata(integrity_metadata);
532
533 document.fetch(LoadType::Stylesheet(url), request, context);
534 }
535
536 fn parse(
537 self,
538 listener: StylesheetContext,
539 element: &HTMLElement,
540 document: &Document,
541 cx: &mut js::context::JSContext,
542 ) {
543 let shared_lock = document.style_shared_author_lock().clone();
544 let quirks_mode = document.quirks_mode();
545 let window = element.owner_window();
546
547 match self {
548 ElementStylesheetLoader::Synchronous { .. } => {
549 let stylesheet =
550 listener.parse(quirks_mode, shared_lock, window.css_error_reporter(), self);
551 listener.do_post_parse_tasks(true, stylesheet, cx);
552 },
553 ElementStylesheetLoader::Asynchronous(asynchronous_loader) => {
554 let css_error_reporter = window.css_error_reporter().clone();
555
556 let parse_stylesheet = move || {
557 let pipeline_id = asynchronous_loader.pipeline_id;
558 let main_thread_sender = asynchronous_loader.main_thread_sender.clone();
559 let loader = ElementStylesheetLoader::Asynchronous(asynchronous_loader);
560 let stylesheet =
561 listener.parse(quirks_mode, shared_lock, &css_error_reporter, loader);
562
563 let task = task!(finish_parsing_of_stylesheet_on_main_thread: move |cx| {
564 listener.do_post_parse_tasks(true, stylesheet, cx);
565 });
566 let _ = main_thread_sender.send(MainThreadScriptMsg::Common(
567 CommonScriptMsg::Task(
568 ScriptThreadEventCategory::StylesheetLoad,
569 Box::new(task),
570 Some(pipeline_id),
571 TaskSourceName::Networking,
572 ),
573 ));
574 };
575
576 let thread_pool = STYLE_THREAD_POOL.pool();
577 if let Some(thread_pool) = thread_pool.as_ref() {
578 thread_pool.spawn(parse_stylesheet);
579 } else {
580 parse_stylesheet();
581 }
582 },
583 };
584 }
585}
586
587impl StyleStylesheetLoader for ElementStylesheetLoader<'_> {
588 fn request_stylesheet(
591 &self,
592 url: CssUrl,
593 source_location: SourceLocation,
594 lock: &SharedRwLock,
595 media: Arc<Locked<MediaList>>,
596 supports: Option<ImportSupportsCondition>,
597 layer: ImportLayer,
598 ) -> Arc<Locked<ImportRule>> {
599 if supports.as_ref().is_some_and(|s| !s.enabled) {
601 return Arc::new(lock.wrap(ImportRule {
602 url,
603 stylesheet: ImportSheet::new_refused(),
604 supports,
605 layer,
606 source_location,
607 }));
608 }
609
610 let resolved_url = match url.url().cloned() {
611 Some(url) => url,
612 None => {
613 return Arc::new(lock.wrap(ImportRule {
614 url,
615 stylesheet: ImportSheet::new_refused(),
616 supports,
617 layer,
618 source_location,
619 }));
620 },
621 };
622
623 let import_rule = Arc::new(lock.wrap(ImportRule {
624 url,
625 stylesheet: ImportSheet::new_pending(),
626 supports,
627 layer,
628 source_location,
629 }));
630
631 let source = StylesheetContextSource::Import(import_rule.clone());
634
635 match self {
636 ElementStylesheetLoader::Synchronous { element } => {
637 #[expect(unsafe_code)]
639 let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
640 Self::load_with_element(
641 &mut cx,
642 element,
643 source,
644 media,
645 resolved_url.into(),
646 None,
647 "".to_owned(),
648 );
649 },
650 ElementStylesheetLoader::Asynchronous(AsynchronousStylesheetLoader {
651 element,
652 main_thread_sender,
653 pipeline_id,
654 }) => {
655 let element = element.clone();
656 let task = task!(load_import_stylesheet_on_main_thread: move || {
657 #[expect(unsafe_code)]
659 let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
660 Self::load_with_element(
661 &mut cx,
662 &element.root(),
663 source,
664 media,
665 resolved_url.into(),
666 None,
667 "".to_owned()
668 );
669 });
670 let _ =
671 main_thread_sender.send(MainThreadScriptMsg::Common(CommonScriptMsg::Task(
672 ScriptThreadEventCategory::StylesheetLoad,
673 Box::new(task),
674 Some(*pipeline_id),
675 TaskSourceName::Networking,
676 )));
677 },
678 }
679
680 import_rule
681 }
682}
683
684pub(crate) struct AsynchronousStylesheetLoader {
685 element: Trusted<HTMLElement>,
686 main_thread_sender: Sender<MainThreadScriptMsg>,
687 pipeline_id: PipelineId,
688}
689
690impl AsynchronousStylesheetLoader {
691 pub(crate) fn new(element: &HTMLElement) -> Self {
692 let window = element.owner_window();
693 Self {
694 element: Trusted::new(element),
695 main_thread_sender: window.main_thread_script_chan().clone(),
696 pipeline_id: window.pipeline_id(),
697 }
698 }
699}