Electron WebContents API 知识点总结
目录
1. 概述
webContents 是什么?
webContents 是负责渲染和控制网页的模块,是 BrowserWindow 对象的属性。
┌─────────────────────────────────────────────────────────┐
│ BrowserWindow │
│ ┌─────────────────────────────────────────────────┐ │
│ │ webContents │ │
│ │ │ │
│ │ • 加载页面 (loadURL, loadFile) │ │
│ │ • 导航控制 (goBack, goForward) │ │
│ │ • 页面操作 (print, savePage) │ │
│ │ • 开发者工具 (openDevTools) │ │
│ │ │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
获取 webContents
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({ width: 800, height: 600 })
const contents = win.webContents
const { webContents } = require('electron')
const contents = webContents.fromId(1)
2. 导航事件流程
2.1 完整导航事件顺序
文档导航 (完整页面加载)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. did-start-loading 开始加载
↓
2. will-frame-navigate 任何 frame 准备导航 (可取消)
↓
3. will-navigate 主 frame 准备导航 (可取消)
↓
4. will-redirect 重定向发生时 (可取消)
↓
5. did-redirect-navigation 重定向完成
↓
6. did-frame-navigate 任意 frame 导航完成
↓
7. did-navigate 主 frame 导航完成
↓
8. did-finish-load 加载完成
↓
9. did-stop-loading 停止加载
↓
10. dom-ready DOM 准备就绪
2.2 页面内导航
页面内导航 (不刷新页面)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. did-start-loading
↓
2. did-navigate-in-page 页面内导航完成
↓
3. did-stop-loading
2.3 事件对比
| 事件 | 触发时机 | 可取消 | 适用场景 |
|---|
will-navigate | 主 frame 导航前 | ✅ | 拦截主 frame 导航 |
will-frame-navigate | 任意 frame 导航前 | ✅ | 拦截所有 frame 导航 |
will-redirect | 重定向发生前 | ✅ | 阻止重定向 |
did-navigate | 主 frame 导航完成 | ❌ | 记录导航完成 |
did-frame-navigate | 任意 frame 导航完成 | ❌ | 记录 frame 导航 |
did-navigate-in-page | 页面内导航 | ❌ | hashchange, pushState |
2.4 导航事件示例
const { BrowserWindow } = require('electron')
const win = new BrowserWindow()
const { webContents } = win
webContents.on('will-navigate', (event, url) => {
console.log('准备导航到:', url)
if (!url.startsWith('https://myapp.com')) {
event.preventDefault()
console.log('已阻止导航')
}
})
webContents.on('did-navigate', (event, url, httpResponseCode) => {
console.log('导航完成:', url)
console.log('HTTP 状态码:', httpResponseCode)
})
webContents.on('did-navigate-in-page', (event, url, isMainFrame) => {
console.log('页面内导航:', url)
})
3. 页面加载事件
3.1 加载状态事件
| 事件 | 说明 |
|---|
did-start-loading | 加载开始(spinner 开始旋转) |
did-stop-loading | 加载结束(spinner 停止旋转) |
did-finish-load | 加载完成 |
did-fail-load | 加载失败 |
did-fail-provisional-load | 加载取消或失败 |
did-frame-finish-load | Frame 加载完成 |
dom-ready | DOM 准备就绪 |
3.2 示例
webContents.on('did-finish-load', () => {
console.log('页面加载完成')
})
webContents.on('did-fail-load', (event, errorCode, errorDescription) => {
console.log('加载失败:', errorDescription)
})
webContents.on('dom-ready', () => {
console.log('DOM 已就绪,可以操作页面元素')
})
4. 窗口管理事件
4.1 页面创建事件
webContents.on('did-create-window', (window, details) => {
console.log('新窗口创建:')
console.log(' URL:', details.url)
console.log(' 名称:', details.frameName)
console.log(' 打开方式:', details.disposition)
window.webContents.on('did-finish-load', () => {
window.setTitle('新窗口标题')
})
})
4.2 窗口移动/调整大小
webContents.on('content-bounds-updated', (event, bounds) => {
console.log('窗口新边界:', bounds)
})
5. 渲染进程事件
5.1 进程状态事件
| 事件 | 说明 | 原因 |
|---|
render-process-gone | 渲染进程消失 | 崩溃、被杀、OOM |
unresponsive | 页面无响应 | JS 执行时间过长 |
responsive | 页面恢复响应 | JS 执行完成 |
crashed | 渲染进程崩溃(已废弃) | - |
webContents.on('render-process-gone', (event, details) => {
console.log('渲染进程消失:', details.reason)
if (details.reason === 'crashed') {
win.loadURL('error.html')
}
})
webContents.on('unresponsive', () => {
console.log('页面无响应')
})
webContents.on('responsive', () => {
console.log('页面恢复响应')
})
5.2 离开页面事件
webContents.on('will-prevent-unload', (event) => {
const choice = dialog.showMessageBoxSync(win, {
type: 'question',
buttons: ['离开', '留下'],
message: '您确定要离开吗?'
})
if (choice === 1) {
event.preventDefault()
}
})
6. 静态方法
6.1 获取所有/聚焦的 WebContents
const { webContents } = require('electron')
const allContents = webContents.getAllWebContents()
console.log('所有 WebContents 数量:', allContents.length)
const focused = webContents.getFocusedWebContents()
6.2 通过 ID 获取
const contents = webContents.fromId(1)
const contents = webContents.fromFrame(frame)
const contents = webContents.fromDevToolsTargetId(targetId)
7. 实例方法
7.1 页面加载
await webContents.loadURL('https://example.com')
await webContents.loadFile('./index.html')
await webContents.loadURL('https://example.com', {
httpReferrer: 'https://google.com',
userAgent: 'Mozilla/5.0',
extraHeaders: 'X-Custom-Header: value'
})
7.2 导航控制
webContents.goBack()
webContents.goForward()
webContents.goToOffset(offset)
webContents.goToIndex(index)
const history = webContents.getAllShared逸s()
7.3 页面操作
webContents.print(options, callback)
webContents.savePage(fullPath, saveType, callback)
webContents.setZoomFactor(1.5)
webContents.setZoomLevel(1)
webContents.getZoomFactor()
webContents.getZoomLevel()
webContents.focus()
7.4 开发者工具
webContents.openDevTools()
webContents.closeDevTools()
webContents.isDevToolsOpened()
webContents.isDevToolsFocused()
7.5 JavaScript 执行
webContents.executeJavaScript(`
document.title = 'New Title'
`).then(result => {
console.log('执行结果:', result)
})
webContents.on('did-finish-load', () => {
webContents.executeJavaScript('console.log("页面加载完成")')
})
7.6 发送消息到渲染进程
webContents.send('channel', ...args)
webContents.send('update-data', { name: '张三', age: 25 })
8. 实际应用示例
8.1 页面加载监控
class PageLoader {
constructor(webContents) {
this.webContents = webContents
this.setupListeners()
}
setupListeners() {
this.webContents.on('did-start-loading', () => {
console.log('🔄 开始加载...')
this.updateLoadingUI(true)
})
this.webContents.on('did-finish-load', () => {
console.log('✅ 加载完成')
this.updateLoadingUI(false)
})
this.webContents.on('did-fail-load', (event, code, desc) => {
console.log(`❌ 加载失败: ${desc}`)
this.showErrorUI(desc)
})
this.webContents.on('render-process-gone', (event, details) => {
console.log(`💥 进程消失: ${details.reason}`)
if (details.reason === 'crashed') {
this.showCrashUI()
}
})
}
updateLoadingUI(isLoading) { }
showErrorUI(error) { }
showCrashUI() { }
}
8.2 导航拦截
class NavigationGuard {
constructor(webContents, allowedDomains) {
this.webContents = webContents
this.allowedDomains = allowedDomains
this.setupGuard()
}
setupGuard() {
this.webContents.on('will-navigate', (event, url) => {
const parsedUrl = new URL(url)
if (!this.allowedDomains.includes(parsedUrl.hostname)) {
event.preventDefault()
console.log(`⛔ 阻止跳转到: ${url}`)
}
})
this.webContents.on('will-redirect', (event, url) => {
console.log(`🔄 重定向到: ${url}`)
})
}
}
const guard = new NavigationGuard(win.webContents, [
'myapp.com',
'api.myapp.com'
])
8.3 自动刷新页面
class HotReload {
constructor(webContents) {
this.webContents = webContents
this.enabled = false
}
enable() {
this.enabled = true
this.webContents.on('did-finish-load', () => {
if (this.enabled) {
this.webContents.reload()
}
})
}
disable() {
this.enabled = false
}
}
8.4 注入脚本
async function injectScripts(webContents) {
await webContents.whenReady()
await webContents.executeJavaScript(`
// 添加样式
const style = document.createElement('style')
style.textContent = '.debug { border: 1px solid red; }'
document.head.appendChild(style)
// 添加功能
window.showDebug = true
// 返回结果
true
`)
console.log('脚本注入完成')
}
8.5 窗口打开控制
webContents.setWindowOpenHandler(({ url, frameName, features, disposition }) => {
console.log('window.open 被调用:')
console.log(' URL:', url)
console.log(' 名称:', frameName)
console.log(' 方式:', disposition)
if (url.startsWith('https://trusted.com')) {
return {
action: 'allow',
overrideBrowserWindowOptions: {
width: 800,
height: 600,
title: frameName
}
}
} else {
return { action: 'deny' }
}
})
9. 安全考虑
9.1 危险操作
webContents.setIgnoreMenuShortcut(true)
webContents.pageZoomIn()
webContents.enablePlugin()
webContents.allowRunningInsecureContent = true
9.2 推荐的验证方式
webContents.executeJavaScript(code).then(result => {
}).catch(error => {
console.error('执行失败:', error)
})
const win = new BrowserWindow({
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
sandbox: true
}
})
webContents.on('will-navigate', (event, url) => {
try {
const parsed = new URL(url)
if (parsed.protocol !== 'https:') {
event.preventDefault()
}
} catch (e) {
event.preventDefault()
}
})
事件速查表
导航事件
| 事件 | 可取消 | 说明 |
|---|
will-navigate | ✅ | 主 frame 导航前 |
will-frame-navigate | ✅ | 任意 frame 导航前 |
will-redirect | ✅ | 重定向前 |
did-start-navigation | ❌ | 导航开始 |
did-redirect-navigation | ❌ | 重定向完成 |
did-frame-navigate | ❌ | frame 导航完成 |
did-navigate | ❌ | 主 frame 导航完成 |
did-navigate-in-page | ❌ | 页面内导航 |
加载事件
| 事件 | 说明 |
|---|
did-start-loading | 开始加载 |
did-stop-loading | 停止加载 |
did-finish-load | 加载完成 |
did-fail-load | 加载失败 |
dom-ready | DOM 就绪 |
进程事件
| 事件 | 说明 |
|---|
render-process-gone | 渲染进程消失 |
unresponsive | 页面无响应 |
responsive | 页面恢复响应 |
crashed | 进程崩溃(废弃) |
窗口事件
| 事件 | 说明 |
|---|
did-create-window | 新窗口创建 |
content-bounds-updated | 窗口边界更新 |
will-prevent-unload | 阻止卸载 |
方法速查表
页面操作
webContents.loadURL(url, options)
webContents.loadFile(path)
webContents.reload()
webContents.goBack()
webContents.goForward()
webContents.stop()
开发者工具
webContents.openDevTools()
webContents.closeDevTools()
webContents.isDevToolsOpened()
执行代码
webContents.executeJavaScript(code)
webContents.print(options)
webContents.savePage(path, type)
消息通信
webContents.send(channel, ...args)
webContents.postMessage(channel, data)
文档基于 Electron v28+ WebContents API 编写