Skip to content

Releases: mplsllc/macsurf

MacSurf 2.0

Choose a tag to compare

@mplsllc mplsllc released this 11 Jul 19:54

MacSurf 2.0 — Prettier. Faster. Puffin-er.

MacSurf 2.0

A real web browser for Classic Mac OS 9. Real CSS, a modern JavaScript engine, HTTPS handled right on the Mac. No proxy. No second machine. Just your G3 or G4 and the live web. This is the big one, and I could not be more excited to finally put it in your hands.

Here's the headline: if MacSurf ever opened to a blank window on you, that's over. Fixed for good. And 2.0 repairs an affected Mac all by itself the first time you launch it. No cache-clearing, no Virtual Memory dance (that one never actually did anything, we measured it). If you'd given up on MacSurf, this is your invitation to come back.

First, the ask

MacSurf is what I do full-time, and it only keeps moving at this pace if it pays its own way. So if it put your old Mac back online, I'd be grateful for a few dollars. Supporters get the dev logs and a real say in what I build next. And if I never ask, I can't expect any, so here goes:

Huge thank you to Shlooom, Kestral, and Mothra on Patreon, and kilgeist and Turuun on Ko-Fi. You keep the lights on.

Ko-Fi QR code      Patreon QR code

Ko-Fi        ·       Patreon

What's new (and I'm thrilled about all of it)

The blank screen is dead. This one haunted me. Two separate bugs were behind it. MacSurf was deciding whether a chunk of memory was real by checking it against some hardcoded addresses, and those addresses were just wrong on Macs with more RAM, where the program loads higher up. So it threw away perfectly good memory and quietly stopped fetching anything. On top of that, the list it keeps of unreachable servers was getting saved to disk with no expiry, so one bad moment on your network could condemn your home page forever. Both fixed. MacSurf now asks the system where it actually lives, and that dead-server list only lasts the session. I reproduced this on a maxed-out machine and got to watch it go from a blank window to loading two full sites. So good.

A ton more of the HTTPS web works now, and yes, that includes macintoshgarden.org. Lots of servers send their security certificates a little out of order and trust the browser to sort it out. MacSurf's TLS stack used to be too strict about that. Now it sorts them, and a whole family of sites that used to kick you down to plain http open securely on the Mac.

68kmla.org in MacSurf 2.0

Heavy forums load in seconds instead of minutes. Images wait until you actually scroll to them. A busy 68kmla thread with a big attachment used to take anywhere from 20 seconds to two minutes. Now it's a few seconds. Night and day.

The address bar finishes your URLs for you. Start typing a site you've been to and it completes it from your history, with a dropdown of the other matches. Arrow through them or just hit Return.

Real History and Bookmark managers. A proper History window that groups by day and can be cleared (Cmd-H), plus a Bookmark manager with folders you can rename, move, and drag around (Cmd-B).

History manager   Bookmark manager

And it just looks better. New toolbar, a pill-shaped address bar, an animated loading spinner, a real progress bar, a fresh set of buttons, and a new About box. Typing in text fields is snappy again, every letter shows up the instant you press it. Get Info finally reports the version instead of "N/A." Multi-line copy/paste, favicons, and the duplicated Apple menu are all sorted too.

Under the hood it's still native TLS 1.3 through macTLS, modern ES2023 JavaScript through macQJS, built on NetSurf with CodeWarrior and Carbon. On a 233 MHz G3.

The honest part

Still in-progress software, and I'd rather you hear it from me. The really heavy modern stuff (GitHub, YouTube, big React apps) still doesn't render. Tab reaches form fields but not links yet. Tabs, as in several pages in one window, aren't turned on. A handful of sites hit image-decode quirks. If something breaks, open an issue and drop your MacSurf Debug.log in. It genuinely helps, and this release exists because people did exactly that.

Getting it

Grab the .sit below, expand it with StuffIt Expander, and double-click MacSurf. No installer. Runs on a Power Mac G3 or G4 under Mac OS 9.1 to 9.2.2 (CarbonLib 1.5+), 64 MB RAM or so. Already on a Mac OS 9 machine? Pull it straight from http://macsurf.org/, no other computer needed.

home.macsurf.org over native HTTPS

Thank you

To @glossolallia, @CaptainPanic29, and @Azryael on GitHub and @SandwichEnthusiast7 on Reddit: the crash logs and hardware testing you sent are the whole reason the blank-screen bug got found. And to everyone at 68kmla.org, who's tested this thing every single day and been so kind about it. Getting your forums to render, log in, and post a reply from a real Mac OS 9 machine has meant the world.

Now go put a 25-year-old Mac back on the web.

For Gary & Kaija

MacSurf 1.68.2 — macQJS

Choose a tag to compare

@mplsllc mplsllc released this 08 Jul 21:46

MacSurf 1.68.2 "macQJS" — a focused stability patch that closes two ways the browser could leave you at a blank or frozen window on real hardware.

