Why Mawu’s Dark Mode Used to Freeze the Tab for Two Seconds

A dark-mode engine that reads every element’s real color and flips it works well right up until the page it’s reading has thousands of elements on it. A busy list-table screen can freeze the tab for two full seconds before anything renders. Here’s the exact benchmark that proved it, the fix that removed the freeze without changing how long the page actually takes to go dark, and why “same total time” and “no blocking” turned out to be two separate problems with two separate answers.

What “freeze the tab” actually means here

Mawu’s content-area engine walks the DOM once on page load: for every element inside #wpbody, read its real computed background, text, and border color, then recalculate each in HSL space. That’s the whole mechanism, and it’s what lets Mawu handle content it’s never seen before instead of relying on a fixed stylesheet. The cost is that the walk runs as one JavaScript function call, and a browser tab can only run one JavaScript function at a time. While that function is running, nothing else can: no scrolling, no clicking, no repaint, nothing. A long enough walk isn’t a slow dark mode. It’s a frozen page.

The benchmark, on a screen built to be heavy

The engine had shipped without ever being measured against a truly large admin screen, a known gap called out directly in this plugin’s own working notes. Closing it meant building a synthetic #wpbody-shaped tree sized like a real worst case, a busy list table with a few thousand rows, each with the usual handful of cells, links, and action buttons, then running the actual, unmodified engine against it in a real Chromium tab.

At 1,000 rows, roughly 19,000 elements, the walk took a single synchronous task of about 608 milliseconds. At 3,000 rows, roughly 57,000 elements, that grew to about 2.1 seconds. Both numbers are one continuous blocking task apiece, confirmed with a real browser’s own performance timeline rather than a guess. Two seconds is well past the point where a person watches a page load and starts wondering if something broke.

The fix, and the constraint it had to respect

The obvious fix, spread the same work across multiple smaller tasks instead of one big one, is straightforward in principle and easy to get wrong in a way that just moves the problem. The actual fix is a new walkChunked() function that processes the tree in slices, yielding between each one via requestIdleCallback, with a setTimeout fallback for the one major browser (Safari) that doesn’t implement it. Each slice checks the browser’s own idle deadline before starting on the next element, so no individual chunk can itself run long enough to become a new blocking task, by construction, not by tuning a chunk size and hoping it holds.

This only replaces the initial full-tree walk that runs once on page load. The function used everywhere else, for newly added nodes the MutationObserver picks up and for the hover/focus re-checks that catch :hover-only styles, is untouched. Those paths only ever touch one small subtree or a single element at a time; they were never the source of the freeze, and chunking something that’s already small would just add overhead for nothing.

Re-measuring: the part that’s easy to get backwards

The obvious way to check a fix like this is wrong is to time how long the page takes to look fully dark, see the same number as before, and assume nothing improved. That’s exactly what the re-measurement showed on its surface: about 1.2 seconds to fully dark at 1,000 rows, about 2.3 seconds at 3,000, both roughly matching the pre-fix totals, in one case slightly higher.

That comparison is measuring the wrong thing. The problem was never that the walk took a while; a page that takes a couple of seconds to finish applying dark mode, while staying responsive to scrolling and clicking the whole time, is a completely different experience from a page that’s unresponsive for that same stretch. The number that actually matters is blocking tasks, not elapsed time, and on that number the fix is unambiguous: a PerformanceObserver watching for longtask entries reported several before the fix and none after. The tab stays interactive throughout. It just takes the same wall-clock time to finish, the same way a background download doesn’t freeze your browser just because it’s still running.

Why this needed a synthetic benchmark instead of waiting for a report

A two-second freeze on a 3,000-row list table doesn’t show up in normal development. Most admin screens checked during regular testing have a handful of items, load fast, and never come close to that element count. This is the same shape of gap as testing that only ever exercises the default configuration: the failure mode exists entirely outside what routine use surfaces, so it either gets found by deliberately constructing the worst case, or it gets found by an actual user running a large site who eventually files a report that starts with “the admin freezes for a second when.” Building the synthetic tree and measuring it directly meant finding this before it shipped as a live complaint instead of after.

See how the engine works in the docs, or try Mawu Dark Mode on your own admin screens.

Related Posts

  • Six Dark Themes, One WCAG Audit: What Actually Failed
  • Every Way to Get Dark Mode in WordPress, Compared
  • The Heroes Behind Mawu’s Seven New Dark Themes
  • A Developer’s Guide to Ariadne’s Hooks and Filters
  • Scheduled Dark Mode and What the Sleep Research Actually Says