1use selectors::context::QuirksMode;
2use std::{io::Cursor, sync::Arc, sync::atomic::AtomicBool};
3use style::{
4 font_face::{FontFaceSourceFormat, FontFaceSourceFormatKeyword, Source},
5 media_queries::MediaList,
6 parser::ParserContext,
7 servo_arc::Arc as ServoArc,
8 shared_lock::SharedRwLock,
9 shared_lock::{Locked, SharedRwLockReadGuard},
10 stylesheets::{
11 AllowImportRules, CssRule, CssRules, DocumentStyleSheet, ImportRule, Origin, Stylesheet,
12 StylesheetContents, StylesheetInDocument, StylesheetLoader as ServoStylesheetLoader,
13 UrlExtraData,
14 import_rule::{ImportLayer, ImportSheet, ImportSupportsCondition},
15 },
16 values::{CssUrl, SourceLocation},
17};
18
19use blitz_traits::net::{Bytes, NetHandler, Request, SharedCallback, SharedProvider};
20
21use url::Url;
22
23use crate::util::ImageType;
24
25#[derive(Clone, Debug)]
26pub enum Resource {
27 Image(usize, ImageType, u32, u32, Arc<Vec<u8>>),
28 #[cfg(feature = "svg")]
29 Svg(usize, ImageType, Box<usvg::Tree>),
30 Css(usize, DocumentStyleSheet),
31 Font(Bytes),
32 Navigation {
33 url: String,
34 document: Bytes,
35 },
36 None,
37}
38pub struct CssHandler {
39 pub node: usize,
40 pub source_url: Url,
41 pub guard: SharedRwLock,
42 pub provider: SharedProvider<Resource>,
43}
44
45#[derive(Clone)]
46pub(crate) struct StylesheetLoader(pub(crate) usize, pub(crate) SharedProvider<Resource>);
47impl ServoStylesheetLoader for StylesheetLoader {
48 fn request_stylesheet(
49 &self,
50 url: CssUrl,
51 location: SourceLocation,
52 context: &ParserContext,
53 lock: &SharedRwLock,
54 media: ServoArc<Locked<MediaList>>,
55 supports: Option<ImportSupportsCondition>,
56 layer: ImportLayer,
57 ) -> ServoArc<Locked<ImportRule>> {
58 if !supports.as_ref().is_none_or(|s| s.enabled) {
59 return ServoArc::new(lock.wrap(ImportRule {
60 url,
61 stylesheet: ImportSheet::new_refused(),
62 supports,
63 layer,
64 source_location: location,
65 }));
66 }
67
68 let sheet = ServoArc::new(Stylesheet {
69 contents: StylesheetContents::from_data(
70 CssRules::new(Vec::new(), lock),
71 context.stylesheet_origin,
72 context.url_data.clone(),
73 context.quirks_mode,
74 ),
75 media,
76 shared_lock: lock.clone(),
77 disabled: AtomicBool::new(false),
78 });
79
80 let stylesheet = ImportSheet::new(sheet.clone());
81 let import = ImportRule {
82 url,
83 stylesheet,
84 supports,
85 layer,
86 source_location: location,
87 };
88
89 struct StylesheetLoaderInner {
90 loader: StylesheetLoader,
91 read_lock: SharedRwLock,
92 url: ServoArc<Url>,
93 sheet: ServoArc<Stylesheet>,
94 provider: SharedProvider<Resource>,
95 }
96 impl NetHandler<Resource> for StylesheetLoaderInner {
97 fn bytes(
98 self: Box<Self>,
99 doc_id: usize,
100 bytes: Bytes,
101 callback: SharedCallback<Resource>,
102 ) {
103 let Ok(css) = std::str::from_utf8(&bytes) else {
104 callback.call(doc_id, Err(Some(String::from("Invalid UTF8"))));
105 return;
106 };
107
108 Stylesheet::update_from_str(
111 &self.sheet,
112 css,
113 UrlExtraData(self.url),
114 Some(&self.loader),
115 None,
116 AllowImportRules::Yes,
117 );
118 fetch_font_face(doc_id, &self.sheet, &self.provider, &self.read_lock.read());
119 callback.call(doc_id, Ok(Resource::None))
120 }
121 }
122 let url = import.url.url().unwrap();
123 self.1.fetch(
124 self.0,
125 Request::get(url.as_ref().clone()),
126 Box::new(StylesheetLoaderInner {
127 url: url.clone(),
128 loader: self.clone(),
129 read_lock: lock.clone(),
130 sheet: sheet.clone(),
131 provider: self.1.clone(),
132 }),
133 );
134
135 ServoArc::new(lock.wrap(import))
136 }
137}
138impl NetHandler<Resource> for CssHandler {
139 fn bytes(self: Box<Self>, doc_id: usize, bytes: Bytes, callback: SharedCallback<Resource>) {
140 let Ok(css) = std::str::from_utf8(&bytes) else {
141 callback.call(doc_id, Err(Some(String::from("Invalid UTF8"))));
142 return;
143 };
144
145 let sheet = Stylesheet::from_str(
149 css,
150 self.source_url.into(),
151 Origin::Author,
152 ServoArc::new(self.guard.wrap(MediaList::empty())),
153 self.guard.clone(),
154 Some(&StylesheetLoader(doc_id, self.provider.clone())),
155 None,
156 QuirksMode::NoQuirks,
157 AllowImportRules::Yes,
158 );
159 let read_guard = self.guard.read();
160 fetch_font_face(doc_id, &sheet, &self.provider, &read_guard);
161
162 callback.call(
163 doc_id,
164 Ok(Resource::Css(
165 self.node,
166 DocumentStyleSheet(ServoArc::new(sheet)),
167 )),
168 )
169 }
170}
171struct FontFaceHandler(FontFaceSourceFormatKeyword);
172impl NetHandler<Resource> for FontFaceHandler {
173 fn bytes(mut self: Box<Self>, doc_id: usize, bytes: Bytes, callback: SharedCallback<Resource>) {
174 if self.0 == FontFaceSourceFormatKeyword::None {
175 self.0 = match bytes.as_ref() {
176 #[cfg(any(feature = "woff-c", feature = "woff-rust"))]
183 [b'w', b'O', b'F', b'2', ..] => FontFaceSourceFormatKeyword::Woff2,
184 [b'O', b'T', b'T', b'O', ..] => FontFaceSourceFormatKeyword::Opentype,
187 [0x00, 0x01, 0x00, 0x00, ..] => FontFaceSourceFormatKeyword::Truetype,
190 [b't', b'r', b'u', b'e', ..] => FontFaceSourceFormatKeyword::Truetype,
193 _ => FontFaceSourceFormatKeyword::None,
194 }
195 }
196
197 #[cfg(any(feature = "woff-c", feature = "woff-rust"))]
199 let mut bytes = bytes;
200
201 match self.0 {
202 #[cfg(any(feature = "woff-c", feature = "woff-rust"))]
218 FontFaceSourceFormatKeyword::Woff2 => {
219 #[cfg(feature = "tracing")]
220 tracing::info!("Decompressing woff2 font");
221
222 #[cfg(feature = "woff-c")]
224 let decompressed = woff::version2::decompress(&bytes);
225
226 #[cfg(feature = "woff-rust")]
228 let decompressed = woff2::decode::convert_woff2_to_ttf(&mut bytes).ok();
229
230 if let Some(decompressed) = decompressed {
231 bytes = Bytes::from(decompressed);
232 } else {
233 #[cfg(feature = "tracing")]
234 tracing::warn!("Failed to decompress woff2 font");
235 }
236 }
237 FontFaceSourceFormatKeyword::None => {
238 return;
239 }
240 _ => {}
241 }
242
243 callback.call(doc_id, Ok(Resource::Font(bytes)))
244 }
245}
246
247fn fetch_font_face(
248 doc_id: usize,
249 sheet: &Stylesheet,
250 network_provider: &SharedProvider<Resource>,
251 read_guard: &SharedRwLockReadGuard,
252) {
253 sheet
254 .rules(read_guard)
255 .iter()
256 .filter_map(|rule| match rule {
257 CssRule::FontFace(font_face) => font_face.read_with(read_guard).sources.as_ref(),
258 _ => None,
259 })
260 .flat_map(|source_list| &source_list.0)
261 .filter_map(|source| match source {
262 Source::Url(url_source) => Some(url_source),
263 _ => None,
264 })
265 .for_each(|url_source| {
266 let mut format = match &url_source.format_hint {
267 Some(FontFaceSourceFormat::Keyword(fmt)) => *fmt,
268 Some(FontFaceSourceFormat::String(str)) => match str.as_str() {
269 "woff2" => FontFaceSourceFormatKeyword::Woff2,
270 "ttf" => FontFaceSourceFormatKeyword::Truetype,
271 "otf" => FontFaceSourceFormatKeyword::Opentype,
272 _ => FontFaceSourceFormatKeyword::None,
273 },
274 _ => FontFaceSourceFormatKeyword::None,
275 };
276 if format == FontFaceSourceFormatKeyword::None {
277 let Some((_, end)) = url_source.url.as_str().rsplit_once('.') else {
278 return;
279 };
280 format = match end {
281 "woff2" => FontFaceSourceFormatKeyword::Woff2,
282 "woff" => FontFaceSourceFormatKeyword::Woff,
283 "ttf" => FontFaceSourceFormatKeyword::Truetype,
284 "otf" => FontFaceSourceFormatKeyword::Opentype,
285 "svg" => FontFaceSourceFormatKeyword::Svg,
286 "eot" => FontFaceSourceFormatKeyword::EmbeddedOpentype,
287 _ => FontFaceSourceFormatKeyword::None,
288 }
289 }
290 if let _font_format @ (FontFaceSourceFormatKeyword::Svg
291 | FontFaceSourceFormatKeyword::EmbeddedOpentype
292 | FontFaceSourceFormatKeyword::Woff) = format
293 {
294 #[cfg(feature = "tracing")]
295 tracing::warn!("Skipping unsupported font of type {:?}", _font_format);
296 return;
297 }
298 let url = url_source.url.url().unwrap().as_ref().clone();
299 network_provider.fetch(doc_id, Request::get(url), Box::new(FontFaceHandler(format)))
300 });
301}
302
303pub struct ImageHandler(usize, ImageType);
304impl ImageHandler {
305 pub fn new(node_id: usize, kind: ImageType) -> Self {
306 Self(node_id, kind)
307 }
308}
309impl NetHandler<Resource> for ImageHandler {
310 fn bytes(self: Box<Self>, doc_id: usize, bytes: Bytes, callback: SharedCallback<Resource>) {
311 if let Ok(image) = image::ImageReader::new(Cursor::new(&bytes))
313 .with_guessed_format()
314 .expect("IO errors impossible with Cursor")
315 .decode()
316 {
317 let raw_rgba8_data = image.clone().into_rgba8().into_raw();
318 callback.call(
319 doc_id,
320 Ok(Resource::Image(
321 self.0,
322 self.1,
323 image.width(),
324 image.height(),
325 Arc::new(raw_rgba8_data),
326 )),
327 );
328 return;
329 };
330
331 #[cfg(feature = "svg")]
332 {
333 use crate::util::parse_svg;
334 if let Ok(tree) = parse_svg(&bytes) {
335 callback.call(doc_id, Ok(Resource::Svg(self.0, self.1, Box::new(tree))));
336 return;
337 }
338 }
339
340 callback.call(doc_id, Err(Some(String::from("Could not parse image"))))
341 }
342}