Electron 分主进程和渲染进程。在主进程如果想要控制嵌入其他网页,推荐使用 BrowserView, 用法和 BrowserWindow 类似。在渲染进程想要控制其他嵌入网页,推荐使用 iframe, 还有 webview。iframe 一切都好,但是有跨域的限制,而 webview 基于 chromium 的 webview 开发,可能不太稳定,具体取舍需要根据实际情况选择,本文主要介绍 webview.
启用
以下代码基于 Electron: 15.3.0
Electron >5 默认禁用 wevbiew, 所以使用时需要先配置启用 webviewTag.
mainWin = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
webviewTag: true,
},
})
特性
1、webview 是一个自定义元素,仅在 electron 内可以使用。它们是以 “进程外的 iframe” 方式实现。
2、和 webview 所有的通信都是异步使用 ipc 进行的。
3、在 webview 中单击时,页面焦点将从嵌入器移动到 webview。
4、无法将键盘、鼠标和滚动事件侦听器添加到 webview。
5、webview 的样式推荐使用 display:flex; 来确保其内容在传统和 flex 布局一起使用时可以填充 wevbiew 容器的宽高。除非指定内联布局的 display:inline-flex;, 否则不要覆盖默认的 display: flex; CSS 属性。
6、webview.loadURL(): url 地址必须包含协议前缀,如 http:// https:// file://
webview
.loadURL()
.then(() => {
// 等于 did-start-loading
})
.catch((e) => {
// 等于 did-stop-loading
})
7、在 8.5.5 能用的 webview.getWebContents() 函数在 v15 上已经被废弃掉(和 remote 模块被废弃是同步的查看详细进度),取而代之的是 webview.getWebContentsId(),之后的操作得通过 ipc 去主进程实现了。
生命周期
错误提示
1、从上图可以看出, webview 标签加载失败时会有 did-fail-load 生命周期调用,然后继续调用 did-finish-load did-stop-loading。所以,如果需要展示错误,就需要在 did-fail-load 时保存错误信息,在 did-stop-loading 时展示出来。
let failLoadError
webview.addEventListener('did-fail-load', (error) => { failLoadError = error })
webview.addEventListener('did-finish-load', () => {})
webview.addEventListener('did-stop-loading', () => {})
2、chromium 常见的错误可以通过 chrome://network-errors/ 查看,此表中所列的即为常见的浏览器错误,通过 did-fail-load 即可捕获.
渲染进程打开新窗口
1、属性中包含 target="_blank", 单击链接或提交表单即可
2、在 JavaScript 中调用 window.open()
3、通过 webContents.setWindowOpenHandler() 拦截前两者行为并控制打开窗口。它和前两者的不同在于可以直接拿到对应的 BrowserWindow 对象进行操作。
4、注意:如果是在 标签中打开新窗口,需要给标签添加 allowpopups 属性
<webview partition="persist" allowpopups enabledremotemodule="false" disablewebsecurity/>
1)对于同源内容,新窗口是在同一个进程内创建的,所以父窗口可以直接访问子窗口。(对于首选项面板等子窗口来说非常有用,因为父窗口可以直接渲染子窗口,就好像子窗口是父窗口的一个 div
// 比如如下就是在 renderer 进程中通过 window.open 打开了一个新窗口,同时返回了这个新窗口的 window 对象,通过它就可以和新窗口直接进行通信(比如通过 document 修改新窗口的元素、执行 js, postMessage 等)
// 可以看出,当有这种需求时,通过 window.open 来操作是彼此交互比较便捷的一种方式。
const childWin = window.open('', 'model')
console.log('[win-blank]', childWin)
childWin.document.write(`
<head>
<link href="https://cdn.bootcdn.net/ajax/libs/mdui/1.0.2/css/mdui.min.css" rel="stylesheet" crossorigin="anonymous">
</head>
<div class="mdui-typo">
<h1>这是 window.open('', 'model') 打开的</h1>
</div>
<script>
window.addEventListener(
'message',
(e) => {
console.log('[renderer] 消息: ', e)
document.write('<h1 class="mdui-typo-display-2-opacity"> 老铁,消息收到啦啦啦啦啦!!!!' + e.data + '</h1>' )
window.postMessage('小弟收到回信咯', '*')
},
false
)
</script>
`)
setTimeout(() => {
childWin.postMessage('model 窗口的回信', '*')
}, 1000)
2)当 nativeWindowOpen: false 时, window.open 实际创建返回的是 BrowserWindowProxy, 一个轻量级 BrowserWindow 封装,和上一步的 window 完全不同。
3)Electron 底层是把原生 Chrome Window 和 BrowserWindow 绑定的。所以当在主进程通过 webContents.setWindowOpenHandler() 为渲染进程创建窗口时,你可以使用所有 BrowserWindow 可用的自定义功能。
// BrowserWindow 构造器的参数解析优先级从低到高分别为: 从 `window.open()` 中的 `features` 字符串解析选项; 继承父窗口的安全相关的 `webPreference` 选项; 从 `webContents.setWindowOpenHandler()` 中获取的选项。注意,`webContents.setWindowOpenHandler` 的优先级最高,因为它最终是在主进程调用的。
// `window.open(url[, frameName][, features])` return -> `BrowserWindowProxy | Window`
// - `features` 是以分号分隔的 key-value 列表,遵循浏览器的标准格式。
// - 一些可用的 `webPreference` feature 字符串如: `zoomFactor`、`nodeIntegration`、`preload`, `javascript`, `contextIsolation`, `webviewTag` 等
window.open(
'https://github.com',
'_blank',
'top=500,left=200,frame=false,nodeIntegration=no'
)
// 1、如果父窗口禁用了 node integration, 那么子窗口将始终禁用.
// 2、如果父窗口启用了上下文隔离 contextIsolation, 则子窗口也将始终启用.
// 3、父窗口禁用 javascript, 则子窗口也将始终禁用.
// 4、features 中不被 Chromium/Electron 处理的参数,将会传递给 `webContents` 的 `did-create-window` 事件处理器的 `option` 参数重.
// 5、`frameName` 可以用来指定窗口的 `windowName`
4)为了能自定义或者取消子窗口的创建,推荐在主进程使用 webContents.setWindowOpenHandler, 此方法返回 { action: 'deny' } 可以取消窗口的创建. 返回 { action: 'allow', overrideBrowserWindowOptions: {...}} 则会打开窗口并覆盖之前已有的选项。
const mainWin = new BrowserWindow()
mainWin.webContents.setWindowOpenHandler(({ url }) => {
console.log('[main] setWindowOpenHandler: ', url)
return {
action: 'allow',
overrideBrowserWindowOptions: {
frame: false,
fullscreenable: false,
backgroundColor: 'black',
webPreferences: {
preload: path.join(process.cwd(), 'renderer.js'),
},
},
}
})
五星推荐
[electron-webview 完全指南](http://www.ayqy.net/blog/electron-webview完全指南/)