Electron webview完全指南

一.webview标签

Electron提供了标签,用来嵌入Web页面:

Display external web content in an isolated frame and process.

作用上类似于HTML里的标签,但跑在独立进程中,主要出于安全性考虑

从应用场景来看,类似于于Android的,外部对嵌入页面的控制权较大,包括CSS/JS注入、资源拦截等,而嵌入页面对外部的影响很小,是个相对安全的沙盒,例如仅可以通过一些特定方式与外部通信(如Android的)

二.webContents

像BrowserWindow一样,也拥有与之关联的对象

本质上,是个EventEmitter,用来连通页面与外部环境:

webContents is an EventEmitter. It is responsible for rendering and controlling a web page and is a property of the BrowserWindow object.

三.webContents与webview的关系

从API列表上来看,似乎身上的大多数接口,在身上也有,那么二者是什么关系?

这个问题不太容易弄明白,文档及GitHub都没有相关信息。实际上,这个问题与Electron关系不大,与Chromium有关

Chromium在设计上分为六个概念层:

Chromium-conceptual-application-layers

中间有一层叫:

WebContents: A reusable component that is the main class of the Content module. It’s easily embeddable to allow multiprocess rendering of HTML into a view. See the content module pages for more information.

(引自How Chromium Displays Web Pages)

用于在指定的视图区域渲染HTML

暂时回到Electron上下文,视图区域当然由标签来指定,我们通过宽高/布局来圈定这块区域。确定了画布之后,与关联的对象负责渲染HTML,把要嵌入的页面内容画上去

那么,正常情况下,二者的关系应该是一对一的,即每个都有一个与之关联的对象,所以,有理由猜测身上的大多数接口,应该都只是代理到对应的对象,如果这个对应关系保持不变,那么用谁身上的接口应该都一样,比如:

在功能上差不多等价,都只在页面载入时触发一次,已知的区别是初始时还没有关联对象,要等到第一次才能拿到关联的对象:

需要这样做:

所以,的缺少了第一次,单从该场景看,的事件更符合预期

P.S.异常情况指的是,这个一对一关系并非固定不变,而是可以手动修改的,比如能够把某个webview对应的DevTools塞进另一个webview,具体见Add API to set arbitrary WebContents as devtools

P.S.当然,Electron的与Chromium的确实有紧密联系,但二者从概念上和实现上都是完全不同的,Chromium的明显是负责干活的,而Electron的只是个,一方面把内部状态暴露出去(事件),另一方面提供接口允许从外部影响内部状态和行为(方法)

Frame

除了,还会经常见到Frame这个概念,同样与Chromium有关。但很容易理解,因为Web环境天天见,比如

每个对象都关联一个Frame Tree,树上每个节点代表一个页面。例如:

浏览器打开这个页面的话,Frame Tree上会有3个节点,分别代表A,B,C页面。那么,在哪里能看到Frame呢?

chrome-devtools-frames

每个Frame对应一个页面,每个页面都有自己的对象,在这里切换上下文

四.重写新窗体跳转

webview默认只支持在当前窗体打开的链接跳转(如),对于要求在新窗体打开的,会静默失败,例如:

此类跳转没有任何反应,不会开个新“窗体”,也不会在当前页加载目标页面,需要重写掉这种默认行为:

阻止默认行为,并在当前webview加载目标页面

P.S.有个属性也与有关,说是默认不允许弹窗,实际使用没发现有什么作用,具体见allowpopups

五.注入CSS

可以通过方法注入CSS,例如:

简单有效,看似已经搞定了。实际上跳页或者刷新,注入的样式就没了,所以应该在需要的时候再补一发,这样做:

每次加载新页或刷新都会触发事件,在这里注入,恰到好处

六.注入JS

有2种注入方式:

属性

方法

preload

属性能够在内所有脚本执行之前,先执行指定的脚本

注意,要求其值必须是协议或者协议:

The protocol of script’s URL must be either file: or asar:, because it will be loaded by require in guest page under the hood.

所以,要稍微麻烦一些:

