diff --git a/crates/gosub_css3/src/lib.rs b/crates/gosub_css3/src/lib.rs index 41890bbf1..1807d9640 100644 --- a/crates/gosub_css3/src/lib.rs +++ b/crates/gosub_css3/src/lib.rs @@ -22,7 +22,7 @@ pub mod matcher; pub mod node; pub mod parser; pub mod stylesheet; -mod system; +pub mod system; pub mod tokenizer; mod unicode; pub mod walker; diff --git a/crates/gosub_css3/src/system.rs b/crates/gosub_css3/src/system.rs index 0e407d495..437a6f280 100644 --- a/crates/gosub_css3/src/system.rs +++ b/crates/gosub_css3/src/system.rs @@ -17,7 +17,7 @@ use crate::stylesheet::{CssDeclaration, CssValue, Specificity}; use gosub_shared::types::Result; #[derive(Debug, Clone)] -struct Css3System; +pub struct Css3System; impl CssSystem for Css3System { type Stylesheet = crate::stylesheet::CssStylesheet; diff --git a/crates/gosub_html5/src/parser.rs b/crates/gosub_html5/src/parser.rs index d25794b6d..e28ae4118 100644 --- a/crates/gosub_html5/src/parser.rs +++ b/crates/gosub_html5/src/parser.rs @@ -230,6 +230,31 @@ pub struct Html5Parser<'chars, D: Document, C: CssSystem> { context_doc: Option>, } +impl, C: CssSystem> gosub_shared::traits::html5::Html5Parser + for Html5Parser<'_, D, C> +{ + type Document = D; + type Options = Html5ParserOptions; + + fn parse( + stream: &mut ByteStream, + doc: DocumentHandle, + opts: Option, + ) -> Result> { + Self::parse_document(stream, doc, opts) + } + + fn parse_fragment( + stream: &mut ByteStream, + doc: DocumentHandle, + context_node: &>::Node, + options: Option, + start_location: Location, + ) -> Result> { + Self::parse_fragment(stream, doc, context_node, options, start_location) + } +} + /// Defines the scopes for in_scope() #[derive(Clone, Copy)] enum Scope { diff --git a/crates/gosub_render_backend/src/layout.rs b/crates/gosub_render_backend/src/layout.rs index a8912763b..aaad8bf4f 100644 --- a/crates/gosub_render_backend/src/layout.rs +++ b/crates/gosub_render_backend/src/layout.rs @@ -125,7 +125,7 @@ pub trait Layout: Default { pub trait Node { type Property: CssProperty; - fn get_property(&mut self, name: &str) -> Option<&mut Self::Property>; + fn get_property(&self, name: &str) -> Option<&Self::Property>; fn text_data(&self) -> Option<&str>; fn text_size(&self) -> Option; diff --git a/crates/gosub_render_backend/src/lib.rs b/crates/gosub_render_backend/src/lib.rs index 1b3ac11dc..caba549db 100644 --- a/crates/gosub_render_backend/src/lib.rs +++ b/crates/gosub_render_backend/src/lib.rs @@ -11,7 +11,6 @@ use smallvec::SmallVec; pub mod geo; pub mod layout; pub mod svg; -mod render_tree; pub trait WindowHandle: HasDisplayHandle + HasWindowHandle + Send + Sync + Clone {} diff --git a/crates/gosub_render_utils/src/lib.rs b/crates/gosub_render_utils/src/lib.rs index ea8d8e974..949b917a5 100644 --- a/crates/gosub_render_utils/src/lib.rs +++ b/crates/gosub_render_utils/src/lib.rs @@ -4,5 +4,6 @@ //! pub mod position; -pub mod macos_render_tree; +// pub mod macos_render_tree; +pub mod render_tree; pub mod text; diff --git a/crates/gosub_render_utils/src/macos_render_tree.rs b/crates/gosub_render_utils/src/macos_render_tree.rs index 24e7a5f25..7ef05419c 100644 --- a/crates/gosub_render_utils/src/macos_render_tree.rs +++ b/crates/gosub_render_utils/src/macos_render_tree.rs @@ -1,10 +1,10 @@ -use std::borrow::BorrowMut; -use std::{cell::RefCell, rc::Rc}; -use gosub_html5::node::node::NodeDataTypeInternal; -use gosub_shared::document::DocumentHandle; - use crate::macos_render_tree::properties::Position; use crate::macos_render_tree::{properties::Rectangle, text::TextNode}; +use gosub_html5::node::node::NodeDataTypeInternal; +use gosub_shared::document::DocumentHandle; +use gosub_shared::traits::node::TextDataType; +use std::borrow::BorrowMut; +use std::{cell::RefCell, rc::Rc}; pub mod properties; pub mod text; diff --git a/crates/gosub_render_utils/src/position.rs b/crates/gosub_render_utils/src/position.rs index 15e4b29da..14ba7277e 100644 --- a/crates/gosub_render_utils/src/position.rs +++ b/crates/gosub_render_utils/src/position.rs @@ -2,9 +2,12 @@ use std::cmp::Ordering; use rstar::{RTree, RTreeObject, AABB}; +use crate::render_tree::RenderTree; use gosub_render_backend::layout::{Layout, LayoutTree, Layouter}; use gosub_render_backend::RenderBackend; use gosub_shared::node::NodeId; +use gosub_shared::traits::css3::CssSystem; +use gosub_shared::traits::document::Document; #[derive(Debug)] pub struct Element { @@ -32,7 +35,9 @@ pub struct PositionTree { } impl PositionTree { - pub fn from_tree(from_tree: &RenderTree) -> Self { + pub fn from_tree, C: CssSystem>( + from_tree: &RenderTree, + ) -> Self { let mut tree = RTree::new(); //TODO: we somehow need to get the border radius and a potential stacking context of the element here @@ -42,8 +47,8 @@ impl PositionTree { Self { tree } } - fn add_node_to_tree( - from_tree: &RenderTree, + fn add_node_to_tree, C: CssSystem>( + from_tree: &RenderTree, id: NodeId, z_index: i32, tree: &mut RTree, diff --git a/crates/gosub_render_backend/src/render_tree.rs b/crates/gosub_render_utils/src/render_tree.rs similarity index 97% rename from crates/gosub_render_backend/src/render_tree.rs rename to crates/gosub_render_utils/src/render_tree.rs index 542ef4b89..109381b0b 100644 --- a/crates/gosub_render_backend/src/render_tree.rs +++ b/crates/gosub_render_utils/src/render_tree.rs @@ -1,19 +1,15 @@ -use crate::geo::Size; -use crate::layout::{HasTextLayout, Layout, LayoutTree, Layouter, Node, TextLayout}; use gosub_html5::document::document::TreeIterator; -use gosub_html5::node::data::element::ElementData; +use gosub_render_backend::layout::{HasTextLayout, Layout, LayoutTree, Layouter, TextLayout}; +use gosub_render_backend::{layout, Size}; use gosub_shared::document::DocumentHandle; use gosub_shared::node::NodeId; -use gosub_shared::traits::css3::CssStylesheet; use gosub_shared::traits::css3::{CssProperty, CssPropertyMap, CssSystem}; use gosub_shared::traits::document::Document; -use gosub_shared::traits::node::NodeData; use gosub_shared::traits::node::{ElementDataType, Node as DocumentNode, TextDataType}; +use gosub_shared::traits::node::{Node, NodeData}; use gosub_shared::types::Result; -use log::warn; use std::collections::HashMap; use std::fmt::{Debug, Formatter}; -use std::marker::PhantomData; mod desc; @@ -30,7 +26,7 @@ pub struct RenderTree, C: CssSystem> { pub root: NodeId, pub dirty: bool, next_id: NodeId, - handle: Option>, + pub handle: Option>, } #[allow(unused)] @@ -308,7 +304,7 @@ impl, C: CssSystem> RenderTree { for id in self.nodes.keys() { // Check CSS styles and remove if not renderable - if let Some(mut prop) = self.get_property(*id, "display") { + if let Some(prop) = self.get_property(*id, "display") { if prop.as_string() == Some("none") { delete_list.append(&mut self.get_child_node_ids(*id)); delete_list.push(*id); @@ -602,11 +598,11 @@ impl HasTextLayout for RenderTreeNode { } } -impl Node for RenderTreeNode { +impl layout::Node for RenderTreeNode { type Property = C::Property; - fn get_property(&mut self, name: &str) -> Option<&mut Self::Property> { - self.get_property(name) + fn get_property(&self, name: &str) -> Option<&Self::Property> { + self.properties.get(name) } fn text_data(&self) -> Option<&str> { if let RenderNodeData::Text(text) = &self.data { diff --git a/crates/gosub_render_backend/src/render_tree/desc.rs b/crates/gosub_render_utils/src/render_tree/desc.rs similarity index 94% rename from crates/gosub_render_backend/src/render_tree/desc.rs rename to crates/gosub_render_utils/src/render_tree/desc.rs index f017baaf3..7ae505fd8 100644 --- a/crates/gosub_render_backend/src/render_tree/desc.rs +++ b/crates/gosub_render_utils/src/render_tree/desc.rs @@ -1,6 +1,6 @@ -use crate::layout::{Layout, Layouter}; use crate::render_tree::{RenderNodeData, RenderTree}; -use crate::{NodeDesc, Point, Size}; +use gosub_render_backend::layout::{Layout, Layouter}; +use gosub_render_backend::{NodeDesc, Point, Size}; use gosub_shared::node::NodeId; use gosub_shared::traits::css3::{CssPropertyMap, CssSystem}; use gosub_shared::traits::document::Document; diff --git a/crates/gosub_renderer/src/draw.rs b/crates/gosub_renderer/src/draw.rs index 81ca98347..df11b39c2 100644 --- a/crates/gosub_renderer/src/draw.rs +++ b/crates/gosub_renderer/src/draw.rs @@ -8,16 +8,21 @@ use gosub_css3::colors::RgbColor; use gosub_css3::stylesheet::CssValue; use gosub_net::http::fetcher::Fetcher; use gosub_render_backend::geo::{Size, SizeU32, FP}; -use gosub_render_backend::layout::{Layout, LayoutTree, Layouter, TextLayout}; +use gosub_render_backend::layout::{Layout, LayoutTree, Layouter, Node as _, TextLayout}; use gosub_render_backend::svg::SvgRenderer; use gosub_render_backend::{ Border, BorderSide, BorderStyle, Brush, Color, ImageBuffer, NodeDesc, Rect, RenderBackend, RenderBorder, RenderRect, RenderText, Scene as TScene, Text, Transform, }; + use gosub_rendering::position::PositionTree; +use gosub_rendering::render_tree::{RenderNodeData, RenderTree, RenderTreeNode}; use gosub_shared::node::NodeId; +use gosub_shared::traits::css3::{CssProperty, CssPropertyMap, CssSystem}; +use gosub_shared::traits::document::Document; +use gosub_shared::traits::html5::Html5Parser; +use gosub_shared::traits::node::{ElementDataType, Node}; use gosub_shared::types::Result; -use gosub_css3::render_tree::{RenderNodeData, RenderTree, RenderTreeNode}; use crate::debug::scale::px_scale; use crate::draw::img::request_img; @@ -25,14 +30,22 @@ use crate::render_tree::{load_html_rendertree, TreeDrawer}; mod img; -pub trait SceneDrawer> { +pub trait SceneDrawer< + B: RenderBackend, + L: Layouter, + LT: LayoutTree, + D: Document, + C: CssSystem, +> +{ fn draw(&mut self, backend: &mut B, data: &mut B::WindowData<'_>, size: SizeU32) -> bool; fn mouse_move(&mut self, backend: &mut B, x: FP, y: FP) -> bool; fn scroll(&mut self, point: Point); - fn from_url(url: Url, layouter: L, debug: bool) -> Result + fn from_url

