镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Commit 20d3ecc

Browse files
committed
Fix stale viewport update after timeout by passing AbortSignal to handler
When runHandlerWithTimeout times out, the underlying handler continues executing. If a newer commit completes first, the timed-out handler's eventual resolution would call updatePreviewServerSnapshot with stale dimensions, overwriting the correct newer state. Fix: Pass an AbortSignal from runHandlerWithTimeout to the handler. The signal is aborted on timeout, and handleViewportChange checks signal.aborted before applying side effects.
1 parent f0c571c commit 20d3ecc

3 files changed

Lines changed: 26 additions & 9 deletions

File tree

‎apps/web/src/browser/browserViewportActions.test.ts‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ describe("browserViewportActions", () => {
2121
height: 700,
2222
});
2323
expect(first).not.toHaveBeenCalled();
24-
expect(second).toHaveBeenCalledWith({ _tag: "freeform", width: 900, height: 700 });
24+
expect(second).toHaveBeenCalledWith(
25+
{ _tag: "freeform", width: 900, height: 700 },
26+
expect.any(AbortSignal),
27+
);
2528

2629
unsubscribeSecond();
2730
await expect(
@@ -75,7 +78,9 @@ describe("browserViewportActions", () => {
7578
vi.useFakeTimers();
7679
try {
7780
const never = new Promise<void>(() => undefined);
78-
const handler = vi.fn(async (_setting: PreviewViewportSetting): Promise<void> => undefined);
81+
const handler = vi.fn(
82+
async (_setting: PreviewViewportSetting, _signal: AbortSignal): Promise<void> => undefined,
83+
);
7984
handler.mockImplementationOnce(() => never).mockResolvedValueOnce(undefined);
8085
const unsubscribe = subscribeBrowserViewportChange("tab-timeout", handler);
8186
const first = commitBrowserViewportChange("tab-timeout", {
@@ -97,7 +102,11 @@ describe("browserViewportActions", () => {
97102
await second;
98103

99104
expect(handler).toHaveBeenCalledTimes(2);
105+
expect(handler.mock.calls[0]?.[1]).toBeInstanceOf(AbortSignal);
106+
expect((handler.mock.calls[0]?.[1] as AbortSignal).aborted).toBe(true);
100107
expect(handler.mock.calls[1]?.[0]).toMatchObject({ width: 900, height: 700 });
108+
expect(handler.mock.calls[1]?.[1]).toBeInstanceOf(AbortSignal);
109+
expect((handler.mock.calls[1]?.[1] as AbortSignal).aborted).toBe(false);
101110
unsubscribe();
102111
} finally {
103112
vi.useRealTimers();

‎apps/web/src/browser/browserViewportActions.ts‎

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import type { PreviewViewportSetting } from "@t3tools/contracts";
22

3-
type BrowserViewportHandler = (setting: PreviewViewportSetting) => Promise<void>;
3+
type BrowserViewportHandler = (
4+
setting: PreviewViewportSetting,
5+
signal: AbortSignal,
6+
) => Promise<void>;
47

58
export const BROWSER_VIEWPORT_COMMIT_TIMEOUT_MS = 15_000;
69

@@ -20,14 +23,18 @@ const runHandlerWithTimeout = (
2023
handler: BrowserViewportHandler,
2124
setting: PreviewViewportSetting,
2225
): Promise<void> => {
26+
const controller = new AbortController();
2327
let timeoutId: ReturnType<typeof setTimeout> | undefined;
2428
const timeout = new Promise<never>((_resolve, reject) => {
25-
timeoutId = setTimeout(
26-
() => reject(new BrowserViewportCommitTimeoutError(tabId)),
27-
BROWSER_VIEWPORT_COMMIT_TIMEOUT_MS,
28-
);
29+
timeoutId = setTimeout(() => {
30+
controller.abort();
31+
reject(new BrowserViewportCommitTimeoutError(tabId));
32+
}, BROWSER_VIEWPORT_COMMIT_TIMEOUT_MS);
2933
});
30-
return Promise.race([Promise.resolve().then(() => handler(setting)), timeout]).finally(() => {
34+
return Promise.race([
35+
Promise.resolve().then(() => handler(setting, controller.signal)),
36+
timeout,
37+
]).finally(() => {
3138
if (timeoutId !== undefined) clearTimeout(timeoutId);
3239
});
3340
};

‎apps/web/src/components/preview/PreviewView.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ export function PreviewView({ threadRef, tabId: requestedTabId, configuredUrls,
152152
}, [tabId]);
153153

154154
const handleViewportChange = useCallback(
155-
async (nextViewport: PreviewViewportSetting) => {
155+
async (nextViewport: PreviewViewportSetting, signal: AbortSignal) => {
156156
if (!tabId) return;
157157
const result = await resize({
158158
environmentId: threadRef.environmentId,
@@ -162,6 +162,7 @@ export function PreviewView({ threadRef, tabId: requestedTabId, configuredUrls,
162162
viewport: nextViewport,
163163
},
164164
});
165+
if (signal.aborted) return;
165166
if (result._tag === "Failure") {
166167
const error = squashAtomCommandFailure(result);
167168
toastManager.add({

0 commit comments

Comments
 (0)