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

1 participant