Split the 2,722-line main.rs into 7 modules (net_utils, app_state, event_handler, focus_outline, hit_test, form, render_mode) and a tests/ directory with 5 submodule files, following the same pattern used in recent refactors of display_list, rasterizer, style, and engine. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
201 lines
5.7 KiB
Rust
201 lines
5.7 KiB
Rust
#![deny(unsafe_code)]
|
|
|
|
use anyhow::{anyhow, Result};
|
|
use browser_runtime::BrowserRuntime;
|
|
use css::ColorScheme;
|
|
use image::ImageStore;
|
|
use net::NetworkStack;
|
|
use once_cell::sync::Lazy;
|
|
use platform::{PlatformContext, WindowConfig};
|
|
use shared::{bootstrap, BrowserUrl, Subsystem};
|
|
use std::collections::HashMap;
|
|
use std::env;
|
|
use tracing::info;
|
|
|
|
mod app_state;
|
|
mod chrome;
|
|
mod event_handler;
|
|
mod focus_outline;
|
|
mod form;
|
|
mod hit_test;
|
|
mod net_utils;
|
|
mod pipeline;
|
|
mod render_mode;
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
use app_state::AppState;
|
|
use chrome::BrowserChrome;
|
|
use event_handler::handle_event;
|
|
use net_utils::{load_url, url_to_title, ResponseOrError};
|
|
use pipeline::Pipeline;
|
|
use render_mode::run_render_mode;
|
|
|
|
static APP_NAME: Lazy<&str> = Lazy::new(|| "rust-browser");
|
|
|
|
/// Default viewport dimensions.
|
|
const DEFAULT_WIDTH: u32 = 800;
|
|
const DEFAULT_HEIGHT: u32 = 600;
|
|
|
|
fn main() -> Result<()> {
|
|
let args: Vec<String> = env::args().collect();
|
|
|
|
// Check for --help flag
|
|
if args.iter().any(|arg| arg == "--help" || arg == "-h") {
|
|
print_help();
|
|
return Ok(());
|
|
}
|
|
|
|
tracing_subscriber::fmt::init();
|
|
|
|
let dark = args.iter().any(|arg| arg == "--dark");
|
|
let color_scheme = if dark {
|
|
ColorScheme::Dark
|
|
} else {
|
|
ColorScheme::Light
|
|
};
|
|
|
|
// Check for --render mode
|
|
if args.len() > 1 && args[1] == "--render" {
|
|
return run_render_mode(&args[2..], color_scheme);
|
|
}
|
|
|
|
// Collect positional args (skip --dark)
|
|
let positional: Vec<&str> = args[1..]
|
|
.iter()
|
|
.filter(|a| *a != "--dark")
|
|
.map(|s| s.as_str())
|
|
.collect();
|
|
|
|
// Check if a file path was provided
|
|
if positional.is_empty() {
|
|
// Normal browser mode (original behavior)
|
|
return run_normal_mode();
|
|
}
|
|
|
|
// Windowed mode with file argument
|
|
run_windowed_mode(positional[0], color_scheme)
|
|
}
|
|
|
|
/// Print help message and usage information.
|
|
fn print_help() {
|
|
let version = env!("CARGO_PKG_VERSION");
|
|
println!("rust-browser {}", version);
|
|
println!();
|
|
println!("A minimal web browser written in Rust.");
|
|
println!();
|
|
println!("USAGE:");
|
|
println!(" app_browser [OPTIONS] [URL]");
|
|
println!();
|
|
println!("ARGS:");
|
|
println!(" <URL> URL or file path to open in the browser window");
|
|
println!();
|
|
println!("OPTIONS:");
|
|
println!(" -h, --help Print this help message");
|
|
println!(" --dark Use dark color scheme (prefers-color-scheme: dark)");
|
|
println!(
|
|
" --render <FILE|URL> Render an HTML file or URL to layout/display list dumps"
|
|
);
|
|
println!(
|
|
" --output-dir <DIR> Output directory for render mode (default: same as input for files, . for URLs)"
|
|
);
|
|
println!(" --png Also generate a PNG image in render mode");
|
|
println!();
|
|
println!("EXAMPLES:");
|
|
println!(" app_browser https://example.com");
|
|
println!(" app_browser file:///path/to/page.html");
|
|
println!(" app_browser ./local-file.html");
|
|
println!(" app_browser --render input.html --png");
|
|
println!(" app_browser --render input.html --output-dir ./output --png");
|
|
println!(" app_browser --render https://example.com --output-dir /tmp/out --png");
|
|
}
|
|
|
|
/// Original browser mode without file argument.
|
|
fn run_normal_mode() -> Result<()> {
|
|
info!("starting {}", *APP_NAME);
|
|
|
|
let mut platform = PlatformContext::new();
|
|
let mut runtime = BrowserRuntime::new();
|
|
|
|
{
|
|
let mut subsystems: Vec<&mut dyn Subsystem> = vec![&mut platform, &mut runtime];
|
|
bootstrap(subsystems.as_mut_slice())?;
|
|
}
|
|
|
|
// Without a file to display, just log that we're ready
|
|
info!("browser ready (no file specified)");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Windowed mode: display rendered HTML in a window.
|
|
fn run_windowed_mode(input: &str, color_scheme: ColorScheme) -> Result<()> {
|
|
info!("starting {} in windowed mode", *APP_NAME);
|
|
|
|
// Parse the input as a URL
|
|
let url =
|
|
BrowserUrl::parse(input).map_err(|e| anyhow!("Failed to parse URL '{}': {}", input, e))?;
|
|
|
|
// Initialize subsystems
|
|
let mut platform = PlatformContext::new();
|
|
let mut runtime = BrowserRuntime::new();
|
|
|
|
{
|
|
let mut subsystems: Vec<&mut dyn Subsystem> = vec![&mut platform, &mut runtime];
|
|
bootstrap(subsystems.as_mut_slice())?;
|
|
}
|
|
|
|
// Create network stack and load the initial URL
|
|
let network = NetworkStack::new();
|
|
let initial_response = load_url(&network, &url, None);
|
|
|
|
// Create window configuration
|
|
let title = url_to_title(&url);
|
|
|
|
let config = WindowConfig {
|
|
title,
|
|
width: DEFAULT_WIDTH,
|
|
height: DEFAULT_HEIGHT,
|
|
};
|
|
|
|
// Create browser chrome with initial URL
|
|
let chrome = BrowserChrome::with_url(url.as_str());
|
|
|
|
// Create application state
|
|
let mut state = AppState {
|
|
runtime,
|
|
network,
|
|
chrome,
|
|
pipeline: Pipeline::new(DEFAULT_WIDTH as f32, DEFAULT_HEIGHT as f32)
|
|
.with_color_scheme(color_scheme),
|
|
current_url: url,
|
|
current_response: initial_response,
|
|
cached_buffer: None,
|
|
document: None,
|
|
layout_tree: None,
|
|
image_store: ImageStore::new(),
|
|
computed_styles: None,
|
|
image_map: None,
|
|
bg_image_map: None,
|
|
scroll_y: 0.0,
|
|
max_scroll_y: 0.0,
|
|
scale_factor: 1.0,
|
|
focused_node: None,
|
|
input_states: HashMap::new(),
|
|
};
|
|
|
|
// Push initial URL to history if successfully loaded
|
|
if matches!(state.current_response, ResponseOrError::Response(_)) {
|
|
state
|
|
.runtime
|
|
.browsing_context_mut()
|
|
.push_to_history(state.current_url.clone());
|
|
}
|
|
|
|
// Run the event loop
|
|
platform.run_event_loop(config, state, handle_event)?;
|
|
|
|
Ok(())
|
|
}
|