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