1use std::io::{Read, Seek, Write};
6use std::sync::atomic::{AtomicUsize, Ordering};
7
8use crossbeam_channel::Sender;
9use cssparser::SourceLocation;
10use encoding_rs::UTF_8;
11use js::context::JSContext;
12use net_traits::mime_classifier::MimeClassifier;
13use net_traits::request::{CorsSettings, Destination, RequestId};
14use net_traits::{
15 FetchMetadata, FilteredMetadata, LoadContext, Metadata, NetworkError, ReferrerPolicy,
16 ResourceFetchTiming,
17};
18use servo_arc::Arc;
19use servo_base::id::PipelineId;
20use servo_config::pref;
21use servo_url::ServoUrl;
22use style::context::QuirksMode;
23use style::global_style_data::STYLE_THREAD_POOL;
24use style::media_queries::MediaList;
25use style::shared_lock::{Locked, SharedRwLock};
26use style::stylesheets::import_rule::{ImportLayer, ImportSheet, ImportSupportsCondition};
27use style::stylesheets::{
28 ImportRule, Origin, Stylesheet, StylesheetLoader as StyleStylesheetLoader, UrlExtraData,
29};
30use style::values::CssUrl;
31
32use crate::document_loader::LoadType;
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;
47use crate::dom::window::CSSErrorReporter;
48use crate::fetch::{RequestWithGlobalScope, create_a_potential_cors_request};
49use crate::messaging::{CommonScriptMsg, MainThreadScriptMsg};
50use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
51use crate::script_runtime::ScriptThreadEventCategory;
52use crate::task_source::TaskSourceName;
53use crate::unminify::{
54 BeautifyFileType, create_output_file, create_temp_files, execute_js_beautify,
55};
56
57#[derive(Clone, Copy, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
60pub(crate) struct StylesheetContextId(usize);
61
62impl StylesheetContextId {
63 fn next() -> Self {
64 static NEXT_STYLESHEET_CONTEXT_INDEX: AtomicUsize = AtomicUsize::new(0);
65 Self(NEXT_STYLESHEET_CONTEXT_INDEX.fetch_add(1, Ordering::Relaxed))
66 }
67}
68
69pub(crate) trait StylesheetOwner {
70 fn parser_inserted(&self) -> bool;
73
74 fn potentially_render_blocking(&self) -> bool;
76
77 fn referrer_policy(&self, cx: &mut JSContext) -> ReferrerPolicy;
79
80 fn increment_pending_loads_count(&self);
82
83 fn load_finished(&self, successful: bool) -> Option<bool>;
86
87 fn set_origin_clean(&self, origin_clean: bool);
89}
90
91pub(crate) enum StylesheetContextSource {
92 LinkElement,
93 Import(Arc<Locked<ImportRule>>),
94}
95
96struct StylesheetContext {
98 id: StylesheetContextId,
101 element: Trusted<HTMLElement>,
103 source: StylesheetContextSource,
104 media: Arc<Locked<MediaList>>,
105 url: ServoUrl,
106 metadata: Option<Metadata>,
107 data: Vec<u8>,
109 document: Trusted<Document>,
111 shadow_root: Option<Trusted<ShadowRoot>>,
112 origin_clean: bool,
113 request_generation_id: Option<RequestGenerationId>,
116 is_script_blocking: bool,
118 is_render_blocking: bool,
120}
121
122impl StylesheetContext {
123 fn unminify_css(&mut self, file_url: ServoUrl) {
124 let Some(unminified_dir) = self.document.root().window().unminified_css_dir() else {
125 return;
126 };
127
128 let mut style_content = std::mem::take(&mut self.data);
129 if let Some((input, mut output)) = create_temp_files() &&
130 execute_js_beautify(
131 input.path(),
132 output.try_clone().unwrap(),
133 BeautifyFileType::Css,
134 )
135 {
136 output.seek(std::io::SeekFrom::Start(0)).unwrap();
137 output.read_to_end(&mut style_content).unwrap();
138 }
139 match create_output_file(unminified_dir, &file_url, None) {
140 Ok(mut file) => {
141 file.write_all(&style_content).unwrap();
142 },
143 Err(why) => {
144 log::warn!("Could not store script {:?}", why);
145 },
146 }
147
148 self.data = style_content;
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, stylesheet);
304 },
305 StylesheetContextSource::Import(import_rule) => {
306 document.load_web_fonts_from_stylesheet(cx, &stylesheet);
309
310 let mut guard = document.style_shared_author_lock().write();
311 import_rule.write_with(&mut guard).stylesheet = ImportSheet::Sheet(stylesheet);
312 },
313 }
314
315 if let Some(ref shadow_root) = self.shadow_root {
316 shadow_root.root().invalidate_stylesheets();
317 } else {
318 document.invalidate_stylesheets();
319 }
320 owner.set_origin_clean(self.origin_clean);
321
322 if let Some(any_failed) = owner.load_finished(success) {
329 let event = match any_failed {
332 true => atom!("error"),
333 false => atom!("load"),
334 };
335 element.upcast::<EventTarget>().fire_event(cx, event);
336 }
337 self.decrement_blockers_and_finish_load(&document, cx);
343 }
344}
345
346impl FetchResponseListener for StylesheetContext {
347 fn process_request_body(&mut self, _: RequestId) {}
348
349 fn process_response(
350 &mut self,
351 _: &mut js::context::JSContext,
352 _: RequestId,
353 metadata: Result<FetchMetadata, NetworkError>,
354 ) {
355 if let Ok(FetchMetadata::Filtered {
356 filtered: FilteredMetadata::Opaque | FilteredMetadata::OpaqueRedirect(_),
357 ..
358 }) = metadata
359 {
360 self.origin_clean = false;
361 }
362
363 self.metadata = metadata.ok().map(|m| match m {
364 FetchMetadata::Unfiltered(m) => m,
365 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
366 });
367 }
368
369 fn process_response_chunk(
370 &mut self,
371 _: &mut js::context::JSContext,
372 _: RequestId,
373 mut payload: Vec<u8>,
374 ) {
375 self.data.append(&mut payload);
376 }
377
378 fn process_response_eof(
379 mut self,
380 cx: &mut js::context::JSContext,
381 _: RequestId,
382 status: Result<(), NetworkError>,
383 timing: ResourceFetchTiming,
384 ) {
385 network_listener::submit_timing(cx, &self, &status, &timing);
386
387 let document = self.document.root();
388 let Some(metadata) = self.metadata.as_ref() else {
389 let empty_stylesheet = self.empty_stylesheet(&document);
390 self.do_post_parse_tasks(false, empty_stylesheet, cx);
391 return;
392 };
393
394 let element = self.element.root();
395
396 if element.is::<HTMLLinkElement>() {
398 let is_css = MimeClassifier::is_css(
400 &metadata.resource_content_type_metadata(LoadContext::Style, &self.data),
401 ) || (
402 document.quirks_mode() == QuirksMode::Quirks &&
408 document.origin().immutable().clone() == metadata.final_url.origin()
409 );
410
411 if !is_css {
412 let empty_stylesheet = self.empty_stylesheet(&document);
413 self.do_post_parse_tasks(false, empty_stylesheet, cx);
414 return;
415 }
416
417 if !self.contributes_to_the_styling_processing_model(&element) {
420 self.decrement_blockers_and_finish_load(&document, cx);
422 return;
424 }
425 }
426
427 if metadata.status != http::StatusCode::OK {
428 let empty_stylesheet = self.empty_stylesheet(&document);
429 self.do_post_parse_tasks(false, empty_stylesheet, cx);
430 return;
431 }
432
433 self.unminify_css(metadata.final_url.clone());
434
435 let loader = if pref!(dom_parallel_css_parsing_enabled) {
436 ElementStylesheetLoader::Asynchronous(AsynchronousStylesheetLoader::new(&element))
437 } else {
438 ElementStylesheetLoader::Synchronous { element: &element }
439 };
440 loader.parse(self, &element, &document, cx);
441 }
442
443 fn process_csp_violations(
444 &mut self,
445 cx: &mut js::context::JSContext,
446 _request_id: RequestId,
447 violations: Vec<Violation>,
448 ) {
449 let global = &self.resource_timing_global();
450 global.report_csp_violations(cx, violations, None, None);
451 }
452}
453
454impl ResourceTimingListener for StylesheetContext {
455 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
456 let initiator_type = InitiatorType::LocalName(
457 self.element
458 .root()
459 .upcast::<Element>()
460 .local_name()
461 .to_string(),
462 );
463 (initiator_type, self.url.clone())
464 }
465
466 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
467 self.element.root().owner_document().global()
468 }
469}
470
471pub(crate) enum ElementStylesheetLoader<'a> {
472 Synchronous { element: &'a HTMLElement },
473 Asynchronous(AsynchronousStylesheetLoader),
474}
475
476impl<'a> ElementStylesheetLoader<'a> {
477 pub(crate) fn new(element: &'a HTMLElement) -> Self {
478 ElementStylesheetLoader::Synchronous { element }
479 }
480}
481
482impl ElementStylesheetLoader<'_> {
483 pub(crate) fn load_with_element(
484 cx: &mut JSContext,
485 element: &HTMLElement,
486 source: StylesheetContextSource,
487 media: Arc<Locked<MediaList>>,
488 url: ServoUrl,
489 cors_setting: Option<CorsSettings>,
490 integrity_metadata: String,
491 ) {
492 let document = element.owner_document();
493 let shadow_root = element
494 .containing_shadow_root()
495 .map(|shadow_root| Trusted::new(&*shadow_root));
496 let generation = element
497 .downcast::<HTMLLinkElement>()
498 .map(HTMLLinkElement::get_request_generation_id);
499 let mut context = StylesheetContext {
500 id: StylesheetContextId::next(),
501 element: Trusted::new(element),
502 source,
503 media,
504 url: url.clone(),
505 metadata: None,
506 data: vec![],
507 document: Trusted::new(&*document),
508 shadow_root,
509 origin_clean: true,
510 request_generation_id: generation,
511 is_script_blocking: false,
512 is_render_blocking: false,
513 };
514
515 let owner = element
516 .upcast::<Element>()
517 .as_stylesheet_owner()
518 .expect("Stylesheet not loaded by <style> or <link> element!");
519 let referrer_policy = owner.referrer_policy(cx);
520 owner.increment_pending_loads_count();
521
522 context.is_script_blocking =
527 context.contributes_a_script_blocking_style_sheet(element, owner, &document);
528 if context.is_script_blocking {
529 document.add_script_blocking_stylesheet(context.id);
530 }
531
532 context.is_render_blocking = element.media_attribute_matches_media_environment() &&
535 owner.potentially_render_blocking() &&
536 document.allows_adding_render_blocking_elements();
537 if context.is_render_blocking {
538 document.increment_render_blocking_element_count();
539 }
540
541 let global = element.global();
543 let request = create_a_potential_cors_request(
544 Some(document.webview_id()),
545 url.clone(),
546 Destination::Style,
547 cors_setting,
548 None,
549 global.get_referrer(),
550 )
551 .with_global_scope(&global)
552 .referrer_policy(referrer_policy)
553 .integrity_metadata(integrity_metadata);
554
555 document.fetch(LoadType::Stylesheet(url), request, context);
556 }
557
558 fn parse(
559 self,
560 listener: StylesheetContext,
561 element: &HTMLElement,
562 document: &Document,
563 cx: &mut js::context::JSContext,
564 ) {
565 let shared_lock = document.style_shared_author_lock().clone();
566 let quirks_mode = document.quirks_mode();
567 let window = element.owner_window();
568
569 match self {
570 ElementStylesheetLoader::Synchronous { .. } => {
571 let stylesheet =
572 listener.parse(quirks_mode, shared_lock, window.css_error_reporter(), self);
573 listener.do_post_parse_tasks(true, stylesheet, cx);
574 },
575 ElementStylesheetLoader::Asynchronous(asynchronous_loader) => {
576 let css_error_reporter = window.css_error_reporter().clone();
577
578 let parse_stylesheet = move || {
579 let pipeline_id = asynchronous_loader.pipeline_id;
580 let main_thread_sender = asynchronous_loader.main_thread_sender.clone();
581 let loader = ElementStylesheetLoader::Asynchronous(asynchronous_loader);
582 let stylesheet =
583 listener.parse(quirks_mode, shared_lock, &css_error_reporter, loader);
584
585 let task = task!(finish_parsing_of_stylesheet_on_main_thread: move |cx| {
586 listener.do_post_parse_tasks(true, stylesheet, cx);
587 });
588 let _ = main_thread_sender.send(MainThreadScriptMsg::Common(
589 CommonScriptMsg::Task(
590 ScriptThreadEventCategory::StylesheetLoad,
591 Box::new(task),
592 Some(pipeline_id),
593 TaskSourceName::Networking,
594 ),
595 ));
596 };
597
598 let thread_pool = STYLE_THREAD_POOL.pool();
599 if let Some(thread_pool) = thread_pool.as_ref() {
600 thread_pool.spawn(parse_stylesheet);
601 } else {
602 parse_stylesheet();
603 }
604 },
605 };
606 }
607}
608
609impl StyleStylesheetLoader for ElementStylesheetLoader<'_> {
610 fn request_stylesheet(
613 &self,
614 url: CssUrl,
615 source_location: SourceLocation,
616 lock: &SharedRwLock,
617 media: Arc<Locked<MediaList>>,
618 supports: Option<ImportSupportsCondition>,
619 layer: ImportLayer,
620 ) -> Arc<Locked<ImportRule>> {
621 if supports.as_ref().is_some_and(|s| !s.enabled) {
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 let resolved_url = match url.url().cloned() {
633 Some(url) => url,
634 None => {
635 return Arc::new(lock.wrap(ImportRule {
636 url,
637 stylesheet: ImportSheet::new_refused(),
638 supports,
639 layer,
640 source_location,
641 }));
642 },
643 };
644
645 let import_rule = Arc::new(lock.wrap(ImportRule {
646 url,
647 stylesheet: ImportSheet::new_pending(),
648 supports,
649 layer,
650 source_location,
651 }));
652
653 let source = StylesheetContextSource::Import(import_rule.clone());
656
657 match self {
658 ElementStylesheetLoader::Synchronous { element } => {
659 #[expect(unsafe_code)]
661 let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
662 Self::load_with_element(
663 &mut cx,
664 element,
665 source,
666 media,
667 resolved_url.into(),
668 None,
669 "".to_owned(),
670 );
671 },
672 ElementStylesheetLoader::Asynchronous(AsynchronousStylesheetLoader {
673 element,
674 main_thread_sender,
675 pipeline_id,
676 }) => {
677 let element = element.clone();
678 let task = task!(load_import_stylesheet_on_main_thread: move || {
679 #[expect(unsafe_code)]
681 let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
682 Self::load_with_element(
683 &mut cx,
684 &element.root(),
685 source,
686 media,
687 resolved_url.into(),
688 None,
689 "".to_owned()
690 );
691 });
692 let _ =
693 main_thread_sender.send(MainThreadScriptMsg::Common(CommonScriptMsg::Task(
694 ScriptThreadEventCategory::StylesheetLoad,
695 Box::new(task),
696 Some(*pipeline_id),
697 TaskSourceName::Networking,
698 )));
699 },
700 }
701
702 import_rule
703 }
704}
705
706pub(crate) struct AsynchronousStylesheetLoader {
707 element: Trusted<HTMLElement>,
708 main_thread_sender: Sender<MainThreadScriptMsg>,
709 pipeline_id: PipelineId,
710}
711
712impl AsynchronousStylesheetLoader {
713 pub(crate) fn new(element: &HTMLElement) -> Self {
714 let window = element.owner_window();
715 Self {
716 element: Trusted::new(element),
717 main_thread_sender: window.main_thread_script_chan().clone(),
718 pipeline_id: window.pipeline_id(),
719 }
720 }
721}