在 Electron 中通过预加载脚本访问 BrowserWindow 实例
Electron 应用通常由主进程和渲染进程组成,主进程负责管理应用生命周期和窗口(BrowserWindow),渲染进程负责页面内容渲染。预加载脚本是连接主进程和渲染进程的中间层,它能在渲染进程加载页面前执行,且拥有有限的 Node.js 和 Electron API 访问权限。本文将介绍如何通过预加载脚本在渲染进程中访问 BrowserWindow 实例相关能力。
核心原理
BrowserWindow 实例本质是主进程中的对象,无法直接传递到渲染进程。Electron 提供了 ipcMain 和 ipcRenderer 模块实现主进程与渲染进程的通信,同时预加载脚本可以通过 contextBridge 安全地向渲染进程暴露 API,避免直接将 Node.js/Electron API 暴露给页面,提升应用安全性。
实现步骤
1. 主进程创建 BrowserWindow 并加载预加载脚本
主进程中先引入必要的模块,创建 BrowserWindow 时通过 webPreferences.preload 指定预加载脚本的路径,同时可以在主进程中监听渲染进程发送的窗口相关操作请求。
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
let mainWindow = null
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// 指定预加载脚本路径,使用绝对路径避免路径问题
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true, // 开启上下文隔离,默认开启,保障安全
nodeIntegration: false // 关闭 Node.js 集成,避免安全风险
}
})
// 加载页面,这里使用本地文件作为示例
mainWindow.loadFile('index.html')
// 监听渲染进程发来的最小化窗口请求
ipcMain.on('window-minimize', (event) => {
const win = BrowserWindow.fromWebContents(event.sender)
if (win) {
win.minimize()
}
})
// 监听渲染进程发来的最大化/还原窗口请求
ipcMain.on('window-maximize', (event) => {
const win = BrowserWindow.fromWebContents(event.sender)
if (win) {
if (win.isMaximized()) {
win.unmaximize()
} else {
win.maximize()
}
}
})
// 监听渲染进程发来的关闭窗口请求
ipcMain.on('window-close', (event) => {
const win = BrowserWindow.fromWebContents(event.sender)
if (win) {
win.close()
}
})
}
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
2. 编写预加载脚本暴露窗口操作 API
预加载脚本运行在独立上下文中,通过 contextBridge.exposeInMainWorld 向渲染进程的全局对象(通常是 window)注入安全的 API,内部通过 ipcRenderer 与主进程通信,间接操作 BrowserWindow 实例。
const { contextBridge, ipcRenderer } = require('electron')
// 向渲染进程暴露名为 electronAPI 的对象
contextBridge.exposeInMainWorld('electronAPI', {
// 最小化窗口
minimizeWindow: () => {
ipcRenderer.send('window-minimize')
},
// 最大化/还原窗口
maximizeWindow: () => {
ipcRenderer.send('window-maximize')
},
// 关闭窗口
closeWindow: () => {
ipcRenderer.send('window-close')
},
// 获取窗口当前状态(是否最大化)
getWindowState: () => {
return new Promise((resolve) => {
ipcRenderer.once('window-state-response', (event, isMaximized) => {
resolve(isMaximized)
})
ipcRenderer.send('get-window-state')
})
}
})
// 预加载脚本中也可以监听主进程发来的窗口状态变化,主动通知渲染进程
ipcRenderer.on('window-state-change', (event, isMaximized) => {
// 触发渲染进程自定义的全局事件,传递窗口状态
window.dispatchEvent(new CustomEvent('window-state-change', { detail: isMaximized }))
})
3. 主进程补充窗口状态查询逻辑
如果需要获取窗口的状态(如是否最大化),可以在主进程中补充对应的 ipcMain 监听逻辑,通过 BrowserWindow.fromWebContents 获取发送请求的窗口实例,再调用实例方法获取状态。
// 在主进程代码中添加,和之前的 ipcMain 监听逻辑放在一起
ipcMain.on('get-window-state', (event) => {
const win = BrowserWindow.fromWebContents(event.sender)
if (win) {
event.reply('window-state-response', win.isMaximized())
}
})
// 监听窗口最大化/还原事件,主动通知渲染进程状态变化
ipcMain.on('window-maximize', (event) => {
const win = BrowserWindow.fromWebContents(event.sender)
if (win) {
if (win.isMaximized()) {
win.unmaximize()
} else {
win.maximize()
}
// 状态变化后通知渲染进程
win.webContents.send('window-state-change', win.isMaximized())
}
})
4. 渲染进程中调用暴露的 API
渲染进程的页面中可以直接访问 window.electronAPI 调用预加载脚本暴露的方法,无需引入任何 Electron 模块,既安全又能操作窗口相关能力。
// 渲染进程页面中的逻辑,比如点击按钮操作窗口
document.getElementById('minimize-btn').addEventListener('click', () => {
window.electronAPI.minimizeWindow()
})
document.getElementById('maximize-btn').addEventListener('click', () => {
window.electronAPI.maximizeWindow()
})
document.getElementById('close-btn').addEventListener('click', () => {
window.electronAPI.closeWindow()
})
// 获取窗口初始状态
window.electronAPI.getWindowState().then((isMaximized) => {
console.log('窗口是否最大化:', isMaximized)
const maximizeBtn = document.getElementById('maximize-btn')
maximizeBtn.textContent = isMaximized ? '还原窗口' : '最大化窗口'
})
// 监听窗口状态变化
window.addEventListener('window-state-change', (e) => {
const isMaximized = e.detail
console.log('窗口状态变化,是否最大化:', isMaximized)
const maximizeBtn = document.getElementById('maximize-btn')
maximizeBtn.textContent = isMaximized ? '还原窗口' : '最大化窗口'
})
注意事项
-
必须开启
contextIsolation(默认开启),否则contextBridge无法正常工作,且会导致安全风险。 -
不要将整个
ipcRenderer或BrowserWindow直接暴露给渲染进程,只暴露需要的最小功能,避免恶意代码利用暴露的 API 执行危险操作。 -
主进程中通过
BrowserWindow.fromWebContents(event.sender)可以准确获取发送请求的窗口实例,避免操作错误的窗口。 -
预加载脚本的路径需要使用绝对路径,避免相对路径导致的加载失败问题。
总结
通过预加载脚本访问 BrowserWindow 实例的核心思路是:主进程管理 BrowserWindow 实例并通过 ipcMain 监听窗口操作请求,预加载脚本通过 contextBridge 向渲染进程暴露安全的 API,内部使用 ipcRenderer 与主进程通信,最终实现渲染进程间接操作 BrowserWindow 的能力。这种方式既保障了应用的安全性,又满足了渲染进程对窗口操作的需求。