@lynker-desktop/electron-window-manager 0.0.9-alpha.7 → 0.0.9-alpha.70

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