唤醒手腕前端 Electron Gui 桌面应用开发详细教程(流程模型、进程通信、进程沙盒化)

news2024/11/25 0:27:49

流程模型

Electron 继承了来自 Chromium 的多进程架构,这使得此框架在架构上非常相似于一个现代的网页浏览器。

为什么不是一个单一的进程?

网页浏览器是个极其复杂的应用程序。 除了显示网页内容的主要能力之外,他们还有许多次要的职责,例如:管理众多窗口 ( 或 标签页 ) 和加载第三方扩展。

在早期,浏览器通常使用单个进程来处理所有这些功能。 虽然这种模式意味着您打开每个标签页的开销较少,但也同时意味着一个网站的崩溃或无响应会影响到整个浏览器。

多进程模型

为了解决这个问题,Chrome 团队决定让每个标签页在自己的进程中渲染, 从而限制了一个网页上的有误或恶意代码可能导致的对整个应用程序造成的伤害。 然后用单个浏览器进程控制这些标签页进程,以及整个应用程序的生命周期。 下方来自 Chrome 漫画 的图表可视化了此模型:
在这里插入图片描述

Electron 应用程序的结构非常相似。 作为应用开发者,你将控制两种类型的进程:主进程 和 渲染器进程。 这类似于上文所述的 Chrome 的浏览器和渲染器进程。

主进程 Main Process

每个 Electron 应用都有一个单一的主进程,作为应用程序的入口点。 主进程在 Node.js 环境中运行,这意味着它具有 require 模块和使用所有 Node.js API 的能力。

窗口管理

主进程的主要目的是使用 BrowserWindow 模块创建和管理应用程序窗口。

BrowserWindow 类的每个实例创建一个应用程序窗口,且在单独的渲染器进程中加载一个网页。 您可从主进程用 window 的 webContent 对象与网页内容进行交互。

const { BrowserWindow } = require('electron')

const win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('https://github.com')

const contents = win.webContents
console.log(contents)

注意:渲染器进程也是为 web embeds 而被创建的,例如 BrowserView 模块。 嵌入式网页内容也可访问
webContents 对象。

由于 BrowserWindow 模块是一个 EventEmitter, 所以您也可以为各种用户事件 ( 例如,最小化 或 最大化您的窗口 ) 添加处理程序。

当一个 BrowserWindow 实例被销毁时,与其相应的渲染器进程也会被终止。

应用程序生命周期

主进程还能通过 Electron 的 app 模块来控制您应用程序的生命周期。 该模块提供了一整套的事件和方法,可以让您用来添加自定义的应用程序行为 (例如:以编程方式退出您的应用程序、修改应用程序坞,或显示一个关于面板) 。

app.on('window-all-closed', () => {
	if (process.platform !== 'darwin') app.quit()
})

原生 API

为了使 Electron 的功能不仅仅限于对网页内容的封装,主进程也添加了自定义的 API 来与用户的作业系统进行交互。 Electron 有着多种控制原生桌面功能的模块,例如菜单、对话框以及托盘图标。

渲染器进程

每个 Electron 应用都会为每个打开的 BrowserWindow ( 与每个网页嵌入 ) 生成一个单独的渲染器进程。 洽如其名,渲染器负责 渲染 网页内容。 所以实际上,运行于渲染器进程中的代码是须遵照网页标准的 (至少就目前使用的 Chromium 而言是如此) 。

因此,一个浏览器窗口中的所有的用户界面和应用功能,都应与您在网页开发上使用相同的工具和规范来进行攥写。

虽然解释每一个网页规范超出了本指南的范围,但您最起码要知道的是:

以一个 HTML 文件作为渲染器进程的入口点。
使用层叠样式表 (Cascading Style Sheets, CSS) 对 UI 添加样式。
通过 <script> 元素可添加可执行的 JavaScript 代码。

此外,这也意味着渲染器无权直接访问 require 或其他 Node.js API。 为了在渲染器中直接包含 NPM 模块,您必须使用与在 web 开发时相同的打包工具 (例如 webpack 或 parcel)

