Every Puppeteer click ran isIntersectingViewport (an actionability
check that wraps IntersectionObserver in a promise). Our IO.observe
schedules re-fires at 120/500/1500/3500/7000ms to cover infinite-scroll
sentinels. Puppeteer's callback disconnects the observer on the first
fire, so the re-fires are no-ops, but they keep the deno_core event
loop 'not idle' until they all fire.
resolve_promises was waiting for the loop to go idle (5s hard cap), so
every awaitPromise call paid the full 5s even though the result was
ready in <1ms. Effect: page.click() took ~7s instead of ~150ms because
clickableBox + isIntersectingViewport each hit the cap.
Fix: each evaluate / callFunctionOn now writes a unique sentinel
(globalThis.__obscura_done_<counter> = true) in a finally block. The
new resolve_promises_until polls that sentinel with a 1ms / 2ms / ...
backoff up to 50ms and exits as soon as the IIFE is done, instead of
waiting for unrelated timers to drain. Worst-case miss is one tick
(50ms).
Measured impact (Puppeteer CDP scenarios, lower is better):
scenario before after
data: click button 7420ms ~530ms (14x)
data: addEvt click 14536ms ~680ms (21x)
data: form submit 7676ms ~810ms (9x)
CDP bench total 35190ms ~7400ms (~5x)
28/28 integration tests still pass.