web engine - experimental web browser
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}