惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Proofpoint News Feed
T
The Blog of Author Tim Ferriss
aimingoo的专栏
aimingoo的专栏
M
MIT News - Artificial intelligence
N
Netflix TechBlog - Medium
Y
Y Combinator Blog
B
Blog RSS Feed
H
Help Net Security
Blog — PlanetScale
Blog — PlanetScale
Vercel News
Vercel News
Google DeepMind News
Google DeepMind News
Microsoft Security Blog
Microsoft Security Blog
G
Google Developers Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 司徒正美
L
LangChain Blog
IT之家
IT之家
F
Fortinet All Blogs
V
V2EX
C
Check Point Blog
The Cloudflare Blog
博客园_首页
阮一峰的网络日志
阮一峰的网络日志
A
About on SuperTechFans

博客园 - liulun

开源自荐:功能齐全的截图软件(仅1M) 开源自荐:功能齐全的截图工具(含录屏、截长图,体积仅780KB) 自荐:开源截图工具ScreenCapture:超多控制指令,支持截长图 脚踏esbuild祥云,胸怀tsx利刃,身披scss羽衣,追寻前端的本质 开源项目自荐:截图工具(小、快、功能丰富) 如何开发Vite3插件构建Electron开发环境 Electron是什么以及可以做什么 线上Electron应用具备哪些特征? 桌面软件开发框架大赏 如何把Electron做成一个Runtime,让多个应用共享同一个Electron 微软要放弃Electron了???聊聊WebView2 自己实现一个Electron跨进程消息组件(兼新书自荐) 如何点击穿透Electron不规则窗体的透明区域 Electron团队为什么要干掉remote模块 谈Vite在Electron环境下吃花卷拉馒头的现象 公司只提供签名服务,不提供证书文件,如何打包Electron应用 vue3微前端架构——基于蚂蚁qiankun框架 Vite ❤ Electron——基于Vite搭建Electron+Vue3的开发环境【一】 Electron安装过程深入解析(读完此文解决Electron安装失败导致的无法启动,无法打包的问题) 大型Electron应用本地数据库技术选型 极客手中的利器Electron
一个Electron的设计缺陷及应对方案
liulun · 2021-09-30 · via 博客园 - liulun

当你想实现阻止Electron窗口关闭,并弹出询问对话框,提示用户:“文章尚未保存,是否要关闭窗口”这类业务时,那么你99%会碰到这个BUG:

https://github.com/electron/electron/issues/24994

这是我在去年8月份发现的BUG,Electron的作者也已经确认了这个BUG,但遗憾的是现在还没有修复。下面我们就聊聊这个问题,以及应对这个问题的方案。

问题描述

要阻止窗口关闭,必须在窗口的关闭事件中,执行preventDefault操作才行,如下代码所示:

win.on("close", (e) => {
  e.preventDefault();
});

然而这个preventDefault的操作,必须同步调用才能生效,所有异步调用preventDefault的操作都没有任何效果,代码如下所示:

win.on("close", async (e) => {
 console.log("win close");
 await new Promise((resolve) => setTimeout(resolve, 1000));
 e.preventDefault();  //没有任何作用
});

上述代码中的preventDefault操作就不会起任何作用。这就带来了一个业务问题:我们往往在询问用户并获得用户的允可后才会阻止窗口关闭,比如:“文章尚未保存,您确认关闭窗口吗?”开发者无法在这种异步的询问通知前执行preventDefault操作,就无法正确的阻止窗口关闭。

可能你会想到用dialog模块的showMessageBoxSync方法来完成这个询问操作,如下代码所示:

win.webContents.on('will-prevent-unload', event => {
    let choice = dialog.showMessageBoxSync({
      title:'do you want to close',
      buttons:['cancel','yes']
    });
    if(choice === 1)  event.preventDefault();
    //...
  })

没错showMessageBoxSync是一个同步方法,但这也会导致整个主进程的JavaScript线程阻塞,你预期在未来发生的所有事件,以及这些事件的回调方法,都不会再执行了(想想看,你的setInterval的回调方法不会定时执行的结果)。

直到用户关闭showMessageBoxSync方法打开的窗口,主进程的JavaScript线程才会恢复,如果用户永远不做出这个选择,那么整个JavaScript线程就会一直等待下去。

应对方案

为了解决这个问题,我们可以通过额外的哨兵变量来处理,代码如下所示:

//import { app, BrowserWindow, dialog } from "electron";
let winCanBeClosedFlag = false;
win.on("close", async (e) => {
  if (!winCanBeClosedFlag) {
    e.preventDefault();
    let choice = await dialog.showMessageBox(win, {
      title: "do you want to close",
      message: "你确定要关闭窗口吗?",
      buttons: ["否", "是"],
    });
    if (choice.response == 1) {
      winCanBeClosedFlag = true;
      win.close();
      return;
    }
  }
  winCanBeClosedFlag = false;
});

默认情况下winCanBeClosedFlag 这个变量的值是false,即不允许用户关闭窗口(此处的preventDefault是同步操作),当我们询问过用户,并且用户做出了确认关闭的选择后,这个变量才会被设置为true。此时立即调用窗口的close方法,这个窗口的close事件被再次触发,因为winCanBeClosedFlag 变量已经被置为true了,所以不会执行preventDefault操作,窗口被正常关闭。

窗口被关闭的同时winCanBeClosedFlag变量又被置为false,以备下一次用户的操作。