在這部分的教程中,你將會了解什么是預(yù)加載腳本,并且學(xué)會如何使用預(yù)加載腳本來安全地將特權(quán) API 暴露至渲染進(jìn)程中。 不僅如此,你還會學(xué)到如何使用 Electron 的進(jìn)程間通信 (IPC) 模組來讓主進(jìn)程與渲染進(jìn)程間進(jìn)行通信。
Electron 的主進(jìn)程是一個擁有著完全操作系統(tǒng)訪問權(quán)限的 Node.js 環(huán)境。 除了 Electron 模組 之外,你也可以使用 Node.js 內(nèi)置模塊 和所有通過 npm 安裝的軟件包。 另一方面,出于安全原因,渲染進(jìn)程默認(rèn)跑在網(wǎng)頁頁面上,而并非 Node.js里。
為了將 Electron 的不同類型的進(jìn)程橋接在一起,我們需要使用被稱為 預(yù)加載 的特殊腳本。
BrowserWindow 的預(yù)加載腳本運行在具有 HTML DOM 和 Node.js、Electron API 的有限子集訪問權(quán)限的環(huán)境中。
::: info 預(yù)加載腳本沙盒化
從 Electron 20 開始,預(yù)加載腳本默認(rèn) 沙盒化 ,不再擁有完整 Node.js 環(huán)境的訪問權(quán)。 實際上,這意味著你只擁有一個 polyfilled 的 require
函數(shù),這個函數(shù)只能訪問一組有限的 API。
可用的 API | 詳細(xì)信息 |
---|---|
Electron 模塊 | 渲染進(jìn)程模塊 |
Node.js 模塊 | events 、timers 、url
|
Polyfilled 的全局模塊 | Buffer 、process 、clearImmediate 、setImmediate
|
有關(guān)詳細(xì)信息,請閱讀 進(jìn)程沙盒化 教程。
:::
預(yù)加載腳本像 Chrome 擴(kuò)展的 內(nèi)容腳本(Content Script)一樣,會在渲染器的網(wǎng)頁加載之前注入。 如果你想向渲染器加入需要特殊權(quán)限的功能,你可以通過 contextBridge 接口定義 全局對象。
為了演示這一概念,你將會創(chuàng)建一個將應(yīng)用中的 Chrome、Node、Electron 版本號暴露至渲染器的預(yù)加載腳本
新建一個 preload.js
文件。該腳本通過 versions
這一全局變量,將 Electron 的 process.versions
對象暴露給渲染器。
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
// 能暴露的不僅僅是函數(shù),我們還可以暴露變量
})
為了將腳本附在渲染進(jìn)程上,在 BrowserWindow 構(gòu)造器中使用 webPreferences.preload
傳入腳本的路徑。
const { app, BrowserWindow } = require('electron')
const path = require('path')
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
})
INFO
這里使用了兩個Node.js概念:
現(xiàn)在渲染器能夠全局訪問 versions
了,讓我們快快將里邊的信息顯示在窗口中。 這個變量不僅可以通過 window.versions
訪問,也可以很簡單地使用 versions
來訪問。 新建一個 renderer.js
腳本, 這個腳本使用 document.getElementById
DOM 接口來替換 id
屬性為 info
的 HTML 元素顯示文本。
const information = document.getElementById('info')
information.innerText = `本應(yīng)用正在使用 Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), 和 Electron (v${versions.electron()})`
然后請修改你的 index.html
文件。加上一個 id
屬性為 info
的全新元素,并且記得加上你的 renderer.js
腳本:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<meta
http-equiv="X-Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<title>來自 Electron 渲染器的問好!</title>
</head>
<body>
<h1>來自 Electron 渲染器的問好!</h1>
<p></p>
<p id="info"></p>
</body>
<script src="./renderer.js"></script>
</html>
做完這幾步之后,你的應(yīng)用應(yīng)該長這樣:
你的代碼應(yīng)該長這樣:
main.js | preload.js | index.html | renderer.js |
|
|
|
|
DOCS/FIDDLES/TUTORIAL-PRELOAD (22.0.2)
我們之前提到,Electron 的主進(jìn)程和渲染進(jìn)程有著清楚的分工并且不可互換。 這代表著無論是從渲染進(jìn)程直接訪問 Node.js 接口,亦或者是從主進(jìn)程訪問 HTML 文檔對象模型 (DOM),都是不可能的。
解決這一問題的方法是使用進(jìn)程間通信 (IPC)??梢允褂?Electron 的 ipcMain
模塊和 ipcRenderer
模塊來進(jìn)行進(jìn)程間通信。 為了從你的網(wǎng)頁向主進(jìn)程發(fā)送消息,你可以使用 ipcMain.handle
設(shè)置一個主進(jìn)程處理程序(handler),然后在預(yù)處理腳本中暴露一個被稱為 ipcRenderer.invoke
的函數(shù)來觸發(fā)該處理程序(handler)。
我們將向渲染器添加一個叫做 ping()
的全局函數(shù)來演示這一點。這個函數(shù)將返回一個從主進(jìn)程翻山越嶺而來的字符串。
首先,在預(yù)處理腳本中設(shè)置 invoke
調(diào)用:
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
ping: () => ipcRenderer.invoke('ping'),
// 能暴露的不僅僅是函數(shù),我們還可以暴露變量
})
IPC 安全
可以注意到我們使用了一個輔助函數(shù)來包裹
ipcRenderer.invoke('ping')
調(diào)用,而并非直接通過 context bridge 暴露ipcRenderer
模塊。 你永遠(yuǎn)都不會想要通過預(yù)加載直接暴露整個ipcRenderer
模塊。 這將使得你的渲染器能夠直接向主進(jìn)程發(fā)送任意的 IPC 信息,會使得其成為惡意代碼最強(qiáng)有力的攻擊媒介。
然后,在主進(jìn)程中設(shè)置你的 handle
監(jiān)聽器。 我們在 HTML 文件加載之前完成了這些,所以才能保證在你從渲染器發(fā)送 invoke
調(diào)用之前處理程序能夠準(zhǔn)備就緒。
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
})
ipcMain.handle('ping', () => 'pong')
win.loadFile('index.html')
}
app.whenReady().then(createWindow)
將發(fā)送器與接收器設(shè)置完成之后,現(xiàn)在你可以將信息通過剛剛定義的 'ping'
通道從渲染器發(fā)送至主進(jìn)程當(dāng)中。
const func = async () => {
const response = await window.versions.ping()
console.log(response) // 打印 'pong'
}
func()
INFO
如欲了解使用
ipcRenderer
模塊和ipcMain
模塊的詳細(xì)說明,請訪問完整的 進(jìn)程間通信 指南。
預(yù)加載腳本包含在瀏覽器窗口加載網(wǎng)頁之前運行的代碼。 其可訪問 DOM 接口和 Node.js 環(huán)境,并且經(jīng)常在其中使用 contextBridge
接口將特權(quán)接口暴露給渲染器。
由于主進(jìn)程和渲染進(jìn)程有著完全不同的分工,Electron 應(yīng)用通常使用預(yù)加載腳本來設(shè)置進(jìn)程間通信 (IPC) 接口以在兩種進(jìn)程之間傳輸任意信息。
在下一部分的教程中,我們將向你展示如何向你的應(yīng)用中添加更多的功能,之后將向你傳授如何向用戶分發(fā)你的應(yīng)用。
更多建議: