This fork has two branches:
mainhas the performance changes plus some idiosyncratic ones I wanted. This is the one to install.perfhas only the performance changes, on top of upstream.
Warning
No human has read a line of this extra code. AI coding agents wrote the code and the tests afte I said "make it fast" again and again 100 times. Install it at your own risk.
I have been frustrated with how slow Dark Reader gets for a long time. Especially when you have lots of tabs open, and/or after it has accumulated a lot of cruft in its settings (cannot tell which). It used to take seconds to turn on and off. I uninstalled it, but Brave's native "make everything dark all the time" setting is no good either. Now LLMs are good enough that I can just tell them to fork and yap "make it fast make it faster no you can do better even faster" and it seems to actually work.
One of the software big think chaps said that this is the age of of 'custom software', I believe. Here's an example.
Each mark is the median of three page loads, logged out, so Facebook is its login page. Script time is how long Dark Reader's own code ran. Longest block is the most Dark Reader time inside one long task, while the page could not respond to input. On the GitHub issue page, stock never got past its plain grey loading cover, so there is nothing to compare.
Both flame graphs share one time scale. On a GitHub repository page, most of stock's time goes down one path: every CSS variable update rebuilt and re-inserted the whole rule that held it.
- Updating a CSS variable rewrote and reparsed the whole rule that held it. Only the changed declarations are updated now (0c12aa6e).
- Colour parsing and image analysis drew on a DOM canvas, whose first draw cost 130–150 ms on any page with
oklch(),lab()orcolor-mix()colours. AnOffscreenCanvastakes under 1 ms (edd795a4). - Each SVG with a
fillattribute was measured and then styled in turn, so every measurement forced a fresh layout. The measurements are now taken in one batch before any style is written (43bafa63). - The check for a site's own dark theme forces full style and layout passes, up to a second on a large shopping page, and it ran again on every theme message. It now runs once per page, and on GitHub it reads the theme attributes instead (0b4f5c45, 1b9b75f2).
- A background tab was not themed until it was shown, so switching to a heavy tab ran the whole pass as one long task, about 2.4 s on a heavy social page. Tabs are now themed while they load, and only later re-theming waits until they are shown (aef521ed, cfcdf684).
- Long stylesheet and variable scans now yield to the page between chunks (e9eeb74c).
- Every frame load or unload in any tab rewrote the state of all open tabs to storage, and the write came back as a change event that read it all again. With 200 open tabs, one page load wrote 0.75 MB and cost the service worker 36–41 ms. Each tab now has its own key in session storage, so a page load writes 0.01 MB and costs 3–7 ms. Alt+D reaches the active tab before background tabs, which halves its delay when many tabs share a site (446db9be).
- The image cache list in session storage gained a duplicate entry for every image analysed (e519a870).
- Dark Reader's own inserted styles counted as moved page styles, so the first batch of page changes re-rendered every managed stylesheet: all 26 on a GitHub repository page. Its own styles are now ignored in that check (75dd535b).
- No calls to darkreader.org: no news poll, install or uninstall pages, donation banners, or download of remote site fixes. Site fixes come from the source tree and update when upstream is merged.
- Alt+D switches Dark Reader for the current site, PDFs included, and remembers the choice. Alt+Shift+D switches the extension off and on everywhere.
- The theme uses a grey background, #1f1f1f, with #f0f0f0 text, contrast +10 and brightness +5, instead of near-black.
- The toolbar icon is faded on tabs Dark Reader is not darkening.
- Sites with their own dark theme are recognised more reliably and left alone. The check waits until the page has loaded and its tab is shown, pauses the page's transitions while it samples colours, and no longer takes a bright button for a light background. In a sample of 202 recently shared pages, 11 already-dark pages that stock darkens again are now left alone, and no light page lost its theme.
- Pale tints such as cream backgrounds stay near-neutral instead of turning brown.
- Settings sync through the browser by default.
- Any element with a
bgcolorattribute threw an error and stayed light, a regression from upstream 404e8cfe. Style sheets also win over the attribute again, as they do in browsers. - A page whose first message to the extension was lost, for example while the service worker restarted, stayed on the plain grey cover until refreshed.
- Every GitHub page logged an uncaught error from the extension, because GitHub's inline logo SVG has no
xmlnsand cannot load as an image. - After the extension reloaded, scripts left in open tabs threw "Extension context invalidated".
- PDFs in Chromium's current viewer could stay white.
- Every extension page logged "Unrecognized Content-Security-Policy directive 'navigate-to'".
- Some CSS variables were never typed as colours, so backgrounds that use them were dropped, on GitHub and YouTube among others (566fc168, 8c4a61be).
There is no store listing. Build the extension and load it unpacked:
npm install
npm run build
This writes one folder, build/release/darkreader-fast. Brave and Chrome on macOS and Windows only enable a packed .crx that comes from the Web Store, so the folder is what gets loaded.
Then in Brave or Chrome:
- Open
brave://extensions(chrome://extensionsin Chrome) and turn on Developer mode. - Click Load unpacked and choose
build/release/darkreader-fast. - Remove stock Dark Reader. In Brave, also set
brave://flags/#enable-force-darkto Disabled, or pages are darkened twice. - Check the keys at
brave://extensions/shortcuts.
To update, pull, rebuild, and click the reload arrow on the extension's card.
The browser derives an unpacked extension's ID from its folder path, and synced settings are stored under that ID. Load the build from the same path on every machine whose settings should sync.
src/one_offs/benchmark-load.mjsstarts a fresh headless browser profile, loads each URL once with the given build, and appends one JSON row per page tobuild/bench/LABEL.jsonl. Branded Chrome ignores--load-extension, so pointCHROME_BINat Chrome for Testing.docs/benchmark/results.csvholds every run behind the speed chart, andsrc/one_offs/plot-benchmark.pydraws it.src/one_offs/flame-compare.pyfolds the CPU profiles intodocs/benchmark/*.foldedand draws the flame graphs.
CHROME_BIN=/path/to/chrome-for-testing node src/one_offs/benchmark-load.mjs build/release/darkreader-fast main-r1 9410 https://github.com/microsoft/vscode
The charts come from Chrome for Testing 146, headless, with a fresh profile per build, a light system scheme and no logins, on 10 October 2026. Stock and this fork ran in turn, three rounds each. Stock is upstream 93b124d9. An MDN page was also measured and left out, because Dark Reader's site config marks MDN as having its own dark theme and neither build changes it.
Tests: npm test runs the unit tests, and CHROME_BIN=... node src/one_offs/test-inject-cft.mjs runs the browser tests in a browser it owns.
Dark Reader is by Alexander Shutau and its contributors, under the MIT licence, which this fork keeps. Nearly all of the code is theirs.