环境可以使用Node API,所以,又一个既能用Node API,又能访问DOM、BOM的特殊环境,我们熟悉的另一个类似环境是renderer

另外,属性的特点是只在第一次加载页面时执行,后续加载新页不会再执行脚本

executeJavaScript

另一种注入JS的方式是通过来做,例如:

在时机上更灵活一些,可以在每个页面随时注入(比如像注入CSS一样,时候补一发,实现整站注入),但默认无法访问Node API(需要开启属性,本文最后有提到)

注意,与身上都有这个接口,但存在差异:

Returns Promise – A promise that resolves with the result of the executed code or is rejected if the result of the code is a rejected promise.

Evaluates code in page. If userGesture is set, it will create the user gesture context in the page. HTML APIs like requestFullScreen, which require user action, can take advantage of this option for automation.

最明显的区别是一个有返回值(返回Promise),一个没有返回值,例如:

从作用上没感受到太大区别,但这样的API设计确实让人有些混乱

七.移动设备模拟

提供了设备模拟API,可以用来模拟移动设备,例如:

但实际效果很弱,不支持touch事件。另外,通过打开的Chrome DevTools也不带Toggle device按钮(小手机图标),相关讨论具体见webview doesn’t render and support deviceEmulation

所以,要像浏览器DevTools一样模拟移动设备的话,用是做不到的

那么,可以通过另一种更粗暴的方式来做,开个,用它的DevTools:

这样就不存在特殊环境的限制了,设备模拟非常靠谱,touch事件也是可用的。但缺点是要开独立窗体,体验比较难受

八.截图

还提供了截图支持,,例如:

5s后截屏,不传默认截整屏(不是整页,长图不用想了,不支持),返回的是个NativeImage实例,想怎么捏就怎么捏

P.S.实际使用发现,设备模拟再截屏,截到的东西是不带模拟的。。。而BrowserWindow开的设备模拟截屏是正常的

九.其它问题及注意事项

1.控制webview显示隐藏

常规做法是,但会引发一些奇怪的问题,比如页面内容区域变小了

webview has issues being hidden using the hidden attribute or using display: none;. It can cause unusual rendering behaviour within its child browserplugin object and the web page is reloaded when the webview is un-hidden. The recommended approach is to hide the webview using visibility: hidden.

大致原因是不允许重写的值,只能是,其它值会引发奇怪问题

官方建议采用:来隐藏,但仍然占据空间,不一定能满足布局需要。社区有一种替代的方法:

P.S.关于显示隐藏webview的更多讨论,见 contents don’t get properly resized if window is resized when webview is hidden

2.允许webview访问Node API

标签有个属性,用来开启Node API访问权限,默认不开

像上面开了之后可以在webview加载的页面里使用Node API,如,

P.S.属性指定的JS文件允许使用Node API,无论开不开,但全局状态修改会被清掉:

When the guest page doesn’t have node integration this script will still have access to all Node APIs, but global objects injected by Node will be deleted after this script has finished executing.

3.导出Console信息

对于注入JS的场景,为了方便调试,可以通过的事件拿到Console信息:

能满足一般调试需要,但缺陷是,消息是跨进程通信传过来的,所以会被强转字符串,所以输出的对象会变成后的

4.webview与renderer通信

有内置的IPC机制,简单方便,例如:

P.S.webview环境部分可以通过注入JS小节提到的属性来完成

如果处理了上一条提到的事件,将看到Console输出:

5.前进/后退/刷新/地址跳转

默认没有提供这些控件(不像标签之类的),但提供了用来实现这些行为的API,如下:

完整示例见下面Demo,更多API见 Tag与webContents

十.Demo地址

GitHub仓库:ayqy/electron-webview-quick-start

一个简单的单tab浏览器,本文中提到的所有内容在Demo中都有涉及,注释详尽

参考资料

Electron Intercept HTTP request, response on BrowserWindow:拦截资源请求

Chromium网页加载过程简要介绍和学习计划:又见老罗,果然一切都有关联

WebView详解与简单实现Android与H5互调

联系ayqy

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180405G1895300?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

同媒体快讯

扫码关注云+社区

领取腾讯云代金券