Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,15 @@ export interface BrowserPort {
readonly viewport?: WebuiBrowserViewport;
},
onFrame: (frame: WebuiBrowserImageFrame) => void,
/**
* The service's refusal, when the stream gave up.
*
* The frames themselves say nothing about a stream that stopped, so the
* refusal is reported here rather than left to the panel to infer: a
* panel that is waiting and a panel whose stream failed look identical
* until one of them is told.
*/
onFailure?: (error: Error) => void,
) => () => void;
/**
* The session's web tabs, as a snapshot, on a stream.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ const PANEL_COPY = {
absent: "当前运行时没有浏览器能力,浏览器面板不可用。",
notReady: "浏览器能力尚未就绪(例如扩展未连接),面板暂时无法显示页面。",
unknown: "当前运行时没有报告浏览器能力,面板按未授权处理。",
// The stream gave up. The reason is said once, beside this notice, as a
// toast; this sentence says the half that is about the panel — that the
// picture is not coming, and that opening the page again is the way back.
failed: "浏览器画面流已失败,画面不会再来。重新打开页面可以重试。",
} as const;

export interface BrowserPanelProps {
Expand Down Expand Up @@ -411,6 +415,19 @@ export function BrowserPanel({
</FeedbackLiveRegion>
) : null}

{/*
A stream that gave up is not a page that is still loading. The empty
placeholder below looks the same in both cases, so the failure says
so — and it says it here, in the panel, rather than only in a toast
that a person can miss. It renders only when the panel failed, so the
default DOM is the one that was there before.
*/}
{state?.status === "error" ? (
<FeedbackLiveRegion className="webui-browser-notice" testId="browser-panel-failed">
{PANEL_COPY.failed}
</FeedbackLiveRegion>
) : null}

<div className="webui-browser-stage" ref={stageRef}>
{frameSrc ? (
<img
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,12 @@ export function BrowserPanelHost({
dispatch({ type: "frame", frame });
}
},
// The service's refusal, not the panel's guess. A stream that gave up is
// reported as an error, so it stops looking like a page that is still
// loading — the two are the same picture on screen otherwise.
(error) => {
dispatch({ type: "failed", error: toBrowserError(error) });
},
);
return () => {
stop();
Expand Down
4 changes: 2 additions & 2 deletions packages/webui/src/client/platform/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,8 @@ export function createWebuiTransport(options: WebuiTransportOptions): Required<W
...(body.runId ? { runId: body.runId } : {}),
}, { reuseConnection: true }),
sendBrowserCommand: (body) => request(SEND_BROWSER_COMMAND_OPERATION_NAME, body),
watchBrowserFrames: (body, onFrame) =>
watchBrowserFrames(WATCH_BROWSER_FRAMES_OPERATION_NAME, body, onFrame),
watchBrowserFrames: (body, onFrame, onFailure) =>
watchBrowserFrames(WATCH_BROWSER_FRAMES_OPERATION_NAME, body, onFrame, onFailure),
watchBrowserWebTabs: (body, onSnapshot) =>
watchBrowserWebTabs(WATCH_BROWSER_WEB_TABS_OPERATION_NAME, body, onSnapshot),
openBrowserWebTab: (body) => request(OPEN_BROWSER_WEB_TAB_OPERATION_NAME, body),
Expand Down
10 changes: 10 additions & 0 deletions packages/webui/src/client/platform/wire.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ export interface WebuiWire {
operation: string,
requestBody: { readonly surface: WebuiBrowserSurface; readonly target: WebuiBrowserTargetId },
onFrame: (frame: WebuiBrowserImageFrame) => void,
onFailure?: (error: Error) => void,
): () => void;
/**
* The web-tab snapshot stream.
Expand Down Expand Up @@ -614,13 +615,22 @@ export function createWebuiWire({
readonly viewport?: WebuiBrowserViewport;
},
onFrame: (frame: WebuiBrowserImageFrame) => void,
onFailure?: (error: Error) => void,
): () => void {
let stopped = false;
const session = openWireSession(operation, requestBody, {
frame: (_session, frame) => {
// A stopped watch must not deliver, and `close()` is not a promise that
// the socket stops emitting: the caller's flag is what guarantees it.
if (stopped) return;
// An error frame is the service's answer: it says the stream gave up
// and why. Dropping it is what leaves a panel waiting for a picture
// that is never coming — the watcher has no way to tell "still
// loading" from "the service said it stopped", so it says neither.
if (frame.kind === "error") {
onFailure?.(new WebuiWireError(frame.code, frame.message, frame.requestId));
return;
}
if (frame.kind !== "event" || !frame.body || typeof frame.body !== "object") return;
onFrame(frame.body as WebuiBrowserImageFrame);
},
Expand Down
Loading
Loading