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