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 && bytes.len() >= 4 {
175 self.0 = match &bytes.as_ref()[0..4] {
176 #[cfg(any(feature = "woff-c", feature = "woff-rust"))]
179 b"wOFF" => FontFaceSourceFormatKeyword::Woff,
180 #[cfg(any(feature = "woff-c", feature = "woff-rust"))]
183 b"wOF2" => FontFaceSourceFormatKeyword::Woff2,
184 b"OTTO" => FontFaceSourceFormatKeyword::Opentype,
187 &[0x00, 0x01, 0x00, 0x00] => FontFaceSourceFormatKeyword::Truetype,
190 b"true" => 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"))]
203 FontFaceSourceFormatKeyword::Woff => {
204 #[cfg(feature = "tracing")]
205 tracing::info!("Decompressing woff1 font");
206
207 #[cfg(feature = "woff-c")]
209 let decompressed = woff::version1::decompress(&bytes);
210
211 #[cfg(feature = "woff-rust")]
213 let decompressed = wuff::decompress_woff1(&bytes).ok();
214
215 if let Some(decompressed) = decompressed {
216 bytes = Bytes::from(decompressed);
217 } else {
218 #[cfg(feature = "tracing")]
219 tracing::warn!("Failed to decompress woff1 font");
220 }
221 }
222 #[cfg(any(feature = "woff-c", feature = "woff-rust"))]
223 FontFaceSourceFormatKeyword::Woff2 => {
224 #[cfg(feature = "tracing")]
225 tracing::info!("Decompressing woff2 font");
226
227 #[cfg(feature = "woff-c")]
229 let decompressed = woff::version2::decompress(&bytes);
230
231 #[cfg(feature = "woff-rust")]
233 let decompressed = wuff::decompress_woff2(&bytes).ok();
234
235 if let Some(decompressed) = decompressed {
236 bytes = Bytes::from(decompressed);
237 } else {
238 #[cfg(feature = "tracing")]
239 tracing::warn!("Failed to decompress woff2 font");
240 }
241 }
242 FontFaceSourceFormatKeyword::None => {
243 return;
244 }
245 _ => {}
246 }
247
248 callback.call(doc_id, Ok(Resource::Font(bytes)))
249 }
250}
251
252fn fetch_font_face(
253 doc_id: usize,
254 sheet: &Stylesheet,
255 network_provider: &SharedProvider<Resource>,
256 read_guard: &SharedRwLockReadGuard,
257) {
258 sheet
259 .rules(read_guard)
260 .iter()
261 .filter_map(|rule| match rule {
262 CssRule::FontFace(font_face) => font_face.read_with(read_guard).sources.as_ref(),
263 _ => None,
264 })
265 .flat_map(|source_list| &source_list.0)
266 .filter_map(|source| match source {
267 Source::Url(url_source) => Some(url_source),
268 _ => None,
269 })
270 .for_each(|url_source| {
271 let mut format = match &url_source.format_hint {
272 Some(FontFaceSourceFormat::Keyword(fmt)) => *fmt,
273 Some(FontFaceSourceFormat::String(str)) => match str.as_str() {
274 "woff2" => FontFaceSourceFormatKeyword::Woff2,
275 "ttf" => FontFaceSourceFormatKeyword::Truetype,
276 "otf" => FontFaceSourceFormatKeyword::Opentype,
277 _ => FontFaceSourceFormatKeyword::None,
278 },
279 _ => FontFaceSourceFormatKeyword::None,
280 };
281 if format == FontFaceSourceFormatKeyword::None {
282 let Some((_, end)) = url_source.url.as_str().rsplit_once('.') else {
283 return;
284 };
285 format = match end {
286 "woff2" => FontFaceSourceFormatKeyword::Woff2,
287 "woff" => FontFaceSourceFormatKeyword::Woff,
288 "ttf" => FontFaceSourceFormatKeyword::Truetype,
289 "otf" => FontFaceSourceFormatKeyword::Opentype,
290 "svg" => FontFaceSourceFormatKeyword::Svg,
291 "eot" => FontFaceSourceFormatKeyword::EmbeddedOpentype,
292 _ => FontFaceSourceFormatKeyword::None,
293 }
294 }
295 if let _font_format @ (FontFaceSourceFormatKeyword::Svg
296 | FontFaceSourceFormatKeyword::EmbeddedOpentype
297 | FontFaceSourceFormatKeyword::Woff) = format
298 {
299 #[cfg(feature = "tracing")]
300 tracing::warn!("Skipping unsupported font of type {:?}", _font_format);
301 return;
302 }
303 let url = url_source.url.url().unwrap().as_ref().clone();
304 network_provider.fetch(doc_id, Request::get(url), Box::new(FontFaceHandler(format)))
305 });
306}
307
308pub struct ImageHandler(usize, ImageType);
309impl ImageHandler {
310 pub fn new(node_id: usize, kind: ImageType) -> Self {
311 Self(node_id, kind)
312 }
313}
314impl NetHandler<Resource> for ImageHandler {
315 fn bytes(self: Box<Self>, doc_id: usize, bytes: Bytes, callback: SharedCallback<Resource>) {
316 if let Ok(image) = image::ImageReader::new(Cursor::new(&bytes))
318 .with_guessed_format()
319 .expect("IO errors impossible with Cursor")
320 .decode()
321 {
322 let raw_rgba8_data = image.clone().into_rgba8().into_raw();
323 callback.call(
324 doc_id,
325 Ok(Resource::Image(
326 self.0,
327 self.1,
328 image.width(),
329 image.height(),
330 Arc::new(raw_rgba8_data),
331 )),
332 );
333 return;
334 };
335
336 #[cfg(feature = "svg")]
337 {
338 use crate::util::parse_svg;
339 if let Ok(tree) = parse_svg(&bytes) {
340 callback.call(doc_id, Ok(Resource::Svg(self.0, self.1, Box::new(tree))));
341 return;
342 }
343 }
344
345 callback.call(doc_id, Err(Some(String::from("Could not parse image"))))
346 }
347}