perf(web-runtime): cache randomTap candidate DOM scan per tick

The 16-attempt retry loop in __sanderlingNextAction__ called
randomTap repeatedly; each call ran querySelectorAll over a-button-
input-... and re-flushed layout per match via getBoundingClientRect.
On heavy SPA routes that's the per-tick budget gone.

Cache the scan in a module-level slot, reset at the top of each
__sanderlingNextAction__ invocation so the cache doesn't outlive a tick.
This commit is contained in:
pj committed 2026-05-03 11:06:27 +07:00
1 parent 359527569f
commit fc1e6f9319
1 file changed
+18 -9
+18 -9
View File
@@ -434,16 +434,23 @@ function resolveGenerator(handle: ActionGeneratorHandle): unknown {
} }
} }
// Per-tick cache so the 16-attempt retry loop in __sanderlingNextAction__
// doesn't re-walk the DOM and re-flush layout on every iteration.
let randomTapCandidates: HTMLElement[] | null = null;
function randomTap(): unknown { function randomTap(): unknown {
const candidates = Array.from( if (!randomTapCandidates) {
document.querySelectorAll<HTMLElement>( randomTapCandidates = Array.from(
'a, button, input, select, textarea, [role="button"], [onclick]', document.querySelectorAll<HTMLElement>(
), 'a, button, input, select, textarea, [role="button"], [onclick]',
).filter((element) => { ),
if ((element as HTMLButtonElement).disabled) return false; ).filter((element) => {
const rect = element.getBoundingClientRect(); if ((element as HTMLButtonElement).disabled) return false;
return rect.width > 0 && rect.height > 0; const rect = element.getBoundingClientRect();
}); return rect.width > 0 && rect.height > 0;
});
}
const candidates = randomTapCandidates;
if (candidates.length === 0) return null; if (candidates.length === 0) return null;
const picked = candidates[Math.floor(Math.random() * candidates.length)]; const picked = candidates[Math.floor(Math.random() * candidates.length)];
if (!picked) return null; if (!picked) return null;
@@ -542,6 +549,8 @@ defineLockedGlobal("__sanderlingExtractors__", function (): Record<number, unkno
defineLockedGlobal("__sanderlingNextAction__", function (): unknown { defineLockedGlobal("__sanderlingNextAction__", function (): unknown {
if (!actionsRoot) return null; if (!actionsRoot) return null;
// Reset per-tick caches so each invocation gets a fresh DOM scan.
randomTapCandidates = null;
// Match the goja runtime: retry up to 16 times when a weighted entry's // Match the goja runtime: retry up to 16 times when a weighted entry's
// generator returns []. Otherwise on routes where most generators are // generator returns []. Otherwise on routes where most generators are
// gated to other pages, ~80% of ticks would emit no action. // gated to other pages, ~80% of ticks would emit no action.