Nordstjernen is a web browser, written from scratch in C.
Focused on supporting the HTML and CSS standards.
Nordstjernen is built in Norway.
Runs on the platforms Windows , Mac and Linux, Android, Java, FreeBSD and NetBSD.
HTML Standards: Behaviour is measured against the spec text, section by section, not against another browser — 136 spec rows fully implemented, 27 partial, 4 absent as of June 2026.
Security: each tab's engine runs in its own sandboxed process (seccomp + Landlock on Linux) behind an IPC + shared-memory-framebuffer boundary · no JIT.
Minimalism: The whole engine is about 123,000 lines of C (plus a thin C++ Qt shell) — small enough for one person to read and audit end-to-end.
Nordstjernen has no JIT so it is much more secure, and can still be fast enough. It ships no telemetry of any kind.
Nordstjernen is measured against the spec text, section by section, not against any other browser. The section-by-section walk-through of the in-scope WHATWG HTML standard (§1–§16) in docs/HTML-compatibility.md currently records 136 spec rows fully implemented, 27 partial, and 4 absent (June 2026), besides a handful that are non-goals by design, such as in-process media codecs. Highlights:
| Spec area | Status |
|---|---|
| §2 Common infrastructure — WHATWG URL, IDN, origins, encodings | ✅ |
| §3–§4 Semantics, document structure & tabular content | ✅ |
§4.8 Embedded content — images, SVG, iframe, minimalist MathML presentation layout; <video> decodes and plays inline (MPEG-1); other codecs and <audio> hand off to an external player |
🟡 |
§4.10 Forms — controls, validation, valueAs* |
✅ |
| §4.12–§4.13 Scripting, custom elements | ✅ |
§6 User interaction — focus, inert, contenteditable, hidden/content-visibility, drag-and-drop incl. native file drops |
✅ |
§7–§8 Loading pages, web application APIs — fetch, XHR, timers, observers |
✅ |
§9 Communication — WebSocket, EventSource, postMessage |
✅ |
§10 Web workers — dedicated workers (fetch, crypto.subtle, transferable ArrayBuffers), Service Workers with FetchEvent interception, Cache API (Shared/module workers & worklets aside) |
✅ |
§12 Web storage — localStorage / sessionStorage |
✅ |
| §13 HTML syntax (lexbor parser); §14 XML partial | ✅ |
| §15 Rendering — CSS cascade, flex, grid, transforms | ✅ |
The full section-by-section walk-through lives in docs/HTML-compatibility.md.
- HTML/CSS via the lexbor parser — modern cascade, flex, grid,
transforms, gradients,
@keyframes. - JavaScript on the QuickJS interpreter — DOM, Shadow DOM, observer
APIs, Canvas 2D (
Path2D,ImageBitmap,DOMMatrix), WebCrypto (crypto.subtleover OpenSSL). - Networking over HTTP/2 with libcurl — HSTS, CSP, partitioned cookies.
- Media — images, optional inline PDF;
<video>plays inline for the built-in MPEG-1 codec (decoded in-tree by pl_mpeg, MIT-licensed), withautoplay,loop, and click-to-play/pause; other video codecs and<audio>are handed off to an external player; any script the host has fonts for. - MathML — a minimalist presentation-MathML renderer (
src/mathml.c) coveringmrow,mi/mn/mo/mtext,msup/msub/msubsup,mfrac,msqrt/mroot,munder/mover/munderover,mtable,mfenced, andmphantom, laid out over Pango/Cairo and embedded inline on the text baseline. - Spell checking — optional, via the Enchant library (
src/spellcheck.c): misspelled words in editable text (text inputs,textarea,contenteditable) get a red wavy underline, honouring thespellcheckattribute. Dictionaries load before the renderer seals its sandbox; with Enchant absent it degrades cleanly to no checking. - WebGL — opt-in, per-site WebGL 1 / 2 mapped onto OpenGL ES;
off by default and gated behind a trust prompt. See
docs/webgl.md. - WebAssembly — the full JS API (
compile,instantiate,Memory,Table, externref) over a vendored WAMR interpreter; runs wasm-bindgen bundles. Seedocs/webassembly.md. - Process-per-tab — each tab's engine runs in its own sandboxed
nordstjernen-rendererprocess; the GTK and Qt apps are thin shells that blit the renderer's shared-memory framebuffer and forward input over an IPC control channel (src/rproc_http.c), so a page can't take down the UI. Seedocs/tab-isolation.mdanddocs/Rendering.md. An optional--single-processflag (GTK and Qt) runs every tab's engine inside the shell process instead — same protocol, threads instead of child processes — for low-memory machines, containers, and debugging. Seedocs/single-process-mode.md. - Local AI start page — the
about:startnew-tab page is a chat with a small language model running entirely on your machine via llama.cpp (no cloud, no network at inference time). Pick a model and it downloads once, integrity-checked against a pinned SHA-256 digest; optional GPU offload (Vulkan / Metal). The assistant can also pull a Wikipedia image, run a DuckDuckGo web search, or open a site for you. Seedocs/ai.md. - UI — tabs, bookmarks, find-in-page, save-to-PDF, JS console, settings, headless mode, and a C embedding API.
Nightly builds, rebuilt from main each night. These point at the
latest build — bleeding edge, expect rough edges.
| Platform | Download |
|---|---|
| Windows | Windows store - nordstjernen-windows-x86_64.zip |
| macOS | nordstjernen-macos.dmg |
| Debian | nordstjernen-debian-amd64.deb |
| Ubuntu | nordstjernen-ubuntu-amd64.deb |
| openSUSE | nordstjernen-opensuse-x86_64.rpm |
| Linux (portable GTK+) | nordstjernen-linux-x86_64.zip |
| Linux (portable, Qt) | nordstjernen-linux-qt-x86_64.zip |
| Alpine (musl) | nordstjernen-alpine-x86_64.apk (apk add) · .zip (portable) |
| FreeBSD (portable) | nordstjernen-freebsd-x86_64.zip |
| NetBSD (portable) | nordstjernen-netbsd-x86_64.zip |
| Java browser + API (JDK 21) | nordstjernen-java.jar (runnable fat jar: java -jar) · sources · javadoc · API docs |
| Source | nordstjernen-src.tar.xz |
Windows 10 or later is required: the GTK 4 frontend links
DirectComposition (dcomp.dll), so the build will not start on Windows 7
(and GTK 4 targets Windows 10 anyway). The Qt frontend has the same
floor — Qt 6 also requires Windows 10 — so there is no older-Windows
build of either frontend.
Java (JVM). A Java binding embeds the engine on the JVM (requires
JDK 21): org.nordstjernen.Nordstjernen drives fetch / parse / layout /
script / render from Java — to RGBA, a BufferedImage, a PNG/PDF file, or
extracted text — through a thin JNI bridge over the C embedding API
(src/libnordstjernen.h). A no-JNI alternative (RemotePage /
RemoteBrowser) drives a separate nordstjernen-renderer process over the
renderer's HTTP/JSON protocol instead, so an engine crash can't take down
the JVM. On top of that sits org.nordstjernen.app.Browser, a standalone
Swing browser app with GTK-shell-style chrome (back / forward / reload /
home / URL bar, a scrollbar, and keyboard shortcuts). The nightly ships a
single runnable fat jar — java -jar nordstjernen-java.jar <url> launches the
browser, and the same jar is the embedding library. See
java/README.md.
sudo apt install build-essential pkg-config meson ninja-build \
libgtk-4-dev libepoxy-dev libcurl4-openssl-dev libssl-dev libuchardet-dev librsvg2-dev \
libpsl-dev libsqlite3-dev libseccomp-dev libwebp-dev
meson setup builddir && meson compile -C builddir
./builddir/src/gtk/nordstjernenlexbor, QuickJS, WAMR, Wuffs and pl_mpeg are vendored in-tree — no
submodules, no downloads. The one exception is the optional local-AI feature: meson setup fetches and builds llama.cpp as a pinned subproject; pass
-Dai=disabled for a fully offline build. Windows, Fedora, openSUSE and
macOS instructions are in
docs/. Keyboard, mouse and touch controls are documented in
docs/Controls.md.
Nordstjernen is a clean-room engine — no upstream browser code. The moving parts:
Vendored in-tree (built from the main tree, no submodules):
| Component | Role |
|---|---|
| lexbor | HTML5 → DOM parser, CSS, and the WHATWG URL module (ns_url_*) |
| QuickJS (quickjs-ng fork) | JavaScript engine — no JIT, browser-side hooks added in-tree |
| WAMR (subset) | WebAssembly interpreter behind the WebAssembly JS API (src/wasm.c) |
| Wuffs v0.4 | Memory-safe image decoding — PNG, GIF, BMP, JPEG (WebP is decoded separately by libwebp) |
| pl_mpeg (MIT) | Single-file MPEG-1 video decoder — inline <video> playback (src/video_decode.c) |
Required system libraries:
| Library | Min version | Role |
|---|---|---|
| GTK 4 | ≥ 4.22.1 on Windows (MSYS2 stock), ≥ 4.14 elsewhere (≥ 4.22 preferred) | UI toolkit, GSK renderer |
| GLib / GModule | (ships with GTK) | core types, dynamic module loading |
| libepoxy | — | OpenGL/ES function dispatch for WebGL (src/webgl.c) |
| Pango | (ships with GTK) | text shaping and layout |
| libcurl | ≥ 7.85 | HTTP/2 networking, HSTS, cookies |
| OpenSSL (libcrypto) | — | WebCrypto (crypto.subtle) — hashing, HMAC, AES, RSA, ECDSA/ECDH, HKDF/PBKDF2 |
| uchardet | — | charset detection for ns_html_decode_body |
| libpsl | — | public-suffix list for cookie scoping |
| SQLite | — | IndexedDB persistent storage |
| librsvg | ≥ 2.46 | SVG rendering / icons |
| libwebp | — | WebP decoding (lossy VP8 + lossless VP8L) |
| libseccomp | — (Linux only) | syscall sandbox; no-op on macOS/Windows |
Optional (auto-detected; feature compiled in when present):
| Library | Enables |
|---|---|
| poppler-glib | inline PDF viewing |
| libavif | AVIF images |
| fontconfig / pangoft2 | extra font discovery backends |
Video. <video> plays inline when the source is MPEG-1 (an
.mpg / .mpeg / .m1v stream). The bytes are decoded entirely
in-tree by pl_mpeg — a
single-file, MIT-licensed MPEG-1 video decoder — inside the sandboxed
renderer process; no external library, no GPU API, and no syscalls
beyond memory are needed, so it runs comfortably under the seccomp
filter. Decoded BGRA frames are advanced off the renderer's existing
animation tick, honouring the autoplay, loop, muted,
width/height, and poster attributes; a click toggles play/pause;
and the HTMLMediaElement events (loadedmetadata, durationchange,
canplay, timeupdate, play, pause, ended) fire on the element
as it plays. Only one video codec is built in, by design — MPEG-1 is
small, patent-free, and decodes in pure portable C.
The MPEG-1 stream's MP2 audio track plays too (unless the element
is muted). The seccomp-sandboxed renderer can't open a sound device,
so audio is handed to the unsandboxed nordstjernen-audio helper:
pl_mpeg decodes the MP2 track to PCM and
miniaudio (MIT / public-domain,
vendored in-tree) plays it. The inline player drives the helper —
open/play/pause/seek/stop ride the renderer→shell render
channel, and looping re-syncs the audio at each wrap.
Other media. Nordstjernen ships no other media codecs. <audio>
and non-MPEG-1 <video> render a poster and a play overlay; clicking
it resolves the source URL inside the sandboxed renderer process and
the UI shell hands it to an external player — mpv, VLC,
celluloid, totem, mplayer or ffplay on Linux, otherwise the
desktop's default handler for the media type (found via GAppInfo, so
Flatpak players work too), the default app via open on macOS, and the
registered handler on Windows. If none is found, a status-bar hint
suggests installing mpv. A media player is therefore a
recommended runtime dependency, not a build dependency: the .deb and
.rpm packages Recommend one (defaulting to mpv) so playback works
out of the box, while source builds need none. The player is launched
from the UI shell, never from the page's untrusted renderer.
Streaming sites (YouTube and friends) drive <video> through MSE/blob:
with no plain file URL. For those, clicking hands the page URL to the
player instead, so mpv/VLC resolve it with
yt-dlp — install yt-dlp alongside the
player to watch them.
Nordstjernen Source License v1.0 — use, modify and redistribute freely, except as a competing browser; each release becomes MIT after ten years. See License.md. Commercial licenses by agreement.
Nordstjernen Source License is inspired by https://fsl.software/
The Functional Source License (FSL) is a Fair Source license that converts to Apache 2.0 or MIT.
Project home: https://nordstjernen.org · Copyright 2026 Andreas Røsdal.


