Electron webview

Electron webview

http://www.ayqy.net/blog/electron-webview完全指南/

一.webview标签

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

Display external web content in an isolated frame and process.

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

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

二.webContents

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

本质上,webContents是个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列表上来看,似乎webContents身上的大多数接口,在webview身上也有,那么二者是什么关系

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

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

 

 

Chromium-conceptual-application-layers

中间有一层叫webContents:

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上下文,视图区域当然由webview标签来指定,我们通过宽高/布局来圈定这块区域。确定了画布之后,与webview关联的webContents对象负责渲染HTML,把要嵌入的页面内容画上去

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

webview.addEventListener('dom-ready', onDOMReady);

// 与

webview.getWebContents().on('dom-ready', onDOMReady);

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

webview.addEventListener('dom-ready', () => {

  console.log('webiew dom-ready');

});

//!!! Uncaught TypeError: webview.getWebContents is not a function

const webContents = webview.getWebContents();

需要这样做:

let webContents;

webview.addEventListener('dom-ready', e => {

  console.log('webiew dom-ready');

  if (!webContents) {

    webContents = webview.getWebContents();

    webContents.on('dom-ready', e => {

      console.log('webContents dom-ready');

    });

  }

});

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

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

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

Frame

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

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

 <iframe src="/B"/>

<iframe src-"/C"/>

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

 

 

chrome-devtools-frames

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

四.重写新窗体跳转

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

 

  window.open('http://www.ayqy.net/', '_blank');

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

webview.addEventListener('dom-ready', () => {

  const webContents = webview.getWebContents();

  webContents.on('new-window', (event, url) => {

    event.preventDefault();

    webview.loadURL(url);

  });

});

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

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

五.注入CSS

可以通过insertCSS(cssString)方法注入CSS,例如:

webview.insertCSS(`

  body, p {

    color: #ccc !important;

   

  }

`);

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

webview.addEventListener('dom-ready', e => {

  // Inject CSS

  injectCSS();

});

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

六.注入JS

有2种注入方式:

preload属性

executeJavaScript()方法

preload

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

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

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.

所以,要稍微麻烦一些:

// preload

const preloadFile = 'file://' + require('path').resolve('./preload.js');

webview.setAttribute('preload', preloadFile);

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

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

executeJavaScript

另一种注入JS的方式是通过webview/webContents.executeJavaScript()来做,例如:

webview.addEventListener('dom-ready', e => {

  // Inject JS

  webview.executeJavaScript(`console.log('open <' + document.title + '> at ${new Date().toLocaleString()}')`);

});

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

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

contents.executeJavaScript(code[, userGesture, callback])

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.

.executeJavaScript(code[, userGesture, callback])

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),一个没有返回值,例如:

webContents.executeJavaScript(`1 + 2`, false, result =>

  console.log('webContents exec callback: ' + result)

).then(result =>

  console.log('webContents exec then: ' + result)

);

// 而webview只能通过回调来取

webview.executeJavaScript(`3 + 2`, false, result =>

  console.log('webview exec callback: ' + result)

)

// Uncaught TypeError: Cannot read property 'then' of undefined

// .then(result => console.log('webview exec then: ' + result))

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

七.移动设备模拟

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

// Enable Device Emulation

webContents.setUserAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1');

const size = {

  width: 320,

  height: 480

};

webContents.enableDeviceEmulation({

  screenPosition: 'mobile',

  screenSize: size,

  viewSize: size

});

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

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

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

// Create the browser window.

let win = new BrowserWindow({width: 800, height: 600});

// Load page

mainWindow.loadURL('http://ayqy.net/m/');

// Enable device emulation

const webContents = win.webContents;

webContents.enableDeviceEmulation({

  screenPosition: 'mobile',

  screenSize: { width: 480, height: 640 },

  deviceScaleFactor: 0,

  viewPosition: { x: 0, y: 0 },

  viewSize: { width: 480, height: 640 },

  fitToView: false,

  offset: { x: 0, y: 0 }

});

// Open the DevTools.

win.webContents.openDevTools({

  mode: 'bottom'

});

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

八.截图

webview还提供了截图支持,contents.capturePage([rect, ]callback),例如:

// Capture page

const delay = 5000;

setTimeout(() => {

  webContents.capturePage(image => {

    const base64 = image.toDataURL();

    // 用另一个webview把截屏展示出来

    captureWebview.loadURL(base64);

    // 写入本地文件

    const buffer = image.toPNG();

    const fs = require('fs');

    const tmpFile = '/tmp/page.png';

    fs.open(tmpFile, 'w', (err, fd) => {

      if (err) throw err;

      fs.write(fd, buffer, (err, bytes) => {

        if (err) throw err;

        console.log(`write ${bytes}B to ${tmpFile}`);

      })

    });

  });

}, delay);

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

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

九.其它问题及注意事项

1.控制webview显示隐藏

常规做法是webview.style.display = hidden ? 'none' : '',但会引发一些奇怪的问题,比如页面内容区域变小了

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.

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

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

webview.hidden { width: 0px; height: 0px; flex: 0 1; }

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

2.允许webview访问Node API

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

 

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

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

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的场景,为了方便调试,可以通过webview的console-message事件拿到Console信息:

// Export console message

webview.addEventListener('console-message', e => {

  console.log('webview: ' + e.message);

});

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

4.webview与renderer通信

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

// renderer环境

webview.addEventListener('ipc-message', (event) => {

  //! 消息属性叫channel,有些奇怪,但就是这样

  console.log(event.channel)

})

