- C++ 43.5%
- JavaScript 16.9%
- Go 16.4%
- TypeScript 9.4%
- Shell 6.6%
- Other 7.2%
|
Some checks failed
CI / Lint (push) Failing after 2s
CodeQL / Analyze go (push) Failing after 3s
CodeQL / Analyze javascript-typescript (push) Failing after 3s
CI / Build container image (push) Failing after 27s
CI / Docs link check (push) Failing after 16s
CI / Container smoke test (push) Has been skipped
E2E / docker-compose + Playwright (push) Failing after 51s
Runtime CDP injection of per-session signaling into the cb-chromium native peer, so the isolator can restore a warm cb-chromium golden and inject signaling post-restore instead of cold-booting (~30s saved). Inert unless invoked (isolator flag default-off). Includes the 3 branch-heads/7727 API fixes (raw_ptr, JSONReader::ReadDict, JSON_PARSE_RFC) squashed in. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .github | ||
| build | ||
| capture | ||
| client | ||
| docs | ||
| harness | ||
| infra | ||
| patches | ||
| signaling | ||
| tests | ||
| verification | ||
| .gitignore | ||
| CONTRIBUTING.md | ||
| LICENSE | ||
| Makefile | ||
| PROJECT_BRIEF.md | ||
| README.md | ||
chromeless
Open-source cloud browser with low-latency WebRTC streaming.
Status: Phase 0 — foundations. Container, signaling, and harness are still being bootstrapped. See PROJECT_BRIEF.md for the full roadmap and exit criteria for each phase.
What is this?
chromeless is a remote-browser platform: a real Chromium runs on a
Linux server, its tab is streamed to your local browser over WebRTC, and your
mouse / keyboard / clipboard / audio round-trip back through the same peer
connection. The user-facing experience is "I open a webpage and use a browser
inside it" — useful for browser isolation, scraping, agent tooling, embedded
co-browsing, and anywhere you need a real Chromium that you don't want to (or
can't) run locally.
It is built around three opinions:
- Latency is the product. Everything from capture to encoder tuning to input dispatch is evaluated against glass-to-glass latency. The targets are <100 ms on LAN and <200 ms regional, measured by a flashing-color harness from week one.
- Don't fork Chromium until you have to. The shortest path to a working
demo is stock Chromium + DevTools Protocol +
getDisplayMediapiped intoRTCPeerConnection. CustomFrameSinkVideoCapturercapture, encoder factory injection, and HW encode are scheduled improvements, not v1 prerequisites. - Ship the boring path first. Software encode (libvpx VP9, x264 zero- latency), a Go WebSocket signaler, and a single-tenant Docker container is enough to demo. Multi-tenancy, NVENC/VAAPI, and AV1 come later.
Why not Selkies, Neko, or Kasm?
Each of those projects solves a slightly different problem and we owe them
significant prior art (see docs/prior-art/ for our
surveys):
- Selkies-GStreamer focuses on GStreamer-based desktop streaming with hardware encode; it's not browser-native and not WebRTC-first by default.
- Neko is a multi-user "watch together" room around a shared browser; it is excellent for that shape and not optimized for one-user-per-Chromium isolation or programmatic access.
- Kasm Workspaces is a polished commercial product with VNC/KasmVNC at its core; it is not OSS-first and not WebRTC-first.
We are aiming for: WebRTC-first, one-Chromium-per-user, latency-obsessed, Apache-2.0, with a clear extension path for hardware encoders and custom capture. If that overlaps with what you need, contributions welcome.
Architecture sketch
┌────────────────────── User's machine ──────────────────────┐
│ │
│ ┌────────────────┐ │
│ │ Browser client │ client/index.html + main.ts │
│ │ (Chrome/FF) │ - RTCPeerConnection (recv video/ │
│ └───────┬────────┘ audio; send input via DataChannel)│
│ │ │
└───────────┼────────────────────────────────────────────────┘
│ WebSocket (SDP / ICE) WebRTC media + DC
│ (UDP, SRTP, DTLS)
▼ ▲
┌──────────────────────┐ │
│ signaling/ (Go) │ │
│ ws://…/session/:id │ │
└──────────┬───────────┘ │
│ session bootstrap │
▼ │
┌──────────────────── Headless Chromium container ─────────────── │ ────────┐
│ │ │
│ ┌───────────────────────────┐ ┌──────────────────────────┐ │ │
│ │ Chromium (headless) │ │ capture/ (sidecar) │ │ │
│ │ - Xvfb / headless+gpu │◄──►│ - getDisplayMedia path │ │ │
│ │ - DevTools Input.* │ │ - (later) FrameSink │ │ │
│ │ - PulseAudio sink │ │ VideoCapturer │ │ │
│ └────────────┬──────────────┘ └────────────┬─────────────┘ │ │
│ │ frames + audio + cursor meta │ │ │
│ ▼ ▼ │ │
│ ┌────────────────────────────────────────────────────────┐ │ │
│ │ Encoder + libwebrtc │────┼─────────┘
│ │ - SW VP9 / x264, zero-latency tuning (v1) │
│ │ - HW NVENC/VAAPI behind webrtc::VideoEncoderFactory │
│ │ - Adaptive bitrate via libwebrtc BWE (Phase 2) │
│ └────────────────────────────────────────────────────────┘
│
│ infra/ Dockerfile + compose.yaml + (later) K8s manifests
│
└─────────────────────────────────────────────────────────────────────────┘
harness/ flashing color block + webcam reconciliation
→ measures glass-to-glass latency end-to-end
A more detailed phase-by-phase architecture lives in
PROJECT_BRIEF.md; design docs and ADRs land in
docs/.
Latency budget
Latency is the product. The v1 contract is:
| Path | Target |
|---|---|
| Glass-to-glass, LAN | < 100 ms |
| Glass-to-glass, regional | < 200 ms |
| Input round-trip (separate) | budgeted in v1 doc |
The full, measurable acceptance criteria — resolution, framerate, concurrent
sessions per host, supported input types — live in
docs/v1-success-criteria.md. Every
architectural decision is evaluated against that document.
Latency is measured, not asserted. The
harness/ directory contains a flashing-color-block page and
reconciliation script: a webcam points at the client screen, the page emits
timestamped color transitions, and we recover glass-to-glass latency from the
captured video. See harness/latency/README.md
for the methodology.
Quickstart
⚠️ Coming Phase 0 exit. The container, signaling server, and client stub all land before
docker compose upworks end-to-end. Track progress via the task list in this repo.
Local dev (docker compose)
git clone https://github.com/<org>/chromeless.git
cd chromeless
docker compose -f infra/compose.yaml up
# Then open http://localhost:3000
Add --profile observability to also bring up Prometheus + Grafana
with the cb dashboards (T66) auto-loaded; see
infra/observability/dashboards/README.md.
Kubernetes (Helm)
The supported install path for clusters is the chart at
infra/helm/chromeless/:
helm install chromeless \
oci://ghcr.io/<org>/chromeless/helm/chromeless \
--version 0.1.0 \
--namespace chromeless \
--create-namespace \
-f my-values.yaml
Required my-values.yaml knobs are documented inline in
values.yaml; the
short list is the Ed25519 auth pubkey, the TURN shared secret, and
the TURN URLs. See
docs/operations/phase1-deployment-checklist.md
before going to production, and
docs/operations/runbook.md for
day-2 operations.
For development against individual components before the full stack lands, see each subdirectory's README.
Known limitations (v1)
A handful of things don't fully work in v1 by design — captured here so users hit them with eyes open. Each has a planned-fix milestone.
- WebGL is software-rendered. v1's launch flag set pins Chromium
to ANGLE + SwiftShader (T78 / T91 — Vulkan is disabled because
Xvfb has no Vulkan driver). Simple WebGL pages render correctly;
heavy WebGL (Three.js stress scenes, full-frame post-processing)
will drop below ~5 fps and feel choppy. Phase 4's GPU passthrough
unblocks hardware WebGL. See
docs/research/rendering-matrix.md. - WebGPU is not supported. Dawn requires Vulkan on Linux; v1
disables Vulkan.
navigator.gpuis present, butrequestAdapter()returns null. Same Phase 4 GPU-passthrough fix unblocks it. - Dev compose ships synthetic media.
infra/compose.yamldefaultsCHROMELESS_USE_FAKE_MEDIA=1, which routesgetDisplayMediathrough Chromium 147's synthetic test pattern + tone instead of real screen capture. RealgetDisplayMediafails on Chromium 147- Xvfb regardless of launch-flag tuning (see
docs/capture/path-of-least-resistance.md§3a andcapture/streamer-page/launch.md§"T86: the X11+SwiftShader pin alone wasn't enough"). Phase 2'sFrameSinkVideoCapturer(T47, T55) replaces thegetDisplayMediapath entirely and is the durable fix.
- Xvfb regardless of launch-flag tuning (see
- Single tab per session, single session per container. v1 intentionally ships one Chromium per user; multi-tenant orchestration is Phase 3.
Repo layout
| Path | Contents |
|---|---|
signaling/ |
WebSocket signaling server (Go). One session per browser for v1. |
capture/ |
Headless Chromium capture sidecar + spike branches for FrameSinkVideoCapturer. |
client/ |
Browser-side client (index.html, main.ts) — RTCPeerConnection + input DataChannel. |
infra/ |
Dockerfile, compose.yaml, K8s manifests, TURN/STUN config. |
harness/ |
Glass-to-glass latency harness (flashing color block + webcam reconciliation). |
docs/ |
v1 success criteria, prior-art surveys, ADRs, internal notes. |
tests/ |
Container smoke tests, harness validation, E2E regression. |
PROJECT_BRIEF.md |
Phased roadmap, team shape, risks, definition of v1 done. |
Contributing
This project is in Phase 0; the public contribution flow will solidify once the Phase 0 exit gate (container + harness + signaling stub) is green. In the meantime:
- Issues describing latency regressions, broken inputs, or container build problems are welcome.
- PRs are easiest to land if they (a) come with a measurement from the
harness for anything in the hot path, and (b) reference a section of
PROJECT_BRIEF.mdordocs/v1-success-criteria.md. - Design discussions that would change architecture should land as an
ADR under
docs/before code.
A formal CONTRIBUTING.md and code-of-conduct will be added before the
project is announced publicly.
License
Apache-2.0 — see LICENSE.