Skip to content

fix(remote-popup): stop the refresh loop from re-triggering itself - #169

Open
divya0795 wants to merge 1 commit into
k1tbyte:masterfrom
divya0795:fix/remote-popup-refresh-loop
Open

fix(remote-popup): stop the refresh loop from re-triggering itself#169
divya0795 wants to merge 1 commit into
k1tbyte:masterfrom
divya0795:fix/remote-popup-refresh-loop

Conversation

@divya0795

@divya0795 divya0795 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Fixes #168

Problem

updateLinks assigned anchor.textContent unconditionally. Assigning textContent always removes the existing child text node and inserts a new one, so it emits a childList record even when the string is unchanged.

The script observes childList/subtree on document.documentElement, so that write fed straight back into its own observer:

refresh → updateLinks writes textContent → childList record →
scheduleRefresh → setTimeout(0) → refresh → …

refreshScheduled is reset before refresh() runs, so each pass schedules the next. Once any DOM change kicked the loop off, it sustained itself for as long as a remote-tooltip anchor was present.

Change

Compare before writing — the same idea updateQrCodes already uses at :116 (canvas.dataset.wandRemoteUrl === remoteUrl). The href write is guarded too for symmetry, though it was never a contributor since attributes is not observed.

No behavioural change to what the link ends up showing; only the redundant rewrites go away.

Test

New bridge/src/remote-popup-cleanup.test.ts. The script's own initial refresh() runs before it starts observing, so the loop needs one external mutation to start — the test supplies exactly one appendChild, then counts mutations over 8 macrotasks.

Verified to fail against the unfixed script:

AssertionError: expected 8 to be 0
- 0
+ 8

One mutation per macrotask — the loop is self-sustaining. With the fix, 0.

bridge/tsconfig.json deliberately omits the DOM lib (it targets the Node-side bridge, lib: ["ES2022"], types: ["node"]). Rather than widen that config for one test, the file pulls DOM types in locally with /// <reference lib="dom" />.

Verification

Node 22.23.1 / pnpm 10.17.0, matching the versions .github/workflows/build.yml pins:

  • pnpm test35 passed (13 files), up from 34 on master
  • pnpm lint — clean at --max-warnings=0
  • pnpm typecheck — clean (typecheck:web and typecheck:bridge)

I do not have a licensed WeMod install, so this has not been exercised against a live Wand renderer — the evidence is the jsdom reproduction above.

updateLinks assigned anchor.textContent unconditionally. Assigning textContent
always removes the existing child text node and inserts a new one, so it emits
a childList mutation record even when the string is identical.

The script observes childList/subtree on document.documentElement, so that
write fed straight back into its own observer:

  refresh -> updateLinks writes textContent -> childList record ->
  scheduleRefresh -> setTimeout(0) -> refresh -> ...

Once any DOM change kicked it off, the loop sustained itself for as long as a
remote-tooltip anchor was in the document, re-running querySelectorAll sweeps
and text-node churn one pass per macrotask and never quiescing.

Compare the href write, which is safe because attributes are not observed, and
updateQrCodes, which already guards with
"canvas.dataset.wandRemoteUrl === remoteUrl". Apply the same idea: compare
before writing, so a link that already holds the right value is left untouched.

Added bridge/src/remote-popup-cleanup.test.ts, which kicks the loop with one
external mutation and then asserts the document stops changing. Against the
unfixed script it records 8 mutations over 8 macrotasks -- one per pass,
confirming the loop is self-sustaining -- and 0 with the fix.

The suite needs DOM globals, which bridge/tsconfig.json omits because it targets
the Node-side bridge, so the test file pulls the DOM lib in locally with a
reference directive rather than widening the project config.
@divya0795
divya0795 force-pushed the fix/remote-popup-refresh-loop branch from 19a10fc to 9a6c2b8 Compare July 26, 2026 14:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

remote-popup-cleanup: unguarded textContent write feeds its own MutationObserver (refresh loop never settles)

1 participant