@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/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.createQueue = [];
95
- this.isCreating = false;
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
- if (!this.preloadedBW) {
161
- this._createPreloadBW({}).then(i => {
162
- this.preloadedBW = i;
163
- log('log', 'init preloadedBW: ', !!this.preloadedBW);
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
- if (!this.preloadedBW_FramelessWithButtons) {
170
- this._createPreloadBW({
171
- frame: false,
172
- // transparent: true,
173
- autoHideMenuBar: true,
174
- titleBarStyle: 'hidden',
175
- }).then(i => {
176
- this.preloadedBW_FramelessWithButtons = i;
177
- log('log', 'init preloadedBW_FramelessWithButtons: ', !!this.preloadedBW_FramelessWithButtons);
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
- if (!this.preloadedBW_FramelessNoButtons) {
184
- this._createPreloadBW({
185
- frame: false,
186
- // transparent: true,
187
- autoHideMenuBar: true,
188
- titleBarStyle: 'default',
189
- }).then(i => {
190
- this.preloadedBW_FramelessNoButtons = i;
191
- log('log', 'init preloadedBW_FramelessNoButtons: ', !!this.preloadedBW_FramelessNoButtons);
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
- if (!this.preloadedBV) {
198
- this._createPreloadBV().then(i => {
199
- this.preloadedBV = i;
200
- log('log', 'init preloadedBV: ', !!this.preloadedBV);
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
- const url = webContents.getURL();
633
- // 判断是否本地/内网IP
634
- const isLocalhost = (hostname) => {
635
- return (hostname === 'localhost' ||
636
- hostname === '127.0.0.1' ||
637
- hostname === '::1' ||
638
- /^192\.168\./.test(hostname) ||
639
- /^10\./.test(hostname) ||
640
- /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname));
641
- };
642
- if (this.webviewDomainWhiteList && this.webviewDomainWhiteList.length > 0) {
643
- try {
644
- const { hostname } = new URL(url);
645
- // 优化白名单判断,支持 .example.com 形式的子域名通配和本地/内网IP
646
- const isWhiteListed = this.webviewDomainWhiteList.some(domain => {
647
- if (domain === 'localhost' || domain === '127.0.0.1' || domain === '::1') {
648
- return isLocalhost(hostname);
649
- }
650
- if (domain.startsWith('.')) {
651
- // .example.com 允许所有 *.example.com
652
- return hostname === domain.slice(1) || hostname.endsWith(domain);
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
- else {
663
- log('log', 'webview 域名未在白名单,未启用 remote', url);
688
+ catch {
689
+ log('log', 'webview url 解析失败,未启用 remote', url);
664
690
  }
665
691
  }
666
- catch {
667
- log('log', 'webview url 解析失败,未启用 remote', url);
692
+ else {
693
+ enable(webContents); // 没有配置白名单则全部允许
668
694
  }
669
695
  }
670
- else {
671
- enable(webContents); // 没有配置白名单则全部允许
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
- this.windows.delete(window.id || window._id);
685
- });
709
+ // window.webContents.on('close', () => {
710
+ // this.windows.delete(window.id || window._id)
711
+ // })
686
712
  window.webContents.on('destroyed', () => {
687
- this.windows.delete(window.id || window._id);
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
- this.windows.delete(window.id || window._id);
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
- this.windows.set(window.id || window._id || window.webContents.id, window);
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
- this.windows.forEach((i, key) => {
907
- try {
908
- if (!(i && i?.webContents?.isDestroyed && !i?.webContents?.isDestroyed?.())) {
909
- this.windows.delete(key);
910
- }
911
- }
912
- catch (error) {
913
- log('error', 'get');
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
- if (typeof idOrName === 'number') {
916
- if (i?.id === idOrName || i?._id === idOrName) {
917
- win = i;
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
- else if (typeof idOrName === 'string') {
921
- if (i?._name === idOrName) {
922
- win = i;
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
- // @ts-ignore
927
- if (win && win?.webContents?.isDestroyed && !win?.webContents?.isDestroyed?.()) {
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
- const bwWindows = new Map();
935
- const bvWindows = new Map();
1026
+ // 先清理已销毁的窗口
1027
+ const toDelete = [];
936
1028
  this.windows.forEach((win, key) => {
937
- if (!(win && win?.webContents?.isDestroyed && !win?.webContents?.isDestroyed?.())) {
938
- this.windows.delete(key);
939
- }
940
- if (win?._type === 'BW') {
941
- bwWindows.set(key, win);
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
- if (type === 'BW') {
948
- return bwWindows;
1036
+ toDelete.forEach(key => this.windows.delete(key));
1037
+ if (!type) {
1038
+ return this.windows;
949
1039
  }
950
- if (type === 'BV') {
951
- return bvWindows;
952
- }
953
- return this.windows;
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
- let win = undefined;
958
- this.windows.forEach((i) => {
959
- if (typeof idOrName === 'number') {
960
- if (i?.id === idOrName || i?._id === idOrName) {
961
- win = i;
962
- }
963
- }
964
- else if (typeof idOrName === 'string') {
965
- if (i?._name === idOrName) {
966
- win = i;
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
- _win.removeBrowserView(win);
1066
+ try {
1067
+ _win.removeBrowserView(win);
1068
+ }
1069
+ catch (error) {
1070
+ // 忽略错误,可能已经移除
1071
+ }
980
1072
  }
981
1073
  });
982
1074
  }
983
1075
  // @ts-ignore
984
- win?.webContents?.destroy?.();
1076
+ win.webContents?.destroy?.();
985
1077
  // @ts-ignore
986
- win?.close?.();
1078
+ win.close?.();
987
1079
  // @ts-ignore
988
- win?.destroy?.();
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.forEach((win) => {
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
- win && this.windows.delete(win?.id);
997
- // @ts-ignore
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?.webContents?.destroy?.();
1114
+ win.webContents?.destroy?.();
1008
1115
  // @ts-ignore
1009
- win?.close?.();
1116
+ win.close?.();
1010
1117
  // @ts-ignore
1011
- win?.destroy?.();
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
- let win = undefined;
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
- let nameExists = false;
1037
- this.windows.forEach(i => {
1038
- if (i !== win && i?._name === newName) {
1039
- nameExists = true;
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
- let win = undefined;
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
- // 按zIndex层级排序,数值小的在前
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
- // @ts-ignore
1273
- if (views[i].webContents.id !== sortedViews[i].webContents.id) {
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
- // @ts-ignore
1292
- window.removeBrowserView(view, false);
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
- if (index === 0) {
1298
- // 第一个设置为当前视图
1299
- // @ts-ignore
1300
- window.setBrowserView(view, false);
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
- else {
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);