Northstar Browser 1.0.6
Northstar is a web browser, written from scratch in C, focused on supporting the HTML and CSS standards.
The browser runs on Linux, macOS and Windows.
Northstar is free software, licensed under the GNU General Public License, version 3 or later.
Also see https://github.com/nordstjernen-web/nordstjernen which is based on Northstar Browser.
Northstar 1.0.6
A minimalist web browser written from scratch in C, with no upstream
browser engine. This release is mostly about running real sites: the
layout, CSS and DOM gaps that kept pages like lichess, chess.com, bbc.com
and Hacker News from rendering correctly are closed, and the text and
cascade paths were rebuilt around caches that make a page load
substantially faster.
HIGHLIGHTS
- Text is laid out through ns-pango, a Pango fork carried as a subproject,
which adds the process-wide shaping cache Pango has no place for. Layout
time falls 25% on a text-heavy page and 38% on a table-heavy one.
Shaping results are unchanged and verified byte-identical across a
corpus covering RTL, bidi, CJK, small-caps and font features. - The CSS cascade indexes :is() and :where() subjects and stops allocating
per selector test. On a page built to exercise this the initial cascade
falls 52% and the whole headless run 16%; the css and dom test subset
gains 826 subtest passes from tests that previously ran out of time. - Grid is largely rebuilt: named lines, min-content and max-content
tracks, tracks in any length unit, correct intrinsic widths for a grid
container, and area-placed items that honour align-items. - Web Audio actually renders. OfflineAudioContext used to resolve a buffer
of zeros; oscillators, gain, compressor, biquad, delay, wave shaping and
buffer playback are now implemented in the engine. - Relative colour syntax, the color() function, and one shared scanner for
all six colour functions.
LAYOUT AND CSS
- Grid items can be placed on named lines; a line named in the track list
used to be dropped, so grid-column: main resolved to nothing. - min-content and max-content grid tracks size to their content instead of
stretching like auto. - A grid track can be measured in any length unit. Unreadable tracks were
silently dropped, shifting every remaining track one place left. - A grid container's min-content and max-content widths are the sum of its
columns, not the width of its widest item. - A grid item placed by area name is aligned to its row and stretches by
default. - A single flex line is as tall as the container says, rather than growing
to the tallest item. - align-items: baseline and align-self: baseline work instead of falling
through to flex-start. - An inline-block, inline-flex or inline-grid sits on the text baseline by
the baseline of its own last line box, as CSS 2.1 requires. - Flex and grid items measure intrinsic sizes in the font they will
actually be drawn in, so web-font text no longer wraps mid-phrase. - An absolutely positioned box with width: auto gets a real shrink-to-fit
width, measured by shaping the text rather than guessed at 0.65em per
character. - A percentage inside min(), max() and clamp() resolves against the box
rather than the viewport. - border-radius: 50% paints a circle. Percentage radii were treated as
pixels and calc() radii dropped; elliptical corners now work. - Relative colour syntax is implemented, including calc() over a channel
keyword, and the color() function parses across nine colour spaces. - One scanner now serves all six colour functions, rejecting the malformed
forms that used to parse and accepting colours split across lines. - CSS tokenization closes an open function at end of input, so setting an
unterminated value no longer swallows the rest of an inline style. - Table cells default to vertical-align: middle, as every browser's
user-agent sheet does. - An image is drawn inside its own borders, padding and margin instead of
from the margin-box origin. - An SVG with a viewBox but no width or height is fitted to the 300x150
default object size instead of being rasterised square and stretched. - Dynamic :has() selectors update after class, attribute and child-list
mutations; the selector-invalidation test subset gains 311 subtests. - hypot() returns a length, overflowing integer calculations clamp, large
numbers no longer serialize in exponent notation, and 3D transforms keep
their Z component.
DOM, CSSOM AND JAVASCRIPT
- getComputedStyle resolves every property it enumerates. It listed 218
properties and returned the empty string for 126 of them. - document.styleSheets includes linked sheets with their href and rules.
One test page went from 13 reachable rules to 1895. - The DOM insertion methods run the insertion steps, so a script inserted
through append, before, replaceWith and friends executes and a custom
element gets its connectedCallback. - AbortSignal is a real interface object, so signal instanceof AbortSignal
answers instead of throwing. MessagePort gains the same in the window. - Event-listener objects follow the Web IDL callback-interface algorithm.
- Checkbox and radio activation follows HTML's pre-activation and
canceled-activation steps; three focused tests move from 52/80 to 80/80. - CSSStyleSheet.insertRule() and deleteRule() enforce their arguments, and
addRule()/removeRule() work; that interface test moves from 8/17 to 17/17. - Computed position values are normalized, and transform-origin and
perspective-origin serialize as lengths. - Shorthands through the CSSOM: overflow, gap, grid-area and the
four-value shorthands no longer silently drop the declaration. - A dialog opened from script renders, and a modal one is centred.
- A finished keyframe animation keeps the value animation-fill-mode says
it should, so an opacity: 0 plus fade-forwards no longer stays invisible. - A mouse or pointer event carries the window it was dispatched in;
UIEvent.view used to be null everywhere. - A positioned box answers the pointer in the layer it paints in, so a
fixed high z-index overlay is clickable. - An that paints nothing hands the pointer to what is under it.
- An SVG font-size attribute survives the cascade.
- Instantiating a module through the WebAssembly JS API runs the start
section and nothing else, which is what Emscripten modules expect. - OfflineAudioContext renders audio instead of silence, with a real node
graph behind it. - Workers get the APIs the polyfills already implement; thirteen globals
differed between a worker and the window. A worker also shares the
storage partition of the page that started it, so IndexedDB works there.
NETWORKING
- A page on an origin that does not speak QUIC no longer stalls for the
whole connect timeout. Requests ask for HTTP/2 and are upgraded to
HTTP/3 through the alt-svc cache. Acid3 loads in 2.2 seconds instead of
15.5. NS_FORCE_HTTP3=1 still asks for HTTP/3 outright.
PERFORMANCE
- Text shaping is cached a word at a time rather than a run at a time, and
a paragraph's items are cached too. Over a 200 KB page, shape-cache
misses fall from 5265 to 155, HarfBuzz drops from 28.6% of the process
to 1.5%, and layout goes from 159 ms to 112 ms. - Line, word and sentence breaks are cached. The whole headless page load
runs 5.75% fewer instructions; the text engine alone 20% fewer. - The text caches are sharded sixteen ways, taking four-thread throughput
from about 79 000 to about 98 000 layouts/s. - An IndexedDB write no longer walks the origin's whole storage directory
on every put. - The font metrics behind the ex, ch, cap and ic units are measured once
per font rather than once per length.
USER INTERFACE
- prefers-color-scheme reports what the desktop is actually wearing, and
the internal pages gain the dark half they never had. - An error page appears whenever a navigation has nothing to render, not
only for https:// failures. - The status line clears when the pointer leaves a link and when a load
finishes, and floats over the page only while it has something to say. - The toolbar reports the page zoom, with steps at 90, 100, 110, 125, 150.
- Back and forward are green, reload is blue, and a red stop button
appears while a page is loading. - The bookmark button shows whether the page is bookmarked; Escape closes
the find bar; the title bar no longer doubles the product name. - Escape in the address bar restores the URL of the page on screen.
- The toolbar menu covers New Window, Zoom In, Zoom Out, Reset Zoom, Full
Screen, History and the two save entries, each naming its shortcut. - The title bar is shorter, giving the page the height back.
- A single-line text field shows as many characters as its box has room
for, not as many as its size attribute says. - A #fragment stays anchored while the page finishes loading.
- A popover menu takes the height its items need.
- about:northstar lists the user agent as a request actually resolves it.
- The about:start splash is redrawn for the release.
DOCUMENTATION
- 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.
KNOWN LIMITATIONS
- Web Audio renders mono, and AudioParam automation 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.
DOWNLOADS
northstar-1.0.6-windows-x86_64.zip -- Windows x64, self-contained.
Extract anywhere and run northstar.exe; no installer and no MSYS2
required. 57 MB compressed, 156 MB unpacked.
On Windows 11 you need to right click the .exe file, select properties, and choose "Unblock" or similar.
Windows marks anything downloaded from the internet as blocked, so before extracting, right-click the .zip file, choose Properties, tick "Unblock" at the bottom of the General tab and press OK — doing it on the archive first saves having to repeat it on northstar.exe afterwards, since the mark is copied to every file extracted from a blocked archive.
SHA-256:
c626532e7dc85c8e51ef5aebbc7f26308a3fcd616e81952956e75e30d847a315
- 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.
KNOWN LIMITATIONS
- Web Audio renders mono, and AudioParam automation 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.