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