• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2025-04-20 10:55 Aet 隐藏边栏 |   抢沙发  1 
文章评分 1 次,平均分 5.0

主进程

职责

  1. 应用生命周期管理:控制应用启动、退出,监听 readywindow-all-closed 等事件
  2. 窗口管理:通过 BrowserWindow 模块创建和管理窗口(如最小化、最大化、关闭)
  3. 系统级操作:访问 Node.js API,执行文件读写、调用系统对话框(如打开文件选择器)、与操作系统交互等
  4. IPC 通信中枢:通过 ipcMain 模块监听渲染进程的请求,处理核心逻辑并返回结果

特点

  1. 唯一性:整个应用仅有一个主进程
  2. 权限高:可直接调用 Node.js 模块(如 fspath),但需谨慎处理敏感操作

示例

渲染进程

职责

  1. 渲染界面:每个窗口对应一个渲染进程,负责加载和显示 HTML/CSS/JavaScript 页面
  2. 前端交互:处理用户输入、DOM 操作、前端框架(如 React/Vue)逻辑
  3. 受限系统访问:默认无法直接调用 Node.js API,除非通过配置或预加载脚本暴露特定功能

特点

  1. 多实例性:每个窗口独立运行一个渲染进程,崩溃不会影响其他窗口
  2. 默认隔离:出于安全考虑,渲染进程默认运行在浏览器沙箱环境中,与系统隔离

示例

进程间通信

概述

  1. 主进程与渲染进程通过 IPCInter-Process Communication) 传递消息:

渲染进程 → 主进程

  1. 使用 ipcRenderer.send() 发送事件,主进程通过 ipcMain.on() 监听

主进程 → 渲染进程

  1. 通过 event.reply()webContents.send() 返回数据,渲染进程通过 ipcRenderer.on() 监听

安全最佳实践

禁用 Node.js 集成(渲染进程)

  1. webPreferences 中设置 nodeIntegration: false,避免恶意代码利用 Node.js 能力

