mirror of
https://github.com/kuaifan/dootask.git
synced 2026-01-21 16:48:13 +00:00
refactor: 优化窗口关闭拦截机制,采用声明式注册
- 将 onBeforeUnload 从 utils.js 移至 web-tab-manager.js - 新增声明式拦截注册机制,前端通过 registerCloseInterceptor 声明需要拦截 - 仅对已声明拦截的页面执行 JS 检查,未声明的直接关闭 - 添加 5 秒超时保护,防止网页卡死导致无法关闭窗口 - 修复 command+w 快捷键关闭整个窗口而非当前 tab 的问题
This commit is contained in:
parent
1c27719ac4
commit
cb414b48f6
6
electron/electron.js
vendored
6
electron/electron.js
vendored
@ -17,10 +17,8 @@ const {
|
|||||||
nativeImage,
|
nativeImage,
|
||||||
globalShortcut,
|
globalShortcut,
|
||||||
nativeTheme,
|
nativeTheme,
|
||||||
screen,
|
|
||||||
Tray,
|
Tray,
|
||||||
Menu,
|
Menu,
|
||||||
WebContentsView,
|
|
||||||
BrowserWindow
|
BrowserWindow
|
||||||
} = require('electron')
|
} = require('electron')
|
||||||
|
|
||||||
@ -44,7 +42,7 @@ const electronMenu = require("./electron-menu");
|
|||||||
const { startMCPServer, stopMCPServer } = require("./lib/mcp");
|
const { startMCPServer, stopMCPServer } = require("./lib/mcp");
|
||||||
const {onRenderer, renderer} = require("./lib/renderer");
|
const {onRenderer, renderer} = require("./lib/renderer");
|
||||||
const {onExport} = require("./lib/pdf-export");
|
const {onExport} = require("./lib/pdf-export");
|
||||||
const {allowedCalls, isWin, isMac} = require("./lib/other");
|
const {allowedCalls, isWin} = require("./lib/other");
|
||||||
const webTabManager = require("./lib/web-tab-manager");
|
const webTabManager = require("./lib/web-tab-manager");
|
||||||
const faviconCache = require("./lib/favicon-cache");
|
const faviconCache = require("./lib/favicon-cache");
|
||||||
|
|
||||||
@ -299,7 +297,7 @@ function createMainWindow() {
|
|||||||
|
|
||||||
mainWindow.on('close', event => {
|
mainWindow.on('close', event => {
|
||||||
if (!willQuitApp) {
|
if (!willQuitApp) {
|
||||||
utils.onBeforeUnload(event, mainWindow).then(() => {
|
webTabManager.onBeforeUnload(event, mainWindow).then(() => {
|
||||||
if (['darwin', 'win32'].includes(process.platform)) {
|
if (['darwin', 'win32'].includes(process.platform)) {
|
||||||
if (mainWindow.isFullScreen()) {
|
if (mainWindow.isFullScreen()) {
|
||||||
mainWindow.once('leave-full-screen', () => {
|
mainWindow.once('leave-full-screen', () => {
|
||||||
|
|||||||
32
electron/lib/utils.js
vendored
32
electron/lib/utils.js
vendored
@ -5,7 +5,7 @@ const dayjs = require("dayjs");
|
|||||||
const http = require('http')
|
const http = require('http')
|
||||||
const https = require('https')
|
const https = require('https')
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
const {shell, dialog, session, net, Notification, nativeTheme} = require("electron");
|
const {shell, session, net, Notification, nativeTheme} = require("electron");
|
||||||
const loger = require("electron-log");
|
const loger = require("electron-log");
|
||||||
const Store = require("electron-store");
|
const Store = require("electron-store");
|
||||||
const store = new Store();
|
const store = new Store();
|
||||||
@ -291,6 +291,7 @@ const utils = {
|
|||||||
/**
|
/**
|
||||||
* 正则提取域名
|
* 正则提取域名
|
||||||
* @param weburl
|
* @param weburl
|
||||||
|
* @param toLowerCase
|
||||||
* @returns {string|string}
|
* @returns {string|string}
|
||||||
*/
|
*/
|
||||||
getDomain(weburl, toLowerCase = true) {
|
getDomain(weburl, toLowerCase = true) {
|
||||||
@ -327,35 +328,6 @@ const utils = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 窗口关闭事件
|
|
||||||
* @param event
|
|
||||||
* @param app
|
|
||||||
*/
|
|
||||||
onBeforeUnload(event, app) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const contents = app.webContents
|
|
||||||
if (contents != null) {
|
|
||||||
contents.executeJavaScript(`if(typeof window.__onBeforeUnload === 'function'){window.__onBeforeUnload()}`, true).then(options => {
|
|
||||||
if (utils.isJson(options)) {
|
|
||||||
let choice = dialog.showMessageBoxSync(app, options)
|
|
||||||
if (choice === 1) {
|
|
||||||
contents.executeJavaScript(`if(typeof window.__removeBeforeUnload === 'function'){window.__removeBeforeUnload()}`, true).catch(() => {});
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
} else if (options !== true) {
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
}).catch(_ => {
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
event.preventDefault()
|
|
||||||
} else {
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新窗口打开事件
|
* 新窗口打开事件
|
||||||
* @param webContents
|
* @param webContents
|
||||||
|
|||||||
116
electron/lib/web-tab-manager.js
vendored
116
electron/lib/web-tab-manager.js
vendored
@ -15,7 +15,8 @@ const {
|
|||||||
screen,
|
screen,
|
||||||
Menu,
|
Menu,
|
||||||
WebContentsView,
|
WebContentsView,
|
||||||
BrowserWindow
|
BrowserWindow,
|
||||||
|
dialog
|
||||||
} = require('electron')
|
} = require('electron')
|
||||||
|
|
||||||
const utils = require('./utils')
|
const utils = require('./utils')
|
||||||
@ -42,6 +43,9 @@ const webTabHeight = 40
|
|||||||
// 快捷键关闭状态 Map<windowId, boolean>
|
// 快捷键关闭状态 Map<windowId, boolean>
|
||||||
let webTabClosedByShortcut = new Map()
|
let webTabClosedByShortcut = new Map()
|
||||||
|
|
||||||
|
// 存储已声明关闭拦截的 webContents id
|
||||||
|
const closeInterceptors = new Set()
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 预加载池
|
// 预加载池
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@ -560,13 +564,19 @@ function createWebTabWindowInstance(windowId, position, mode = 'tab') {
|
|||||||
// 检查页面是否有未保存数据
|
// 检查页面是否有未保存数据
|
||||||
if (isShortcut) {
|
if (isShortcut) {
|
||||||
// 快捷键关闭:只检查当前激活的标签
|
// 快捷键关闭:只检查当前激活的标签
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
// 获取当前激活标签
|
||||||
const activeTab = windowData.views.find(v => v.id === windowData.activeTabId)
|
const activeTab = windowData.views.find(v => v.id === windowData.activeTabId)
|
||||||
if (!activeTab) return
|
if (!activeTab) return
|
||||||
|
|
||||||
|
// 构造代理 window 对象
|
||||||
const proxyWindow = Object.create(webTabWindow, {
|
const proxyWindow = Object.create(webTabWindow, {
|
||||||
webContents: { get: () => activeTab.view.webContents }
|
webContents: { get: () => activeTab.view.webContents }
|
||||||
})
|
})
|
||||||
utils.onBeforeUnload(event, proxyWindow).then(() => {
|
|
||||||
|
// 检查并等待用户确认
|
||||||
|
onBeforeUnload(event, proxyWindow).then(() => {
|
||||||
closeWebTabInWindow(windowId, 0)
|
closeWebTabInWindow(windowId, 0)
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@ -583,7 +593,7 @@ function createWebTabWindowInstance(windowId, position, mode = 'tab') {
|
|||||||
webContents: { get: () => tab.view.webContents }
|
webContents: { get: () => tab.view.webContents }
|
||||||
})
|
})
|
||||||
// 检查并等待用户确认(用户取消则 Promise 不 resolve,中断循环)
|
// 检查并等待用户确认(用户取消则 Promise 不 resolve,中断循环)
|
||||||
await utils.onBeforeUnload(event, proxyWindow)
|
await onBeforeUnload(event, proxyWindow)
|
||||||
// 确认后关闭这个标签(最后一个标签关闭时窗口会自动销毁)
|
// 确认后关闭这个标签(最后一个标签关闭时窗口会自动销毁)
|
||||||
closeWebTabInWindow(windowId, tab.id)
|
closeWebTabInWindow(windowId, tab.id)
|
||||||
}
|
}
|
||||||
@ -736,7 +746,7 @@ function createWebTabView(windowId, args) {
|
|||||||
})
|
})
|
||||||
} else if (url && url !== 'about:blank') {
|
} else if (url && url !== 'about:blank') {
|
||||||
// tab 模式下创建新标签
|
// tab 模式下创建新标签
|
||||||
createWebTabWindow({ url, afterId: browserView.webContents.id, windowId })
|
createWebTabWindow({ url, afterId: browserView.webContents.id, windowId: browserView.webTabWindowId })
|
||||||
}
|
}
|
||||||
return { action: 'deny' }
|
return { action: 'deny' }
|
||||||
})
|
})
|
||||||
@ -1136,7 +1146,7 @@ function safeCloseWebTab(windowId, tabId) {
|
|||||||
const proxyWindow = Object.create(windowData.window, {
|
const proxyWindow = Object.create(windowData.window, {
|
||||||
webContents: { get: () => tab.view.webContents }
|
webContents: { get: () => tab.view.webContents }
|
||||||
})
|
})
|
||||||
utils.onBeforeUnload({ preventDefault: () => {} }, proxyWindow).then(() => {
|
onBeforeUnload({ preventDefault: () => {} }, proxyWindow).then(() => {
|
||||||
closeWebTabInWindow(windowId, tabId)
|
closeWebTabInWindow(windowId, tabId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1483,6 +1493,81 @@ function destroyAllWindowMode() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 关闭拦截管理
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册关闭拦截(前端声明需要拦截关闭事件)
|
||||||
|
* @param webContentsId
|
||||||
|
*/
|
||||||
|
function registerCloseInterceptor(webContentsId) {
|
||||||
|
closeInterceptors.add(webContentsId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消关闭拦截
|
||||||
|
* @param webContentsId
|
||||||
|
*/
|
||||||
|
function unregisterCloseInterceptor(webContentsId) {
|
||||||
|
closeInterceptors.delete(webContentsId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否有关闭拦截
|
||||||
|
* @param webContentsId
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function hasCloseInterceptor(webContentsId) {
|
||||||
|
return closeInterceptors.has(webContentsId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 窗口关闭事件
|
||||||
|
* @param event
|
||||||
|
* @param app
|
||||||
|
* @param timeout
|
||||||
|
*/
|
||||||
|
function onBeforeUnload(event, app, timeout = 5000) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const contents = app.webContents
|
||||||
|
if (contents != null && !contents.isDestroyed()) {
|
||||||
|
// 检查是否有声明拦截,没有声明则直接关闭,不执行 JS
|
||||||
|
if (!hasCloseInterceptor(contents.id)) {
|
||||||
|
resolve()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 有声明拦截,执行 JS(带超时保护)
|
||||||
|
const timeoutPromise = new Promise(r => setTimeout(() => r({ __timeout: true }), timeout))
|
||||||
|
const jsPromise = contents.executeJavaScript(`if(typeof window.__onBeforeUnload === 'function'){window.__onBeforeUnload()}`, true)
|
||||||
|
|
||||||
|
Promise.race([jsPromise, timeoutPromise]).then(options => {
|
||||||
|
if (utils.isJson(options)) {
|
||||||
|
// 超时,直接允许关闭
|
||||||
|
if (options.__timeout) {
|
||||||
|
resolve()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 显示确认对话框
|
||||||
|
let choice = dialog.showMessageBoxSync(app, options)
|
||||||
|
if (choice === 1) {
|
||||||
|
contents.executeJavaScript(`if(typeof window.__removeBeforeUnload === 'function'){window.__removeBeforeUnload()}`, true).catch(() => {});
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
} else if (options !== true) {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
}).catch(_ => {
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
event.preventDefault()
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// IPC 注册
|
// IPC 注册
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@ -1923,6 +2008,21 @@ function registerIPC() {
|
|||||||
event.returnValue = "ok"
|
event.returnValue = "ok"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册关闭拦截(前端声明需要拦截关闭事件)
|
||||||
|
*/
|
||||||
|
ipcMain.on('registerCloseInterceptor', (event) => {
|
||||||
|
registerCloseInterceptor(event.sender.id)
|
||||||
|
event.returnValue = "ok"
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消关闭拦截
|
||||||
|
*/
|
||||||
|
ipcMain.on('unregisterCloseInterceptor', (event) => {
|
||||||
|
unregisterCloseInterceptor(event.sender.id)
|
||||||
|
event.returnValue = "ok"
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@ -1950,4 +2050,10 @@ module.exports = {
|
|||||||
destroyAll,
|
destroyAll,
|
||||||
closeAllWindowMode,
|
closeAllWindowMode,
|
||||||
destroyAllWindowMode,
|
destroyAllWindowMode,
|
||||||
|
|
||||||
|
// 关闭拦截管理
|
||||||
|
registerCloseInterceptor,
|
||||||
|
unregisterCloseInterceptor,
|
||||||
|
hasCloseInterceptor,
|
||||||
|
onBeforeUnload,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -594,6 +594,7 @@ export default {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.$Electron.sendMessage('registerCloseInterceptor')
|
||||||
window.__onBeforeOpenWindow = ({url}) => {
|
window.__onBeforeOpenWindow = ({url}) => {
|
||||||
const urlType = this.getUrlMethodType(url)
|
const urlType = this.getUrlMethodType(url)
|
||||||
if (urlType === 2) {
|
if (urlType === 2) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user