use std::cell::RefCell; use std::collections::HashMap; use we_browser::css_loader::collect_stylesheets; use we_browser::img_loader::{collect_images, ImageStore}; use we_browser::loader::{Resource, ResourceLoader, ABOUT_BLANK_HTML}; use we_css::parser::Stylesheet; use we_dom::{Document, NodeId}; use we_html::parse_html; use we_image::pixel::Image; use we_layout::layout; use we_platform::appkit; use we_platform::cg::BitmapContext; use we_render::Renderer; use we_style::computed::resolve_styles; use we_text::font::{self, Font}; use we_url::Url; // --------------------------------------------------------------------------- // Page state: holds everything needed to re-render without re-fetching // --------------------------------------------------------------------------- /// Pre-fetched page data. Stored so that window resizes only re-style, /// re-layout, and re-render — no network requests. struct PageState { doc: Document, stylesheet: Stylesheet, images: ImageStore, } /// Browser state kept in thread-local storage so the resize handler can /// access it. All AppKit callbacks run on the main thread. struct BrowserState { page: PageState, font: Font, bitmap: Box, view: appkit::BitmapView, } thread_local! { static STATE: RefCell> = const { RefCell::new(None) }; } // --------------------------------------------------------------------------- // Rendering pipeline // --------------------------------------------------------------------------- /// Build a `HashMap` of display dimensions for layout. fn image_sizes(store: &ImageStore) -> HashMap { let mut sizes = HashMap::new(); for (node_id, res) in store { if res.display_width > 0.0 || res.display_height > 0.0 { sizes.insert(*node_id, (res.display_width, res.display_height)); } } sizes } /// Build a `HashMap` reference map for the renderer. fn image_refs(store: &ImageStore) -> HashMap { let mut refs = HashMap::new(); for (node_id, res) in store { if let Some(ref img) = res.image { refs.insert(*node_id, img); } } refs } /// Re-run the pipeline: resolve styles → layout → render → copy to bitmap. /// /// Uses pre-fetched `PageState` so no network I/O happens here. fn render_page(page: &PageState, font: &Font, bitmap: &mut BitmapContext) { let width = bitmap.width() as u32; let height = bitmap.height() as u32; if width == 0 || height == 0 { return; } // Resolve computed styles from DOM + stylesheet. let styled = match resolve_styles(&page.doc, std::slice::from_ref(&page.stylesheet)) { Some(s) => s, None => return, }; // Build image maps for layout (sizes) and render (pixel data). let sizes = image_sizes(&page.images); let refs = image_refs(&page.images); // Layout. let tree = layout( &styled, &page.doc, width as f32, height as f32, font, &sizes, ); // Render. let mut renderer = Renderer::new(width, height); renderer.paint(&tree, font, &refs); // Copy rendered pixels into the bitmap context's buffer. let src = renderer.pixels(); let dst = bitmap.pixels_mut(); let len = src.len().min(dst.len()); dst[..len].copy_from_slice(&src[..len]); } /// Called by the platform crate when the window is resized. fn handle_resize(width: f64, height: f64) { STATE.with(|state| { let mut state = state.borrow_mut(); let state = match state.as_mut() { Some(s) => s, None => return, }; let w = width as usize; let h = height as usize; if w == 0 || h == 0 { return; } // Create a new bitmap context with the new dimensions. let mut new_bitmap = match BitmapContext::new(w, h) { Some(b) => Box::new(b), None => return, }; render_page(&state.page, &state.font, &mut new_bitmap); // Swap in the new bitmap and update the view's pointer. state.bitmap = new_bitmap; state.view.update_bitmap(&state.bitmap); }); } // --------------------------------------------------------------------------- // Page loading // --------------------------------------------------------------------------- /// Result of loading a page: HTML text + base URL for resolving subresources. struct LoadedHtml { text: String, base_url: Url, } /// Load content from a command-line argument. /// /// Tries the argument as a URL first (http://, https://, about:, data:), /// then falls back to reading it as a local file. /// On failure, returns an error page instead of exiting. fn load_from_arg(arg: &str) -> LoadedHtml { // Try as URL if it has a recognized scheme. if arg.starts_with("http://") || arg.starts_with("https://") || arg.starts_with("about:") || arg.starts_with("data:") { let mut loader = ResourceLoader::new(); match loader.fetch_url(arg, None) { Ok(Resource::Html { text, base_url, .. }) => { return LoadedHtml { text, base_url }; } Ok(_) => { return error_page(&format!("URL did not return HTML: {arg}")); } Err(e) => { return error_page(&format!("Failed to load {arg}: {e}")); } } } // Fall back to file path. match std::fs::read_to_string(arg) { Ok(content) => { // Use a file:// base URL for resolving relative paths. let abs_path = std::fs::canonicalize(arg).unwrap_or_else(|_| std::path::PathBuf::from(arg)); let base_str = format!("file://{}", abs_path.display()); let base_url = Url::parse(&base_str).unwrap_or_else(|_| { Url::parse("about:blank").expect("about:blank is always valid") }); LoadedHtml { text: content, base_url, } } Err(e) => error_page(&format!("Error reading {arg}: {e}")), } } /// Generate an HTML error page for display. fn error_page(message: &str) -> LoadedHtml { eprintln!("{message}"); let escaped = message .replace('&', "&") .replace('<', "<") .replace('>', ">"); let html = format!( "\ Error\ \

Error

{escaped}

" ); let base_url = Url::parse("about:blank").expect("about:blank is always valid"); LoadedHtml { text: html, base_url, } } /// Load a page: fetch HTML, parse DOM, collect CSS and images. fn load_page(loaded: LoadedHtml) -> PageState { let doc = parse_html(&loaded.text); // Fetch external stylesheets and merge with inline