1use selectors::context::QuirksMode;
2use std::sync::atomic::Ordering as Ao;
3use std::{
4 io::Cursor,
5 sync::{Arc, atomic::AtomicUsize, mpsc::Sender},
6};
7use style::{
8 font_face::{FontFaceSourceFormat, FontFaceSourceFormatKeyword, Source},
9 media_queries::MediaList,
10 servo_arc::Arc as ServoArc,
11 shared_lock::SharedRwLock,
12 shared_lock::{Locked, SharedRwLockReadGuard},
13 stylesheets::{
14 AllowImportRules, CssRule, DocumentStyleSheet, ImportRule, Origin, Stylesheet,
15 StylesheetInDocument, StylesheetLoader as ServoStylesheetLoader, UrlExtraData,
16 import_rule::{ImportLayer, ImportSheet, ImportSupportsCondition},
17 },
18 values::{CssUrl, SourceLocation},
19};
20
21use bliss_traits::net::{Bytes, NetHandler, NetProvider, Request};
22use bliss_traits::shell::ShellProvider;
23
24use url::Url;
25
26use crate::{document::DocumentEvent, util::ImageType};
27
28#[derive(Clone, Debug)]
29pub enum Resource {
30 Image(ImageType, u32, u32, Arc<Vec<u8>>),
31 #[cfg(feature = "svg")]
32 Svg(ImageType, Arc<usvg::Tree>),
33 Css(DocumentStyleSheet),
34 Font(Bytes),
35 None,
36}
37
38pub(crate) struct ResourceHandler<T: Send + Sync + 'static> {
39 doc_id: usize,
40 request_id: usize,
41 node_id: Option<usize>,
42 tx: Sender<DocumentEvent>,
43 shell_provider: Arc<dyn ShellProvider>,
44 data: T,
45}
46
47impl<T: Send + Sync + 'static> ResourceHandler<T> {
48 pub(crate) fn new(
49 tx: Sender<DocumentEvent>,
50 doc_id: usize,
51 node_id: Option<usize>,
52 shell_provider: Arc<dyn ShellProvider>,
53 data: T,
54 ) -> Self {
55 static REQUEST_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
56 Self {
57 request_id: REQUEST_ID_COUNTER.fetch_add(1, Ao::Relaxed),
58 doc_id,
59 node_id,
60 tx,
61 shell_provider,
62 data,
63 }
64 }
65
66 pub(crate) fn boxed(
67 tx: Sender<DocumentEvent>,
68 doc_id: usize,
69 node_id: Option<usize>,
70 shell_provider: Arc<dyn ShellProvider>,
71 data: T,
72 ) -> Box<dyn NetHandler>
73 where
74 ResourceHandler<T>: NetHandler,
75 {
76 Box::new(Self::new(tx, doc_id, node_id, shell_provider, data)) as _
77 }
78
79 fn respond(&self, resolved_url: String, result: Result<Resource, String>) {
80 let response = ResourceLoadResponse {
81 request_id: self.request_id,
82 node_id: self.node_id,
83 resolved_url: Some(resolved_url),
84 result,
85 };
86 let _ = self.tx.send(DocumentEvent::ResourceLoad(response));
87 self.shell_provider.request_redraw();
88 }
89}
90
91#[allow(unused)]
92pub struct ResourceLoadResponse {
93 pub request_id: usize,
94 pub node_id: Option<usize>,
95 pub resolved_url: Option<String>,
96 pub result: Result<Resource, String>,
97}
98
99pub struct StylesheetHandler {
100 pub source_url: Url,
101 pub guard: SharedRwLock,
102 pub net_provider: Arc<dyn NetProvider>,
103}
104
105impl NetHandler for ResourceHandler<StylesheetHandler> {
106 fn bytes(self: Box<Self>, resolved_url: String, bytes: Bytes) {
107 let Ok(css) = std::str::from_utf8(&bytes) else {
108 return self.respond(resolved_url, Err(String::from("Invalid UTF8")));
109 };
110
111 let sheet = Stylesheet::from_str(
115 css,
116 self.data.source_url.clone().into(),
117 Origin::Author,
118 ServoArc::new(self.data.guard.wrap(MediaList::empty())),
119 self.data.guard.clone(),
120 Some(&StylesheetLoader {
121 tx: self.tx.clone(),
122 doc_id: self.doc_id,
123 net_provider: self.data.net_provider.clone(),
124 shell_provider: self.shell_provider.clone(),
125 }),
126 None, QuirksMode::NoQuirks,
128 AllowImportRules::Yes,
129 );
130
131 self.respond(
132 resolved_url,
133 Ok(Resource::Css(DocumentStyleSheet(ServoArc::new(sheet)))),
134 );
135 }
136}
137
138#[derive(Clone)]
139pub(crate) struct StylesheetLoader {
140 pub(crate) tx: Sender<DocumentEvent>,
141 pub(crate) doc_id: usize,
142 pub(crate) net_provider: Arc<dyn NetProvider>,
143 pub(crate) shell_provider: Arc<dyn ShellProvider>,
144}
145impl ServoStylesheetLoader for StylesheetLoader {
146 fn request_stylesheet(
147 &self,
148 url: CssUrl,
149 location: SourceLocation,
150 lock: &SharedRwLock,
151 media: ServoArc<Locked<MediaList>>,
152 supports: Option<ImportSupportsCondition>,
153 layer: ImportLayer,
154 ) -> ServoArc<Locked<ImportRule>> {
155 if !supports.as_ref().is_none_or(|s| s.enabled) {
156 return ServoArc::new(lock.wrap(ImportRule {
157 url,
158 stylesheet: ImportSheet::new_refused(),
159 supports,
160 layer,
161 source_location: location,
162 }));
163 }
164
165 let import = ImportRule {
166 url,
167 stylesheet: ImportSheet::new_pending(),
168 supports,
169 layer,
170 source_location: location,
171 };
172
173 let url = match import.url.url() {
174 Some(url) => url.clone(),
175 None => {
176 return ServoArc::new(lock.wrap(ImportRule {
178 url: import.url,
179 stylesheet: ImportSheet::new_refused(),
180 supports: import.supports,
181 layer: import.layer,
182 source_location: import.source_location,
183 }));
184 }
185 };
186 let import = ServoArc::new(lock.wrap(import));
187 self.net_provider.fetch(
188 self.doc_id,
189 Request::get(url.as_ref().clone()),
190 ResourceHandler::boxed(
191 self.tx.clone(),
192 self.doc_id,
193 None, self.shell_provider.clone(),
195 NestedStylesheetHandler {
196 url: url.clone(),
197 loader: self.clone(),
198 lock: lock.clone(),
199 media,
200 import_rule: import.clone(),
201 net_provider: self.net_provider.clone(),
202 },
203 ),
204 );
205
206 import
207 }
208}
209
210struct NestedStylesheetHandler {
211 loader: StylesheetLoader,
212 lock: SharedRwLock,
213 url: ServoArc<Url>,
214 media: ServoArc<Locked<MediaList>>,
215 import_rule: ServoArc<Locked<ImportRule>>,
216 net_provider: Arc<dyn NetProvider>,
217}
218
219impl NetHandler for ResourceHandler<NestedStylesheetHandler> {
220 fn bytes(self: Box<Self>, resolved_url: String, bytes: Bytes) {
221 let Ok(css) = std::str::from_utf8(&bytes) else {
222 return self.respond(resolved_url, Err(String::from("Invalid UTF8")));
223 };
224
225 let sheet = ServoArc::new(Stylesheet::from_str(
229 css,
230 UrlExtraData(self.data.url.clone()),
231 Origin::Author,
232 self.data.media.clone(),
233 self.data.lock.clone(),
234 Some(&self.data.loader),
235 None, QuirksMode::NoQuirks,
237 AllowImportRules::Yes,
238 ));
239
240 fetch_font_face(
242 self.tx.clone(),
243 self.doc_id,
244 self.node_id,
245 &sheet,
246 &self.data.net_provider,
247 &self.shell_provider,
248 &self.data.lock.read(),
249 );
250
251 let mut guard = self.data.lock.write();
252 self.data.import_rule.write_with(&mut guard).stylesheet = ImportSheet::Sheet(sheet);
253 drop(guard);
254
255 self.respond(resolved_url, Ok(Resource::None))
256 }
257}
258
259struct FontFaceHandler(FontFaceSourceFormatKeyword);
260impl NetHandler for ResourceHandler<FontFaceHandler> {
261 fn bytes(mut self: Box<Self>, resolved_url: String, bytes: Bytes) {
262 let result = self.data.parse(bytes);
263 self.respond(resolved_url, result)
264 }
265}
266impl FontFaceHandler {
267 fn parse(&mut self, bytes: Bytes) -> Result<Resource, String> {
268 if self.0 == FontFaceSourceFormatKeyword::None && bytes.len() >= 4 {
269 self.0 = match &bytes.as_ref()[0..4] {
270 #[cfg(feature = "woff-rust")]
273 b"wOFF" => FontFaceSourceFormatKeyword::Woff,
274 #[cfg(feature = "woff-rust")]
277 b"wOF2" => FontFaceSourceFormatKeyword::Woff2,
278 b"OTTO" => FontFaceSourceFormatKeyword::Opentype,
281 &[0x00, 0x01, 0x00, 0x00] => FontFaceSourceFormatKeyword::Truetype,
284 b"true" => FontFaceSourceFormatKeyword::Truetype,
287 _ => FontFaceSourceFormatKeyword::None,
288 }
289 }
290
291 #[cfg(feature = "woff-rust")]
293 let mut bytes = bytes;
294
295 match self.0 {
296 #[cfg(feature = "woff-rust")]
297 FontFaceSourceFormatKeyword::Woff => {
298 #[cfg(feature = "tracing")]
299 tracing::info!("Decompressing woff1 font");
300
301 #[cfg(feature = "woff-rust")]
303 let decompressed = wuff::decompress_woff1(&bytes).ok();
304
305 if let Some(decompressed) = decompressed {
306 bytes = Bytes::from(decompressed);
307 } else {
308 #[cfg(feature = "tracing")]
309 tracing::warn!("Failed to decompress woff1 font");
310 }
311 }
312 #[cfg(feature = "woff-rust")]
313 FontFaceSourceFormatKeyword::Woff2 => {
314 #[cfg(feature = "tracing")]
315 tracing::info!("Decompressing woff2 font");
316
317 #[cfg(feature = "woff-rust")]
319 let decompressed = wuff::decompress_woff2(&bytes).ok();
320
321 if let Some(decompressed) = decompressed {
322 bytes = Bytes::from(decompressed);
323 } else {
324 #[cfg(feature = "tracing")]
325 tracing::warn!("Failed to decompress woff2 font");
326 }
327 }
328 FontFaceSourceFormatKeyword::None => {
329 return Ok(Resource::None);
331 }
332 _ => {}
333 }
334
335 Ok(Resource::Font(bytes))
336 }
337}
338
339pub(crate) fn fetch_font_face(
340 tx: Sender<DocumentEvent>,
341 doc_id: usize,
342 node_id: Option<usize>,
343 sheet: &Stylesheet,
344 network_provider: &Arc<dyn NetProvider>,
345 shell_provider: &Arc<dyn ShellProvider>,
346 read_guard: &SharedRwLockReadGuard,
347) {
348 sheet
349 .contents(read_guard)
350 .rules(read_guard)
351 .iter()
352 .filter_map(|rule| match rule {
353 CssRule::FontFace(font_face) => font_face.read_with(read_guard).sources.as_ref(),
354 _ => None,
355 })
356 .for_each(|source_list| {
357 let preferred_source = source_list
360 .0
361 .iter()
362 .filter_map(|source| match source {
363 Source::Url(url_source) => Some(url_source),
364 Source::Local(_) => None,
366 })
367 .find_map(|url_source| {
368 let mut format = match &url_source.format_hint {
369 Some(FontFaceSourceFormat::Keyword(fmt)) => *fmt,
370 Some(FontFaceSourceFormat::String(str)) => match str.as_str() {
371 "woff2" => FontFaceSourceFormatKeyword::Woff2,
372 "ttf" => FontFaceSourceFormatKeyword::Truetype,
373 "otf" => FontFaceSourceFormatKeyword::Opentype,
374 _ => FontFaceSourceFormatKeyword::None,
375 },
376 _ => FontFaceSourceFormatKeyword::None,
377 };
378 if format == FontFaceSourceFormatKeyword::None {
379 let (_, end) = url_source.url.as_str().rsplit_once('.')?;
380 format = match end {
381 "woff2" => FontFaceSourceFormatKeyword::Woff2,
382 "woff" => FontFaceSourceFormatKeyword::Woff,
383 "ttf" => FontFaceSourceFormatKeyword::Truetype,
384 "otf" => FontFaceSourceFormatKeyword::Opentype,
385 "svg" => FontFaceSourceFormatKeyword::Svg,
386 "eot" => FontFaceSourceFormatKeyword::EmbeddedOpentype,
387 _ => FontFaceSourceFormatKeyword::None,
388 }
389 }
390
391 if matches!(
392 format,
393 FontFaceSourceFormatKeyword::Svg
394 | FontFaceSourceFormatKeyword::EmbeddedOpentype
395 ) {
396 #[cfg(feature = "tracing")]
397 tracing::warn!("Skipping unsupported font of type {:?}", format);
398 return None;
399 }
400
401 #[cfg(not(feature = "woff-rust"))]
402 if matches!(
403 format,
404 FontFaceSourceFormatKeyword::Woff | FontFaceSourceFormatKeyword::Woff2
405 ) {
406 #[cfg(feature = "tracing")]
407 tracing::warn!("Skipping unsupported font of type {:?}", format);
408 return None;
409 }
410
411 let url = url_source.url.url()?;
412 Some((url.as_ref().clone(), format))
413 });
414
415 if let Some((url, format)) = preferred_source {
416 network_provider.fetch(
417 doc_id,
418 Request::get(url),
419 ResourceHandler::boxed(
420 tx.clone(),
421 doc_id,
422 node_id,
423 shell_provider.clone(),
424 FontFaceHandler(format),
425 ),
426 );
427 }
428 })
429}
430
431pub struct ImageHandler {
432 kind: ImageType,
433}
434impl ImageHandler {
435 pub fn new(kind: ImageType) -> Self {
436 Self { kind }
437 }
438}
439
440impl NetHandler for ResourceHandler<ImageHandler> {
441 fn bytes(self: Box<Self>, resolved_url: String, bytes: Bytes) {
442 let result = self.data.parse(bytes);
443 self.respond(resolved_url, result)
444 }
445}
446
447impl ImageHandler {
448 fn parse(&self, bytes: Bytes) -> Result<Resource, String> {
449 if let Ok(image) = image::ImageReader::new(Cursor::new(&bytes))
451 .with_guessed_format()
452 .expect("IO errors impossible with Cursor")
453 .decode()
454 {
455 let raw_rgba8_data = image.clone().into_rgba8().into_raw();
456 return Ok(Resource::Image(
457 self.kind,
458 image.width(),
459 image.height(),
460 Arc::new(raw_rgba8_data),
461 ));
462 };
463
464 #[cfg(feature = "svg")]
465 {
466 use crate::util::parse_svg;
467 if let Ok(tree) = parse_svg(&bytes) {
468 return Ok(Resource::Svg(self.kind, Arc::new(tree)));
469 }
470 }
471
472 Err(String::from("Could not parse image"))
473 }
474}