Northstar Browser 1.0.7 released!
Today, 8 August 2026, we are pleased to announce the release of Northstar 1.0.7. Northstar is the free-software edition of the Nordstjernen project: a single-window, single-page, single-process desktop browser built from the same clean-room C engine, licensed under the GNU General Public License, version 3 or later. It runs on Linux, macOS and Windows.
This release teaches the browser to print, rebuilds the about:start sky and earth, and closes the layout and framing gaps that kept WebAssembly-driven applications from running at all — frames can now reach their parent, each framed document carries its own Content-Security-Policy, and fetch() resolves with a real Response.
Northstar Browser version 1.0.7 is available now! Read the full release notes here, or jump straight to the downloads. This release adds Linux binaries — a portable zip, an AppImage and a Debian package — alongside the Windows build.
It ships alongside Nordstjernen 1.0.23, the source-available flagship built from the same engine.
« About the Northstar Browser · Back to nordstjernen.org
about:start page — the splash rebuilt for the release, with a scattering atmosphere and a settled earth below it. more screenshots »highlights
- The browser prints.
Ctrl+P, or Print… in the menu, lays the page out for paper and hands the sheets to the operating system’s own print dialog throughGtkPrintOperation— CUPS on Linux, the Win32 printer dialog on Windows, the Cocoa panel on macOS — so no printing code is written per platform and no new dependency is added.--dump=print:FILErenders the same pagination to a multi-page PDF without a printer. - A new sky and a settled earth. The
about:startsplash carries the release number and is redrawn from the ground up — see the splash below. - Layout that real harnesses measure.
offsetLeftandoffsetTopare measured from the offsetParent again, and an absolutely positioned flex child takes the static position the flexbox specification gives it. Together those two tookcss/css-flexboxfrom 653 to 1437 of the same 3535 subtests — 18.5% to 40.7%. - Frames work. A message a frame posts to its parent reaches the parent’s listeners, a framed document is governed by its own Content-Security-Policy rather than the page framing it,
fetch()resolves with a realResponse, andwindowinside a frame is aWindow. Between them these are what a Rust/WebAssembly application needs to start at all. - CSS Scroll Snap, and quickjs-ng v0.16.1 with the ES-module regression it exposed fixed.
the splash
The sky was a two-stop vertical ramp drawn as one filled rectangle per scanline, which is both the slowest way to write a gradient and the one that bands worst. It is now a multi-stop atmosphere rendered as an array, with forward scattering around the sun, a haze band along the horizon and a scatter of stars that fades out as the sky brightens towards it. The north star hangs in the dark of the zenith, cool against the warm sun on the other side of the frame, and twinkles on its own period so the two never pulse together.
The earth had been a soft blur: its terrain came from one 1024² noise field sampled through three fixed mip taps, so coastlines dissolved and nothing on land read as relief. It is built at 2048² now from a continent field, a coastline field and ridged noise for mountain ranges, shaded from the height gradient, and sampled with a real trilinear filter across the whole mip chain — so there are coastlines with a shallow shelf inside them, snow that sits on the peaks rather than across whole regions, and deserts and forests that follow a moisture field. Aerial perspective now decays with distance the way it should, which had been inverted.
The land is settled, in the manner of a turn-based strategy map: capitals are scattered across it at a minimum separation and the ground divided between them by a Voronoi partition whose distances are warped by noise, so frontiers wander the way drawn ones do rather than meeting at straight bisectors. Each nation takes the lowest colour none of its neighbours holds, and wears it as a wash over its territory, a bolder line along its frontiers and a lighter one down its coast, with a capital and up to four towns marked inside. It rides as its own premultiplied overlay above the terrain and fades out with distance, so the markers do not turn into confetti where the texture repeats near the horizon, and passing cloud covers them the way cloud covers everything else.
layout and CSS
offsetLeftandoffsetTopreturned a document coordinate — and one built from the margin box rather than the border box — so an element inside any positioned ancestor reported where it sat on the page instead of where it sat in its parent. CSSOM View asks for the distance from the offsetParent’s padding edge, with a statically positionedbodyor root the exception every engine makes, and that is what they return now. ThecheckLayoutharness most of WPT’s layout tests are written against compares exactly those, so every layout area measured before this fix understates the engine.- An absolutely positioned child of a flex container landed at the container’s content-box origin whatever the container asked for. CSS Flexbox 4.1 says its static position comes from
justify-contentand its ownalign-self, as though it were the only flex item, andflex-direction: *-reverse,flex-wrap: wrap-reverseanddirection: rtleach turn the axis they govern around. Vertical writing modes are not covered — flex layout itself is horizontal-only here. flex-wrap: wrap-reverseputs the first line last. Lines wrapped, but the cross axis was never turned around, so the first line stayed at the top andalign-content: flex-startstayed at the top with it. Lines are now mirrored within the container afteralign-contenthas placed them, and each item within its line.- CSS Scroll Snap.
scroll-snap-typeon a scroll container, withscroll-snap-alignon the things inside it, moves the container onto the nearest snap position once a scroll lands — from the wheel, and fromscrollTop/scrollLeft.scroll-paddingandscroll-margininset the snapport and outset the snap area, both as shorthands and per side;mandatoryalways snaps,proximityonly from within half a page. This is scroll containers only:scroll-snap-typeonhtmlorbodydoes nothing yet. - A translucent background on an inline box is translucent. Inline runs are painted through ns-pango attributes, and while a text colour carrying alpha already emitted a matching foreground-alpha attribute, a background colour emitted only the opaque three-channel one — so
code { background: rgb(0 0 0 / 14%) }painted a solid slab. The same colour on a block, an inline-block or a flex item was correct, which is why this survived: the wrong path is the one a dark theme’s inline chips take.
printing
The engine gets the parts of CSS that a printer needs. @media print now matches — the media type was hardcoded to screen, so a page’s print stylesheet was simply ignored. @page sets the sheet size from a name (A4, letter, legal, ledger, the A and B series), from one or two lengths, or from portrait/landscape, along with its margins. break-before, break-after and break-inside — with the legacy page-break-* spellings mapping onto them and always becoming page — decide where a sheet may end.
A sheet is cut at a forced break if there is one before the page is full; otherwise the cut is pulled up above any box it would have split, which is every leaf box, every line of a paragraph, and anything asking for break-inside: avoid. Printing restores the on-screen layout afterwards, so the page a reader is looking at does not reflow under them.
frames, JavaScript and the web platform
- A message a frame posts to its parent reaches the parent’s listeners. Delivery decided whether the recipient was the top window by comparing it against whichever realm was executing — and while a frame’s script runs, that is the frame’s own realm, so a message correctly addressed to the parent was judged to belong to some other window and handed to a dispatch path where the top window’s
messagelisteners are not registered. The message was built, cloned, queued, and then went nowhere. The comparison is now against the main realm, which does not move. - A framed document is governed by its own Content-Security-Policy. A policy was kept once per browser, so whatever header arrived last decided what every document was allowed to load. Each framed document now carries the policy from its own response and
<meta>, and a resource is judged against the policy of the document holding it — which also survives an application adding a stylesheet from a timer long after the frame finished loading. fetch()resolves with a realResponse. The object had the right properties and methods but not the prototype, soinstanceof Responsewas false andconstructor.namewasObject. Code that branches on the type rather than duck-typing took the wrong path: wasm-bindgen’s loader treats a non-Responseas an already-compiled module and hands it straight toWebAssembly.instantiate.windowinside a frame is aWindow. Frames get their own realm whose global was left an ordinary object, sowindow instanceof Windowwas false there while it was true at the top level — andweb_sys::window()makes exactly that test, so every Rust and WebAssembly UI framework hit it.- A page’s ES modules load again. Reading
document.implementationbeforeDOMImplementationexisted on the global left a TypeError pending on the context. Nothing noticed until quickjs-ng v0.16.1 began reporting a pending exception at the next module boundary, at which point the stray error surfaced as the rejection of everyimporton the page — static, dynamic,data:andblob:alike. - quickjs-ng v0.16.1, up from v0.15.1. The two patches the tree carries — the Windows link fix and the removal of the identical-object shortcut in
Array.prototype.sort, which skipped a comparator the specification says must run — still apply and were regenerated against the new sources. The bytecode cache carries a new format stamp, so entries written by an earlier build are discarded outright rather than costing a failed read on every load until they age out. --versionprints the version and exits. The flag was never recognised, so it fell through to an ordinary startup: the browser armed the watchdog, opened a window and left the caller with exit 255 and no version anywhere. It is now answered before the sandbox, the watchdog and GTK are touched.
text layout
Text layout is ns-pango 2f975d8, and a paragraph now measures the same whether or not another paragraph shaped its words first. The shaping cache decided where a run could be cut by reasoning about Unicode — a space either side of the boundary, or an ideograph — but Unicode does not know what a font does, and Liberation Sans and Liberation Serif, which fontconfig hands out for Arial, Helvetica and Times New Roman, kern the space against the letter that follows it and put the adjustment on the space. A cached piece ending in one therefore carried a width that belonged to whatever word had followed it that time, and served it in front of another: “Type of” came out 1024 units narrow once “Type A” had been laid out, and which paragraph was wrong depended on what the process had rendered earlier.
HarfBuzz answers this directly — asked for unsafe-to-concat flags it marks the clusters whose glyphs depend on the text beyond them — so a piece is now stored only when the shaper cleared both of its cuts, and an item whose pieces do not all survive that is stored whole instead. Verified by rendering the 45 pages in data/render-tests/ with the cache on and with it off: identical layout on every one.
documentation and known limitations
docs/compliance.md records where the engine stands against the HTML and CSS specifications, how to reproduce the web-platform-tests scores, and the known structural gaps. docs/building.md gains a section on how a dependency is actually resolved and how to move a pin — lexbor and quickjs-ng take a system copy whenever the build finds one new enough and only fall back to the wrap, while ns-pango is always the subproject, because the renamed symbols are the whole reason it can sit beside the system Pango that GTK loads.
- Web Audio renders mono, and
AudioParamautomation curves are ignored — a parameter reads as its current value for the whole render. - The stop button ends the loading state and marks the frame stale, but does not abort the network request behind it.
<video>plays MPEG-1 only. There is no MPEG-4 or H.264 decoder, and no Media Source Extensions, so streaming video sites do not play.- Flex layout is horizontal-only: vertical writing modes are not covered, and
css/css-flexboxremains the weakest layout area even after this release’s jump.
about Northstar
Free software. Northstar is licensed under the GNU GPL, version 3 or later — use it, read it, change it, share it.
One process, one page. All rendering happens in a single compact process — about 160,000 lines of original C, excluding the vendored WAMR, Wuffs and audio decoders — prioritizing auditability. On Linux it runs behind a Landlock filesystem sandbox (plus PR_SET_NO_NEW_PRIVS) with a default-deny seccomp syscall filter. No JIT.
Standards-first. The same engine lineage as Nordstjernen: lexbor v3.0.0 for HTML/CSS, quickjs-ng v0.16.1 for JavaScript, ns-pango for text, WAMR for WebAssembly, Wuffs for image decoding. Modern CSS (flex, grid, transforms, gradients, keyframes, scroll snap), Shadow DOM, custom elements, the Navigation API, service workers and WebExtensions. Behaviour is measured against the spec text, section by section.
What it leaves out. Compared with Nordstjernen: no multi-window browsing, no per-tab renderer processes, no WebGL or WebGPU, no embedded PDF viewer. Audio playback (MP3, MP2, Ogg Opus/Vorbis) is included, and video is MPEG-1 only — an ISO standard whose patents have expired, so it costs no dependency and no licence, but also not a format the modern web serves.
It sends no telemetry and no update pings, and includes no AI-style web APIs.
Read the full Northstar documentation, or see the comparison of Nordstjernen and Northstar on the home page.
news
8 AUG 2026 · Northstar 1.0.7 released — printing, a rebuilt about:start sky and earth, CSS Scroll Snap, frames that talk to their parent, and quickjs-ng 0.16.1. Release notes »
8 AUG 2026 ·
Nordstjernen 1.0.23 released — printing with @page and print stylesheets, correct offsetLeft/offsetTop, CSS Scroll Snap, and quickjs-ng 0.16.1.
Announcement » ·
Release notes »
31 JUL 2026 · Northstar 1.0.6 released — a rebuilt grid, an indexed CSS cascade, faster text through ns-pango, Web Audio that renders, and relative colour syntax. Announcement » · Release notes »
31 JUL 2026 · Nordstjernen 1.0.22 released — real-site layout fixes, faster text and regular expressions, and a much more capable Android app. Announcement » · Release notes »
28 JUL 2026 · Northstar 1.0.5 released — in-engine SVG, WebP/APNG, MPEG-1 video playback, and much better HTML, CSS and JavaScript compatibility. Announcement » · Release notes »
23 JUL 2026 · Northstar 1.0.4 released — a maintenance release. Release notes »
22 JUL 2026 · Northstar 1.0.3 released — Media Queries Level 4, CSSOM used values, and use-after-free fixes. Announcement » · Release notes »
download
Latest tagged release: Northstar 1.0.7 — released 8 August 2026. full release notes »
| Windows | northstar-1.0.7-windows-x86_64.zip — self-contained, no installer |
|---|---|
| Linux (portable) | northstar-1.0.7-linux-x86_64.zip |
| Linux (AppImage) | northstar-1.0.7-x86_64.AppImage |
| Debian / Ubuntu | northstar_1.0.7_amd64.deb |
| macOS | build from source with meson — see the build instructions |
| Source | 1.0.7.tar.gz · 1.0.7.zip |
| All releases | github.com/nordstjernen-web/northstar-browser/releases |
The Windows build needs no installer and no MSYS2 — extract it anywhere and run
northstar.exe. Windows marks anything downloaded from the internet as
blocked, so before extracting, right-click the .zip, choose Properties,
tick Unblock at the bottom of the General tab and press OK. Doing it on the
archive first saves repeating it on northstar.exe, since the mark is
copied to every file extracted from a blocked archive.
On macOS, build from source with meson — see the README or the build instructions.