Skip to content
Open
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
9 changes: 9 additions & 0 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import {
import {
clearStaleHarnessAuthCookies,
desktopHarnessUrl,
isAbortedNavigationCode,
isAbortedNavigationError,
shouldLoadHarnessUrl
} from './window-navigation'
Expand Down Expand Up @@ -387,6 +388,14 @@ function installMainWindowRendererRecovery(window: BrowserWindow): void {
})
webContents.on('did-fail-load', (_event, errorCode, errorDescription, validatedURL, isMainFrame) => {
if (!isMainFrame) return
// A programmatic switch (restart, recovery page, Safe Mode toggle,
// second-instance reopen) calls webContents.stop() before the next
// loadFile/loadURL, and Electron reports the cancelled in-flight load as
// did-fail-load with ERR_ABORTED (-3). That is a benign supersession —
// openHarness treats it the same way at the loadURL await site — so do not
// spend a recovery reload (or the shared reload budget) on a navigation
// nobody cancelled on purpose.
if (isAbortedNavigationCode(errorCode)) return
clearProfileBootConfirmation()
// The harness web server is local; a failure to reach it is almost
// always the renderer dropping, not a real network error. Surface the
Expand Down
11 changes: 11 additions & 0 deletions src/main/window-navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,14 @@ export function isAbortedNavigationError(error: unknown): boolean {
/(?:^|\s)ERR_ABORTED\s*\(-3\)(?:\s|$)/.test(navigationError.message)
)
}

/**
* Chromium reports a cancelled in-flight load as `did-fail-load` with the
* numeric net error ERR_ABORTED (-3). `webContents.stop()` followed by a new
* load — restart flows, recovery pages, Safe Mode toggles, second-instance
* reopens — cancels the previous navigation that way, so the code above is not
* enough for the event shape, which carries `errorCode: number`.
*/
export function isAbortedNavigationCode(errorCode: number): boolean {
return errorCode === -3
}
26 changes: 26 additions & 0 deletions test/window-navigation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { describe, expect, it } from 'vitest'
import {
isAbortedNavigationCode,
isAbortedNavigationError
} from '../src/main/window-navigation'

describe('aborted-navigation detection', () => {
it('recognizes the ERR_ABORTED numeric code reported by did-fail-load', () => {
expect(isAbortedNavigationCode(-3)).toBe(true)
})

it('does not treat real load failures as aborted navigations', () => {
// -105 is ERR_NAME_NOT_RESOLVED, -106 is ERR_INTERNET_DISCONNECTED,
// 0 means "no error" — none of them is a superseded load.
expect(isAbortedNavigationCode(-105)).toBe(false)
expect(isAbortedNavigationCode(-106)).toBe(false)
expect(isAbortedNavigationCode(0)).toBe(false)
})

it('recognizes the Error shapes thrown by webContents.loadURL', () => {
expect(isAbortedNavigationError({ code: 'ERR_ABORTED' })).toBe(true)
expect(isAbortedNavigationError({ errno: -3 })).toBe(true)
expect(isAbortedNavigationError(new Error('ERR_ABORTED (-3)'))).toBe(true)
expect(isAbortedNavigationError(new Error('ERR_NAME_NOT_RESOLVED (-105)'))).toBe(false)
})
})