fix(browser): bounded scroll acknowledgement wait
- Released tab.scroll after two seconds when a queued wheel event waits on a busy renderer acknowledgement. - Preserved immediate dispatch failures and added regression coverage for both outcomes. Fixes #5905
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `tab.scroll()` timing out after a queued wheel event waits too long for a busy renderer's acknowledgement ([#5905](https://github.com/can1357/oh-my-pi/issues/5905)).
|
||||
|
||||
## [17.0.3] - 2026-07-17
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -144,6 +144,8 @@ interface OpenDialogInfo {
|
||||
*/
|
||||
const QUICK_OP_TIMEOUT_MS = 20_000;
|
||||
const ACTION_OP_TIMEOUT_MS = 8_000;
|
||||
/** Maximum wait for a renderer acknowledgement after a wheel event is queued. */
|
||||
const SCROLL_ACK_TIMEOUT_MS = 2_000;
|
||||
/** Headroom subtracted from the cell budget so a per-op deadline fires before it. */
|
||||
const OP_DEADLINE_SLACK_MS = CELL_BUDGET_SLACK_MS;
|
||||
/**
|
||||
@@ -175,6 +177,14 @@ export function resolveOpTimeouts(cellTimeoutMs: number): OpTimeouts {
|
||||
};
|
||||
}
|
||||
|
||||
/** Queue a wheel event without treating a delayed renderer acknowledgement as dispatch failure. */
|
||||
export async function dispatchScroll(
|
||||
dispatch: () => Promise<void>,
|
||||
ackTimeoutMs = SCROLL_ACK_TIMEOUT_MS,
|
||||
): Promise<void> {
|
||||
await Promise.race([dispatch(), Bun.sleep(ackTimeoutMs)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Effective timeout for a wait helper (`waitFor*`). A positive explicit `{ timeout }` is
|
||||
* honored but clamped to the cell budget so it still fails fast + named; raising the tool
|
||||
@@ -1216,7 +1226,9 @@ export class WorkerCore {
|
||||
await untilAborted(sig, () => page.keyboard.press(key));
|
||||
}),
|
||||
scroll: (deltaX, deltaY) =>
|
||||
op("tab.scroll()", actionOpMs, sig => untilAborted(sig, () => page.mouse.wheel({ deltaX, deltaY }))),
|
||||
op("tab.scroll()", actionOpMs, sig =>
|
||||
untilAborted(sig, () => dispatchScroll(() => page.mouse.wheel({ deltaX, deltaY }))),
|
||||
),
|
||||
drag: (from, to) => op("tab.drag()", actionOpMs, sig => this.#drag(from, to, sig)),
|
||||
waitFor: (selector, opts) => {
|
||||
const w = waitMs(opts?.timeout);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { resolvePredicateTimeout } from "@oh-my-pi/pi-coding-agent/tools/browser/run-cancellation";
|
||||
import {
|
||||
dispatchScroll,
|
||||
normalizeSelector,
|
||||
resolveOpTimeouts,
|
||||
resolveWaitTimeout,
|
||||
@@ -42,6 +43,20 @@ describe("browser per-op fail-fast ceilings", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("browser scroll acknowledgement", () => {
|
||||
it("returns after the acknowledgement deadline while the renderer remains stalled", async () => {
|
||||
const acknowledgement = Promise.withResolvers<void>();
|
||||
|
||||
await expect(dispatchScroll(() => acknowledgement.promise, 1)).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("preserves wheel dispatch failures received before the acknowledgement deadline", async () => {
|
||||
await expect(dispatchScroll(() => Promise.reject(new Error("target closed")), 100)).rejects.toThrow(
|
||||
"target closed",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("browser wait-helper timeout resolution", () => {
|
||||
it("defaults a wait to the action ceiling when no explicit timeout is given", () => {
|
||||
const cell = 30_000;
|
||||
|
||||
Reference in New Issue
Block a user