@lynker-desktop/electron-window-manager 0.0.9-alpha.46 → 0.0.9-alpha.48
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 +301 -204
- 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 +301 -204
- 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
|
@@ -81,6 +81,8 @@ class WindowsManager {
|
|
|
81
81
|
* - 'localhost'、'127.0.0.1'、'::1' 以及局域网 IP(如 192.168.x.x、10.x.x.x、172.16.x.x~172.31.x.x)都视为本地白名单。
|
|
82
82
|
*/
|
|
83
83
|
constructor(preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList) {
|
|
84
|
+
// 按名称索引的 Map,用于加速查找
|
|
85
|
+
this.windowsByName = new Map();
|
|
84
86
|
// 预加载的窗口
|
|
85
87
|
this.preloadedBW = null;
|
|
86
88
|
// 预加载的窗口(无边框,有按钮)
|
|
@@ -91,9 +93,10 @@ class WindowsManager {
|
|
|
91
93
|
this.preloadedBV = null;
|
|
92
94
|
this.preloading = false;
|
|
93
95
|
this.webviewDomainWhiteList = [];
|
|
94
|
-
//
|
|
95
|
-
this.
|
|
96
|
-
|
|
96
|
+
// 窗口销毁检查的防抖函数,避免频繁检查
|
|
97
|
+
this.cleanupDestroyedWindowsDebounced = lodash.debounce(() => {
|
|
98
|
+
this._cleanupDestroyedWindows();
|
|
99
|
+
}, 500);
|
|
97
100
|
/**
|
|
98
101
|
* 防抖的排序方法
|
|
99
102
|
* @param window 目标窗口
|
|
@@ -146,6 +149,20 @@ class WindowsManager {
|
|
|
146
149
|
log('error', 'setPreloadWebContentsConfig error:', error);
|
|
147
150
|
}
|
|
148
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
|
+
}
|
|
149
166
|
/**
|
|
150
167
|
* 预加载实例
|
|
151
168
|
*/
|
|
@@ -156,54 +173,63 @@ class WindowsManager {
|
|
|
156
173
|
try {
|
|
157
174
|
if (this.preloadWebContentsConfig) {
|
|
158
175
|
log('log', 'preloadWebContentsConfig: ', this.preloadWebContentsConfig);
|
|
176
|
+
const preloadPromises = [];
|
|
159
177
|
// 根据配置决定是否预加载普通窗口
|
|
160
|
-
if (this.preloadWebContentsConfig.enableBW !== false) {
|
|
161
|
-
|
|
162
|
-
this.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
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
|
+
}));
|
|
167
185
|
}
|
|
168
186
|
// 根据配置决定是否预加载无边框有按钮的窗口
|
|
169
|
-
if (this.preloadWebContentsConfig.enableBW_FramelessWithButtons !== false) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
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
|
+
}));
|
|
181
198
|
}
|
|
182
199
|
// 根据配置决定是否预加载无边框无按钮的窗口
|
|
183
|
-
if (this.preloadWebContentsConfig.enableBW_FramelessNoButtons !== false) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
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
|
+
}));
|
|
195
211
|
}
|
|
196
212
|
// 根据配置决定是否预加载浏览器视图
|
|
197
|
-
if (this.preloadWebContentsConfig.enableBV !== false) {
|
|
198
|
-
|
|
199
|
-
this.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
+
// 超时后继续执行,不阻塞后续流程
|
|
203
230
|
}
|
|
204
231
|
}
|
|
205
232
|
}
|
|
206
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
207
233
|
}
|
|
208
234
|
catch (e) {
|
|
209
235
|
log('error', '预创建实例失败', e);
|
|
@@ -630,46 +656,45 @@ class WindowsManager {
|
|
|
630
656
|
}
|
|
631
657
|
window.webContents.on('did-attach-webview', (_event, webContents) => {
|
|
632
658
|
const tryEnable = () => {
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
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);
|
|
654
683
|
}
|
|
655
684
|
else {
|
|
656
|
-
|
|
657
|
-
return hostname === domain;
|
|
685
|
+
log('log', 'webview 域名未在白名单,未启用 remote', url);
|
|
658
686
|
}
|
|
659
|
-
}) || isLocalhost(hostname); // 允许本地回环和内网地址
|
|
660
|
-
if (isWhiteListed) {
|
|
661
|
-
enable(webContents);
|
|
662
687
|
}
|
|
663
|
-
|
|
664
|
-
log('log', 'webview
|
|
688
|
+
catch {
|
|
689
|
+
log('log', 'webview url 解析失败,未启用 remote', url);
|
|
665
690
|
}
|
|
666
691
|
}
|
|
667
|
-
|
|
668
|
-
|
|
692
|
+
else {
|
|
693
|
+
enable(webContents); // 没有配置白名单则全部允许
|
|
669
694
|
}
|
|
670
695
|
}
|
|
671
|
-
|
|
672
|
-
|
|
696
|
+
catch (error) {
|
|
697
|
+
log('error', 'tryEnable webview error:', error);
|
|
673
698
|
}
|
|
674
699
|
};
|
|
675
700
|
// 只监听一次,防止多次触发
|
|
@@ -681,11 +706,14 @@ class WindowsManager {
|
|
|
681
706
|
webContents.on('did-navigate', onDidNavigate);
|
|
682
707
|
webContents.on('did-finish-load', onDidNavigate);
|
|
683
708
|
});
|
|
684
|
-
window.webContents.on('close', () => {
|
|
685
|
-
|
|
686
|
-
})
|
|
709
|
+
// window.webContents.on('close', () => {
|
|
710
|
+
// this.windows.delete(window.id || window._id)
|
|
711
|
+
// })
|
|
687
712
|
window.webContents.on('destroyed', () => {
|
|
688
|
-
|
|
713
|
+
const winId = window.id || window._id;
|
|
714
|
+
this.windows.delete(winId);
|
|
715
|
+
// 同步清理名称索引
|
|
716
|
+
this.windowsByName.delete(window._name);
|
|
689
717
|
});
|
|
690
718
|
window.webContents.on('dom-ready', () => {
|
|
691
719
|
if (openDevTools) {
|
|
@@ -700,14 +728,20 @@ class WindowsManager {
|
|
|
700
728
|
// @ts-ignore
|
|
701
729
|
window.on('closed', () => {
|
|
702
730
|
log('log', 'closed', window.id, window._name);
|
|
703
|
-
|
|
731
|
+
const winId = window.id || window._id;
|
|
732
|
+
this.windows.delete(winId);
|
|
733
|
+
// 同步清理名称索引
|
|
734
|
+
this.windowsByName.delete(window._name);
|
|
704
735
|
});
|
|
705
736
|
}
|
|
706
737
|
if (type === 'BV') {
|
|
707
738
|
parentWin?.addBrowserView(window);
|
|
708
739
|
log('log', 'create - addBrowserView');
|
|
709
740
|
}
|
|
710
|
-
|
|
741
|
+
const winId = window.id || window._id || window.webContents.id;
|
|
742
|
+
this.windows.set(winId, window);
|
|
743
|
+
// 同步更新名称索引
|
|
744
|
+
this.windowsByName.set(window._name, winId);
|
|
711
745
|
log('log', 'create', this.windows.keys());
|
|
712
746
|
// 初始化值
|
|
713
747
|
window.webContents.on('did-finish-load', () => {
|
|
@@ -901,149 +935,213 @@ class WindowsManager {
|
|
|
901
935
|
window.webContents.on('did-stop-loading', onFailure);
|
|
902
936
|
}
|
|
903
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
|
+
}
|
|
904
976
|
get(idOrName) {
|
|
905
977
|
log('log', 'get', idOrName);
|
|
906
978
|
let win;
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
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
|
+
});
|
|
915
989
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
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);
|
|
920
996
|
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
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
|
+
});
|
|
925
1006
|
}
|
|
926
|
-
}
|
|
927
|
-
//
|
|
928
|
-
if (win &&
|
|
1007
|
+
}
|
|
1008
|
+
// 检查找到的窗口是否已销毁
|
|
1009
|
+
if (win && !this._isWindowDestroyed(win)) {
|
|
929
1010
|
return win;
|
|
930
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();
|
|
931
1022
|
return undefined;
|
|
932
1023
|
}
|
|
933
1024
|
getAll(type) {
|
|
934
1025
|
log('log', 'getAll');
|
|
935
|
-
|
|
936
|
-
const
|
|
1026
|
+
// 先清理已销毁的窗口
|
|
1027
|
+
const toDelete = [];
|
|
937
1028
|
this.windows.forEach((win, key) => {
|
|
938
|
-
if (
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
}
|
|
944
|
-
if (win?._type === 'BV') {
|
|
945
|
-
bvWindows.set(key, win);
|
|
1029
|
+
if (this._isWindowDestroyed(win)) {
|
|
1030
|
+
toDelete.push(key);
|
|
1031
|
+
if (win?._name) {
|
|
1032
|
+
this.windowsByName.delete(win._name);
|
|
1033
|
+
}
|
|
946
1034
|
}
|
|
947
1035
|
});
|
|
948
|
-
|
|
949
|
-
|
|
1036
|
+
toDelete.forEach(key => this.windows.delete(key));
|
|
1037
|
+
if (!type) {
|
|
1038
|
+
return this.windows;
|
|
950
1039
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
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;
|
|
955
1048
|
}
|
|
956
1049
|
close(idOrName) {
|
|
957
1050
|
log('log', 'close', idOrName);
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
});
|
|
971
|
-
// @ts-ignore
|
|
972
|
-
win && this.windows.delete(win?.id);
|
|
973
|
-
// @ts-ignore
|
|
974
|
-
if (win) {
|
|
975
|
-
// @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 {
|
|
976
1061
|
if (win._type === 'BV') {
|
|
1062
|
+
// 从所有 BW 窗口中移除该 BV
|
|
977
1063
|
this.windows.forEach(i => {
|
|
978
|
-
if (i?._type === 'BW') {
|
|
1064
|
+
if (i?._type === 'BW' && !this._isWindowDestroyed(i)) {
|
|
979
1065
|
const _win = i;
|
|
980
|
-
|
|
1066
|
+
try {
|
|
1067
|
+
_win.removeBrowserView(win);
|
|
1068
|
+
}
|
|
1069
|
+
catch (error) {
|
|
1070
|
+
// 忽略错误,可能已经移除
|
|
1071
|
+
}
|
|
981
1072
|
}
|
|
982
1073
|
});
|
|
983
1074
|
}
|
|
984
1075
|
// @ts-ignore
|
|
985
|
-
win
|
|
1076
|
+
win.webContents?.destroy?.();
|
|
986
1077
|
// @ts-ignore
|
|
987
|
-
win
|
|
1078
|
+
win.close?.();
|
|
988
1079
|
// @ts-ignore
|
|
989
|
-
win
|
|
1080
|
+
win.destroy?.();
|
|
1081
|
+
}
|
|
1082
|
+
catch (error) {
|
|
1083
|
+
log('error', 'close window error:', error);
|
|
990
1084
|
}
|
|
991
1085
|
return true;
|
|
992
1086
|
}
|
|
993
1087
|
closeAll() {
|
|
994
1088
|
log('log', 'closeAll');
|
|
995
|
-
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) => {
|
|
996
1109
|
try {
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
if (win._type === 'BV') {
|
|
1000
|
-
this.windows.forEach(i => {
|
|
1001
|
-
if (i?._type === 'BW') {
|
|
1002
|
-
const _win = i;
|
|
1003
|
-
_win.removeBrowserView(win);
|
|
1004
|
-
}
|
|
1005
|
-
});
|
|
1110
|
+
if (this._isWindowDestroyed(win)) {
|
|
1111
|
+
return;
|
|
1006
1112
|
}
|
|
1007
1113
|
// @ts-ignore
|
|
1008
|
-
win
|
|
1114
|
+
win.webContents?.destroy?.();
|
|
1009
1115
|
// @ts-ignore
|
|
1010
|
-
win
|
|
1116
|
+
win.close?.();
|
|
1011
1117
|
// @ts-ignore
|
|
1012
|
-
win
|
|
1118
|
+
win.destroy?.();
|
|
1013
1119
|
}
|
|
1014
1120
|
catch (error) {
|
|
1121
|
+
log('error', 'closeAll error:', error);
|
|
1015
1122
|
}
|
|
1016
1123
|
});
|
|
1017
1124
|
}
|
|
1018
1125
|
rename(idOrName, newName) {
|
|
1019
1126
|
log('log', 'rename', idOrName, newName);
|
|
1020
|
-
|
|
1021
|
-
// 先查找目标窗口
|
|
1022
|
-
if (typeof idOrName === 'number') {
|
|
1023
|
-
win = this.get(idOrName);
|
|
1024
|
-
}
|
|
1025
|
-
else if (typeof idOrName === 'string') {
|
|
1026
|
-
this.windows.forEach(i => {
|
|
1027
|
-
if (i?._name === idOrName) {
|
|
1028
|
-
win = i;
|
|
1029
|
-
}
|
|
1030
|
-
});
|
|
1031
|
-
}
|
|
1127
|
+
const win = this.get(idOrName);
|
|
1032
1128
|
if (!win) {
|
|
1033
|
-
// 没有找到目标窗口
|
|
1034
1129
|
return undefined;
|
|
1035
1130
|
}
|
|
1036
|
-
//
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
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;
|
|
1041
1138
|
}
|
|
1042
|
-
});
|
|
1043
|
-
if (nameExists) {
|
|
1044
|
-
// 新名字已被占用
|
|
1045
|
-
return undefined;
|
|
1046
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);
|
|
1047
1145
|
// 修改名字并同步 webContents
|
|
1048
1146
|
win._name = newName;
|
|
1049
1147
|
initWebContentsVal(win, `${this.preload || ''}`);
|
|
@@ -1051,12 +1149,7 @@ class WindowsManager {
|
|
|
1051
1149
|
}
|
|
1052
1150
|
reInitUrl(idOrName, url) {
|
|
1053
1151
|
log('log', 'reInitUrl', idOrName, url);
|
|
1054
|
-
|
|
1055
|
-
this.windows.forEach(i => {
|
|
1056
|
-
if (i?._name === idOrName) {
|
|
1057
|
-
win = i;
|
|
1058
|
-
}
|
|
1059
|
-
});
|
|
1152
|
+
const win = this.get(idOrName);
|
|
1060
1153
|
if (!win) {
|
|
1061
1154
|
return undefined;
|
|
1062
1155
|
}
|
|
@@ -1257,21 +1350,25 @@ class WindowsManager {
|
|
|
1257
1350
|
*/
|
|
1258
1351
|
_sortBrowserViews(window, addView) {
|
|
1259
1352
|
try {
|
|
1353
|
+
if (window.isDestroyed()) {
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1260
1356
|
const views = window.getBrowserViews() || [];
|
|
1261
1357
|
if (views.length <= 1)
|
|
1262
1358
|
return;
|
|
1263
1359
|
log('log', 'sortBrowserViews', views?.map(i => i.webContents.id));
|
|
1264
|
-
//
|
|
1265
|
-
const sortedViews = views.sort((a, b) => {
|
|
1360
|
+
// 创建排序后的视图数组(不修改原数组)
|
|
1361
|
+
const sortedViews = [...views].sort((a, b) => {
|
|
1266
1362
|
const zIndexA = a._zIndex ?? 0;
|
|
1267
1363
|
const zIndexB = b._zIndex ?? 0;
|
|
1268
1364
|
return zIndexA - zIndexB;
|
|
1269
1365
|
});
|
|
1270
|
-
//
|
|
1366
|
+
// 检查是否已经按正确顺序排列(优化:提前退出)
|
|
1271
1367
|
let needsReorder = false;
|
|
1272
1368
|
for (let i = 0; i < views.length; i++) {
|
|
1273
|
-
|
|
1274
|
-
|
|
1369
|
+
const view = views[i];
|
|
1370
|
+
const sortedView = sortedViews[i];
|
|
1371
|
+
if (!view || !sortedView || view.webContents.id !== sortedView.webContents.id) {
|
|
1275
1372
|
needsReorder = true;
|
|
1276
1373
|
break;
|
|
1277
1374
|
}
|
|
@@ -1280,36 +1377,36 @@ class WindowsManager {
|
|
|
1280
1377
|
// 如果已经按正确顺序排列,则不需要重新排序
|
|
1281
1378
|
if (!needsReorder)
|
|
1282
1379
|
return;
|
|
1283
|
-
//
|
|
1284
|
-
// 临时隐藏窗口内容
|
|
1285
|
-
const wasVisible = window.isVisible();
|
|
1286
|
-
if (wasVisible) {
|
|
1287
|
-
// window.hide();
|
|
1288
|
-
}
|
|
1289
|
-
// 移除所有BrowserView
|
|
1380
|
+
// 移除所有BrowserView(排除刚添加的视图,避免不必要的操作)
|
|
1290
1381
|
views.forEach(view => {
|
|
1291
1382
|
if (addView?.webContents?.id !== view.webContents.id) {
|
|
1292
|
-
|
|
1293
|
-
|
|
1383
|
+
try {
|
|
1384
|
+
// @ts-ignore
|
|
1385
|
+
window.removeBrowserView(view, false);
|
|
1386
|
+
}
|
|
1387
|
+
catch (error) {
|
|
1388
|
+
// 忽略错误,视图可能已经被移除
|
|
1389
|
+
}
|
|
1294
1390
|
}
|
|
1295
1391
|
});
|
|
1296
1392
|
// 按正确顺序重新添加
|
|
1297
1393
|
sortedViews.forEach((view, index) => {
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
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
|
+
}
|
|
1302
1405
|
}
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
// @ts-ignore
|
|
1306
|
-
window.addBrowserView(view, false);
|
|
1406
|
+
catch (error) {
|
|
1407
|
+
log('error', 'addBrowserView in sortBrowserViews error:', error);
|
|
1307
1408
|
}
|
|
1308
1409
|
});
|
|
1309
|
-
// 恢复窗口显示
|
|
1310
|
-
if (wasVisible) {
|
|
1311
|
-
// window.show();
|
|
1312
|
-
}
|
|
1313
1410
|
}
|
|
1314
1411
|
catch (error) {
|
|
1315
1412
|
log('error', 'sortBrowserViews error:', error);
|