webview.send('our-secrets', 'ping')

// webview环境

const {ipcRenderer} = require('electron')

ipcRenderer.on('our-secrets', (e, message) => {

  console.log(message);

  ipcRenderer.sendToHost('pong pong')

})

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

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

webview: ping

pong pong

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

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

// Forwards

if (webview.canGoForward()) {

  webview.goForward();

}

// Backwords

if (webview.canGoBack()) {

  webview.goBack();

}

// Refresh

webview.reload();

// loadURL

webview.loadURL(url);

    1人点赞   Electron

 

 

原文地址:https://www.cnblogs.com/bigben0123/p/12485772.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


这篇文章主要讲解了“electron打包中的坑如何解决”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“electron...
这篇文章主要介绍“electron打包的坑如何解决”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“electron打包的坑如何...
这篇文章主要为大家分析了VSCode中如何调试Electron应用的主进程代码的相关知识点,内容详细易懂,操作细节合理,具有一定参考价值。如果感兴趣的话,不妨跟着跟...
这篇“如何在VSCode上调试Electron应用的主进程代码”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价
vue-cli+electron一种新的脚手架(vue-electron)vue-electron主要业务逻辑都放在src下的renderer文件夹内,和之前的vue-cli搭建项目流程没有任何区别 GIT地址:https://github.com/SimulatedGREG/electron-vue 搭建项目:1.全局安装脚手架:npminstall--globalvue-cli
1、首先成功安装Node.js。2、配置好环境变量path,参加上一篇博客《NodeJs安装》3、全局安装electron,并测试。如下图
相关代码:https://github.com/WozHuang/Barrage-helper/blob/master/src/main/index.dev.js在SPA逐渐成为构建优秀交互体验应用的主流方式后,使用Electron开发跨平台的软件是一个优秀的解决方案。下面简单介绍一下Electron-vue安装vue-devtool的方式。安装步骤下载vue-de
前言本人是做java开发的(菜鸟),做web项目的朋友们基本上都会遇到同样一个,永远不知道客户会怎么样使用,或者说永远不知道客户会用什么浏览器打开我们做出来的应用,就算你跟他说明了一定得用某某某浏览器打开,还是有人会用别的浏览器打开,这种情况通常我们会去做适配(前端),最近公司有需求
electron-builder是将electron做的桌面应用打包成安装包的插件。一、安装使用yarn安装,使用npm安装的有问题(没有尝试),先安装yarn工具。npminstall-gyarn安装electron-builderyarnaddelectron-builder--save-dev二、配置在package.json 中配置"build":{
来源:https:/ewsn.net/say/electron-asar.html 在electron中,asar是个特殊的代码格式。asar包里面包含了程序猿编写的代码逻辑。默认情况下,这些代码逻辑,是放置在resource/app目录下面的,明文可见,这样的话,也就有了代码加密(asar打包)的需求 asar如何解密加密?electron的asar的
 字体图标丢失问题解决方案 重新打包文件npmrunbuild再次运行electron 
<img:src="item.headUrl"alt=""class="contact-head":onerror="morenImage">data(){return{morenImage:'this.src="static/image/head.png"',//默认头像}}
在electron-vue中使用了字体图标,但是打包成.exe文件后图标不显示,路劲问题把字体图标放到static目录下就可以了,静态图片也一样我原来放在其它地方不行改到static目录就可以了
//设置登录cookiesetCookie(name,value){varDays=30;varexp=newDate();vardate=Math.round(exp.getTime()/1000)+Days*24*60*60;constcookie={url:this.webApi,name:name,value:value,e
vue部分cnpminstall-gvue-clivueinitsimulatedgreg/electron-vuemy-projectelectron下载失败解决办法:单独设置electron为淘宝镜像,npmconfigsetelectron_mirrorhttps:/pm.taobao.org/mirrors/electron/yarnconfigsetelectron_mirrorhttps:/pm.taobao.org
原始的方式打包下载对应的版本号的ReleaseElectron然后把对应的项目方便整理成这样的目录结构(Windows下)node_modules重新安装,不然可能启动失败把整文件夹给别人就可以了如果想改名子可以用改名工具rcedit应用程序打包成一个文件为了缓解windows路径名过长的问题(就
窗口间通信的问题electron窗口通信比nwjs要麻烦的多electron分主进程和渲染进程,渲染进程又分主窗口的渲染进程和子窗口的渲染进程主窗口的渲染进程给子窗口的渲染进程发消息1234567891011subWin.webContents.on('dom-ready', () => {    subWin.webCo
按照上一个问题here,我有一个使用Electron平台和Javascript的桌面应用程序,我使用以下方法将HTML5画布转换为JPEG:<aid="download"download="Path.jpg">DownloadJPG</a>然后,functiondownload(){vardt=canvas.toDataURL('image/jpeg');this.href
一.什么是Electron?它和NW.js的区别是什么?Electron是GitHub开发的桌面应用开发框架,它支持使用HTML、CSS、JavaScript来构建跨平台的桌面应用。Electron和NW.js(NW.js是什么可以百度下)的区别是:1.整合Chromium和Node.js的方式不同。在NW.js中,Chromium是直接被打补丁打进去
1.打开父子模态创建,<button@click="showModalHandler">父子模态窗口</button>/enderer渲染器中主注册事件showModalHandler(){ipcRenderer.send("child-down-modal");}//主进程中触发事件/***父子模态窗口*/letchildDownModal;ipcMain.on(&#