fix(desktop): keep oauth popups alive on redirect aborts and blank loads

Toutiao's wap_login redirect chain raises ERR_ABORTED (-3) without the
literal symbol in the message, so the previous regex fell through to the
fallback error page. Now match the numeric form too, and add a 7s blank-
page watchdog that auto-reloads stuck "Loading..." popups once per fresh
navigation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-08 22:12:28 +08:00
parent 0f3a9a977e
commit cb42d4b662
+100 -1
View File
@@ -2,16 +2,95 @@ import type { BrowserWindow } from 'electron/main'
import { STANDARD_USER_AGENT } from './user-agent'
const ignoredNavigationFailurePattern = /ERR_ABORTED|ERR_BLOCKED_BY_CLIENT/i
const ignoredNavigationFailurePattern = /ERR_ABORTED|ERR_BLOCKED_BY_CLIENT|\((-3|-20)\) loading/i
const errorPagePrefix = 'data:text/html'
const BLANK_PAGE_PROBE_DELAY_MS = 7_000
const BLANK_PAGE_NODE_THRESHOLD = 3
const BLANK_PAGE_TEXT_THRESHOLD = 5
interface WindowNavigationState {
readyForDetection: boolean
lastMainFrameError: string | null
blankPageWatchdog: NodeJS.Timeout | null
blankPageRetried: boolean
context: string
}
const navigationStateRegistry = new WeakMap<BrowserWindow, WindowNavigationState>()
function clearBlankPageWatchdog(state: WindowNavigationState): void {
if (state.blankPageWatchdog) {
clearTimeout(state.blankPageWatchdog)
state.blankPageWatchdog = null
}
}
async function probePageBodyShape(
window: BrowserWindow,
): Promise<{ nodeCount: number; textLength: number; readyState: string } | null> {
if (window.isDestroyed()) {
return null
}
try {
return (await window.webContents.executeJavaScript(
`(() => ({
nodeCount: document.body ? document.body.querySelectorAll('*').length : 0,
textLength: document.body ? (document.body.innerText || '').trim().length : 0,
readyState: document.readyState,
}))()`,
true,
)) as { nodeCount: number; textLength: number; readyState: string }
} catch {
return null
}
}
function scheduleBlankPageWatchdog(window: BrowserWindow, state: WindowNavigationState): void {
clearBlankPageWatchdog(state)
state.blankPageWatchdog = setTimeout(() => {
state.blankPageWatchdog = null
if (window.isDestroyed()) {
return
}
const currentURL = window.webContents.getURL()
if (currentURL.startsWith(errorPagePrefix) || currentURL === 'about:blank') {
return
}
void probePageBodyShape(window).then((shape) => {
if (!shape || window.isDestroyed()) {
return
}
const looksBlank =
shape.nodeCount < BLANK_PAGE_NODE_THRESHOLD &&
shape.textLength < BLANK_PAGE_TEXT_THRESHOLD
if (!looksBlank) {
return
}
if (state.blankPageRetried) {
console.warn(`[desktop-window] ${state.context} still blank after auto-reload`, {
url: currentURL,
...shape,
})
return
}
state.blankPageRetried = true
console.warn(`[desktop-window] ${state.context} blank page detected, reloading once`, {
url: currentURL,
...shape,
})
window.webContents.reloadIgnoringCache()
})
}, BLANK_PAGE_PROBE_DELAY_MS)
}
function escapeHtml(value: string): string {
return value
.replaceAll('&', '&amp;')
@@ -126,6 +205,9 @@ export function attachWindowDiagnostics(window: BrowserWindow, context: string):
navigationStateRegistry.set(window, {
readyForDetection: false,
lastMainFrameError: null,
blankPageWatchdog: null,
blankPageRetried: false,
context,
})
window.webContents.on('did-start-loading', () => {
@@ -134,6 +216,7 @@ export function attachWindowDiagnostics(window: BrowserWindow, context: string):
return
}
state.readyForDetection = false
clearBlankPageWatchdog(state)
})
window.webContents.on(
@@ -147,6 +230,7 @@ export function attachWindowDiagnostics(window: BrowserWindow, context: string):
if (state) {
state.readyForDetection = false
state.lastMainFrameError = `${errorCode}:${errorDescription}`
clearBlankPageWatchdog(state)
}
console.warn(`[desktop-window] ${context} main-frame load failed`, {
@@ -167,6 +251,7 @@ export function attachWindowDiagnostics(window: BrowserWindow, context: string):
state.readyForDetection = !currentURL.startsWith(errorPagePrefix)
if (state.readyForDetection) {
state.lastMainFrameError = null
scheduleBlankPageWatchdog(window, state)
}
})
@@ -174,10 +259,18 @@ export function attachWindowDiagnostics(window: BrowserWindow, context: string):
const state = navigationStateRegistry.get(window)
if (state) {
state.readyForDetection = false
clearBlankPageWatchdog(state)
}
console.error(`[desktop-window] ${context} renderer gone`, details)
})
window.once('closed', () => {
const state = navigationStateRegistry.get(window)
if (state) {
clearBlankPageWatchdog(state)
}
})
}
export function isWindowReadyForDetection(window: BrowserWindow): boolean {
@@ -193,6 +286,12 @@ export async function loadWindowURLSafely(
targetURL: string,
context: string,
): Promise<void> {
const state = navigationStateRegistry.get(window)
if (state) {
state.blankPageRetried = false
clearBlankPageWatchdog(state)
}
try {
await window.loadURL(targetURL, { userAgent: STANDARD_USER_AGENT })
} catch (error) {