启用上下文隔离(Context Isolation

  1. 隔离渲染进程的前端代码与预加载脚本,防止全局变量污染

使用预加载脚本(Preload Scripts

  1. 选择性暴露有限的 Node.js API 到渲染进程

类比浏览器模型

主进程

  1. 浏览器的主进程(管理标签页、扩展)

渲染进程

  1. 浏览器的每个标签页,但额外支持 Node.js 集成(需谨慎配置)

疑问与理解:其他

关于渲染进程

  1. 概述
    1. Electron 应用中,每个窗口(BrowserWindow)默认会创建一个独立的渲染进程,而窗口内的页面(无论是单页面应用还是多页面应用)都在该窗口的渲染进程中运行
  2. 默认行为
    1. 单窗口应用:整个应用只有一个渲染进程,用于加载所有页面
    2. 多窗口应用:每打开一个新窗口(new BrowserWindow()),Electron 会为其分配一个独立的渲染进程
    3. 标签页(如使用 <webview><iframe>):默认情况下,这些内容共享父窗口的渲染进程,但可以通过配置(如 webPreferences 中的 partition)强制隔离到独立进程中
  3. 隔离性
    1. 崩溃隔离:如果某个窗口的渲染进程崩溃(例如页面 JavaScript 错误),不会影响其他窗口的渲染进程或主进程
    2. 内存隔离:每个渲染进程拥有独立的内存空间,页面间的数据默认不共享(除非通过 IPC 或主进程共享状态)
  4. 进程与页面的关系
    1. 单页应用(SPA):一个窗口加载多个“页面”(通过前端路由切换),但始终在同一个渲染进程中运行
    2. 多窗口多页面:每个窗口加载不同的页面,各自在独立的渲染进程中运行
    3. 动态窗口:例如点击链接时通过 window.open() 创建的新窗口,默认也会生成新的渲染进程
  5. 进程复用与配置
    1. Electron 允许通过配置控制进程的创建行为:
    2. 禁用独立进程:通过 webPreferences 中的 nodeIntegrationInSubFrames: true 等配置,可以让子框架(如 <iframe>)共享父窗口的渲染进程
    3. 强制进程隔离:通过 webPreferences.partition 参数,可以为不同窗口或 <webview> 标签分配独立的进程
  6. 示例
    1. 运行后如果发现 page1.htmlpage2.htmlprocess.pid 不同,说明它们运行在独立的渲染进程中

为什么<webview> 标签默认禁用

  1. 安全风险:防止滥用特权 API
    1. Node.js 集成风险:若 <webview> 标签启用了 Node.js 集成(通过 webPreferences.nodeIntegration),其内部加载的页面可直接访问 Node.js API,可能导致恶意代码执行或系统权限滥用
    2. 跨站脚本攻击(XSS):如果 <webview> 加载不受信任的第三方内容(如用户输入生成的 URL),攻击者可能通过 XSS 漏洞操控渲染进程,甚至通过 IPC 通信攻击主进程
  2. 沙箱隔离的复杂性
    1. 默认沙箱限制:Electron 推荐将渲染进程运行在沙箱环境中,限制其对系统资源的访问。但 <webview> 的默认配置可能绕过沙箱规则,导致隔离失效
    2. 进程共享问题:多个 <webview> 默认共享父窗口的渲染进程,若未正确配置 partition 参数,可能导致敏感数据(如 CookieLocalStorage)在不同页面间泄露
  3. 遵循“最小权限原则”
    1. 安全默认值(Secure by Default):Electron 的核心理念是默认关闭高风险功能,强制开发者显式启用并明确风险。类似的设计还有:
      默认禁用渲染进程的 Node.js 集成(nodeIntegration: false
      默认启用上下文隔离(contextIsolation: true
    2. 减少攻击面:禁用 <webview> 标签可避免开发者无意中引入漏洞,尤其是在嵌入外部内容时
  4. 性能与稳定性
    1. 进程泄漏风险:每个 <webview> 可能创建独立的渲染进程,若未妥善管理(如动态创建/销毁),可能导致内存泄漏或进程残留。
    2. 资源消耗:大量 <webview> 实例会显著增加内存和 CPU 占用,默认禁用可提醒开发者谨慎使用
  5. 替代方案的推荐
    1. Electron 官方更推荐使用 BrowserView 替代 <webview>,因为:
    2. 更安全的进程控制:BrowserView 允许直接配置 webPreferences(如独立 partition),更易实现进程隔离
    3. 布局灵活性:BrowserView 可以自由调整位置和尺寸,适合复杂 UI 集成
    4. 维护优先级:Electron 团队对 <webview> 的维护优先级较低,已知问题可能修复较慢
  6. 如何安全启用 <webview>
    1. 若必须使用 <webview>,需遵循以下安全实践:

BrowserViewwebview的区别

  1. 概述
    1. Electron 中,BrowserView<webview> 都用于在窗口中嵌入子内容(如网页、第三方应用等),但它们在实现方式、安全性、控制粒度等方面有显著差异
  2. 实现方式与架构
特性 BrowserView webview
创建方式 通过主进程的 BrowserView 类创建。 在渲染进程的 HTML 中作为 DOM 标签使用。
进程归属 每个 BrowserView 默认拥有独立渲染进程。 共享父窗口的渲染进程,需显式配置独立进程。
生命周期控制 由主进程直接管理(创建、附加、销毁)。 通过 DOM 操作管理(如动态添加/移除标签)。
布局控制 通过坐标和尺寸精确控制位置,支持叠加和层叠。 作为普通 DOM 元素,依赖 CSS 布局。
  1. 进程隔离与安全性
特性 BrowserView webview
默认进程隔离 ✅ 每个实例默认使用独立渲染进程。 ❌ 默认共享父窗口进程,需通过 partition 隔离。
Node.js 集成 默认禁用,需在 webPreferences 中显式启用。 默认禁用,但父窗口启用 nodeIntegration 时可能继承。
沙箱模式 支持通过 sandbox: true 强制启用沙箱。 需显式设置 <webview sandbox="..."> 属性。
安全风险 隔离性强,适合加载不受信任内容。 配置不当易导致漏洞(如 XSS 攻击)。
  1. 使用场景对比
    1. 适合使用 BrowserView 的场景
      需要严格进程隔离:例如嵌入第三方网页、广告等高风险内容。
      复杂布局需求:多个视图叠加、动态调整位置(如 IDE 的分屏编辑器)
      高性能渲染:独立进程避免主页面卡顿
      官方推荐场景:Electron 官方更推荐 BrowserView,因其维护更积极
    2. 适合使用 <webview> 的场景
      简单内容嵌入:在固定位置加载受信任的子页面(如帮助文档)
      快速原型开发:无需主进程介入,直接通过 HTML 标签嵌入
      旧项目兼容:已有代码基于 <webview>,且无严格安全要求

iframe的配置只需要通过partition参数吗

  1. 概述
    1. Electron 中,通过 <iframe> 实现进程隔离或存储隔离时,partition 参数是核心配置之一,但并非唯一需要关注的参数
  2. partition 的作用
    1. 定义存储分区:partition 参数决定了 <iframe> 的 存储隔离(如 CookieLocalStorageSessionStorage 等)和 进程隔离
    2. 相同 partition 值的 <iframe> 共享存储和进程
    3. 不同 partition 值的 <iframe> 使用独立的存储和进程

  1. 其他关键配置参数,如下:
    1. 要完全控制 <iframe> 的进程隔离和安全性,需结合以下 webPreferences 参数:
  2. sandbox
    1. 启用沙箱模式:限制 <iframe> 的权限(如禁止执行脚本、禁止访问父页面 DOM
    2. allow-scripts: 允许执行 JavaScript
    3. allow-same-origin: 允许同源访问(需谨慎使用)

  1. nodeIntegration
    1. 禁用 Node.js 集成:防止 <iframe> 直接调用 Node.js API
    2. 父窗口禁用 nodeIntegration 后,其所有子 <iframe> 默认继承此配置

  1. contextIsolation
    1. 上下文隔离:隔离父窗口的前端代码与预加载脚本(preload),防止全局变量污染

  1. webSecurity
    1. 同源策略:控制是否允许跨域请求

  1. 完整配置示例

什么是同源访问

  1. 概述
    1. 同源访问(Same-origin Access)是浏览器实施的一项核心安全策略,称为同源策略(Same-origin Policy
    2. 它的核心目的是限制不同源的网页之间的交互,防止恶意网站窃取用户数据或攻击其他网站
  2. 什么是“同源”?
    1. 两个 URL 的 协议(Protocol)、域名(Domain)、端口(Port) 必须完全相同,才属于同源
URL1 URL2 是否同源 原因
https://example.com/app https://example.com/api ✅ 是 协议、域名、端口一致
http://example.com https://example.com ❌ 否 协议不同(HTTP vs HTTPS
https://example.com https://sub.example.com ❌ 否 域名不同(主域 vs 子域)
https://example.com:80 https://example.com:443 ❌ 否 端口不同
  1. 同源策略的限制
    1. 如果两个页面不同源,浏览器会禁止以下操作:
    2. 访问 DOM
      无法通过 JavaScript 读取或修改其他源的页面 DOM
      示例:https://malicious.com 无法通过 iframe.contentWindow.document 获取 https://bank.comDOM
    3. 读取 CookieLocalStorage 等存储
      不同源的页面无法读取彼此的 Cookie 或本地存储数据
    4. 发送跨域请求(AJAX/Fetch
      默认禁止通过 XMLHttpRequestfetch() 向不同源的服务器发送请求(需目标服务器明确允许跨域)
    5. 访问 JavaScript 全局对象
      无法通过 window.open()iframe 跨源访问窗口对象的属性和方法

允许跨源访问的例外情况

  1. CORS(跨源资源共享)
    1. 服务器可通过设置 HTTP 响应头(如 Access-Control-Allow-Origin: *)明确允许特定源的跨域请求

  1. JSONP(已过时)
    1. 通过 <script> 标签加载跨域脚本(仅限 GET 请求),但存在安全风险,已逐渐被 CORS 取代
  2. postMessage API
    1. 通过 window.postMessage() 在不同源的窗口间安全传递消息

  1. document.domain(仅限主域相同)
    1. 如果两个页面主域相同(如 a.example.comb.example.com),可通过设置 document.domain = 'example.com' 实现跨子域访问(需双方都设置)

Electron 中处理同源访问

  1. 概述
    1. Electron 的渲染进程默认遵循浏览器的同源策略,但可通过配置绕过限制(需谨慎使用):
  2. 禁用同源策略(不推荐)
    1. 在创建 BrowserWindow 时关闭 webSecurity

  1. 允许特定源的跨域请求
    1. 通过主进程代理或服务端配置 CORS 头部,安全地实现跨域通信
  2. 使用预加载脚本绕过限制
    1. 在预加载脚本(preload)中暴露安全的方法,通过 IPC 与主进程通信:

ipcMain.on

  1. 用途:
    1. 监听渲染进程通过 ipcRenderer.sendipcRenderer.sendSync 发送的事件
    2. 监听一次性或持续事件
  2. 特点:
    1. 无自动响应:需手动调用 event.reply() 向渲染进程返回数据
    2. 持续监听:除非显式移除监听器(removeListener),否则会一直响应后续的同名事件
    3. 兼容性:适用于所有 Electron 版本
  3. 总结:
    1. 支持单向或双向通信(多次回复)
    2. 适合复杂交互(如进度更新、流式数据传输)
  4. 示例:

ipcMain.handle

  1. 用途:
    1. 配合渲染进程的 ipcRenderer.invoke 使用,专门处理 Promise 驱动的异步操作
  2. 特点:
    1. 自动响应:返回 Promise 的结果会自动发送回渲染进程
    2. 错误传递:若 Promise 被拒绝(Reject),错误会传递到渲染进程的 catch
    3. 简洁性:无需手动回复,代码更简洁
    4. 版本要求:Electron 7.0.0+
  3. 总结:
    1. 仅支持单向请求-响应(一次返回)
    2. 适合简单的异步操作(如数据库查询、文件读写)
  4. 示例:

nextTick 队列

  1. api
    1. process.nextTick(callback)
  2. 优先级最高:在每个 libuv 阶段结束后立即执行
  3. 完全由 Node.js 管理:与 libuv 无关,是 Node.js 自身的任务调度机制
  4. 潜在风险:递归调用 process.nextTick() 可能导致事件循环饥饿(主线程无法进入下一阶段)

microTask队列

  1. api
    1. Promise.then()async/awaitqueueMicrotask()
  2. 优先级次于 nextTick:在 nextTick 队列清空后执行
  3. V8 引擎管理:遵循 ECMAScript 规范,与浏览器中的微任务行为一致
  4. libuv 无关:是 JavaScript 语言层面的异步处理机制

疑问与理解:主进程和渲染进程,同步异步代码

主进程的同步、异步执行

  1. 主进程的主线程(Node.js线程)

    1. Electron 的主进程运行在 Node.js 环境中,因此其主线程就是 Node.js 的主线程
    2. 执行主进程的所有同步代码
    3. 运行 Node.js 的事件循环(Event Loop),处理异步操作的回调
  2. 同步代码

    1. 在主进程的主线程(即 Node.js 主线程)中直接执行
  3. 异步代码

    1. Node.js 的异步操作(如文件读写、网络请求等)会被提交到 libuv 的线程池中执行
    2. 完成后,回调函数会被放入事件循环的对应阶段队列(如 I/O callbacks 阶段),等待主线程空闲时按优先级调度执行

渲染进程的同步,异步操作

  1. 渲染进程的主线程(UI 线程)
    1. 每个渲染进程的 主线程 由 Chromium 管理,负责处理以下任务:
      UI 渲染:HTML/CSS 解析、布局、绘制
      JavaScript 执行:执行页面中的同步 JavaScript 代码
      事件循环:管理任务队列(如 DOM 事件、定时器回调、Promise 微任务等)
  2. 渲染进程的同步代码
    1. 所有同步代码(包括原生 JavaScript 代码和 Node.js 同步 API)都在渲染进程的主线程中直接执行
    2. 如果同步代码耗时过长,会阻塞主线程,导致页面卡顿或无响应

  1. 异步代码如下:
  2. 未暴露 Node.js 功能的渲染进程
    1. 如果渲染进程未启用 Node.js 集成(nodeIntegration: false),或通过预加载脚本仅暴露有限功能,则无法直接调用 Node.js 的异步 API(如 fs.readFile
    2. 此类异步操作必须通过 IPC 请求主进程 代为执行,此时实际执行异步操作的是主进程的 libuv 线程池
  3. 启用 Node.js 集成的渲染进程
    1. 如果渲染进程启用了 Node.js 集成(nodeIntegration: true)且未启用上下文隔离(contextIsolation: false),则渲染进程可以直接调用 Node.js 的异步 API
    2. 此时异步操作由渲染进程自身的 libuv 线程池处理,而非主进程的线程池。这意味着:
      渲染进程的异步任务(如 setTimeoutfs.readFile)会使用自己的事件循环
      如果渲染进程崩溃,其未完成的异步操作也会终止
  4. 通过预加载脚本暴露部分功能
    1. 若渲染进程禁用了 Node.js 集成(nodeIntegration: false)但通过 预加载脚本(Preload Script) 暴露了特定 Node.js 功能(如 ipcRenderer 或自定义异步方法),则:
    2. 预加载脚本中的异步操作会使用 主进程的 libuv 线程池(因为预加载脚本运行在独立上下文,但依赖主进程的 Node.js 环境)
    3. 渲染进程的前端代码(如页面脚本)仍无法直接访问 Node.js,需通过预加载脚本代理

libuv相关

  1. 主线程启动时初始化:
    1. Node.js 主进程启动时,会自动初始化 libuv 库,并创建以下核心组件:
    2. 事件循环(Event Loop):管理异步任务的生命周期,包括多个阶段(如 TimersI/O Callbacks 等)
    3. 线程池(Thread Pool):默认创建 4 个线程(可通过环境变量 UV_THREADPOOL_SIZE 调整),用于处理文件 I/ODNS 解析等阻塞型操作
  2. 主线程的事件循环:
    1. Node.js 事件循环 = libuv 事件循环的封装
    2. libuv 事件循环:是跨平台的异步 I/O 库(C 语言实现),为 Node.js 提供事件循环的基础设施
    3. Node.js 事件循环:在 libuv 事件循环的基础上,Node.js 进行了扩展和封装,使其能够处理 JavaScript 层的异步逻辑(如 PromisenextTick 等)
  3. libuv的任务队列
    1. Timers(setTimeout/setInterval)
    2. Pending I/O(系统中断等)
    3. Idle/Prepare(内部使用)
    4. Poll(检索新 I/O 事件)
    5. ChecksetImmediate
    6. Close(关闭事件回调)
    7. 也就是说,上面这6个阶段的任务队列是 libuv 原生自有的,它们是 libuv 事件循环的核心设计,与 Node.js 的封装无关
  4. node.js自己的任务队列
    1. node.js封装了libuv,在对方的基础上,加了两个任务队列,nextTickmicroTask,这是node.js自有的
    2. nextTick 队列:由 process.nextTick() 触发
    3. microTask队列:由 Promise 回调触发
  5. 异步任务的执行流程:
    1. 主线程提交任务:当调用异步 API(如 fs.readFile)时,主线程将任务提交给 libuv 线程池
    2. 线程池执行任务:线程池中的工作线程执行具体的阻塞操作(如读取文件)
    3. 任务完成通知:操作完成后,线程池将回调函数放入 Pending I/O 队列
    4. 主线程处理回调:事件循环进入 I/O Poll 阶段时,主线程执行这些回调

疑问与理解:框架构成

Node.js

  1. 基于以下三个核心组件构建的运行时环境:
组件 作用 Node.js 的关系
V8 引擎 Google 开发的 JavaScript 引擎,负责解析和执行 JS 代码 Node.js 使用 V8 作为 JS 代码的执行引擎(类似浏览器的 JS 引擎,如 ChromeV8
libuv 跨平台的异步 I/O 库,提供事件循环、线程池、文件系统操作等底层能力 Node.js 通过 libuv 实现非阻塞 I/O 和事件循环,处理异步任务(如文件读写、网络请求)
Node.js 核心模块 内置的 JavaScript/C++ 模块(如 fshttppath 这些模块封装了 libuv 和底层系统调用,提供高层次的 API(如 fs.readFile
  1. 运行环境
    1. 服务端/命令行工具
  2. 进程模型
    1. 单进程(可扩展为多进程)

Electron

  1. ElectronNode.js 基础上 集成了 Chromium,因此可以同时运行 Node.js 主进程和 Chromium 渲染进程
    1. 但在纯 Node.js 环境中,无需 Chromium
  2. 运行环境
    1. 桌面应用(主进程 + 多个渲染进程)
  3. 进程模型
    1. 默认多进程(主进程管理多个渲染进程)

Node.js 的架构层级

Electron相比CEF

  1. electron
    1. ≈ Chromium + Node.js + Web 技术栈
  2. cef
    1. ≈ Chromium + C/C++ API

关于配置

为每个窗口配置单独的渲染进程

  1. 概述
    1. Electron 中,默认情况下每个窗口(BrowserWindow)会创建一个独立的渲染进程,但窗口内的子页面(如通过 <iframe><webview> 加载的内容)默认会共享父窗口的渲染进程
  2. 强制每个页面(包括子页面)单独创建渲染进程,可以通过以下配置实现:
  3. 方法 1webview相关
    1. 使用 <webview> 标签并设置 partition
    2. 关键参数:partition
      如果多个 <webview> 使用相同的 partition,它们会共享一个进程
      若设为唯一值(如 persist:page1persist:page2),则每个 <webview> 会创建独立的渲染进程
    3. <webview> 标签默认禁用,需在父窗口的 webPreferences 中启用:

  1. 方法2webview相关
    1. 通过 BrowserView 动态附加独立进程
    2. 通过 BrowserView 可以更灵活地控制子内容的布局和进程隔离

  1. 方法1iframe相关
    1. 若希望 <iframe> 中的内容运行在独立进程中,需配置父窗口的 webPreferences
    2. sandbox: false:禁用沙箱模式,允许子框架使用独立进程(但需谨慎,可能降低安全性)
    3. partition:若不同 <iframe> 使用不同 partition,则会创建独立进程

  1. 多窗口模式下自动隔离
    1. 直接为每个窗口创建独立的 BrowserWindow 实例:
    2. 每个窗口自动分配独立渲染进程,无需额外配置

  1. 完整示例

硬件加速策略

内容安全策略(CSP

  1. 防止 XSS 攻击,限制资源加载源

权限控制

自定义协议(Protocol

代理设置

自定义菜单

系统托盘

懒加载

开启 DevTools

集成日志框架

自动更新

代码签名(发布必备)

  1. package.json 中设置签名证书(Windows/macOS

使用 Worker 进程

多窗口协作

修改 Chromium 标志

自定义 V8 参数

本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享