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