为了方便开发,可以用完整的 Node.js 环境生成渲染器进程。 在历史上,这是默认的,但由于安全原因,这一功能已被禁用。

这边为什么有安全问题,官方文档上没具体说明,我来解释一下:

就比如说,我们的主进程创建 window 窗口,loadUrl(“http://某钓鱼网站.com”),加载渲染页面,但是这个钓鱼网站就用到调用 计算机系统资源 的脚本,那么你的计算机系统隐私不就被访问量吗?

此刻,您或许会好奇:既然这些特性只能由主进程访问,那渲染器进程用户界面怎样才能与 Node.js 和 Electron 的原生桌面功能进行交互。 而事实上,确实没有直接导入 Electron 內容脚本的方法。

reload 脚本

预加载(preload)脚本包含了那些执行于渲染器进程中,且先于网页内容开始加载的代码 。 这些脚本虽运行于渲染器的环境中,却因能访问 Node.js API 而拥有了更多的权限。

预加载脚本可以在 BrowserWindow 构造方法中的 webPreferences 选项里被附加到主进程。

const { BrowserWindow } = require('electron')

const win = new BrowserWindow({
  webPreferences: {
    preload: 'path/to/preload.js',
  },
})

因为预加载脚本与浏览器共享同一个全局 Window 接口,并且可以访问 Node.js API,所以它通过在全局 window 中暴露任意 API 来增强渲染器,以便你的网页内容使用。

虽然预加载脚本与其所附着的渲染器在共享着一个全局 window 对象,但您并不能从中直接附加任何变动到 window 之上,因为 contextIsolation 是默认的。

preload.js

window.myAPI = {
  desktop: true,
}

renderer.js

console.log(window.myAPI)

## undefined

语境隔离(Context Isolation)意味着预加载脚本与渲染器的主要运行环境是隔离开来的,以避免泄漏任何具特权的 API 到您的网页内容代码中。

取而代之,我们將使用 contextBridge 模块来安全地实现交互:

preload.js

const { contextBridge } = require('electron')

contextBridge.exposeInMainWorld('myAPI', {
  desktop: true,
})

renderer.js

console.log(window.myAPI)
// => { desktop: true }

此功能对两个主要目的來說非常有用:

通过暴露 ipcRenderer 帮手模块于渲染器中,您可以使用 进程间通讯 ( inter-process communication, IPC ) 来从渲染器触发主进程任务 ( 反之亦然 ) 。

如果您正在为远程 URL 上托管的现有 web 应用开发 Electron 封裝,则您可在渲染器的 window 全局变量上添加自定义的属性,好在 web 客户端用上仅适用于桌面应用的设计逻辑 。

效率进程

每个Electron应用程序都可以使用主进程生成多个子进程UtilityProcess API。 主进程在 Node.js 环境中运行,这意味着它具有 require 模块和使用所有 Node.js API 的能力。 实用程序进程可用于托管,例如:不受信任的服务, CPU 密集型任务或以前容易崩溃的组件 托管在主进程或使用Node.jschild_process.fork API 生成的进程中。 效率进程和 Node 生成的进程之间的主要区别.js child_process模块是实用程序进程可以建立通信 通道与使用MessagePort的渲染器进程。 当需要从主进程派生一个子进程时,Electron 应用程序可以总是优先使用 效率进程 API 而不是Node.js child_process.fork API。

上下文隔离

上下文隔离是什么?

上下文隔离功能将确保您的 预加载脚本 和 Electron的内部逻辑 运行在所加载的 webcontent网页 之外的另一个独立的上下文环境里。 这对安全性很重要,因为它有助于阻止网站访问 Electron 的内部组件 和 您的预加载脚本可访问的高等级权限的API 。

这意味着,实际上,您的预加载脚本访问的 window 对象并不是网站所能访问的对象。 例如,如果您在预加载脚本中设置 window.hello = ‘wave’ 并且启用了上下文隔离,当网站尝试访问window.hello对象时将返回 undefined。

自 Electron 12 以来,默认情况下已启用上下文隔离,并且它是 所有应用程序推荐的安全设置。

迁移

没有上下文隔离,从预加载脚本提供API时,经常会使用window.X = apiObject 那么现在呢?

之前: 上下文隔离禁用

在渲染进程中,预加载脚本暴露给已加载的页面 API 是一个常见的使用方式。 当上下文隔离时,您的预加载脚本可能会暴露一个常见的全局window对象给渲染进程。 此后,您可以从中添加任意的属性到预加载在脚本。

preload.js

window.myAPI = {
  doAThing: () => {}
}

doAThing() 函数可以在渲染进程中直接使用。

renderer.js

window.myAPI.doAThing()

之后:启用上下文隔离

Electron 提供一种专门的模块来无阻地帮助您完成这项工作。 contextBridge 模块可以用来安全地从独立运行、上下文隔离的预加载脚本中暴露 API 给正在运行的渲染进程。 API 还可以像以前一样,从 window.myAPI 网站上访问。

preload.js

const { contextBridge } = require('electron')

contextBridge.exposeInMainWorld('myAPI', {
  doAThing: () => {}
})

renderer.js

window.myAPI.doAThing()

安全事项

单单开启和使用 contextIsolation 并不直接意味着您所做的一切都是安全的。 例如,此代码是 不安全的。

preload.js 错误使用

contextBridge.exposeInMainWorld('myAPI', {
  send: ipcRenderer.send
})

它直接暴露了一个没有任何参数过滤的高等级权限 API 。 这将允许任何网站发送任意的 IPC 消息,这不会是你希望发生的。 相反,暴露进程间通信相关 API 的正确方法是为每一种通信消息提供一种实现方法。

preload.js 正确使用

contextBridge.exposeInMainWorld('myAPI', {
  loadPreferences: () => ipcRenderer.invoke('load-prefs')
})

进程间通信

进程间通信 (IPC) 是在 Electron 中构建功能丰富的桌面应用程序的关键部分之一。 由于主进程和渲染器进程在 Electron 的进程模型具有不同的职责,因此 IPC 是执行许多常见任务的唯一方法,例如从 UI 调用原生 API 或从原生菜单触发 Web 内容的更改。

IPC 通道

在 Electron 中,进程使用 ipcMain 和 ipcRenderer 模块,通过开发人员定义的“通道”传递消息来进行通信。 这些通道是 任意 (您可以随意命名它们)和 双向 (您可以在两个模块中使用相同的通道名称)的。

在本指南中,我们将介绍一些基本的 IPC 模式,并提供具体的示例。您可以将这些示例作为您应用程序代码的参考。

模式 1:渲染器进程到主进程(单向)

要将单向 IPC 消息从渲染器进程发送到主进程,您可以使用 ipcRenderer.send API 发送消息,然后使用 ipcMain.on API 接收。

通常使用此模式从 Web 内容调用主进程 API。 我们将通过创建一个简单的应用来演示此模式,可以通过编程方式更改它的窗口标题。

对于此演示,您需要将代码添加到主进程、渲染器进程和预加载脚本。 完整代码如下,我们将在后续章节中对每个文件进行单独解释。

main.js 主进程代码

const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')

function createWindow() {
    const mainWindow = new BrowserWindow({
        width: 400,
        height: 300,
        webPreferences: {
            preload: path.join(__dirname, 'preload.js')
        }
    })

    ipcMain.on('set-title', (event, title) => {
        const webContents = event.sender
        const win = BrowserWindow.fromWebContents(webContents)
        win.setTitle(title)
    })

    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()
})

