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, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming,
15};
16use servo_arc::Arc;
17use servo_config::pref;
18use servo_url::ServoUrl;
19use style::context::QuirksMode;
20use style::global_style_data::STYLE_THREAD_POOL;
21use style::media_queries::MediaList;
22use style::shared_lock::{Locked, SharedRwLock};
23use style::stylesheets::import_rule::{ImportLayer, ImportSheet, ImportSupportsCondition};
24use style::stylesheets::{
25 ImportRule, Origin, Stylesheet, StylesheetLoader as StyleStylesheetLoader, UrlExtraData,
26};
27use style::values::CssUrl;
28
29use crate::document_loader::LoadType;
30use crate::dom::bindings::inheritance::Castable;
31use crate::dom::bindings::refcounted::Trusted;
32use crate::dom::bindings::reflector::DomGlobal;
33use crate::dom::bindings::root::DomRoot;
34use crate::dom::csp::{GlobalCspReporting, Violation};
35use crate::dom::document::Document;
36use crate::dom::element::Element;
37use crate::dom::eventtarget::EventTarget;
38use crate::dom::globalscope::GlobalScope;
39use crate::dom::html::htmlelement::HTMLElement;
40use crate::dom::html::htmllinkelement::{HTMLLinkElement, RequestGenerationId};
41use crate::dom::node::NodeTraits;
42use crate::dom::performance::performanceresourcetiming::InitiatorType;
43use crate::dom::shadowroot::ShadowRoot;
44use crate::dom::window::CSSErrorReporter;
45use crate::fetch::create_a_potential_cors_request;
46use crate::messaging::{CommonScriptMsg, MainThreadScriptMsg};
47use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
48use crate::script_runtime::{CanGc, ScriptThreadEventCategory};
49use crate::task_source::TaskSourceName;
50use crate::unminify::{
51 BeautifyFileType, create_output_file, create_temp_files, execute_js_beautify,
52};
53
54pub(crate) trait StylesheetOwner {
55 fn parser_inserted(&self) -> bool;
58
59 fn referrer_policy(&self) -> ReferrerPolicy;
61
62 fn increment_pending_loads_count(&self);
64
65 fn load_finished(&self, successful: bool) -> Option<bool>;
68
69 fn set_origin_clean(&self, origin_clean: bool);
71}
72
73pub(crate) enum StylesheetContextSource {
74 LinkElement,
75 Import(Arc<Locked<ImportRule>>),
76}
77
78struct StylesheetContext {
80 element: Trusted<HTMLElement>,
82 source: StylesheetContextSource,
83 media: Arc<Locked<MediaList>>,
84 url: ServoUrl,
85 metadata: Option<Metadata>,
86 data: Vec<u8>,
88 document: Trusted<Document>,
90 shadow_root: Option<Trusted<ShadowRoot>>,
91 origin_clean: bool,
92 request_generation_id: Option<RequestGenerationId>,
95}
96
97impl StylesheetContext {
98 fn unminify_css(&mut self, file_url: ServoUrl) {
99 let Some(unminified_dir) = self.document.root().window().unminified_css_dir() else {
100 return;
101 };
102
103 let mut style_content = std::mem::take(&mut self.data);
104 if let Some((input, mut output)) = create_temp_files() {
105 if execute_js_beautify(
106 input.path(),
107 output.try_clone().unwrap(),
108 BeautifyFileType::Css,
109 ) {
110 output.seek(std::io::SeekFrom::Start(0)).unwrap();
111 output.read_to_end(&mut style_content).unwrap();
112 }
113 }
114 match create_output_file(unminified_dir, &file_url, None) {
115 Ok(mut file) => {
116 file.write_all(&style_content).unwrap();
117 },
118 Err(why) => {
119 log::warn!("Could not store script {:?}", why);
120 },
121 }
122
123 self.data = style_content;
124 }
125
126 fn parse(
127 &self,
128 quirks_mode: QuirksMode,
129 shared_lock: SharedRwLock,
130 css_error_reporter: &CSSErrorReporter,
131 loader: ElementStylesheetLoader<'_>,
132 ) -> Arc<Stylesheet> {
133 let metadata = self
134 .metadata
135 .as_ref()
136 .expect("Should never call parse without metadata.");
137
138 let _span = profile_traits::trace_span!("ParseStylesheet").entered();
139 Arc::new(Stylesheet::from_bytes(
140 &self.data,
141 UrlExtraData(metadata.final_url.get_arc()),
142 metadata.charset.as_deref(),
143 Some(UTF_8),
145 Origin::Author,
146 self.media.clone(),
147 shared_lock,
148 Some(&loader),
149 Some(css_error_reporter),
150 quirks_mode,
151 ))
152 }
153
154 fn contributes_to_the_styling_processing_model(&self, element: &HTMLElement) -> bool {
155 if !element.upcast::<Element>().is_connected() {
156 return false;
157 }
158
159 if !matches!(&self.source, StylesheetContextSource::LinkElement) {
166 return true;
167 }
168 let link = element.downcast::<HTMLLinkElement>().unwrap();
169 self.request_generation_id
170 .is_none_or(|generation| generation == link.get_request_generation_id())
171 }
172
173 fn decrement_load_and_render_blockers(&self, owner: &dyn StylesheetOwner, document: &Document) {
174 if !owner.parser_inserted() {
175 return;
176 }
177
178 document.decrement_script_blocking_stylesheet_count();
179
180 if matches!(self.source, StylesheetContextSource::LinkElement) {
184 document.decrement_render_blocking_element_count();
185 }
186 }
187
188 fn finish_load(
189 self,
190 successful: bool,
191 owner: &dyn StylesheetOwner,
192 element: &HTMLElement,
193 document: &Document,
194 ) {
195 self.decrement_load_and_render_blockers(owner, document);
196 document.finish_load(LoadType::Stylesheet(self.url), CanGc::note());
197
198 let Some(any_failed) = owner.load_finished(successful) else {
199 return;
200 };
201
202 if !element.upcast::<Element>().is_connected() {
204 return;
205 }
206
207 let event = match any_failed {
215 true => atom!("error"),
216 false => atom!("load"),
217 };
218 element
219 .upcast::<EventTarget>()
220 .fire_event(event, CanGc::note());
221 }
222
223 fn do_post_parse_tasks(self, successful: bool, stylesheet: Option<Arc<Stylesheet>>) {
224 let element = self.element.root();
225 let document = self.document.root();
226 let owner = element
227 .upcast::<Element>()
228 .as_stylesheet_owner()
229 .expect("Stylesheet not loaded by <style> or <link> element!");
230
231 if !self.contributes_to_the_styling_processing_model(&element) {
238 self.finish_load(true, owner, &element, &document);
241 return;
242 }
243
244 if let Some(stylesheet) = stylesheet {
245 match &self.source {
246 StylesheetContextSource::LinkElement => {
247 let link = element.downcast::<HTMLLinkElement>().unwrap();
248 if link.is_effectively_disabled() {
249 stylesheet.set_disabled(true);
250 }
251 link.set_stylesheet(stylesheet);
252 },
253 StylesheetContextSource::Import(import_rule) => {
254 let window = element.owner_window();
256 let document_context = window.web_font_context();
257
258 document.load_web_fonts_from_stylesheet(&stylesheet, &document_context);
261
262 let mut guard = document.style_shared_lock().write();
263 import_rule.write_with(&mut guard).stylesheet = ImportSheet::Sheet(stylesheet);
264 },
265 }
266 }
267
268 if let Some(ref shadow_root) = self.shadow_root {
269 shadow_root.root().invalidate_stylesheets();
270 } else {
271 document.invalidate_stylesheets();
272 }
273 owner.set_origin_clean(self.origin_clean);
274
275 self.finish_load(successful, owner, &element, &document);
276 }
277}
278
279impl FetchResponseListener for StylesheetContext {
280 fn process_request_body(&mut self, _: RequestId) {}
281
282 fn process_request_eof(&mut self, _: RequestId) {}
283
284 fn process_response(&mut self, _: RequestId, metadata: Result<FetchMetadata, NetworkError>) {
285 if let Ok(FetchMetadata::Filtered {
286 filtered: FilteredMetadata::Opaque | FilteredMetadata::OpaqueRedirect(_),
287 ..
288 }) = metadata
289 {
290 self.origin_clean = false;
291 }
292
293 self.metadata = metadata.ok().map(|m| match m {
294 FetchMetadata::Unfiltered(m) => m,
295 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
296 });
297 }
298
299 fn process_response_chunk(&mut self, _: RequestId, mut payload: Vec<u8>) {
300 self.data.append(&mut payload);
301 }
302
303 fn process_response_eof(
304 mut self,
305 _: RequestId,
306 status: Result<ResourceFetchTiming, NetworkError>,
307 ) {
308 let successful = self
311 .metadata
312 .as_ref()
313 .map(|metadata| metadata.status == http::StatusCode::OK)
314 .unwrap_or(false);
315
316 let Ok(response) = status else {
317 self.do_post_parse_tasks(successful, None);
318 return;
319 };
320
321 network_listener::submit_timing(&self, &response, CanGc::note());
322
323 let Some(metadata) = self.metadata.as_ref() else {
324 self.do_post_parse_tasks(successful, None);
325 return;
326 };
327
328 let element = self.element.root();
329 let document = self.document.root();
330 let is_css =
331 metadata.content_type.clone().is_some_and(|content_type| {
332 MimeClassifier::is_css(&content_type.into_inner().into())
333 }) || (
334 document.quirks_mode() == QuirksMode::Quirks &&
340 document.origin().immutable().clone() == metadata.final_url.origin()
341 );
342
343 self.unminify_css(metadata.final_url.clone());
344 if !is_css {
345 self.data.clear();
346 }
347
348 if !self.contributes_to_the_styling_processing_model(&element) {
355 self.do_post_parse_tasks(successful, None);
356 return;
357 }
358
359 let loader = if pref!(dom_parallel_css_parsing_enabled) {
360 ElementStylesheetLoader::Asynchronous(AsynchronousStylesheetLoader::new(&element))
361 } else {
362 ElementStylesheetLoader::Synchronous { element: &element }
363 };
364 loader.parse(successful, self, &element, &document);
365 }
366
367 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
368 let global = &self.resource_timing_global();
369 global.report_csp_violations(violations, None, None);
370 }
371}
372
373impl ResourceTimingListener for StylesheetContext {
374 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
375 let initiator_type = InitiatorType::LocalName(
376 self.element
377 .root()
378 .upcast::<Element>()
379 .local_name()
380 .to_string(),
381 );
382 (initiator_type, self.url.clone())
383 }
384
385 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
386 self.element.root().owner_document().global()
387 }
388}
389
390pub(crate) enum ElementStylesheetLoader<'a> {
391 Synchronous { element: &'a HTMLElement },
392 Asynchronous(AsynchronousStylesheetLoader),
393}
394
395impl<'a> ElementStylesheetLoader<'a> {
396 pub(crate) fn new(element: &'a HTMLElement) -> Self {
397 ElementStylesheetLoader::Synchronous { element }
398 }
399}
400
401impl ElementStylesheetLoader<'_> {
402 pub(crate) fn load(
403 &self,
404 source: StylesheetContextSource,
405 media: Arc<Locked<MediaList>>,
406 url: ServoUrl,
407 cors_setting: Option<CorsSettings>,
408 integrity_metadata: String,
409 ) {
410 match self {
411 ElementStylesheetLoader::Synchronous { element } => Self::load_with_element(
412 element,
413 source,
414 media,
415 url,
416 cors_setting,
417 integrity_metadata,
418 ),
419 ElementStylesheetLoader::Asynchronous { .. } => unreachable!(
420 "Should never call load directly on an asynchronous ElementStylesheetLoader"
421 ),
422 }
423 }
424
425 fn load_with_element(
426 element: &HTMLElement,
427 source: StylesheetContextSource,
428 media: Arc<Locked<MediaList>>,
429 url: ServoUrl,
430 cors_setting: Option<CorsSettings>,
431 integrity_metadata: String,
432 ) {
433 let document = element.owner_document();
434 let shadow_root = element
435 .containing_shadow_root()
436 .map(|sr| Trusted::new(&*sr));
437 let generation = element
438 .downcast::<HTMLLinkElement>()
439 .map(HTMLLinkElement::get_request_generation_id);
440 let context = StylesheetContext {
441 element: Trusted::new(element),
442 source,
443 media,
444 url: url.clone(),
445 metadata: None,
446 data: vec![],
447 document: Trusted::new(&*document),
448 shadow_root,
449 origin_clean: true,
450 request_generation_id: generation,
451 };
452
453 let owner = element
454 .upcast::<Element>()
455 .as_stylesheet_owner()
456 .expect("Stylesheet not loaded by <style> or <link> element!");
457 let referrer_policy = owner.referrer_policy();
458 owner.increment_pending_loads_count();
459
460 if owner.parser_inserted() {
461 document.increment_script_blocking_stylesheet_count();
462
463 if matches!(context.source, StylesheetContextSource::LinkElement) {
467 document.increment_render_blocking_element_count();
468 }
469 }
470
471 let global = element.global();
473 let request = create_a_potential_cors_request(
474 Some(document.webview_id()),
475 url.clone(),
476 Destination::Style,
477 cors_setting,
478 None,
479 global.get_referrer(),
480 document.insecure_requests_policy(),
481 document.has_trustworthy_ancestor_or_current_origin(),
482 global.policy_container(),
483 )
484 .origin(document.origin().immutable().clone())
485 .pipeline_id(Some(element.global().pipeline_id()))
486 .referrer_policy(referrer_policy)
487 .client(global.request_client())
488 .integrity_metadata(integrity_metadata);
489
490 document.fetch(LoadType::Stylesheet(url), request, context);
491 }
492
493 fn parse(
494 self,
495 successful: bool,
496 listener: StylesheetContext,
497 element: &HTMLElement,
498 document: &Document,
499 ) {
500 let shared_lock = document.style_shared_lock().clone();
501 let quirks_mode = document.quirks_mode();
502 let window = element.owner_window();
503
504 match self {
505 ElementStylesheetLoader::Synchronous { .. } => {
506 let stylesheet =
507 listener.parse(quirks_mode, shared_lock, window.css_error_reporter(), self);
508 listener.do_post_parse_tasks(successful, Some(stylesheet));
509 },
510 ElementStylesheetLoader::Asynchronous(asynchronous_loader) => {
511 let css_error_reporter = window.css_error_reporter().clone();
512 let thread_pool = STYLE_THREAD_POOL.pool();
513 let thread_pool = thread_pool.as_ref().unwrap();
514
515 thread_pool.spawn(move || {
516 let pipeline_id = asynchronous_loader.pipeline_id;
517 let main_thread_sender = asynchronous_loader.main_thread_sender.clone();
518
519 let loader = ElementStylesheetLoader::Asynchronous(asynchronous_loader);
520 let stylesheet =
521 listener.parse(quirks_mode, shared_lock, &css_error_reporter, loader);
522
523 let task = task!(finish_parsing_of_stylesheet_on_main_thread: move || {
524 listener.do_post_parse_tasks(successful, Some(stylesheet));
525 });
526
527 let _ = main_thread_sender.send(MainThreadScriptMsg::Common(
528 CommonScriptMsg::Task(
529 ScriptThreadEventCategory::StylesheetLoad,
530 Box::new(task),
531 Some(pipeline_id),
532 TaskSourceName::Networking,
533 ),
534 ));
535 });
536 },
537 };
538 }
539}
540
541impl StyleStylesheetLoader for ElementStylesheetLoader<'_> {
542 fn request_stylesheet(
545 &self,
546 url: CssUrl,
547 source_location: SourceLocation,
548 lock: &SharedRwLock,
549 media: Arc<Locked<MediaList>>,
550 supports: Option<ImportSupportsCondition>,
551 layer: ImportLayer,
552 ) -> Arc<Locked<ImportRule>> {
553 if supports.as_ref().is_some_and(|s| !s.enabled) {
555 return Arc::new(lock.wrap(ImportRule {
556 url,
557 stylesheet: ImportSheet::new_refused(),
558 supports,
559 layer,
560 source_location,
561 }));
562 }
563
564 let resolved_url = match url.url().cloned() {
565 Some(url) => url,
566 None => {
567 return Arc::new(lock.wrap(ImportRule {
568 url,
569 stylesheet: ImportSheet::new_refused(),
570 supports,
571 layer,
572 source_location,
573 }));
574 },
575 };
576
577 let import_rule = Arc::new(lock.wrap(ImportRule {
578 url,
579 stylesheet: ImportSheet::new_pending(),
580 supports,
581 layer,
582 source_location,
583 }));
584
585 let source = StylesheetContextSource::Import(import_rule.clone());
588
589 match self {
590 ElementStylesheetLoader::Synchronous { element } => {
591 Self::load_with_element(
592 element,
593 source,
594 media,
595 resolved_url.into(),
596 None,
597 "".to_owned(),
598 );
599 },
600 ElementStylesheetLoader::Asynchronous(AsynchronousStylesheetLoader {
601 element,
602 main_thread_sender,
603 pipeline_id,
604 }) => {
605 let element = element.clone();
606 let task = task!(load_import_stylesheet_on_main_thread: move || {
607 Self::load_with_element(
608 &element.root(),
609 source,
610 media,
611 resolved_url.into(),
612 None,
613 "".to_owned()
614 );
615 });
616 let _ =
617 main_thread_sender.send(MainThreadScriptMsg::Common(CommonScriptMsg::Task(
618 ScriptThreadEventCategory::StylesheetLoad,
619 Box::new(task),
620 Some(*pipeline_id),
621 TaskSourceName::Networking,
622 )));
623 },
624 }
625
626 import_rule
627 }
628}
629
630pub(crate) struct AsynchronousStylesheetLoader {
631 element: Trusted<HTMLElement>,
632 main_thread_sender: Sender<MainThreadScriptMsg>,
633 pipeline_id: PipelineId,
634}
635
636impl AsynchronousStylesheetLoader {
637 pub(crate) fn new(element: &HTMLElement) -> Self {
638 let window = element.owner_window();
639 Self {
640 element: Trusted::new(element),
641 main_thread_sender: window.main_thread_script_chan().clone(),
642 pipeline_id: window.pipeline_id(),
643 }
644 }
645}