1#![allow(dead_code)]
8
9use alloc::{
10 string::{String, ToString},
11 vec::Vec,
12};
13
14use super::{
15 css_parser::{CssParser, Stylesheet},
16 dom::Document,
17 layout::{build_layout_tree, LayoutBox},
18 paint::{build_display_list, DisplayList, Painter},
19 style::StyleResolver,
20 tree_builder::TreeBuilder,
21};
22
23pub struct BrowserWindow {
25 pub address_bar: String,
27 pub current_url: String,
29 pub document: Option<Document>,
31 pub stylesheet: Option<Stylesheet>,
33 pub layout_root: Option<LayoutBox>,
35 pub display_list: Option<DisplayList>,
37 pub scroll_y: i32,
39 pub viewport_width: i32,
41 pub viewport_height: i32,
43 pub back_history: Vec<String>,
45 pub forward_history: Vec<String>,
47 resolver: StyleResolver,
49 painter: Option<Painter>,
51}
52
53impl Default for BrowserWindow {
54 fn default() -> Self {
55 Self::new(800, 600)
56 }
57}
58
59impl BrowserWindow {
60 pub fn new(width: i32, height: i32) -> Self {
62 Self {
63 address_bar: String::new(),
64 current_url: String::new(),
65 document: None,
66 stylesheet: None,
67 layout_root: None,
68 display_list: None,
69 scroll_y: 0,
70 viewport_width: width,
71 viewport_height: height,
72 back_history: Vec::new(),
73 forward_history: Vec::new(),
74 resolver: StyleResolver::new(),
75 painter: None,
76 }
77 }
78
79 pub fn navigate(&mut self, url: &str) {
81 if !self.current_url.is_empty() {
83 self.back_history.push(self.current_url.clone());
84 }
85 self.forward_history.clear();
86
87 self.current_url = url.to_string();
88 self.address_bar = url.to_string();
89 self.scroll_y = 0;
90
91 let html = self.fetch_url(url);
93 self.load_html(&html);
94 }
95
96 pub fn load_html(&mut self, html: &str) {
98 let doc = TreeBuilder::build(html);
100
101 let css = Self::extract_styles(html);
103
104 let stylesheet = CssParser::parse(&css);
106
107 self.resolver = StyleResolver::new();
109 self.resolver.add_stylesheet(stylesheet.clone());
110
111 let layout = build_layout_tree(
113 &doc,
114 &self.resolver,
115 self.viewport_width,
116 self.viewport_height,
117 );
118
119 let display_list = build_display_list(&layout, self.scroll_y);
121
122 self.document = Some(doc);
123 self.stylesheet = Some(stylesheet);
124 self.layout_root = Some(layout);
125 self.display_list = Some(display_list);
126 }
127
128 pub fn load_html_with_css(&mut self, html: &str, css: &str) {
130 let doc = TreeBuilder::build(html);
131 let stylesheet = CssParser::parse(css);
132
133 self.resolver = StyleResolver::new();
134 self.resolver.add_stylesheet(stylesheet.clone());
135
136 let layout = build_layout_tree(
137 &doc,
138 &self.resolver,
139 self.viewport_width,
140 self.viewport_height,
141 );
142
143 let display_list = build_display_list(&layout, self.scroll_y);
144
145 self.document = Some(doc);
146 self.stylesheet = Some(stylesheet);
147 self.layout_root = Some(layout);
148 self.display_list = Some(display_list);
149 }
150
151 pub fn render(&mut self) -> &[u32] {
153 let mut painter = Painter::new(self.viewport_width as usize, self.viewport_height as usize);
154
155 if let Some(ref dl) = self.display_list {
156 painter.paint(dl);
157 }
158
159 self.painter = Some(painter);
160 self.painter.as_ref().unwrap().as_bytes()
161 }
162
163 pub fn handle_scroll(&mut self, delta_y: i32) {
165 self.scroll_y += delta_y;
166 if self.scroll_y < 0 {
167 self.scroll_y = 0;
168 }
169
170 if let Some(ref layout) = self.layout_root {
172 let content_height = super::css_parser::fp_to_px(
173 layout.dimensions.content.height
174 + layout.dimensions.padding.top
175 + layout.dimensions.padding.bottom,
176 );
177 let max_scroll = content_height - self.viewport_height;
178 if max_scroll > 0 && self.scroll_y > max_scroll {
179 self.scroll_y = max_scroll;
180 }
181 }
182
183 self.rebuild_display_list();
185 }
186
187 pub fn handle_resize(&mut self, width: i32, height: i32) {
189 self.viewport_width = width;
190 self.viewport_height = height;
191 self.relayout();
192 }
193
194 pub fn go_back(&mut self) -> bool {
196 if let Some(url) = self.back_history.pop() {
197 self.forward_history.push(self.current_url.clone());
198 let url_clone = url.clone();
199 self.current_url = url;
200 self.address_bar = url_clone.clone();
201 let html = self.fetch_url(&url_clone);
202 self.load_html(&html);
203 true
204 } else {
205 false
206 }
207 }
208
209 pub fn go_forward(&mut self) -> bool {
211 if let Some(url) = self.forward_history.pop() {
212 self.back_history.push(self.current_url.clone());
213 let url_clone = url.clone();
214 self.current_url = url;
215 self.address_bar = url_clone.clone();
216 let html = self.fetch_url(&url_clone);
217 self.load_html(&html);
218 true
219 } else {
220 false
221 }
222 }
223
224 pub fn reload(&mut self) {
226 if !self.current_url.is_empty() {
227 let url = self.current_url.clone();
228 let html = self.fetch_url(&url);
229 self.load_html(&html);
230 }
231 }
232
233 pub fn set_url(&mut self, url: &str) {
235 self.address_bar = url.to_string();
236 }
237
238 pub fn get_url(&self) -> &str {
240 &self.current_url
241 }
242
243 pub fn get_address_bar(&self) -> &str {
245 &self.address_bar
246 }
247
248 pub fn address_bar_input(&mut self, ch: char) {
250 self.address_bar.push(ch);
251 }
252
253 pub fn address_bar_backspace(&mut self) {
255 self.address_bar.pop();
256 }
257
258 pub fn address_bar_submit(&mut self) {
260 let url = self.address_bar.clone();
261 self.navigate(&url);
262 }
263
264 pub fn can_go_back(&self) -> bool {
266 !self.back_history.is_empty()
267 }
268
269 pub fn can_go_forward(&self) -> bool {
271 !self.forward_history.is_empty()
272 }
273
274 pub fn get_title(&self) -> String {
276 if let Some(ref doc) = self.document {
277 let titles = doc.get_elements_by_tag_name("title");
278 if let Some(&title_id) = titles.first() {
279 return doc.inner_text(title_id);
280 }
281 }
282 String::from("Untitled")
283 }
284
285 pub fn node_count(&self) -> usize {
287 self.document.as_ref().map(|d| d.node_count()).unwrap_or(0)
288 }
289
290 fn fetch_url(&self, url: &str) -> String {
292 if url.starts_with("about:blank") {
295 return String::new();
296 }
297
298 if url.starts_with("about:") {
299 return alloc::format!(
300 "<!DOCTYPE html><html><head><title>{0}</title></head><body><h1>{0}</\
301 h1><p>Internal page</p></body></html>",
302 url
303 );
304 }
305
306 alloc::format!(
308 "<!DOCTYPE html><html><head><title>Error</title></head><body><h1>Page Not \
309 Found</h1><p>Could not load: {}</p></body></html>",
310 url
311 )
312 }
313
314 fn extract_styles(html: &str) -> String {
316 let mut css = String::new();
317 let mut pos = 0;
318 let bytes = html.as_bytes();
319
320 while pos < bytes.len() {
321 if let Some(start) = find_tag_start(bytes, pos, b"<style") {
323 let mut tag_end = start + 6;
325 while tag_end < bytes.len() && bytes[tag_end] != b'>' {
326 tag_end += 1;
327 }
328 tag_end += 1; if let Some(end) = find_tag_start(bytes, tag_end, b"</style") {
332 let style_content = &html[tag_end..end];
333 css.push_str(style_content);
334 css.push('\n');
335 pos = end + 8;
336 } else {
337 pos = tag_end;
338 }
339 } else {
340 break;
341 }
342 }
343
344 css
345 }
346
347 fn relayout(&mut self) {
349 if let Some(ref doc) = self.document {
350 let layout = build_layout_tree(
351 doc,
352 &self.resolver,
353 self.viewport_width,
354 self.viewport_height,
355 );
356 let display_list = build_display_list(&layout, self.scroll_y);
357 self.layout_root = Some(layout);
358 self.display_list = Some(display_list);
359 }
360 }
361
362 fn rebuild_display_list(&mut self) {
364 if let Some(ref layout) = self.layout_root {
365 let display_list = build_display_list(layout, self.scroll_y);
366 self.display_list = Some(display_list);
367 }
368 }
369}
370
371fn find_tag_start(bytes: &[u8], start: usize, tag: &[u8]) -> Option<usize> {
373 if tag.is_empty() || start + tag.len() > bytes.len() {
374 return None;
375 }
376
377 for i in start..=(bytes.len() - tag.len()) {
378 let mut matched = true;
379 for (j, &t) in tag.iter().enumerate() {
380 if !bytes[i + j].eq_ignore_ascii_case(&t) {
381 matched = false;
382 break;
383 }
384 }
385 if matched {
386 return Some(i);
387 }
388 }
389 None
390}
391
392#[cfg(test)]
393mod tests {
394 #[allow(unused_imports)]
395 use alloc::vec;
396
397 use super::*;
398
399 #[test]
400 fn test_new_window() {
401 let w = BrowserWindow::new(800, 600);
402 assert_eq!(w.viewport_width, 800);
403 assert_eq!(w.viewport_height, 600);
404 assert_eq!(w.scroll_y, 0);
405 }
406
407 #[test]
408 fn test_default_window() {
409 let w = BrowserWindow::default();
410 assert_eq!(w.viewport_width, 800);
411 }
412
413 #[test]
414 fn test_load_html() {
415 let mut w = BrowserWindow::new(800, 600);
416 w.load_html("<p>Hello</p>");
417 assert!(w.document.is_some());
418 assert!(w.layout_root.is_some());
419 }
420
421 #[test]
422 fn test_load_html_with_css() {
423 let mut w = BrowserWindow::new(800, 600);
424 w.load_html_with_css("<p>Hello</p>", "p { color: red; }");
425 assert!(w.document.is_some());
426 assert!(w.stylesheet.is_some());
427 }
428
429 #[test]
430 fn test_navigate() {
431 let mut w = BrowserWindow::new(800, 600);
432 w.navigate("about:test");
433 assert_eq!(w.current_url, "about:test");
434 assert!(w.document.is_some());
435 }
436
437 #[test]
438 fn test_navigate_about_blank() {
439 let mut w = BrowserWindow::new(800, 600);
440 w.navigate("about:blank");
441 assert_eq!(w.current_url, "about:blank");
442 }
443
444 #[test]
445 fn test_history_back_forward() {
446 let mut w = BrowserWindow::new(800, 600);
447 w.navigate("about:page1");
448 w.navigate("about:page2");
449 assert!(w.can_go_back());
450 assert!(!w.can_go_forward());
451
452 w.go_back();
453 assert_eq!(w.current_url, "about:page1");
454 assert!(w.can_go_forward());
455
456 w.go_forward();
457 assert_eq!(w.current_url, "about:page2");
458 }
459
460 #[test]
461 fn test_no_back_when_empty() {
462 let mut w = BrowserWindow::new(800, 600);
463 assert!(!w.can_go_back());
464 assert!(!w.go_back());
465 }
466
467 #[test]
468 fn test_reload() {
469 let mut w = BrowserWindow::new(800, 600);
470 w.navigate("about:test");
471 w.reload();
472 assert_eq!(w.current_url, "about:test");
473 }
474
475 #[test]
476 fn test_scroll() {
477 let mut w = BrowserWindow::new(800, 600);
478 w.load_html("<p>Hello</p>");
479 w.handle_scroll(100);
480 assert!(w.scroll_y >= 0);
481 }
482
483 #[test]
484 fn test_scroll_negative_clamped() {
485 let mut w = BrowserWindow::new(800, 600);
486 w.load_html("<p>Hello</p>");
487 w.handle_scroll(-100);
488 assert_eq!(w.scroll_y, 0);
489 }
490
491 #[test]
492 fn test_resize() {
493 let mut w = BrowserWindow::new(800, 600);
494 w.load_html("<p>Hello</p>");
495 w.handle_resize(1024, 768);
496 assert_eq!(w.viewport_width, 1024);
497 assert_eq!(w.viewport_height, 768);
498 }
499
500 #[test]
501 fn test_get_title() {
502 let mut w = BrowserWindow::new(800, 600);
503 w.load_html("<html><head><title>My Page</title></head><body></body></html>");
504 assert_eq!(w.get_title(), "My Page");
505 }
506
507 #[test]
508 fn test_get_title_default() {
509 let w = BrowserWindow::new(800, 600);
510 assert_eq!(w.get_title(), "Untitled");
511 }
512
513 #[test]
514 fn test_address_bar() {
515 let mut w = BrowserWindow::new(800, 600);
516 w.set_url("https://example.com");
517 assert_eq!(w.get_address_bar(), "https://example.com");
518 }
519
520 #[test]
521 fn test_address_bar_input() {
522 let mut w = BrowserWindow::new(800, 600);
523 w.address_bar_input('h');
524 w.address_bar_input('i');
525 assert_eq!(w.get_address_bar(), "hi");
526 }
527
528 #[test]
529 fn test_address_bar_backspace() {
530 let mut w = BrowserWindow::new(800, 600);
531 w.set_url("abc");
532 w.address_bar_backspace();
533 assert_eq!(w.get_address_bar(), "ab");
534 }
535
536 #[test]
537 fn test_node_count() {
538 let mut w = BrowserWindow::new(800, 600);
539 assert_eq!(w.node_count(), 0);
540 w.load_html("<p>Hello</p>");
541 assert!(w.node_count() > 0);
542 }
543
544 #[test]
545 fn test_extract_styles() {
546 let css = BrowserWindow::extract_styles(
547 "<html><head><style>p { color: red; }</style></head><body></body></html>",
548 );
549 assert!(css.contains("color: red"));
550 }
551
552 #[test]
553 fn test_extract_no_styles() {
554 let css = BrowserWindow::extract_styles("<html><body></body></html>");
555 assert!(css.is_empty());
556 }
557
558 #[test]
559 fn test_find_tag_start() {
560 let bytes = b"hello <style>content</style>";
561 let result = find_tag_start(bytes, 0, b"<style");
562 assert_eq!(result, Some(6));
563 }
564
565 #[test]
566 fn test_find_tag_not_found() {
567 let bytes = b"hello world";
568 assert!(find_tag_start(bytes, 0, b"<style").is_none());
569 }
570}