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" 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 — ~150 host functions │ │
│ │ canvas · gpu · audio · video · capture · fetch · streaming │ │
│ │ websocket · webrtc · midi · timers · animation frames │ │
│ │ console · storage · clipboard · widgets · crypto · ... │ │
│ └────────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────▼───────────────────────────────┐ │
│ │ Guest .wasm Module │ │
│ │ exports: start_app(), on_frame(dt) │ │
│ │ imports: oxide::* (via oxide-sdk) │ │
│ └────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
oxide/
├── oxide-browser/ # Host browser application
│ └── src/
│ ├── main.rs # GPUI entry — BrowserHost + run_browser
│ ├── engine.rs # WasmEngine, SandboxPolicy, compile & memory bounds
│ ├── runtime.rs # BrowserHost, fetch/load/instantiate pipeline
│ ├── capabilities.rs # ~100 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 # GPUI shell — toolbar, canvas paint, console, widgets
├── 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
├── audio-player/ # Audio playback
├── video-player/ # FFmpeg video, subtitles, PiP, HLS
├── media-capture/ # Camera, microphone, screen capture
├── gpu-graphics-demo/ # WebGPU-style rendering and compute
├── rtc-chat/ # WebRTC peer-to-peer chat
├── ws-chat/ # WebSocket chat
├── stream-fetch-demo/ # Streaming HTTP fetch
├── timer-demo/ # set_timeout / set_interval
├── raf-demo/ # request_animation_frame
├── midi-demo/ # MIDI input visualizer
├── index/ # Demo hub linking the others
└── fullstack-notes/ # Full-stack 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 (4096 pages / 256 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, plus the oxide_sdk::draw high-level API |
| GPU | gpu_create_buffer/texture/shader, gpu_create_pipeline, gpu_create_compute_pipeline, gpu_write_buffer, gpu_draw, gpu_dispatch_compute (WGSL) |
| 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) |
| HTTP | fetch (full HTTP), fetch_get/post/put/delete, fetch_post_proto |
| Streaming HTTP | fetch_begin, fetch_state, fetch_status, fetch_recv, fetch_error, fetch_abort |
| WebSocket | ws_connect, ws_send_text/binary, ws_recv, ws_ready_state, ws_close, ws_remove |
| WebRTC | rtc_create_peer, rtc_create_offer/answer, rtc_set_local/remote_description, rtc_add_ice_candidate, rtc_create_data_channel, rtc_send_text/binary, rtc_recv, rtc_add_track, rtc_poll_track, rtc_signal_* |
| Audio | audio_play, audio_play_url, audio_pause/resume/stop, audio_set_volume, audio_seek, audio_set_loop, audio_channel_play (multi-channel), audio_detect_format |
| Video | video_load, video_load_url, video_play/pause/stop, video_render, video_seek, video_set_pip, video_hls_*, subtitle_load_srt/vtt (FFmpeg-backed) |
| Media capture | camera_open, camera_capture_frame, microphone_open, microphone_read_samples, screen_capture |
| MIDI | midi_input_count/output_count, midi_input_name/output_name, midi_open_input/output, midi_send, midi_recv, midi_close |
| Timers | set_timeout, set_interval, clear_timer, request_animation_frame, cancel_animation_frame |
| Navigation | navigate, push_state, replace_state, get_url, history_back/forward |
| Hyperlinks | register_hyperlink, clear_hyperlinks (clickable canvas regions) |
| Crypto / encoding | hash_sha256, hash_sha256_hex, 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. ui.rsdrains both queues each frame and paints them with GPUI (paint_quad,paint_path,paint_image, text shaping) inside the canvas region.- Images are decoded once and cached as GPUI
RenderImagetextures (same path for video frames). - 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 | 256 MB (4096 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 + tokio-tungstenite |
HTTP fetch (.wasm, streaming) and WebSocket frames |
| Async | tokio |
Async runtime for network and background work |
| UI | GPUI | GPU-accelerated shell; URL bar, canvas, console, tabs |
| 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 |
| Video | ffmpeg-next |
H.264/H.265/AV1/VP9 decode, HLS, subtitles, PiP |
| Audio | rodio |
Multi-channel playback and decode |
| Capture | nokhwa, cpal, screenshots |
Camera, microphone, screen capture |
| GPU | wgpu |
WebGPU-style host backend for guest GPU APIs |
| WebRTC | webrtc |
Peer connections, data channels, media tracks |
| MIDI | coremidi (macOS) |
Cross-platform MIDI I/O |
| Crypto | sha2 |
SHA-256 hashing for guest modules |
See DOCS.md for the full developer guide, API reference, and instructions for building WASM websites.
