use selectors::context::QuirksMode;
use std::sync::atomic::Ordering as Ao;
use std::{
io::Cursor,
sync::{Arc, atomic::AtomicUsize, mpsc::Sender},
};
use style::{
font_face::{FontFaceSourceFormat, FontFaceSourceFormatKeyword, Source},
media_queries::MediaList,
servo_arc::Arc as ServoArc,
shared_lock::SharedRwLock,
shared_lock::{Locked, SharedRwLockReadGuard},
stylesheets::{
AllowImportRules, CssRule, DocumentStyleSheet, ImportRule, Origin, Stylesheet,
StylesheetInDocument, StylesheetLoader as ServoStylesheetLoader, UrlExtraData,
import_rule::{ImportLayer, ImportSheet, ImportSupportsCondition},
},
values::{CssUrl, SourceLocation},
};
use blitz_traits::net::{Bytes, NetHandler, NetProvider, Request};
use blitz_traits::shell::ShellProvider;
use url::Url;
use crate::{document::DocumentEvent, util::ImageType};
#[derive(Clone, Debug)]
pub enum Resource {
Image(ImageType, u32, u32, Arc<Vec<u8>>),
#[cfg(feature = "svg")]
Svg(ImageType, Arc<usvg::Tree>),
Css(DocumentStyleSheet),
Font(Bytes),
None,
}
pub(crate) struct ResourceHandler<T: Send + Sync + 'static> {
doc_id: usize,
request_id: usize,
node_id: Option<usize>,
tx: Sender<DocumentEvent>,
shell_provider: Arc<dyn ShellProvider>,
data: T,
}
impl<T: Send + Sync + 'static> ResourceHandler<T> {
pub(crate) fn new(
tx: Sender<DocumentEvent>,
doc_id: usize,
node_id: Option<usize>,
shell_provider: Arc<dyn ShellProvider>,
data: T,
) -> Self {
static REQUEST_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
Self {
request_id: REQUEST_ID_COUNTER.fetch_add(1, Ao::Relaxed),
doc_id,
node_id,
tx,
shell_provider,
data,
}
}
pub(crate) fn boxed(
tx: Sender<DocumentEvent>,
doc_id: usize,
node_id: Option<usize>,
shell_provider: Arc<dyn ShellProvider>,
data: T,
) -> Box<dyn NetHandler>
where
ResourceHandler<T>: NetHandler,
{
Box::new(Self::new(tx, doc_id, node_id, shell_provider, data)) as _
}
pub(crate) fn request_id(&self) -> usize {
self.request_id
}
fn respond(&self, resolved_url: String, result: Result<Resource, String>) {
let response = ResourceLoadResponse {
request_id: self.request_id,
node_id: self.node_id,
resolved_url: Some(resolved_url),
result,
};
let _ = self.tx.send(DocumentEvent::ResourceLoad(response));
self.shell_provider.request_redraw();
}
}
#[allow(unused)]
pub struct ResourceLoadResponse {
pub request_id: usize,
pub node_id: Option<usize>,
pub resolved_url: Option<String>,
pub result: Result<Resource, String>,
}
pub struct StylesheetHandler {
pub source_url: Url,
pub guard: SharedRwLock,
pub net_provider: Arc<dyn NetProvider>,
}
impl NetHandler for ResourceHandler<StylesheetHandler> {
fn bytes(self: Box<Self>, resolved_url: String, bytes: Bytes) {
let Ok(css) = std::str::from_utf8(&bytes) else {
return self.respond(resolved_url, Err(String::from("Invalid UTF8")));
};
let sheet = Stylesheet::from_str(
css,
self.data.source_url.clone().into(),
Origin::Author,
ServoArc::new(self.data.guard.wrap(MediaList::empty())),
self.data.guard.clone(),
Some(&StylesheetLoader {
tx: self.tx.clone(),
doc_id: self.doc_id,
net_provider: self.data.net_provider.clone(),
shell_provider: self.shell_provider.clone(),
}),
None, QuirksMode::NoQuirks,
AllowImportRules::Yes,
);
self.respond(
resolved_url,
Ok(Resource::Css(DocumentStyleSheet(ServoArc::new(sheet)))),
);
}
}
#[derive(Clone)]
pub(crate) struct StylesheetLoader {
pub(crate) tx: Sender<DocumentEvent>,
pub(crate) doc_id: usize,
pub(crate) net_provider: Arc<dyn NetProvider>,
pub(crate) shell_provider: Arc<dyn ShellProvider>,
}
impl ServoStylesheetLoader for StylesheetLoader {
fn request_stylesheet(
&self,
url: CssUrl,
location: SourceLocation,
lock: &SharedRwLock,
media: ServoArc<Locked<MediaList>>,
supports: Option<ImportSupportsCondition>,
layer: ImportLayer,
) -> ServoArc<Locked<ImportRule>> {
if !supports.as_ref().is_none_or(|s| s.enabled) {
return ServoArc::new(lock.wrap(ImportRule {
url,
stylesheet: ImportSheet::new_refused(),
supports,
layer,
source_location: location,
}));
}
let import = ImportRule {
url,
stylesheet: ImportSheet::new_pending(),
supports,
layer,
source_location: location,
};
let url = import.url.url().unwrap().clone();
let import = ServoArc::new(lock.wrap(import));
self.net_provider.fetch(
self.doc_id,
Request::get(url.as_ref().clone()),
ResourceHandler::boxed(
self.tx.clone(),
self.doc_id,
None, self.shell_provider.clone(),
NestedStylesheetHandler {
url: url.clone(),
loader: self.clone(),
lock: lock.clone(),
media,
import_rule: import.clone(),
net_provider: self.net_provider.clone(),
},
),
);
import
}
}
struct NestedStylesheetHandler {
loader: StylesheetLoader,
lock: SharedRwLock,
url: ServoArc<Url>,
media: ServoArc<Locked<MediaList>>,
import_rule: ServoArc<Locked<ImportRule>>,
net_provider: Arc<dyn NetProvider>,
}
impl NetHandler for ResourceHandler<NestedStylesheetHandler> {
fn bytes(self: Box<Self>, resolved_url: String, bytes: Bytes) {
let Ok(css) = std::str::from_utf8(&bytes) else {
return self.respond(resolved_url, Err(String::from("Invalid UTF8")));
};
let sheet = ServoArc::new(Stylesheet::from_str(
css,
UrlExtraData(self.data.url.clone()),
Origin::Author,
self.data.media.clone(),
self.data.lock.clone(),
Some(&self.data.loader),
None, QuirksMode::NoQuirks,
AllowImportRules::Yes,
));
fetch_font_face(
self.tx.clone(),
self.doc_id,
self.node_id,
&sheet,
&self.data.net_provider,
&self.shell_provider,
&self.data.lock.read(),
);
let mut guard = self.data.lock.write();
self.data.import_rule.write_with(&mut guard).stylesheet = ImportSheet::Sheet(sheet);
drop(guard);
self.respond(resolved_url, Ok(Resource::None))
}
}
struct FontFaceHandler(FontFaceSourceFormatKeyword);
impl NetHandler for ResourceHandler<FontFaceHandler> {
fn bytes(mut self: Box<Self>, resolved_url: String, bytes: Bytes) {
let result = self.data.parse(bytes);
self.respond(resolved_url, result)
}
}
impl FontFaceHandler {
fn parse(&mut self, bytes: Bytes) -> Result<Resource, String> {
if self.0 == FontFaceSourceFormatKeyword::None && bytes.len() >= 4 {
self.0 = match &bytes.as_ref()[0..4] {
#[cfg(feature = "woff")]
b"wOFF" => FontFaceSourceFormatKeyword::Woff,
#[cfg(feature = "woff")]
b"wOF2" => FontFaceSourceFormatKeyword::Woff2,
b"OTTO" => FontFaceSourceFormatKeyword::Opentype,
&[0x00, 0x01, 0x00, 0x00] => FontFaceSourceFormatKeyword::Truetype,
b"true" => FontFaceSourceFormatKeyword::Truetype,
_ => FontFaceSourceFormatKeyword::None,
}
}
#[cfg(feature = "woff")]
let mut bytes = bytes;
match self.0 {
#[cfg(feature = "woff")]
FontFaceSourceFormatKeyword::Woff => {
#[cfg(feature = "tracing")]
tracing::info!("Decompressing woff1 font");
let decompressed = wuff::decompress_woff1(&bytes).ok();
if let Some(decompressed) = decompressed {
bytes = Bytes::from(decompressed);
} else {
#[cfg(feature = "tracing")]
tracing::warn!("Failed to decompress woff1 font");
}
}
#[cfg(feature = "woff")]
FontFaceSourceFormatKeyword::Woff2 => {
#[cfg(feature = "tracing")]
tracing::info!("Decompressing woff2 font");
let decompressed = wuff::decompress_woff2(&bytes).ok();
if let Some(decompressed) = decompressed {
bytes = Bytes::from(decompressed);
} else {
#[cfg(feature = "tracing")]
tracing::warn!("Failed to decompress woff2 font");
}
}
FontFaceSourceFormatKeyword::None => {
return Ok(Resource::None);
}
_ => {}
}
Ok(Resource::Font(bytes))
}
}
pub(crate) fn fetch_font_face(
tx: Sender<DocumentEvent>,
doc_id: usize,
node_id: Option<usize>,
sheet: &Stylesheet,
network_provider: &Arc<dyn NetProvider>,
shell_provider: &Arc<dyn ShellProvider>,
read_guard: &SharedRwLockReadGuard,
) {
sheet
.contents(read_guard)
.rules(read_guard)
.iter()
.filter_map(|rule| match rule {
CssRule::FontFace(font_face) => {
let descriptor = &font_face.read_with(read_guard).descriptors;
descriptor
.src
.as_ref()
.filter(|_| descriptor.font_family.is_some())
}
_ => None,
})
.for_each(|source_list| {
let preferred_source = source_list
.0
.iter()
.filter_map(|source| match source {
Source::Url(url_source) => Some(url_source),
Source::Local(_) => None,
})
.find_map(|url_source| {
let mut format = match &url_source.format_hint {
Some(FontFaceSourceFormat::Keyword(fmt)) => *fmt,
Some(FontFaceSourceFormat::String(str)) => match str.as_str() {
"woff2" => FontFaceSourceFormatKeyword::Woff2,
"ttf" => FontFaceSourceFormatKeyword::Truetype,
"otf" => FontFaceSourceFormatKeyword::Opentype,
_ => FontFaceSourceFormatKeyword::None,
},
_ => FontFaceSourceFormatKeyword::None,
};
if format == FontFaceSourceFormatKeyword::None {
let (_, end) = url_source.url.as_str().rsplit_once('.')?;
format = match end {
"woff2" => FontFaceSourceFormatKeyword::Woff2,
"woff" => FontFaceSourceFormatKeyword::Woff,
"ttf" => FontFaceSourceFormatKeyword::Truetype,
"otf" => FontFaceSourceFormatKeyword::Opentype,
"svg" => FontFaceSourceFormatKeyword::Svg,
"eot" => FontFaceSourceFormatKeyword::EmbeddedOpentype,
_ => FontFaceSourceFormatKeyword::None,
}
}
if matches!(
format,
FontFaceSourceFormatKeyword::Svg
| FontFaceSourceFormatKeyword::EmbeddedOpentype
) {
#[cfg(feature = "tracing")]
tracing::warn!("Skipping unsupported font of type {:?}", format);
return None;
}
#[cfg(not(feature = "woff"))]
if matches!(
format,
FontFaceSourceFormatKeyword::Woff | FontFaceSourceFormatKeyword::Woff2
) {
#[cfg(feature = "tracing")]
tracing::warn!("Skipping unsupported font of type {:?}", format);
return None;
}
let url = url_source.url.url().unwrap().as_ref().clone();
Some((url, format))
});
if let Some((url, format)) = preferred_source {
network_provider.fetch(
doc_id,
Request::get(url),
ResourceHandler::boxed(
tx.clone(),
doc_id,
node_id,
shell_provider.clone(),
FontFaceHandler(format),
),
);
}
})
}
pub struct ImageHandler {
kind: ImageType,
}
impl ImageHandler {
pub fn new(kind: ImageType) -> Self {
Self { kind }
}
}
impl NetHandler for ResourceHandler<ImageHandler> {
fn bytes(self: Box<Self>, resolved_url: String, bytes: Bytes) {
let result = self.data.parse(bytes);
self.respond(resolved_url, result)
}
}
impl ImageHandler {
fn parse(&self, bytes: Bytes) -> Result<Resource, String> {
if let Ok(image) = image::ImageReader::new(Cursor::new(&bytes))
.with_guessed_format()
.expect("IO errors impossible with Cursor")
.decode()
{
let raw_rgba8_data = image.clone().into_rgba8().into_raw();
return Ok(Resource::Image(
self.kind,
image.width(),
image.height(),
Arc::new(raw_rgba8_data),
));
};
#[cfg(feature = "svg")]
{
use crate::util::parse_svg;
if let Ok(tree) = parse_svg(&bytes) {
return Ok(Resource::Svg(self.kind, Arc::new(tree)));
}
}
Err(String::from("Could not parse image"))
}
}