静态页面 index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
    <title>Hello World!</title>
  </head>
  <body>
    Title: <input id="title"/>
    <button id="btn" type="button">Set</button>
    <script src="./renderer.js"></script>
  </body>
</html>

预加载脚本 preload.js

const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
    setTitle: (title) => ipcRenderer.send('set-title', title)
})

渲染进程 renderer.js

const setButton = document.getElementById('btn')
const titleInput = document.getElementById('title')
setButton.addEventListener('click', () => {
    const title = titleInput.value
    window.electronAPI.setTitle(title)
});

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/385432.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

红黑树(Insert())

文章目录红黑树代码红黑树性质红黑树vsAVL树红黑树的实现Insert()情况一&#xff1a;如果我插入的新节点时红色的情况二&#xff1a;叔叔是黑色或者不存在情况三: cur红,p为红,g为黑,u不存在或者为黑-双旋检查erase()红黑树vsAVL树红黑树的应用&#xff1a;红黑树 二叉搜索树 …

分布式框架RabbitMQ详解以及编程特性Base实现

消息队列MQ MQ的主要作用有&#xff1a; 异步 例子: 快递员发快递&#xff0c;直接到客户家效率会很低。引入菜鸟驿站后&#xff0c;快递员只需要把快递放到菜鸟驿站&#xff0c;就可以继续发其他快递去了。客户再按自己的时间安排去菜鸟驿站取快递. 作用:异步能提高系统的…