(url: Url, layouter: L, debug: bool) -> Result where - Self: Sized; + Self: Sized, + P: Html5Parser; fn clear_buffers(&mut self); fn toggle_debug(&mut self); @@ -52,7 +65,8 @@ const DEBUG_BORDER_COLOR: (u8, u8, u8) = (255, 72, 72); //rgb(255, 72, 72) type Point = gosub_shared::types::Point; -impl SceneDrawer> for TreeDrawer +impl, C: CssSystem> + SceneDrawer, D, C> for TreeDrawer where <::Text as Text>::Font: From<<::TextLayout as TextLayout>::Font>, @@ -196,8 +210,11 @@ where self.dirty = true; } - fn from_url(url: Url, layouter: L, debug: bool) -> Result { - let rt = load_html_rendertree(url.clone())?; + fn from_url

(url: Url, layouter: L, debug: bool) -> Result + where + P: Html5Parser, + { + let rt = load_html_rendertree::(url.clone())?; Ok(Self::new(rt, layouter, url, debug)) } @@ -236,13 +253,13 @@ where } } -struct Drawer<'s, 't, B: RenderBackend, L: Layouter> { +struct Drawer<'s, 't, B: RenderBackend, L: Layouter, D: Document, C: CssSystem> { scene: &'s mut B::Scene, - drawer: &'t mut TreeDrawer, + drawer: &'t mut TreeDrawer, svg: B::SVGRenderer, } -impl Drawer<'_, '_, B, L> +impl, C: CssSystem> Drawer<'_, '_, B, L, D, C> where <::Text as Text>::Font: From<<::TextLayout as TextLayout>::Font>, @@ -260,7 +277,7 @@ where // print_tree(&self.taffy, self.root, &self.style); - self.drawer.position = PositionTree::from_tree::(&self.drawer.tree); + self.drawer.position = PositionTree::from_tree::(&self.drawer.tree); self.render_node_with_children(self.drawer.tree.root, Point::ZERO); } @@ -282,56 +299,71 @@ where } fn render_node(&mut self, id: NodeId, pos: &mut Point) -> anyhow::Result<()> { - let mut needs_redraw = false; - + let mut size_change = None; let node = self .drawer .tree - .get_node_mut(id) + .get_node(id) .ok_or(anyhow!("Node {id} not found"))?; let p = node.layout.rel_pos(); pos.x += p.x as FP; pos.y += p.y as FP; - let (border_radius, redraw) = - render_bg::(node, self.scene, pos, &mut self.svg, &self.drawer.fetcher); + let (border_radius, new_size) = + render_bg::(node, self.scene, pos, &mut self.svg, &self.drawer.fetcher); - needs_redraw |= redraw; + size_change = new_size; - if let RenderNodeData::Element(element) = &node.data { - if element.name() == "img" { - let src = element - .attributes - .get("src") - .ok_or(anyhow!("Image element has no src attribute"))?; + if node.name == "img" { + let Some(handle) = self.drawer.tree.handle.as_ref() else { + return Err(anyhow!("No document handle")); + }; - let url = src.as_str(); + let doc = handle.get(); - let size = node.layout.size_or().map(|x| x.u32()); + let dom_node = doc.node_by_id(id).ok_or(anyhow!("Node not found"))?; - let img = request_img(&self.drawer.fetcher, &mut self.svg, url, size)?; + let element = dom_node + .get_element_data() + .ok_or(anyhow!("Node is not an element"))?; - if size.is_none() { - node.layout.set_size_and_content(img.size()); - needs_redraw |= true; - } + let src = element + .attribute("src") + .ok_or(anyhow!("Image element has no src attribute"))?; - let fit = element - .attributes - .get("object-fit") - .map(|prop| prop.as_str()) - .unwrap_or("contain"); + let url = src.as_str(); - let size = size.unwrap_or(img.size()).f32(); + let size = node.layout.size_or().map(|x| x.u32()); - render_image::(img, self.scene, *pos, size, border_radius, fit)?; + let img = request_img(&self.drawer.fetcher, &mut self.svg, url, size)?; + + if size.is_none() { + size_change = Some(img.size()); } + + let fit = node + .properties + .get("object-fit") + .and_then(|prop| prop.as_string()) + .unwrap_or("contain"); + + let size = size.unwrap_or(img.size()).f32(); + + render_image::(img, self.scene, *pos, size, border_radius, fit)?; } - render_text::(node, self.scene, pos); + render_text::(node, self.scene, pos); + + if let Some(new) = size_change { + let node = self + .drawer + .tree + .get_node_mut(id) + .ok_or(anyhow!("Node {id} not found"))?; + + node.layout.set_size(new.into()); - if needs_redraw { self.drawer.set_needs_redraw() } @@ -339,8 +371,8 @@ where } } -fn render_text( - node: &mut RenderTreeNode, +fn render_text( + node: &RenderTreeNode, scene: &mut B::Scene, pos: &Point, ) where @@ -358,19 +390,11 @@ fn render_text( let color = node .properties .get("color") - .and_then(|prop| { - prop.compute_value(); - - match &prop.actual { - CssValue::Color(color) => Some(*color), - CssValue::String(color) => Some(RgbColor::from(color.as_str())), - _ => None, - } - }) - .map(|color| Color::rgba(color.r as u8, color.g as u8, color.b as u8, color.a as u8)) + .and_then(|prop| prop.parse_color()) + .map(|color| Color::rgba(color.0 as u8, color.1 as u8, color.2 as u8, color.3 as u8)) .unwrap_or(Color::BLACK); - if let RenderNodeData::Text(ref text) = node.data { + if let RenderNodeData::Text(text) = &node.data { let Some(layout) = text.layout.as_ref() else { warn!("No layout for text node"); return; @@ -399,61 +423,41 @@ fn render_text( } } -fn render_bg( - node: &mut RenderTreeNode, +fn render_bg( + node: &RenderTreeNode, scene: &mut B::Scene, pos: &Point, svg: &mut B::SVGRenderer, fetcher: &Fetcher, -) -> ((FP, FP, FP, FP), bool) { +) -> ((FP, FP, FP, FP), Option) { let bg_color = node .properties .get("background-color") - .and_then(|prop| { - prop.compute_value(); - - match &prop.actual { - CssValue::Color(color) => Some(*color), - CssValue::String(color) => Some(RgbColor::from(color.as_str())), - _ => None, - } - }) - .map(|color| Color::rgba(color.r as u8, color.g as u8, color.b as u8, color.a as u8)); + .and_then(|prop| prop.parse_color()) + .map(|color| Color::rgba(color.0 as u8, color.1 as u8, color.2 as u8, color.3 as u8)); let border_radius_left = node .properties .get("border-radius-left") - .map(|prop| { - prop.compute_value(); - prop.actual.unit_to_px() as f64 - }) + .map(|prop| prop.unit_to_px() as f64) .unwrap_or(0.0); let border_radius_right = node .properties .get("border-radius-right") - .map(|prop| { - prop.compute_value(); - prop.actual.unit_to_px() as f64 - }) + .map(|prop| prop.unit_to_px() as f64) .unwrap_or(0.0); let border_radius_top = node .properties .get("border-radius-top") - .map(|prop| { - prop.compute_value(); - prop.actual.unit_to_px() as f64 - }) + .map(|prop| prop.unit_to_px() as f64) .unwrap_or(0.0); let border_radius_bottom = node .properties .get("border-radius-bottom") - .map(|prop| { - prop.compute_value(); - prop.actual.unit_to_px() as f64 - }) + .map(|prop| prop.unit_to_px() as f64) .unwrap_or(0.0); let border_radius = ( @@ -463,7 +467,7 @@ fn render_bg( border_radius_left as FP, ); - let border = get_border::(node).map(|border| RenderBorder::new(border)); + let border = get_border::(node).map(|border| RenderBorder::new(border)); if let Some(bg_color) = bg_color { let size = node.layout.size(); @@ -507,16 +511,12 @@ fn render_bg( scene.draw_rect(&rect); } - let background_image = node.properties.get("background-image").and_then(|prop| { - prop.compute_value(); - - match &prop.actual { - CssValue::String(url) => Some(url.as_str()), - _ => None, - } - }); + let background_image = node + .properties + .get("background-image") + .and_then(|prop| prop.as_string()); - let mut redraw = false; + let mut img_size = None; if let Some(url) = background_image { let size = node.layout.size_or().map(|x| x.u32()); @@ -525,14 +525,12 @@ fn render_bg( Ok(img) => img, Err(e) => { eprintln!("Error loading image: {:?}", e); - return (border_radius, false); + return (border_radius, None); } }; if size.is_none() { - node.layout.set_size_and_content(img.size()); - - redraw = true; + img_size = Some(img.size()); } let _ = render_image::(img, scene, *pos, node.layout.size(), border_radius, "fill") @@ -541,7 +539,7 @@ fn render_bg( }); } - (border_radius, redraw) + (border_radius, img_size) } enum Side { @@ -707,11 +705,13 @@ pub fn print_tree( } */ -fn get_border(node: &mut RenderTreeNode) -> Option { - let left = get_border_side::(node, Side::Left); - let right = get_border_side::(node, Side::Right); - let top = get_border_side::(node, Side::Top); - let bottom = get_border_side::(node, Side::Bottom); +fn get_border( + node: &RenderTreeNode, +) -> Option { + let left = get_border_side::(node, Side::Left); + let right = get_border_side::(node, Side::Right); + let top = get_border_side::(node, Side::Top); + let bottom = get_border_side::(node, Side::Bottom); if left.is_none() && right.is_none() && top.is_none() && bottom.is_none() { return None; @@ -738,53 +738,39 @@ fn get_border(node: &mut RenderTreeNode) -> Op Some(border) } -fn get_border_side( - node: &mut RenderTreeNode, +fn get_border_side( + node: &RenderTreeNode, side: Side, ) -> Option { let width = node .properties .get(&format!("border-{}-width", side.to_str())) - .map(|prop| { - prop.compute_value(); - prop.actual.unit_to_px() - })?; + .map(|prop| prop.unit_to_px())?; let color = node .properties .get(&format!("border-{}-color", side.to_str())) - .and_then(|prop| { - prop.compute_value(); - - match &prop.actual { - CssValue::Color(color) => Some(*color), - CssValue::String(color) => Some(RgbColor::from(color.as_str())), - _ => None, - } - })?; + .and_then(|prop| prop.parse_color())?; let style = node .properties .get(&format!("border-{}-style", side.to_str())) - .map(|prop| { - prop.compute_value(); - prop.actual.to_string() - }) - .unwrap_or("none".to_string()); + .and_then(|prop| prop.as_string()) + .unwrap_or("none"); let style = BorderStyle::from_str(&style); let brush = Brush::color(Color::rgba( - color.r as u8, - color.g as u8, - color.b as u8, - color.a as u8, + color.0 as u8, + color.1 as u8, + color.2 as u8, + color.3 as u8, )); Some(BorderSide::new(width as FP, style, brush)) } -impl TreeDrawer { +impl, C: CssSystem> TreeDrawer { fn debug_annotate(&mut self, e: NodeId) -> bool { let Some(node) = self.tree.get_node(e) else { return false; diff --git a/crates/gosub_renderer/src/render_tree.rs b/crates/gosub_renderer/src/render_tree.rs index 352742dbf..2b1cb5bc8 100644 --- a/crates/gosub_renderer/src/render_tree.rs +++ b/crates/gosub_renderer/src/render_tree.rs @@ -1,24 +1,25 @@ use std::fs; use anyhow::bail; -use url::Url; - -use gosub_html5::node::NodeId; -use gosub_html5::parser::document::{Document, DocumentBuilder}; -use gosub_html5::parser::Html5Parser; +use gosub_css3::matcher::styling::CssProperties; use gosub_net::http::fetcher::Fetcher; use gosub_net::http::ureq; use gosub_render_backend::geo::SizeU32; use gosub_render_backend::layout::Layouter; use gosub_render_backend::RenderBackend; use gosub_rendering::position::PositionTree; +use gosub_rendering::render_tree::{generate_render_tree, RenderTree}; use gosub_shared::byte_stream::{ByteStream, Encoding}; -use gosub_css3::render_tree::{generate_render_tree, RenderNodeData, RenderTree}; -use gosub_css3::styling::CssProperties; +use gosub_shared::document::DocumentHandle; +use gosub_shared::node::NodeId; +use gosub_shared::traits::css3::CssSystem; +use gosub_shared::traits::document::{Document, DocumentBuilder}; +use gosub_shared::traits::html5::Html5Parser; +use url::Url; -pub struct TreeDrawer { +pub struct TreeDrawer, C: CssSystem> { pub(crate) fetcher: Fetcher, - pub(crate) tree: RenderTree, + pub(crate) tree: RenderTree, pub(crate) layouter: L, pub(crate) size: Option, pub(crate) position: PositionTree, @@ -31,8 +32,8 @@ pub struct TreeDrawer { pub(crate) scene_transform: Option, } -impl TreeDrawer { - pub fn new(tree: RenderTree, layouter: L, url: Url, debug: bool) -> Self { +impl, C: CssSystem> TreeDrawer { + pub fn new(tree: RenderTree, layouter: L, url: Url, debug: bool) -> Self { Self { tree, layouter, @@ -50,19 +51,19 @@ impl TreeDrawer { } } -pub struct RenderTreeNode { - pub parent: Option, - pub children: Vec, - pub layout: i32, //TODO - pub name: String, - pub properties: CssProperties, - pub namespace: Option, - pub data: RenderNodeData, -} +// pub struct RenderTreeNode { +// pub parent: Option, +// pub children: Vec, +// pub layout: i32, //TODO +// pub name: String, +// pub properties: CssProperties, +// pub namespace: Option, +// pub data: RenderNodeData, +// } -pub(crate) fn load_html_rendertree( +pub(crate) fn load_html_rendertree, C: CssSystem>( url: Url, -) -> gosub_shared::types::Result> { +) -> gosub_shared::types::Result> { let html = if url.scheme() == "http" || url.scheme() == "https" { // Fetch the html from the url let response = ureq::get(url.as_ref()).call()?; @@ -83,19 +84,18 @@ pub(crate) fn load_html_rendertree( stream.read_from_str(&html, Some(Encoding::UTF8)); stream.close(); - let mut doc_handle = DocumentBuilder::new_document(Some(url)); - let parse_errors = - Html5Parser::parse_document(&mut stream, Document::clone(&doc_handle), None)?; + let mut doc_handle = >::Builder::new_document(Some(url)); + let parse_errors = P::parse(&mut stream, DocumentHandle::clone(&doc_handle), None)?; for error in parse_errors { eprintln!("Parse error: {:?}", error); } - let mut doc = doc_handle.get_mut(); - doc.stylesheets - .push(gosub_css3::load_default_useragent_stylesheet()?); + _ = doc_handle.get_mut(); + + // doc.add_stylesheet(C::load_default_useragent_stylesheet()?); - drop(doc); + // drop(doc); - generate_render_tree(Document::clone(&doc_handle)) + generate_render_tree(DocumentHandle::clone(&doc_handle)) } diff --git a/crates/gosub_taffy/src/compute/inline.rs b/crates/gosub_taffy/src/compute/inline.rs index f2d6f27ec..8e152a5d4 100644 --- a/crates/gosub_taffy/src/compute/inline.rs +++ b/crates/gosub_taffy/src/compute/inline.rs @@ -10,9 +10,8 @@ use taffy::{ }; use gosub_render_backend::geo; -use gosub_render_backend::layout::{ - CssProperty, CssValue, Decoration, DecorationStyle, HasTextLayout, LayoutTree, Node, -}; +use gosub_render_backend::layout::{Decoration, DecorationStyle, HasTextLayout, LayoutTree, Node}; +use gosub_shared::traits::css3::{CssProperty, CssValue}; use gosub_typeface::font::Glyph; use crate::text::{Font, TextLayout}; diff --git a/crates/gosub_taffy/src/style/parse.rs b/crates/gosub_taffy/src/style/parse.rs index 6ceb4615d..447b8dd8b 100644 --- a/crates/gosub_taffy/src/style/parse.rs +++ b/crates/gosub_taffy/src/style/parse.rs @@ -5,15 +5,14 @@ use taffy::{ }; use gosub_render_backend::geo::Size; -use gosub_render_backend::layout::{CssProperty, Node}; +use gosub_render_backend::layout::Node; +use gosub_shared::traits::css3::CssProperty; pub fn parse_len(node: &mut impl Node, name: &str) -> LengthPercentage { let Some(property) = node.get_property(name) else { return LengthPercentage::Length(0.0); }; - property.compute_value(); - if let Some(percent) = property.as_percentage() { return LengthPercentage::Percent(percent / 100.0); } @@ -26,8 +25,6 @@ pub fn parse_len_auto(node: &mut impl Node, name: &str) -> LengthPercentageAuto return LengthPercentageAuto::Length(0.0); }; - property.compute_value(); - if let Some(str) = property.as_string() { if str == "auto" { return LengthPercentageAuto::Auto; @@ -46,8 +43,6 @@ pub fn parse_dimension(node: &mut impl Node, name: &str) -> Dimension { return Dimension::Auto; }; - property.compute_value(); - if let Some(str) = property.as_string() { if str == "auto" { return Dimension::Auto; @@ -73,8 +68,6 @@ pub fn parse_text_dim(size: Size, name: &str) -> Dimension { pub fn parse_align_i(node: &mut impl Node, name: &str) -> Option { let display = node.get_property(name)?; - display.compute_value(); - let value = display.as_string()?; match value { @@ -92,8 +85,6 @@ pub fn parse_align_i(node: &mut impl Node, name: &str) -> Option { pub fn parse_align_c(node: &mut impl Node, name: &str) -> Option { let display = node.get_property(name)?; - display.compute_value(); - let value = display.as_string()?; match value { @@ -117,8 +108,6 @@ pub fn parse_tracking_sizing_function( return Vec::new(); }; - display.compute_value(); - let Some(_value) = display.as_string() else { return Vec::new(); }; @@ -139,8 +128,6 @@ pub fn parse_grid_auto(node: &mut impl Node, name: &str) -> Vec GridPlacement { return GridPlacement::Auto; }; - display.compute_value(); - if let Some(value) = &display.as_string() { return if value.starts_with("span") { let value = value.trim_start_matches("span").trim(); @@ -172,7 +157,7 @@ pub fn parse_grid_placement(node: &mut impl Node, name: &str) -> GridPlacement { } if let Some(value) = display.as_number() { - return GridPlacement::from_line_index((value) as i16); + return GridPlacement::from_line_index(value as i16); } GridPlacement::Auto } diff --git a/crates/gosub_taffy/src/style/parse_properties.rs b/crates/gosub_taffy/src/style/parse_properties.rs index 37a9aadc5..73da68362 100644 --- a/crates/gosub_taffy/src/style/parse_properties.rs +++ b/crates/gosub_taffy/src/style/parse_properties.rs @@ -2,20 +2,18 @@ use regex::Regex; use taffy::prelude::*; use taffy::{Overflow, Point}; -use gosub_render_backend::layout::{CssProperty, Node}; - use crate::style::parse::{ parse_align_c, parse_align_i, parse_dimension, parse_grid_auto, parse_grid_placement, parse_len, parse_len_auto, parse_text_dim, parse_tracking_sizing_function, }; +use gosub_render_backend::layout::Node; +use gosub_shared::traits::css3::CssProperty; pub fn parse_display(node: &mut impl Node) -> (Display, crate::Display) { let Some(display) = node.get_property("display") else { return (Display::Block, crate::Display::Taffy); }; - display.compute_value(); - let Some(value) = display.as_string() else { return (Display::Block, crate::Display::Taffy); }; @@ -48,8 +46,6 @@ pub fn parse_overflow(node: &mut impl Node) -> Point { }; if let Some(display) = node.get_property("overflow-x") { - display.compute_value(); - if let Some(value) = display.as_string() { let x = parse(value); overflow.x = x; @@ -57,8 +53,6 @@ pub fn parse_overflow(node: &mut impl Node) -> Point { }; if let Some(display) = node.get_property("overflow-y") { - display.compute_value(); - if let Some(value) = display.as_string() { let y = parse(value); overflow.y = y; @@ -73,8 +67,6 @@ pub fn parse_position(node: &mut impl Node) -> Position { return Position::Relative; }; - position.compute_value(); - let Some(value) = position.as_string() else { return Position::Relative; }; @@ -140,8 +132,6 @@ pub fn parse_max_size(node: &mut impl Node) -> Size { pub fn parse_aspect_ratio(node: &mut impl Node) -> Option { let aspect_ratio = node.get_property("aspect-ratio")?; - aspect_ratio.compute_value(); - if let Some(value) = aspect_ratio.as_number() { return Some(value); } @@ -204,8 +194,6 @@ pub fn parse_border(node: &mut impl Node) -> Rect { pub fn parse_align_items(node: &mut impl Node) -> Option { let display = node.get_property("align-items")?; - display.compute_value(); - let value = display.as_string()?; match value { @@ -252,8 +240,6 @@ pub fn parse_flex_direction(node: &mut impl Node) -> FlexDirection { return FlexDirection::Row; }; - property.compute_value(); - if let Some(value) = property.as_string() { match value { "row" => FlexDirection::Row, @@ -272,8 +258,6 @@ pub fn parse_flex_wrap(node: &mut impl Node) -> FlexWrap { return FlexWrap::NoWrap; }; - property.compute_value(); - if let Some(value) = property.as_string() { match value { "nowrap" => FlexWrap::NoWrap, @@ -295,8 +279,6 @@ pub fn parse_flex_grow(node: &mut impl Node) -> f32 { return 0.0; }; - property.compute_value(); - property.as_number().unwrap_or(0.0) } @@ -305,8 +287,6 @@ pub fn parse_flex_shrink(node: &mut impl Node) -> f32 { return 1.0; }; - property.compute_value(); - property.as_number().unwrap_or(1.0) } @@ -331,8 +311,6 @@ pub fn parse_grid_auto_flow(node: &mut impl Node) -> GridAutoFlow { return GridAutoFlow::Row; }; - property.compute_value(); - if let Some(value) = property.as_string() { match value { "row" => GridAutoFlow::Row, diff --git a/crates/gosub_useragent/src/application.rs b/crates/gosub_useragent/src/application.rs index aca7241de..3b669139f 100644 --- a/crates/gosub_useragent/src/application.rs +++ b/crates/gosub_useragent/src/application.rs @@ -10,28 +10,43 @@ use winit::window::WindowId; use gosub_render_backend::layout::{LayoutTree, Layouter}; use gosub_render_backend::{NodeDesc, RenderBackend}; use gosub_renderer::draw::SceneDrawer; +use gosub_shared::traits::css3::CssSystem; +use gosub_shared::traits::document::Document; +use gosub_shared::traits::html5::Html5Parser; use gosub_shared::types::Result; use crate::window::Window; pub struct Application< 'a, - D: SceneDrawer, + D: SceneDrawer, B: RenderBackend, L: Layouter, LT: LayoutTree, + Doc: Document, + C: CssSystem, + P: Html5Parser, > { open_windows: Vec>, // Vec of Windows, each with a Vec of URLs, representing tabs - windows: HashMap>, + windows: HashMap>, backend: B, layouter: L, proxy: Option>, event_loop: Option>, debug: bool, + _marker: std::marker::PhantomData<&'a P>, } -impl<'a, D: SceneDrawer, B: RenderBackend, L: Layouter, LT: LayoutTree> - ApplicationHandler for Application<'a, D, B, L, LT> +impl< + 'a, + D: SceneDrawer, + B: RenderBackend, + L: Layouter, + LT: LayoutTree, + Doc: Document, + C: CssSystem, + P: Html5Parser, + > ApplicationHandler for Application<'a, D, B, L, LT, Doc, C, P> { fn resumed(&mut self, _event_loop: &ActiveEventLoop) { for window in self.windows.values_mut() { @@ -44,7 +59,7 @@ impl<'a, D: SceneDrawer, B: RenderBackend, L: Layouter, LT: LayoutTree fn user_event(&mut self, event_loop: &ActiveEventLoop, event: CustomEvent) { match event { CustomEvent::OpenWindow(url) => { - let mut window = match Window::new( + let mut window = match Window::new::

( event_loop, &mut self.backend, self.layouter.clone(), @@ -69,7 +84,7 @@ impl<'a, D: SceneDrawer, B: RenderBackend, L: Layouter, LT: LayoutTree } CustomEvent::OpenInitial => { for urls in self.open_windows.drain(..) { - let mut window = match Window::new( + let mut window = match Window::new::

( event_loop, &mut self.backend, self.layouter.clone(), @@ -132,8 +147,16 @@ impl<'a, D: SceneDrawer, B: RenderBackend, L: Layouter, LT: LayoutTree } } -impl<'a, D: SceneDrawer, B: RenderBackend, L: Layouter, LT: LayoutTree> - Application<'a, D, B, L, LT> +impl< + 'a, + D: SceneDrawer, + B: RenderBackend, + L: Layouter, + LT: LayoutTree, + Doc: Document, + C: CssSystem, + P: Html5Parser, + > Application<'a, D, B, L, LT, Doc, C, P> { pub fn new(backend: B, layouter: L, debug: bool) -> Self { Self { @@ -144,6 +167,7 @@ impl<'a, D: SceneDrawer, B: RenderBackend, L: Layouter, LT: LayoutTree event_loop: None, open_windows: Vec::new(), debug, + _marker: std::marker::PhantomData, } } @@ -155,7 +179,7 @@ impl<'a, D: SceneDrawer, B: RenderBackend, L: Layouter, LT: LayoutTree self.open_windows.append(&mut windows); } - pub fn add_window(&mut self, window: Window<'a, D, B, L, LT>) { + pub fn add_window(&mut self, window: Window<'a, D, B, L, LT, Doc, C>) { self.windows.insert(window.window.id(), window); } diff --git a/crates/gosub_useragent/src/event_loop.rs b/crates/gosub_useragent/src/event_loop.rs index 7a53b3899..58dcadedd 100644 --- a/crates/gosub_useragent/src/event_loop.rs +++ b/crates/gosub_useragent/src/event_loop.rs @@ -5,12 +5,21 @@ use winit::keyboard::{KeyCode, PhysicalKey}; use gosub_render_backend::layout::{LayoutTree, Layouter}; use gosub_render_backend::{Point, RenderBackend, SizeU32, FP}; use gosub_renderer::draw::SceneDrawer; +use gosub_shared::traits::css3::CssSystem; +use gosub_shared::traits::document::Document; use gosub_shared::types::Result; use crate::window::{Window, WindowState}; -impl<'a, D: SceneDrawer, B: RenderBackend, L: Layouter, LT: LayoutTree> - Window<'a, D, B, L, LT> +impl< + 'a, + D: SceneDrawer, + B: RenderBackend, + L: Layouter, + LT: LayoutTree, + Doc: Document, + C: CssSystem, + > Window<'a, D, B, L, LT, Doc, C> { pub fn event( &mut self, diff --git a/crates/gosub_useragent/src/tabs.rs b/crates/gosub_useragent/src/tabs.rs index 4a100b205..f301674f4 100644 --- a/crates/gosub_useragent/src/tabs.rs +++ b/crates/gosub_useragent/src/tabs.rs @@ -5,16 +5,34 @@ use url::Url; use gosub_render_backend::layout::{LayoutTree, Layouter}; use gosub_render_backend::{NodeDesc, RenderBackend}; use gosub_renderer::draw::SceneDrawer; +use gosub_shared::traits::css3::CssSystem; +use gosub_shared::traits::document::Document; +use gosub_shared::traits::html5::Html5Parser; use gosub_shared::types::Result; -pub struct Tabs, B: RenderBackend, L: Layouter, LT: LayoutTree> { - pub tabs: SlotMap>, +pub struct Tabs< + D: SceneDrawer, + B: RenderBackend, + L: Layouter, + LT: LayoutTree, + Doc: Document, + C: CssSystem, +> { + pub tabs: SlotMap>, pub active: TabID, _marker: std::marker::PhantomData<(B, L, LT)>, } -impl, L: Layouter, LT: LayoutTree, B: RenderBackend> Tabs { - pub fn new(initial: Tab) -> Self { +impl< + D: SceneDrawer, + L: Layouter, + LT: LayoutTree, + B: RenderBackend, + Doc: Document, + C: CssSystem, + > Tabs +{ + pub fn new(initial: Tab) -> Self { let mut tabs = SlotMap::new(); let active = TabID(tabs.insert(initial)); @@ -25,7 +43,7 @@ impl, L: Layouter, LT: LayoutTree, B: RenderBackend> } } - pub fn add_tab(&mut self, tab: Tab) -> TabID { + pub fn add_tab(&mut self, tab: Tab) -> TabID { TabID(self.tabs.insert(tab)) } @@ -37,12 +55,16 @@ impl, L: Layouter, LT: LayoutTree, B: RenderBackend> self.active = id; } - pub fn get_current_tab(&mut self) -> Option<&mut Tab> { + pub fn get_current_tab(&mut self) -> Option<&mut Tab> { self.tabs.get_mut(self.active.0) } - pub(crate) fn from_url(url: Url, layouter: L, debug: bool) -> Result { - let tab = Tab::from_url(url, layouter, debug)?; + pub(crate) fn from_url>( + url: Url, + layouter: L, + debug: bool, + ) -> Result { + let tab = Tab::from_url::

(url, layouter, debug)?; Ok(Self::new(tab)) } @@ -66,14 +88,29 @@ impl, L: Layouter, LT: LayoutTree, B: RenderBackend> } } -pub struct Tab, B: RenderBackend, L: Layouter, LT: LayoutTree> { +pub struct Tab< + D: SceneDrawer, + B: RenderBackend, + L: Layouter, + LT: LayoutTree, + Doc: Document, + C: CssSystem, +> { pub title: String, pub url: Url, pub data: D, - _marker: std::marker::PhantomData<(B, L, LT)>, + _marker: std::marker::PhantomData<(B, L, LT, Doc, C)>, } -impl, B: RenderBackend, L: Layouter, LT: LayoutTree> Tab { +impl< + D: SceneDrawer, + B: RenderBackend, + L: Layouter, + LT: LayoutTree, + Doc: Document, + C: CssSystem, + > Tab +{ pub fn new(title: String, url: Url, data: D) -> Self { Self { title, @@ -83,8 +120,12 @@ impl, B: RenderBackend, L: Layouter, LT: LayoutTree> } } - pub fn from_url(url: Url, layouter: L, debug: bool) -> Result { - let data = D::from_url(url.clone(), layouter, debug)?; + pub fn from_url>( + url: Url, + layouter: L, + debug: bool, + ) -> Result { + let data = D::from_url::

(url.clone(), layouter, debug)?; Ok(Self { title: url.as_str().to_string(), diff --git a/crates/gosub_useragent/src/window.rs b/crates/gosub_useragent/src/window.rs index 8afc3f5b5..5862ad41c 100644 --- a/crates/gosub_useragent/src/window.rs +++ b/crates/gosub_useragent/src/window.rs @@ -14,6 +14,9 @@ use gosub_render_backend::geo::SizeU32; use gosub_render_backend::layout::{LayoutTree, Layouter}; use gosub_render_backend::{NodeDesc, RenderBackend}; use gosub_renderer::draw::SceneDrawer; +use gosub_shared::traits::css3::CssSystem; +use gosub_shared::traits::document::Document; +use gosub_shared::traits::html5::Html5Parser; use gosub_shared::types::Result; use crate::tabs::Tabs; @@ -45,17 +48,32 @@ static ICON: LazyCell = LazyCell::new(|| { }); } -pub struct Window<'a, D: SceneDrawer, B: RenderBackend, L: Layouter, LT: LayoutTree> { +pub struct Window< + 'a, + D: SceneDrawer, + B: RenderBackend, + L: Layouter, + LT: LayoutTree, + Doc: Document, + C: CssSystem, +> { pub(crate) state: WindowState<'a, B>, pub(crate) window: Arc, pub(crate) renderer_data: B::WindowData<'a>, - pub(crate) tabs: Tabs, + pub(crate) tabs: Tabs, } -impl<'a, D: SceneDrawer, B: RenderBackend, L: Layouter, LT: LayoutTree> - Window<'a, D, B, L, LT> +impl< + 'a, + D: SceneDrawer, + B: RenderBackend, + L: Layouter, + LT: LayoutTree, + Doc: Document, + C: CssSystem, + > Window<'a, D, B, L, LT, Doc, C> { - pub fn new( + pub fn new>( event_loop: &ActiveEventLoop, backend: &mut B, layouter: L, @@ -70,7 +88,7 @@ impl<'a, D: SceneDrawer, B: RenderBackend, L: Layouter, LT: LayoutTree state: WindowState::Suspended, window, renderer_data, - tabs: Tabs::from_url(default_url, layouter, debug)?, + tabs: Tabs::from_url::

(default_url, layouter, debug)?, }) } diff --git a/src/bin/renderer.rs b/src/bin/renderer.rs index e530945a6..df23de4cb 100644 --- a/src/bin/renderer.rs +++ b/src/bin/renderer.rs @@ -2,19 +2,28 @@ use std::sync::mpsc; use std::{io, thread}; use clap::ArgAction; -use url::Url; - +use gosub_css3::system::Css3System; +use gosub_html5::document::document::DocumentImpl; +use gosub_html5::parser::Html5Parser; use gosub_renderer::render_tree::TreeDrawer; +use gosub_rendering::render_tree::RenderTree; use gosub_shared::types::Result; -use gosub_css3::render_tree::RenderTree; use gosub_taffy::TaffyLayouter; use gosub_useragent::application::{Application, CustomEvent}; use gosub_vello::VelloBackend; +use url::Url; type Backend = VelloBackend; type Layouter = TaffyLayouter; -type Drawer = TreeDrawer; -type Tree = RenderTree; + +type CssSystem = Css3System; + +type Document = DocumentImpl; + +type HtmlParser<'a> = Html5Parser<'a, Document, CssSystem>; + +type Drawer = TreeDrawer; +type Tree = RenderTree; fn main() -> Result<()> { let matches = clap::Command::new("Gosub Renderer") @@ -39,8 +48,15 @@ fn main() -> Result<()> { // let mut rt = load_html_rendertree(&url)?; // - let mut application: Application = - Application::new(VelloBackend::new(), TaffyLayouter, debug); + let mut application: Application< + Drawer, + Backend, + Layouter, + Tree, + Document, + CssSystem, + HtmlParser, + > = Application::new(VelloBackend::new(), TaffyLayouter, debug); application.initial_tab(Url::parse(&url)?); diff --git a/src/engine.rs b/src/engine.rs index 917c82379..cccc51d6b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,9 +1,13 @@ use gosub_shared::byte_stream::{ByteStream, Encoding}; +use gosub_shared::document::DocumentHandle; +use gosub_shared::traits::css3::CssSystem; +use gosub_shared::traits::document::{Document, DocumentBuilder}; +use gosub_shared::traits::html5::Html5Parser as Html5ParserT; +use std::fmt::Display; #[cfg(not(target_arch = "wasm32"))] use { cookie::CookieJar, core::fmt::Debug, - gosub_html5::parser::document::{Document, DocumentBuilder, DocumentHandle}, gosub_html5::parser::Html5Parser, gosub_net::{ dns::{Dns, ResolveType}, @@ -23,13 +27,13 @@ const MAX_BYTES: u64 = 10_000_000; /// Response that is returned from the fetch function #[cfg(not(target_arch = "wasm32"))] -pub struct FetchResponse { +pub struct FetchResponse, C: CssSystem> { /// Request that has been send pub request: Request, /// Response that has been received pub response: Response, /// Document tree that is made from the response - pub document: DocumentHandle, + pub document: DocumentHandle, /// Parse errors that occurred while parsing the document tree pub parse_errors: Vec, /// Rendertree that is generated from the document tree and css tree @@ -37,14 +41,14 @@ pub struct FetchResponse { } #[cfg(not(target_arch = "wasm32"))] -impl Debug for FetchResponse { +impl + Debug, C: CssSystem> Debug for FetchResponse { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { writeln!(f, "Request:")?; writeln!(f, "{}", self.request)?; writeln!(f, "Response:")?; writeln!(f, "{}", self.response)?; writeln!(f, "Document tree:")?; - writeln!(f, "{}", self.document)?; + writeln!(f, "{:?}", self.document)?; writeln!(f, "Parse errors:")?; for error in &self.parse_errors { writeln!( @@ -62,12 +66,12 @@ impl Debug for FetchResponse { #[cfg(not(target_arch = "wasm32"))] #[allow(dead_code)] -fn fetch_url( +fn fetch_url, C: CssSystem>( method: &str, url: &str, headers: Headers, cookies: CookieJar, -) -> Result { +) -> Result> { let mut http_req = Request::new(method, url, "HTTP/1.1"); http_req.headers = headers.clone(); http_req.cookies = cookies.clone(); @@ -77,7 +81,7 @@ fn fetch_url( let mut fetch_response = FetchResponse { request: http_req, response: Response::new(), - document: DocumentBuilder::new_document(Some(parts.clone())), + document: >::Builder::new_document(Some(parts.clone())), parse_errors: vec![], render_tree: String::new(), }; @@ -140,10 +144,13 @@ fn fetch_url( let mut stream = ByteStream::new(Encoding::UTF8, None); let _ = stream.read_from_bytes(&fetch_response.response.body); - fetch_response.document = DocumentBuilder::new_document(Some(parts)); + fetch_response.document = >::Builder::new_document(Some(parts)); - match Html5Parser::parse_document(&mut stream, Document::clone(&fetch_response.document), None) - { + match P::parse( + &mut stream, + DocumentHandle::clone(&fetch_response.document), + None, + ) { Ok(parse_errors) => { fetch_response.parse_errors = parse_errors; } @@ -160,6 +167,8 @@ fn fetch_url( #[cfg(test)] mod tests { use super::*; + use gosub_css3::system::Css3System; + use gosub_html5::document::document::DocumentImpl; #[cfg(not(target_arch = "wasm32"))] #[test] @@ -169,7 +178,9 @@ mod tests { headers.set_str("User-Agent", USER_AGENT); let cookies = CookieJar::new(); - let resp = fetch_url("GET", url, headers, cookies); + let resp = fetch_url::, Css3System>, Css3System>( + "GET", url, headers, cookies, + ); assert!(resp.is_ok()); } }