web engine - experimental web browser
at x25519 162 lines 4.9 kB view raw
1use std::cell::RefCell; 2 3use we_html::parse_html; 4use we_layout::layout; 5use we_platform::appkit; 6use we_platform::cg::BitmapContext; 7use we_render::Renderer; 8use we_style::computed::{extract_stylesheets, resolve_styles}; 9use we_text::font::{self, Font}; 10 11/// Default HTML page shown when no file argument is provided. 12const DEFAULT_HTML: &str = r#"<!DOCTYPE html> 13<html> 14<head> 15<title>we browser</title> 16<style> 17h1 { color: blue; } 18h2 { color: green; } 19p { color: #333333; } 20</style> 21</head> 22<body> 23<h1>Hello from we!</h1> 24<p>This is a from-scratch web browser engine written in pure Rust.</p> 25<p>Zero external crate dependencies. Every subsystem is implemented in Rust.</p> 26<h2>Features</h2> 27<p>HTML5 tokenizer, DOM tree, block layout, CSS cascade, and software rendering.</p> 28</body> 29</html>"#; 30 31/// Browser state kept in thread-local storage so the resize handler can 32/// access it. All AppKit callbacks run on the main thread. 33struct BrowserState { 34 html: String, 35 font: Font, 36 bitmap: Box<BitmapContext>, 37 view: appkit::BitmapView, 38} 39 40thread_local! { 41 static STATE: RefCell<Option<BrowserState>> = const { RefCell::new(None) }; 42} 43 44/// Re-run the full pipeline: parse → extract CSS → resolve styles → layout → render → copy to bitmap. 45fn render_page(html: &str, font: &Font, bitmap: &mut BitmapContext) { 46 let width = bitmap.width() as u32; 47 let height = bitmap.height() as u32; 48 if width == 0 || height == 0 { 49 return; 50 } 51 52 // Parse HTML into DOM. 53 let doc = parse_html(html); 54 55 // Extract CSS from <style> elements and resolve computed styles. 56 let stylesheets = extract_stylesheets(&doc); 57 let styled = match resolve_styles(&doc, &stylesheets) { 58 Some(s) => s, 59 None => return, 60 }; 61 62 // Layout using styled tree (CSS-driven). 63 let tree = layout(&styled, &doc, width as f32, height as f32, font); 64 65 let mut renderer = Renderer::new(width, height); 66 renderer.paint(&tree, font); 67 68 // Copy rendered pixels into the bitmap context's buffer. 69 let src = renderer.pixels(); 70 let dst = bitmap.pixels_mut(); 71 let len = src.len().min(dst.len()); 72 dst[..len].copy_from_slice(&src[..len]); 73} 74 75/// Called by the platform crate when the window is resized. 76fn handle_resize(width: f64, height: f64) { 77 STATE.with(|state| { 78 let mut state = state.borrow_mut(); 79 let state = match state.as_mut() { 80 Some(s) => s, 81 None => return, 82 }; 83 84 let w = width as usize; 85 let h = height as usize; 86 if w == 0 || h == 0 { 87 return; 88 } 89 90 // Create a new bitmap context with the new dimensions. 91 let mut new_bitmap = match BitmapContext::new(w, h) { 92 Some(b) => Box::new(b), 93 None => return, 94 }; 95 96 render_page(&state.html, &state.font, &mut new_bitmap); 97 98 // Swap in the new bitmap and update the view's pointer. 99 state.bitmap = new_bitmap; 100 state.view.update_bitmap(&state.bitmap); 101 }); 102} 103 104fn main() { 105 // Load HTML from file argument or use default page. 106 let html = match std::env::args().nth(1) { 107 Some(path) => match std::fs::read_to_string(&path) { 108 Ok(content) => content, 109 Err(e) => { 110 eprintln!("Error reading {}: {}", path, e); 111 std::process::exit(1); 112 } 113 }, 114 None => DEFAULT_HTML.to_string(), 115 }; 116 117 // Load a system font for text rendering. 118 let font = match font::load_system_font() { 119 Ok(f) => f, 120 Err(e) => { 121 eprintln!("Error loading system font: {:?}", e); 122 std::process::exit(1); 123 } 124 }; 125 126 let _pool = appkit::AutoreleasePool::new(); 127 128 let app = appkit::App::shared(); 129 app.set_activation_policy(appkit::NS_APPLICATION_ACTIVATION_POLICY_REGULAR); 130 appkit::install_app_delegate(&app); 131 132 let window = appkit::create_standard_window("we"); 133 appkit::install_window_delegate(&window); 134 window.set_accepts_mouse_moved_events(true); 135 136 // Initial render at the default window size (800x600). 137 let mut bitmap = 138 Box::new(BitmapContext::new(800, 600).expect("failed to create bitmap context")); 139 render_page(&html, &font, &mut bitmap); 140 141 // Create the view backed by the rendered bitmap. 142 let frame = appkit::NSRect::new(0.0, 0.0, 800.0, 600.0); 143 let view = appkit::BitmapView::new(frame, &bitmap); 144 window.set_content_view(&view.id()); 145 146 // Store state for the resize handler. 147 STATE.with(|state| { 148 *state.borrow_mut() = Some(BrowserState { 149 html, 150 font, 151 bitmap, 152 view, 153 }); 154 }); 155 156 // Register resize handler so re-layout happens on window resize. 157 appkit::set_resize_handler(handle_resize); 158 159 window.make_key_and_order_front(); 160 app.activate(); 161 app.run(); 162}