@lynker-desktop/electron-window-manager 0.0.9-alpha.5 → 0.0.9-alpha.50

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