目錄
一、主進程和渲染進程
1、主進程(main)
2、渲染進程(renderer)
二、預加載腳本
三、沙盒化
為單個進程禁用沙盒
全局啟用沙盒
四、環境訪問權限控制:contextIsolation和nodeIntegration
1、contextIsolation
🌰啟用contextIsolation:true時(默認推薦做法)
🌰不啟用contextIsolation:false時
2、nodeIntegration
🌰 如果啟用nodeIntegration: true(不推薦)
🌰如果不啟用,我們應該如何借助Node.js的功能呢?
Electron可以簡單理解為一個桌面應用程序的“殼”,內里還是遵循瀏覽器的行為,加載網頁進行渲染(可以是本地、也可以是遠程網頁)
一、主進程和渲染進程
Electron的架構其實類似現代瀏覽器,為了管理應用程序窗口中不同的頁面,每個標簽頁在自己的進程中渲染,?從而限制了一個網頁上的有誤或惡意代碼可能導致的對整個應用程序造成的傷害。
在Electron中,我們可控制的兩類進程為:主進程 和 渲染進程。
1、主進程(main)
1?? 運行環境:node.js,具有?require
?模塊和使用所有 Node.js API
2?? 職責:
- 窗口管理:創建 / 銷毀窗口實例BrowserWindow,其相當于一個小的EventEmitter,窗口將在單獨的渲染器進程中加載一個網頁。窗口中的
webContents
代表的是 渲染進程 內部的網頁內容(Web 頁面),可以執行向渲染進程通信、網頁內容生命周期監聽、訪問/控制devtools等
// main.js
const { BrowserWindow } = require('electron')const win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('https://github.com')const contents = win.webContents
console.log(contents)
- 應用程序的生命周期:監聽app生命周期、做相應處理
// quitting the app when no windows are open on non-macOS platforms
app.on('window-all-closed', () => {if (process.platform !== 'darwin') app.quit()
})
- 調用api與操作系統交互:Electron 有著多種控制原生桌面功能的模塊,例如菜單、對話框以及托盤圖標。下面舉個例子🌰,創建應用菜單!
const { app, Menu, BrowserWindow } = require('electron');app.whenReady().then(() => {const win = new BrowserWindow({width: 800,height: 600,webPreferences: {nodeIntegration: true}});win.loadURL('https://example.com');// 創建菜單templateconst menuTemplate = [{label: '文件',submenu: [{ label: '打開文件', click: () => console.log('打開文件') },{ type: 'separator' }, // 分割線{ label: '退出', role: 'quit' }]},{label: '編輯',submenu: [{ label: '撤銷', role: 'undo' },{ label: '重做', role: 'redo' },{ type: 'separator' },{ label: '剪切', role: 'cut' },{ label: '復制', role: 'copy' },{ label: '粘貼', role: 'paste' }]}];// 設置應用菜單const menu = Menu.buildFromTemplate(menuTemplate); //從模版設置菜單實例Menu.setApplicationMenu(menu); // 放入應用菜單
});
2、渲染進程(renderer)
1?? 運行環境:渲染器負責?渲染?網頁內容。 所以實際上,運行于渲染器進程中的代碼是須遵照網頁web標準的。?
二、預加載腳本
由于主進程和渲染進程的運行環境完全不同,且默認情況下,二者無權直接訪問互相之間的環境。
所以出現了預加載腳本preload.js作為環境橋梁,它包含了那些執行于渲染器進程中,且先于網頁內容開始加載的代碼,與瀏覽器共享一個全局window接口?。 這些腳本雖運行于渲染器的環境中,卻因能訪問 Node.js API 而擁有了更多的權限。
preload.js可以部分暴露一些api給對方進程、可以作為通信中轉站等等。
三、沙盒化
當 Electron 中的渲染進程被沙盒化時,它們的行為與常規 Chrome 渲染器一樣。 一個沙盒化的渲染器不會有一個 Node.js 環境。
附屬于沙盒化的渲染進程的 preload 腳本中仍可使用一部分以 Polyfill 形式實現的 Node.js API。
為單個進程禁用沙盒
app.whenReady().then(() => {const win = new BrowserWindow({webPreferences: {sandbox: false}})win.loadURL('https://google.com')
})
全局啟用沙盒
app.enableSandbox :注意,此 API 必須在應用的?ready
?事件之前調用
app.enableSandbox()
app.whenReady().then(() => }// 因為調用了app.enableSandbox(),所以任何sandbox:false的調用都會被覆蓋。const win = new BrowserWindow()win.loadURL('https://google.com')
})
四、環境訪問權限控制:contextIsolation和nodeIntegration
在創建一個browserWindow實例的時候,會配置webPreferences中的字段。
其中有兩個字段與渲染進程的訪問權限密切相關:contextIsolation 和 nodeIntegration
1、contextIsolation
- 控制
window
對象是否在獨立的 JavaScript 上下文中運行。 - 默認值:true
- 如果為true,代表渲染進程在獨立的js上下文中。因此preload.js、第三方庫都不能直接修改渲染進程中的window全局對象;如果為false,preload.js可以直接通過修改window屬性傳遞值
那么不同設置下,如何借助預加載腳本讓主進程與渲染進程通信呢?
🌰啟用contextIsolation:true時(默認推薦做法)
main.js
const { BrowserWindow } = require('electron');
const win = new BrowserWindow({webPreferences: {contextIsolation: true, // 開啟上下文隔離preload: 'preload.js'}
});
preload.js:利用exposeInMainWorld將屬性掛在window對象中
const { contextBridge } = require('electron');contextBridge.exposeInMainWorld('myAPI', {sayHello: () => console.log('Hello!')
});
renderer.js
console.log(window.myAPI.sayHello()); // ? "Hello!"
🌰不啟用contextIsolation:false時
preload.js:可直接篡改window對象
window.myApi= {sayHello: () => console.log('Hello!')
};
2、nodeIntegration
- 控制是否可以在渲染進程中直接使用Node.js API(如fs、path、require等語法和api)
- 默認值:false,無法直接使用node環境
🌰 如果啟用nodeIntegration: true(不推薦)
渲染進程中的js文件
// 渲染進程 index.html
<script>const fs = require('fs');fs.writeFileSync('test.txt', 'Hello, Electron!');
</script>
🌰如果不啟用,我們應該如何借助Node.js的功能呢?
通過【 預加載腳本】執行ipcRenderer通信,發送至主進程處理
?preload.js
// preload.js
const { contextBridge, ipcRenderer } = require('electron');contextBridge.exposeInMainWorld('electronAPI', {sendTitle: (title) => ipcRenderer.send('set-title', title),readFile : (filePath) => ipcRenderer.invoke('read-file', filePath)
});
main.js的利用ipcMain接受處理函數
const { app, BrowserWindow, ipcMain } = require('electron/main')
const path = require('node:path')
const fs = require('fs').promises;function createWindow () {const mainWindow = new BrowserWindow({webPreferences: {preload: path.join(__dirname, 'preload.js')}})// send 'sendTitle'方法的接收器ipcMain.on('set-title', (event, title) => {//通過event.sender獲取網頁內容對象const webContents = event.sender// fromWebContents解析const win = BrowserWindow.fromWebContents(webContents)win.setTitle(title)})// invoke 'readFile'方法的處理器ipcMain.handle('read-file', async (_, filePath) => {try {const data = await fs.readFile(filePath, 'utf-8');return { success: true, data };} catch (error) {return { success: false, error: error.message };}});mainWindow.loadFile('index.html')
}app.whenReady().then(() => {createWindow()app.on('activate', function () {if (BrowserWindow.getAllWindows().length === 0) createWindow()})
})app.on('window-all-closed', function () {if (process.platform !== 'darwin') app.quit()
})
??注意:send + on和invoke + handle兩種通信方法的區別
?send + on:單向通信,單向發送->單邊處理
invoke + handle :雙向通信,發送者發送信息 -> 接收者理后可return一個返回值 -> 發送者接收到返回值的Promise對象,可針對返回值再處理
🌟推薦實踐:
啟用 sandbox: true
(沙箱模式)進一步增強安全性
const win = new BrowserWindow({webPreferences: {contextIsolation: true, (默認,無需特別設置)nodeIntegration: false,(默認,無需特別設置)preload: 'preload.js',sandbox: true}
});