Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Perf: shared mutation router for subtree watchers (#150 Tier 2)#155

Merged
twschiller merged 1 commit into
mainfrom
perf/shared-mutation-router
Jun 5, 2026
Merged

Perf: shared mutation router for subtree watchers (#150 Tier 2)#155
twschiller merged 1 commit into
mainfrom
perf/shared-mutation-router

Conversation

@twschiller

Copy link
Copy Markdown
Contributor

Summary

  • Replace per-createSubtreeWatcher MutationObserver with one shared observer per observed root that fans out to subscribers (uBO's safeObserverHandler model — issue Perf: speed up rule application during fast infinite scroll and SPA route transitions #150 Tier 2 idea build(deps): Bump actions/deploy-pages from 4 to 5 #10).
  • With 24 rules all watching document.body, this collapses 24 observer callbacks, 24 visibilitychange listeners, and 24 subscribeRouteChange callbacks into one each; the shared nodeType / IGNORE_TAGS / isConnected filter runs once per added node regardless of subscriber count.
  • Per-subscriber state (throttleMs, skipPlaceholderSubtrees, burst-flush, pending) stays independent — createSubtreeWatcher / SubtreeWatcher API is unchanged, so no call site updates are needed.

Mechanics

  • Module-level Map<Node, Router> keyed by observed root. First subscriber on a target creates the router + observer + listeners; last subscriber to stop() tears it all down and deletes the map entry.
  • Document and document.body collapse to the same router via resolveTarget. document.head (used by meta-injection-strip) gets its own router.
  • Route-change rAF sweep iterates subscribers and fans out onSubtrees([document.body]) — preserves existing sweep semantics.
  • Adds __resetSubtreeWatcherForTesting mirroring __resetRouteChangeForTesting so test isolation doesn't depend on every stop() running.

Test plan

  • bun run test src/lib/__tests__/subtree-watcher.test.ts — 45/45 pass (35 existing + 10 new covering fan-out, single-observer-construction via constructor spy, independent skipPlaceholderSubtrees / throttleMs, partial-stop keeping observer alive, full teardown on last stop, Documentbody routing, body vs. head router isolation, route-change and visibility fan-out).
  • bun run test — 1200/1200 pass across all 63 suites (covers all 24 rule consumers transitively).
  • bun run typecheck clean.
  • bun run check (Biome + ESLint) clean.

🤖 Generated with Claude Code

Collapse one MutationObserver per `createSubtreeWatcher` into one shared
observer per observed root, with per-subscriber fan-out. The 24 rules that
all watch `document.body` previously installed 24 observers, 24
visibilitychange listeners, and 24 route-change subscriptions, each
running the same nodeType/IGNORE_TAGS/isConnected filter; now they share
one of each, with the cheap shared filter running once per added node
regardless of subscriber count.

Public API (`createSubtreeWatcher` / `SubtreeWatcher`) is unchanged.
Per-subscriber `throttleMs`, `skipPlaceholderSubtrees`, and burst-flush
behavior stay independent. Last subscriber to stop() tears the router
down (disconnect, remove listeners, cancel rAF, delete from registry).

Adds `__resetSubtreeWatcherForTesting` mirroring the existing
`__resetRouteChangeForTesting` so test isolation does not depend on
every `stop()` running.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@vercel

vercel Bot commented Jun 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agent-browser-shield-demo-site Ready Ready Preview, Comment Jun 5, 2026 4:29am

Request Review

@twschiller twschiller merged commit d7fd25e into main Jun 5, 2026
7 checks passed
@twschiller twschiller deleted the perf/shared-mutation-router branch June 5, 2026 04:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant