§ 01
Why a fork, not a shell
The first prototype was an Electron app. It was a good sketch of the design, but Electron is an app framework, not a browser: Chrome extensions, the Web Store, a real tab model and Chrome’s settings would all have had to be rebuilt by hand, and every web page would have lived inside another web page.
So exceeder is Chromium itself: Blink, V8, the sandbox, the tab strip and the extension system, compiled by us. The prototype stayed in the repository as the design reference.
§ 02
How the tree is laid out
The layout borrows from Brave’s: our code never lives only inside a Chromium checkout. The repository holds three things, and one script lays them onto a clean checkout:
- exceeder/: our own C++ and TypeScript, copied in as its own directory. The cognira panel, the setup pages, the importer and the new tab page’s additions live here.
- patches/: one patch per upstream file we change, 65 of them. Small on purpose: most wire something of ours in, or switch a Chrome behaviour off.
- overlay/: files that replace upstream ones outright, like the icons, generated from the brand kit by a script.
bash
python scripts/exceeder.py apply # our tree + overlay + patches + rebrand python scripts/exceeder.py build # the dev build python scripts/exceeder.py update-patches # turn edits in the checkout back into patches
Chrome features we don’t want, like the avatar sign-in pill, the restore bar, the new tab promos and Google’s AI Mode entry points, are switched off from one file of feature defaults rather than patched out where they live. The next Chromium version is then a rebase of a short list, not an archaeology dig.
§ 03
The numbers
| Chromium version | 155.0.8059.85 |
|---|---|
| Patches on upstream files | 65, one file each |
| Steps in a release build | 66,925 |
| Object files | 47,772 |
| chrome.dll, release | 346 MB |
| Installer | 132 MB |
| Machine | 6-core Ryzen 5 7600X, 32 GB RAM |
| Dev rebuild after a UI change | 58 seconds |
The release build is Chrome’s “official” configuration, which turns on ThinLTO: the compiler leaves optimisation half-done, and the linker finishes it across the whole program. That makes a faster browser and a long, hungry link. Compiling took a little over five hours on this machine.
§ 04
The link that ran out of memory
With every file compiled, the build reached the link of chrome.dll. Chromium tells the linker to run its ThinLTO work on all of the machine’s threads: twelve here, each holding its own slice of the program in memory. The PC ran critically low on memory and the build was stopped.
The obvious fixes were worse. The setting is hard-coded in Chromium’s build files, and changing it changes the link command of every program in the build, so dozens of build tools relink and everything they generate is treated as new. The linker’s _LINK_ environment variable looked like an escape hatch, but lld puts its contents before the command line, so the build’s own setting still wins. We checked: the output name from the command line beat the one from the variable.
The fix was to tell the linker it had fewer CPUs. LLVM’s “all” counts the CPUs the process is allowed to run on, and child processes inherit that. On a test link the linker peaked at 18 threads with every CPU allowed, 12 with six, and 8 with two. So the restarted build ran pinned to one logical CPU per physical core:
python
# One logical CPU per core on a 6-core, 12-thread Ryzen: 0b010101010101. kernel32.SetProcessAffinityMask(kernel32.GetCurrentProcess(), 0x555)
Six ThinLTO jobs instead of twelve, and no build command changed. The linker also keeps a cache of the work it has finished, so the restart picked up where the stopped link left off: the whole build finished in 11 minutes.
§ 05
A bug in our own tooling
The first release build showed two headers on the cognira panel, though the change that removed one was already in the repository. Our apply script copied changed files with their original timestamps. The release build had compiled the old version of those files after we edited them, so the copies looked older than the compiled objects, and the build kept the stale ones.
Now a copied file gets the current time, and an unchanged one keeps its timestamp, so builds stay incremental. The bug lasted one build.
§ 06
Day to day
Day-to-day work happens in a component build, which splits Chromium into many small libraries so a change relinks quickly. A small UI change rebuilds in about a minute: 58 seconds for the change that gave the panel its single header and the Ctrl Shift . shortcut.
The setup pages and the cognira panel are TypeScript, and a small preview tool compiles them with Chromium’s own TypeScript settings against a fake browser, then screenshots them in a few seconds. Most design work never waits for a build at all.
What exceeder does for you is on its page, and what is still missing is in the launch post.