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