基于明道云平台重建医院管理流程

一、龙华区医疗信息化建设情况 首先&#xff0c;给大家介绍一下龙华区医疗信息化建设的情况&#xff0c;龙华区位于深圳市的中部&#xff0c;目前下属3家公立医院&#xff0c;2家公共卫生机构。2017年&#xff0c;龙华区提出了建设智慧龙华总体框架方案&#xff0c;龙华区卫生…

FPGA Cameralink图像生成模板,Cameralink采集卡图像采集

FPGA Cameralink图像生成模板&#xff0c;Cameralink采集卡图像采集。1&#xff1a;采集卡安装2&#xff1a;FPGA产生图像模板module vga_lcd_driver(input clk,input [7:0] r_i,input [7:0] g_i,input [7:0] b_i,output [7:0] r_o,output [7:0] g_o,output [7:0] b_o,output d…

《QDebug 2023年2月》

一、Qt Widgets 问题交流 二、Qt Quick 问题交流 三、其他 1.使用 QDir::toNativeSeparators() 转换路径中的 "/" 分割符为 "\" 在网上抄代码的时候&#xff0c;总会遇到这样的代码&#xff1a; file_path.replace("/", "\\"); …

界面开发(3)--- PyQt5用户登录界面连接数据库

文章目录数据库账户注册账号登录找回密码为了实现用户登录界面的登录功能&#xff0c;我们必须建立一个数据库&#xff0c;并把账号和对应的密码&#xff0c;存储到数据库中。如果输入的账号和密码与数据库中的一致&#xff0c;那我们就允许用户登录&#xff0c;进入新的界面。…

win10 WSL2 使用Ubuntu配置与安装教程

Win10 22H2ubuntu 22.04ROS2 文章目录一、什么是WSL2二、Win10 系统配置2.1 更新Windows版本2.2 Win10系统启用两个功能2.3 Win10开启BIOS/CPU开启虚拟化(VT)&#xff08;很关键&#xff09;2.4 下载并安装wsl_update_x64.msi2.5 PowerShell安装组件三、PowerShell安装Ubuntu3.…

WMS相关知识点(二)

目录一、Android 图形显示系统1. 从下层往上层理解一、Android 图形显示系统 1. 从下层往上层理解 1.1 显示屏 显示屏上的内容&#xff0c;是从硬件帧缓冲区的&#xff0c;大致读取过程为&#xff1a;从Buffer的起始地址开始&#xff0c;从上往下&#xff0c;从左往右&#x…

Redis持久化RDB和AOF

Redis 是内存数据库&#xff0c;数据都是存储在内存中&#xff0c;为了避免进程退出导致数据的永久丢失&#xff0c;需要定期将 Redis 中的数据以某种形式&#xff08;数据或命令&#xff09;从内存保存到硬盘。当下次 Redis 重启时&#xff0c;利用持久化文件实现数据恢复。除…

《C++ Primer》 第十二章 动态内存

