@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.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.createQueue = [];
82
- this.isCreating = false;
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
- if (!this.preloadedBW) {
148
- this._createPreloadBW({}).then(i => {
149
- this.preloadedBW = i;
150
- log('log', 'init preloadedBW: ', !!this.preloadedBW);
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
- if (!this.preloadedBW_FramelessWithButtons) {
157
- this._createPreloadBW({
158
- frame: false,
159
- // transparent: true,
160
- autoHideMenuBar: true,
161
- titleBarStyle: 'hidden',
162
- }).then(i => {
163
- this.preloadedBW_FramelessWithButtons = i;
164
- log('log', 'init preloadedBW_FramelessWithButtons: ', !!this.preloadedBW_FramelessWithButtons);
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
- if (!this.preloadedBW_FramelessNoButtons) {
171
- this._createPreloadBW({
172
- frame: false,
173
- // transparent: true,
174
- autoHideMenuBar: true,
175
- titleBarStyle: 'default',
176
- }).then(i => {
177
- this.preloadedBW_FramelessNoButtons = i;
178
- log('log', 'init preloadedBW_FramelessNoButtons: ', !!this.preloadedBW_FramelessNoButtons);
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
- if (!this.preloadedBV) {
185
- this._createPreloadBV().then(i => {
186
- this.preloadedBV = i;
187
- log('log', 'init preloadedBV: ', !!this.preloadedBV);
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
- const url = webContents.getURL();
620
- // 判断是否本地/内网IP
621
- const isLocalhost = (hostname) => {
622
- return (hostname === 'localhost' ||
623
- hostname === '127.0.0.1' ||
624
- hostname === '::1' ||
625
- /^192\.168\./.test(hostname) ||
626
- /^10\./.test(hostname) ||
627
- /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname));
628
- };
629
- if (this.webviewDomainWhiteList && this.webviewDomainWhiteList.length > 0) {
630
- try {
631
- const { hostname } = new URL(url);
632
- // 优化白名单判断,支持 .example.com 形式的子域名通配和本地/内网IP
633
- const isWhiteListed = this.webviewDomainWhiteList.some(domain => {
634
- if (domain === 'localhost' || domain === '127.0.0.1' || domain === '::1') {
635
- return isLocalhost(hostname);
636
- }
637
- if (domain.startsWith('.')) {
638
- // .example.com 允许所有 *.example.com
639
- return hostname === domain.slice(1) || hostname.endsWith(domain);
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
- else {
650
- log('log', 'webview 域名未在白名单,未启用 remote', url);
675
+ catch {
676
+ log('log', 'webview url 解析失败,未启用 remote', url);
651
677
  }
652
678
  }
653
- catch {
654
- log('log', 'webview url 解析失败,未启用 remote', url);
679
+ else {
680
+ enable(webContents); // 没有配置白名单则全部允许
655
681
  }
656
682
  }
657
- else {
658
- enable(webContents); // 没有配置白名单则全部允许
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
- this.windows.delete(window.id || window._id);
672
- });
696
+ // window.webContents.on('close', () => {
697
+ // this.windows.delete(window.id || window._id)
698
+ // })
673
699
  window.webContents.on('destroyed', () => {
674
- this.windows.delete(window.id || window._id);
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
- this.windows.delete(window.id || window._id);
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
- this.windows.set(window.id || window._id || window.webContents.id, window);
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
- this.windows.forEach((i, key) => {
894
- try {
895
- if (!(i && i?.webContents?.isDestroyed && !i?.webContents?.isDestroyed?.())) {
896
- this.windows.delete(key);
897
- }
898
- }
899
- catch (error) {
900
- log('error', 'get');
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
- if (typeof idOrName === 'number') {
903
- if (i?.id === idOrName || i?._id === idOrName) {
904
- win = i;
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
- else if (typeof idOrName === 'string') {
908
- if (i?._name === idOrName) {
909
- win = i;
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
- // @ts-ignore
914
- if (win && win?.webContents?.isDestroyed && !win?.webContents?.isDestroyed?.()) {
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
- const bwWindows = new Map();
922
- const bvWindows = new Map();
1013
+ // 先清理已销毁的窗口
1014
+ const toDelete = [];
923
1015
  this.windows.forEach((win, key) => {
924
- if (!(win && win?.webContents?.isDestroyed && !win?.webContents?.isDestroyed?.())) {
925
- this.windows.delete(key);
926
- }
927
- if (win?._type === 'BW') {
928
- bwWindows.set(key, win);
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
- if (type === 'BW') {
935
- return bwWindows;
1023
+ toDelete.forEach(key => this.windows.delete(key));
1024
+ if (!type) {
1025
+ return this.windows;
936
1026
  }
937
- if (type === 'BV') {
938
- return bvWindows;
939
- }
940
- return this.windows;
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
- let win = undefined;
945
- this.windows.forEach((i) => {
946
- if (typeof idOrName === 'number') {
947
- if (i?.id === idOrName || i?._id === idOrName) {
948
- win = i;
949
- }
950
- }
951
- else if (typeof idOrName === 'string') {
952
- if (i?._name === idOrName) {
953
- win = i;
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
- _win.removeBrowserView(win);
1053
+ try {
1054
+ _win.removeBrowserView(win);
1055
+ }
1056
+ catch (error) {
1057
+ // 忽略错误,可能已经移除
1058
+ }
967
1059
  }
968
1060
  });
969
1061
  }
970
1062
  // @ts-ignore
971
- win?.webContents?.destroy?.();
1063
+ win.webContents?.destroy?.();
972
1064
  // @ts-ignore
973
- win?.close?.();
1065
+ win.close?.();
974
1066
  // @ts-ignore
975
- win?.destroy?.();
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.forEach((win) => {
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
- win && this.windows.delete(win?.id);
984
- // @ts-ignore
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?.webContents?.destroy?.();
1101
+ win.webContents?.destroy?.();
995
1102
  // @ts-ignore
996
- win?.close?.();
1103
+ win.close?.();
997
1104
  // @ts-ignore
998
- win?.destroy?.();
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
- let win = undefined;
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
- let nameExists = false;
1024
- this.windows.forEach(i => {
1025
- if (i !== win && i?._name === newName) {
1026
- nameExists = true;
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
- let win = undefined;
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
- // 按zIndex层级排序,数值小的在前
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
- // @ts-ignore
1260
- if (views[i].webContents.id !== sortedViews[i].webContents.id) {
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
- // @ts-ignore
1279
- window.removeBrowserView(view, false);
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
- if (index === 0) {
1285
- // 第一个设置为当前视图
1286
- // @ts-ignore
1287
- window.setBrowserView(view, false);
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
- else {
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);