CI also records the package footprint after each passing gate: staged-tree ZIP and unpacked bytes,
bundled DNR rules, manifest-injected scripts, and content and worker source bytes. The
limits in static-budget.json are compared with the staged package
by node tools/check-performance-budget.js; CI keeps the measurements as a build
artifact for 90 days. A release ZIP archived from a commit includes its commit ID in the ZIP
comment, so its byte count can differ from the staged-tree metric; the published SHA-256 covers
the actual release ZIP. These are size and rule-count checks. The browser profile below
measures execution time and heap with the worker held awake; what a worker wake costs the browser
is measured separately, under Cold worker wakes.
Each profile-<commit>.json here is an enabled-versus-disabled measurement of that release
candidate in a real browser with the real extension loaded, produced by
node tools/perf-profile.js; the .md beside it is the readable summary, and the JSON keeps the
raw per-run values, the readiness evidence and the metadata (commit, browser and extension
versions, CPU, throttle, date). Raw DevTools traces from --trace land in traces/, which is
not tracked (tens of MB each).
The separate YouTube memory review records the owner's 1.2 GB Brave warning, controlled playback measurements, and what remains unproven.
The worker stops after about thirty quiet seconds, and the next tab close, popup open or
navigation starts it again. Whatever a wake asks of Chrome is answered on the browser's UI
thread, the one that draws the tab strip and routes scrollbar drags. node tools/browser-cold-wake.js
waits for the remote lists to install, lets the worker sleep, then closes a tab and opens the
popup, each on a sleeping worker, and fails on any UI-thread stall answering a rule-API call or
on more than 300 ms of UI-thread stalls in all. It needs Edge and network access and takes about
three minutes, so it is not part of the gate.
Measured on 2026-10-01 (Edge 150, i5-12400F, 22,134 dynamic rules). Six appliers read every
dynamic rule on each wake to filter their own band; Chrome builds each reply on the UI thread in
170-270 ms and sends it as a ~21.8 MB message. Closing a tab on a sleeping worker froze the UI
thread six times, 1,123 ms in all; dragging the popup's scrollbar straight after opening it saw a
worst move of 500 ms to 5 s. With band-filtered reads (getDynamicRulesInBand) the same tab
close shows no rule-API stall (UI thread 226-252 ms busy over 5 s, against 1,260 ms) and the
popup drag's worst move is 66 ms. The harness above keeps a debugger attached to the worker,
which keeps it awake, so it cannot see any of this; releaseWorker in perf-profile.js lets
a tool measure a wake.
- off is the same browser with no extension; on is WardenOne loaded from the working tree. The profile is the difference, per page, per phase (cold = first navigation with the cache cleared, warm = a reload), over five runs: median, p90 and max.
- The pages are synthetic and local: an article with cross-site links and a sign-in form, a page that builds a deep DOM tree in-document the way frameworks do (the mutation-scan stress case), and a page with eight same-origin frames. They are meant to be harder than an ordinary page, not representative of one; the numbers are ceilings for those shapes, and a change between two profiles matters more than any absolute.
- Compare cells within one profile. Between browser sessions the frames page in particular moves by up to 2x (code-cache state for the per-frame scripts), while the five runs inside one session stay tight; the harness runs every variant in its own fresh session for that reason, and a cross-session comparison is only meaningful when the change is larger than that.
- regress:mutation-dedup is a deliberately broken copy of the extension -- the outermost-node
dedup of the mutation scan undone -- and exists to prove the harness can see a known
structural regression before its numbers are trusted as a gate.
--expect-regressionfails the run if it cannot. - The harness fails closed: an "on" variant whose worker does not report the repository's version, or whose engine did not stamp its readiness marker in the page, refuses to measure; an "off" variant with the marker present refuses too. Chrome 152 no longer loads unpacked extensions from the command line, so the browser is Microsoft Edge, and its version is in every profile.
Edge 150, i5-12400F, no CPU throttle, 5 runs per cell. Main-thread task time, cold, median: article 43 ms off / 95 ms on (+52 ms); churn stress page 192 ms / 1,094 ms (+902 ms, one long task of ~170 ms); eight frames 50 ms / 546 ms (+496 ms). Heap after GC: +0.8 MB on the article, +2.5 MB on the churn page, +5.9 MB with eight frames. The known regression showed as 1,458 ms against 1,094 ms on the churn page (+33%, long-task time 751 ms against 166 ms), so the harness is sensitive enough to serve.
What the numbers say, beyond "it costs something": the per-frame cost is the standout. The
frames page adds roughly 60 ms of main-thread time per same-origin frame, which is the
all-frames MAIN-world scripts (domain-utils.js, anti-redirect.js, fingerprint-realm.js,
permission-chain.js) and the isolated bridge being evaluated in every frame; a page with many
frames pays it many times. That is a measurement, not a fix, and it is recorded here so the next
profile can be compared against it.