《C Primer》 第十二章 动态内存 动态内存与智能指针 shared_ptr允许多个指针指向同一个对象&#xff1b;unique_ptr则“独占”所指向的对象&#xff0c;weak_ptr指向shared_ptr所管理的对象。这三种类型都定义在memory头文件中。 shared_ptr类&#xff1a;默认初始化的智能…

Docker容器化部署.net core API

1.为API集成Docker环境。&#xff08;VS自带&#xff0c;傻瓜式操作&#xff09; 1.1 点击项目&#xff0c;右键&#xff0c;添加&#xff0c;选择Docker支持 1.2 找到项目根目录中的Dockerfile文件&#xff0c;这是VS刚刚帮我们自动生成的。进入和做如图标红地方修改。 把文…

map和set 的封装

文章目录引入key-value模型map和set底层setset的几个重要接口mapmap几个重要的接口map和set的封装引入 对于map和set的引入&#xff0c;我们用一道在程序中常见的问题解决&#xff1a; 给定一个数组int arr[]{1,2,1,3,1,4,1,5,5,2,3,4,5};&#xff0c;给出以下问题的解决方案&…

【基础算法】双指针---最长连续不重复子序列

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…

html基础(列表(ul、ol、dl)、表格table、表单(input、button、label)、div和span、空格nbsp)

1无序列表<ul>和有序列表<ol>1.1无序列表<ul><!-- 无序列表 --><ul><li>吃饭</li><li>睡觉</li><li>打豆豆</li></ul>1.2有序列表<ol><!-- 有序列表 --><ol><li>吃饭</li…

电机控制中的2个重要变量(速度和转矩)

电机控制的其它相关内容,大家可以参看专栏的系列文章,链接如下: 运动控制系统(伺服3环)_运动控制三环控制周期_RXXW_Dor的博客-CSDN博客1 、这篇作为运动控制系列的第一篇吧,后续慢慢更新关于PLC的运动控制https://blog.csdn.net/m0_46143730/article/details/124075713…

阿里10年测开经验分享-我的软件测试之路也并不是一帆风顺

简单的先说一下&#xff0c;坐标西安&#xff0c;16届本科毕业&#xff0c;目前在跳槽&#xff0c;一共有面试了有5家公司&#xff08;因为不想请假&#xff0c;因此只是每个晚上去其他公司面试&#xff0c;所以面试的公司比较少&#xff09; 其中成功的有5家&#xff0c;另外2…

你不会工作1年了连枚举都还不知道吧?

文章目录一、枚举(Enum)1.1 枚举概述1.2 定义枚举类型1.2.1 静态常量案例1.2.2 枚举案例1.2.3 枚举与switch1.3 枚举的用法1.3.1 枚举类的成员1.3.2 枚举类的构造方法1&#xff09;枚举的无参构造方法2&#xff09;枚举的有参构造方法1.3.3 枚举中的抽象方法1.4 Enum 类1.4.1 E…

Azure OpenAI 官方指南02|ChatGPT 的架构设计与应用实例

ChatGPT 作为即将在微软全球 Azure 公有云平台正式发布的服务&#xff0c;已经迅速成为了众多用户关心的服务之一。而由 OpenAI 发布的 ChatGPT 产品&#xff0c;仅仅上线两个月&#xff0c;就成为互联网历史上最快突破一亿月活的应用。本期从技术角度深度解析 ChatGPT 的架构设…

大数据平台小结

搭建大数据平台启动流程1、启动Nginx服务&#xff08;在bdp-web-mysql服务中&#xff09;cd /usr/local/nginx/# 启动Nginx ./sbin/nginx# 查看端口是否存在 netstat -tunlp|grep 200012、启动zookeeper&#xff08;在bdp-executor-realtime123&#xff09;cd /app/bdp/apache-…

二.项目使用vue-router,引入ant-design-vue的UI框架,引入less

根据前文《使用Vue脚手架工具搭建vue项目》搭建好脚手架后使用 1.vue-router 2.引入UI框架ant design vue 3.引入less 1.vue-router vue-router分为两种模式(默认为hash模式)&#xff1a; hash history hash&#xff1a; 特征&#xff1a; 1.hash会在浏览器路径里带#号&#…