Fixes

  • Blank screen / silent crash on low-contiguous-memory machines (#207 follow-up). 1.68.1's bulletproof allocator only covered QuickJS and the core; the layout libraries (libdom/libcss/libwapcaplet) still called the standard library directly, so under heap fragmentation one could store a NULL DOM node and later write through it to $0000 — silent corruption and a dead blank window, especially with Virtual Memory off. 1.68.2 routes all three layout libraries through macsurf_safe_alloc as well, eliminating the $0000 write at its source. Reporters who could reliably reproduce (VM off / tight RAM) no longer can.
  • Frozen or blank window on image-heavy pages (e.g. avatar-rich forum threads). Reusing a pooled keep-alive HTTPS connection could block the cooperative thread on a full TCP send window, hanging the UI mid-load. The TLS layer is now fully non-blocking with flow control.
  • Version reporting — About box, request User-Agent, and navigator.userAgent all report MacSurf/1.68.2.

Verified on Power Macintosh G3 iMac and G4, Mac OS 9.2.2.

Download MacSurf1682.sit below, or over plain HTTP from http://macsurf.org from your vintage Mac.

MacSurf 1.68.1 — macQJS

Choose a tag to compare

@mplsllc mplsllc released this 08 Jul 16:15

MacSurf 1.68.1 — Stability Patch

A native web browser for Classic Mac OS (PowerPC, Mac OS 9.1–9.2.2). This is a critical stability release patching low-memory vulnerabilities and heap fragmentation on real hardware.

Verified on: Power Macintosh G3 iMac and G4, Mac OS 9.2.2.


Critical Fixes in 1.68.1

This patch release addresses a critical issue where the browser would crash and freeze the entire OS when running low on contiguous memory.

  • Low-Memory Crash Protection (Heap Fragmentation Mitigation — #207):
    On machines with limited RAM (e.g., 256MB G3/G4s), thousands of tiny allocations from NetSurf and QuickJS fragment the heap, causing allocations to fail even when total free memory is high. Instead of returning NULL and letting the browser write to $0000 (which halts the Classic Mac OS instantly due to lacking memory protection), a new bulletproof allocator intercepts all allocations:

    • Safe Allocator Wrappers: macsurf_safe_alloc, macsurf_safe_calloc, and macsurf_safe_realloc are guaranteed to never return NULL to the engines.
    • Clean Panic & Diagnostics: On allocation failure, they capture the exact MaxBlock() and FreeMem() readings, write a fatal log entry to MacSurf Debug.log (flushed immediately), post a native Carbon StandardAlert explaining the heap fragmentation state, and terminate cleanly via ExitToShell() to avoid system corruption.
    • QuickJS Allocator Hook: QuickJS runtime initialization is updated to route allocations through these safe wrappers, preventing internal QuickJS OOM writes to $0000.
    • Disk Cache Recovery: The cached body lookup gracefully falls back to a network fetch under memory starvation rather than panicking.
    • Standard Library Parsing Order Safeguard: Allocator macro overrides in the prefix header are ordered to avoid compiler errors with Metrowerks Standard Library.
  • Version Visibility & Reporting:

    • About Box: Updated the Apple menu's "About MacSurf..." dialog to display MacSurf 1.68.1.
    • User-Agent Reporting: Synchronized request headers to send MacSurf/1.68.1.
    • JavaScript Environment: Updated the engine's navigator.userAgent to report MacSurf/1.68.1 to on-page scripts.

Community Credits

Special thanks to @glossolallia, @CaptainPanic29, and @Azryael for their detailed bug reports, crash logs, and hardware verification work on G3 and G4 systems that made isolating this issue possible.


Downloading & Support

Grab the latest build from the plain-HTTP macsurf.org directly from your vintage Mac.

If MacSurf put your old Mac back online, please consider supporting the project:

MacSurf 1.68 — macQJS

Choose a tag to compare

@mplsllc mplsllc released this 07 Jul 05:35

MacSurf 1.68 — "macQJS"

A native web browser for Classic Mac OS (PowerPC, Mac OS 9.1–9.2.2), with real HTTPS and a modern JavaScript engine — no proxy, no companion machine.

Verified on: Power Macintosh G3 iMac and G4, Mac OS 9.2.2.
The 1.68 number honors 68kmla.org — the community and the site that drove the majority of this cycle's work.


Help keep MacSurf full-time

MacSurf is a full-time project. Bringing the modern, HTTPS-only web back to real Mac OS 9 hardware — native TLS 1.3, a real JavaScript engine, the whole NetSurf rendering pipeline cross-compiled for PowerPC — is a lot of work, and this pace only continues if the project can pay for itself.

If MacSurf put your old Mac back online, please consider chipping in. Every bit genuinely helps, and supporters get the dev logs and a say in what gets built next:

No pressure — but if I don't ask, I can't keep doing this, so I'm asking. Thank you to everyone who already has.


For 68kmla.org — thank you

This release is named 1.68 in honor of 68kmla.org, the 68k/PowerPC Mac community that has been the heart of this cycle's work.

An enormous amount of 1.68 exists because 68kmla exists: it's the site I test against every single day, it's where the toughest real-world rendering, login, and JavaScript problems surfaced, and it's the reason so many of the fixes below are as thorough as they are. More than that, the 68kmla community has been genuinely kind and welcoming to a new, rough-around-the-edges project — patient with the bugs, generous with detailed feedback, and encouraging at every step. That reception is a big part of why MacSurf kept moving forward. So: thank you, 68kmla. Getting your forums to render, log in, and let you post a reply from a real Mac OS 9 machine has been one of the most rewarding targets to build toward.


The headline: MacSurf now runs a real JavaScript engine — macQJS

macQJS

The single biggest change since 1.5 is under the hood: MacSurf's JavaScript engine has been replaced. Duktape (ES5) is gone; MacSurf now runs macQJS — a QuickJS port for Classic Mac OS — which executes modern ES2023 JavaScript natively on a PowerPC running Mac OS 9.

Why this matters:

  • Modern JS runs as-is. The old engine was ES5-only, so MacSurf carried an in-house ES6→ES5 transpiler to pre-chew modern bundles before running them. That transpiler is retired — its async/await rewriting was silently corrupting minified bundles. macQJS runs let/const, arrow functions, classes, template literals, Promises, generators, modern regex, etc. directly.
  • Real sites' real scripts. Pages no longer need their JavaScript dumbed down to run; the actual site bundles execute on-device.
  • Guarded. A per-heap memory cap and an execution deadline keep a runaway or hostile script from hanging or OOMing the Mac.

macQJS is developed as its own project so other Classic Mac OS software can use it too. This was a multi-month migration (fixes482–554) plus a long hardware-verified crash-stability arc (fixes565–577) to make the new engine solid on real hardware.


Showstoppers fixed this cycle

These are the "how was this ever shipped" basics that are now solid:

  • You can see and place a text cursor. Input fields and textareas now draw a real blinking caret, so you can tell where your typing will land.
  • You can select text in fields. Click-and-drag selection works; Cut/Copy/Paste route to the focused field (the clipboard is wired to the Mac Scrap Manager).
  • Logins persist. Signing in to a site now keeps you signed in — the browser was serving a stale, cached, logged-out copy of the page right after login. Fixed.
  • Arrow keys behave. They move the caret in fields and scroll the page — and no longer shove the whole viewport sideways.
  • Tab moves between form fields. Fill out a login or search form with the keyboard.
  • The browser opens almost instantly. A diagnostic self-test was running a heavy JavaScript benchmark battery at every launch — roughly a 17-second freeze before the first page. It's gone; startup is now well under a second.
  • Cookie-consent / sticky bars are clickable. The "Accept" button on sticky overlays (68kmla's cookie banner) now clicks where you'd expect, instead of falling through to the page behind it.
  • Second window no longer closes the first. Window lifecycle is fixed.
  • Maximize (zoom box) works, and "About MacSurf" opens.

Everything new in 1.68, by area

JavaScript engine (macQJS)

  • Replaced Duktape (ES5) with macQJS / QuickJS (ES2023), linked into the build and gated by WITH_QUICKJS.
  • Retired the in-house ES6→ES5 transpiler and the old hand-built JS API surface (they papered over engine gaps and corrupted some bundles).
  • Per-navigation fresh JS realm so a page's top-level let/const/class declarations don't collide with the previous page's on the next load.
  • Memory limit + ~20 s execution deadline guard against runaway scripts.

Site compatibility (68kmla / XenForo)

  • The reply/post editor works again. The engine switch meant XenForo's real preamble/core/editor bundles ran natively and crashed (a parentNode-null → jQuery Sizzle → jQuery.supportXF.Element cascade), collapsing the editor to a bare one-line box. Small ES5 shims are substituted for those three bundles to set has-js, define XF.Element, and reveal + size the editor — the same mechanism that first let a real forum reply post from Mac OS 9.
  • Doomed XenForo feature bundles (lightbox, media gallery, etc.) that only throw on this engine are now skipped rather than parsed-and-thrown, reclaiming page-load time.

Text input & keyboard

  • Blinking text caret in <input> / <textarea> (native place_caret), including password fields.
  • Mouse click-drag text selection in fields.
  • Cut / Copy / Paste to the focused field via the Edit menu and Cmd-keys (Scrap Manager clipboard).
  • Tab / Shift-Tab cycle text fields in document order (works with or without a <form>).
  • Arrow keys, Home/End, Page Up/Down, backspace and forward-delete route to the focused field; when nothing is focused they scroll the page. The old "arrows shove the viewport" bug is fixed.

Chrome & usability

  • Bookmarks menu. Saved bookmarks appear as clickable menu items and persist across relaunch.
  • Downloads manager. HTTPS downloads now work and auto-save to a Downloads folder (server-suggested, HFS-sanitised, de-duplicated filename); a modeless Downloads window shows live byte progress with per-row Cancel. (The old "downloads do nothing" was a modal save dialog returning -5699 from inside the fetch callback.)
  • Window maximize (zoom box) fills the screen and restores exactly.
  • One MacSurfData folder next to the app holds everything MacSurf writes — Cache/ and Downloads/ subfolders, plus bookmarks and the debug log at its root — instead of scattering MacSurf * folders on the boot Desktop. Bookmarks live outside Cache/, so clearing the cache can't delete them. (This also fixes the old bug where the cache and log were hardcoded to the boot volume's Desktop rather than next to the app.)
  • "About MacSurf" Apple-menu item works.

Networking & logins

  • Login sessions persist (the post-login redirect no longer serves a stale logged-out page from cache).
  • Cookie jar persists to disk inside MacSurfData so a login survives relaunch.
  • Per-host User-Agent overrides and the RFC-6265 cookie jar remain wired for real logins.

Rendering & CSS

  • Split-scrollbar / runaway page-width bug fixed (the single biggest render fix this cycle): an unresolved box kept its "born" width of INT_MAX, blowing the document content width up to ~2.1 billion px and producing a narrow content column beside a giant empty canvas with a spurious horizontal scrollbar. XenForo forums (68kmla, tinkerdifferent) now lay out full-width.
  • Downloadable web-font icon glyphs render — Material Design Icons / FontAwesome @font-face icon fonts now paint on Classic Mac OS (a first, as far as we know), via sfnt cmap parsing + QuickDraw region fill.
  • Text word-merging / awkward wrap fixed — inter-word spaces no longer collapse ("softwareand"); font measurement and painting now advance identically.
  • SVG colour regression fixed (inline SVGs were being blended away by the alpha-compositing path).
  • Sticky-overlay hit-testing (see Showstoppers) and a corrected white page-base (light sites no longer show a grey base behind un-styled regions).
  • CSS Grid track limit raised 8 → 16 columns (from 1.5) so modern 12-column grids stop collapsing.

Performance

  • Instant startup — removed a ~17 s diagnostic JS self-test battery that ran synchronously at every launch before the event loop.
  • Image & web-font disk cache — images and downloadable fonts are now cached to disk (bounded 64 MB budget with LRU eviction), so revisiting an image-heavy page — or reloading after Mac OS evicts the RAM cache — no longer re-downloads everything.
  • Prepared-image GWorld cacheplot_bitmap no longer rebuilds a 32-bit GWorld (RGBA→XRGB swap + downscale) on every paint (~2.5 s/paint on a big image); the ready-to-blit buffer is cached, killing scroll jank.
  • Faster box construction — the HTML→box conversion yielded to the event loop every 10 nodes; it now processes 100, cutting scheduler round-trips ~10× on large pages.
  • Deferred per-box DOM lookups in redraw, and a large log-volume reduction (per-element diagnostics suppressed by default).

Stability

  • A long arc of use-after-free and crash-stability fixes to make the new engine solid on real hardware (box-walk lifetime guards, content-registry liven...
Read more

MacSurf 1.4 — Open House

Choose a tag to compare

@mplsllc mplsllc released this 01 Jun 03:40

MacSurf 1.4 — Open House

Released: 2026-06-01
Codename: Open House
Engine HEAD: fixes352 (MacSurf side); macTLS unchanged from v1.3.1
Verified on: Power Macintosh G3 iMac, Mac OS 9.2.2


The headline

MacSurf 1.4 closes the JavaScript marathon. Twenty-three GitHub issues went from open to closed in this release — setTimeout / setInterval / requestAnimationFrame, window.location, window.history, URL + URLSearchParams, element.classList, element.style, Event + CustomEvent + MouseEvent + KeyboardEvent constructors, MutationObserver, DOMParser, FormData, localStorage, fetch, addEventListener for load + DOMContentLoaded, plus <details> / <summary> toggle and the hidden attribute. A purpose-built probe page (mactrove.com/t.html) ran 19 JS probes on a G3 iMac and finished JS 19/19 pass, 0 fail.

Three diagnostic / power-user features land too: about:cache, about:memory, about:config, and about:perf now render real diagnostic pages with live counters. View Source is wired through content_get_source_data + a data:text/html URL so it renders the markup inline. Find-in-page opens a real Carbon dialog with a text input and Find / Cancel buttons, then highlights matches via NetSurf's textsearch.

Underneath that, the data: URL fetcher was a stub returning empty body since launch — fixed in this release. Every data: URL on every page now works, not just View Source.

about:config rendered on a G3 iMac

about:config showing the live nsoption table. Same Geneva chrome and orange-banded table styling as about:cache / about:memory / about:perf. Type any of the four into the URL bar; the new RFC 3986 scheme scanner in fixes351 stops the old strstr("://") heuristic from mangling them to https://about:cache.


What's new

JavaScript marathon

The JS bridge gained twenty-three closed issues in this release. The full list, with the fix-round that closed each one, is below. Each is exercised by the probe suite at mactrove.com/t.html (J0–J17 + J-write + J18 / J19 visual cards).

Probe Issue Closed by
J1 setTimeout / setInterval / clearTimeout / clearInterval #103 fixes321
J2 requestAnimationFrame #117 fixes322-324
J3 window.location (full surface: href + protocol + host + hostname + port + pathname + search + hash + origin + assign + replace) #118 fixes323 + fixes350
J4 window.history (back + forward + go + length + pushState + replaceState + state) #122 fixes324 + fixes350
J5 URL + URLSearchParams #123 fixes325
J6 element.classList.add / remove / toggle / contains #30 fixes326 + fixes349
J7 element.style.<prop> setters #32 fixes327 + fixes349
J8 load + DOMContentLoaded via window.addEventListener #31 fixes328 + fixes350
J9 DOMParser #125 fixes331
J10 FormData #124 fixes332
J11 localStorage (set / get / remove) #46 fixes333
J12 fetch (shim presence) #104 fixes334
J13 Event / CustomEvent / MouseEvent / KeyboardEvent constructors #105 fixes339-340
J14 MutationObserver #105 fixes339-340
J15 element.matches / closest / getBoundingClientRect / contains fixes336-337 + fixes349
J16 window.scrollTo / getComputedStyle / matchMedia / Promise / addEventListener fixes338
J17 document.title / readyState / navigator.userAgent fixes341
J18 <details> / <summary> click-to-toggle #110 fixes351
J19 <span hidden> attribute #114 fixes329

Two structural bugs caught and fixed along the way:

  • fixes349 — IIFE per-element installer. fixes342 had switched the JS init helper to duk_peval_string_noresult (which discards the eval result) but four per-element install sites in macsurf_js_dom.c still expected the function to be left on the stack for the subsequent duk_dup(-2); duk_call(1). Result: TypeError: [object Object] not callable on first use of any element wrapper, abort mid-install, every element lost classList / style / matches / closest / getBoundingClientRect / textContent setter / className setter for the rest of its lifetime. Added a macsurf_js__install_per_element helper that does eval + duk_pcall with proper stack management; refactored the four sites.
  • fixes350 — window-level event dispatch. js_fire_event only invoked the inline window.on<type> handler. Listeners registered via window.addEventListener('load', fn) (parked by fixes338 in window._winListeners) were never dispatched, so load and DOMContentLoaded were unreachable through the standard JS API. Now walks _winListeners[type] and pcalls each callable entry, then runs the inline on<type> handler as before.

Diagnostic + power-user

  • about:cache, about:memory, about:config, about:perf — all four now render. About:perf carries a live counters table fed by a new macsurf__site_reformat_ms global captured in html_reformat; about:config dumps the nsoption table; about:memory and about:cache surface the partition + memory + cache shape.
  • #99 root cause was the URL-bar scheme heuristic. strstr(r, "://") only catches hierarchical schemes (http://, https://, file://, ftp://) — opaque schemes (about:, data:, javascript:, mailto:, resource:) use <scheme>:<opaque> with no //, so about:cache was forced to https://about:cache, nsurl parsed that as host=about path=cache, fetcher 404'd, page went blank. Replaced with a proper RFC 3986 scheme scan (ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) terminated by :). Side effect: also unblocks data:, javascript:, mailto:, file:, resource: from URL-bar typing.

View Source — real, not invented

The old path navigated to view-source:<url> which no fetcher in this codebase recognises (NetSurf core's nsurl.h enumerates only HTTP / HTTPS / FILE / FTP / MAILTO / DATA / OTHER). Navigation silently went nowhere.

The new path uses content_get_source_data to get the raw HTML bytes already in memory, HTML-escapes them into a <pre> block, percent-encodes the whole document into a data:text/html;charset=utf-8,... URL, and navigates to that. NetSurf's html handler renders it inline. Size cap at 32 KB for V1.

The fix sequence shows up clearly in the commit history: fixes352 builds the View Source helper, fixes352a rewrites the broken data: URL fetcher stub (it was returning body="" for everything — fixed here), fixes352b switches text/plaintext/html wrapped in <pre> because MacSurf has no inline text/plain handler.

Find-in-page — real Carbon dialog

The previous "Find" was a StandardAlert reading "future round will install a Find dialog." This release installs the actual dialog.

The dialog is built programmatically with CreateNewWindow + TENew + a ModalDialog-style event loop, so no Rez source changes were needed. The kMovableModalWindowClass path was rejected by CarbonLib (errInvalidWindowAttributesForClass), so the dialog uses kDocumentWindowClass + kWindowCloseBoxAttribute — the same proven pattern as the main browser window. Last search term is cached for future Find Again wiring.

The dialog routes to browser_window_search(bw, NULL, SEARCH_FLAG_FORWARDS, term), which highlights matches via the existing text-redraw path. Important behavioural choice: after the search returns, MacSurf explicitly resets scroll to (0, 0) instead of letting NetSurf core auto-scroll to the first match. The core's scroll-to-match math produces invalid coordinates on MacSurf (e.g., scroll=(571, 335) for a content_width=949 page, stranding the user past the content edge); resetting to top keeps the highlight on the match and lets the user navigate normally.

Other CSS / layout fixes that landed in 1.3.1 → 1.4

  • fixes318 — Repeating gradient + multi-stop fidelity. Closes #145 #147 #148. The shorthand-gradient extractor now preserves background-repeat, handles 3+ color stops cleanly (intermediate stop becomes the visual "second distinct" colour for pinstripe recovery), and skips matches inside repeating-linear-gradient( so the inner linear-gradient substring isn't grabbed by accident.
  • fixes344b — Real alpha-aware gradient stops. Full ARGB stops stored on an outer-struct side channel (macsurf_gradient_full) so RGBA + transparent no longer get truncated to opaque RGB.
  • fixes345 — Radial-gradient size + position prefix. Parses <W>px <H>px at <X>% <Y>%, captures into the radial cascade, plotter uses it.
  • fixes346 — Pinstripe / repeating-pattern recovery. When the source was repeating-linear-gradient(white 0, white 1px, gray 1px, ...) (mactrove's Platinum title-bar) the first == last detection now swaps last for the first distinct intermediate colour so the gradient becomes white→gray instead of white→white solid.
  • fixes347 — Pseudo-element background-image fetch. The element-level fetch at the bottom of box_construct_element was firing for elements but never for pseudos, so gen->background stayed NULL forever. Now per-pseudo html_fetch_object runs from box_construct_generate. Pair with the has_background image-url trigger so the redraw path picks the bg-image as a background source.
  • fixes348 — Alpha-overlay gradient downgrade (closes #158). Platinum pinstripe gradients like linear-gradient(rgba(255,255,255,.5) 1px, transparent 1px) were rendering as harsh black-to-white gradients because the painter ignored alpha. When a stop has alpha < 0xC0 OR the gradient has the transparent-pinstripe shape, the cascade now downgrades to NONE so the underlying background-color shows through. Fully-opaque gradients are untouched.

How to upgrade

This is a **MacSur...

Read more

MacSurf v1.3.1 — Forward, refined

Choose a tag to compare

@mplsllc mplsllc released this 29 May 20:55

Released: 2026-05-29
Verified on: Power Macintosh G3 iMac, Mac OS 9.2.2
Engine HEAD: fixes318 (MacSurf side); macTLS at 2725837 (multi-curve ECDHE)

The headline

Multi-curve ECDHE lands in TLS 1.3. macTLS now offers three curves in supported_groups — X25519, P-256, P-384 — and handles HelloRetryRequest cleanly. That unblocks servers that disallow X25519 (FIPS zones, certain XenForo-on-nginx configs, some Cloudflare strict zones).

68kmla.org/bb/ — one of the largest active Classic Mac communities — now fully renders on a G3 iMac via TLS 1.3 + P-384.

MacSurf 1.3.1 rendering 68kmla.org on a G3 iMac

Full forum index. 2,759 box-tree nodes. 19 images. Zero handshake failures across a browsing session. TLS 1.3 over P-384 via HelloRetryRequest.

What's new

Three ECDHE curves now offered in TLS 1.3 ClientHello:

  • 0x001D X25519 — preferred, all-purpose
  • 0x0017 secp256r1 (NIST P-256)
  • 0x0018 secp384r1 (NIST P-384)

key_share carries an X25519 public key by default. If the server's supported_groups excludes X25519, the server sends HelloRetryRequest naming the curve it actually wants, and macTLS now resends ClientHello exactly once with a fresh key share on the requested curve.

A latent HelloRetryRequest infinite-loop bug (was being handled inline via WantRead) is closed by a clean hrr_pending state transition consumed at the next pump step.

Regression status

X25519-default sites all verified unchanged on host and on G3 hardware:

  • mactrove.com (TLS 1.3, 0x1303 ChaCha20-Poly1305, X25519)
  • google.com (TLS 1.3, X25519)
  • cloudflare.com /cdn-cgi/trace (TLS 1.3, X25519)
  • howsmyssl.com (TLS 1.3, X25519)

MacTLSTest unaffected.

Companion MacSurf fixes

Shipped alongside the macTLS update:

  • fixes317 — universal HTTPS↔HTTP fallback with per-host bounce-loop guard. Whichever scheme the user types is tried first; on failure the other scheme is attempted exactly once; on second failure FETCH_ERROR. HSTS sites whose TLS fails no longer spin in a redirect loop.
  • fixes317a — URL parser repairs single-slash schemes (https:/host/ typo → https://host/) so a slipped keystroke doesn't double-prepend.
  • fixes318 — diagnostic instrumentation (CHUNKDIAG, FCDIAG) for the pending Google Fonts chunked-stall capture.

What's NOT in this release

For honest accounting:

  • Google Fonts (fonts.googleapis.com) occasionally stalls on a chunked + keep-alive response that doesn't self-terminate cleanly. Diagnostic instrumentation shipped; root cause is browser-side fetcher, not macTLS. Capture pending.
  • TLS 1.3 session resumption (PSK / tickets) still deferred.
  • Post-quantum key agreement still deferred.

Credits

Multi-curve ECDHE work landed in macTLS by the dedicated TLS agent. BearSSL by Thomas Pornin provides the cryptographic primitives — the EC curve implementations used here (ec_c25519_m15, ec_p256_m15, ec_prime_i15) have been in BearSSL since its earliest releases.

Full notes

docs/release-notes/MacSurf-1.3.1.md

Building from source

v1.3.1 is a transparent macTLS engine upgrade. No MacSurf project file changes vs v1.3. No new BearSSL files. Just rebuild against the updated macTLS tree.

v1.3 builders pulling onto a v1.2 workspace need to add four macTLS files to enable TLS 1.3: bearssl/src/ec/ec_c25519_m15.c, os9/ostls_tls13_keysched.c, os9/ostls_tls13_record.c, os9/ostls_tls13_handshake.c.

MacSurf v1.3 — Forward

Choose a tag to compare

@mplsllc mplsllc released this 29 May 17:32

Released: 2026-05-29
Verified on: Power Macintosh G3 iMac, Mac OS 9.2.2
Engine HEAD: fixes315 (MacSurf side); macTLS at tls13-v1 (c405117)

macTLS

The headline

MacSurf 1.3 negotiates TLS 1.3 natively on Classic Mac OS. No proxy. No helper machine. The ClientHello, the key schedule, the record layer, and the decrypted application data all happen on a 233 MHz Power Macintosh G3 iMac running Mac OS 9.2.2.

As far as we can find, this is the first native TLS 1.3 implementation on Classic Mac OS, ever. It shipped less than 24 hours after the v1.2 "Sealed" release that closed the entropy hole.

Third-party verification

This isn't an internal claim. Four independent test sites confirm TLS 1.3 negotiation from MacSurf on real hardware.

Akamai tls13.akamai.io

Akamai TLS 1.3

TLS Version: tls1.3 · TLS Cipher Name: TLS_CHACHA20_POLY1305_SHA256 · User Agent: MacSurf/0.2 (Macintosh; PPC Mac OS 9) · "Your client negotiated TLS 1.3, the latest version of the TLS protocol!"

BrowserLeaks browserleaks.com/tls

BrowserLeaks

TLS Protocol: 0x0304 TLS 1.3 · Cipher Suite: 0x1303 TLS_CHACHA20_POLY1305_SHA256 · Key Exchange: 0x001D x25519 · Signature Scheme: 0x0804 rsa_pss_rsae_sha256

How's My SSL howsmyssl.com

howsmyssl

Version: Good — "Your client is using TLS 1.3, the most modern version of the encryption protocol." Forward secrecy, no TLS compression, no insecure cipher suites.

Cloudflare /cdn-cgi/trace

uag=MacSurf/0.2 (Macintosh; PPC Mac OS 9)
tls=TLSv1.3
kex=X25519
sni=plaintext

What's new under the hood

  • Hand-written TLS 1.3 handshake, key schedule, and record layer per RFC 8446, built on BearSSL cryptographic primitives only (BearSSL itself has no 1.3).
  • X25519 key exchange. Cipher suites 0x1303 (CHACHA20-POLY1305) and 0x1301 (AES-128-GCM). SHA-256 transcript hash.
  • Server auth against the 121-anchor Mozilla CCADB bundle (unchanged since v0.6).
  • RFC 8446 + RFC 8448 test vectors pass on host and on-device.
  • Every connection opens a 1.3 ClientHello that also advertises 1.2 suites; falls back to BearSSL's full 1.2 path if the server declines 1.3. Switch is transparent through the macTLS public API.
  • TLS 1.2 sync, async, and session resumption are unchanged.

What's NOT in this release

Documented for honest accounting:

  • TLS 1.3 session resumption (PSK / tickets — the CDN-resumption win)
  • Post-quantum key agreement
  • TLS client certificates
  • 0-RTT early data

Credits

Adapted from Certainly by minorbug (MIT) — C99 / Retro68 → CodeWarrior 8 strict C89. BearSSL by Thomas Pornin (MIT) for the cryptographic primitives.

Full notes

docs/release-notes/MacSurf-1.3.md

Building from source

Same as 1.2 plus four new macTLS files in MacSurf.mcp to enable TLS 1.3:

  • bearssl/src/ec/ec_c25519_m15.c
  • os9/ostls_tls13_keysched.c
  • os9/ostls_tls13_record.c
  • os9/ostls_tls13_handshake.c

No other MacSurf-side changes required; the upgrade is entirely transparent through the macTLS public API.

MacSurf v1.2 — Sealed

Choose a tag to compare

@mplsllc mplsllc released this 29 May 01:16

Released: 2026-05-29
Verified on: Power Macintosh G3 iMac, Mac OS 9.1
Engine HEAD: fixes315

macTLS

The headline

macTLS v0.x ran on a documented insecure-stub entropy source. It was the largest known security limitation in 1.0 — the rotate-XOR placeholder lifted from BearSSL's reference scaffolding, sitting in the spot where a real CSPRNG belongs. 1.2 closes that hole. macTLS v1.0 (macEntropy v1.0) replaces it with a SHA-256 accumulator feeding BearSSL's HMAC-DRBG, fed by OT packet-arrival jitter, mouse and key-press timing from the event loop, high-resolution clock samples, and a cold-start seed file persisted across boots.

This is what "production HTTPS" was supposed to mean. 1.0 was the chrome polish release; 1.2 is the security release.

Three pieces in scope

  1. macTLS v1.0 (macEntropy v1.0) — hardware-validated on G3 across four separate launches with the Stage E statistical self-test: distinct seed fingerprints per run (94A7251B, 52AB2050, 665DF442, 165814CB), the actual per-run-entropy proof. The pre-1.2 binary was using a rotating XOR seeded only from TickCount(); every TLS handshake on that build shared a predictable seed line. Replaced.
  2. POST forms work. Through 1.0 both fetcher API setup functions silently discarded post_urlenc at entry ((void)pu;). Every form POST became a no-op — search forms on results pages, login forms, comment submissions. 1.2 wires the body through; DDG's "search again from the results page" (the canonical #144 repro) now returns fresh results.
  3. A working download manager. Through 1.0 clicking a download link either did nothing visible or rendered the binary as HTML. 1.2 implements all four gui_download_table callbacks: NavPutFile save dialog, FSWrite streaming, MIME-mapped Mac type/creator codes, partial-file cleanup on error, and Content-Disposition: attachment detection in the fetcher so Drupal-style sites (mactrove, macintoshgarden) route through download instead of render.

Issues closed since 1.0

  • #41background-attachment: fixed anchors to viewport in window coords (fixes309)
  • #56white-space: pre-line collapses internal whitespace runs (fixes307)
  • #36 — SVG fill-opacity / stroke-opacity (fixes305 + 305a)
  • #143<img> HTML width/height verified not reproducing (closed during the lead-in)
  • #144 — POST form body wired through both HTTP and HTTPS (fixes312)
  • #149 — Content-Disposition: attachment routes to download (fixes313b)
  • #150 — HTTPS abort + auto-upgrade FALLBACK crash on DDG (fixes315)

Plus the macTLS chain: macEntropy Stages A → C → B → E → v1.0 → D, tracked in the macTLS repo and tagged macentropy-v1.0.

Full notes

docs/release-notes/MacSurf-1.2.md

Building

Same as 1.0. Clone the repo, open browser/netsurf/frontends/macos9/MacSurf.mcp in CodeWarrior 8 Pro (8.3 update) on the Mac side, choose Build. One project-file note for builders pulling 1.2 onto a 1.0 workspace: desktop/download.c needs to be in MacSurf.mcp (introduced as part of fixes313, provides download_context_create and the four download_context_get_* accessors).

MacSurf v1.0 — Showcase

Choose a tag to compare

@mplsllc mplsllc released this 28 May 04:02

MacSurf 1.0 — Showcase

Released: 2026-05-28
Codename: Showcase
Engine HEAD: fixes304
Verified on: Power Macintosh G3 iMac, Mac OS 9.1


The headline

The first full release. Through 0.6 the question was "can this even speak modern TLS." Through 0.7 it was "can it render real sites without falling over." 1.0 is the answer to a different question: "does it actually feel like a real browser." It does.

Three things make this 1.0 and not 0.8:

  1. The image pipeline is correctness-clean. Every rendering oddity that made 0.x feel like a tech demo — the blue tint on certain images, the "faded" mactrove logo, the white halos around toolbar buttons — was the same QuickDraw CopyBits colorizing behavior leaking the port's foreground color into every blit. fixes301j resets fg=black / bg=white before every image transfer; fixes301c marks fully-opaque PNGs as opaque so they take the colour-accurate srcCopy path instead of CopyMask. The result is true colors end to end on screenshots, transparent PNGs, JPEGs, and GIFs.
  2. The chrome looks like a real browser. Toolbar redesign across fixes297–303 produced state-aware icons (disabled back/forward, animated reload, dim home at the home URL), invisible user-pane buttons, matted icon corners (no halos against the platinum toolbar), a razor-sharp 1px URL field, and a 1px #555555 accent line separating chrome from page. Netscape-7-vibe "tool belt" rather than four chrome buttons floating on grey.
  3. A site we can show off on. home.macsurf.org launched alongside the release — a server-rendered portal page (PHP, no JS dependency) showing weather, search, four news feeds, and a "Part of the MPLS LLC Network" widget. Rendered on a G3 iMac running OS 9.1, through macTLS direct, the screenshot below is the new default home and the headline image of the project.

The standards expansion is the other story of the release: 25+ CSS-related issues closed across a single Bundles A-M sprint (fixes272–281), bringing CSS direction (rtl), @media (orientation) / (prefers-color-scheme), grid-auto-flow with dense, place-items / place-content, Logical Properties Level 1, extended cursor mapping, stacked-gradient first-layer rendering, narrow calc() arithmetic, and a repeating-gradient fallback. The CSS coverage count moved meaningfully without a single regression.


Headline screenshot

MacSurf 1.0 rendering home.macsurf.org on G3 iMac, OS 9.1

https://home.macsurf.org/, served via macTLS, rendered with the fixes302–303 toolbar.


What landed (by area)

Image rendering — correctness end to end

  • fixes301j — RGBForeColor(black) + RGBBackColor(white) before every CopyBits / CopyMask. Classic QuickDraw colorizes the transfer with the port's current foreground color. The page had drawn blue link text just before plotting images, leaving the port fg blue; every image was tinted toward that. Diagnosed via a dest-readback probe that caught a black source pixel [255,0,0,0] landing in the composite as [0,0,95,169] — exactly the blue the user was seeing. Fix: two QuickDraw calls before the blit, the colorize becomes the identity transform. Same mechanism behind the long-standing "mactrove faded images" symptom.
  • fixes301c — Opaque PNGs take the srcCopy path. macos9_image.c was unconditionally set_opaque(false) on every decoded PNG, forcing them through CopyMask even when they had no transparent pixels. Now both decoders (macos9_png_decode_target for display-size decode and macos9_png_decode_to_bitmap for natural decode) detect "no pixel has alpha < 8" → has_trans = 0set_opaque(true) → plain CopyBits srcCopy (the path JPEGs use, proven colour-accurate).
  • fixes303 — Halo-free toolbar icons. macos9_decode_png_to_gworld now writes #D6D6D6 (the platinum toolbar grey) into every transparent pixel at decode time. The icon GWorlds carry the grey corner pixels natively; opaque CopyBits srcCopy blits them onto the toolbar without the white rounded-corner outline that previously framed every button.

macintoshgarden.org compatibility

  • fixes300b — nscss_screen_dpi 90 → 96. MacSurf inherited NetSurf's RISC-OS-era default. With css_unit_css2device_px = css_px × device_dpi ÷ 96, every CSS length was rendering at 90/96 = 93.75% of its CSS value. That shrank em-based containers out of sync with HTML width="" attributes and intrinsic image sizes; macintoshgarden's #wrapper{width:59em} resolved to 708px instead of 767px, leaving too little room beside its float:right sidebar, so the main-content <table> (a BFC that can't overlap a float) dropped below it and the page rendered with a blank content column. 96dpi = 1 CSS px : 1 device px (modern browser convention); all pages now render ~6.7% larger.
  • fixes299 — Non-destructive HTTPS auto-upgrade fallback (#140, #141). Bare-domain URL typing now tries HTTPS first and falls back to HTTP if the TLS handshake fails. The mark stays in place for the main page fetch so sub-resource failures don't consume it before the main page itself can fall back. Closes #140 and the auto-upgrade half of #141.
  • fixes304 — URL-bar Enter bypasses the disk cache (one-shot, same flag Reload uses). After editing a page server-side, re-typing the URL would have returned the stale cached HTML — now it always pulls fresh. Link clicks within a page still use the cache.

Browser chrome — toolbar redesign

  • fixes294, fixes295 — Per-site favicons. Phase 0: a static default puffin icon shown in the URL bar. Phase 1: real per-site favicon fetching for PNG and ICO (including PNG-inside-ICO and 32-/24-bit DIB-inside-ICO variants — the formats real-world favicon.ico files actually use).
  • fixes297 — State-aware toolbar icons. Coloured icons when nav is available, greyed when not. Reload becomes an animated "loading" icon during page loads. Home icon dims when already at the home URL. Eight icon assets baked into the binary (24-bit RGBA PNG, decoded via lodepng).
  • fixes298 — Invisible user-pane buttons + Platinum toolbar. Toolbar grew from 22 to 28 px tall, button slots became invisible user-pane controls so the icon paints directly on the platinum toolbar instead of through Carbon's chrome button. PtInRect click handling matches the new geometry.
  • fixes302 — Geometry, image masthead, Geneva text. URL field aligned to the button vertical band on a unified horizontal baseline. Field width sized to leave a native-grey gap above and below. Text in Geneva 12, vertically centred past the favicon so it never touches the left bevel.
  • fixes303 — Tool-belt dense layout. Buttons at a 34-px pitch (2-px gap), no per-button frames, no white halos (matted at decode time). URL field bevel reduced from 2 px sunken to a razor-sharp 1-px inset: #444444 top + left, #FFFFFF bottom + right. 1-px #555555 accent line at y=content_rect.top-1 separates chrome from page.

CSS standards expansion (Bundles A–M, fixes272–281)

A single-sprint standards round closing 25+ CSS issues. By bundle:

  • fixes272 (#35 + #49) — HTML dir attribute → CSS direction. The HTML dir="rtl" attribute wires through to the libcss cascade as direction: rtl, then through to inline-flow direction in layout.
  • fixes273 (#52 + #74 + #76) — at-rules preprocessor + @media (orientation). Single-pass preprocessor that recognises @layer, @supports, @media (orientation: …), @media (prefers-color-scheme: …) and dispatches each to the cascade correctly. prefers-color-scheme: dark matches when explicitly requested via a future user-preference flip; currently always-light by default.
  • fixes274 (#25 + #64) — CSS Grid V2 alignment (justify-items / justify-self consumed in layout_grid track distribution).
  • fixes275 (#65) — grid-auto-flow: row / column / dense. Dense packing finds the first empty cell that fits each auto-placed item, not just the next sequential slot.
  • fixes276 (#55 + #59) — Inline metrics. vertical-align: text-top / text-bottom / super / sub and line-height as a unitless numeric multiplier.
  • fixes277 (#61) — CSS Logical Properties Level 1. inline-size / block-size, padding-inline-* / margin-inline-* / inset-* map to their physical equivalents at the cascade preprocessor stage (LTR only — RTL inline-mapping is a later issue).
  • fixes278 (#79) — Extended cursor mapping in macos9_gw_set_pointer: pointer, text, wait, help, not-allowed, crosshair, move, progress, plus the four *-resize and the four arrow cursors.
  • fixes279 (#27) — Stacked background-image fallback. Comma-separated layered gradients (linear-gradient(a), linear-gradient(b)) now render the first layer instead of dropping the whole property at the trailing comma.
  • fixes280 — Narrow calc() arithmetic preprocessor. Resolves calc(100% - 20px)-style expressions at cascade time for the cases that don't need a full expression tree (length ± length, length ± percentage on a known containing block).
  • fixes281 — repeating-linear-gradient / repeating-radial-gradient fallback. Renders the platinum-grey solid placeholder instead of dropping the whole declaration. Cosmetic placeholder for what would otherwise be a blank area.

Other rendering / chrome

  • fixes291 (#101) — <img> alt-text fallback. When an image content fails to fetch or decode, the layout box paints the alt="" text in the box's text style, with a 1-px dotted border to mark "image here, didn't load." Closes #101.
  • Merge #138 — HTML form required + pattern validation. Patch from a community contributor. Form submit blocks on unmet required fields and unmatched pattern regex; offending inputs get a red 2-px error borde...
Read more

MacSurf v0.7 — Cleanup

Choose a tag to compare

@mplsllc mplsllc released this 26 May 03:03

MacSurf 0.7 — Cleanup

Released: 2026-05-26
Codename: Cleanup
Engine HEAD: fixes271b
Verified on: Power Macintosh G3 iMac, Mac OS 9.1


The headline

Twelve issues closed in a single day. v0.7 is the "resolve all known issues" sprint — a focused correctness + cleanup round on top of v0.6.2's speed work. Nine commits (fixes264 → fixes271b) cleared the entire open-issues backlog on mplsllc/macsurf. Author CSS now reaches more paint paths, image-heavy navigation stays memory-clean, CSS Grid honors author alignment intent, and a long-standing struct-naming workaround got resolved at the root.

Two things stand out from the round:

  1. Element-scoped var() resolution. Author CSS like .header { background-image: var(--tile) } now resolves correctly when --tile is defined inline on a different element's style="..." attribute. Mactrove's --header-tile random-header pattern just works.
  2. CSS Grid V2 alignment with zero libcss surgery. The previous attempt (fixes159) had failed in nine rounds trying to add a new vendor property; turned out libcss already had public accessors for justify-content, align-content, align-items, align-self — just had to consume them.

What landed (by area)

CSS correctness

  • fixes264 — var() resolves element's own inline --custom-prop. Threads the cascading element's inline_style sheet through lookup_var() as highest-priority source. Works for the same-element case (element defines and uses the var).
  • fixes267 — Doc-global inline-extras custom-property table. Aggregates custom_properties from every style="..." attribute parsed by nscss_create_inline_style into a doc-global table consulted by lookup_var(). Covers the cross-element case (parent defines, child uses). Cleared per html_create so per-page state doesn't bleed. Pragmatic V1 — last-writer-wins for name collisions, which covers mactrove's single---header-tile-element pattern.
  • fixes266 — background-image: linear-gradient(...) rewrite. New cssh_css preprocessor pass renames background-image: to -macsurf-gradient: when the value's first non-whitespace token is linear-gradient( or radial-gradient(. Author CSS using standard CSS3 syntax now reaches the existing vendor gradient paint path. Stacked layered backgrounds still drop on the trailing comma (no regression). Wired into both external sheets and inline-style attributes.
  • fixes270 — CSS Grid V2 alignment. layout_grid consumes css_computed_justify_content (inline-axis track distribution: flex-end, center, space-between, space-around, space-evenly) and css_computed_align_content (block-axis distribution when there's a definite container height). Discovery that closed the prior 9-round fixes159 stall: libcss already exposed these accessors — no new vendor property needed. fixes270a preserves the CSS-declared container height through the redistribution so the box paints at full declared size instead of shrinking back to natural content height. align-items / align-self had already shipped at fixes178d; justify-items / justify-self deferred (libcss does not expose those accessors).

Image / memory hygiene

  • fixes265 — Direct image URL navigation scale-fit-to-cap. Typing a large image URL (e.g. https://example.com/photo.jpg) in the address bar pre-fix rendered blank because browser_window passed natural dimensions (4000×3000 → 48 MB RGBA, exceeds the 16 MB per-image ceiling). Pre-clamps dst_w/dst_h proportionally in macos9_qt_image_redraw before the cap-check, preserving aspect ratio. Integer-only math (CW8 PPC long long is unsafe per the well-documented gotcha).
  • fixes268 #10 — Decoded-image purge on top-level nav. New macos9_purge_decoded_images() evicts the QT-deferred LRU and zeroes macsurf__decoded_img_bytes_current. Called from browser_window_content_ready after hlcache_handle_release of the old current_content; llcache_clean(true) purges the low-level cache alongside. Heavy-page → heavy-page navigation now starts with fresh budget headroom. Verified on hardware as img purge: evicted=1769472 remaining=0 on a real nav.
  • fixes269 #8 — Above-fold lazy decode. macos9_qt_image_redraw now early-returns when the image's destination rect lies entirely outside the redraw clip. NetSurf core's box-tree walker prunes most below-fold boxes but occasionally invokes redraw on parents whose bbox intersects the clip while specific children don't. Below-fold images stay undecoded until the user scrolls into them; scroll fires a new redraw with a different clip that triggers the decode.

Diagnostic visibility

  • fixes268 #9 — Total CSS budget by importance. New 384 KB cumulative cap on CSS bytes per page in nscss_process_data. Sheets arriving after the cap is hit short-circuit immediately (document order wins — site's main stylesheet processes fully, vendor tail sheets get truncated). macsurf__site_css_total_bytes reset per page in html_create.
  • fixes268 #11 — Heavy mode + blocker= field. SITE diagnostic line now emits heavy=N blocker=NAME (none / img_budget / css_budget / fetch_slots / fonts) and css_total=N/cap. heavy=1 latches when any skip counter is non-zero; blocker name picked by priority order. Visible at every html_reformat completion.
  • fixes268a — SITE line unsilencer. fixes253 had silenced SITE url=... lines by default as a log-noise reduction. v0.7 requires the SITE line for css_total= and blocker= verification per the issue acceptance criteria, so SITE is back to firing per reformat. Low volume — survivable.

Multicol

  • fixes268 #17 — Multicol balancing via bisection. layout_multicol_inner's per-segment target_height now goes through a new multicol_compute_target_height() helper:
    • column-fill: auto uses the viewport height as target (was identical to balance pre-fix; auto was unimplemented).
    • column-fill: balance starts with max(tallest, ceil(total/count)) heuristic, then bisects downward (6 iterations, 8px convergence) to the smallest target that still fits in count columns. Tighter, more visually balanced output for non-uniform item heights.

Build cleanup

  • fixes271b — box_multicol_data rename + Linux/Mac box.h alignment. The _ns suffix on struct box_multicol_seg_ns and struct box_multicol_dat_ns was a fixes199h workaround for a CW8 redefinition error. Root cause turned out to be that the Mac's box.h had been carrying these struct definitions plus a multicol_data field on struct box since fixes199h, while the Linux box.h had neither. Aligned: added the structs + field to Linux's box.h, removed the duplicates from layout_internal.h, restored standard names (box_multicol_segment, box_multicol_data) in layout.c. Source trees now match across platforms; single source of truth in box.h.

Issues closed (12)

All on mplsllc/macsurf:

# Title Resolution
#2 Image memory pressure on modern image-heavy pages fixes268 #10 (purge on nav) + existing fixes161b/162/259/265 deferred-decode
#3 %u specifier in macsurf_debug_log_writef calls Audit found no remaining hits; already resolved
#4 CSS Grid V2 alignment fixes270 / fixes270a
#5 Wheel-mouse scroll can crash Not reproducible on user's hardware; fixes140/141 defensive disable remains effective
#8 Lazy image decode + above-the-fold bias fixes269
#9 Total CSS budget by importance fixes268
#10 Purge memory cache + decoded-image budget on top-level navigation fixes268
#11 Heavy-site mode + blocker= field in SITE diagnostic line fixes268
#12 fixes161 memory sweep umbrella All constituent issues shipped
#17 Multicol V2 — Balancing fixes268
#18 Header Audit & Build Cleanup (Struct Renaming) fixes271b
#22 var() referencing parent's inline --custom-prop drops fixes267

Plus #23 (background-image gradient drops) and #24 (direct image URL blank) filed and closed for the early-session work (fixes266, fixes265).


Verified on hardware

  • test.html — fixes264/266/267 probes: purple cross-element var() block + four gradient blocks (vertical 2-stop, horizontal 2-stop, vertical 3-stop, radial). All paint correctly post-fixes267.
  • test2.html — fixes268 probes: M1 multicol balance with mixed heights + M2 column-fill: auto in 200px container. Plus fixes267/266 regression checks.
  • test3.html — fixes270 probes GA1–GA7: justify-content (center, flex-end, space-between, space-around, space-evenly), align-content: center (200px container, fixes270a confirmation), align-items: flex-end regression check. All seven probes render correctly.
  • MacSurf Debug.log confirms fixes268 SITE line: heavy=0 blocker=none ... css_total=N/393216 ... per reformat, img purge: evicted=N remaining=0 on real navigation events.

Known limitations carried forward

  • justify-items / justify-self (cell horizontal alignment on grids) — libcss in this vintage does not expose css_computed_justify_items / css_computed_justify_self. Either libcss surgery (the trap zone — see project memory) or a text-align workaround would close the gap. Lower priority since container-level distribution (V1 shipped) covers most layouts.
  • Stacked background-image: linear-gradient(...), linear-gradient(...) — single-gradient case works; multi-layer parser fails on the trailing comma. Same as pre-fix behaviour, no regression.
  • TLS fingerprint blocks (Google / Facebook) — carried from v0.6.x; needs ALPN extension support in macTLS or a TLS 1.3 swap. The persistent dead-host list makes them fast-fail.
  • gap: A B two-value form — single value works (97% of cases), two-value collapses to column-gap=B. Full-fidelity row-gap storage requires a new bit slot in `css_c...
Read more