Oxide is a decentralised browser that fetches and executes .wasm (WebAssembly) modules instead of HTML/JavaScript. Guest applications run in a secure, sandboxed environment with capability-based access to host APIs.
# Install the wasm target
rustup target add wasm32-unknown-unknown
# Build and run the browser
cargo run -p oxide-browser
# Build the example guest app
cargo build --target wasm32-unknown-unknown --release -p hello-oxide
# In the browser, click "Open File" and select:
# target/wasm32-unknown-unknown/release/hello_oxide.wasm┌──────────────────────────────────────────────────────────────────┐
│ Oxide Browser │
│ │
│ ┌──────────┐ ┌────────────────────────┐ ┌─────────────────┐ │
│ │ URL Bar │ │ Canvas │ │ Console │ │
│ └────┬─────┘ └───────────┬────────────┘ └────────┬────────┘ │
│ │ │ │ │
│ ┌────▼────────────────────▼────────────────────────▼─-───────┐ │
│ │ Host Runtime │ │
│ │ wasmtime engine · fuel metering · bounded memory │ │
│ └────────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────▼───────────────────────────────┐ │
│ │ Capability Layer │ │
│ │ "oxide" import module — ~50 host functions │ │
│ │ canvas · console · storage · clipboard · fetch · crypto │ │
│ │ input · widgets · navigation · dynamic module loading │ │
│ └────────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────▼───────────────────────────────┐ │
│ │ Guest .wasm Module │ │
│ │ exports: start_app(), on_frame(dt) │ │
│ │ imports: oxide::* (via oxide-sdk) │ │
│ └────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
oxide/
├── oxide-browser/ # Host browser application
│ └── src/
│ ├── main.rs # eframe bootstrap
│ ├── engine.rs # WasmEngine, SandboxPolicy, compile & memory bounds
│ ├── runtime.rs # BrowserHost, fetch/load/instantiate pipeline
│ ├── capabilities.rs # ~50 host functions registered into the wasmtime Linker
│ ├── navigation.rs # History stack, back/forward, push/replace state
│ ├── url.rs # WHATWG-style URL parser (http, https, file, oxide schemes)
│ └── ui.rs # egui UI — toolbar, canvas painter, console, widget renderer
├── oxide-sdk/ # Guest-side SDK (no dependencies, pure FFI wrappers)
│ └── src/
│ ├── lib.rs # Safe Rust wrappers over host imports
│ └── proto.rs # Zero-dependency protobuf wire-format codec
└── examples/
├── hello-oxide/ # Minimal guest app
└── fullstack-notes/ # Full-stack example (Rust frontend + backend)
When a user enters a URL or opens a .wasm file, the browser walks through a short, deterministic pipeline — no parsing, no tree-building, no style resolution:
┌───────────────┐ ┌──────────────┐ ┌──────────────────┐
│ Fetch bytes │────▶│ Compile │────▶│ Link host fns │
│ (HTTP/file) │ │ (wasmtime) │ │ + bounded mem │
└───────────────┘ └──────────────┘ └────────┬─────────┘
│
┌───────────────┐ ┌──────────────┐ ┌────────▼─────────┐
│ Frame loop │◀────│ start_app() │◀────│ Instantiate │
│ (on_frame) │ │ entry call │ │ wasm module │
└───────────────┘ └──────────────┘ └──────────────────┘
- Fetch — download raw
.wasmbytes via HTTP or read from a local file (max 50 MB). - Compile —
WasmEnginecompiles the module through wasmtime with aSandboxPolicythat sets fuel and memory bounds. - Link — A
Linkerregisters alloxide::*host functions fromcapabilities.rsand creates a bounded linear memory (256 pages / 16 MB max). - Instantiate — wasmtime creates an
Instance; theStorecarries aHostStateholding canvas commands, console lines, input state, storage maps, and widget state. - start_app() — the guest's exported entry point runs once.
- on_frame(dt_ms) — if the guest exports this function, the browser calls it every frame for interactive/immediate-mode apps. Fuel is replenished each frame.
Load and execution happen on a background thread with its own tokio runtime. The UI communicates via RunRequest/RunResult channels and keeps a LiveModule handle for the frame loop.
Guest modules start with zero capabilities. Every interaction with the outside world goes through explicit host functions registered in the wasmtime Linker under the "oxide" namespace:
| Category | Host Functions |
|---|---|
| Canvas | clear, rect, circle, text, line, image, dimensions |
| UI widgets | button, checkbox, slider, text_input (immediate-mode) |
| Console | log, warn, error |
| Input | mouse_position, mouse_button_down/clicked, key_down/pressed, scroll_delta, modifiers |
| Storage | storage_set/get/remove (session), kv_store_set/get/delete (persistent, sled-backed) |
| Networking | fetch (full HTTP), convenience wrappers for GET/POST/PUT/DELETE |
| Navigation | navigate, push_state, replace_state, get_url, history_back/forward |
| Crypto / encoding | hash_sha256, base64_encode/decode |
| Clipboard | read, write |
| Time / random | time_now_ms, random_u64, random_f64 |
| Dynamic loading | load_module — fetch and run a child .wasm with isolated memory and fuel |
Data crosses the boundary through the guest's linear memory: the guest passes (ptr, len) pairs; the host reads/writes with read_guest_string / write_guest_bytes. Return values pack status and length into a single integer where needed.
Oxide uses an immediate-mode rendering approach — there is no retained scene graph or DOM:
- Each frame, the guest issues draw commands (
canvas_clear,canvas_rect,canvas_text, …) which pushDrawCommandvariants intoHostState.canvas.commands. - Widget calls (
ui_button,ui_slider, …) pushWidgetCommandentries. - The egui
CentralPanelinui.rsdrains both queues and paints them using egui primitives (painter.rect_filled,painter.text, etc.) on an 800×600 canvas. - Images are decoded once and cached as egui textures.
- Widget interactions (clicks, drags, text edits) flow back through
widget_statesandwidget_clicked, which the guest reads on the next frame call.
No layout engine. No style cascade. The guest decides exactly where every pixel goes.
| Constraint | Value | Purpose |
|---|---|---|
| Filesystem access | None | Guest cannot touch host files |
| Environment variables | None | Guest cannot read host env |
| Network sockets | None | All HTTP is mediated by the host |
| Memory ceiling | 16 MB (256 pages) | Prevents memory exhaustion |
| Fuel budget | 500M instructions/call | Prevents infinite loops and DoS |
Security is additive, not subtractive: there is nothing to claw back because nothing is granted by default. File uploads go through a host-side native file picker; HTTP goes through a host-side reqwest client; child modules get their own isolated memory and fuel. No WASI is linked — the sandbox is airtight by construction.
| Component | Crate | Purpose |
|---|---|---|
| Runtime | wasmtime |
WASM execution with fuel metering and memory limits |
| Networking | reqwest |
Fetch .wasm binaries from URLs |
| Async | tokio |
Async runtime for network operations |
| UI | egui / eframe |
URL bar, canvas renderer, console panel |
| Storage | sled |
Persistent key-value store (per-origin) |
| File Picker | rfd |
Native OS file dialogs |
| Clipboard | arboard |
System clipboard access |
| Imaging | image |
PNG/JPEG/GIF/WebP decoding for canvas images |
| Crypto | sha2 |
SHA-256 hashing for guest modules |
See DOCS.md for the full developer guide, API reference, and instructions for building WASM websites.
