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