@lynker-desktop/electron-window-manager 0.0.9-alpha.6 → 0.0.9-alpha.61

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,7 +3,18 @@ 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
 
8
+ const getQueue = (() => {
9
+ let queue;
10
+ return async () => {
11
+ if (!queue) {
12
+ // const { default: PQueue } = await import('p-queue')
13
+ queue = new PQueue({ concurrency: 1 });
14
+ }
15
+ return queue;
16
+ };
17
+ })();
7
18
  const getCustomSession = (() => {
8
19
  let customSession;
9
20
  return () => {
@@ -23,21 +34,30 @@ const enable = (win) => {
23
34
  remote.enable(win);
24
35
  };
25
36
  const initWebContentsVal = (win, preload) => {
26
- win.webContents.executeJavaScript(`
27
37
  try {
28
- const data = {
29
- __ELECTRON_WINDOW_MANAGER_WEB_CONTENTS_ID__: ${JSON.stringify(win.webContents.id)},
30
- __ELECTRON_WINDOW_MANAGER_TYPE__: ${JSON.stringify(win._type)},
31
- __ELECTRON_WINDOW_MANAGER_NAME__: ${JSON.stringify(win._name)},
32
- __ELECTRON_WINDOW_MANAGER_EXTRA_DATA__: ${JSON.stringify(win._extraData || '')},
33
- __ELECTRON_WINDOW_MANAGER_PRELOAD__: ${JSON.stringify(preload)},
34
- __ELECTRON_WINDOW_MANAGER_INIT_URL__: ${JSON.stringify(win._initUrl || '')}
35
- };
36
- Object.entries(data).forEach(([key, value]) => {
37
- window[key] = value;
38
- });
39
- } catch (error) {}
40
- `);
38
+ if (!win?.webContents || win.webContents.isDestroyed()) {
39
+ return;
40
+ }
41
+ const webContentsId = win.webContents.id;
42
+ win.webContents.executeJavaScript(`
43
+ try {
44
+ const data = {
45
+ __ELECTRON_WINDOW_MANAGER_WEB_CONTENTS_ID__: ${JSON.stringify(webContentsId)},
46
+ __ELECTRON_WINDOW_MANAGER_TYPE__: ${JSON.stringify(win._type)},
47
+ __ELECTRON_WINDOW_MANAGER_NAME__: ${JSON.stringify(win._name)},
48
+ __ELECTRON_WINDOW_MANAGER_EXTRA_DATA__: ${JSON.stringify(win._extraData || '')},
49
+ __ELECTRON_WINDOW_MANAGER_PRELOAD__: ${JSON.stringify(preload)},
50
+ __ELECTRON_WINDOW_MANAGER_INIT_URL__: ${JSON.stringify(win._initUrl || '')}
51
+ };
52
+ Object.entries(data).forEach(([key, value]) => {
53
+ window[key] = value;
54
+ });
55
+ } catch (error) {}
56
+ `);
57
+ }
58
+ catch (error) {
59
+ // 忽略错误,webContents 可能已销毁
60
+ }
41
61
  };
42
62
  class WindowsManager {
43
63
  /**
@@ -56,7 +76,9 @@ class WindowsManager {
56
76
  * - 不带点的(如 example.com)只匹配主域名。
57
77
  * - 'localhost'、'127.0.0.1'、'::1' 以及局域网 IP(如 192.168.x.x、10.x.x.x、172.16.x.x~172.31.x.x)都视为本地白名单。
58
78
  */
59
- constructor(preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl, webviewDomainWhiteList) {
79
+ constructor(preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList) {
80
+ // 按名称索引的 Map,用于加速查找
81
+ this.windowsByName = new Map();
60
82
  // 预加载的窗口
61
83
  this.preloadedBW = null;
62
84
  // 预加载的窗口(无边框,有按钮)
@@ -67,33 +89,50 @@ class WindowsManager {
67
89
  this.preloadedBV = null;
68
90
  this.preloading = false;
69
91
  this.webviewDomainWhiteList = [];
70
- // 创建队列相关属性
71
- this.createQueue = [];
72
- this.isCreating = false;
92
+ // 窗口销毁检查的防抖函数,避免频繁检查
93
+ this.cleanupDestroyedWindowsDebounced = lodash.debounce(() => {
94
+ this._cleanupDestroyedWindows();
95
+ }, 500);
96
+ /**
97
+ * 防抖的排序方法
98
+ * @param window 目标窗口
99
+ */
100
+ this.sortBrowserViewsDebounced = lodash.debounce((window, view) => {
101
+ this._sortBrowserViews(window, view);
102
+ }, 50);
73
103
  this.preload = preload;
74
104
  this.windows = new Map();
75
105
  this.loadingViewUrl = `${loadingViewUrl ?? ''}`;
76
106
  this.errorViewUrl = `${errorViewUrl ?? ''}`;
77
- this.preloadWebContentsUrl = `${preloadWebContentsUrl ?? ''}`;
107
+ this.preloadWebContentsConfig = preloadWebContentsConfig;
78
108
  this.webviewDomainWhiteList = webviewDomainWhiteList || [];
79
- log('log', 'preloadWebContentsUrl: ', this.preloadWebContentsUrl);
80
- if (this.preloadWebContentsUrl) {
81
- app.on('ready', () => {
82
- if (this.preloadWebContentsUrl) {
83
- this.setPreloadWebContentsUrl(this.preloadWebContentsUrl);
109
+ log('log', 'preloadWebContentsConfig: ', this.preloadWebContentsConfig);
110
+ if (this.preloadWebContentsConfig) {
111
+ if (this.preloadWebContentsConfig.nodeIntegration === undefined) {
112
+ this.preloadWebContentsConfig.nodeIntegration = true;
113
+ }
114
+ if (this.preloadWebContentsConfig.contextIsolation === undefined) {
115
+ this.preloadWebContentsConfig.contextIsolation = false;
116
+ }
117
+ getQueue();
118
+ app.whenReady().then(() => {
119
+ if (this.preloadWebContentsConfig) {
120
+ this.setPreloadWebContentsConfig(this.preloadWebContentsConfig);
84
121
  }
85
122
  });
86
123
  }
87
124
  }
88
125
  /**
89
- * 设置预加载的webContents的url
90
- * @param preloadWebContentsUrl 预加载的webContents的url
126
+ * 设置预加载的webContents配置
127
+ * @param preloadWebContentsConfig 预加载的webContents配置
91
128
  */
92
- setPreloadWebContentsUrl(preloadWebContentsUrl) {
129
+ setPreloadWebContentsConfig(preloadWebContentsConfig) {
93
130
  try {
94
- this.preloadWebContentsUrl = preloadWebContentsUrl;
95
- if (this.preloadWebContentsUrl) {
96
- this._preloadInstances();
131
+ this.preloadWebContentsConfig = preloadWebContentsConfig;
132
+ if (this.preloadWebContentsConfig) {
133
+ getQueue().then(q => q.add(async () => {
134
+ return await this._preloadInstances();
135
+ }));
97
136
  }
98
137
  else {
99
138
  this.preloadedBW = null;
@@ -103,9 +142,23 @@ class WindowsManager {
103
142
  }
104
143
  }
105
144
  catch (error) {
106
- log('error', 'setPreloadWebContentsUrl error:', error);
145
+ log('error', 'setPreloadWebContentsConfig error:', error);
107
146
  }
108
147
  }
148
+ /**
149
+ * Promise 超时包装函数
150
+ * @param promise 要包装的 Promise
151
+ * @param timeout 超时时间(毫秒)
152
+ * @param errorMessage 超时错误信息
153
+ */
154
+ async _withTimeout(promise, timeout, errorMessage) {
155
+ const timeoutPromise = new Promise((_, reject) => {
156
+ setTimeout(() => {
157
+ reject(new Error(errorMessage));
158
+ }, timeout);
159
+ });
160
+ return Promise.race([promise, timeoutPromise]);
161
+ }
109
162
  /**
110
163
  * 预加载实例
111
164
  */
@@ -114,23 +167,64 @@ class WindowsManager {
114
167
  return;
115
168
  this.preloading = true;
116
169
  try {
117
- if (this.preloadWebContentsUrl) {
118
- // 预加载的窗口
119
- this.preloadedBW = this.preloadedBW || await this._createPreloadBW({});
120
- // 预加载的窗口(无边框,有按钮)
121
- this.preloadedBW_FramelessWithButtons = this.preloadedBW_FramelessWithButtons || await this._createPreloadBW({
122
- frame: false,
123
- transparent: true,
124
- titleBarStyle: 'hidden',
125
- });
126
- // 预加载的窗口(无边框,无按钮)
127
- this.preloadedBW_FramelessNoButtons = this.preloadedBW_FramelessNoButtons || await this._createPreloadBW({
128
- frame: false,
129
- transparent: true,
130
- titleBarStyle: 'customButtonsOnHover',
131
- });
132
- // 预加载的BV
133
- this.preloadedBV = this.preloadedBV || await this._createPreloadBV();
170
+ if (this.preloadWebContentsConfig) {
171
+ log('log', 'preloadWebContentsConfig: ', this.preloadWebContentsConfig);
172
+ const preloadPromises = [];
173
+ // 根据配置决定是否预加载普通窗口
174
+ if (this.preloadWebContentsConfig.enableBW !== false && !this.preloadedBW) {
175
+ preloadPromises.push(this._createPreloadBW({}).then(i => {
176
+ this.preloadedBW = i;
177
+ log('log', 'init preloadedBW: ', !!this.preloadedBW);
178
+ }).catch(error => {
179
+ log('error', '预加载 BW 失败:', error);
180
+ }));
181
+ }
182
+ // 根据配置决定是否预加载无边框有按钮的窗口
183
+ if (this.preloadWebContentsConfig.enableBW_FramelessWithButtons !== false && !this.preloadedBW_FramelessWithButtons) {
184
+ preloadPromises.push(this._createPreloadBW({
185
+ frame: false,
186
+ autoHideMenuBar: true,
187
+ titleBarStyle: 'hidden',
188
+ }).then(i => {
189
+ this.preloadedBW_FramelessWithButtons = i;
190
+ log('log', 'init preloadedBW_FramelessWithButtons: ', !!this.preloadedBW_FramelessWithButtons);
191
+ }).catch(error => {
192
+ log('error', '预加载 BW_FramelessWithButtons 失败:', error);
193
+ }));
194
+ }
195
+ // 根据配置决定是否预加载无边框无按钮的窗口
196
+ if (this.preloadWebContentsConfig.enableBW_FramelessNoButtons !== false && !this.preloadedBW_FramelessNoButtons) {
197
+ preloadPromises.push(this._createPreloadBW({
198
+ frame: false,
199
+ autoHideMenuBar: true,
200
+ titleBarStyle: 'default',
201
+ }).then(i => {
202
+ this.preloadedBW_FramelessNoButtons = i;
203
+ log('log', 'init preloadedBW_FramelessNoButtons: ', !!this.preloadedBW_FramelessNoButtons);
204
+ }).catch(error => {
205
+ log('error', '预加载 BW_FramelessNoButtons 失败:', error);
206
+ }));
207
+ }
208
+ // 根据配置决定是否预加载浏览器视图
209
+ if (this.preloadWebContentsConfig.enableBV !== false && !this.preloadedBV) {
210
+ preloadPromises.push(this._createPreloadBV().then(i => {
211
+ this.preloadedBV = i;
212
+ log('log', 'init preloadedBV: ', !!this.preloadedBV);
213
+ }).catch(error => {
214
+ log('error', '预加载 BV 失败:', error);
215
+ }));
216
+ }
217
+ if (preloadPromises.length > 0) {
218
+ // 添加超时机制,默认 10 秒超时
219
+ const timeout = 10000; // 10 秒
220
+ try {
221
+ await this._withTimeout(Promise.allSettled(preloadPromises), timeout, `预加载超时(${timeout}ms)`);
222
+ }
223
+ catch (error) {
224
+ log('error', '预加载超时:', error);
225
+ // 超时后继续执行,不阻塞后续流程
226
+ }
227
+ }
134
228
  }
135
229
  }
136
230
  catch (e) {
@@ -146,20 +240,25 @@ class WindowsManager {
146
240
  _createPreloadBW(options = {}) {
147
241
  return new Promise((resolve) => {
148
242
  const preload = this.preload;
149
- const url = this.preloadWebContentsUrl;
150
- if (this.preloadWebContentsUrl) {
243
+ const url = this.preloadWebContentsConfig?.url;
244
+ if (this.preloadWebContentsConfig?.url) {
245
+ const webPreferences = (options.webPreferences || {});
151
246
  const instance = new BrowserWindow({
152
- ...options,
247
+ useContentSize: true,
153
248
  show: false,
249
+ backgroundColor: '#ffffff',
250
+ ...options,
154
251
  webPreferences: {
155
- ...(options.webPreferences || {}),
252
+ ...webPreferences,
253
+ sandbox: false,
156
254
  webviewTag: true,
157
255
  plugins: true,
158
- nodeIntegration: true,
159
- contextIsolation: false,
256
+ nodeIntegration: this.preloadWebContentsConfig?.nodeIntegration ?? true,
257
+ contextIsolation: this.preloadWebContentsConfig?.contextIsolation ?? false,
160
258
  backgroundThrottling: false,
161
259
  webSecurity: false,
162
- preload: preload,
260
+ preload: webPreferences.preload || preload,
261
+ defaultEncoding: 'utf-8',
163
262
  }
164
263
  });
165
264
  try {
@@ -169,43 +268,55 @@ class WindowsManager {
169
268
  log('error', '预加载 BW 设置 remote 失败', error);
170
269
  }
171
270
  try {
172
- // @ts-ignore
173
- instance._id = instance.webContents.id;
271
+ const webContentsId = instance.webContents?.id;
272
+ if (webContentsId !== undefined) {
273
+ // @ts-ignore
274
+ instance._id = webContentsId;
275
+ }
174
276
  }
175
277
  catch (error) {
176
278
  log('error', '预加载 BW 设置 _id 失败', error);
177
279
  }
178
280
  // @ts-ignore
179
- log('log', '创建预BW: ', instance._id, this.preloadWebContentsUrl);
281
+ const webContentsId = instance.webContents?.id;
282
+ log('log', '创建预BW: ', webContentsId, this.preloadWebContentsConfig?.url);
180
283
  // instance.webContents.once('did-finish-load', () => {
181
284
  // resolve(instance as BWItem);
182
285
  // });
183
286
  // instance.webContents.once('did-fail-load', () => {
184
287
  // resolve(instance as BWItem);
185
288
  // });
289
+ // @ts-ignore
186
290
  instance.loadURL(url ? `${url}` : 'about:blank');
187
291
  resolve(instance);
188
292
  }
293
+ else {
294
+ resolve(null);
295
+ }
189
296
  });
190
297
  }
191
298
  /**
192
299
  * 创建预加载的浏览器视图
193
300
  * @returns 预加载的浏览器视图
194
301
  */
195
- _createPreloadBV() {
302
+ _createPreloadBV(options = {}) {
196
303
  return new Promise((resolve) => {
197
304
  const preload = this.preload;
198
- const url = this.preloadWebContentsUrl;
199
- if (this.preloadWebContentsUrl) {
305
+ const url = this.preloadWebContentsConfig?.url;
306
+ if (this.preloadWebContentsConfig?.url) {
307
+ const webPreferences = (options.webPreferences || {});
200
308
  const instance = new BrowserView({
201
309
  webPreferences: {
310
+ ...webPreferences,
311
+ sandbox: false,
202
312
  webviewTag: true,
203
313
  plugins: true,
204
- nodeIntegration: true,
205
- contextIsolation: false,
314
+ nodeIntegration: this.preloadWebContentsConfig?.nodeIntegration ?? true,
315
+ contextIsolation: this.preloadWebContentsConfig?.contextIsolation ?? false,
206
316
  // backgroundThrottling: false,
207
317
  webSecurity: false,
208
- preload: preload,
318
+ preload: webPreferences.preload || preload,
319
+ defaultEncoding: 'utf-8',
209
320
  }
210
321
  });
211
322
  try {
@@ -215,96 +326,117 @@ class WindowsManager {
215
326
  log('error', '预加载 BV 设置 remote 失败', error);
216
327
  }
217
328
  try {
218
- // @ts-ignore
219
- instance._id = instance.webContents.id;
329
+ const webContentsId = instance.webContents?.id;
330
+ if (webContentsId !== undefined) {
331
+ // @ts-ignore
332
+ instance._id = webContentsId;
333
+ }
334
+ // 设置默认zIndex层级
335
+ instance._zIndex = 0;
220
336
  }
221
337
  catch (error) {
222
338
  log('error', '预加载 BV 设置 _id 失败', error);
223
339
  }
224
340
  // @ts-ignore
225
- log('log', '创建预BV: ', instance._id, this.preloadWebContentsUrl);
341
+ const webContentsId = instance.webContents?.id;
342
+ log('log', '创建预BV: ', webContentsId, this.preloadWebContentsConfig?.url);
226
343
  // instance.webContents.once('did-finish-load', () => {
227
344
  // resolve(instance as BVItem);
228
345
  // });
229
346
  // instance.webContents.once('did-fail-load', () => {
230
347
  // resolve(instance as BVItem);
231
348
  // });
349
+ // @ts-ignore
232
350
  instance.webContents.loadURL(url || 'about:blank');
233
351
  resolve(instance);
234
352
  }
235
- });
236
- }
237
- create(options) {
238
- return new Promise((resolve, reject) => {
239
- // 将创建请求添加到队列
240
- this.createQueue.push({ options, resolve, reject });
241
- // 如果当前没有在创建,则开始处理队列
242
- if (!this.isCreating) {
243
- this.processCreateQueue();
353
+ else {
354
+ resolve(null);
244
355
  }
245
356
  });
246
357
  }
247
- /**
248
- * 处理创建队列
249
- */
250
- async processCreateQueue() {
251
- if (this.isCreating || this.createQueue.length === 0) {
252
- return;
253
- }
254
- this.isCreating = true;
255
- while (this.createQueue.length > 0) {
256
- const { options, resolve, reject } = this.createQueue.shift();
257
- try {
258
- const window = await this._createWindow(options);
259
- resolve(window);
260
- }
261
- catch (error) {
262
- log('error', 'create window failed:', error);
263
- reject(error);
264
- }
265
- }
266
- this.isCreating = false;
358
+ async create(options) {
359
+ const queue = await getQueue();
360
+ const win = await queue.add(async () => {
361
+ const window = await this._createWindow(options);
362
+ return window;
363
+ });
364
+ return win;
267
365
  }
268
366
  /**
269
367
  * 实际的窗口创建逻辑
270
368
  */
271
369
  async _createWindow(options) {
272
370
  let window;
273
- const { usePreload = true, type = 'BW', name = 'anonymous', url, loadingView = { url: undefined }, errorView = { url: undefined }, browserWindow: browserWindowOptions, openDevTools = false, preventOriginClose = false, } = options;
371
+ const { usePreload = true, type = 'BW', name = 'anonymous', url, loadingView = { url: undefined }, errorView = { url: undefined }, browserWindow: browserWindowOptions, openDevTools = false, preventOriginClose = false, zIndex = 0, } = options;
372
+ const existingWinId = this.windowsByName.get(options.name);
373
+ if (existingWinId) {
374
+ window = this.windows.get(existingWinId);
375
+ if (window) {
376
+ return window;
377
+ }
378
+ // 清理无效的引用
379
+ this.windowsByName.delete(options.name);
380
+ }
274
381
  options.type = type;
275
382
  // 优先复用预创建实例
276
383
  let preloadWin = null;
277
- if (type === 'BW' && usePreload && this.preloadWebContentsUrl) {
384
+ if (type === 'BW' && usePreload && this.preloadWebContentsConfig?.url) {
278
385
  const bwOptions = browserWindowOptions || {};
279
386
  if (bwOptions.frame === false) {
280
- if (bwOptions.titleBarStyle === 'customButtonsOnHover') {
281
- preloadWin = this.preloadedBW_FramelessNoButtons;
282
- this.preloadedBW_FramelessNoButtons = null;
283
- setTimeout(async () => this.preloadedBW_FramelessNoButtons = await this._createPreloadBW({ frame: false, transparent: true, titleBarStyle: 'customButtonsOnHover' }), 0);
387
+ if (bwOptions.titleBarStyle === 'default' || !bwOptions.titleBarStyle) {
388
+ if (this.preloadWebContentsConfig.enableBW_FramelessNoButtons !== false && this.preloadedBW_FramelessNoButtons) {
389
+ preloadWin = this.preloadedBW_FramelessNoButtons;
390
+ this.preloadedBW_FramelessNoButtons = await this._createPreloadBW({
391
+ frame: false,
392
+ // transparent: true,
393
+ titleBarStyle: 'default',
394
+ webPreferences: {
395
+ preload: bwOptions?.webPreferences?.preload || this.preload,
396
+ }
397
+ });
398
+ }
284
399
  }
285
400
  else {
286
- preloadWin = this.preloadedBW_FramelessWithButtons;
287
- this.preloadedBW_FramelessWithButtons = null;
288
- setTimeout(async () => this.preloadedBW_FramelessWithButtons = await this._createPreloadBW({ frame: false, transparent: true, titleBarStyle: 'hidden' }), 0);
401
+ if (this.preloadWebContentsConfig.enableBW_FramelessWithButtons !== false && this.preloadedBW_FramelessWithButtons) {
402
+ preloadWin = this.preloadedBW_FramelessWithButtons;
403
+ this.preloadedBW_FramelessWithButtons = await this._createPreloadBW({
404
+ frame: false,
405
+ // transparent: true,
406
+ titleBarStyle: 'hidden',
407
+ webPreferences: {
408
+ preload: this.preload,
409
+ }
410
+ });
411
+ }
289
412
  }
290
413
  }
291
414
  else {
292
- preloadWin = this.preloadedBW;
293
- this.preloadedBW = null;
294
- setTimeout(async () => this.preloadedBW = await this._createPreloadBW({}), 0);
415
+ if (this.preloadWebContentsConfig.enableBW !== false && this.preloadedBW) {
416
+ preloadWin = this.preloadedBW;
417
+ this.preloadedBW = await this._createPreloadBW({
418
+ webPreferences: {
419
+ preload: bwOptions?.webPreferences?.preload || this.preload,
420
+ }
421
+ });
422
+ }
295
423
  }
296
424
  }
297
- if (type === 'BV' && usePreload && this.preloadWebContentsUrl) {
298
- if (this.preloadedBV) {
425
+ if (type === 'BV' && usePreload && this.preloadWebContentsConfig?.url) {
426
+ const bvOptions = browserWindowOptions || {};
427
+ if (this.preloadWebContentsConfig.enableBV !== false && this.preloadedBV) {
299
428
  preloadWin = this.preloadedBV;
300
- this.preloadedBV = null;
301
- setTimeout(async () => this.preloadedBV = await this._createPreloadBV(), 0);
429
+ this.preloadedBV = await this._createPreloadBV({
430
+ webPreferences: {
431
+ preload: bvOptions?.webPreferences?.preload || this.preload,
432
+ }
433
+ });
302
434
  }
303
435
  }
304
436
  if (preloadWin) {
305
437
  const win = preloadWin;
306
- log('log', `${name} 使用预加载窗口(${type})`, win._id);
307
- win._type = 'BW';
438
+ log('log', `${name} 使用预加载窗口(${type})`, this._getWebContentsId(win));
439
+ win._type = type;
308
440
  win._name = options.name || 'anonymous';
309
441
  win._extraData = `${options?.extraData || ''}`;
310
442
  win._initUrl = `${options?.url || ''}`;
@@ -318,27 +450,101 @@ class WindowsManager {
318
450
  if (type === 'BV') {
319
451
  this._applyBrowserViewOptions(win, options);
320
452
  }
453
+ if (typeof this.preloadWebContentsConfig?.customLoadURL === 'function') {
454
+ try {
455
+ if (type === 'BW') {
456
+ // @ts-ignore
457
+ const originLoadURL = win.loadURL;
458
+ // @ts-ignore
459
+ win.loadURL = async (url, useNativeLoadURL = false) => {
460
+ return new Promise(async (resolve, reject) => {
461
+ if (useNativeLoadURL) {
462
+ log('log', 'useNativeLoadURL win.loadURL');
463
+ try {
464
+ await originLoadURL.call(win, url);
465
+ resolve(undefined);
466
+ }
467
+ catch (error) {
468
+ reject(error);
469
+ }
470
+ return;
471
+ }
472
+ try {
473
+ log('log', 'customLoadURL win.loadURL');
474
+ // @ts-ignore
475
+ await this.preloadWebContentsConfig.customLoadURL(url || 'about:blank', (url) => originLoadURL.call(win, url), win.webContents);
476
+ try {
477
+ win.emit('ready-to-show');
478
+ }
479
+ catch (error) {
480
+ log('error', 'emit ready-to-show event failed:', error);
481
+ }
482
+ resolve(undefined);
483
+ }
484
+ catch (error) {
485
+ reject(error);
486
+ }
487
+ });
488
+ };
489
+ }
490
+ const originWebContentsLoadURL = win.webContents.loadURL;
491
+ // @ts-ignore
492
+ win.webContents.loadURL = async (url, useNativeLoadURL = false) => {
493
+ return new Promise(async (resolve, reject) => {
494
+ if (useNativeLoadURL) {
495
+ log('log', 'useNativeLoadURL win.webContents.loadURL');
496
+ try {
497
+ await originWebContentsLoadURL.call(win.webContents, url);
498
+ resolve(undefined);
499
+ }
500
+ catch (error) {
501
+ reject(error);
502
+ }
503
+ return;
504
+ }
505
+ try {
506
+ log('log', 'customLoadURL win.webContents.loadURL');
507
+ // @ts-ignore
508
+ await this.preloadWebContentsConfig.customLoadURL(url || 'about:blank', (url) => originWebContentsLoadURL.call(win.webContents, url), win.webContents);
509
+ try {
510
+ win.webContents.emit('ready-to-show');
511
+ }
512
+ catch (error) {
513
+ log('error', 'emit ready-to-show event failed:', error);
514
+ }
515
+ resolve(undefined);
516
+ }
517
+ catch (error) {
518
+ reject(error);
519
+ }
520
+ });
521
+ };
522
+ }
523
+ catch (error) {
524
+ log('error', 'customLoadURL error', error);
525
+ }
526
+ }
321
527
  window = win;
322
528
  }
323
529
  try {
324
- try {
325
- loadingView.url = `${loadingView?.url ?? this.loadingViewUrl}`;
326
- lodash.merge(options, {
327
- loadingView,
328
- });
329
- }
330
- catch (error) {
331
- log('error', 'loadingView error:', loadingView, this.loadingViewUrl);
332
- }
333
- try {
334
- errorView.url = `${errorView?.url ?? this.errorViewUrl}`;
335
- lodash.merge(options, {
336
- errorView,
337
- });
338
- }
339
- catch (error) {
340
- log('error', 'errorView error:', errorView, this.errorViewUrl);
341
- }
530
+ loadingView.url = `${loadingView?.url ?? this.loadingViewUrl}`;
531
+ lodash.merge(options, {
532
+ loadingView,
533
+ });
534
+ }
535
+ catch (error) {
536
+ log('error', 'loadingView error:', loadingView, this.loadingViewUrl);
537
+ }
538
+ try {
539
+ errorView.url = `${errorView?.url ?? this.errorViewUrl}`;
540
+ lodash.merge(options, {
541
+ errorView,
542
+ });
543
+ }
544
+ catch (error) {
545
+ log('error', 'errorView error:', errorView, this.errorViewUrl);
546
+ }
547
+ try {
342
548
  let parentWin = undefined;
343
549
  if (typeof browserWindowOptions?.parent === 'number') {
344
550
  parentWin = BrowserWindow.fromId(browserWindowOptions?.parent) || undefined;
@@ -356,6 +562,7 @@ class WindowsManager {
356
562
  window = type === 'BV' ?
357
563
  new BrowserView(lodash.merge((browserWindowOptions || {}), {
358
564
  webPreferences: lodash.merge({
565
+ sandbox: false,
359
566
  webviewTag: true,
360
567
  // session: getCustomSession(),
361
568
  plugins: true,
@@ -366,6 +573,7 @@ class WindowsManager {
366
573
  nativeWindowOpen: true,
367
574
  webSecurity: false,
368
575
  preload: preload,
576
+ defaultEncoding: 'utf-8',
369
577
  }, browserWindowOptions?.webPreferences || {})
370
578
  }))
371
579
  : new BrowserWindow(lodash.merge({
@@ -373,6 +581,7 @@ class WindowsManager {
373
581
  }, (browserWindowOptions || {}), {
374
582
  parent: parentWin,
375
583
  webPreferences: lodash.merge({
584
+ sandbox: false,
376
585
  webviewTag: true,
377
586
  // session: getCustomSession(),
378
587
  plugins: true,
@@ -382,6 +591,7 @@ class WindowsManager {
382
591
  nativeWindowOpen: true,
383
592
  webSecurity: false,
384
593
  preload: preload,
594
+ defaultEncoding: 'utf-8',
385
595
  }, browserWindowOptions?.webPreferences || {})
386
596
  }));
387
597
  log('log', `${name} 不使用 ${type === 'BV' ? 'preloadedBV' : 'preloadedBW'}`, window?.webContents?.id);
@@ -392,16 +602,15 @@ class WindowsManager {
392
602
  log('error', 'enable: ', error);
393
603
  }
394
604
  }
605
+ // 停止加载
606
+ // window.webContents?.stop?.();
395
607
  // @ts-ignore
396
- try {
397
- window.id = Number(`${window.id || window.webContents.id}`);
398
- }
399
- catch (error) {
400
- // log('error', 'set id: ', error)
401
- }
402
608
  // @ts-ignore
403
609
  try {
404
- window._id = Number(`${window.id || window.webContents.id}`);
610
+ const webContentsId = this._getWebContentsId(window);
611
+ if (webContentsId !== undefined) {
612
+ window._id = Number(`${webContentsId}`);
613
+ }
405
614
  }
406
615
  catch (error) {
407
616
  // log('error', 'set id: ', error)
@@ -410,14 +619,16 @@ class WindowsManager {
410
619
  window._name = name;
411
620
  window._extraData = `${options?.extraData || ''}`;
412
621
  window._initUrl = `${options?.url || ''}`;
413
- log('log', 'create 5: ', window.id, window._id, window._name);
414
- if (loadingView?.url) {
622
+ // 设置zIndex层级
623
+ window._zIndex = options.zIndex ?? 0;
624
+ log('log', 'create 5: ', this._getWebContentsId(window), window._name);
625
+ if (loadingView?.url && loadingView?.url !== 'about:blank') {
415
626
  if (type === 'BW') {
416
627
  // @ts-ignore
417
628
  this._setLoadingView(window, options);
418
629
  }
419
630
  }
420
- if (errorView?.url) {
631
+ if (errorView?.url && errorView?.url !== 'about:blank') {
421
632
  if (type === 'BW') {
422
633
  const showErrorView = lodash.debounce(() => {
423
634
  const _url = window._initUrl;
@@ -469,46 +680,45 @@ class WindowsManager {
469
680
  }
470
681
  window.webContents.on('did-attach-webview', (_event, webContents) => {
471
682
  const tryEnable = () => {
472
- const url = webContents.getURL();
473
- // 判断是否本地/内网IP
474
- const isLocalhost = (hostname) => {
475
- return (hostname === 'localhost' ||
476
- hostname === '127.0.0.1' ||
477
- hostname === '::1' ||
478
- /^192\.168\./.test(hostname) ||
479
- /^10\./.test(hostname) ||
480
- /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname));
481
- };
482
- if (this.webviewDomainWhiteList && this.webviewDomainWhiteList.length > 0) {
483
- try {
484
- const { hostname } = new URL(url);
485
- // 优化白名单判断,支持 .example.com 形式的子域名通配和本地/内网IP
486
- const isWhiteListed = this.webviewDomainWhiteList.some(domain => {
487
- if (domain === 'localhost' || domain === '127.0.0.1' || domain === '::1') {
488
- return isLocalhost(hostname);
489
- }
490
- if (domain.startsWith('.')) {
491
- // .example.com 允许所有 *.example.com
492
- return hostname === domain.slice(1) || hostname.endsWith(domain);
683
+ try {
684
+ const url = webContents.getURL();
685
+ if (!url || url === 'about:blank') {
686
+ return;
687
+ }
688
+ if (this.webviewDomainWhiteList && this.webviewDomainWhiteList.length > 0) {
689
+ try {
690
+ const { hostname } = new URL(url);
691
+ // 优化白名单判断,支持 .example.com 形式的子域名通配和本地/内网IP
692
+ const isWhiteListed = this.webviewDomainWhiteList.some(domain => {
693
+ if (domain === 'localhost' || domain === '127.0.0.1' || domain === '::1') {
694
+ return this._isLocalhost(hostname);
695
+ }
696
+ if (domain.startsWith('.')) {
697
+ // .example.com 允许所有 *.example.com
698
+ return hostname === domain.slice(1) || hostname.endsWith(domain);
699
+ }
700
+ else {
701
+ // 精确匹配
702
+ return hostname === domain;
703
+ }
704
+ }) || this._isLocalhost(hostname); // 允许本地回环和内网地址
705
+ if (isWhiteListed) {
706
+ enable(webContents);
493
707
  }
494
708
  else {
495
- // 精确匹配
496
- return hostname === domain;
709
+ log('log', 'webview 域名未在白名单,未启用 remote', url);
497
710
  }
498
- }) || isLocalhost(hostname); // 允许本地回环和内网地址
499
- if (isWhiteListed) {
500
- enable(webContents);
501
711
  }
502
- else {
503
- log('log', 'webview 域名未在白名单,未启用 remote', url);
712
+ catch {
713
+ log('log', 'webview url 解析失败,未启用 remote', url);
504
714
  }
505
715
  }
506
- catch {
507
- log('log', 'webview url 解析失败,未启用 remote', url);
716
+ else {
717
+ enable(webContents); // 没有配置白名单则全部允许
508
718
  }
509
719
  }
510
- else {
511
- enable(webContents); // 没有配置白名单则全部允许
720
+ catch (error) {
721
+ log('error', 'tryEnable webview error:', error);
512
722
  }
513
723
  };
514
724
  // 只监听一次,防止多次触发
@@ -520,11 +730,16 @@ class WindowsManager {
520
730
  webContents.on('did-navigate', onDidNavigate);
521
731
  webContents.on('did-finish-load', onDidNavigate);
522
732
  });
523
- window.webContents.on('close', () => {
524
- this.windows.delete(window.id || window._id);
525
- });
733
+ // window.webContents.on('close', () => {
734
+ // this.windows.delete(window.id || window._id)
735
+ // })
526
736
  window.webContents.on('destroyed', () => {
527
- this.windows.delete(window.id || window._id);
737
+ const winId = this._getWebContentsId(window);
738
+ if (winId !== undefined) {
739
+ this.windows.delete(winId);
740
+ }
741
+ // 同步清理名称索引
742
+ this.windowsByName.delete(window._name);
528
743
  });
529
744
  window.webContents.on('dom-ready', () => {
530
745
  if (openDevTools) {
@@ -538,23 +753,33 @@ class WindowsManager {
538
753
  }
539
754
  // @ts-ignore
540
755
  window.on('closed', () => {
541
- log('log', 'closed', window.id, window._name);
542
- this.windows.delete(window.id || window._id);
756
+ const winId = this._getWebContentsId(window);
757
+ log('log', 'closed', winId, window._name);
758
+ if (winId !== undefined) {
759
+ this.windows.delete(winId);
760
+ }
761
+ // 同步清理名称索引
762
+ this.windowsByName.delete(window._name);
543
763
  });
544
764
  }
545
765
  if (type === 'BV') {
546
766
  parentWin?.addBrowserView(window);
547
767
  log('log', 'create - addBrowserView');
548
768
  }
549
- this.windows.set(window.id || window._id || window.webContents.id, window);
769
+ const winId = this._getWebContentsId(window);
770
+ if (winId !== undefined) {
771
+ this.windows.set(winId, window);
772
+ // 同步更新名称索引
773
+ this.windowsByName.set(window._name, winId);
774
+ }
550
775
  log('log', 'create', this.windows.keys());
551
776
  // 初始化值
552
777
  window.webContents.on('did-finish-load', () => {
553
- console.error('did-finish-load', window.webContents.id);
778
+ log('log', 'did-finish-load', this._getWebContentsId(window));
554
779
  initWebContentsVal(window, `${preload || ''}`);
555
780
  });
556
781
  window.webContents.on('did-start-loading', () => {
557
- console.error('did-start-loading', window.webContents.id);
782
+ log('log', 'did-start-loading', this._getWebContentsId(window));
558
783
  initWebContentsVal(window, `${preload || ''}`);
559
784
  });
560
785
  if (type === 'BW') {
@@ -565,7 +790,7 @@ class WindowsManager {
565
790
  try {
566
791
  window.dispatchEvent(new Event('focus'));
567
792
  } catch (error) {
568
- console.error('focus', error);
793
+ // 忽略错误,避免影响主流程
569
794
  }
570
795
  `);
571
796
  }
@@ -579,7 +804,7 @@ class WindowsManager {
579
804
  try {
580
805
  window.dispatchEvent(new Event('blur'));
581
806
  } catch (error) {
582
- console.error('blur', error);
807
+ // 忽略错误,避免影响主流程
583
808
  }
584
809
  `);
585
810
  }
@@ -615,39 +840,58 @@ class WindowsManager {
615
840
  });
616
841
  try {
617
842
  const _addBrowserView = window.addBrowserView;
618
- window.addBrowserView = (view) => {
843
+ window.addBrowserView = (view, isSort = false) => {
619
844
  _addBrowserView.call(window, view);
620
845
  handleBrowserViewFocus(view);
846
+ // 添加BrowserView后重新排序(如果未禁用自动排序)
847
+ log('log', 'addBrowserView-sort', isSort, window.getBrowserViews());
848
+ if (isSort) {
849
+ this.sortBrowserViewsDebounced(window, view);
850
+ }
621
851
  };
622
852
  const _removeBrowserView = window.removeBrowserView;
623
- window.removeBrowserView = (view) => {
853
+ window.removeBrowserView = (view, isSort = false) => {
624
854
  _removeBrowserView.call(window, view);
625
855
  handleBrowserViewBlur(view);
856
+ // 移除BrowserView后重新排序(如果未禁用自动排序)
857
+ log('log', 'removeBrowserView-sort', isSort);
858
+ if (isSort) {
859
+ this.sortBrowserViewsDebounced(window, view);
860
+ }
626
861
  };
627
862
  const _setBrowserView = window.setBrowserView;
628
- window.setBrowserView = (view) => {
863
+ window.setBrowserView = (view, isSort = false) => {
629
864
  const views = window.getBrowserViews() || [];
630
- for (const view of views) {
631
- handleBrowserViewBlur(view);
865
+ for (const existingView of views) {
866
+ handleBrowserViewBlur(existingView);
632
867
  }
633
868
  _setBrowserView.call(window, view);
634
869
  handleBrowserViewFocus(view);
870
+ log('log', 'setBrowserView-sort', isSort);
871
+ if (isSort) {
872
+ this.sortBrowserViewsDebounced(window, view);
873
+ }
635
874
  };
636
875
  }
637
876
  catch (error) {
638
877
  log('error', 'focus', error);
639
878
  }
640
879
  }
641
- console.log('message xxxx', options.url);
642
880
  if (options.url) {
643
881
  // @ts-ignore
644
882
  window.loadURL ? window.loadURL(options.url) : window.webContents.loadURL(options.url);
645
- // @ts-ignore
646
- window.focus ? window.focus() : window.webContents.focus();
883
+ if (options.browserWindow?.focusable !== false) {
884
+ window?.focus?.();
885
+ }
886
+ window?.webContents?.focus?.();
647
887
  }
648
888
  }
649
889
  catch (error) {
650
890
  log('error', 'create', error);
891
+ throw error;
892
+ }
893
+ if (!window) {
894
+ throw new Error(`Failed to create window: ${name}`);
651
895
  }
652
896
  return window;
653
897
  }
@@ -656,6 +900,7 @@ class WindowsManager {
656
900
  const { loadingView, preventOriginNavigate = false, } = createOptions;
657
901
  let _loadingView = new BrowserView({
658
902
  webPreferences: {
903
+ sandbox: false,
659
904
  // session: getCustomSession(),
660
905
  contextIsolation: false,
661
906
  nodeIntegration: true,
@@ -668,7 +913,7 @@ class WindowsManager {
668
913
  }
669
914
  const loadLoadingView = () => {
670
915
  const [viewWidth, viewHeight] = window.getSize();
671
- window.setBrowserView(_loadingView);
916
+ window.addBrowserView(_loadingView);
672
917
  _loadingView.setBounds({
673
918
  x: 0,
674
919
  y: 0,
@@ -708,7 +953,7 @@ class WindowsManager {
708
953
  return;
709
954
  }
710
955
  if (_loadingView.webContents && !_loadingView.webContents.isDestroyed()) {
711
- window.setBrowserView(_loadingView);
956
+ window.addBrowserView(_loadingView);
712
957
  }
713
958
  else {
714
959
  // if loadingView has been destroyed
@@ -724,148 +969,231 @@ class WindowsManager {
724
969
  window.webContents.on('did-stop-loading', onFailure);
725
970
  }
726
971
  }
972
+ /**
973
+ * 检查窗口是否已销毁
974
+ */
975
+ _isWindowDestroyed(win) {
976
+ try {
977
+ return !win || !win.webContents || win.webContents.isDestroyed();
978
+ }
979
+ catch {
980
+ return true;
981
+ }
982
+ }
983
+ /**
984
+ * 安全地获取窗口的 webContents.id
985
+ * 如果 webContents 已销毁,返回 undefined
986
+ */
987
+ _getWebContentsId(win) {
988
+ try {
989
+ if (!win || !win.webContents) {
990
+ return undefined;
991
+ }
992
+ if (win.webContents.isDestroyed()) {
993
+ return undefined;
994
+ }
995
+ return win.webContents.id;
996
+ }
997
+ catch {
998
+ return undefined;
999
+ }
1000
+ }
1001
+ /**
1002
+ * 判断是否本地/内网IP
1003
+ */
1004
+ _isLocalhost(hostname) {
1005
+ return (hostname === 'localhost' ||
1006
+ hostname === '127.0.0.1' ||
1007
+ hostname === '::1' ||
1008
+ /^192\.168\./.test(hostname) ||
1009
+ /^10\./.test(hostname) ||
1010
+ /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname));
1011
+ }
1012
+ /**
1013
+ * 清理已销毁的窗口(延迟执行,避免频繁检查)
1014
+ */
1015
+ _cleanupDestroyedWindows() {
1016
+ const toDelete = [];
1017
+ this.windows.forEach((win, key) => {
1018
+ if (this._isWindowDestroyed(win)) {
1019
+ toDelete.push(key);
1020
+ // 同步清理名称索引
1021
+ if (win?._name) {
1022
+ this.windowsByName.delete(win._name);
1023
+ }
1024
+ }
1025
+ });
1026
+ toDelete.forEach(key => this.windows.delete(key));
1027
+ }
727
1028
  get(idOrName) {
728
1029
  log('log', 'get', idOrName);
729
1030
  let win;
730
- this.windows.forEach((i, key) => {
731
- try {
732
- if (!(i && i?.webContents?.isDestroyed && !i?.webContents?.isDestroyed?.())) {
733
- this.windows.delete(key);
734
- }
1031
+ if (typeof idOrName === 'number') {
1032
+ // 按 ID 查找(O(1)),使用 webContents.id
1033
+ win = this.windows.get(idOrName);
1034
+ }
1035
+ else if (typeof idOrName === 'string') {
1036
+ // 按名称查找(O(1),使用索引)
1037
+ const winId = this.windowsByName.get(idOrName);
1038
+ if (winId !== undefined) {
1039
+ win = this.windows.get(winId);
735
1040
  }
736
- catch (error) {
737
- log('error', 'get');
1041
+ // 如果索引中找不到,回退到遍历查找(兼容旧数据)
1042
+ if (!win) {
1043
+ this.windows.forEach(w => {
1044
+ if (w._name === idOrName) {
1045
+ win = w;
1046
+ // 更新索引,使用 webContents.id
1047
+ const webContentsId = this._getWebContentsId(w);
1048
+ if (webContentsId !== undefined) {
1049
+ this.windowsByName.set(idOrName, webContentsId);
1050
+ }
1051
+ }
1052
+ });
738
1053
  }
739
- if (typeof idOrName === 'number') {
740
- if (i?.id === idOrName || i?._id === idOrName) {
741
- win = i;
742
- }
1054
+ }
1055
+ // 检查找到的窗口是否已销毁
1056
+ if (win && !this._isWindowDestroyed(win)) {
1057
+ return win;
1058
+ }
1059
+ // 窗口已销毁,触发清理
1060
+ if (win) {
1061
+ const winId = this._getWebContentsId(win);
1062
+ if (winId !== undefined) {
1063
+ this.windows.delete(winId);
743
1064
  }
744
- else if (typeof idOrName === 'string') {
745
- if (i?._name === idOrName) {
746
- win = i;
747
- }
1065
+ if (win._name) {
1066
+ this.windowsByName.delete(win._name);
748
1067
  }
749
- });
750
- // @ts-ignore
751
- if (win && win?.webContents?.isDestroyed && !win?.webContents?.isDestroyed?.()) {
752
- return win;
753
1068
  }
1069
+ // 延迟清理其他已销毁的窗口
1070
+ this.cleanupDestroyedWindowsDebounced();
754
1071
  return undefined;
755
1072
  }
756
1073
  getAll(type) {
757
1074
  log('log', 'getAll');
758
- const bwWindows = new Map();
759
- const bvWindows = new Map();
1075
+ // 先清理已销毁的窗口
1076
+ const toDelete = [];
760
1077
  this.windows.forEach((win, key) => {
761
- if (!(win && win?.webContents?.isDestroyed && !win?.webContents?.isDestroyed?.())) {
762
- this.windows.delete(key);
763
- }
764
- if (win?._type === 'BW') {
765
- bwWindows.set(key, win);
766
- }
767
- if (win?._type === 'BV') {
768
- bvWindows.set(key, win);
1078
+ if (this._isWindowDestroyed(win)) {
1079
+ toDelete.push(key);
1080
+ if (win?._name) {
1081
+ this.windowsByName.delete(win._name);
1082
+ }
769
1083
  }
770
1084
  });
771
- if (type === 'BW') {
772
- return bwWindows;
773
- }
774
- if (type === 'BV') {
775
- return bvWindows;
1085
+ toDelete.forEach(key => this.windows.delete(key));
1086
+ if (!type) {
1087
+ return this.windows;
776
1088
  }
777
- return this.windows;
1089
+ const result = new Map();
1090
+ this.windows.forEach((win, key) => {
1091
+ if (!this._isWindowDestroyed(win) && win._type === type) {
1092
+ result.set(key, win);
1093
+ }
1094
+ });
1095
+ // 使用类型断言,TypeScript 会通过方法重载确保类型正确
1096
+ return result;
778
1097
  }
779
1098
  close(idOrName) {
780
1099
  log('log', 'close', idOrName);
781
- let win = undefined;
782
- this.windows.forEach((i) => {
783
- if (typeof idOrName === 'number') {
784
- if (i?.id === idOrName || i?._id === idOrName) {
785
- win = i;
786
- }
787
- }
788
- else if (typeof idOrName === 'string') {
789
- if (i?._name === idOrName) {
790
- win = i;
791
- }
792
- }
793
- });
794
- // @ts-ignore
795
- win && this.windows.delete(win?.id);
796
- // @ts-ignore
797
- if (win) {
798
- // @ts-ignore
1100
+ const win = this.get(idOrName);
1101
+ if (!win) {
1102
+ return false;
1103
+ }
1104
+ const winId = this._getWebContentsId(win);
1105
+ if (winId !== undefined) {
1106
+ this.windows.delete(winId);
1107
+ }
1108
+ if (win._name) {
1109
+ this.windowsByName.delete(win._name);
1110
+ }
1111
+ try {
799
1112
  if (win._type === 'BV') {
1113
+ // 从所有 BW 窗口中移除该 BV
800
1114
  this.windows.forEach(i => {
801
- if (i?._type === 'BW') {
1115
+ if (i?._type === 'BW' && !this._isWindowDestroyed(i)) {
802
1116
  const _win = i;
803
- _win.removeBrowserView(win);
1117
+ try {
1118
+ _win.removeBrowserView(win);
1119
+ }
1120
+ catch (error) {
1121
+ // 忽略错误,可能已经移除
1122
+ }
804
1123
  }
805
1124
  });
806
1125
  }
807
1126
  // @ts-ignore
808
- win?.webContents?.destroy?.();
1127
+ win.webContents?.destroy?.();
809
1128
  // @ts-ignore
810
- win?.close?.();
1129
+ win.close?.();
811
1130
  // @ts-ignore
812
- win?.destroy?.();
1131
+ win.destroy?.();
1132
+ }
1133
+ catch (error) {
1134
+ log('error', 'close window error:', error);
813
1135
  }
814
1136
  return true;
815
1137
  }
816
1138
  closeAll() {
817
1139
  log('log', 'closeAll');
818
- this.windows.forEach((win) => {
1140
+ const windowsToClose = Array.from(this.windows.values());
1141
+ // 先清空 Map,避免在遍历过程中修改
1142
+ this.windows.clear();
1143
+ this.windowsByName.clear();
1144
+ // 收集所有 BW 窗口,用于批量移除 BV
1145
+ const bwWindows = windowsToClose.filter(w => w._type === 'BW' && !this._isWindowDestroyed(w));
1146
+ const bvWindows = windowsToClose.filter(w => w._type === 'BV');
1147
+ // 先从所有 BW 窗口中移除 BV
1148
+ bvWindows.forEach(bv => {
1149
+ bwWindows.forEach(bw => {
1150
+ try {
1151
+ bw.removeBrowserView(bv);
1152
+ }
1153
+ catch (error) {
1154
+ // 忽略错误,可能已经移除
1155
+ }
1156
+ });
1157
+ });
1158
+ // 然后销毁所有窗口
1159
+ windowsToClose.forEach((win) => {
819
1160
  try {
820
- win && this.windows.delete(win?.id);
821
- // @ts-ignore
822
- if (win._type === 'BV') {
823
- this.windows.forEach(i => {
824
- if (i?._type === 'BW') {
825
- const _win = i;
826
- _win.removeBrowserView(win);
827
- }
828
- });
1161
+ if (this._isWindowDestroyed(win)) {
1162
+ return;
829
1163
  }
830
1164
  // @ts-ignore
831
- win?.webContents?.destroy?.();
1165
+ win.webContents?.destroy?.();
832
1166
  // @ts-ignore
833
- win?.close?.();
1167
+ win.close?.();
834
1168
  // @ts-ignore
835
- win?.destroy?.();
1169
+ win.destroy?.();
836
1170
  }
837
1171
  catch (error) {
1172
+ log('error', 'closeAll error:', error);
838
1173
  }
839
1174
  });
840
1175
  }
841
1176
  rename(idOrName, newName) {
842
1177
  log('log', 'rename', idOrName, newName);
843
- let win = undefined;
844
- // 先查找目标窗口
845
- if (typeof idOrName === 'number') {
846
- win = this.get(idOrName);
847
- }
848
- else if (typeof idOrName === 'string') {
849
- this.windows.forEach(i => {
850
- if (i?._name === idOrName) {
851
- win = i;
852
- }
853
- });
854
- }
1178
+ const win = this.get(idOrName);
855
1179
  if (!win) {
856
- // 没有找到目标窗口
857
1180
  return undefined;
858
1181
  }
859
- // 检查新名字是否已存在
860
- let nameExists = false;
861
- this.windows.forEach(i => {
862
- if (i !== win && i?._name === newName) {
863
- nameExists = true;
1182
+ // 检查新名字是否已存在(使用索引加速)
1183
+ const existingWinId = this.windowsByName.get(newName);
1184
+ if (existingWinId !== undefined) {
1185
+ const existingWin = this.windows.get(existingWinId);
1186
+ if (existingWin && existingWin !== win && !this._isWindowDestroyed(existingWin)) {
1187
+ // 新名字已被占用
1188
+ return undefined;
864
1189
  }
865
- });
866
- if (nameExists) {
867
- // 新名字已被占用
868
- return undefined;
1190
+ }
1191
+ // 更新名称索引
1192
+ const oldName = win._name;
1193
+ const winId = this._getWebContentsId(win);
1194
+ if (winId !== undefined) {
1195
+ this.windowsByName.delete(oldName);
1196
+ this.windowsByName.set(newName, winId);
869
1197
  }
870
1198
  // 修改名字并同步 webContents
871
1199
  win._name = newName;
@@ -874,12 +1202,7 @@ class WindowsManager {
874
1202
  }
875
1203
  reInitUrl(idOrName, url) {
876
1204
  log('log', 'reInitUrl', idOrName, url);
877
- let win = undefined;
878
- this.windows.forEach(i => {
879
- if (i?._name === idOrName) {
880
- win = i;
881
- }
882
- });
1205
+ const win = this.get(idOrName);
883
1206
  if (!win) {
884
1207
  return undefined;
885
1208
  }
@@ -908,6 +1231,86 @@ class WindowsManager {
908
1231
  if (typeof browserWindowOptions.alwaysOnTop === 'boolean') {
909
1232
  win.setAlwaysOnTop(browserWindowOptions.alwaysOnTop);
910
1233
  }
1234
+ // 设置背景颜色
1235
+ if (typeof browserWindowOptions.backgroundColor === 'string') {
1236
+ win.setBackgroundColor(browserWindowOptions.backgroundColor);
1237
+ }
1238
+ // 居中
1239
+ if (browserWindowOptions?.center !== false) {
1240
+ win.center();
1241
+ }
1242
+ // 设置窗口移动
1243
+ if (typeof browserWindowOptions.movable === 'boolean') {
1244
+ win.setMovable(browserWindowOptions.movable);
1245
+ }
1246
+ // 设置窗口大小调整
1247
+ if (typeof browserWindowOptions.resizable === 'boolean') {
1248
+ win.setResizable(browserWindowOptions.resizable);
1249
+ }
1250
+ // 设置全屏模式
1251
+ if (typeof browserWindowOptions.fullscreenable === 'boolean') {
1252
+ win.setFullScreenable(browserWindowOptions.fullscreenable);
1253
+ }
1254
+ // 设置窗口阴影
1255
+ if (typeof browserWindowOptions.hasShadow === 'boolean') {
1256
+ win.setHasShadow(browserWindowOptions.hasShadow);
1257
+ }
1258
+ // 设置窗口最小尺寸
1259
+ if (typeof browserWindowOptions.minWidth === 'number' && typeof browserWindowOptions.minHeight === 'number') {
1260
+ win.setMinimumSize(browserWindowOptions.minWidth, browserWindowOptions.minHeight);
1261
+ }
1262
+ // 设置窗口最大尺寸
1263
+ if (typeof browserWindowOptions.maxWidth === 'number' && typeof browserWindowOptions.maxHeight === 'number') {
1264
+ win.setMaximumSize(browserWindowOptions.maxWidth, browserWindowOptions.maxHeight);
1265
+ }
1266
+ // 设置窗口位置
1267
+ if (typeof browserWindowOptions.x === 'number' && typeof browserWindowOptions.y === 'number') {
1268
+ win.setPosition(browserWindowOptions.x, browserWindowOptions.y);
1269
+ }
1270
+ // 设置窗口标题
1271
+ if (typeof browserWindowOptions.title === 'string') {
1272
+ win.setTitle(browserWindowOptions.title);
1273
+ }
1274
+ // 设置窗口图标
1275
+ if (typeof browserWindowOptions.icon === 'string') {
1276
+ win.setIcon(browserWindowOptions.icon);
1277
+ }
1278
+ // 设置窗口菜单栏可见性
1279
+ if (typeof browserWindowOptions.autoHideMenuBar === 'boolean') {
1280
+ win.setAutoHideMenuBar(browserWindowOptions.autoHideMenuBar);
1281
+ }
1282
+ // 设置窗口最小化按钮
1283
+ if (browserWindowOptions.minimizable === false) {
1284
+ win.setMinimizable(false);
1285
+ }
1286
+ // 设置窗口最大化按钮
1287
+ if (browserWindowOptions.maximizable === false) {
1288
+ win.setMaximizable(false);
1289
+ }
1290
+ // 设置窗口关闭按钮
1291
+ if (browserWindowOptions.closable === false) {
1292
+ win.setClosable(false);
1293
+ }
1294
+ // 设置窗口焦点
1295
+ if (browserWindowOptions.focusable === false) {
1296
+ win.setFocusable(false);
1297
+ }
1298
+ // 设置窗口全屏
1299
+ if (browserWindowOptions.fullscreen === true) {
1300
+ win.setFullScreen(true);
1301
+ }
1302
+ // 设置窗口背景材质
1303
+ if (typeof browserWindowOptions.vibrancy === 'string') {
1304
+ win.setVibrancy(browserWindowOptions.vibrancy);
1305
+ }
1306
+ // 设置窗口透明度
1307
+ if (typeof browserWindowOptions.opacity === 'number') {
1308
+ win.setOpacity(browserWindowOptions.opacity);
1309
+ }
1310
+ // 设置窗口显示状态
1311
+ if (browserWindowOptions.show === false) {
1312
+ win.hide();
1313
+ }
911
1314
  // 可继续扩展其他动态属性
912
1315
  }
913
1316
  _applyBrowserViewOptions(view, options) {
@@ -923,35 +1326,175 @@ class WindowsManager {
923
1326
  if (typeof browserWindowOptions.width === 'number' && typeof browserWindowOptions.height === 'number') {
924
1327
  view.setBounds({ x: 0, y: 0, width: browserWindowOptions.width, height: browserWindowOptions.height });
925
1328
  }
1329
+ // 设置视图位置
1330
+ if (typeof browserWindowOptions.x === 'number' && typeof browserWindowOptions.y === 'number') {
1331
+ const bounds = view.getBounds();
1332
+ view.setBounds({
1333
+ x: browserWindowOptions.x,
1334
+ y: browserWindowOptions.y,
1335
+ width: bounds.width,
1336
+ height: bounds.height
1337
+ });
1338
+ }
1339
+ // 设置视图背景颜色
1340
+ if (typeof browserWindowOptions.backgroundColor === 'string') {
1341
+ view.setBackgroundColor(browserWindowOptions.backgroundColor);
1342
+ }
926
1343
  // 可继续扩展其他动态属性
927
1344
  }
928
1345
  // 生成一个bv 做为预加载资源窗口,加载完成后销毁
929
1346
  async createPreloadWebContents(url) {
930
- return new Promise(async (resolve, reject) => {
931
- let bv = await this.create({
1347
+ let bv = null;
1348
+ try {
1349
+ bv = await this.create({
932
1350
  type: 'BV',
933
1351
  url,
934
1352
  name: `preload-web-contents-${md5(url)}`,
935
1353
  extraData: `${url}`
936
1354
  });
937
- bv.webContents.on('did-finish-load', () => {
938
- this.close(bv.id || bv._id);
939
- resolve(true);
940
- bv = null;
1355
+ return new Promise((resolve, reject) => {
1356
+ const winId = this._getWebContentsId(bv);
1357
+ if (!winId) {
1358
+ reject(new Error('Failed to get window ID'));
1359
+ return;
1360
+ }
1361
+ bv.webContents.on('did-finish-load', () => {
1362
+ if (winId) {
1363
+ this.close(winId);
1364
+ }
1365
+ resolve(true);
1366
+ bv = null;
1367
+ });
1368
+ bv.webContents.on('did-fail-load', () => {
1369
+ if (winId) {
1370
+ this.close(winId);
1371
+ }
1372
+ reject(new Error('Failed to load web contents'));
1373
+ bv = null;
1374
+ });
1375
+ bv.webContents.loadURL(url);
941
1376
  });
942
- bv.webContents.on('did-fail-load', () => {
943
- this.close(bv.id || bv._id);
944
- reject(false);
945
- bv = null;
1377
+ }
1378
+ catch (error) {
1379
+ if (bv) {
1380
+ const winId = this._getWebContentsId(bv);
1381
+ if (winId) {
1382
+ this.close(winId);
1383
+ }
1384
+ }
1385
+ throw error;
1386
+ }
1387
+ }
1388
+ async getWindowForWebContentsId(wcId) {
1389
+ const wc = webContents.fromId(wcId);
1390
+ if (!wc)
1391
+ return undefined;
1392
+ // Case 1: BrowserView
1393
+ for (const win of BrowserWindow.getAllWindows()) {
1394
+ for (const view of win.getBrowserViews()) {
1395
+ if (view.webContents.id === wcId) {
1396
+ return win;
1397
+ }
1398
+ }
1399
+ }
1400
+ // Case 2: WebView
1401
+ // webview 有 hostWebContents,指向它所在的 BrowserWindow 的 webContents
1402
+ if (wc.hostWebContents) {
1403
+ return BrowserWindow.fromWebContents(wc.hostWebContents);
1404
+ }
1405
+ // Case 3: 普通 window 本身
1406
+ const win = BrowserWindow.fromWebContents(wc);
1407
+ if (win)
1408
+ return win;
1409
+ return undefined;
1410
+ }
1411
+ /**
1412
+ * 手动对BrowserView进行排序
1413
+ * @param windowId 窗口ID或名称
1414
+ */
1415
+ sortBrowserViews(windowId) {
1416
+ const window = this.get(windowId);
1417
+ if (window && window._type === 'BW') {
1418
+ this._sortBrowserViews(window);
1419
+ }
1420
+ }
1421
+ /**
1422
+ * 对BrowserView进行排序
1423
+ * @param window 目标窗口
1424
+ */
1425
+ _sortBrowserViews(window, addView) {
1426
+ try {
1427
+ if (window.isDestroyed()) {
1428
+ return;
1429
+ }
1430
+ const views = window.getBrowserViews() || [];
1431
+ if (views.length <= 1)
1432
+ return;
1433
+ log('log', 'sortBrowserViews', views?.map(i => this._getWebContentsId(i)));
1434
+ // 创建排序后的视图数组(不修改原数组)
1435
+ const sortedViews = [...views].sort((a, b) => {
1436
+ const zIndexA = a._zIndex ?? 0;
1437
+ const zIndexB = b._zIndex ?? 0;
1438
+ return zIndexA - zIndexB;
946
1439
  });
947
- bv.webContents.loadURL(url);
948
- });
1440
+ // 检查是否已经按正确顺序排列(优化:提前退出)
1441
+ let needsReorder = false;
1442
+ for (let i = 0; i < views.length; i++) {
1443
+ const view = views[i];
1444
+ const sortedView = sortedViews[i];
1445
+ const viewId = this._getWebContentsId(view);
1446
+ const sortedViewId = this._getWebContentsId(sortedView);
1447
+ if (!view || !sortedView || viewId !== sortedViewId) {
1448
+ needsReorder = true;
1449
+ break;
1450
+ }
1451
+ }
1452
+ log('log', 'sortBrowserViews needsReorder', needsReorder, sortedViews?.map(i => this._getWebContentsId(i)));
1453
+ // 如果已经按正确顺序排列,则不需要重新排序
1454
+ if (!needsReorder)
1455
+ return;
1456
+ // 移除所有BrowserView(排除刚添加的视图,避免不必要的操作)
1457
+ const addViewId = this._getWebContentsId(addView);
1458
+ views.forEach(view => {
1459
+ const viewId = this._getWebContentsId(view);
1460
+ if (addViewId !== viewId) {
1461
+ try {
1462
+ // @ts-ignore
1463
+ window.removeBrowserView(view, false);
1464
+ }
1465
+ catch (error) {
1466
+ // 忽略错误,视图可能已经被移除
1467
+ }
1468
+ }
1469
+ });
1470
+ // 按正确顺序重新添加
1471
+ sortedViews.forEach((view, index) => {
1472
+ try {
1473
+ if (index === 0) {
1474
+ // 第一个设置为当前视图
1475
+ // @ts-ignore
1476
+ window.setBrowserView(view, false);
1477
+ }
1478
+ else {
1479
+ // 其他视图添加到后面
1480
+ // @ts-ignore
1481
+ window.addBrowserView(view, false);
1482
+ }
1483
+ }
1484
+ catch (error) {
1485
+ log('error', 'addBrowserView in sortBrowserViews error:', error);
1486
+ }
1487
+ });
1488
+ }
1489
+ catch (error) {
1490
+ log('error', 'sortBrowserViews error:', error);
1491
+ }
949
1492
  }
950
1493
  }
951
1494
  // @ts-ignore
952
1495
  global['__ELECTRON_WINDOWS_MANAGER__'] = undefined;
953
1496
  let isInitialized = false;
954
- const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl, webviewDomainWhiteList) => {
1497
+ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList) => {
955
1498
  // @ts-ignore
956
1499
  if (isInitialized && global['__ELECTRON_WINDOWS_MANAGER__']) {
957
1500
  // @ts-ignore
@@ -959,7 +1502,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
959
1502
  }
960
1503
  isInitialized = true;
961
1504
  // @ts-ignore
962
- const wm = global['__ELECTRON_WINDOWS_MANAGER__'] = new WindowsManager(preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl, webviewDomainWhiteList);
1505
+ const wm = global['__ELECTRON_WINDOWS_MANAGER__'] = new WindowsManager(preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList);
963
1506
  eIpc.mainIPC.handleRenderer('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', async (data) => {
964
1507
  if (data?.type === 'create') {
965
1508
  const opt = data;
@@ -975,46 +1518,71 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
975
1518
  findWin.webContents.reload();
976
1519
  }, 100);
977
1520
  }
1521
+ if (opt.data.browserWindow?.parent) {
1522
+ try {
1523
+ if (findWin._type === 'BW') {
1524
+ findWin.setParentWindow(BrowserWindow.fromId(Number(opt.data.browserWindow.parent)));
1525
+ }
1526
+ if (findWin._type === 'BV') {
1527
+ BrowserWindow.fromId(Number(opt.data.browserWindow.parent))?.addBrowserView(findWin);
1528
+ }
1529
+ }
1530
+ catch (error) {
1531
+ log('error', 'setParentWindow error:', error);
1532
+ }
1533
+ }
1534
+ if (findWin?._type === 'BV' && opt.data.zIndex) {
1535
+ findWin._zIndex = opt.data.zIndex;
1536
+ }
1537
+ const winId = wm._getWebContentsId(findWin);
978
1538
  return {
979
- winId: Number(`${findWin?.id || findWin?._id || -1}`),
1539
+ winId: Number(`${winId || -1}`),
980
1540
  winName: `${findWin?._name || ''}`,
981
1541
  winType: `${findWin?._type || ''}`,
982
1542
  winExtraData: `${findWin?._extraData || ''}`,
983
1543
  winInitUrl: `${findWin?._initUrl || ''}`,
1544
+ winZIndex: `${findWin._zIndex || 0}`,
984
1545
  };
985
1546
  }
986
1547
  const res = await wm.create(opt.data);
1548
+ const winId = wm._getWebContentsId(res);
987
1549
  return {
988
- winId: Number(`${res.id || res._id || -1}`),
1550
+ winId: Number(`${winId || -1}`),
989
1551
  winName: `${res?._name || ''}`,
990
1552
  winType: `${res?._type || ''}`,
991
1553
  winExtraData: `${res?._extraData || ''}`,
992
1554
  winInitUrl: `${res?._initUrl || ''}`,
1555
+ winZIndex: `${res?._zIndex || 0}`,
993
1556
  };
994
1557
  }
995
1558
  if (data?.type === 'get') {
996
1559
  const opt = data;
997
1560
  const res = wm.get(opt?.data);
1561
+ const winId = wm._getWebContentsId(res);
998
1562
  return {
999
- winId: Number(`${res?.id || res?._id || -1}`),
1563
+ winId: Number(`${winId || -1}`),
1000
1564
  winName: `${res?._name || ''}`,
1001
1565
  winType: `${res?._type || ''}`,
1002
1566
  winExtraData: `${res?._extraData || ''}`,
1003
1567
  winInitUrl: `${res?._initUrl || ''}`,
1568
+ winZIndex: `${res?._zIndex || 0}`,
1004
1569
  };
1005
1570
  }
1006
1571
  if (data?.type === 'getAll') {
1007
1572
  const res = wm.getAll();
1008
1573
  const obj = {};
1009
1574
  res.forEach(i => {
1010
- // @ts-ignore
1011
- obj[i.id || i._id] = {
1012
- winId: Number(`${i?.id || i?._id || -1}`),
1013
- winName: `${i?._name || ''}`,
1014
- winType: `${i?._type || ''}`,
1015
- winExtraData: `${i?._extraData || ''}`,
1016
- winInitUrl: `${i?._initUrl || ''}`,
1017
- };
1575
+ const winId = wm._getWebContentsId(i);
1576
+ if (winId !== undefined) {
1577
+ obj[winId] = {
1578
+ winId: Number(`${winId}`),
1579
+ winName: `${i?._name || ''}`,
1580
+ winType: `${i?._type || ''}`,
1581
+ winExtraData: `${i?._extraData || ''}`,
1582
+ winInitUrl: `${i?._initUrl || ''}`,
1583
+ winZIndex: `${i?._zIndex || 0}`,
1584
+ };
1585
+ }
1018
1586
  });
1019
1587
  return obj;
1020
1588
  }
@@ -1031,12 +1599,14 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
1031
1599
  const opt = data;
1032
1600
  const res = wm.rename(opt.data.idOrName, opt.data.newName);
1033
1601
  if (res) {
1602
+ const winId = wm._getWebContentsId(res);
1034
1603
  return {
1035
- winId: Number(`${res?.id || res?._id || -1}`),
1604
+ winId: Number(`${winId || -1}`),
1036
1605
  winName: `${res?._name || ''}`,
1037
1606
  winType: `${res?._type || ''}`,
1038
1607
  winExtraData: `${res?._extraData || ''}`,
1039
1608
  winInitUrl: `${res?._initUrl || ''}`,
1609
+ winZIndex: `${res?._zIndex || 0}`,
1040
1610
  };
1041
1611
  }
1042
1612
  return undefined;
@@ -1045,40 +1615,23 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
1045
1615
  const opt = data;
1046
1616
  const res = wm.reInitUrl(opt.data.idOrName, opt.data.url);
1047
1617
  if (res) {
1618
+ const winId = wm._getWebContentsId(res);
1048
1619
  return {
1049
- winId: Number(`${res?.id || res?._id || -1}`),
1620
+ winId: Number(`${winId || -1}`),
1050
1621
  winName: `${res?._name || ''}`,
1051
1622
  winType: `${res?._type || ''}`,
1052
1623
  winExtraData: `${res?._extraData || ''}`,
1053
1624
  winInitUrl: `${res?._initUrl || ''}`,
1625
+ winZIndex: `${res?._zIndex || 0}`,
1054
1626
  };
1055
1627
  }
1056
1628
  return undefined;
1057
1629
  }
1058
- if (data?.type === 'getWindowForWebContentId') {
1630
+ if (data?.type === 'getWindowForWebContentsId') {
1059
1631
  const opt = data;
1060
- const targetWebContents = webContents.fromId(opt.data);
1061
- if (targetWebContents) {
1062
- let win = BrowserWindow.fromWebContents(targetWebContents);
1063
- if (!win) {
1064
- // 获取所有的 BrowserWindows
1065
- let allWindows = BrowserWindow.getAllWindows();
1066
- // 遍历所有窗口,检查每个窗口的 BrowserView
1067
- for (let _win of allWindows) {
1068
- let views = _win.getBrowserViews();
1069
- // 遍历窗口的所有 BrowserView
1070
- for (let view of views) {
1071
- if (view.webContents === targetWebContents) {
1072
- win = _win;
1073
- break;
1074
- }
1075
- }
1076
- if (win)
1077
- break;
1078
- }
1079
- }
1080
- // @ts-ignore
1081
- return win?.id || win?._id;
1632
+ const res = await wm.getWindowForWebContentsId(opt.data);
1633
+ if (res) {
1634
+ return wm._getWebContentsId(res);
1082
1635
  }
1083
1636
  return undefined;
1084
1637
  }
@@ -1119,15 +1672,20 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
1119
1672
  return undefined;
1120
1673
  }
1121
1674
  // 是否开启预加载窗口
1122
- if (data?.type === 'setPreloadWebContentsUrl') {
1675
+ if (data?.type === 'setPreloadWebContentsConfig') {
1123
1676
  const opt = data;
1124
- wm.setPreloadWebContentsUrl(opt.data);
1677
+ wm.setPreloadWebContentsConfig(opt.data);
1125
1678
  }
1126
1679
  if (data?.type === 'createPreloadWebContents') {
1127
1680
  const opt = data;
1128
1681
  const res = await wm.createPreloadWebContents(opt.data);
1129
1682
  return res;
1130
1683
  }
1684
+ if (data?.type === 'sortBrowserViews') {
1685
+ const opt = data;
1686
+ wm.sortBrowserViews(opt.data);
1687
+ return true;
1688
+ }
1131
1689
  return undefined;
1132
1690
  });
1133
1691
  return wm;