[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:
ciregenz
2026-09-02 00:02:58 -07:00
co-authored by Claude Fable 5.1
parent 68b9ad32ea
commit 4d9ecb9a16
4 changed files with 37 additions and 6 deletions
+16 -5
View File
@@ -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/');
});
+8
View File
@@ -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,
};
}