@lynker-desktop/electron-window-manager 0.0.9-alpha.45 → 0.0.9-alpha.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/main/index.d.ts +21 -2
- package/esm/main/index.d.ts.map +1 -1
- package/esm/main/index.js +303 -205
- package/esm/main/index.js.map +1 -1
- package/esm/renderer/index.d.ts.map +1 -1
- package/esm/renderer/index.js +316 -190
- package/esm/renderer/index.js.map +1 -1
- package/main/index.d.ts +21 -2
- package/main/index.d.ts.map +1 -1
- package/main/index.js +303 -205
- package/main/index.js.map +1 -1
- package/package.json +2 -2
- package/renderer/index.d.ts.map +1 -1
- package/renderer/index.js +316 -190
- package/renderer/index.js.map +1 -1
package/main/index.js
CHANGED
|
@@ -3,6 +3,7 @@ const electron = require('electron');
|
|
|
3
3
|
const remote = require('@electron/remote/main');
|
|
4
4
|
const eIpc = require('@lynker-desktop/electron-ipc/main');
|
|
5
5
|
const md5 = require('md5');
|
|
6
|
+
const PQueue = require('p-queue');
|
|
6
7
|
|
|
7
8
|
function _interopNamespaceDefault(e) {
|
|
8
9
|
const n = Object.create(null);
|
|
@@ -21,7 +22,7 @@ const getQueue = (() => {
|
|
|
21
22
|
let queue;
|
|
22
23
|
return async () => {
|
|
23
24
|
if (!queue) {
|
|
24
|
-
const { default: PQueue } = await import('p-queue')
|
|
25
|
+
// const { default: PQueue } = await import('p-queue')
|
|
25
26
|
queue = new PQueue({ concurrency: 1 });
|
|
26
27
|
}
|
|
27
28
|
return queue;
|
|
@@ -80,6 +81,8 @@ class WindowsManager {
|
|
|
80
81
|
* - 'localhost'、'127.0.0.1'、'::1' 以及局域网 IP(如 192.168.x.x、10.x.x.x、172.16.x.x~172.31.x.x)都视为本地白名单。
|
|
81
82
|
*/
|
|
82
83
|
constructor(preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList) {
|
|
84
|
+
// 按名称索引的 Map,用于加速查找
|
|
85
|
+
this.windowsByName = new Map();
|
|
83
86
|
// 预加载的窗口
|
|
84
87
|
this.preloadedBW = null;
|
|
85
88
|
// 预加载的窗口(无边框,有按钮)
|
|
@@ -90,9 +93,10 @@ class WindowsManager {
|
|
|
90
93
|
this.preloadedBV = null;
|
|
91
94
|
this.preloading = false;
|
|
92
95
|
this.webviewDomainWhiteList = [];
|
|
93
|
-
//
|
|
94
|
-
this.
|
|
95
|
-
|
|
96
|
+
// 窗口销毁检查的防抖函数,避免频繁检查
|
|
97
|
+
this.cleanupDestroyedWindowsDebounced = lodash.debounce(() => {
|
|
98
|
+
this._cleanupDestroyedWindows();
|
|
99
|
+
}, 500);
|
|
96
100
|
/**
|
|
97
101
|
* 防抖的排序方法
|
|
98
102
|
* @param window 目标窗口
|
|
@@ -145,6 +149,20 @@ class WindowsManager {
|
|
|
145
149
|
log('error', 'setPreloadWebContentsConfig error:', error);
|
|
146
150
|
}
|
|
147
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Promise 超时包装函数
|
|
154
|
+
* @param promise 要包装的 Promise
|
|
155
|
+
* @param timeout 超时时间(毫秒)
|
|
156
|
+
* @param errorMessage 超时错误信息
|
|
157
|
+
*/
|
|
158
|
+
async _withTimeout(promise, timeout, errorMessage) {
|
|
159
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
160
|
+
setTimeout(() => {
|
|
161
|
+
reject(new Error(errorMessage));
|
|
162
|
+
}, timeout);
|
|
163
|
+
});
|
|
164
|
+
return Promise.race([promise, timeoutPromise]);
|
|
165
|
+
}
|
|
148
166
|
/**
|
|
149
167
|
* 预加载实例
|
|
150
168
|
*/
|
|
@@ -155,54 +173,63 @@ class WindowsManager {
|
|
|
155
173
|
try {
|
|
156
174
|
if (this.preloadWebContentsConfig) {
|
|
157
175
|
log('log', 'preloadWebContentsConfig: ', this.preloadWebContentsConfig);
|
|
176
|
+
const preloadPromises = [];
|
|
158
177
|
// 根据配置决定是否预加载普通窗口
|
|
159
|
-
if (this.preloadWebContentsConfig.enableBW !== false) {
|
|
160
|
-
|
|
161
|
-
this.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
178
|
+
if (this.preloadWebContentsConfig.enableBW !== false && !this.preloadedBW) {
|
|
179
|
+
preloadPromises.push(this._createPreloadBW({}).then(i => {
|
|
180
|
+
this.preloadedBW = i;
|
|
181
|
+
log('log', 'init preloadedBW: ', !!this.preloadedBW);
|
|
182
|
+
}).catch(error => {
|
|
183
|
+
log('error', '预加载 BW 失败:', error);
|
|
184
|
+
}));
|
|
166
185
|
}
|
|
167
186
|
// 根据配置决定是否预加载无边框有按钮的窗口
|
|
168
|
-
if (this.preloadWebContentsConfig.enableBW_FramelessWithButtons !== false) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
187
|
+
if (this.preloadWebContentsConfig.enableBW_FramelessWithButtons !== false && !this.preloadedBW_FramelessWithButtons) {
|
|
188
|
+
preloadPromises.push(this._createPreloadBW({
|
|
189
|
+
frame: false,
|
|
190
|
+
autoHideMenuBar: true,
|
|
191
|
+
titleBarStyle: 'hidden',
|
|
192
|
+
}).then(i => {
|
|
193
|
+
this.preloadedBW_FramelessWithButtons = i;
|
|
194
|
+
log('log', 'init preloadedBW_FramelessWithButtons: ', !!this.preloadedBW_FramelessWithButtons);
|
|
195
|
+
}).catch(error => {
|
|
196
|
+
log('error', '预加载 BW_FramelessWithButtons 失败:', error);
|
|
197
|
+
}));
|
|
180
198
|
}
|
|
181
199
|
// 根据配置决定是否预加载无边框无按钮的窗口
|
|
182
|
-
if (this.preloadWebContentsConfig.enableBW_FramelessNoButtons !== false) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
200
|
+
if (this.preloadWebContentsConfig.enableBW_FramelessNoButtons !== false && !this.preloadedBW_FramelessNoButtons) {
|
|
201
|
+
preloadPromises.push(this._createPreloadBW({
|
|
202
|
+
frame: false,
|
|
203
|
+
autoHideMenuBar: true,
|
|
204
|
+
titleBarStyle: 'default',
|
|
205
|
+
}).then(i => {
|
|
206
|
+
this.preloadedBW_FramelessNoButtons = i;
|
|
207
|
+
log('log', 'init preloadedBW_FramelessNoButtons: ', !!this.preloadedBW_FramelessNoButtons);
|
|
208
|
+
}).catch(error => {
|
|
209
|
+
log('error', '预加载 BW_FramelessNoButtons 失败:', error);
|
|
210
|
+
}));
|
|
194
211
|
}
|
|
195
212
|
// 根据配置决定是否预加载浏览器视图
|
|
196
|
-
if (this.preloadWebContentsConfig.enableBV !== false) {
|
|
197
|
-
|
|
198
|
-
this.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
213
|
+
if (this.preloadWebContentsConfig.enableBV !== false && !this.preloadedBV) {
|
|
214
|
+
preloadPromises.push(this._createPreloadBV().then(i => {
|
|
215
|
+
this.preloadedBV = i;
|
|
216
|
+
log('log', 'init preloadedBV: ', !!this.preloadedBV);
|
|
217
|
+
}).catch(error => {
|
|
218
|
+
log('error', '预加载 BV 失败:', error);
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
if (preloadPromises.length > 0) {
|
|
222
|
+
// 添加超时机制,默认 10 秒超时
|
|
223
|
+
const timeout = 10000; // 10 秒
|
|
224
|
+
try {
|
|
225
|
+
await this._withTimeout(Promise.allSettled(preloadPromises), timeout, `预加载超时(${timeout}ms)`);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
log('error', '预加载超时:', error);
|
|
229
|
+
// 超时后继续执行,不阻塞后续流程
|
|
202
230
|
}
|
|
203
231
|
}
|
|
204
232
|
}
|
|
205
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
206
233
|
}
|
|
207
234
|
catch (e) {
|
|
208
235
|
log('error', '预创建实例失败', e);
|
|
@@ -629,46 +656,45 @@ class WindowsManager {
|
|
|
629
656
|
}
|
|
630
657
|
window.webContents.on('did-attach-webview', (_event, webContents) => {
|
|
631
658
|
const tryEnable = () => {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
659
|
+
try {
|
|
660
|
+
const url = webContents.getURL();
|
|
661
|
+
if (!url || url === 'about:blank') {
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
if (this.webviewDomainWhiteList && this.webviewDomainWhiteList.length > 0) {
|
|
665
|
+
try {
|
|
666
|
+
const { hostname } = new URL(url);
|
|
667
|
+
// 优化白名单判断,支持 .example.com 形式的子域名通配和本地/内网IP
|
|
668
|
+
const isWhiteListed = this.webviewDomainWhiteList.some(domain => {
|
|
669
|
+
if (domain === 'localhost' || domain === '127.0.0.1' || domain === '::1') {
|
|
670
|
+
return this._isLocalhost(hostname);
|
|
671
|
+
}
|
|
672
|
+
if (domain.startsWith('.')) {
|
|
673
|
+
// .example.com 允许所有 *.example.com
|
|
674
|
+
return hostname === domain.slice(1) || hostname.endsWith(domain);
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
// 精确匹配
|
|
678
|
+
return hostname === domain;
|
|
679
|
+
}
|
|
680
|
+
}) || this._isLocalhost(hostname); // 允许本地回环和内网地址
|
|
681
|
+
if (isWhiteListed) {
|
|
682
|
+
enable(webContents);
|
|
653
683
|
}
|
|
654
684
|
else {
|
|
655
|
-
|
|
656
|
-
return hostname === domain;
|
|
685
|
+
log('log', 'webview 域名未在白名单,未启用 remote', url);
|
|
657
686
|
}
|
|
658
|
-
}) || isLocalhost(hostname); // 允许本地回环和内网地址
|
|
659
|
-
if (isWhiteListed) {
|
|
660
|
-
enable(webContents);
|
|
661
687
|
}
|
|
662
|
-
|
|
663
|
-
log('log', 'webview
|
|
688
|
+
catch {
|
|
689
|
+
log('log', 'webview url 解析失败,未启用 remote', url);
|
|
664
690
|
}
|
|
665
691
|
}
|
|
666
|
-
|
|
667
|
-
|
|
692
|
+
else {
|
|
693
|
+
enable(webContents); // 没有配置白名单则全部允许
|
|
668
694
|
}
|
|
669
695
|
}
|
|
670
|
-
|
|
671
|
-
|
|
696
|
+
catch (error) {
|
|
697
|
+
log('error', 'tryEnable webview error:', error);
|
|
672
698
|
}
|
|
673
699
|
};
|
|
674
700
|
// 只监听一次,防止多次触发
|
|
@@ -680,11 +706,14 @@ class WindowsManager {
|
|
|
680
706
|
webContents.on('did-navigate', onDidNavigate);
|
|
681
707
|
webContents.on('did-finish-load', onDidNavigate);
|
|
682
708
|
});
|
|
683
|
-
window.webContents.on('close', () => {
|
|
684
|
-
|
|
685
|
-
})
|
|
709
|
+
// window.webContents.on('close', () => {
|
|
710
|
+
// this.windows.delete(window.id || window._id)
|
|
711
|
+
// })
|
|
686
712
|
window.webContents.on('destroyed', () => {
|
|
687
|
-
|
|
713
|
+
const winId = window.id || window._id;
|
|
714
|
+
this.windows.delete(winId);
|
|
715
|
+
// 同步清理名称索引
|
|
716
|
+
this.windowsByName.delete(window._name);
|
|
688
717
|
});
|
|
689
718
|
window.webContents.on('dom-ready', () => {
|
|
690
719
|
if (openDevTools) {
|
|
@@ -699,14 +728,20 @@ class WindowsManager {
|
|
|
699
728
|
// @ts-ignore
|
|
700
729
|
window.on('closed', () => {
|
|
701
730
|
log('log', 'closed', window.id, window._name);
|
|
702
|
-
|
|
731
|
+
const winId = window.id || window._id;
|
|
732
|
+
this.windows.delete(winId);
|
|
733
|
+
// 同步清理名称索引
|
|
734
|
+
this.windowsByName.delete(window._name);
|
|
703
735
|
});
|
|
704
736
|
}
|
|
705
737
|
if (type === 'BV') {
|
|
706
738
|
parentWin?.addBrowserView(window);
|
|
707
739
|
log('log', 'create - addBrowserView');
|
|
708
740
|
}
|
|
709
|
-
|
|
741
|
+
const winId = window.id || window._id || window.webContents.id;
|
|
742
|
+
this.windows.set(winId, window);
|
|
743
|
+
// 同步更新名称索引
|
|
744
|
+
this.windowsByName.set(window._name, winId);
|
|
710
745
|
log('log', 'create', this.windows.keys());
|
|
711
746
|
// 初始化值
|
|
712
747
|
window.webContents.on('did-finish-load', () => {
|
|
@@ -900,149 +935,213 @@ class WindowsManager {
|
|
|
900
935
|
window.webContents.on('did-stop-loading', onFailure);
|
|
901
936
|
}
|
|
902
937
|
}
|
|
938
|
+
/**
|
|
939
|
+
* 检查窗口是否已销毁
|
|
940
|
+
*/
|
|
941
|
+
_isWindowDestroyed(win) {
|
|
942
|
+
try {
|
|
943
|
+
return !win || !win.webContents || win.webContents.isDestroyed();
|
|
944
|
+
}
|
|
945
|
+
catch {
|
|
946
|
+
return true;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* 判断是否本地/内网IP
|
|
951
|
+
*/
|
|
952
|
+
_isLocalhost(hostname) {
|
|
953
|
+
return (hostname === 'localhost' ||
|
|
954
|
+
hostname === '127.0.0.1' ||
|
|
955
|
+
hostname === '::1' ||
|
|
956
|
+
/^192\.168\./.test(hostname) ||
|
|
957
|
+
/^10\./.test(hostname) ||
|
|
958
|
+
/^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname));
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* 清理已销毁的窗口(延迟执行,避免频繁检查)
|
|
962
|
+
*/
|
|
963
|
+
_cleanupDestroyedWindows() {
|
|
964
|
+
const toDelete = [];
|
|
965
|
+
this.windows.forEach((win, key) => {
|
|
966
|
+
if (this._isWindowDestroyed(win)) {
|
|
967
|
+
toDelete.push(key);
|
|
968
|
+
// 同步清理名称索引
|
|
969
|
+
if (win?._name) {
|
|
970
|
+
this.windowsByName.delete(win._name);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
toDelete.forEach(key => this.windows.delete(key));
|
|
975
|
+
}
|
|
903
976
|
get(idOrName) {
|
|
904
977
|
log('log', 'get', idOrName);
|
|
905
978
|
let win;
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
979
|
+
if (typeof idOrName === 'number') {
|
|
980
|
+
// 按 ID 查找(O(1))
|
|
981
|
+
win = this.windows.get(idOrName);
|
|
982
|
+
// 如果找不到,尝试按 _id 查找
|
|
983
|
+
if (!win) {
|
|
984
|
+
this.windows.forEach(w => {
|
|
985
|
+
if (w._id === idOrName) {
|
|
986
|
+
win = w;
|
|
987
|
+
}
|
|
988
|
+
});
|
|
914
989
|
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
990
|
+
}
|
|
991
|
+
else if (typeof idOrName === 'string') {
|
|
992
|
+
// 按名称查找(O(1),使用索引)
|
|
993
|
+
const winId = this.windowsByName.get(idOrName);
|
|
994
|
+
if (winId !== undefined) {
|
|
995
|
+
win = this.windows.get(winId);
|
|
919
996
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
997
|
+
// 如果索引中找不到,回退到遍历查找(兼容旧数据)
|
|
998
|
+
if (!win) {
|
|
999
|
+
this.windows.forEach(w => {
|
|
1000
|
+
if (w._name === idOrName) {
|
|
1001
|
+
win = w;
|
|
1002
|
+
// 更新索引
|
|
1003
|
+
this.windowsByName.set(idOrName, w.id || w._id);
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
924
1006
|
}
|
|
925
|
-
}
|
|
926
|
-
//
|
|
927
|
-
if (win &&
|
|
1007
|
+
}
|
|
1008
|
+
// 检查找到的窗口是否已销毁
|
|
1009
|
+
if (win && !this._isWindowDestroyed(win)) {
|
|
928
1010
|
return win;
|
|
929
1011
|
}
|
|
1012
|
+
// 窗口已销毁,触发清理
|
|
1013
|
+
if (win) {
|
|
1014
|
+
const winId = win.id || win._id;
|
|
1015
|
+
this.windows.delete(winId);
|
|
1016
|
+
if (win._name) {
|
|
1017
|
+
this.windowsByName.delete(win._name);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
// 延迟清理其他已销毁的窗口
|
|
1021
|
+
this.cleanupDestroyedWindowsDebounced();
|
|
930
1022
|
return undefined;
|
|
931
1023
|
}
|
|
932
1024
|
getAll(type) {
|
|
933
1025
|
log('log', 'getAll');
|
|
934
|
-
|
|
935
|
-
const
|
|
1026
|
+
// 先清理已销毁的窗口
|
|
1027
|
+
const toDelete = [];
|
|
936
1028
|
this.windows.forEach((win, key) => {
|
|
937
|
-
if (
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
}
|
|
943
|
-
if (win?._type === 'BV') {
|
|
944
|
-
bvWindows.set(key, win);
|
|
1029
|
+
if (this._isWindowDestroyed(win)) {
|
|
1030
|
+
toDelete.push(key);
|
|
1031
|
+
if (win?._name) {
|
|
1032
|
+
this.windowsByName.delete(win._name);
|
|
1033
|
+
}
|
|
945
1034
|
}
|
|
946
1035
|
});
|
|
947
|
-
|
|
948
|
-
|
|
1036
|
+
toDelete.forEach(key => this.windows.delete(key));
|
|
1037
|
+
if (!type) {
|
|
1038
|
+
return this.windows;
|
|
949
1039
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1040
|
+
const result = new Map();
|
|
1041
|
+
this.windows.forEach((win, key) => {
|
|
1042
|
+
if (!this._isWindowDestroyed(win) && win._type === type) {
|
|
1043
|
+
result.set(key, win);
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
// 使用类型断言,TypeScript 会通过方法重载确保类型正确
|
|
1047
|
+
return result;
|
|
954
1048
|
}
|
|
955
1049
|
close(idOrName) {
|
|
956
1050
|
log('log', 'close', idOrName);
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
});
|
|
970
|
-
// @ts-ignore
|
|
971
|
-
win && this.windows.delete(win?.id);
|
|
972
|
-
// @ts-ignore
|
|
973
|
-
if (win) {
|
|
974
|
-
// @ts-ignore
|
|
1051
|
+
const win = this.get(idOrName);
|
|
1052
|
+
if (!win) {
|
|
1053
|
+
return false;
|
|
1054
|
+
}
|
|
1055
|
+
const winId = win.id || win._id;
|
|
1056
|
+
this.windows.delete(winId);
|
|
1057
|
+
if (win._name) {
|
|
1058
|
+
this.windowsByName.delete(win._name);
|
|
1059
|
+
}
|
|
1060
|
+
try {
|
|
975
1061
|
if (win._type === 'BV') {
|
|
1062
|
+
// 从所有 BW 窗口中移除该 BV
|
|
976
1063
|
this.windows.forEach(i => {
|
|
977
|
-
if (i?._type === 'BW') {
|
|
1064
|
+
if (i?._type === 'BW' && !this._isWindowDestroyed(i)) {
|
|
978
1065
|
const _win = i;
|
|
979
|
-
|
|
1066
|
+
try {
|
|
1067
|
+
_win.removeBrowserView(win);
|
|
1068
|
+
}
|
|
1069
|
+
catch (error) {
|
|
1070
|
+
// 忽略错误,可能已经移除
|
|
1071
|
+
}
|
|
980
1072
|
}
|
|
981
1073
|
});
|
|
982
1074
|
}
|
|
983
1075
|
// @ts-ignore
|
|
984
|
-
win
|
|
1076
|
+
win.webContents?.destroy?.();
|
|
985
1077
|
// @ts-ignore
|
|
986
|
-
win
|
|
1078
|
+
win.close?.();
|
|
987
1079
|
// @ts-ignore
|
|
988
|
-
win
|
|
1080
|
+
win.destroy?.();
|
|
1081
|
+
}
|
|
1082
|
+
catch (error) {
|
|
1083
|
+
log('error', 'close window error:', error);
|
|
989
1084
|
}
|
|
990
1085
|
return true;
|
|
991
1086
|
}
|
|
992
1087
|
closeAll() {
|
|
993
1088
|
log('log', 'closeAll');
|
|
994
|
-
this.windows.
|
|
1089
|
+
const windowsToClose = Array.from(this.windows.values());
|
|
1090
|
+
// 先清空 Map,避免在遍历过程中修改
|
|
1091
|
+
this.windows.clear();
|
|
1092
|
+
this.windowsByName.clear();
|
|
1093
|
+
// 收集所有 BW 窗口,用于批量移除 BV
|
|
1094
|
+
const bwWindows = windowsToClose.filter(w => w._type === 'BW' && !this._isWindowDestroyed(w));
|
|
1095
|
+
const bvWindows = windowsToClose.filter(w => w._type === 'BV');
|
|
1096
|
+
// 先从所有 BW 窗口中移除 BV
|
|
1097
|
+
bvWindows.forEach(bv => {
|
|
1098
|
+
bwWindows.forEach(bw => {
|
|
1099
|
+
try {
|
|
1100
|
+
bw.removeBrowserView(bv);
|
|
1101
|
+
}
|
|
1102
|
+
catch (error) {
|
|
1103
|
+
// 忽略错误,可能已经移除
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1106
|
+
});
|
|
1107
|
+
// 然后销毁所有窗口
|
|
1108
|
+
windowsToClose.forEach((win) => {
|
|
995
1109
|
try {
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
if (win._type === 'BV') {
|
|
999
|
-
this.windows.forEach(i => {
|
|
1000
|
-
if (i?._type === 'BW') {
|
|
1001
|
-
const _win = i;
|
|
1002
|
-
_win.removeBrowserView(win);
|
|
1003
|
-
}
|
|
1004
|
-
});
|
|
1110
|
+
if (this._isWindowDestroyed(win)) {
|
|
1111
|
+
return;
|
|
1005
1112
|
}
|
|
1006
1113
|
// @ts-ignore
|
|
1007
|
-
win
|
|
1114
|
+
win.webContents?.destroy?.();
|
|
1008
1115
|
// @ts-ignore
|
|
1009
|
-
win
|
|
1116
|
+
win.close?.();
|
|
1010
1117
|
// @ts-ignore
|
|
1011
|
-
win
|
|
1118
|
+
win.destroy?.();
|
|
1012
1119
|
}
|
|
1013
1120
|
catch (error) {
|
|
1121
|
+
log('error', 'closeAll error:', error);
|
|
1014
1122
|
}
|
|
1015
1123
|
});
|
|
1016
1124
|
}
|
|
1017
1125
|
rename(idOrName, newName) {
|
|
1018
1126
|
log('log', 'rename', idOrName, newName);
|
|
1019
|
-
|
|
1020
|
-
// 先查找目标窗口
|
|
1021
|
-
if (typeof idOrName === 'number') {
|
|
1022
|
-
win = this.get(idOrName);
|
|
1023
|
-
}
|
|
1024
|
-
else if (typeof idOrName === 'string') {
|
|
1025
|
-
this.windows.forEach(i => {
|
|
1026
|
-
if (i?._name === idOrName) {
|
|
1027
|
-
win = i;
|
|
1028
|
-
}
|
|
1029
|
-
});
|
|
1030
|
-
}
|
|
1127
|
+
const win = this.get(idOrName);
|
|
1031
1128
|
if (!win) {
|
|
1032
|
-
// 没有找到目标窗口
|
|
1033
1129
|
return undefined;
|
|
1034
1130
|
}
|
|
1035
|
-
//
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1131
|
+
// 检查新名字是否已存在(使用索引加速)
|
|
1132
|
+
const existingWinId = this.windowsByName.get(newName);
|
|
1133
|
+
if (existingWinId !== undefined) {
|
|
1134
|
+
const existingWin = this.windows.get(existingWinId);
|
|
1135
|
+
if (existingWin && existingWin !== win && !this._isWindowDestroyed(existingWin)) {
|
|
1136
|
+
// 新名字已被占用
|
|
1137
|
+
return undefined;
|
|
1040
1138
|
}
|
|
1041
|
-
});
|
|
1042
|
-
if (nameExists) {
|
|
1043
|
-
// 新名字已被占用
|
|
1044
|
-
return undefined;
|
|
1045
1139
|
}
|
|
1140
|
+
// 更新名称索引
|
|
1141
|
+
const oldName = win._name;
|
|
1142
|
+
const winId = win.id || win._id;
|
|
1143
|
+
this.windowsByName.delete(oldName);
|
|
1144
|
+
this.windowsByName.set(newName, winId);
|
|
1046
1145
|
// 修改名字并同步 webContents
|
|
1047
1146
|
win._name = newName;
|
|
1048
1147
|
initWebContentsVal(win, `${this.preload || ''}`);
|
|
@@ -1050,12 +1149,7 @@ class WindowsManager {
|
|
|
1050
1149
|
}
|
|
1051
1150
|
reInitUrl(idOrName, url) {
|
|
1052
1151
|
log('log', 'reInitUrl', idOrName, url);
|
|
1053
|
-
|
|
1054
|
-
this.windows.forEach(i => {
|
|
1055
|
-
if (i?._name === idOrName) {
|
|
1056
|
-
win = i;
|
|
1057
|
-
}
|
|
1058
|
-
});
|
|
1152
|
+
const win = this.get(idOrName);
|
|
1059
1153
|
if (!win) {
|
|
1060
1154
|
return undefined;
|
|
1061
1155
|
}
|
|
@@ -1256,21 +1350,25 @@ class WindowsManager {
|
|
|
1256
1350
|
*/
|
|
1257
1351
|
_sortBrowserViews(window, addView) {
|
|
1258
1352
|
try {
|
|
1353
|
+
if (window.isDestroyed()) {
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1259
1356
|
const views = window.getBrowserViews() || [];
|
|
1260
1357
|
if (views.length <= 1)
|
|
1261
1358
|
return;
|
|
1262
1359
|
log('log', 'sortBrowserViews', views?.map(i => i.webContents.id));
|
|
1263
|
-
//
|
|
1264
|
-
const sortedViews = views.sort((a, b) => {
|
|
1360
|
+
// 创建排序后的视图数组(不修改原数组)
|
|
1361
|
+
const sortedViews = [...views].sort((a, b) => {
|
|
1265
1362
|
const zIndexA = a._zIndex ?? 0;
|
|
1266
1363
|
const zIndexB = b._zIndex ?? 0;
|
|
1267
1364
|
return zIndexA - zIndexB;
|
|
1268
1365
|
});
|
|
1269
|
-
//
|
|
1366
|
+
// 检查是否已经按正确顺序排列(优化:提前退出)
|
|
1270
1367
|
let needsReorder = false;
|
|
1271
1368
|
for (let i = 0; i < views.length; i++) {
|
|
1272
|
-
|
|
1273
|
-
|
|
1369
|
+
const view = views[i];
|
|
1370
|
+
const sortedView = sortedViews[i];
|
|
1371
|
+
if (!view || !sortedView || view.webContents.id !== sortedView.webContents.id) {
|
|
1274
1372
|
needsReorder = true;
|
|
1275
1373
|
break;
|
|
1276
1374
|
}
|
|
@@ -1279,36 +1377,36 @@ class WindowsManager {
|
|
|
1279
1377
|
// 如果已经按正确顺序排列,则不需要重新排序
|
|
1280
1378
|
if (!needsReorder)
|
|
1281
1379
|
return;
|
|
1282
|
-
//
|
|
1283
|
-
// 临时隐藏窗口内容
|
|
1284
|
-
const wasVisible = window.isVisible();
|
|
1285
|
-
if (wasVisible) {
|
|
1286
|
-
// window.hide();
|
|
1287
|
-
}
|
|
1288
|
-
// 移除所有BrowserView
|
|
1380
|
+
// 移除所有BrowserView(排除刚添加的视图,避免不必要的操作)
|
|
1289
1381
|
views.forEach(view => {
|
|
1290
1382
|
if (addView?.webContents?.id !== view.webContents.id) {
|
|
1291
|
-
|
|
1292
|
-
|
|
1383
|
+
try {
|
|
1384
|
+
// @ts-ignore
|
|
1385
|
+
window.removeBrowserView(view, false);
|
|
1386
|
+
}
|
|
1387
|
+
catch (error) {
|
|
1388
|
+
// 忽略错误,视图可能已经被移除
|
|
1389
|
+
}
|
|
1293
1390
|
}
|
|
1294
1391
|
});
|
|
1295
1392
|
// 按正确顺序重新添加
|
|
1296
1393
|
sortedViews.forEach((view, index) => {
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1394
|
+
try {
|
|
1395
|
+
if (index === 0) {
|
|
1396
|
+
// 第一个设置为当前视图
|
|
1397
|
+
// @ts-ignore
|
|
1398
|
+
window.setBrowserView(view, false);
|
|
1399
|
+
}
|
|
1400
|
+
else {
|
|
1401
|
+
// 其他视图添加到后面
|
|
1402
|
+
// @ts-ignore
|
|
1403
|
+
window.addBrowserView(view, false);
|
|
1404
|
+
}
|
|
1301
1405
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
// @ts-ignore
|
|
1305
|
-
window.addBrowserView(view, false);
|
|
1406
|
+
catch (error) {
|
|
1407
|
+
log('error', 'addBrowserView in sortBrowserViews error:', error);
|
|
1306
1408
|
}
|
|
1307
1409
|
});
|
|
1308
|
-
// 恢复窗口显示
|
|
1309
|
-
if (wasVisible) {
|
|
1310
|
-
// window.show();
|
|
1311
|
-
}
|
|
1312
1410
|
}
|
|
1313
1411
|
catch (error) {
|
|
1314
1412
|
log('error', 'sortBrowserViews error:', error);
|