1use std::io::{Read, Seek, Write};
6
7use cssparser::SourceLocation;
8use encoding_rs::UTF_8;
9use mime::{self, Mime};
10use net_traits::request::{CorsSettings, Destination, RequestId};
11use net_traits::{
12 FetchMetadata, FetchResponseListener, FilteredMetadata, Metadata, NetworkError, ReferrerPolicy,
13 ResourceFetchTiming, ResourceTimingType,
14};
15use servo_arc::Arc;
16use servo_url::ServoUrl;
17use style::context::QuirksMode;
18use style::media_queries::MediaList;
19use style::shared_lock::{Locked, SharedRwLock};
20use style::stylesheets::import_rule::{ImportLayer, ImportSheet, ImportSupportsCondition};
21use style::stylesheets::{
22 ImportRule, Origin, Stylesheet, StylesheetLoader as StyleStylesheetLoader, UrlExtraData,
23};
24use style::values::CssUrl;
25
26use crate::document_loader::LoadType;
27use crate::dom::bindings::inheritance::Castable;
28use crate::dom::bindings::refcounted::Trusted;
29use crate::dom::bindings::reflector::DomGlobal;
30use crate::dom::bindings::root::DomRoot;
31use crate::dom::csp::{GlobalCspReporting, Violation};
32use crate::dom::document::Document;
33use crate::dom::element::Element;
34use crate::dom::eventtarget::EventTarget;
35use crate::dom::globalscope::GlobalScope;
36use crate::dom::html::htmlelement::HTMLElement;
37use crate::dom::html::htmllinkelement::{HTMLLinkElement, RequestGenerationId};
38use crate::dom::node::NodeTraits;
39use crate::dom::performanceresourcetiming::InitiatorType;
40use crate::dom::shadowroot::ShadowRoot;
41use crate::fetch::create_a_potential_cors_request;
42use crate::network_listener::{self, PreInvoke, ResourceTimingListener};
43use crate::script_runtime::CanGc;
44use crate::unminify::{
45 BeautifyFileType, create_output_file, create_temp_files, execute_js_beautify,
46};
47
48pub(crate) trait StylesheetOwner {
49 fn parser_inserted(&self) -> bool;
52
53 fn referrer_policy(&self) -> ReferrerPolicy;
55
56 fn increment_pending_loads_count(&self);
58
59 fn load_finished(&self, successful: bool) -> Option<bool>;
62
63 fn set_origin_clean(&self, origin_clean: bool);
65}
66
67pub(crate) enum StylesheetContextSource {
68 LinkElement {
69 media: Arc<Locked<MediaList>>,
70 },
71 Import {
72 import_rule: Arc<Locked<ImportRule>>,
73 media: Arc<Locked<MediaList>>,
74 },
75}
76
77pub(crate) struct StylesheetContext {
79 elem: Trusted<HTMLElement>,
81 source: StylesheetContextSource,
82 url: ServoUrl,
83 metadata: Option<Metadata>,
84 data: Vec<u8>,
86 document: Trusted<Document>,
88 shadow_root: Option<Trusted<ShadowRoot>>,
89 origin_clean: bool,
90 request_generation_id: Option<RequestGenerationId>,
93 resource_timing: ResourceFetchTiming,
94}
95
96impl StylesheetContext {
97 fn unminify_css(&self, data: Vec<u8>, file_url: ServoUrl) -> Vec<u8> {
98 let Some(unminified_dir) = self.document.root().window().unminified_css_dir() else {
99 return data;
100 };
101
102 let mut style_content = data;
103
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 style_content
124 }
125}
126
127impl PreInvoke for StylesheetContext {}
128
129impl FetchResponseListener for StylesheetContext {
130 fn process_request_body(&mut self, _: RequestId) {}
131
132 fn process_request_eof(&mut self, _: RequestId) {}
133
134 fn process_response(&mut self, _: RequestId, metadata: Result<FetchMetadata, NetworkError>) {
135 if let Ok(FetchMetadata::Filtered {
136 filtered: FilteredMetadata::Opaque | FilteredMetadata::OpaqueRedirect(_),
137 ..
138 }) = metadata
139 {
140 self.origin_clean = false;
141 }
142
143 self.metadata = metadata.ok().map(|m| match m {
144 FetchMetadata::Unfiltered(m) => m,
145 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
146 });
147 }
148
149 fn process_response_chunk(&mut self, _: RequestId, mut payload: Vec<u8>) {
150 self.data.append(&mut payload);
151 }
152
153 fn process_response_eof(
154 &mut self,
155 _: RequestId,
156 status: Result<ResourceFetchTiming, NetworkError>,
157 ) {
158 let element = self.elem.root();
159 let document = self.document.root();
160 let mut successful = false;
161
162 if status.is_ok() {
163 let metadata = match self.metadata.take() {
164 Some(meta) => meta,
165 None => return,
166 };
167
168 let mut is_css = metadata.content_type.is_some_and(|ct| {
169 let mime: Mime = ct.into_inner().into();
170 mime.type_() == mime::TEXT && mime.subtype() == mime::CSS
171 }) || (
172 document.quirks_mode() == QuirksMode::Quirks &&
179 document.origin().immutable().clone() == metadata.final_url.origin()
180 );
181
182 if document.quirks_mode() == QuirksMode::Quirks &&
188 document.url().origin() == self.url.origin()
189 {
190 is_css = true;
191 }
192
193 let data = if is_css {
194 let data = std::mem::take(&mut self.data);
195 self.unminify_css(data, metadata.final_url.clone())
196 } else {
197 vec![]
198 };
199
200 let environment_encoding = UTF_8;
202 let protocol_encoding_label = metadata.charset.as_deref();
203 let final_url = metadata.final_url;
204
205 let win = element.owner_window();
206
207 let loader = ElementStylesheetLoader::new(&element);
208 let shared_lock = document.style_shared_lock();
209 let stylesheet = |media| {
210 #[cfg(feature = "tracing")]
211 let _span =
212 tracing::trace_span!("ParseStylesheet", servo_profiling = true).entered();
213 Arc::new(Stylesheet::from_bytes(
214 &data,
215 UrlExtraData(final_url.get_arc()),
216 protocol_encoding_label,
217 Some(environment_encoding),
218 Origin::Author,
219 media,
220 shared_lock.clone(),
221 Some(&loader),
222 win.css_error_reporter(),
223 document.quirks_mode(),
224 ))
225 };
226 match &self.source {
227 StylesheetContextSource::LinkElement { media } => {
228 let link = element.downcast::<HTMLLinkElement>().unwrap();
229 let is_stylesheet_load_applicable = self
232 .request_generation_id
233 .is_none_or(|generation| generation == link.get_request_generation_id());
234 if is_stylesheet_load_applicable {
235 let stylesheet = stylesheet(media.clone());
236 if link.is_effectively_disabled() {
237 stylesheet.set_disabled(true);
238 }
239 link.set_stylesheet(stylesheet);
240 }
241 },
242 StylesheetContextSource::Import { import_rule, media } => {
243 let stylesheet = stylesheet(media.clone());
244
245 document.load_web_fonts_from_stylesheet(&stylesheet);
248
249 let mut guard = shared_lock.write();
250 import_rule.write_with(&mut guard).stylesheet = ImportSheet::Sheet(stylesheet);
251 },
252 }
253
254 if let Some(ref shadow_root) = self.shadow_root {
255 shadow_root.root().invalidate_stylesheets();
256 } else {
257 document.invalidate_stylesheets();
258 }
259
260 successful = metadata.status == http::StatusCode::OK;
263 }
264
265 let owner = element
266 .upcast::<Element>()
267 .as_stylesheet_owner()
268 .expect("Stylesheet not loaded by <style> or <link> element!");
269 owner.set_origin_clean(self.origin_clean);
270 if owner.parser_inserted() {
271 document.decrement_script_blocking_stylesheet_count();
272 }
273
274 if matches!(self.source, StylesheetContextSource::LinkElement { .. }) &&
278 owner.parser_inserted()
279 {
280 document.decrement_render_blocking_element_count();
281 }
282
283 document.finish_load(LoadType::Stylesheet(self.url.clone()), CanGc::note());
284
285 if let Some(any_failed) = owner.load_finished(successful) {
286 let event = if any_failed {
287 atom!("error")
288 } else {
289 atom!("load")
290 };
291 element
292 .upcast::<EventTarget>()
293 .fire_event(event, CanGc::note());
294 }
295 }
296
297 fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
298 &mut self.resource_timing
299 }
300
301 fn resource_timing(&self) -> &ResourceFetchTiming {
302 &self.resource_timing
303 }
304
305 fn submit_resource_timing(&mut self) {
306 network_listener::submit_timing(self, CanGc::note())
307 }
308
309 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
310 let global = &self.resource_timing_global();
311 global.report_csp_violations(violations, None, None);
312 }
313}
314
315impl ResourceTimingListener for StylesheetContext {
316 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
317 let initiator_type = InitiatorType::LocalName(
318 self.elem
319 .root()
320 .upcast::<Element>()
321 .local_name()
322 .to_string(),
323 );
324 (initiator_type, self.url.clone())
325 }
326
327 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
328 self.elem.root().owner_document().global()
329 }
330}
331
332pub(crate) struct ElementStylesheetLoader<'a> {
333 element: &'a HTMLElement,
334}
335
336impl<'a> ElementStylesheetLoader<'a> {
337 pub(crate) fn new(element: &'a HTMLElement) -> Self {
338 ElementStylesheetLoader { element }
339 }
340}
341
342impl ElementStylesheetLoader<'_> {
343 pub(crate) fn load(
344 &self,
345 source: StylesheetContextSource,
346 url: ServoUrl,
347 cors_setting: Option<CorsSettings>,
348 integrity_metadata: String,
349 ) {
350 let document = self.element.owner_document();
351 let shadow_root = self
352 .element
353 .containing_shadow_root()
354 .map(|sr| Trusted::new(&*sr));
355 let generation = self
356 .element
357 .downcast::<HTMLLinkElement>()
358 .map(HTMLLinkElement::get_request_generation_id);
359 let context = StylesheetContext {
360 elem: Trusted::new(self.element),
361 source,
362 url: url.clone(),
363 metadata: None,
364 data: vec![],
365 document: Trusted::new(&*document),
366 shadow_root,
367 origin_clean: true,
368 request_generation_id: generation,
369 resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource),
370 };
371
372 let owner = self
373 .element
374 .upcast::<Element>()
375 .as_stylesheet_owner()
376 .expect("Stylesheet not loaded by <style> or <link> element!");
377 let referrer_policy = owner.referrer_policy();
378 owner.increment_pending_loads_count();
379 if owner.parser_inserted() {
380 document.increment_script_blocking_stylesheet_count();
381 }
382
383 if matches!(context.source, StylesheetContextSource::LinkElement { .. }) &&
387 owner.parser_inserted()
388 {
389 document.increment_render_blocking_element_count();
390 }
391
392 let global = self.element.global();
394 let request = create_a_potential_cors_request(
395 Some(document.webview_id()),
396 url.clone(),
397 Destination::Style,
398 cors_setting,
399 None,
400 global.get_referrer(),
401 document.insecure_requests_policy(),
402 document.has_trustworthy_ancestor_or_current_origin(),
403 global.policy_container(),
404 )
405 .origin(document.origin().immutable().clone())
406 .pipeline_id(Some(self.element.global().pipeline_id()))
407 .referrer_policy(referrer_policy)
408 .integrity_metadata(integrity_metadata);
409
410 document.fetch(LoadType::Stylesheet(url), request, context);
411 }
412}
413
414impl StyleStylesheetLoader for ElementStylesheetLoader<'_> {
415 fn request_stylesheet(
418 &self,
419 url: CssUrl,
420 source_location: SourceLocation,
421 lock: &SharedRwLock,
422 media: Arc<Locked<MediaList>>,
423 supports: Option<ImportSupportsCondition>,
424 layer: ImportLayer,
425 ) -> Arc<Locked<ImportRule>> {
426 if supports.as_ref().is_some_and(|s| !s.enabled) {
428 return Arc::new(lock.wrap(ImportRule {
429 url,
430 stylesheet: ImportSheet::new_refused(),
431 supports,
432 layer,
433 source_location,
434 }));
435 }
436
437 let resolved_url = match url.url().cloned() {
438 Some(url) => url,
439 None => {
440 return Arc::new(lock.wrap(ImportRule {
441 url,
442 stylesheet: ImportSheet::new_refused(),
443 supports,
444 layer,
445 source_location,
446 }));
447 },
448 };
449
450 let import_rule = Arc::new(lock.wrap(ImportRule {
451 url,
452 stylesheet: ImportSheet::new_pending(),
453 supports,
454 layer,
455 source_location,
456 }));
457
458 let source = StylesheetContextSource::Import {
461 import_rule: import_rule.clone(),
462 media,
463 };
464 self.load(source, resolved_url.into(), None, "".to_owned());
465
466 import_rule
467 }
468}