Files
rust_browser/crates/app_browser/src/main.rs
Zachary D. Rowitsch db8e21d811 Refactor app_browser/main.rs into focused modules
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>
2026-02-18 21:14:53 -05:00

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(())
}