perf: 优化错误页

This commit is contained in:
kuaifan 2025-08-19 16:13:17 +08:00
parent bb83875c99
commit f534f012d2
3 changed files with 219 additions and 20 deletions

75
electron/electron.js vendored
View File

@ -23,7 +23,7 @@ const {
nativeTheme,
Tray,
Menu,
BrowserView,
WebContentsView,
BrowserWindow
} = require('electron')
@ -515,6 +515,9 @@ function createChildWindow(args) {
contextIsolation: true,
}, webPreferences),
}, config)
if (!options.webPreferences.contextIsolation) {
delete options.webPreferences.preload;
}
if (options.parent) {
options.parent = mainWindow
}
@ -838,17 +841,16 @@ function createWebTabWindow(args) {
webTabWindow.show();
// 创建 tab 子窗口
const viewOptions = Object.assign({
useHTMLTitleAndIcon: true,
useLoadingView: true,
useErrorView: true,
}, args.config || {})
const viewOptions = args.config || {}
viewOptions.webPreferences = Object.assign({
preload: path.join(__dirname, 'electron-preload.js'),
nodeIntegration: true,
contextIsolation: true
}, args.webPreferences || {})
const browserView = new BrowserView(viewOptions)
if (!viewOptions.webPreferences.contextIsolation) {
delete viewOptions.webPreferences.preload;
}
const browserView = new WebContentsView(viewOptions)
if (args.backgroundColor) {
browserView.setBackgroundColor(args.backgroundColor)
} else if (nativeTheme.shouldUseDarkColors) {
@ -885,6 +887,20 @@ function createWebTabWindow(args) {
if (!errorDescription) {
return
}
// 主框架加载失败时,展示内置的错误页面
if (isMainFrame) {
const originalUrl = validatedURL || args.url || ''
const filePath = path.join(__dirname, 'render', 'tabs', 'error.html')
browserView.webContents.loadFile(filePath, {
query: {
id: String(browserView.webContents.id),
url: originalUrl,
code: String(errorCode),
desc: errorDescription,
}
}).then(_ => { }).catch(_ => { })
return
}
utils.onDispatchEvent(webTabWindow.webContents, {
event: 'title',
id: browserView.webContents.id,
@ -900,6 +916,9 @@ function createWebTabWindow(args) {
}).then(_ => { })
})
browserView.webContents.on('did-start-loading', _ => {
webTabView.forEach(({id: vid, view}) => {
view.setVisible(vid === browserView.webContents.id)
})
utils.onDispatchEvent(webTabWindow.webContents, {
event: 'start-loading',
id: browserView.webContents.id,
@ -933,8 +952,9 @@ function createWebTabWindow(args) {
electronMenu.webContentsMenu(browserView.webContents, true)
browserView.webContents.loadURL(args.url).then(_ => { }).catch(_ => { })
browserView.setVisible(true)
webTabWindow.addBrowserView(browserView)
webTabWindow.contentView.addChildView(browserView)
webTabView.push({
id: browserView.webContents.id,
view: browserView
@ -950,15 +970,36 @@ function createWebTabWindow(args) {
/**
* 获取当前内置浏览器标签
* @returns {Electron.BrowserView|undefined}
* @returns {Electron.WebContentsView|undefined}
*/
function currentWebTab() {
const views = webTabWindow.getBrowserViews()
const view = views.length ? views[views.length - 1] : undefined
if (!view) {
return undefined
// 第一:使用当前可见的标签
try {
const item = webTabView.find(({view}) => view?.getVisible && view.getVisible())
if (item) {
return item
}
} catch (e) {}
// 第二:使用当前聚焦的 webContents
try {
const focused = webContents.getFocusedWebContents?.()
if (focused) {
const item = webTabView.find(it => it.id === focused.id)
if (item) {
return item
}
}
} catch (e) {}
// 兜底:根据 children 顺序选择最上层的可用视图
const children = webTabWindow.contentView.children || []
for (let i = children.length - 1; i >= 0; i--) {
const id = children[i]?.webContents?.id
const item = webTabView.find(it => it.id === id)
if (item) {
return item
}
}
return webTabView.find(item => item.id == view.webContents.id)
return undefined
}
/**
@ -1011,8 +1052,10 @@ function activateWebTab(id) {
if (!item) {
return
}
webTabView.forEach(({id: vid, view}) => {
view.setVisible(vid === item.id)
})
resizeWebTab(item.id)
webTabWindow.setTopBrowserView(item.view)
item.view.webContents.focus()
utils.onDispatchEvent(webTabWindow.webContents, {
event: 'switch',
@ -1032,7 +1075,7 @@ function closeWebTab(id) {
if (webTabView.length === 1) {
webTabWindow.hide()
}
webTabWindow.removeBrowserView(item.view)
webTabWindow.contentView.removeChildView(item.view)
try {
item.view.webContents.close()
} catch (e) {

View File

@ -159,8 +159,8 @@ body {
.tab-icon {
display: inline-block;
flex-shrink: 0;
width: 18px;
height: 18px;
width: 16px;
height: 16px;
background-size: cover;
}
@ -207,8 +207,7 @@ body {
.tab-title {
display: inline-block;
flex: 1;
margin-right: 8px;
margin-left: 6px;
margin: 0 8px;
overflow: hidden;
line-height: 150%;
text-overflow: ellipsis;

View File

@ -0,0 +1,157 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>LOAD FAILED</title>
<style>
:root {
--bg: #ffffff;
--fg: #1f2328;
--muted: #6a737d;
--border: #e1e4e8;
--btn: #84c56a;
--btn-fg: #ffffff;
--btn-outline: #d0e2ff;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #0D0D0D;
--fg: #e6edf3;
--muted: #9aa7b2;
--border: #30363d;
--btn: #84c56a;
--btn-fg: #ffffff;
--btn-outline: #84c56a44;
}
}
html,
body {
height: 100%;
}
body {
margin: 0;
background: var(--bg);
color: var(--fg);
font: 14px/1.5 -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
display: grid;
place-items: center;
}
.card {
width: min(680px, calc(100% - 32px));
padding: 20px;
box-sizing: border-box;
}
h1 {
margin: 0 0 12px;
font-size: 18px;
}
p {
margin: 0 0 12px;
color: var(--muted);
}
code {
background: var(--btn-outline);
padding: 2px 6px;
border-radius: 6px;
}
.row {
display: flex;
margin-bottom: 8px;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
.url {
overflow-wrap: anywhere;
}
.actions {
margin-top: 14px;
display: flex;
gap: 12px;
flex-wrap: wrap;
}
button {
appearance: none;
border: 1px solid var(--btn);
background: var(--btn);
color: var(--btn-fg);
padding: 8px 24px;
border-radius: 8px;
cursor: pointer;
}
button.secondary {
background: transparent;
color: var(--fg);
border-color: var(--border);
}
</style>
<script>
function qs(key) {
return new URLSearchParams(location.search).get(key) || ''
}
function setText(id, text) {
var el = document.getElementById(id);
if (el) {
el.textContent = text
}
}
function retry() {
var u = qs('url');
if (u) {
location.href = u
}
}
function closeTab() {
window.close()
}
document.addEventListener('DOMContentLoaded', function () {
setText('url', qs('url'))
setText('code', qs('code'))
setText('desc', qs('desc'))
})
</script>
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline';">
<meta name="color-scheme" content="light dark">
<meta name="referrer" content="no-referrer">
<meta name="robots" content="noindex">
<meta name="format-detection" content="telephone=no,email=no,address=no">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#00000000">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Load Error">
</head>
<body>
<div class="card">
<h1>LOAD FAILED</h1>
<div class="row">URL: <span id="url" class="url"></span></div>
<div class="row">Error code: <code id="code"></code></div>
<p id="desc"></p>
<div class="actions">
<button onclick="retry()">Retry</button>
<button class="secondary" onclick="closeTab()">Close</button>
</div>
</div>
</body>
</html>