refactor(desktop-client): harden external-window load flow with loading page and timeout watchdog
- Introduce `navigateRemoteURL` / `showLocalPage` / `showErrorPage` helpers that centralize remote navigation, watchdog wiring, and error fallback, replacing the ad-hoc logic inside `loadWindowURLSafely`. - Show a localized loading page (`loadingPageDataURL`) while a remote URL is fetching, so the window no longer sits on blank white during slow navigations. - Add a 25s remote-load timeout watchdog that switches the window to the error page when the target never finishes loading, plus an active load token so stale watchdogs from earlier navigations cannot fire on the current attempt. - Track `localPageKind` and `activeTargetURL` per window so blank-page retries, did-fail-load handlers, and ready-for-detection checks behave correctly when the window is currently showing a local loading/error page. - Escalate the blank-page watchdog: after the retry attempt, if the page is still blank, surface the error page instead of looping silent reloads. - Restyle the error page (drop gradients, use a neutral light/dark token palette, single primary retry button) and add a dedicated loading page template with the same look. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import { STANDARD_USER_AGENT } from './user-agent'
|
||||
const ignoredNavigationFailurePattern = /ERR_ABORTED|ERR_BLOCKED_BY_CLIENT|\((-3|-20)\) loading/i
|
||||
const errorPagePrefix = 'data:text/html'
|
||||
|
||||
const REMOTE_PAGE_LOAD_TIMEOUT_MS = 25_000
|
||||
const BLANK_PAGE_PROBE_DELAY_MS = 7_000
|
||||
const BLANK_PAGE_NODE_THRESHOLD = 3
|
||||
const BLANK_PAGE_TEXT_THRESHOLD = 5
|
||||
@@ -12,8 +13,12 @@ const BLANK_PAGE_TEXT_THRESHOLD = 5
|
||||
interface WindowNavigationState {
|
||||
readyForDetection: boolean
|
||||
lastMainFrameError: string | null
|
||||
loadTimeoutWatchdog: NodeJS.Timeout | null
|
||||
blankPageWatchdog: NodeJS.Timeout | null
|
||||
blankPageRetried: boolean
|
||||
localPageKind: 'loading' | 'error' | null
|
||||
activeTargetURL: string | null
|
||||
activeLoadToken: number
|
||||
context: string
|
||||
}
|
||||
|
||||
@@ -26,6 +31,30 @@ function clearBlankPageWatchdog(state: WindowNavigationState): void {
|
||||
}
|
||||
}
|
||||
|
||||
function clearLoadTimeoutWatchdog(state: WindowNavigationState): void {
|
||||
if (state.loadTimeoutWatchdog) {
|
||||
clearTimeout(state.loadTimeoutWatchdog)
|
||||
state.loadTimeoutWatchdog = null
|
||||
}
|
||||
}
|
||||
|
||||
function clearNavigationWatchdogs(state: WindowNavigationState): void {
|
||||
clearLoadTimeoutWatchdog(state)
|
||||
clearBlankPageWatchdog(state)
|
||||
}
|
||||
|
||||
function isLocalPageURL(url: string): boolean {
|
||||
return url.startsWith(errorPagePrefix)
|
||||
}
|
||||
|
||||
function isIgnoredNavigationFailure(errorCode: number, errorDescription = ''): boolean {
|
||||
return (
|
||||
errorCode === -3 ||
|
||||
errorCode === -20 ||
|
||||
ignoredNavigationFailurePattern.test(`${errorCode}:${errorDescription}`)
|
||||
)
|
||||
}
|
||||
|
||||
async function probePageBodyShape(
|
||||
window: BrowserWindow,
|
||||
): Promise<{ nodeCount: number; textLength: number; readyState: string } | null> {
|
||||
@@ -78,6 +107,12 @@ function scheduleBlankPageWatchdog(window: BrowserWindow, state: WindowNavigatio
|
||||
url: currentURL,
|
||||
...shape,
|
||||
})
|
||||
void showErrorPage(
|
||||
window,
|
||||
state.context,
|
||||
currentURL,
|
||||
'页面加载完成后仍然是空白状态,可能是网络、平台风控、兼容性或远程脚本加载失败导致。',
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -86,7 +121,10 @@ function scheduleBlankPageWatchdog(window: BrowserWindow, state: WindowNavigatio
|
||||
url: currentURL,
|
||||
...shape,
|
||||
})
|
||||
window.webContents.reloadIgnoringCache()
|
||||
void navigateRemoteURL(window, currentURL, state.context, {
|
||||
resetBlankRetry: false,
|
||||
showLoadingPage: true,
|
||||
})
|
||||
})
|
||||
}, BLANK_PAGE_PROBE_DELAY_MS)
|
||||
}
|
||||
@@ -100,6 +138,126 @@ function escapeHtml(value: string): string {
|
||||
.replaceAll("'", ''')
|
||||
}
|
||||
|
||||
function targetHostLabel(targetURL: string): string {
|
||||
try {
|
||||
return new URL(targetURL).host
|
||||
} catch {
|
||||
return targetURL
|
||||
}
|
||||
}
|
||||
|
||||
function loadingPageDataURL(context: string, targetURL: string): string {
|
||||
const html = `<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>${escapeHtml(context)} - 正在打开</title>
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
font-family: "IBM Plex Sans", "Segoe UI Variable", "Segoe UI", sans-serif;
|
||||
background: #f6f7f9;
|
||||
color: #111827;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 24px;
|
||||
background: #f6f7f9;
|
||||
overflow: hidden;
|
||||
}
|
||||
main {
|
||||
width: min(520px, 100%);
|
||||
padding: 28px;
|
||||
border: 1px solid #d9dee7;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
text-align: center;
|
||||
}
|
||||
.spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin: 0 auto;
|
||||
border-radius: 50%;
|
||||
border: 3px solid #d9dee7;
|
||||
border-top-color: #2563eb;
|
||||
animation: spin 0.9s linear infinite;
|
||||
}
|
||||
h1 {
|
||||
margin: 18px 0 0;
|
||||
font-size: 22px;
|
||||
line-height: 1.35;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
p {
|
||||
margin: 10px 0 0;
|
||||
color: #5b6573;
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
code {
|
||||
display: block;
|
||||
margin-top: 16px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 6px;
|
||||
background: #f1f4f8;
|
||||
color: #374151;
|
||||
font-family: "SFMono-Regular", Consolas, monospace;
|
||||
font-size: 13px;
|
||||
word-break: break-all;
|
||||
}
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root,
|
||||
body {
|
||||
background: #111827;
|
||||
color: #f9fafb;
|
||||
}
|
||||
main {
|
||||
border-color: #2f3948;
|
||||
background: #182130;
|
||||
}
|
||||
p {
|
||||
color: #b6bfcb;
|
||||
}
|
||||
code {
|
||||
background: #111827;
|
||||
color: #d1d5db;
|
||||
}
|
||||
.spinner {
|
||||
border-color: #344154;
|
||||
border-top-color: #60a5fa;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="spinner" aria-hidden="true"></div>
|
||||
<h1>正在打开${escapeHtml(context)}</h1>
|
||||
<p>如果平台网络较慢,窗口会停留在这里;加载失败时会给出重试入口。</p>
|
||||
<code>${escapeHtml(targetHostLabel(targetURL))}</code>
|
||||
</main>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
return `data:text/html;charset=UTF-8,${encodeURIComponent(html)}`
|
||||
}
|
||||
|
||||
function errorPageDataURL(context: string, targetURL: string, message: string): string {
|
||||
const html = `<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
@@ -111,6 +269,15 @@ function errorPageDataURL(context: string, targetURL: string, message: string):
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
font-family: "IBM Plex Sans", "Segoe UI Variable", "Segoe UI", sans-serif;
|
||||
background: #f6f7f9;
|
||||
color: #111827;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
@@ -118,26 +285,21 @@ function errorPageDataURL(context: string, targetURL: string, message: string):
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 24px;
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(15, 118, 110, 0.12), transparent 28%),
|
||||
radial-gradient(circle at top right, rgba(37, 99, 235, 0.1), transparent 24%),
|
||||
linear-gradient(180deg, #eef3f8, #e4ebf3);
|
||||
color: #0f172a;
|
||||
background: #f6f7f9;
|
||||
}
|
||||
.card {
|
||||
width: min(680px, 100%);
|
||||
padding: 24px;
|
||||
border-radius: 24px;
|
||||
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
box-shadow: 0 18px 42px rgba(15, 23, 42, 0.12);
|
||||
border-radius: 8px;
|
||||
border: 1px solid #d9dee7;
|
||||
background: #ffffff;
|
||||
}
|
||||
h1, p, pre {
|
||||
margin: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
letter-spacing: -0.04em;
|
||||
font-size: 24px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
p {
|
||||
margin-top: 12px;
|
||||
@@ -147,8 +309,8 @@ function errorPageDataURL(context: string, targetURL: string, message: string):
|
||||
pre {
|
||||
margin-top: 16px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 16px;
|
||||
background: rgba(15, 23, 42, 0.05);
|
||||
border-radius: 6px;
|
||||
background: #f1f4f8;
|
||||
overflow: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
@@ -157,42 +319,70 @@ function errorPageDataURL(context: string, targetURL: string, message: string):
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
margin-top: 18px;
|
||||
}
|
||||
button,
|
||||
a {
|
||||
button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 44px;
|
||||
padding: 0 16px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||
border-radius: 8px;
|
||||
border: 1px solid #d9dee7;
|
||||
background: white;
|
||||
color: #0f172a;
|
||||
text-decoration: none;
|
||||
font: inherit;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.72;
|
||||
}
|
||||
.primary {
|
||||
background: linear-gradient(180deg, #44a7ff, #1b6cff);
|
||||
border-color: rgba(27, 108, 255, 0.18);
|
||||
background: #2563eb;
|
||||
border-color: #2563eb;
|
||||
color: white;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root,
|
||||
body {
|
||||
background: #111827;
|
||||
color: #f9fafb;
|
||||
}
|
||||
.card {
|
||||
border-color: #2f3948;
|
||||
background: #182130;
|
||||
}
|
||||
p {
|
||||
color: #b6bfcb;
|
||||
}
|
||||
pre {
|
||||
background: #111827;
|
||||
}
|
||||
button {
|
||||
border-color: #344154;
|
||||
background: #111827;
|
||||
color: #f9fafb;
|
||||
}
|
||||
.primary {
|
||||
border-color: #3b82f6;
|
||||
background: #3b82f6;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="card">
|
||||
<h1>授权页加载失败</h1>
|
||||
<p>${escapeHtml(context)} 在打开远程页面时出现网络或 TLS 握手异常。窗口已保留,不再直接闪退。</p>
|
||||
<p>如果这是偶发网络抖动,点“重新加载”即可;如果持续失败,再看控制台里打印的 URL 和错误码定位。</p>
|
||||
<p>${escapeHtml(context)} 在打开远程页面时遇到网络、TLS、平台脚本或兼容性异常。窗口已保留,可以直接重试。</p>
|
||||
<p>如果持续失败,请检查代理、DNS、系统时间和平台是否触发风控。</p>
|
||||
<pre>${escapeHtml(message)}
|
||||
|
||||
${escapeHtml(targetURL)}</pre>
|
||||
<div class="actions">
|
||||
<button class="primary" onclick="location.href = ${JSON.stringify(targetURL)}">重新加载</button>
|
||||
<a href=${JSON.stringify(targetURL)} target="_self">打开目标地址</a>
|
||||
<button class="primary" onclick="this.disabled = true; this.textContent = '正在重试...'; location.replace(${JSON.stringify(targetURL)})">重新加载</button>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
@@ -201,12 +391,160 @@ ${escapeHtml(targetURL)}</pre>
|
||||
return `data:text/html;charset=UTF-8,${encodeURIComponent(html)}`
|
||||
}
|
||||
|
||||
async function showLocalPage(
|
||||
window: BrowserWindow,
|
||||
pageURL: string,
|
||||
kind: WindowNavigationState['localPageKind'],
|
||||
): Promise<void> {
|
||||
if (window.isDestroyed()) {
|
||||
return
|
||||
}
|
||||
|
||||
const state = navigationStateRegistry.get(window)
|
||||
if (state) {
|
||||
state.localPageKind = kind
|
||||
}
|
||||
|
||||
try {
|
||||
await window.loadURL(pageURL)
|
||||
} catch (error) {
|
||||
if (!window.isDestroyed()) {
|
||||
console.warn('[desktop-window] local page load failed', {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function showErrorPage(
|
||||
window: BrowserWindow,
|
||||
context: string,
|
||||
targetURL: string,
|
||||
message: string,
|
||||
): Promise<void> {
|
||||
if (window.isDestroyed()) {
|
||||
return
|
||||
}
|
||||
|
||||
const state = navigationStateRegistry.get(window)
|
||||
if (state) {
|
||||
state.readyForDetection = false
|
||||
state.lastMainFrameError = message
|
||||
clearNavigationWatchdogs(state)
|
||||
}
|
||||
|
||||
await showLocalPage(window, errorPageDataURL(context, targetURL, message), 'error')
|
||||
}
|
||||
|
||||
function scheduleRemoteLoadTimeout(
|
||||
window: BrowserWindow,
|
||||
state: WindowNavigationState,
|
||||
targetURL: string,
|
||||
context: string,
|
||||
loadToken: number,
|
||||
): void {
|
||||
clearLoadTimeoutWatchdog(state)
|
||||
state.loadTimeoutWatchdog = setTimeout(() => {
|
||||
if (window.isDestroyed()) {
|
||||
return
|
||||
}
|
||||
|
||||
const latestState = navigationStateRegistry.get(window)
|
||||
if (!latestState || latestState.activeLoadToken !== loadToken) {
|
||||
return
|
||||
}
|
||||
|
||||
const currentURL = window.webContents.getURL()
|
||||
if (isLocalPageURL(currentURL) && latestState.localPageKind === 'error') {
|
||||
return
|
||||
}
|
||||
|
||||
console.warn(`[desktop-window] ${context} remote page load timed out`, {
|
||||
targetURL,
|
||||
currentURL,
|
||||
timeoutMs: REMOTE_PAGE_LOAD_TIMEOUT_MS,
|
||||
})
|
||||
void showErrorPage(
|
||||
window,
|
||||
context,
|
||||
targetURL,
|
||||
`页面加载超过 ${Math.round(REMOTE_PAGE_LOAD_TIMEOUT_MS / 1000)} 秒,可能是网络连接或平台服务暂时不可用。`,
|
||||
)
|
||||
}, REMOTE_PAGE_LOAD_TIMEOUT_MS)
|
||||
}
|
||||
|
||||
async function navigateRemoteURL(
|
||||
window: BrowserWindow,
|
||||
targetURL: string,
|
||||
context: string,
|
||||
options: { resetBlankRetry: boolean; showLoadingPage: boolean },
|
||||
): Promise<void> {
|
||||
const state = navigationStateRegistry.get(window)
|
||||
const loadToken = (state?.activeLoadToken ?? 0) + 1
|
||||
|
||||
if (state) {
|
||||
state.activeLoadToken = loadToken
|
||||
state.activeTargetURL = targetURL
|
||||
state.readyForDetection = false
|
||||
if (options.resetBlankRetry) {
|
||||
state.blankPageRetried = false
|
||||
}
|
||||
clearNavigationWatchdogs(state)
|
||||
}
|
||||
|
||||
if (options.showLoadingPage) {
|
||||
await showLocalPage(window, loadingPageDataURL(context, targetURL), 'loading')
|
||||
}
|
||||
|
||||
if (window.isDestroyed()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (state && state.activeLoadToken === loadToken) {
|
||||
scheduleRemoteLoadTimeout(window, state, targetURL, context, loadToken)
|
||||
}
|
||||
|
||||
try {
|
||||
await window.loadURL(targetURL, { userAgent: STANDARD_USER_AGENT })
|
||||
const latestState = navigationStateRegistry.get(window)
|
||||
if (latestState?.activeLoadToken === loadToken) {
|
||||
clearLoadTimeoutWatchdog(latestState)
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
const latestState = navigationStateRegistry.get(window)
|
||||
if (latestState?.activeLoadToken === loadToken) {
|
||||
clearLoadTimeoutWatchdog(latestState)
|
||||
}
|
||||
|
||||
if (ignoredNavigationFailurePattern.test(message) || window.isDestroyed()) {
|
||||
return
|
||||
}
|
||||
|
||||
console.error(`[desktop-window] ${context} loadURL failed`, {
|
||||
targetURL,
|
||||
message,
|
||||
})
|
||||
|
||||
const currentURL = window.webContents.getURL()
|
||||
if (isLocalPageURL(currentURL) && latestState?.localPageKind === 'error') {
|
||||
return
|
||||
}
|
||||
|
||||
await showErrorPage(window, context, targetURL, message)
|
||||
}
|
||||
}
|
||||
|
||||
export function attachWindowDiagnostics(window: BrowserWindow, context: string): void {
|
||||
navigationStateRegistry.set(window, {
|
||||
readyForDetection: false,
|
||||
lastMainFrameError: null,
|
||||
loadTimeoutWatchdog: null,
|
||||
blankPageWatchdog: null,
|
||||
blankPageRetried: false,
|
||||
localPageKind: null,
|
||||
activeTargetURL: null,
|
||||
activeLoadToken: 0,
|
||||
context,
|
||||
})
|
||||
|
||||
@@ -219,10 +557,36 @@ export function attachWindowDiagnostics(window: BrowserWindow, context: string):
|
||||
clearBlankPageWatchdog(state)
|
||||
})
|
||||
|
||||
window.webContents.on(
|
||||
'did-start-navigation',
|
||||
(_event, url, isInPlace, isMainFrame) => {
|
||||
if (!isMainFrame || isInPlace || !/^https?:\/\//i.test(url)) {
|
||||
return
|
||||
}
|
||||
|
||||
const state = navigationStateRegistry.get(window)
|
||||
if (!state) {
|
||||
return
|
||||
}
|
||||
|
||||
const fromLocalPage = isLocalPageURL(window.webContents.getURL())
|
||||
state.readyForDetection = false
|
||||
state.activeTargetURL = url
|
||||
if (fromLocalPage) {
|
||||
state.localPageKind = 'loading'
|
||||
}
|
||||
|
||||
if (fromLocalPage || !state.loadTimeoutWatchdog) {
|
||||
state.activeLoadToken += 1
|
||||
scheduleRemoteLoadTimeout(window, state, url, context, state.activeLoadToken)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
window.webContents.on(
|
||||
'did-fail-load',
|
||||
(_event, errorCode, errorDescription, validatedURL, isMainFrame) => {
|
||||
if (!isMainFrame || errorCode === -3) {
|
||||
if (!isMainFrame || isIgnoredNavigationFailure(errorCode, errorDescription)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -230,7 +594,7 @@ export function attachWindowDiagnostics(window: BrowserWindow, context: string):
|
||||
if (state) {
|
||||
state.readyForDetection = false
|
||||
state.lastMainFrameError = `${errorCode}:${errorDescription}`
|
||||
clearBlankPageWatchdog(state)
|
||||
clearNavigationWatchdogs(state)
|
||||
}
|
||||
|
||||
console.warn(`[desktop-window] ${context} main-frame load failed`, {
|
||||
@@ -238,6 +602,9 @@ export function attachWindowDiagnostics(window: BrowserWindow, context: string):
|
||||
errorDescription,
|
||||
validatedURL,
|
||||
})
|
||||
|
||||
const targetURL = validatedURL || state?.activeTargetURL || window.webContents.getURL()
|
||||
void showErrorPage(window, context, targetURL, `${errorCode}: ${errorDescription}`)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -248,7 +615,12 @@ export function attachWindowDiagnostics(window: BrowserWindow, context: string):
|
||||
}
|
||||
|
||||
const currentURL = window.webContents.getURL()
|
||||
state.readyForDetection = !currentURL.startsWith(errorPagePrefix)
|
||||
clearLoadTimeoutWatchdog(state)
|
||||
const isLocalPage = isLocalPageURL(currentURL)
|
||||
state.readyForDetection = !isLocalPage
|
||||
if (!isLocalPage) {
|
||||
state.localPageKind = null
|
||||
}
|
||||
if (state.readyForDetection) {
|
||||
state.lastMainFrameError = null
|
||||
scheduleBlankPageWatchdog(window, state)
|
||||
@@ -259,7 +631,7 @@ export function attachWindowDiagnostics(window: BrowserWindow, context: string):
|
||||
const state = navigationStateRegistry.get(window)
|
||||
if (state) {
|
||||
state.readyForDetection = false
|
||||
clearBlankPageWatchdog(state)
|
||||
clearNavigationWatchdogs(state)
|
||||
}
|
||||
|
||||
console.error(`[desktop-window] ${context} renderer gone`, details)
|
||||
@@ -268,7 +640,7 @@ export function attachWindowDiagnostics(window: BrowserWindow, context: string):
|
||||
window.once('closed', () => {
|
||||
const state = navigationStateRegistry.get(window)
|
||||
if (state) {
|
||||
clearBlankPageWatchdog(state)
|
||||
clearNavigationWatchdogs(state)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -276,7 +648,7 @@ export function attachWindowDiagnostics(window: BrowserWindow, context: string):
|
||||
export function isWindowReadyForDetection(window: BrowserWindow): boolean {
|
||||
const state = navigationStateRegistry.get(window)
|
||||
if (!state) {
|
||||
return !window.webContents.getURL().startsWith(errorPagePrefix)
|
||||
return !isLocalPageURL(window.webContents.getURL())
|
||||
}
|
||||
return state.readyForDetection
|
||||
}
|
||||
@@ -286,39 +658,8 @@ 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) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
|
||||
if (ignoredNavigationFailurePattern.test(message) || window.isDestroyed()) {
|
||||
return
|
||||
}
|
||||
|
||||
console.error(`[desktop-window] ${context} loadURL failed`, {
|
||||
targetURL,
|
||||
message,
|
||||
})
|
||||
|
||||
if (window.webContents.getURL().startsWith(errorPagePrefix)) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await window.loadURL(errorPageDataURL(context, targetURL, message))
|
||||
} catch (fallbackError) {
|
||||
const fallbackMessage =
|
||||
fallbackError instanceof Error ? fallbackError.message : String(fallbackError)
|
||||
console.error(`[desktop-window] ${context} error-page loadURL failed`, {
|
||||
targetURL,
|
||||
message: fallbackMessage,
|
||||
})
|
||||
}
|
||||
}
|
||||
await navigateRemoteURL(window, targetURL, context, {
|
||||
resetBlankRetry: true,
|
||||
showLoadingPage: true,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user