@lynker-desktop/electron-window-manager 0.0.9-alpha.3 → 0.0.9-alpha.30

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
@@ -69,30 +69,50 @@ class WindowsManager {
69
69
  * - 不带点的(如 example.com)只匹配主域名。
70
70
  * - 'localhost'、'127.0.0.1'、'::1' 以及局域网 IP(如 192.168.x.x、10.x.x.x、172.16.x.x~172.31.x.x)都视为本地白名单。
71
71
  */
72
- constructor(preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl, webviewDomainWhiteList) {
72
+ constructor(preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList) {
73
+ // 预加载的窗口
73
74
  this.preloadedBW = null;
75
+ // 预加载的窗口(无边框,有按钮)
74
76
  this.preloadedBW_FramelessWithButtons = null;
77
+ // 预加载的窗口(无边框,无按钮)
75
78
  this.preloadedBW_FramelessNoButtons = null;
79
+ // 预加载的浏览器视图
76
80
  this.preloadedBV = null;
77
81
  this.preloading = false;
78
82
  this.webviewDomainWhiteList = [];
83
+ // 创建队列相关属性
84
+ this.createQueue = [];
85
+ this.isCreating = false;
86
+ /**
87
+ * 防抖的排序方法
88
+ * @param window 目标窗口
89
+ */
90
+ this.sortBrowserViewsDebounced = lodash.debounce((window, view) => {
91
+ this._sortBrowserViews(window, view);
92
+ }, 50);
79
93
  this.preload = preload;
80
94
  this.windows = new Map();
81
95
  this.loadingViewUrl = `${loadingViewUrl ?? ''}`;
82
96
  this.errorViewUrl = `${errorViewUrl ?? ''}`;
83
- this.preloadWebContentsUrl = `${preloadWebContentsUrl ?? ''}`;
97
+ this.preloadWebContentsConfig = preloadWebContentsConfig;
84
98
  this.webviewDomainWhiteList = webviewDomainWhiteList || [];
85
- log('log', 'preloadWebContentsUrl: ', this.preloadWebContentsUrl);
86
- if (this.preloadWebContentsUrl) {
87
- electron.app.on('ready', () => {
88
- this._preloadInstances();
99
+ log('log', 'preloadWebContentsConfig: ', this.preloadWebContentsConfig);
100
+ if (this.preloadWebContentsConfig) {
101
+ electron.app.whenReady().then(() => {
102
+ if (this.preloadWebContentsConfig) {
103
+ this.setPreloadWebContentsConfig(this.preloadWebContentsConfig);
104
+ }
89
105
  });
90
106
  }
91
107
  }
92
- setPreloadWebContentsUrl(preloadWebContentsUrl) {
108
+ /**
109
+ * 设置预加载的webContents配置
110
+ * @param preloadWebContentsConfig 预加载的webContents配置
111
+ */
112
+ setPreloadWebContentsConfig(preloadWebContentsConfig) {
93
113
  try {
94
- this.preloadWebContentsUrl = preloadWebContentsUrl;
95
- if (this.preloadWebContentsUrl) {
114
+ this.preloadWebContentsConfig = preloadWebContentsConfig;
115
+ if (this.preloadWebContentsConfig) {
96
116
  this._preloadInstances();
97
117
  }
98
118
  else {
@@ -103,27 +123,65 @@ class WindowsManager {
103
123
  }
104
124
  }
105
125
  catch (error) {
106
- log('error', 'setPreloadWebContentsUrl error:', error);
126
+ log('error', 'setPreloadWebContentsConfig error:', error);
107
127
  }
108
128
  }
129
+ /**
130
+ * 预加载实例
131
+ */
109
132
  async _preloadInstances() {
110
133
  if (this.preloading)
111
134
  return;
112
135
  this.preloading = true;
113
136
  try {
114
- if (this.preloadWebContentsUrl) {
115
- this.preloadedBW = this.preloadedBW || await this._createPreloadBW({});
116
- this.preloadedBW_FramelessWithButtons = this.preloadedBW_FramelessWithButtons || await this._createPreloadBW({
117
- frame: false,
118
- transparent: true,
119
- titleBarStyle: 'hidden',
120
- });
121
- this.preloadedBW_FramelessNoButtons = this.preloadedBW_FramelessNoButtons || await this._createPreloadBW({
122
- frame: false,
123
- transparent: true,
124
- titleBarStyle: 'customButtonsOnHover',
125
- });
126
- this.preloadedBV = this.preloadedBV || await this._createPreloadBV();
137
+ if (this.preloadWebContentsConfig) {
138
+ log('log', 'preloadWebContentsConfig: ', this.preloadWebContentsConfig);
139
+ // 根据配置决定是否预加载普通窗口
140
+ if (this.preloadWebContentsConfig.enableBW !== false) {
141
+ if (!this.preloadedBW) {
142
+ this._createPreloadBW({}).then(i => {
143
+ this.preloadedBW = i;
144
+ log('log', 'init preloadedBW: ', !!this.preloadedBW);
145
+ });
146
+ }
147
+ }
148
+ // 根据配置决定是否预加载无边框有按钮的窗口
149
+ if (this.preloadWebContentsConfig.enableBW_FramelessWithButtons !== false) {
150
+ if (!this.preloadedBW_FramelessWithButtons) {
151
+ this._createPreloadBW({
152
+ frame: false,
153
+ // transparent: true,
154
+ autoHideMenuBar: true,
155
+ titleBarStyle: 'hidden',
156
+ }).then(i => {
157
+ this.preloadedBW_FramelessWithButtons = i;
158
+ log('log', 'init preloadedBW_FramelessWithButtons: ', !!this.preloadedBW_FramelessWithButtons);
159
+ });
160
+ }
161
+ }
162
+ // 根据配置决定是否预加载无边框无按钮的窗口
163
+ if (this.preloadWebContentsConfig.enableBW_FramelessNoButtons !== false) {
164
+ if (!this.preloadedBW_FramelessNoButtons) {
165
+ this._createPreloadBW({
166
+ frame: false,
167
+ // transparent: true,
168
+ autoHideMenuBar: true,
169
+ titleBarStyle: 'default',
170
+ }).then(i => {
171
+ this.preloadedBW_FramelessNoButtons = i;
172
+ log('log', 'init preloadedBW_FramelessNoButtons: ', !!this.preloadedBW_FramelessNoButtons);
173
+ });
174
+ }
175
+ }
176
+ // 根据配置决定是否预加载浏览器视图
177
+ if (this.preloadWebContentsConfig.enableBV !== false) {
178
+ if (!this.preloadedBV) {
179
+ this._createPreloadBV().then(i => {
180
+ this.preloadedBV = i;
181
+ log('log', 'init preloadedBV: ', !!this.preloadedBV);
182
+ });
183
+ }
184
+ }
127
185
  }
128
186
  }
129
187
  catch (e) {
@@ -131,23 +189,32 @@ class WindowsManager {
131
189
  }
132
190
  this.preloading = false;
133
191
  }
192
+ /**
193
+ * 创建预加载的窗口
194
+ * @param options 窗口选项
195
+ * @returns 预加载的窗口
196
+ */
134
197
  _createPreloadBW(options = {}) {
135
198
  return new Promise((resolve) => {
136
199
  const preload = this.preload;
137
- const url = this.preloadWebContentsUrl;
138
- if (this.preloadWebContentsUrl) {
200
+ const url = this.preloadWebContentsConfig?.url;
201
+ if (this.preloadWebContentsConfig?.url) {
202
+ const webPreferences = (options.webPreferences || {});
139
203
  const instance = new electron.BrowserWindow({
204
+ useContentSize: true,
140
205
  show: false,
206
+ backgroundColor: '#ffffff',
141
207
  ...options,
142
208
  webPreferences: {
209
+ ...webPreferences,
143
210
  webviewTag: true,
144
211
  plugins: true,
145
212
  nodeIntegration: true,
146
213
  contextIsolation: false,
147
214
  backgroundThrottling: false,
148
215
  webSecurity: false,
149
- preload: preload,
150
- ...(options.webPreferences || {}),
216
+ preload: webPreferences.preload || preload,
217
+ defaultEncoding: 'utf-8',
151
218
  }
152
219
  });
153
220
  try {
@@ -164,31 +231,42 @@ class WindowsManager {
164
231
  log('error', '预加载 BW 设置 _id 失败', error);
165
232
  }
166
233
  // @ts-ignore
167
- log('log', '创建预BW: ', instance._id, this.preloadWebContentsUrl);
168
- instance.webContents.once('did-finish-load', () => {
169
- resolve(instance);
170
- });
171
- instance.webContents.once('did-fail-load', () => {
172
- resolve(instance);
173
- });
174
- instance.loadURL(url || 'about:blank');
234
+ log('log', '创建预BW: ', instance._id, this.preloadWebContentsConfig?.url);
235
+ // instance.webContents.once('did-finish-load', () => {
236
+ // resolve(instance as BWItem);
237
+ // });
238
+ // instance.webContents.once('did-fail-load', () => {
239
+ // resolve(instance as BWItem);
240
+ // });
241
+ instance.loadURL(url ? `${url}` : 'about:blank');
242
+ resolve(instance);
243
+ }
244
+ else {
245
+ resolve(null);
175
246
  }
176
247
  });
177
248
  }
178
- _createPreloadBV() {
249
+ /**
250
+ * 创建预加载的浏览器视图
251
+ * @returns 预加载的浏览器视图
252
+ */
253
+ _createPreloadBV(options = {}) {
179
254
  return new Promise((resolve) => {
180
255
  const preload = this.preload;
181
- const url = this.preloadWebContentsUrl;
182
- if (this.preloadWebContentsUrl) {
256
+ const url = this.preloadWebContentsConfig?.url;
257
+ if (this.preloadWebContentsConfig?.url) {
258
+ const webPreferences = (options.webPreferences || {});
183
259
  const instance = new electron.BrowserView({
184
260
  webPreferences: {
261
+ ...webPreferences,
185
262
  webviewTag: true,
186
263
  plugins: true,
187
264
  nodeIntegration: true,
188
265
  contextIsolation: false,
189
266
  // backgroundThrottling: false,
190
267
  webSecurity: false,
191
- preload: preload,
268
+ preload: webPreferences.preload || preload,
269
+ defaultEncoding: 'utf-8',
192
270
  }
193
271
  });
194
272
  try {
@@ -200,61 +278,130 @@ class WindowsManager {
200
278
  try {
201
279
  // @ts-ignore
202
280
  instance._id = instance.webContents.id;
281
+ // 设置默认zIndex层级
282
+ instance._zIndex = 0;
203
283
  }
204
284
  catch (error) {
205
285
  log('error', '预加载 BV 设置 _id 失败', error);
206
286
  }
207
287
  // @ts-ignore
208
- log('log', '创建预BV: ', instance._id, this.preloadWebContentsUrl);
209
- instance.webContents.once('did-finish-load', () => {
210
- resolve(instance);
211
- });
212
- instance.webContents.once('did-fail-load', () => {
213
- resolve(instance);
214
- });
288
+ log('log', '创建预BV: ', instance._id, this.preloadWebContentsConfig?.url);
289
+ // instance.webContents.once('did-finish-load', () => {
290
+ // resolve(instance as BVItem);
291
+ // });
292
+ // instance.webContents.once('did-fail-load', () => {
293
+ // resolve(instance as BVItem);
294
+ // });
215
295
  instance.webContents.loadURL(url || 'about:blank');
296
+ resolve(instance);
297
+ }
298
+ else {
299
+ resolve(null);
216
300
  }
217
301
  });
218
302
  }
219
303
  create(options) {
304
+ return new Promise((resolve, reject) => {
305
+ // 将创建请求添加到队列
306
+ this.createQueue.push({ options, resolve, reject });
307
+ // 如果当前没有在创建,则开始处理队列
308
+ if (!this.isCreating) {
309
+ this.processCreateQueue();
310
+ }
311
+ });
312
+ }
313
+ /**
314
+ * 处理创建队列
315
+ */
316
+ async processCreateQueue() {
317
+ if (this.isCreating || this.createQueue.length === 0) {
318
+ return;
319
+ }
320
+ this.isCreating = true;
321
+ while (this.createQueue.length > 0) {
322
+ const { options, resolve, reject } = this.createQueue.shift();
323
+ try {
324
+ const window = await this._createWindow(options);
325
+ resolve(window);
326
+ }
327
+ catch (error) {
328
+ log('error', 'create window failed:', error);
329
+ reject(error);
330
+ }
331
+ }
332
+ this.isCreating = false;
333
+ }
334
+ /**
335
+ * 实际的窗口创建逻辑
336
+ */
337
+ async _createWindow(options) {
220
338
  let window;
221
- const { usePreload = true, type = 'BW', name = 'anonymous', url, loadingView = { url: undefined }, errorView = { url: undefined }, browserWindow: browserWindowOptions, openDevTools = false, preventOriginClose = false, } = options;
339
+ const { usePreload = true, type = 'BW', name = 'anonymous', url, loadingView = { url: undefined }, errorView = { url: undefined }, browserWindow: browserWindowOptions, openDevTools = false, preventOriginClose = false, zIndex = 0, } = options;
222
340
  options.type = type;
223
341
  // 优先复用预创建实例
224
342
  let preloadWin = null;
225
- if (type === 'BW' && usePreload && this.preloadWebContentsUrl) {
343
+ if (type === 'BW' && usePreload && this.preloadWebContentsConfig?.url) {
226
344
  const bwOptions = browserWindowOptions || {};
227
- if (bwOptions.frame === false && bwOptions.titleBarStyle === 'hidden') {
228
- preloadWin = this.preloadedBW_FramelessWithButtons;
229
- this.preloadedBW_FramelessWithButtons = null;
230
- setTimeout(() => this._createPreloadBW({ frame: false, transparent: true, titleBarStyle: 'hidden' }), 0);
231
- }
232
- else if (bwOptions.frame === false && bwOptions.titleBarStyle === 'customButtonsOnHover') {
233
- preloadWin = this.preloadedBW_FramelessNoButtons;
234
- this.preloadedBW_FramelessNoButtons = null;
235
- setTimeout(() => this._createPreloadBW({ frame: false, transparent: true, titleBarStyle: 'customButtonsOnHover' }), 0);
345
+ if (bwOptions.frame === false) {
346
+ if (bwOptions.titleBarStyle === 'default' || !bwOptions.titleBarStyle) {
347
+ if (this.preloadWebContentsConfig.enableBW_FramelessNoButtons !== false && this.preloadedBW_FramelessNoButtons) {
348
+ preloadWin = this.preloadedBW_FramelessNoButtons;
349
+ this.preloadedBW_FramelessNoButtons = await this._createPreloadBW({
350
+ frame: false,
351
+ // transparent: true,
352
+ titleBarStyle: 'default',
353
+ webPreferences: {
354
+ preload: bwOptions?.webPreferences?.preload || this.preload,
355
+ }
356
+ });
357
+ }
358
+ }
359
+ else {
360
+ if (this.preloadWebContentsConfig.enableBW_FramelessWithButtons !== false && this.preloadedBW_FramelessWithButtons) {
361
+ preloadWin = this.preloadedBW_FramelessWithButtons;
362
+ this.preloadedBW_FramelessWithButtons = await this._createPreloadBW({
363
+ frame: false,
364
+ // transparent: true,
365
+ titleBarStyle: 'hidden',
366
+ webPreferences: {
367
+ preload: this.preload,
368
+ }
369
+ });
370
+ }
371
+ }
236
372
  }
237
373
  else {
238
- preloadWin = this.preloadedBW;
239
- this.preloadedBW = null;
240
- setTimeout(() => this._createPreloadBW({}), 0);
374
+ if (this.preloadWebContentsConfig.enableBW !== false && this.preloadedBW) {
375
+ preloadWin = this.preloadedBW;
376
+ this.preloadedBW = await this._createPreloadBW({
377
+ webPreferences: {
378
+ preload: bwOptions?.webPreferences?.preload || this.preload,
379
+ }
380
+ });
381
+ }
241
382
  }
242
383
  }
243
- if (type === 'BV' && usePreload && this.preloadWebContentsUrl) {
244
- preloadWin = this.preloadedBV;
245
- this.preloadedBV = null;
246
- setTimeout(() => this._createPreloadBV(), 0);
384
+ if (type === 'BV' && usePreload && this.preloadWebContentsConfig?.url) {
385
+ const bvOptions = browserWindowOptions || {};
386
+ if (this.preloadWebContentsConfig.enableBV !== false && this.preloadedBV) {
387
+ preloadWin = this.preloadedBV;
388
+ this.preloadedBV = await this._createPreloadBV({
389
+ webPreferences: {
390
+ preload: bvOptions?.webPreferences?.preload || this.preload,
391
+ }
392
+ });
393
+ }
247
394
  }
248
395
  if (preloadWin) {
249
396
  const win = preloadWin;
250
- log('log', `${name} 使用预加载窗口`, win._id);
397
+ log('log', `${name} 使用预加载窗口(${type})`, win._id);
251
398
  win._type = 'BW';
252
399
  win._name = options.name || 'anonymous';
253
400
  win._extraData = `${options?.extraData || ''}`;
254
401
  win._initUrl = `${options?.url || ''}`;
255
402
  // @ts-ignore
256
- win?.removeAllListeners && win?.removeAllListeners?.();
257
- win.webContents.removeAllListeners && win.webContents.removeAllListeners();
403
+ // win?.removeAllListeners && win?.removeAllListeners?.();
404
+ // win.webContents.removeAllListeners && win.webContents.removeAllListeners();
258
405
  if (type === 'BW') {
259
406
  // @ts-ignore
260
407
  this._applyBrowserWindowOptions(win, options);
@@ -262,27 +409,79 @@ class WindowsManager {
262
409
  if (type === 'BV') {
263
410
  this._applyBrowserViewOptions(win, options);
264
411
  }
412
+ if (typeof this.preloadWebContentsConfig?.customLoadURL === 'function') {
413
+ try {
414
+ if (type === 'BW') {
415
+ // @ts-ignore
416
+ const originLoadURL = win.loadURL;
417
+ // @ts-ignore
418
+ win.loadURL = async (url) => {
419
+ return new Promise(async (resolve, reject) => {
420
+ try {
421
+ console.error('customLoadURL win.loadURL');
422
+ // @ts-ignore
423
+ await this.preloadWebContentsConfig.customLoadURL(url || 'about:blank', (url) => originLoadURL.call(win, url), win.webContents);
424
+ try {
425
+ win.emit('ready-to-show');
426
+ }
427
+ catch (error) {
428
+ log('error', 'emit ready-to-show event failed:', error);
429
+ }
430
+ resolve(undefined);
431
+ }
432
+ catch (error) {
433
+ reject(error);
434
+ }
435
+ });
436
+ };
437
+ }
438
+ const originWebContentsLoadURL = win.webContents.loadURL;
439
+ // @ts-ignore
440
+ win.webContents.loadURL = async (url) => {
441
+ return new Promise(async (resolve, reject) => {
442
+ try {
443
+ console.error('customLoadURL win.webContents.loadURL');
444
+ // @ts-ignore
445
+ await this.preloadWebContentsConfig.customLoadURL(url || 'about:blank', (url) => originWebContentsLoadURL.call(win.webContents, url), win.webContents);
446
+ try {
447
+ win.webContents.emit('ready-to-show');
448
+ }
449
+ catch (error) {
450
+ log('error', 'emit ready-to-show event failed:', error);
451
+ }
452
+ resolve(undefined);
453
+ }
454
+ catch (error) {
455
+ reject(error);
456
+ }
457
+ });
458
+ };
459
+ }
460
+ catch (error) {
461
+ console.error('customLoadURL error', error);
462
+ }
463
+ }
265
464
  window = win;
266
465
  }
267
466
  try {
268
- try {
269
- loadingView.url = `${loadingView?.url ?? this.loadingViewUrl}`;
270
- lodash.merge(options, {
271
- loadingView,
272
- });
273
- }
274
- catch (error) {
275
- log('error', 'loadingView error:', loadingView, this.loadingViewUrl);
276
- }
277
- try {
278
- errorView.url = `${errorView?.url ?? this.errorViewUrl}`;
279
- lodash.merge(options, {
280
- errorView,
281
- });
282
- }
283
- catch (error) {
284
- log('error', 'errorView error:', errorView, this.errorViewUrl);
285
- }
467
+ loadingView.url = `${loadingView?.url ?? this.loadingViewUrl}`;
468
+ lodash.merge(options, {
469
+ loadingView,
470
+ });
471
+ }
472
+ catch (error) {
473
+ log('error', 'loadingView error:', loadingView, this.loadingViewUrl);
474
+ }
475
+ try {
476
+ errorView.url = `${errorView?.url ?? this.errorViewUrl}`;
477
+ lodash.merge(options, {
478
+ errorView,
479
+ });
480
+ }
481
+ catch (error) {
482
+ log('error', 'errorView error:', errorView, this.errorViewUrl);
483
+ }
484
+ try {
286
485
  let parentWin = undefined;
287
486
  if (typeof browserWindowOptions?.parent === 'number') {
288
487
  parentWin = electron.BrowserWindow.fromId(browserWindowOptions?.parent) || undefined;
@@ -310,6 +509,7 @@ class WindowsManager {
310
509
  nativeWindowOpen: true,
311
510
  webSecurity: false,
312
511
  preload: preload,
512
+ defaultEncoding: 'utf-8',
313
513
  }, browserWindowOptions?.webPreferences || {})
314
514
  }))
315
515
  : new electron.BrowserWindow(lodash.merge({
@@ -326,6 +526,7 @@ class WindowsManager {
326
526
  nativeWindowOpen: true,
327
527
  webSecurity: false,
328
528
  preload: preload,
529
+ defaultEncoding: 'utf-8',
329
530
  }, browserWindowOptions?.webPreferences || {})
330
531
  }));
331
532
  log('log', `${name} 不使用 ${type === 'BV' ? 'preloadedBV' : 'preloadedBW'}`, window?.webContents?.id);
@@ -336,6 +537,8 @@ class WindowsManager {
336
537
  log('error', 'enable: ', error);
337
538
  }
338
539
  }
540
+ // 停止加载
541
+ // window.webContents?.stop?.();
339
542
  // @ts-ignore
340
543
  try {
341
544
  window.id = Number(`${window.id || window.webContents.id}`);
@@ -354,6 +557,8 @@ class WindowsManager {
354
557
  window._name = name;
355
558
  window._extraData = `${options?.extraData || ''}`;
356
559
  window._initUrl = `${options?.url || ''}`;
560
+ // 设置zIndex层级
561
+ window._zIndex = options.zIndex ?? 0;
357
562
  log('log', 'create 5: ', window.id, window._id, window._name);
358
563
  if (loadingView?.url) {
359
564
  if (type === 'BW') {
@@ -364,7 +569,7 @@ class WindowsManager {
364
569
  if (errorView?.url) {
365
570
  if (type === 'BW') {
366
571
  const showErrorView = lodash.debounce(() => {
367
- const _url = window?.webContents?.getURL?.() || url;
572
+ const _url = window._initUrl;
368
573
  /**
369
574
  * 判断是否是错误视图
370
575
  */
@@ -378,16 +583,37 @@ class WindowsManager {
378
583
  sessionStorage.setItem(key, "${_url}");
379
584
  `);
380
585
  }
381
- }, 300);
586
+ }, 1000);
382
587
  // @ts-ignore
383
588
  window.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL, isMainFrame) => {
384
589
  if (isMainFrame) {
385
590
  showErrorView();
386
591
  }
387
592
  });
593
+ // 当开始加载时取消错误视图
388
594
  window.webContents.on('did-start-loading', () => {
389
595
  showErrorView.cancel();
390
596
  });
597
+ // 当导航开始时取消错误视图
598
+ window.webContents.on('did-start-navigation', () => {
599
+ showErrorView.cancel();
600
+ });
601
+ // 当页面重新加载时取消错误视图
602
+ window.webContents.on('did-navigate', () => {
603
+ showErrorView.cancel();
604
+ });
605
+ // 当页面完成加载时取消错误视图
606
+ window.webContents.on('did-finish-load', () => {
607
+ showErrorView.cancel();
608
+ });
609
+ // 当窗口关闭时取消错误视图
610
+ window.webContents.on('close', () => {
611
+ showErrorView.cancel();
612
+ });
613
+ // 当窗口销毁时取消错误视图
614
+ window.webContents.on('destroyed', () => {
615
+ showErrorView.cancel();
616
+ });
391
617
  }
392
618
  }
393
619
  window.webContents.on('did-attach-webview', (_event, webContents) => {
@@ -469,10 +695,6 @@ class WindowsManager {
469
695
  parentWin?.addBrowserView(window);
470
696
  log('log', 'create - addBrowserView');
471
697
  }
472
- // @ts-ignore
473
- window.loadURL ? window.loadURL(options.url) : window.webContents.loadURL(options.url);
474
- // @ts-ignore
475
- window.focus ? window.focus() : window.webContents.focus();
476
698
  this.windows.set(window.id || window._id || window.webContents.id, window);
477
699
  log('log', 'create', this.windows.keys());
478
700
  // 初始化值
@@ -542,29 +764,51 @@ class WindowsManager {
542
764
  });
543
765
  try {
544
766
  const _addBrowserView = window.addBrowserView;
545
- window.addBrowserView = (view) => {
767
+ window.addBrowserView = (view, isSort = false) => {
546
768
  _addBrowserView.call(window, view);
547
769
  handleBrowserViewFocus(view);
770
+ // 添加BrowserView后重新排序(如果未禁用自动排序)
771
+ log('log', 'addBrowserView-sort', isSort, window.getBrowserViews());
772
+ if (isSort) {
773
+ this.sortBrowserViewsDebounced(window, view);
774
+ }
548
775
  };
549
776
  const _removeBrowserView = window.removeBrowserView;
550
- window.removeBrowserView = (view) => {
777
+ window.removeBrowserView = (view, isSort = false) => {
551
778
  _removeBrowserView.call(window, view);
552
779
  handleBrowserViewBlur(view);
780
+ // 移除BrowserView后重新排序(如果未禁用自动排序)
781
+ log('log', 'removeBrowserView-sort', isSort);
782
+ if (isSort) {
783
+ this.sortBrowserViewsDebounced(window, view);
784
+ }
553
785
  };
554
786
  const _setBrowserView = window.setBrowserView;
555
- window.setBrowserView = (view) => {
787
+ window.setBrowserView = (view, isSort = false) => {
556
788
  const views = window.getBrowserViews() || [];
557
789
  for (const view of views) {
558
790
  handleBrowserViewBlur(view);
559
791
  }
560
792
  _setBrowserView.call(window, view);
561
793
  handleBrowserViewFocus(view);
794
+ log('log', 'setBrowserView-sort', isSort);
795
+ if (isSort) {
796
+ this.sortBrowserViewsDebounced(window, view);
797
+ }
562
798
  };
563
799
  }
564
800
  catch (error) {
565
801
  log('error', 'focus', error);
566
802
  }
567
803
  }
804
+ if (options.url) {
805
+ // @ts-ignore
806
+ window.loadURL ? window.loadURL(options.url) : window.webContents.loadURL(options.url);
807
+ if (options.browserWindow?.focusable !== false) {
808
+ window?.focus?.();
809
+ }
810
+ window?.webContents?.focus?.();
811
+ }
568
812
  }
569
813
  catch (error) {
570
814
  log('error', 'create', error);
@@ -588,7 +832,7 @@ class WindowsManager {
588
832
  }
589
833
  const loadLoadingView = () => {
590
834
  const [viewWidth, viewHeight] = window.getSize();
591
- window.setBrowserView(_loadingView);
835
+ window.addBrowserView(_loadingView);
592
836
  _loadingView.setBounds({
593
837
  x: 0,
594
838
  y: 0,
@@ -628,7 +872,7 @@ class WindowsManager {
628
872
  return;
629
873
  }
630
874
  if (_loadingView.webContents && !_loadingView.webContents.isDestroyed()) {
631
- window.setBrowserView(_loadingView);
875
+ window.addBrowserView(_loadingView);
632
876
  }
633
877
  else {
634
878
  // if loadingView has been destroyed
@@ -828,6 +1072,86 @@ class WindowsManager {
828
1072
  if (typeof browserWindowOptions.alwaysOnTop === 'boolean') {
829
1073
  win.setAlwaysOnTop(browserWindowOptions.alwaysOnTop);
830
1074
  }
1075
+ // 设置背景颜色
1076
+ if (typeof browserWindowOptions.backgroundColor === 'string') {
1077
+ win.setBackgroundColor(browserWindowOptions.backgroundColor);
1078
+ }
1079
+ // 居中
1080
+ if (browserWindowOptions?.center !== false) {
1081
+ win.center();
1082
+ }
1083
+ // 设置窗口移动
1084
+ if (typeof browserWindowOptions.movable === 'boolean') {
1085
+ win.setMovable(browserWindowOptions.movable);
1086
+ }
1087
+ // 设置窗口大小调整
1088
+ if (typeof browserWindowOptions.resizable === 'boolean') {
1089
+ win.setResizable(browserWindowOptions.resizable);
1090
+ }
1091
+ // 设置全屏模式
1092
+ if (typeof browserWindowOptions.fullscreenable === 'boolean') {
1093
+ win.setFullScreenable(browserWindowOptions.fullscreenable);
1094
+ }
1095
+ // 设置窗口阴影
1096
+ if (typeof browserWindowOptions.hasShadow === 'boolean') {
1097
+ win.setHasShadow(browserWindowOptions.hasShadow);
1098
+ }
1099
+ // 设置窗口最小尺寸
1100
+ if (typeof browserWindowOptions.minWidth === 'number' && typeof browserWindowOptions.minHeight === 'number') {
1101
+ win.setMinimumSize(browserWindowOptions.minWidth, browserWindowOptions.minHeight);
1102
+ }
1103
+ // 设置窗口最大尺寸
1104
+ if (typeof browserWindowOptions.maxWidth === 'number' && typeof browserWindowOptions.maxHeight === 'number') {
1105
+ win.setMaximumSize(browserWindowOptions.maxWidth, browserWindowOptions.maxHeight);
1106
+ }
1107
+ // 设置窗口位置
1108
+ if (typeof browserWindowOptions.x === 'number' && typeof browserWindowOptions.y === 'number') {
1109
+ win.setPosition(browserWindowOptions.x, browserWindowOptions.y);
1110
+ }
1111
+ // 设置窗口标题
1112
+ if (typeof browserWindowOptions.title === 'string') {
1113
+ win.setTitle(browserWindowOptions.title);
1114
+ }
1115
+ // 设置窗口图标
1116
+ if (typeof browserWindowOptions.icon === 'string') {
1117
+ win.setIcon(browserWindowOptions.icon);
1118
+ }
1119
+ // 设置窗口菜单栏可见性
1120
+ if (typeof browserWindowOptions.autoHideMenuBar === 'boolean') {
1121
+ win.setAutoHideMenuBar(browserWindowOptions.autoHideMenuBar);
1122
+ }
1123
+ // 设置窗口最小化按钮
1124
+ if (browserWindowOptions.minimizable === false) {
1125
+ win.setMinimizable(false);
1126
+ }
1127
+ // 设置窗口最大化按钮
1128
+ if (browserWindowOptions.maximizable === false) {
1129
+ win.setMaximizable(false);
1130
+ }
1131
+ // 设置窗口关闭按钮
1132
+ if (browserWindowOptions.closable === false) {
1133
+ win.setClosable(false);
1134
+ }
1135
+ // 设置窗口焦点
1136
+ if (browserWindowOptions.focusable === false) {
1137
+ win.setFocusable(false);
1138
+ }
1139
+ // 设置窗口全屏
1140
+ if (browserWindowOptions.fullscreen === true) {
1141
+ win.setFullScreen(true);
1142
+ }
1143
+ // 设置窗口背景材质
1144
+ if (typeof browserWindowOptions.vibrancy === 'string') {
1145
+ win.setVibrancy(browserWindowOptions.vibrancy);
1146
+ }
1147
+ // 设置窗口透明度
1148
+ if (typeof browserWindowOptions.opacity === 'number') {
1149
+ win.setOpacity(browserWindowOptions.opacity);
1150
+ }
1151
+ // 设置窗口显示状态
1152
+ if (browserWindowOptions.show === false) {
1153
+ win.hide();
1154
+ }
831
1155
  // 可继续扩展其他动态属性
832
1156
  }
833
1157
  _applyBrowserViewOptions(view, options) {
@@ -843,12 +1167,26 @@ class WindowsManager {
843
1167
  if (typeof browserWindowOptions.width === 'number' && typeof browserWindowOptions.height === 'number') {
844
1168
  view.setBounds({ x: 0, y: 0, width: browserWindowOptions.width, height: browserWindowOptions.height });
845
1169
  }
1170
+ // 设置视图位置
1171
+ if (typeof browserWindowOptions.x === 'number' && typeof browserWindowOptions.y === 'number') {
1172
+ const bounds = view.getBounds();
1173
+ view.setBounds({
1174
+ x: browserWindowOptions.x,
1175
+ y: browserWindowOptions.y,
1176
+ width: bounds.width,
1177
+ height: bounds.height
1178
+ });
1179
+ }
1180
+ // 设置视图背景颜色
1181
+ if (typeof browserWindowOptions.backgroundColor === 'string') {
1182
+ view.setBackgroundColor(browserWindowOptions.backgroundColor);
1183
+ }
846
1184
  // 可继续扩展其他动态属性
847
1185
  }
848
1186
  // 生成一个bv 做为预加载资源窗口,加载完成后销毁
849
1187
  async createPreloadWebContents(url) {
850
1188
  return new Promise(async (resolve, reject) => {
851
- let bv = this.create({
1189
+ let bv = await this.create({
852
1190
  type: 'BV',
853
1191
  url,
854
1192
  name: `preload-web-contents-${md5(url)}`,
@@ -867,11 +1205,108 @@ class WindowsManager {
867
1205
  bv.webContents.loadURL(url);
868
1206
  });
869
1207
  }
1208
+ async getWindowForWebContentsId(wcId) {
1209
+ const wc = electron.webContents.fromId(wcId);
1210
+ if (!wc)
1211
+ return undefined;
1212
+ // Case 1: BrowserView
1213
+ for (const win of electron.BrowserWindow.getAllWindows()) {
1214
+ for (const view of win.getBrowserViews()) {
1215
+ if (view.webContents.id === wcId) {
1216
+ return win;
1217
+ }
1218
+ }
1219
+ }
1220
+ // Case 2: WebView
1221
+ // webview 有 hostWebContents,指向它所在的 BrowserWindow 的 webContents
1222
+ if (wc.hostWebContents) {
1223
+ return electron.BrowserWindow.fromWebContents(wc.hostWebContents);
1224
+ }
1225
+ // Case 3: 普通 window 本身
1226
+ const win = electron.BrowserWindow.fromWebContents(wc);
1227
+ if (win)
1228
+ return win;
1229
+ return undefined;
1230
+ }
1231
+ /**
1232
+ * 手动对BrowserView进行排序
1233
+ * @param windowId 窗口ID或名称
1234
+ */
1235
+ sortBrowserViews(windowId) {
1236
+ const window = this.get(windowId);
1237
+ if (window && window._type === 'BW') {
1238
+ this._sortBrowserViews(window);
1239
+ }
1240
+ }
1241
+ /**
1242
+ * 对BrowserView进行排序
1243
+ * @param window 目标窗口
1244
+ */
1245
+ _sortBrowserViews(window, addView) {
1246
+ try {
1247
+ const views = window.getBrowserViews() || [];
1248
+ if (views.length <= 1)
1249
+ return;
1250
+ log('log', 'sortBrowserViews', views?.map(i => i.webContents.id));
1251
+ // 按zIndex层级排序,数值小的在前
1252
+ const sortedViews = views.sort((a, b) => {
1253
+ const zIndexA = a._zIndex ?? 0;
1254
+ const zIndexB = b._zIndex ?? 0;
1255
+ return zIndexA - zIndexB;
1256
+ });
1257
+ // 检查是否已经按正确顺序排列
1258
+ let needsReorder = false;
1259
+ for (let i = 0; i < views.length; i++) {
1260
+ // @ts-ignore
1261
+ if (views[i].webContents.id !== sortedViews[i].webContents.id) {
1262
+ needsReorder = true;
1263
+ break;
1264
+ }
1265
+ }
1266
+ log('log', 'sortBrowserViews needsReorder', needsReorder, sortedViews?.map(i => i.webContents.id));
1267
+ // 如果已经按正确顺序排列,则不需要重新排序
1268
+ if (!needsReorder)
1269
+ return;
1270
+ // 使用批量操作来减少闪烁
1271
+ // 临时隐藏窗口内容
1272
+ const wasVisible = window.isVisible();
1273
+ if (wasVisible) {
1274
+ // window.hide();
1275
+ }
1276
+ // 移除所有BrowserView
1277
+ views.forEach(view => {
1278
+ if (addView?.webContents?.id !== view.webContents.id) {
1279
+ // @ts-ignore
1280
+ window.removeBrowserView(view, false);
1281
+ }
1282
+ });
1283
+ // 按正确顺序重新添加
1284
+ sortedViews.forEach((view, index) => {
1285
+ if (index === 0) {
1286
+ // 第一个设置为当前视图
1287
+ // @ts-ignore
1288
+ window.setBrowserView(view, false);
1289
+ }
1290
+ else {
1291
+ // 其他视图添加到后面
1292
+ // @ts-ignore
1293
+ window.addBrowserView(view, false);
1294
+ }
1295
+ });
1296
+ // 恢复窗口显示
1297
+ if (wasVisible) {
1298
+ // window.show();
1299
+ }
1300
+ }
1301
+ catch (error) {
1302
+ log('error', 'sortBrowserViews error:', error);
1303
+ }
1304
+ }
870
1305
  }
871
1306
  // @ts-ignore
872
1307
  global['__ELECTRON_WINDOWS_MANAGER__'] = undefined;
873
1308
  exports.isInitialized = false;
874
- const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl, webviewDomainWhiteList) => {
1309
+ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList) => {
875
1310
  // @ts-ignore
876
1311
  if (exports.isInitialized && global['__ELECTRON_WINDOWS_MANAGER__']) {
877
1312
  // @ts-ignore
@@ -879,7 +1314,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
879
1314
  }
880
1315
  exports.isInitialized = true;
881
1316
  // @ts-ignore
882
- const wm = global['__ELECTRON_WINDOWS_MANAGER__'] = new WindowsManager(preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl, webviewDomainWhiteList);
1317
+ const wm = global['__ELECTRON_WINDOWS_MANAGER__'] = new WindowsManager(preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList);
883
1318
  eIpc.mainIPC.handleRenderer('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', async (data) => {
884
1319
  if (data?.type === 'create') {
885
1320
  const opt = data;
@@ -895,21 +1330,39 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
895
1330
  findWin.webContents.reload();
896
1331
  }, 100);
897
1332
  }
1333
+ if (opt.data.browserWindow?.parent) {
1334
+ try {
1335
+ if (findWin._type === 'BW') {
1336
+ findWin.setParentWindow(electron.BrowserWindow.fromId(Number(opt.data.browserWindow.parent)));
1337
+ }
1338
+ if (findWin._type === 'BV') {
1339
+ electron.BrowserWindow.fromId(Number(opt.data.browserWindow.parent))?.addBrowserView(findWin);
1340
+ }
1341
+ }
1342
+ catch (error) {
1343
+ log('error', 'setParentWindow error:', error);
1344
+ }
1345
+ }
1346
+ if (findWin?._type === 'BV' && opt.data.zIndex) {
1347
+ findWin._zIndex = opt.data.zIndex;
1348
+ }
898
1349
  return {
899
1350
  winId: Number(`${findWin?.id || findWin?._id || -1}`),
900
1351
  winName: `${findWin?._name || ''}`,
901
1352
  winType: `${findWin?._type || ''}`,
902
1353
  winExtraData: `${findWin?._extraData || ''}`,
903
1354
  winInitUrl: `${findWin?._initUrl || ''}`,
1355
+ winZIndex: `${findWin._zIndex || 0}`,
904
1356
  };
905
1357
  }
906
- const res = wm.create(opt.data);
1358
+ const res = await wm.create(opt.data);
907
1359
  return {
908
1360
  winId: Number(`${res.id || res._id || -1}`),
909
1361
  winName: `${res?._name || ''}`,
910
1362
  winType: `${res?._type || ''}`,
911
1363
  winExtraData: `${res?._extraData || ''}`,
912
1364
  winInitUrl: `${res?._initUrl || ''}`,
1365
+ winZIndex: `${res?._zIndex || 0}`,
913
1366
  };
914
1367
  }
915
1368
  if (data?.type === 'get') {
@@ -921,6 +1374,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
921
1374
  winType: `${res?._type || ''}`,
922
1375
  winExtraData: `${res?._extraData || ''}`,
923
1376
  winInitUrl: `${res?._initUrl || ''}`,
1377
+ winZIndex: `${res?._zIndex || 0}`,
924
1378
  };
925
1379
  }
926
1380
  if (data?.type === 'getAll') {
@@ -934,6 +1388,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
934
1388
  winType: `${i?._type || ''}`,
935
1389
  winExtraData: `${i?._extraData || ''}`,
936
1390
  winInitUrl: `${i?._initUrl || ''}`,
1391
+ winZIndex: `${i?._zIndex || 0}`,
937
1392
  };
938
1393
  });
939
1394
  return obj;
@@ -957,6 +1412,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
957
1412
  winType: `${res?._type || ''}`,
958
1413
  winExtraData: `${res?._extraData || ''}`,
959
1414
  winInitUrl: `${res?._initUrl || ''}`,
1415
+ winZIndex: `${res?._zIndex || 0}`,
960
1416
  };
961
1417
  }
962
1418
  return undefined;
@@ -971,34 +1427,16 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
971
1427
  winType: `${res?._type || ''}`,
972
1428
  winExtraData: `${res?._extraData || ''}`,
973
1429
  winInitUrl: `${res?._initUrl || ''}`,
1430
+ winZIndex: `${res?._zIndex || 0}`,
974
1431
  };
975
1432
  }
976
1433
  return undefined;
977
1434
  }
978
- if (data?.type === 'getWindowForWebContentId') {
1435
+ if (data?.type === 'getWindowForWebContentsId') {
979
1436
  const opt = data;
980
- const targetWebContents = electron.webContents.fromId(opt.data);
981
- if (targetWebContents) {
982
- let win = electron.BrowserWindow.fromWebContents(targetWebContents);
983
- if (!win) {
984
- // 获取所有的 BrowserWindows
985
- let allWindows = electron.BrowserWindow.getAllWindows();
986
- // 遍历所有窗口,检查每个窗口的 BrowserView
987
- for (let _win of allWindows) {
988
- let views = _win.getBrowserViews();
989
- // 遍历窗口的所有 BrowserView
990
- for (let view of views) {
991
- if (view.webContents === targetWebContents) {
992
- win = _win;
993
- break;
994
- }
995
- }
996
- if (win)
997
- break;
998
- }
999
- }
1000
- // @ts-ignore
1001
- return win?.id || win?._id;
1437
+ const res = await wm.getWindowForWebContentsId(opt.data);
1438
+ if (res) {
1439
+ return res?.id;
1002
1440
  }
1003
1441
  return undefined;
1004
1442
  }
@@ -1039,15 +1477,20 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
1039
1477
  return undefined;
1040
1478
  }
1041
1479
  // 是否开启预加载窗口
1042
- if (data?.type === 'setPreloadWebContentsUrl') {
1480
+ if (data?.type === 'setPreloadWebContentsConfig') {
1043
1481
  const opt = data;
1044
- wm.setPreloadWebContentsUrl(opt.data);
1482
+ wm.setPreloadWebContentsConfig(opt.data);
1045
1483
  }
1046
1484
  if (data?.type === 'createPreloadWebContents') {
1047
1485
  const opt = data;
1048
1486
  const res = await wm.createPreloadWebContents(opt.data);
1049
1487
  return res;
1050
1488
  }
1489
+ if (data?.type === 'sortBrowserViews') {
1490
+ const opt = data;
1491
+ wm.sortBrowserViews(opt.data);
1492
+ return true;
1493
+ }
1051
1494
  return undefined;
1052
1495
  });
1053
1496
  return wm;