mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-10-01 22:14:51 +02:00
[eric] browser: a load Chromium refuses is a failed navigation, not "Navigated to" an empty page; exp.3 changelog names the popup, self-heal and navigation fixes
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012G8kyALnPjsA7aJFmMBq3R
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
68b9ad32ea
commit
4d9ecb9a16
@@ -11,7 +11,7 @@ import { resolveInput } from './resolveUrl';
|
||||
import { rankAndCapInteractives, type RankItem } from './interactiveRanking';
|
||||
import { shouldStopWaiting, SETTLE_POLL_MS, settleProbeJs } from './browserSettle';
|
||||
import { unwrapCdpEval } from './cdpEval';
|
||||
import { navigationOutcome, sameDoc } from './navigationOutcome';
|
||||
import { loadFailureError, navigationOutcome, sameDoc } from './navigationOutcome';
|
||||
import { handleCanvasCommand } from './canvasCommandHandler';
|
||||
import { typeChars, type TypedKeys } from './typeChars';
|
||||
|
||||
@@ -381,17 +381,28 @@ async function handleNavigate(wv: BrowserWebview, params: Record<string, any>):
|
||||
removeReady = () => wv.removeEventListener('dom-ready', onReady);
|
||||
});
|
||||
let aborted = false;
|
||||
const fullyLoaded = wv.loadURL(url).catch((err: any) => {
|
||||
// A load Chromium refuses (unsafe port, DNS, connection refused) still fires dom-ready for its empty error document, which used to win the race and turn the refusal into "Navigated to" (ENG-404 drill, 2026-09-01).
|
||||
let loadFailure: string | null = null;
|
||||
const onFail = (e: Event) => {
|
||||
const f = e as Event & { errorCode?: number; errorDescription?: string; isMainFrame?: boolean };
|
||||
if (f.isMainFrame !== false && f.errorCode !== undefined && f.errorCode !== -3) loadFailure = `${f.errorDescription || 'load failed'} (${f.errorCode})`;
|
||||
};
|
||||
wv.addEventListener('did-fail-load', onFail);
|
||||
const fullyLoaded = wv.loadURL(url).catch((err: unknown) => {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
// ERR_ABORTED can be a benign supersede OR a nav the guest refused outright; the landed-URL check below tells them apart.
|
||||
if (err?.message?.includes('ERR_ABORTED')) { aborted = true; return; }
|
||||
throw err;
|
||||
if (msg.includes('ERR_ABORTED')) { aborted = true; return; }
|
||||
loadFailure = loadFailure || msg;
|
||||
});
|
||||
fullyLoaded.catch(() => {}); // a late load failure shouldn't throw once dom-ready returned
|
||||
try {
|
||||
await Promise.race([fullyLoaded, domReady]);
|
||||
// dom-ready can beat the rejection by a few ms; give the refusal its say before trusting the document.
|
||||
await Promise.race([fullyLoaded, new Promise<void>((r) => setTimeout(r, 300))]);
|
||||
} finally {
|
||||
removeReady();
|
||||
wv.removeEventListener('did-fail-load', onFail);
|
||||
}
|
||||
if (loadFailure) return loadFailureError(url, loadFailure);
|
||||
// A navigate that lands on a raw JSON/API document (Instagram's topsearch, any /api/... GET)
|
||||
// paints an unreadable wall in the card and reads as a crash to the user. Hand the data to the
|
||||
// agent as the result instead, and quietly get the card off the wall, so a person never sees
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// so a nav that leaves the document parked on the old URL can only ever read as a failure.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { navigationOutcome, sameDoc } from './navigationOutcome';
|
||||
import { loadFailureError, navigationOutcome, sameDoc } from './navigationOutcome';
|
||||
|
||||
test('a landed URL equal to the old page is stuck, never ok', () => {
|
||||
const v = navigationOutcome('https://en.wikipedia.org/wiki/Helium', 'https://en.wikipedia.org/wiki/Argon', 'https://en.wikipedia.org/wiki/Argon');
|
||||
@@ -39,3 +39,11 @@ test('sameDoc ignores only the trailing slash', () => {
|
||||
assert.equal(sameDoc('https://a.com', 'https://a.com/'), true);
|
||||
assert.equal(sameDoc('https://a.com/x', 'https://a.com/x#frag'), false);
|
||||
});
|
||||
|
||||
test('a refused load is an error that names the failure and the empty document, never "Navigated to"', () => {
|
||||
const r = loadFailureError('http://127.0.0.1:1/', 'ERR_UNSAFE_PORT (-312)');
|
||||
assert.match(r.error, /FAILED \(ERR_UNSAFE_PORT \(-312\)\)/);
|
||||
assert.match(r.error, /empty error document/);
|
||||
assert.match(r.error, /report the failure instead of describing the page/);
|
||||
assert.equal(r.url, 'http://127.0.0.1:1/');
|
||||
});
|
||||
|
||||
@@ -16,3 +16,11 @@ export function navigationOutcome(requested: string, before: string, landed: str
|
||||
if (!sameDoc(landed, requested)) return { kind: 'redirected', url: landed, requested };
|
||||
return { kind: 'ok', url: landed };
|
||||
}
|
||||
|
||||
/** The error a navigate returns when Chromium refused the load outright (unsafe port, DNS, connection refused): the guest then shows its own empty document, which reads as "a blank page" unless the agent is told. */
|
||||
export function loadFailureError(requested: string, failure: string): { error: string; url: string } {
|
||||
return {
|
||||
error: `Navigation to ${requested} FAILED (${failure}). The browser is showing its empty error document, not the site; retry once, and if it fails again report the failure instead of describing the page.`,
|
||||
url: requested,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user