@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/esm/main/index.js CHANGED
@@ -56,30 +56,50 @@ class WindowsManager {
56
56
  * - 不带点的(如 example.com)只匹配主域名。
57
57
  * - 'localhost'、'127.0.0.1'、'::1' 以及局域网 IP(如 192.168.x.x、10.x.x.x、172.16.x.x~172.31.x.x)都视为本地白名单。
58
58
  */
59
- constructor(preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl, webviewDomainWhiteList) {
59
+ constructor(preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList) {
60
+ // 预加载的窗口
60
61
  this.preloadedBW = null;
62
+ // 预加载的窗口(无边框,有按钮)
61
63
  this.preloadedBW_FramelessWithButtons = null;
64
+ // 预加载的窗口(无边框,无按钮)
62
65
  this.preloadedBW_FramelessNoButtons = null;
66
+ // 预加载的浏览器视图
63
67
  this.preloadedBV = null;
64
68
  this.preloading = false;
65
69
  this.webviewDomainWhiteList = [];
70
+ // 创建队列相关属性
71
+ this.createQueue = [];
72
+ this.isCreating = false;
73
+ /**
74
+ * 防抖的排序方法
75
+ * @param window 目标窗口
76
+ */
77
+ this.sortBrowserViewsDebounced = lodash.debounce((window, view) => {
78
+ this._sortBrowserViews(window, view);
79
+ }, 50);
66
80
  this.preload = preload;
67
81
  this.windows = new Map();
68
82
  this.loadingViewUrl = `${loadingViewUrl ?? ''}`;
69
83
  this.errorViewUrl = `${errorViewUrl ?? ''}`;
70
- this.preloadWebContentsUrl = `${preloadWebContentsUrl ?? ''}`;
84
+ this.preloadWebContentsConfig = preloadWebContentsConfig;
71
85
  this.webviewDomainWhiteList = webviewDomainWhiteList || [];
72
- log('log', 'preloadWebContentsUrl: ', this.preloadWebContentsUrl);
73
- if (this.preloadWebContentsUrl) {
74
- app.on('ready', () => {
75
- this._preloadInstances();
86
+ log('log', 'preloadWebContentsConfig: ', this.preloadWebContentsConfig);
87
+ if (this.preloadWebContentsConfig) {
88
+ app.whenReady().then(() => {
89
+ if (this.preloadWebContentsConfig) {
90
+ this.setPreloadWebContentsConfig(this.preloadWebContentsConfig);
91
+ }
76
92
  });
77
93
  }
78
94
  }
79
- setPreloadWebContentsUrl(preloadWebContentsUrl) {
95
+ /**
96
+ * 设置预加载的webContents配置
97
+ * @param preloadWebContentsConfig 预加载的webContents配置
98
+ */
99
+ setPreloadWebContentsConfig(preloadWebContentsConfig) {
80
100
  try {
81
- this.preloadWebContentsUrl = preloadWebContentsUrl;
82
- if (this.preloadWebContentsUrl) {
101
+ this.preloadWebContentsConfig = preloadWebContentsConfig;
102
+ if (this.preloadWebContentsConfig) {
83
103
  this._preloadInstances();
84
104
  }
85
105
  else {
@@ -90,27 +110,65 @@ class WindowsManager {
90
110
  }
91
111
  }
92
112
  catch (error) {
93
- log('error', 'setPreloadWebContentsUrl error:', error);
113
+ log('error', 'setPreloadWebContentsConfig error:', error);
94
114
  }
95
115
  }
116
+ /**
117
+ * 预加载实例
118
+ */
96
119
  async _preloadInstances() {
97
120
  if (this.preloading)
98
121
  return;
99
122
  this.preloading = true;
100
123
  try {
101
- if (this.preloadWebContentsUrl) {
102
- this.preloadedBW = this.preloadedBW || await this._createPreloadBW({});
103
- this.preloadedBW_FramelessWithButtons = this.preloadedBW_FramelessWithButtons || await this._createPreloadBW({
104
- frame: false,
105
- transparent: true,
106
- titleBarStyle: 'hidden',
107
- });
108
- this.preloadedBW_FramelessNoButtons = this.preloadedBW_FramelessNoButtons || await this._createPreloadBW({
109
- frame: false,
110
- transparent: true,
111
- titleBarStyle: 'customButtonsOnHover',
112
- });
113
- this.preloadedBV = this.preloadedBV || await this._createPreloadBV();
124
+ if (this.preloadWebContentsConfig) {
125
+ log('log', 'preloadWebContentsConfig: ', this.preloadWebContentsConfig);
126
+ // 根据配置决定是否预加载普通窗口
127
+ if (this.preloadWebContentsConfig.enableBW !== false) {
128
+ if (!this.preloadedBW) {
129
+ this._createPreloadBW({}).then(i => {
130
+ this.preloadedBW = i;
131
+ log('log', 'init preloadedBW: ', !!this.preloadedBW);
132
+ });
133
+ }
134
+ }
135
+ // 根据配置决定是否预加载无边框有按钮的窗口
136
+ if (this.preloadWebContentsConfig.enableBW_FramelessWithButtons !== false) {
137
+ if (!this.preloadedBW_FramelessWithButtons) {
138
+ this._createPreloadBW({
139
+ frame: false,
140
+ // transparent: true,
141
+ autoHideMenuBar: true,
142
+ titleBarStyle: 'hidden',
143
+ }).then(i => {
144
+ this.preloadedBW_FramelessWithButtons = i;
145
+ log('log', 'init preloadedBW_FramelessWithButtons: ', !!this.preloadedBW_FramelessWithButtons);
146
+ });
147
+ }
148
+ }
149
+ // 根据配置决定是否预加载无边框无按钮的窗口
150
+ if (this.preloadWebContentsConfig.enableBW_FramelessNoButtons !== false) {
151
+ if (!this.preloadedBW_FramelessNoButtons) {
152
+ this._createPreloadBW({
153
+ frame: false,
154
+ // transparent: true,
155
+ autoHideMenuBar: true,
156
+ titleBarStyle: 'default',
157
+ }).then(i => {
158
+ this.preloadedBW_FramelessNoButtons = i;
159
+ log('log', 'init preloadedBW_FramelessNoButtons: ', !!this.preloadedBW_FramelessNoButtons);
160
+ });
161
+ }
162
+ }
163
+ // 根据配置决定是否预加载浏览器视图
164
+ if (this.preloadWebContentsConfig.enableBV !== false) {
165
+ if (!this.preloadedBV) {
166
+ this._createPreloadBV().then(i => {
167
+ this.preloadedBV = i;
168
+ log('log', 'init preloadedBV: ', !!this.preloadedBV);
169
+ });
170
+ }
171
+ }
114
172
  }
115
173
  }
116
174
  catch (e) {
@@ -118,23 +176,32 @@ class WindowsManager {
118
176
  }
119
177
  this.preloading = false;
120
178
  }
179
+ /**
180
+ * 创建预加载的窗口
181
+ * @param options 窗口选项
182
+ * @returns 预加载的窗口
183
+ */
121
184
  _createPreloadBW(options = {}) {
122
185
  return new Promise((resolve) => {
123
186
  const preload = this.preload;
124
- const url = this.preloadWebContentsUrl;
125
- if (this.preloadWebContentsUrl) {
187
+ const url = this.preloadWebContentsConfig?.url;
188
+ if (this.preloadWebContentsConfig?.url) {
189
+ const webPreferences = (options.webPreferences || {});
126
190
  const instance = new BrowserWindow({
191
+ useContentSize: true,
127
192
  show: false,
193
+ backgroundColor: '#ffffff',
128
194
  ...options,
129
195
  webPreferences: {
196
+ ...webPreferences,
130
197
  webviewTag: true,
131
198
  plugins: true,
132
199
  nodeIntegration: true,
133
200
  contextIsolation: false,
134
201
  backgroundThrottling: false,
135
202
  webSecurity: false,
136
- preload: preload,
137
- ...(options.webPreferences || {}),
203
+ preload: webPreferences.preload || preload,
204
+ defaultEncoding: 'utf-8',
138
205
  }
139
206
  });
140
207
  try {
@@ -151,31 +218,42 @@ class WindowsManager {
151
218
  log('error', '预加载 BW 设置 _id 失败', error);
152
219
  }
153
220
  // @ts-ignore
154
- log('log', '创建预BW: ', instance._id, this.preloadWebContentsUrl);
155
- instance.webContents.once('did-finish-load', () => {
156
- resolve(instance);
157
- });
158
- instance.webContents.once('did-fail-load', () => {
159
- resolve(instance);
160
- });
161
- instance.loadURL(url || 'about:blank');
221
+ log('log', '创建预BW: ', instance._id, this.preloadWebContentsConfig?.url);
222
+ // instance.webContents.once('did-finish-load', () => {
223
+ // resolve(instance as BWItem);
224
+ // });
225
+ // instance.webContents.once('did-fail-load', () => {
226
+ // resolve(instance as BWItem);
227
+ // });
228
+ instance.loadURL(url ? `${url}` : 'about:blank');
229
+ resolve(instance);
230
+ }
231
+ else {
232
+ resolve(null);
162
233
  }
163
234
  });
164
235
  }
165
- _createPreloadBV() {
236
+ /**
237
+ * 创建预加载的浏览器视图
238
+ * @returns 预加载的浏览器视图
239
+ */
240
+ _createPreloadBV(options = {}) {
166
241
  return new Promise((resolve) => {
167
242
  const preload = this.preload;
168
- const url = this.preloadWebContentsUrl;
169
- if (this.preloadWebContentsUrl) {
243
+ const url = this.preloadWebContentsConfig?.url;
244
+ if (this.preloadWebContentsConfig?.url) {
245
+ const webPreferences = (options.webPreferences || {});
170
246
  const instance = new BrowserView({
171
247
  webPreferences: {
248
+ ...webPreferences,
172
249
  webviewTag: true,
173
250
  plugins: true,
174
251
  nodeIntegration: true,
175
252
  contextIsolation: false,
176
253
  // backgroundThrottling: false,
177
254
  webSecurity: false,
178
- preload: preload,
255
+ preload: webPreferences.preload || preload,
256
+ defaultEncoding: 'utf-8',
179
257
  }
180
258
  });
181
259
  try {
@@ -187,61 +265,130 @@ class WindowsManager {
187
265
  try {
188
266
  // @ts-ignore
189
267
  instance._id = instance.webContents.id;
268
+ // 设置默认zIndex层级
269
+ instance._zIndex = 0;
190
270
  }
191
271
  catch (error) {
192
272
  log('error', '预加载 BV 设置 _id 失败', error);
193
273
  }
194
274
  // @ts-ignore
195
- log('log', '创建预BV: ', instance._id, this.preloadWebContentsUrl);
196
- instance.webContents.once('did-finish-load', () => {
197
- resolve(instance);
198
- });
199
- instance.webContents.once('did-fail-load', () => {
200
- resolve(instance);
201
- });
275
+ log('log', '创建预BV: ', instance._id, this.preloadWebContentsConfig?.url);
276
+ // instance.webContents.once('did-finish-load', () => {
277
+ // resolve(instance as BVItem);
278
+ // });
279
+ // instance.webContents.once('did-fail-load', () => {
280
+ // resolve(instance as BVItem);
281
+ // });
202
282
  instance.webContents.loadURL(url || 'about:blank');
283
+ resolve(instance);
284
+ }
285
+ else {
286
+ resolve(null);
203
287
  }
204
288
  });
205
289
  }
206
290
  create(options) {
291
+ return new Promise((resolve, reject) => {
292
+ // 将创建请求添加到队列
293
+ this.createQueue.push({ options, resolve, reject });
294
+ // 如果当前没有在创建,则开始处理队列
295
+ if (!this.isCreating) {
296
+ this.processCreateQueue();
297
+ }
298
+ });
299
+ }
300
+ /**
301
+ * 处理创建队列
302
+ */
303
+ async processCreateQueue() {
304
+ if (this.isCreating || this.createQueue.length === 0) {
305
+ return;
306
+ }
307
+ this.isCreating = true;
308
+ while (this.createQueue.length > 0) {
309
+ const { options, resolve, reject } = this.createQueue.shift();
310
+ try {
311
+ const window = await this._createWindow(options);
312
+ resolve(window);
313
+ }
314
+ catch (error) {
315
+ log('error', 'create window failed:', error);
316
+ reject(error);
317
+ }
318
+ }
319
+ this.isCreating = false;
320
+ }
321
+ /**
322
+ * 实际的窗口创建逻辑
323
+ */
324
+ async _createWindow(options) {
207
325
  let window;
208
- const { usePreload = true, type = 'BW', name = 'anonymous', url, loadingView = { url: undefined }, errorView = { url: undefined }, browserWindow: browserWindowOptions, openDevTools = false, preventOriginClose = false, } = options;
326
+ const { usePreload = true, type = 'BW', name = 'anonymous', url, loadingView = { url: undefined }, errorView = { url: undefined }, browserWindow: browserWindowOptions, openDevTools = false, preventOriginClose = false, zIndex = 0, } = options;
209
327
  options.type = type;
210
328
  // 优先复用预创建实例
211
329
  let preloadWin = null;
212
- if (type === 'BW' && usePreload && this.preloadWebContentsUrl) {
330
+ if (type === 'BW' && usePreload && this.preloadWebContentsConfig?.url) {
213
331
  const bwOptions = browserWindowOptions || {};
214
- if (bwOptions.frame === false && bwOptions.titleBarStyle === 'hidden') {
215
- preloadWin = this.preloadedBW_FramelessWithButtons;
216
- this.preloadedBW_FramelessWithButtons = null;
217
- setTimeout(() => this._createPreloadBW({ frame: false, transparent: true, titleBarStyle: 'hidden' }), 0);
218
- }
219
- else if (bwOptions.frame === false && bwOptions.titleBarStyle === 'customButtonsOnHover') {
220
- preloadWin = this.preloadedBW_FramelessNoButtons;
221
- this.preloadedBW_FramelessNoButtons = null;
222
- setTimeout(() => this._createPreloadBW({ frame: false, transparent: true, titleBarStyle: 'customButtonsOnHover' }), 0);
332
+ if (bwOptions.frame === false) {
333
+ if (bwOptions.titleBarStyle === 'default' || !bwOptions.titleBarStyle) {
334
+ if (this.preloadWebContentsConfig.enableBW_FramelessNoButtons !== false && this.preloadedBW_FramelessNoButtons) {
335
+ preloadWin = this.preloadedBW_FramelessNoButtons;
336
+ this.preloadedBW_FramelessNoButtons = await this._createPreloadBW({
337
+ frame: false,
338
+ // transparent: true,
339
+ titleBarStyle: 'default',
340
+ webPreferences: {
341
+ preload: bwOptions?.webPreferences?.preload || this.preload,
342
+ }
343
+ });
344
+ }
345
+ }
346
+ else {
347
+ if (this.preloadWebContentsConfig.enableBW_FramelessWithButtons !== false && this.preloadedBW_FramelessWithButtons) {
348
+ preloadWin = this.preloadedBW_FramelessWithButtons;
349
+ this.preloadedBW_FramelessWithButtons = await this._createPreloadBW({
350
+ frame: false,
351
+ // transparent: true,
352
+ titleBarStyle: 'hidden',
353
+ webPreferences: {
354
+ preload: this.preload,
355
+ }
356
+ });
357
+ }
358
+ }
223
359
  }
224
360
  else {
225
- preloadWin = this.preloadedBW;
226
- this.preloadedBW = null;
227
- setTimeout(() => this._createPreloadBW({}), 0);
361
+ if (this.preloadWebContentsConfig.enableBW !== false && this.preloadedBW) {
362
+ preloadWin = this.preloadedBW;
363
+ this.preloadedBW = await this._createPreloadBW({
364
+ webPreferences: {
365
+ preload: bwOptions?.webPreferences?.preload || this.preload,
366
+ }
367
+ });
368
+ }
228
369
  }
229
370
  }
230
- if (type === 'BV' && usePreload && this.preloadWebContentsUrl) {
231
- preloadWin = this.preloadedBV;
232
- this.preloadedBV = null;
233
- setTimeout(() => this._createPreloadBV(), 0);
371
+ if (type === 'BV' && usePreload && this.preloadWebContentsConfig?.url) {
372
+ const bvOptions = browserWindowOptions || {};
373
+ if (this.preloadWebContentsConfig.enableBV !== false && this.preloadedBV) {
374
+ preloadWin = this.preloadedBV;
375
+ this.preloadedBV = await this._createPreloadBV({
376
+ webPreferences: {
377
+ preload: bvOptions?.webPreferences?.preload || this.preload,
378
+ }
379
+ });
380
+ }
234
381
  }
235
382
  if (preloadWin) {
236
383
  const win = preloadWin;
237
- log('log', `${name} 使用预加载窗口`, win._id);
384
+ log('log', `${name} 使用预加载窗口(${type})`, win._id);
238
385
  win._type = 'BW';
239
386
  win._name = options.name || 'anonymous';
240
387
  win._extraData = `${options?.extraData || ''}`;
241
388
  win._initUrl = `${options?.url || ''}`;
242
389
  // @ts-ignore
243
- win?.removeAllListeners && win?.removeAllListeners?.();
244
- win.webContents.removeAllListeners && win.webContents.removeAllListeners();
390
+ // win?.removeAllListeners && win?.removeAllListeners?.();
391
+ // win.webContents.removeAllListeners && win.webContents.removeAllListeners();
245
392
  if (type === 'BW') {
246
393
  // @ts-ignore
247
394
  this._applyBrowserWindowOptions(win, options);
@@ -249,27 +396,79 @@ class WindowsManager {
249
396
  if (type === 'BV') {
250
397
  this._applyBrowserViewOptions(win, options);
251
398
  }
399
+ if (typeof this.preloadWebContentsConfig?.customLoadURL === 'function') {
400
+ try {
401
+ if (type === 'BW') {
402
+ // @ts-ignore
403
+ const originLoadURL = win.loadURL;
404
+ // @ts-ignore
405
+ win.loadURL = async (url) => {
406
+ return new Promise(async (resolve, reject) => {
407
+ try {
408
+ console.error('customLoadURL win.loadURL');
409
+ // @ts-ignore
410
+ await this.preloadWebContentsConfig.customLoadURL(url || 'about:blank', (url) => originLoadURL.call(win, url), win.webContents);
411
+ try {
412
+ win.emit('ready-to-show');
413
+ }
414
+ catch (error) {
415
+ log('error', 'emit ready-to-show event failed:', error);
416
+ }
417
+ resolve(undefined);
418
+ }
419
+ catch (error) {
420
+ reject(error);
421
+ }
422
+ });
423
+ };
424
+ }
425
+ const originWebContentsLoadURL = win.webContents.loadURL;
426
+ // @ts-ignore
427
+ win.webContents.loadURL = async (url) => {
428
+ return new Promise(async (resolve, reject) => {
429
+ try {
430
+ console.error('customLoadURL win.webContents.loadURL');
431
+ // @ts-ignore
432
+ await this.preloadWebContentsConfig.customLoadURL(url || 'about:blank', (url) => originWebContentsLoadURL.call(win.webContents, url), win.webContents);
433
+ try {
434
+ win.webContents.emit('ready-to-show');
435
+ }
436
+ catch (error) {
437
+ log('error', 'emit ready-to-show event failed:', error);
438
+ }
439
+ resolve(undefined);
440
+ }
441
+ catch (error) {
442
+ reject(error);
443
+ }
444
+ });
445
+ };
446
+ }
447
+ catch (error) {
448
+ console.error('customLoadURL error', error);
449
+ }
450
+ }
252
451
  window = win;
253
452
  }
254
453
  try {
255
- try {
256
- loadingView.url = `${loadingView?.url ?? this.loadingViewUrl}`;
257
- lodash.merge(options, {
258
- loadingView,
259
- });
260
- }
261
- catch (error) {
262
- log('error', 'loadingView error:', loadingView, this.loadingViewUrl);
263
- }
264
- try {
265
- errorView.url = `${errorView?.url ?? this.errorViewUrl}`;
266
- lodash.merge(options, {
267
- errorView,
268
- });
269
- }
270
- catch (error) {
271
- log('error', 'errorView error:', errorView, this.errorViewUrl);
272
- }
454
+ loadingView.url = `${loadingView?.url ?? this.loadingViewUrl}`;
455
+ lodash.merge(options, {
456
+ loadingView,
457
+ });
458
+ }
459
+ catch (error) {
460
+ log('error', 'loadingView error:', loadingView, this.loadingViewUrl);
461
+ }
462
+ try {
463
+ errorView.url = `${errorView?.url ?? this.errorViewUrl}`;
464
+ lodash.merge(options, {
465
+ errorView,
466
+ });
467
+ }
468
+ catch (error) {
469
+ log('error', 'errorView error:', errorView, this.errorViewUrl);
470
+ }
471
+ try {
273
472
  let parentWin = undefined;
274
473
  if (typeof browserWindowOptions?.parent === 'number') {
275
474
  parentWin = BrowserWindow.fromId(browserWindowOptions?.parent) || undefined;
@@ -297,6 +496,7 @@ class WindowsManager {
297
496
  nativeWindowOpen: true,
298
497
  webSecurity: false,
299
498
  preload: preload,
499
+ defaultEncoding: 'utf-8',
300
500
  }, browserWindowOptions?.webPreferences || {})
301
501
  }))
302
502
  : new BrowserWindow(lodash.merge({
@@ -313,6 +513,7 @@ class WindowsManager {
313
513
  nativeWindowOpen: true,
314
514
  webSecurity: false,
315
515
  preload: preload,
516
+ defaultEncoding: 'utf-8',
316
517
  }, browserWindowOptions?.webPreferences || {})
317
518
  }));
318
519
  log('log', `${name} 不使用 ${type === 'BV' ? 'preloadedBV' : 'preloadedBW'}`, window?.webContents?.id);
@@ -323,6 +524,8 @@ class WindowsManager {
323
524
  log('error', 'enable: ', error);
324
525
  }
325
526
  }
527
+ // 停止加载
528
+ // window.webContents?.stop?.();
326
529
  // @ts-ignore
327
530
  try {
328
531
  window.id = Number(`${window.id || window.webContents.id}`);
@@ -341,6 +544,8 @@ class WindowsManager {
341
544
  window._name = name;
342
545
  window._extraData = `${options?.extraData || ''}`;
343
546
  window._initUrl = `${options?.url || ''}`;
547
+ // 设置zIndex层级
548
+ window._zIndex = options.zIndex ?? 0;
344
549
  log('log', 'create 5: ', window.id, window._id, window._name);
345
550
  if (loadingView?.url) {
346
551
  if (type === 'BW') {
@@ -351,7 +556,7 @@ class WindowsManager {
351
556
  if (errorView?.url) {
352
557
  if (type === 'BW') {
353
558
  const showErrorView = lodash.debounce(() => {
354
- const _url = window?.webContents?.getURL?.() || url;
559
+ const _url = window._initUrl;
355
560
  /**
356
561
  * 判断是否是错误视图
357
562
  */
@@ -365,16 +570,37 @@ class WindowsManager {
365
570
  sessionStorage.setItem(key, "${_url}");
366
571
  `);
367
572
  }
368
- }, 300);
573
+ }, 1000);
369
574
  // @ts-ignore
370
575
  window.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL, isMainFrame) => {
371
576
  if (isMainFrame) {
372
577
  showErrorView();
373
578
  }
374
579
  });
580
+ // 当开始加载时取消错误视图
375
581
  window.webContents.on('did-start-loading', () => {
376
582
  showErrorView.cancel();
377
583
  });
584
+ // 当导航开始时取消错误视图
585
+ window.webContents.on('did-start-navigation', () => {
586
+ showErrorView.cancel();
587
+ });
588
+ // 当页面重新加载时取消错误视图
589
+ window.webContents.on('did-navigate', () => {
590
+ showErrorView.cancel();
591
+ });
592
+ // 当页面完成加载时取消错误视图
593
+ window.webContents.on('did-finish-load', () => {
594
+ showErrorView.cancel();
595
+ });
596
+ // 当窗口关闭时取消错误视图
597
+ window.webContents.on('close', () => {
598
+ showErrorView.cancel();
599
+ });
600
+ // 当窗口销毁时取消错误视图
601
+ window.webContents.on('destroyed', () => {
602
+ showErrorView.cancel();
603
+ });
378
604
  }
379
605
  }
380
606
  window.webContents.on('did-attach-webview', (_event, webContents) => {
@@ -456,10 +682,6 @@ class WindowsManager {
456
682
  parentWin?.addBrowserView(window);
457
683
  log('log', 'create - addBrowserView');
458
684
  }
459
- // @ts-ignore
460
- window.loadURL ? window.loadURL(options.url) : window.webContents.loadURL(options.url);
461
- // @ts-ignore
462
- window.focus ? window.focus() : window.webContents.focus();
463
685
  this.windows.set(window.id || window._id || window.webContents.id, window);
464
686
  log('log', 'create', this.windows.keys());
465
687
  // 初始化值
@@ -529,29 +751,51 @@ class WindowsManager {
529
751
  });
530
752
  try {
531
753
  const _addBrowserView = window.addBrowserView;
532
- window.addBrowserView = (view) => {
754
+ window.addBrowserView = (view, isSort = false) => {
533
755
  _addBrowserView.call(window, view);
534
756
  handleBrowserViewFocus(view);
757
+ // 添加BrowserView后重新排序(如果未禁用自动排序)
758
+ log('log', 'addBrowserView-sort', isSort, window.getBrowserViews());
759
+ if (isSort) {
760
+ this.sortBrowserViewsDebounced(window, view);
761
+ }
535
762
  };
536
763
  const _removeBrowserView = window.removeBrowserView;
537
- window.removeBrowserView = (view) => {
764
+ window.removeBrowserView = (view, isSort = false) => {
538
765
  _removeBrowserView.call(window, view);
539
766
  handleBrowserViewBlur(view);
767
+ // 移除BrowserView后重新排序(如果未禁用自动排序)
768
+ log('log', 'removeBrowserView-sort', isSort);
769
+ if (isSort) {
770
+ this.sortBrowserViewsDebounced(window, view);
771
+ }
540
772
  };
541
773
  const _setBrowserView = window.setBrowserView;
542
- window.setBrowserView = (view) => {
774
+ window.setBrowserView = (view, isSort = false) => {
543
775
  const views = window.getBrowserViews() || [];
544
776
  for (const view of views) {
545
777
  handleBrowserViewBlur(view);
546
778
  }
547
779
  _setBrowserView.call(window, view);
548
780
  handleBrowserViewFocus(view);
781
+ log('log', 'setBrowserView-sort', isSort);
782
+ if (isSort) {
783
+ this.sortBrowserViewsDebounced(window, view);
784
+ }
549
785
  };
550
786
  }
551
787
  catch (error) {
552
788
  log('error', 'focus', error);
553
789
  }
554
790
  }
791
+ if (options.url) {
792
+ // @ts-ignore
793
+ window.loadURL ? window.loadURL(options.url) : window.webContents.loadURL(options.url);
794
+ if (options.browserWindow?.focusable !== false) {
795
+ window?.focus?.();
796
+ }
797
+ window?.webContents?.focus?.();
798
+ }
555
799
  }
556
800
  catch (error) {
557
801
  log('error', 'create', error);
@@ -575,7 +819,7 @@ class WindowsManager {
575
819
  }
576
820
  const loadLoadingView = () => {
577
821
  const [viewWidth, viewHeight] = window.getSize();
578
- window.setBrowserView(_loadingView);
822
+ window.addBrowserView(_loadingView);
579
823
  _loadingView.setBounds({
580
824
  x: 0,
581
825
  y: 0,
@@ -615,7 +859,7 @@ class WindowsManager {
615
859
  return;
616
860
  }
617
861
  if (_loadingView.webContents && !_loadingView.webContents.isDestroyed()) {
618
- window.setBrowserView(_loadingView);
862
+ window.addBrowserView(_loadingView);
619
863
  }
620
864
  else {
621
865
  // if loadingView has been destroyed
@@ -815,6 +1059,86 @@ class WindowsManager {
815
1059
  if (typeof browserWindowOptions.alwaysOnTop === 'boolean') {
816
1060
  win.setAlwaysOnTop(browserWindowOptions.alwaysOnTop);
817
1061
  }
1062
+ // 设置背景颜色
1063
+ if (typeof browserWindowOptions.backgroundColor === 'string') {
1064
+ win.setBackgroundColor(browserWindowOptions.backgroundColor);
1065
+ }
1066
+ // 居中
1067
+ if (browserWindowOptions?.center !== false) {
1068
+ win.center();
1069
+ }
1070
+ // 设置窗口移动
1071
+ if (typeof browserWindowOptions.movable === 'boolean') {
1072
+ win.setMovable(browserWindowOptions.movable);
1073
+ }
1074
+ // 设置窗口大小调整
1075
+ if (typeof browserWindowOptions.resizable === 'boolean') {
1076
+ win.setResizable(browserWindowOptions.resizable);
1077
+ }
1078
+ // 设置全屏模式
1079
+ if (typeof browserWindowOptions.fullscreenable === 'boolean') {
1080
+ win.setFullScreenable(browserWindowOptions.fullscreenable);
1081
+ }
1082
+ // 设置窗口阴影
1083
+ if (typeof browserWindowOptions.hasShadow === 'boolean') {
1084
+ win.setHasShadow(browserWindowOptions.hasShadow);
1085
+ }
1086
+ // 设置窗口最小尺寸
1087
+ if (typeof browserWindowOptions.minWidth === 'number' && typeof browserWindowOptions.minHeight === 'number') {
1088
+ win.setMinimumSize(browserWindowOptions.minWidth, browserWindowOptions.minHeight);
1089
+ }
1090
+ // 设置窗口最大尺寸
1091
+ if (typeof browserWindowOptions.maxWidth === 'number' && typeof browserWindowOptions.maxHeight === 'number') {
1092
+ win.setMaximumSize(browserWindowOptions.maxWidth, browserWindowOptions.maxHeight);
1093
+ }
1094
+ // 设置窗口位置
1095
+ if (typeof browserWindowOptions.x === 'number' && typeof browserWindowOptions.y === 'number') {
1096
+ win.setPosition(browserWindowOptions.x, browserWindowOptions.y);
1097
+ }
1098
+ // 设置窗口标题
1099
+ if (typeof browserWindowOptions.title === 'string') {
1100
+ win.setTitle(browserWindowOptions.title);
1101
+ }
1102
+ // 设置窗口图标
1103
+ if (typeof browserWindowOptions.icon === 'string') {
1104
+ win.setIcon(browserWindowOptions.icon);
1105
+ }
1106
+ // 设置窗口菜单栏可见性
1107
+ if (typeof browserWindowOptions.autoHideMenuBar === 'boolean') {
1108
+ win.setAutoHideMenuBar(browserWindowOptions.autoHideMenuBar);
1109
+ }
1110
+ // 设置窗口最小化按钮
1111
+ if (browserWindowOptions.minimizable === false) {
1112
+ win.setMinimizable(false);
1113
+ }
1114
+ // 设置窗口最大化按钮
1115
+ if (browserWindowOptions.maximizable === false) {
1116
+ win.setMaximizable(false);
1117
+ }
1118
+ // 设置窗口关闭按钮
1119
+ if (browserWindowOptions.closable === false) {
1120
+ win.setClosable(false);
1121
+ }
1122
+ // 设置窗口焦点
1123
+ if (browserWindowOptions.focusable === false) {
1124
+ win.setFocusable(false);
1125
+ }
1126
+ // 设置窗口全屏
1127
+ if (browserWindowOptions.fullscreen === true) {
1128
+ win.setFullScreen(true);
1129
+ }
1130
+ // 设置窗口背景材质
1131
+ if (typeof browserWindowOptions.vibrancy === 'string') {
1132
+ win.setVibrancy(browserWindowOptions.vibrancy);
1133
+ }
1134
+ // 设置窗口透明度
1135
+ if (typeof browserWindowOptions.opacity === 'number') {
1136
+ win.setOpacity(browserWindowOptions.opacity);
1137
+ }
1138
+ // 设置窗口显示状态
1139
+ if (browserWindowOptions.show === false) {
1140
+ win.hide();
1141
+ }
818
1142
  // 可继续扩展其他动态属性
819
1143
  }
820
1144
  _applyBrowserViewOptions(view, options) {
@@ -830,12 +1154,26 @@ class WindowsManager {
830
1154
  if (typeof browserWindowOptions.width === 'number' && typeof browserWindowOptions.height === 'number') {
831
1155
  view.setBounds({ x: 0, y: 0, width: browserWindowOptions.width, height: browserWindowOptions.height });
832
1156
  }
1157
+ // 设置视图位置
1158
+ if (typeof browserWindowOptions.x === 'number' && typeof browserWindowOptions.y === 'number') {
1159
+ const bounds = view.getBounds();
1160
+ view.setBounds({
1161
+ x: browserWindowOptions.x,
1162
+ y: browserWindowOptions.y,
1163
+ width: bounds.width,
1164
+ height: bounds.height
1165
+ });
1166
+ }
1167
+ // 设置视图背景颜色
1168
+ if (typeof browserWindowOptions.backgroundColor === 'string') {
1169
+ view.setBackgroundColor(browserWindowOptions.backgroundColor);
1170
+ }
833
1171
  // 可继续扩展其他动态属性
834
1172
  }
835
1173
  // 生成一个bv 做为预加载资源窗口,加载完成后销毁
836
1174
  async createPreloadWebContents(url) {
837
1175
  return new Promise(async (resolve, reject) => {
838
- let bv = this.create({
1176
+ let bv = await this.create({
839
1177
  type: 'BV',
840
1178
  url,
841
1179
  name: `preload-web-contents-${md5(url)}`,
@@ -854,11 +1192,108 @@ class WindowsManager {
854
1192
  bv.webContents.loadURL(url);
855
1193
  });
856
1194
  }
1195
+ async getWindowForWebContentsId(wcId) {
1196
+ const wc = webContents.fromId(wcId);
1197
+ if (!wc)
1198
+ return undefined;
1199
+ // Case 1: BrowserView
1200
+ for (const win of BrowserWindow.getAllWindows()) {
1201
+ for (const view of win.getBrowserViews()) {
1202
+ if (view.webContents.id === wcId) {
1203
+ return win;
1204
+ }
1205
+ }
1206
+ }
1207
+ // Case 2: WebView
1208
+ // webview 有 hostWebContents,指向它所在的 BrowserWindow 的 webContents
1209
+ if (wc.hostWebContents) {
1210
+ return BrowserWindow.fromWebContents(wc.hostWebContents);
1211
+ }
1212
+ // Case 3: 普通 window 本身
1213
+ const win = BrowserWindow.fromWebContents(wc);
1214
+ if (win)
1215
+ return win;
1216
+ return undefined;
1217
+ }
1218
+ /**
1219
+ * 手动对BrowserView进行排序
1220
+ * @param windowId 窗口ID或名称
1221
+ */
1222
+ sortBrowserViews(windowId) {
1223
+ const window = this.get(windowId);
1224
+ if (window && window._type === 'BW') {
1225
+ this._sortBrowserViews(window);
1226
+ }
1227
+ }
1228
+ /**
1229
+ * 对BrowserView进行排序
1230
+ * @param window 目标窗口
1231
+ */
1232
+ _sortBrowserViews(window, addView) {
1233
+ try {
1234
+ const views = window.getBrowserViews() || [];
1235
+ if (views.length <= 1)
1236
+ return;
1237
+ log('log', 'sortBrowserViews', views?.map(i => i.webContents.id));
1238
+ // 按zIndex层级排序,数值小的在前
1239
+ const sortedViews = views.sort((a, b) => {
1240
+ const zIndexA = a._zIndex ?? 0;
1241
+ const zIndexB = b._zIndex ?? 0;
1242
+ return zIndexA - zIndexB;
1243
+ });
1244
+ // 检查是否已经按正确顺序排列
1245
+ let needsReorder = false;
1246
+ for (let i = 0; i < views.length; i++) {
1247
+ // @ts-ignore
1248
+ if (views[i].webContents.id !== sortedViews[i].webContents.id) {
1249
+ needsReorder = true;
1250
+ break;
1251
+ }
1252
+ }
1253
+ log('log', 'sortBrowserViews needsReorder', needsReorder, sortedViews?.map(i => i.webContents.id));
1254
+ // 如果已经按正确顺序排列,则不需要重新排序
1255
+ if (!needsReorder)
1256
+ return;
1257
+ // 使用批量操作来减少闪烁
1258
+ // 临时隐藏窗口内容
1259
+ const wasVisible = window.isVisible();
1260
+ if (wasVisible) {
1261
+ // window.hide();
1262
+ }
1263
+ // 移除所有BrowserView
1264
+ views.forEach(view => {
1265
+ if (addView?.webContents?.id !== view.webContents.id) {
1266
+ // @ts-ignore
1267
+ window.removeBrowserView(view, false);
1268
+ }
1269
+ });
1270
+ // 按正确顺序重新添加
1271
+ sortedViews.forEach((view, index) => {
1272
+ if (index === 0) {
1273
+ // 第一个设置为当前视图
1274
+ // @ts-ignore
1275
+ window.setBrowserView(view, false);
1276
+ }
1277
+ else {
1278
+ // 其他视图添加到后面
1279
+ // @ts-ignore
1280
+ window.addBrowserView(view, false);
1281
+ }
1282
+ });
1283
+ // 恢复窗口显示
1284
+ if (wasVisible) {
1285
+ // window.show();
1286
+ }
1287
+ }
1288
+ catch (error) {
1289
+ log('error', 'sortBrowserViews error:', error);
1290
+ }
1291
+ }
857
1292
  }
858
1293
  // @ts-ignore
859
1294
  global['__ELECTRON_WINDOWS_MANAGER__'] = undefined;
860
1295
  let isInitialized = false;
861
- const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl, webviewDomainWhiteList) => {
1296
+ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList) => {
862
1297
  // @ts-ignore
863
1298
  if (isInitialized && global['__ELECTRON_WINDOWS_MANAGER__']) {
864
1299
  // @ts-ignore
@@ -866,7 +1301,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
866
1301
  }
867
1302
  isInitialized = true;
868
1303
  // @ts-ignore
869
- const wm = global['__ELECTRON_WINDOWS_MANAGER__'] = new WindowsManager(preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl, webviewDomainWhiteList);
1304
+ const wm = global['__ELECTRON_WINDOWS_MANAGER__'] = new WindowsManager(preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList);
870
1305
  eIpc.mainIPC.handleRenderer('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', async (data) => {
871
1306
  if (data?.type === 'create') {
872
1307
  const opt = data;
@@ -882,21 +1317,39 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
882
1317
  findWin.webContents.reload();
883
1318
  }, 100);
884
1319
  }
1320
+ if (opt.data.browserWindow?.parent) {
1321
+ try {
1322
+ if (findWin._type === 'BW') {
1323
+ findWin.setParentWindow(BrowserWindow.fromId(Number(opt.data.browserWindow.parent)));
1324
+ }
1325
+ if (findWin._type === 'BV') {
1326
+ BrowserWindow.fromId(Number(opt.data.browserWindow.parent))?.addBrowserView(findWin);
1327
+ }
1328
+ }
1329
+ catch (error) {
1330
+ log('error', 'setParentWindow error:', error);
1331
+ }
1332
+ }
1333
+ if (findWin?._type === 'BV' && opt.data.zIndex) {
1334
+ findWin._zIndex = opt.data.zIndex;
1335
+ }
885
1336
  return {
886
1337
  winId: Number(`${findWin?.id || findWin?._id || -1}`),
887
1338
  winName: `${findWin?._name || ''}`,
888
1339
  winType: `${findWin?._type || ''}`,
889
1340
  winExtraData: `${findWin?._extraData || ''}`,
890
1341
  winInitUrl: `${findWin?._initUrl || ''}`,
1342
+ winZIndex: `${findWin._zIndex || 0}`,
891
1343
  };
892
1344
  }
893
- const res = wm.create(opt.data);
1345
+ const res = await wm.create(opt.data);
894
1346
  return {
895
1347
  winId: Number(`${res.id || res._id || -1}`),
896
1348
  winName: `${res?._name || ''}`,
897
1349
  winType: `${res?._type || ''}`,
898
1350
  winExtraData: `${res?._extraData || ''}`,
899
1351
  winInitUrl: `${res?._initUrl || ''}`,
1352
+ winZIndex: `${res?._zIndex || 0}`,
900
1353
  };
901
1354
  }
902
1355
  if (data?.type === 'get') {
@@ -908,6 +1361,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
908
1361
  winType: `${res?._type || ''}`,
909
1362
  winExtraData: `${res?._extraData || ''}`,
910
1363
  winInitUrl: `${res?._initUrl || ''}`,
1364
+ winZIndex: `${res?._zIndex || 0}`,
911
1365
  };
912
1366
  }
913
1367
  if (data?.type === 'getAll') {
@@ -921,6 +1375,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
921
1375
  winType: `${i?._type || ''}`,
922
1376
  winExtraData: `${i?._extraData || ''}`,
923
1377
  winInitUrl: `${i?._initUrl || ''}`,
1378
+ winZIndex: `${i?._zIndex || 0}`,
924
1379
  };
925
1380
  });
926
1381
  return obj;
@@ -944,6 +1399,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
944
1399
  winType: `${res?._type || ''}`,
945
1400
  winExtraData: `${res?._extraData || ''}`,
946
1401
  winInitUrl: `${res?._initUrl || ''}`,
1402
+ winZIndex: `${res?._zIndex || 0}`,
947
1403
  };
948
1404
  }
949
1405
  return undefined;
@@ -958,34 +1414,16 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
958
1414
  winType: `${res?._type || ''}`,
959
1415
  winExtraData: `${res?._extraData || ''}`,
960
1416
  winInitUrl: `${res?._initUrl || ''}`,
1417
+ winZIndex: `${res?._zIndex || 0}`,
961
1418
  };
962
1419
  }
963
1420
  return undefined;
964
1421
  }
965
- if (data?.type === 'getWindowForWebContentId') {
1422
+ if (data?.type === 'getWindowForWebContentsId') {
966
1423
  const opt = data;
967
- const targetWebContents = webContents.fromId(opt.data);
968
- if (targetWebContents) {
969
- let win = BrowserWindow.fromWebContents(targetWebContents);
970
- if (!win) {
971
- // 获取所有的 BrowserWindows
972
- let allWindows = BrowserWindow.getAllWindows();
973
- // 遍历所有窗口,检查每个窗口的 BrowserView
974
- for (let _win of allWindows) {
975
- let views = _win.getBrowserViews();
976
- // 遍历窗口的所有 BrowserView
977
- for (let view of views) {
978
- if (view.webContents === targetWebContents) {
979
- win = _win;
980
- break;
981
- }
982
- }
983
- if (win)
984
- break;
985
- }
986
- }
987
- // @ts-ignore
988
- return win?.id || win?._id;
1424
+ const res = await wm.getWindowForWebContentsId(opt.data);
1425
+ if (res) {
1426
+ return res?.id;
989
1427
  }
990
1428
  return undefined;
991
1429
  }
@@ -1026,15 +1464,20 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
1026
1464
  return undefined;
1027
1465
  }
1028
1466
  // 是否开启预加载窗口
1029
- if (data?.type === 'setPreloadWebContentsUrl') {
1467
+ if (data?.type === 'setPreloadWebContentsConfig') {
1030
1468
  const opt = data;
1031
- wm.setPreloadWebContentsUrl(opt.data);
1469
+ wm.setPreloadWebContentsConfig(opt.data);
1032
1470
  }
1033
1471
  if (data?.type === 'createPreloadWebContents') {
1034
1472
  const opt = data;
1035
1473
  const res = await wm.createPreloadWebContents(opt.data);
1036
1474
  return res;
1037
1475
  }
1476
+ if (data?.type === 'sortBrowserViews') {
1477
+ const opt = data;
1478
+ wm.sortBrowserViews(opt.data);
1479
+ return true;
1480
+ }
1038
1481
  return undefined;
1039
1482
  });
1040
1483
  return wm;