Compare commits
2 Commits
0f3a9a977e
...
8396bdeb48
| Author | SHA1 | Date | |
|---|---|---|---|
| 8396bdeb48 | |||
| cb42d4b662 |
@@ -29,6 +29,12 @@ export default defineConfig({
|
|||||||
plugins: [externalizeDepsPlugin({ exclude: workspacePackages })],
|
plugins: [externalizeDepsPlugin({ exclude: workspacePackages })],
|
||||||
build: {
|
build: {
|
||||||
sourcemap: 'hidden',
|
sourcemap: 'hidden',
|
||||||
|
// Disable minification for the main process: the bundle is only loaded
|
||||||
|
// once on app startup so size is irrelevant, and minified short names
|
||||||
|
// break Playwright `page.evaluate` because the function is serialized
|
||||||
|
// via `.toString()` and helper identifiers (e.g. `__name` renamed to
|
||||||
|
// `FK`) become undefined in the browser context.
|
||||||
|
minify: false,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: {
|
input: {
|
||||||
bootstrap: resolve(rootDir, 'src/main/bootstrap.ts'),
|
bootstrap: resolve(rootDir, 'src/main/bootstrap.ts'),
|
||||||
@@ -51,6 +57,7 @@ export default defineConfig({
|
|||||||
plugins: [externalizeDepsPlugin({ exclude: workspacePackages })],
|
plugins: [externalizeDepsPlugin({ exclude: workspacePackages })],
|
||||||
build: {
|
build: {
|
||||||
sourcemap: 'hidden',
|
sourcemap: 'hidden',
|
||||||
|
minify: false,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: {
|
input: {
|
||||||
bridge: resolve(rootDir, 'src/preload/bridge.ts'),
|
bridge: resolve(rootDir, 'src/preload/bridge.ts'),
|
||||||
|
|||||||
@@ -2,16 +2,95 @@ import type { BrowserWindow } from 'electron/main'
|
|||||||
|
|
||||||
import { STANDARD_USER_AGENT } from './user-agent'
|
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 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 {
|
interface WindowNavigationState {
|
||||||
readyForDetection: boolean
|
readyForDetection: boolean
|
||||||
lastMainFrameError: string | null
|
lastMainFrameError: string | null
|
||||||
|
blankPageWatchdog: NodeJS.Timeout | null
|
||||||
|
blankPageRetried: boolean
|
||||||
|
context: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const navigationStateRegistry = new WeakMap<BrowserWindow, WindowNavigationState>()
|
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 {
|
function escapeHtml(value: string): string {
|
||||||
return value
|
return value
|
||||||
.replaceAll('&', '&')
|
.replaceAll('&', '&')
|
||||||
@@ -126,6 +205,9 @@ export function attachWindowDiagnostics(window: BrowserWindow, context: string):
|
|||||||
navigationStateRegistry.set(window, {
|
navigationStateRegistry.set(window, {
|
||||||
readyForDetection: false,
|
readyForDetection: false,
|
||||||
lastMainFrameError: null,
|
lastMainFrameError: null,
|
||||||
|
blankPageWatchdog: null,
|
||||||
|
blankPageRetried: false,
|
||||||
|
context,
|
||||||
})
|
})
|
||||||
|
|
||||||
window.webContents.on('did-start-loading', () => {
|
window.webContents.on('did-start-loading', () => {
|
||||||
@@ -134,6 +216,7 @@ export function attachWindowDiagnostics(window: BrowserWindow, context: string):
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
state.readyForDetection = false
|
state.readyForDetection = false
|
||||||
|
clearBlankPageWatchdog(state)
|
||||||
})
|
})
|
||||||
|
|
||||||
window.webContents.on(
|
window.webContents.on(
|
||||||
@@ -147,6 +230,7 @@ export function attachWindowDiagnostics(window: BrowserWindow, context: string):
|
|||||||
if (state) {
|
if (state) {
|
||||||
state.readyForDetection = false
|
state.readyForDetection = false
|
||||||
state.lastMainFrameError = `${errorCode}:${errorDescription}`
|
state.lastMainFrameError = `${errorCode}:${errorDescription}`
|
||||||
|
clearBlankPageWatchdog(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.warn(`[desktop-window] ${context} main-frame load failed`, {
|
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)
|
state.readyForDetection = !currentURL.startsWith(errorPagePrefix)
|
||||||
if (state.readyForDetection) {
|
if (state.readyForDetection) {
|
||||||
state.lastMainFrameError = null
|
state.lastMainFrameError = null
|
||||||
|
scheduleBlankPageWatchdog(window, state)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -174,10 +259,18 @@ export function attachWindowDiagnostics(window: BrowserWindow, context: string):
|
|||||||
const state = navigationStateRegistry.get(window)
|
const state = navigationStateRegistry.get(window)
|
||||||
if (state) {
|
if (state) {
|
||||||
state.readyForDetection = false
|
state.readyForDetection = false
|
||||||
|
clearBlankPageWatchdog(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error(`[desktop-window] ${context} renderer gone`, details)
|
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 {
|
export function isWindowReadyForDetection(window: BrowserWindow): boolean {
|
||||||
@@ -193,6 +286,12 @@ export async function loadWindowURLSafely(
|
|||||||
targetURL: string,
|
targetURL: string,
|
||||||
context: string,
|
context: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const state = navigationStateRegistry.get(window)
|
||||||
|
if (state) {
|
||||||
|
state.blankPageRetried = false
|
||||||
|
clearBlankPageWatchdog(state)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await window.loadURL(targetURL, { userAgent: STANDARD_USER_AGENT })
|
await window.loadURL(targetURL, { userAgent: STANDARD_USER_AGENT })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user