we (web engine): Experimental web browser project to understand the limits of Claude
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement about:blank document

Add about: scheme handling to ResourceLoader:
- Recognize about:blank and return a minimal empty HTML document
(<!DOCTYPE html><html><head></head><body></body></html>)
- UTF-8 encoding, no network requests
- Unsupported about: URLs return InvalidUrl error

Update browser main to use about:blank as default:
- No URL argument opens about:blank instead of hardcoded demo page
- URL arguments (http://, https://, about:, data:) load via ResourceLoader
- File path arguments still work as before

4 tests covering fetch/fetch_url paths, DOM structure validation,
and unsupported about: URL rejection.

Implements issue 3mhkt7vaxsb23

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+156 -30
+118 -2
crates/browser/src/loader.rs
··· 101 101 /// text resources using the appropriate character encoding (per WHATWG spec), 102 102 /// and returns the result as a typed `Resource`. 103 103 /// 104 - /// Handles `data:` URLs locally without network access. 104 + /// Handles `data:` and `about:` URLs locally without network access. 105 105 pub fn fetch(&mut self, url: &Url) -> Result<Resource, LoadError> { 106 106 // Handle data: URLs without network fetch. 107 107 if url.scheme() == "data" { 108 108 return fetch_data_url(&url.serialize()); 109 + } 110 + 111 + // Handle about: URLs without network fetch. 112 + if url.scheme() == "about" { 113 + return fetch_about_url(url); 109 114 } 110 115 111 116 let response = self.client.get(url)?; ··· 170 175 171 176 /// Fetch a URL string, resolving it against an optional base URL. 172 177 /// 173 - /// Handles `data:` URLs locally without network access. 178 + /// Handles `data:` and `about:` URLs locally without network access. 174 179 pub fn fetch_url(&mut self, url_str: &str, base: Option<&Url>) -> Result<Resource, LoadError> { 175 180 // Handle data URLs directly — no network fetch needed. 176 181 if is_data_url(url_str) { 177 182 return fetch_data_url(url_str); 183 + } 184 + 185 + // Handle about: URLs without network fetch. 186 + if url_str.starts_with("about:") { 187 + let url = 188 + Url::parse(url_str).map_err(|_| LoadError::InvalidUrl(url_str.to_string()))?; 189 + return fetch_about_url(&url); 178 190 } 179 191 180 192 let url = match base { ··· 325 337 }) 326 338 } 327 339 } 340 + } 341 + } 342 + 343 + // --------------------------------------------------------------------------- 344 + // about: URL handling 345 + // --------------------------------------------------------------------------- 346 + 347 + /// The minimal HTML document for about:blank. 348 + pub const ABOUT_BLANK_HTML: &str = "<!DOCTYPE html><html><head></head><body></body></html>"; 349 + 350 + /// Fetch an about: URL, returning the appropriate resource. 351 + /// 352 + /// Currently only `about:blank` is supported, which returns an empty HTML 353 + /// document with UTF-8 encoding. 354 + fn fetch_about_url(url: &Url) -> Result<Resource, LoadError> { 355 + match url.path().as_str() { 356 + "blank" => Ok(Resource::Html { 357 + text: ABOUT_BLANK_HTML.to_string(), 358 + base_url: url.clone(), 359 + encoding: Encoding::Utf8, 360 + }), 361 + other => Err(LoadError::InvalidUrl(format!( 362 + "unsupported about: URL: about:{other}" 363 + ))), 328 364 } 329 365 } 330 366 ··· 714 750 } 715 751 other => panic!("expected Other, got {:?}", other), 716 752 } 753 + } 754 + 755 + // ----------------------------------------------------------------------- 756 + // about: URL loading 757 + // ----------------------------------------------------------------------- 758 + 759 + #[test] 760 + fn about_blank_via_fetch_url() { 761 + let mut loader = ResourceLoader::new(); 762 + let result = loader.fetch_url("about:blank", None); 763 + assert!(result.is_ok()); 764 + match result.unwrap() { 765 + Resource::Html { 766 + text, 767 + encoding, 768 + base_url, 769 + .. 770 + } => { 771 + assert_eq!(text, ABOUT_BLANK_HTML); 772 + assert_eq!(encoding, Encoding::Utf8); 773 + assert_eq!(base_url.scheme(), "about"); 774 + } 775 + other => panic!("expected Html, got {:?}", other), 776 + } 777 + } 778 + 779 + #[test] 780 + fn about_blank_via_fetch() { 781 + let mut loader = ResourceLoader::new(); 782 + let url = Url::parse("about:blank").unwrap(); 783 + let result = loader.fetch(&url); 784 + assert!(result.is_ok()); 785 + match result.unwrap() { 786 + Resource::Html { 787 + text, 788 + encoding, 789 + base_url, 790 + .. 791 + } => { 792 + assert_eq!(text, ABOUT_BLANK_HTML); 793 + assert_eq!(encoding, Encoding::Utf8); 794 + assert_eq!(base_url.scheme(), "about"); 795 + } 796 + other => panic!("expected Html, got {:?}", other), 797 + } 798 + } 799 + 800 + #[test] 801 + fn about_blank_dom_structure() { 802 + let doc = we_html::parse_html(ABOUT_BLANK_HTML); 803 + 804 + // Find the <html> element under the document root. 805 + let html = doc 806 + .children(doc.root()) 807 + .find(|&n| doc.tag_name(n) == Some("html")); 808 + assert!(html.is_some(), "document should have an <html> element"); 809 + let html = html.unwrap(); 810 + 811 + // The DOM should have html > head + body structure. 812 + let children: Vec<_> = doc 813 + .children(html) 814 + .filter(|&n| doc.tag_name(n).is_some()) 815 + .collect(); 816 + assert_eq!(children.len(), 2); 817 + assert_eq!(doc.tag_name(children[0]).unwrap(), "head"); 818 + assert_eq!(doc.tag_name(children[1]).unwrap(), "body"); 819 + 820 + // Body should have no child elements. 821 + let body_children: Vec<_> = doc 822 + .children(children[1]) 823 + .filter(|&n| doc.tag_name(n).is_some()) 824 + .collect(); 825 + assert!(body_children.is_empty()); 826 + } 827 + 828 + #[test] 829 + fn about_unsupported_url() { 830 + let mut loader = ResourceLoader::new(); 831 + let result = loader.fetch_url("about:invalid", None); 832 + assert!(matches!(result, Err(LoadError::InvalidUrl(_)))); 717 833 } 718 834 }
+38 -28
crates/browser/src/main.rs
··· 1 1 use std::cell::RefCell; 2 2 use std::collections::HashMap; 3 3 4 + use we_browser::loader::{ResourceLoader, ABOUT_BLANK_HTML}; 4 5 use we_html::parse_html; 5 6 use we_layout::layout; 6 7 use we_platform::appkit; ··· 8 9 use we_render::Renderer; 9 10 use we_style::computed::{extract_stylesheets, resolve_styles}; 10 11 use we_text::font::{self, Font}; 11 - 12 - /// Default HTML page shown when no file argument is provided. 13 - const DEFAULT_HTML: &str = r#"<!DOCTYPE html> 14 - <html> 15 - <head> 16 - <title>we browser</title> 17 - <style> 18 - h1 { color: blue; } 19 - h2 { color: green; } 20 - p { color: #333333; } 21 - </style> 22 - </head> 23 - <body> 24 - <h1>Hello from we!</h1> 25 - <p>This is a from-scratch web browser engine written in pure Rust.</p> 26 - <p>Zero external crate dependencies. Every subsystem is implemented in Rust.</p> 27 - <h2>Features</h2> 28 - <p>HTML5 tokenizer, DOM tree, block layout, CSS cascade, and software rendering.</p> 29 - </body> 30 - </html>"#; 31 12 32 13 /// Browser state kept in thread-local storage so the resize handler can 33 14 /// access it. All AppKit callbacks run on the main thread. ··· 109 90 }); 110 91 } 111 92 112 - fn main() { 113 - // Load HTML from file argument or use default page. 114 - let html = match std::env::args().nth(1) { 115 - Some(path) => match std::fs::read_to_string(&path) { 116 - Ok(content) => content, 93 + /// Load content from a command-line argument. 94 + /// 95 + /// Tries the argument as a URL first (http://, https://, about:, data:), 96 + /// then falls back to reading it as a file path. 97 + fn load_from_arg(arg: &str) -> String { 98 + // Try as URL if it has a recognized scheme. 99 + if arg.starts_with("http://") 100 + || arg.starts_with("https://") 101 + || arg.starts_with("about:") 102 + || arg.starts_with("data:") 103 + { 104 + let mut loader = ResourceLoader::new(); 105 + match loader.fetch_url(arg, None) { 106 + Ok(we_browser::loader::Resource::Html { text, .. }) => return text, 107 + Ok(_) => { 108 + eprintln!("URL did not return HTML: {arg}"); 109 + std::process::exit(1); 110 + } 117 111 Err(e) => { 118 - eprintln!("Error reading {}: {}", path, e); 112 + eprintln!("Error loading {arg}: {e}"); 119 113 std::process::exit(1); 120 114 } 121 - }, 122 - None => DEFAULT_HTML.to_string(), 115 + } 116 + } 117 + 118 + // Fall back to file path. 119 + match std::fs::read_to_string(arg) { 120 + Ok(content) => content, 121 + Err(e) => { 122 + eprintln!("Error reading {arg}: {e}"); 123 + std::process::exit(1); 124 + } 125 + } 126 + } 127 + 128 + fn main() { 129 + // Load HTML from argument (URL, file path) or default to about:blank. 130 + let html = match std::env::args().nth(1) { 131 + Some(arg) => load_from_arg(&arg), 132 + None => ABOUT_BLANK_HTML.to_string(), 123 133 }; 124 134 125 135 // Load a system font for text rendering.