@lynker-desktop/electron-window-manager 0.0.9-alpha.4 → 0.0.9-alpha.41

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,7 +69,7 @@ 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
74
  this.preloadedBW = null;
75
75
  // 预加载的窗口(无边框,有按钮)
@@ -80,29 +80,39 @@ class WindowsManager {
80
80
  this.preloadedBV = null;
81
81
  this.preloading = false;
82
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);
83
93
  this.preload = preload;
84
94
  this.windows = new Map();
85
95
  this.loadingViewUrl = `${loadingViewUrl ?? ''}`;
86
96
  this.errorViewUrl = `${errorViewUrl ?? ''}`;
87
- this.preloadWebContentsUrl = `${preloadWebContentsUrl ?? ''}`;
97
+ this.preloadWebContentsConfig = preloadWebContentsConfig;
88
98
  this.webviewDomainWhiteList = webviewDomainWhiteList || [];
89
- log('log', 'preloadWebContentsUrl: ', this.preloadWebContentsUrl);
90
- if (this.preloadWebContentsUrl) {
91
- electron.app.on('ready', () => {
92
- if (this.preloadWebContentsUrl) {
93
- this.setPreloadWebContentsUrl(this.preloadWebContentsUrl);
99
+ log('log', 'preloadWebContentsConfig: ', this.preloadWebContentsConfig);
100
+ if (this.preloadWebContentsConfig) {
101
+ electron.app.whenReady().then(() => {
102
+ if (this.preloadWebContentsConfig) {
103
+ this.setPreloadWebContentsConfig(this.preloadWebContentsConfig);
94
104
  }
95
105
  });
96
106
  }
97
107
  }
98
108
  /**
99
- * 设置预加载的webContents的url
100
- * @param preloadWebContentsUrl 预加载的webContents的url
109
+ * 设置预加载的webContents配置
110
+ * @param preloadWebContentsConfig 预加载的webContents配置
101
111
  */
102
- setPreloadWebContentsUrl(preloadWebContentsUrl) {
112
+ setPreloadWebContentsConfig(preloadWebContentsConfig) {
103
113
  try {
104
- this.preloadWebContentsUrl = preloadWebContentsUrl;
105
- if (this.preloadWebContentsUrl) {
114
+ this.preloadWebContentsConfig = preloadWebContentsConfig;
115
+ if (this.preloadWebContentsConfig) {
106
116
  this._preloadInstances();
107
117
  }
108
118
  else {
@@ -113,7 +123,7 @@ class WindowsManager {
113
123
  }
114
124
  }
115
125
  catch (error) {
116
- log('error', 'setPreloadWebContentsUrl error:', error);
126
+ log('error', 'setPreloadWebContentsConfig error:', error);
117
127
  }
118
128
  }
119
129
  /**
@@ -124,23 +134,54 @@ class WindowsManager {
124
134
  return;
125
135
  this.preloading = true;
126
136
  try {
127
- if (this.preloadWebContentsUrl) {
128
- // 预加载的窗口
129
- this.preloadedBW = this.preloadedBW || await this._createPreloadBW({});
130
- // 预加载的窗口(无边框,有按钮)
131
- this.preloadedBW_FramelessWithButtons = this.preloadedBW_FramelessWithButtons || await this._createPreloadBW({
132
- frame: false,
133
- transparent: true,
134
- titleBarStyle: 'hidden',
135
- });
136
- // 预加载的窗口(无边框,无按钮)
137
- this.preloadedBW_FramelessNoButtons = this.preloadedBW_FramelessNoButtons || await this._createPreloadBW({
138
- frame: false,
139
- transparent: true,
140
- titleBarStyle: 'customButtonsOnHover',
141
- });
142
- // 预加载的BV
143
- 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
+ }
144
185
  }
145
186
  }
146
187
  catch (e) {
@@ -156,20 +197,24 @@ class WindowsManager {
156
197
  _createPreloadBW(options = {}) {
157
198
  return new Promise((resolve) => {
158
199
  const preload = this.preload;
159
- const url = this.preloadWebContentsUrl;
160
- if (this.preloadWebContentsUrl) {
200
+ const url = this.preloadWebContentsConfig?.url;
201
+ if (this.preloadWebContentsConfig?.url) {
202
+ const webPreferences = (options.webPreferences || {});
161
203
  const instance = new electron.BrowserWindow({
204
+ useContentSize: true,
162
205
  show: false,
206
+ backgroundColor: '#ffffff',
163
207
  ...options,
164
208
  webPreferences: {
209
+ ...webPreferences,
165
210
  webviewTag: true,
166
211
  plugins: true,
167
212
  nodeIntegration: true,
168
213
  contextIsolation: false,
169
214
  backgroundThrottling: false,
170
215
  webSecurity: false,
171
- preload: preload,
172
- ...(options.webPreferences || {}),
216
+ preload: webPreferences.preload || preload,
217
+ defaultEncoding: 'utf-8',
173
218
  }
174
219
  });
175
220
  try {
@@ -186,14 +231,19 @@ class WindowsManager {
186
231
  log('error', '预加载 BW 设置 _id 失败', error);
187
232
  }
188
233
  // @ts-ignore
189
- log('log', '创建预BW: ', instance._id, this.preloadWebContentsUrl);
190
- instance.webContents.once('did-finish-load', () => {
191
- resolve(instance);
192
- });
193
- instance.webContents.once('did-fail-load', () => {
194
- resolve(instance);
195
- });
196
- 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
+ // @ts-ignore
242
+ instance.loadURL(url ? `${url}` : 'about:blank');
243
+ resolve(instance);
244
+ }
245
+ else {
246
+ resolve(null);
197
247
  }
198
248
  });
199
249
  }
@@ -201,20 +251,23 @@ class WindowsManager {
201
251
  * 创建预加载的浏览器视图
202
252
  * @returns 预加载的浏览器视图
203
253
  */
204
- _createPreloadBV() {
254
+ _createPreloadBV(options = {}) {
205
255
  return new Promise((resolve) => {
206
256
  const preload = this.preload;
207
- const url = this.preloadWebContentsUrl;
208
- if (this.preloadWebContentsUrl) {
257
+ const url = this.preloadWebContentsConfig?.url;
258
+ if (this.preloadWebContentsConfig?.url) {
259
+ const webPreferences = (options.webPreferences || {});
209
260
  const instance = new electron.BrowserView({
210
261
  webPreferences: {
262
+ ...webPreferences,
211
263
  webviewTag: true,
212
264
  plugins: true,
213
265
  nodeIntegration: true,
214
266
  contextIsolation: false,
215
267
  // backgroundThrottling: false,
216
268
  webSecurity: false,
217
- preload: preload,
269
+ preload: webPreferences.preload || preload,
270
+ defaultEncoding: 'utf-8',
218
271
  }
219
272
  });
220
273
  try {
@@ -226,61 +279,131 @@ class WindowsManager {
226
279
  try {
227
280
  // @ts-ignore
228
281
  instance._id = instance.webContents.id;
282
+ // 设置默认zIndex层级
283
+ instance._zIndex = 0;
229
284
  }
230
285
  catch (error) {
231
286
  log('error', '预加载 BV 设置 _id 失败', error);
232
287
  }
233
288
  // @ts-ignore
234
- log('log', '创建预BV: ', instance._id, this.preloadWebContentsUrl);
235
- instance.webContents.once('did-finish-load', () => {
236
- resolve(instance);
237
- });
238
- instance.webContents.once('did-fail-load', () => {
239
- resolve(instance);
240
- });
289
+ log('log', '创建预BV: ', instance._id, this.preloadWebContentsConfig?.url);
290
+ // instance.webContents.once('did-finish-load', () => {
291
+ // resolve(instance as BVItem);
292
+ // });
293
+ // instance.webContents.once('did-fail-load', () => {
294
+ // resolve(instance as BVItem);
295
+ // });
296
+ // @ts-ignore
241
297
  instance.webContents.loadURL(url || 'about:blank');
298
+ resolve(instance);
299
+ }
300
+ else {
301
+ resolve(null);
242
302
  }
243
303
  });
244
304
  }
245
305
  create(options) {
306
+ return new Promise((resolve, reject) => {
307
+ // 将创建请求添加到队列
308
+ this.createQueue.push({ options, resolve, reject });
309
+ // 如果当前没有在创建,则开始处理队列
310
+ if (!this.isCreating) {
311
+ this.processCreateQueue();
312
+ }
313
+ });
314
+ }
315
+ /**
316
+ * 处理创建队列
317
+ */
318
+ async processCreateQueue() {
319
+ if (this.isCreating || this.createQueue.length === 0) {
320
+ return;
321
+ }
322
+ this.isCreating = true;
323
+ while (this.createQueue.length > 0) {
324
+ const { options, resolve, reject } = this.createQueue.shift();
325
+ try {
326
+ const window = await this._createWindow(options);
327
+ resolve(window);
328
+ }
329
+ catch (error) {
330
+ log('error', 'create window failed:', error);
331
+ reject(error);
332
+ }
333
+ }
334
+ this.isCreating = false;
335
+ }
336
+ /**
337
+ * 实际的窗口创建逻辑
338
+ */
339
+ async _createWindow(options) {
246
340
  let window;
247
- const { usePreload = true, type = 'BW', name = 'anonymous', url, loadingView = { url: undefined }, errorView = { url: undefined }, browserWindow: browserWindowOptions, openDevTools = false, preventOriginClose = false, } = options;
341
+ const { usePreload = true, type = 'BW', name = 'anonymous', url, loadingView = { url: undefined }, errorView = { url: undefined }, browserWindow: browserWindowOptions, openDevTools = false, preventOriginClose = false, zIndex = 0, } = options;
248
342
  options.type = type;
249
343
  // 优先复用预创建实例
250
344
  let preloadWin = null;
251
- if (type === 'BW' && usePreload && this.preloadWebContentsUrl) {
345
+ if (type === 'BW' && usePreload && this.preloadWebContentsConfig?.url) {
252
346
  const bwOptions = browserWindowOptions || {};
253
- if (bwOptions.frame === false && bwOptions.titleBarStyle === 'hidden') {
254
- preloadWin = this.preloadedBW_FramelessWithButtons;
255
- this.preloadedBW_FramelessWithButtons = null;
256
- setTimeout(() => this._createPreloadBW({ frame: false, transparent: true, titleBarStyle: 'hidden' }), 0);
257
- }
258
- else if (bwOptions.frame === false && bwOptions.titleBarStyle === 'customButtonsOnHover') {
259
- preloadWin = this.preloadedBW_FramelessNoButtons;
260
- this.preloadedBW_FramelessNoButtons = null;
261
- setTimeout(() => this._createPreloadBW({ frame: false, transparent: true, titleBarStyle: 'customButtonsOnHover' }), 0);
347
+ if (bwOptions.frame === false) {
348
+ if (bwOptions.titleBarStyle === 'default' || !bwOptions.titleBarStyle) {
349
+ if (this.preloadWebContentsConfig.enableBW_FramelessNoButtons !== false && this.preloadedBW_FramelessNoButtons) {
350
+ preloadWin = this.preloadedBW_FramelessNoButtons;
351
+ this.preloadedBW_FramelessNoButtons = await this._createPreloadBW({
352
+ frame: false,
353
+ // transparent: true,
354
+ titleBarStyle: 'default',
355
+ webPreferences: {
356
+ preload: bwOptions?.webPreferences?.preload || this.preload,
357
+ }
358
+ });
359
+ }
360
+ }
361
+ else {
362
+ if (this.preloadWebContentsConfig.enableBW_FramelessWithButtons !== false && this.preloadedBW_FramelessWithButtons) {
363
+ preloadWin = this.preloadedBW_FramelessWithButtons;
364
+ this.preloadedBW_FramelessWithButtons = await this._createPreloadBW({
365
+ frame: false,
366
+ // transparent: true,
367
+ titleBarStyle: 'hidden',
368
+ webPreferences: {
369
+ preload: this.preload,
370
+ }
371
+ });
372
+ }
373
+ }
262
374
  }
263
375
  else {
264
- preloadWin = this.preloadedBW;
265
- this.preloadedBW = null;
266
- setTimeout(() => this._createPreloadBW({}), 0);
376
+ if (this.preloadWebContentsConfig.enableBW !== false && this.preloadedBW) {
377
+ preloadWin = this.preloadedBW;
378
+ this.preloadedBW = await this._createPreloadBW({
379
+ webPreferences: {
380
+ preload: bwOptions?.webPreferences?.preload || this.preload,
381
+ }
382
+ });
383
+ }
267
384
  }
268
385
  }
269
- if (type === 'BV' && usePreload && this.preloadWebContentsUrl) {
270
- preloadWin = this.preloadedBV;
271
- this.preloadedBV = null;
272
- setTimeout(() => this._createPreloadBV(), 0);
386
+ if (type === 'BV' && usePreload && this.preloadWebContentsConfig?.url) {
387
+ const bvOptions = browserWindowOptions || {};
388
+ if (this.preloadWebContentsConfig.enableBV !== false && this.preloadedBV) {
389
+ preloadWin = this.preloadedBV;
390
+ this.preloadedBV = await this._createPreloadBV({
391
+ webPreferences: {
392
+ preload: bvOptions?.webPreferences?.preload || this.preload,
393
+ }
394
+ });
395
+ }
273
396
  }
274
397
  if (preloadWin) {
275
398
  const win = preloadWin;
276
- log('log', `${name} 使用预加载窗口`, win._id);
399
+ log('log', `${name} 使用预加载窗口(${type})`, win._id);
277
400
  win._type = 'BW';
278
401
  win._name = options.name || 'anonymous';
279
402
  win._extraData = `${options?.extraData || ''}`;
280
403
  win._initUrl = `${options?.url || ''}`;
281
404
  // @ts-ignore
282
- win?.removeAllListeners && win?.removeAllListeners?.();
283
- win.webContents.removeAllListeners && win.webContents.removeAllListeners();
405
+ // win?.removeAllListeners && win?.removeAllListeners?.();
406
+ // win.webContents.removeAllListeners && win.webContents.removeAllListeners();
284
407
  if (type === 'BW') {
285
408
  // @ts-ignore
286
409
  this._applyBrowserWindowOptions(win, options);
@@ -288,27 +411,87 @@ class WindowsManager {
288
411
  if (type === 'BV') {
289
412
  this._applyBrowserViewOptions(win, options);
290
413
  }
414
+ if (typeof this.preloadWebContentsConfig?.customLoadURL === 'function') {
415
+ try {
416
+ if (type === 'BW') {
417
+ // @ts-ignore
418
+ const originLoadURL = win.loadURL;
419
+ // @ts-ignore
420
+ win.loadURL = async (url, useNativeLoadURL = false) => {
421
+ return new Promise(async (resolve, reject) => {
422
+ if (useNativeLoadURL) {
423
+ console.error('useNativeLoadURL win.loadURL');
424
+ return originLoadURL.call(win, url);
425
+ }
426
+ try {
427
+ console.error('customLoadURL win.loadURL');
428
+ // @ts-ignore
429
+ await this.preloadWebContentsConfig.customLoadURL(url || 'about:blank', (url) => originLoadURL.call(win, url), win.webContents);
430
+ try {
431
+ win.emit('ready-to-show');
432
+ }
433
+ catch (error) {
434
+ log('error', 'emit ready-to-show event failed:', error);
435
+ }
436
+ resolve(undefined);
437
+ }
438
+ catch (error) {
439
+ reject(error);
440
+ }
441
+ });
442
+ };
443
+ }
444
+ const originWebContentsLoadURL = win.webContents.loadURL;
445
+ // @ts-ignore
446
+ win.webContents.loadURL = async (url, useNativeLoadURL = false) => {
447
+ return new Promise(async (resolve, reject) => {
448
+ if (useNativeLoadURL) {
449
+ console.error('useNativeLoadURL win.webContents.loadURL');
450
+ return originWebContentsLoadURL.call(win.webContents, url);
451
+ }
452
+ try {
453
+ console.error('customLoadURL win.webContents.loadURL');
454
+ // @ts-ignore
455
+ await this.preloadWebContentsConfig.customLoadURL(url || 'about:blank', (url) => originWebContentsLoadURL.call(win.webContents, url), win.webContents);
456
+ try {
457
+ win.webContents.emit('ready-to-show');
458
+ }
459
+ catch (error) {
460
+ log('error', 'emit ready-to-show event failed:', error);
461
+ }
462
+ resolve(undefined);
463
+ }
464
+ catch (error) {
465
+ reject(error);
466
+ }
467
+ });
468
+ };
469
+ }
470
+ catch (error) {
471
+ console.error('customLoadURL error', error);
472
+ }
473
+ }
291
474
  window = win;
292
475
  }
293
476
  try {
294
- try {
295
- loadingView.url = `${loadingView?.url ?? this.loadingViewUrl}`;
296
- lodash.merge(options, {
297
- loadingView,
298
- });
299
- }
300
- catch (error) {
301
- log('error', 'loadingView error:', loadingView, this.loadingViewUrl);
302
- }
303
- try {
304
- errorView.url = `${errorView?.url ?? this.errorViewUrl}`;
305
- lodash.merge(options, {
306
- errorView,
307
- });
308
- }
309
- catch (error) {
310
- log('error', 'errorView error:', errorView, this.errorViewUrl);
311
- }
477
+ loadingView.url = `${loadingView?.url ?? this.loadingViewUrl}`;
478
+ lodash.merge(options, {
479
+ loadingView,
480
+ });
481
+ }
482
+ catch (error) {
483
+ log('error', 'loadingView error:', loadingView, this.loadingViewUrl);
484
+ }
485
+ try {
486
+ errorView.url = `${errorView?.url ?? this.errorViewUrl}`;
487
+ lodash.merge(options, {
488
+ errorView,
489
+ });
490
+ }
491
+ catch (error) {
492
+ log('error', 'errorView error:', errorView, this.errorViewUrl);
493
+ }
494
+ try {
312
495
  let parentWin = undefined;
313
496
  if (typeof browserWindowOptions?.parent === 'number') {
314
497
  parentWin = electron.BrowserWindow.fromId(browserWindowOptions?.parent) || undefined;
@@ -336,6 +519,7 @@ class WindowsManager {
336
519
  nativeWindowOpen: true,
337
520
  webSecurity: false,
338
521
  preload: preload,
522
+ defaultEncoding: 'utf-8',
339
523
  }, browserWindowOptions?.webPreferences || {})
340
524
  }))
341
525
  : new electron.BrowserWindow(lodash.merge({
@@ -352,6 +536,7 @@ class WindowsManager {
352
536
  nativeWindowOpen: true,
353
537
  webSecurity: false,
354
538
  preload: preload,
539
+ defaultEncoding: 'utf-8',
355
540
  }, browserWindowOptions?.webPreferences || {})
356
541
  }));
357
542
  log('log', `${name} 不使用 ${type === 'BV' ? 'preloadedBV' : 'preloadedBW'}`, window?.webContents?.id);
@@ -362,6 +547,8 @@ class WindowsManager {
362
547
  log('error', 'enable: ', error);
363
548
  }
364
549
  }
550
+ // 停止加载
551
+ // window.webContents?.stop?.();
365
552
  // @ts-ignore
366
553
  try {
367
554
  window.id = Number(`${window.id || window.webContents.id}`);
@@ -380,14 +567,16 @@ class WindowsManager {
380
567
  window._name = name;
381
568
  window._extraData = `${options?.extraData || ''}`;
382
569
  window._initUrl = `${options?.url || ''}`;
570
+ // 设置zIndex层级
571
+ window._zIndex = options.zIndex ?? 0;
383
572
  log('log', 'create 5: ', window.id, window._id, window._name);
384
- if (loadingView?.url) {
573
+ if (loadingView?.url && loadingView?.url !== 'about:blank') {
385
574
  if (type === 'BW') {
386
575
  // @ts-ignore
387
576
  this._setLoadingView(window, options);
388
577
  }
389
578
  }
390
- if (errorView?.url) {
579
+ if (errorView?.url && errorView?.url !== 'about:blank') {
391
580
  if (type === 'BW') {
392
581
  const showErrorView = lodash.debounce(() => {
393
582
  const _url = window._initUrl;
@@ -516,10 +705,6 @@ class WindowsManager {
516
705
  parentWin?.addBrowserView(window);
517
706
  log('log', 'create - addBrowserView');
518
707
  }
519
- // @ts-ignore
520
- window.loadURL ? window.loadURL(options.url) : window.webContents.loadURL(options.url);
521
- // @ts-ignore
522
- window.focus ? window.focus() : window.webContents.focus();
523
708
  this.windows.set(window.id || window._id || window.webContents.id, window);
524
709
  log('log', 'create', this.windows.keys());
525
710
  // 初始化值
@@ -589,29 +774,51 @@ class WindowsManager {
589
774
  });
590
775
  try {
591
776
  const _addBrowserView = window.addBrowserView;
592
- window.addBrowserView = (view) => {
777
+ window.addBrowserView = (view, isSort = false) => {
593
778
  _addBrowserView.call(window, view);
594
779
  handleBrowserViewFocus(view);
780
+ // 添加BrowserView后重新排序(如果未禁用自动排序)
781
+ log('log', 'addBrowserView-sort', isSort, window.getBrowserViews());
782
+ if (isSort) {
783
+ this.sortBrowserViewsDebounced(window, view);
784
+ }
595
785
  };
596
786
  const _removeBrowserView = window.removeBrowserView;
597
- window.removeBrowserView = (view) => {
787
+ window.removeBrowserView = (view, isSort = false) => {
598
788
  _removeBrowserView.call(window, view);
599
789
  handleBrowserViewBlur(view);
790
+ // 移除BrowserView后重新排序(如果未禁用自动排序)
791
+ log('log', 'removeBrowserView-sort', isSort);
792
+ if (isSort) {
793
+ this.sortBrowserViewsDebounced(window, view);
794
+ }
600
795
  };
601
796
  const _setBrowserView = window.setBrowserView;
602
- window.setBrowserView = (view) => {
797
+ window.setBrowserView = (view, isSort = false) => {
603
798
  const views = window.getBrowserViews() || [];
604
799
  for (const view of views) {
605
800
  handleBrowserViewBlur(view);
606
801
  }
607
802
  _setBrowserView.call(window, view);
608
803
  handleBrowserViewFocus(view);
804
+ log('log', 'setBrowserView-sort', isSort);
805
+ if (isSort) {
806
+ this.sortBrowserViewsDebounced(window, view);
807
+ }
609
808
  };
610
809
  }
611
810
  catch (error) {
612
811
  log('error', 'focus', error);
613
812
  }
614
813
  }
814
+ if (options.url) {
815
+ // @ts-ignore
816
+ window.loadURL ? window.loadURL(options.url) : window.webContents.loadURL(options.url);
817
+ if (options.browserWindow?.focusable !== false) {
818
+ window?.focus?.();
819
+ }
820
+ window?.webContents?.focus?.();
821
+ }
615
822
  }
616
823
  catch (error) {
617
824
  log('error', 'create', error);
@@ -635,7 +842,7 @@ class WindowsManager {
635
842
  }
636
843
  const loadLoadingView = () => {
637
844
  const [viewWidth, viewHeight] = window.getSize();
638
- window.setBrowserView(_loadingView);
845
+ window.addBrowserView(_loadingView);
639
846
  _loadingView.setBounds({
640
847
  x: 0,
641
848
  y: 0,
@@ -675,7 +882,7 @@ class WindowsManager {
675
882
  return;
676
883
  }
677
884
  if (_loadingView.webContents && !_loadingView.webContents.isDestroyed()) {
678
- window.setBrowserView(_loadingView);
885
+ window.addBrowserView(_loadingView);
679
886
  }
680
887
  else {
681
888
  // if loadingView has been destroyed
@@ -875,6 +1082,86 @@ class WindowsManager {
875
1082
  if (typeof browserWindowOptions.alwaysOnTop === 'boolean') {
876
1083
  win.setAlwaysOnTop(browserWindowOptions.alwaysOnTop);
877
1084
  }
1085
+ // 设置背景颜色
1086
+ if (typeof browserWindowOptions.backgroundColor === 'string') {
1087
+ win.setBackgroundColor(browserWindowOptions.backgroundColor);
1088
+ }
1089
+ // 居中
1090
+ if (browserWindowOptions?.center !== false) {
1091
+ win.center();
1092
+ }
1093
+ // 设置窗口移动
1094
+ if (typeof browserWindowOptions.movable === 'boolean') {
1095
+ win.setMovable(browserWindowOptions.movable);
1096
+ }
1097
+ // 设置窗口大小调整
1098
+ if (typeof browserWindowOptions.resizable === 'boolean') {
1099
+ win.setResizable(browserWindowOptions.resizable);
1100
+ }
1101
+ // 设置全屏模式
1102
+ if (typeof browserWindowOptions.fullscreenable === 'boolean') {
1103
+ win.setFullScreenable(browserWindowOptions.fullscreenable);
1104
+ }
1105
+ // 设置窗口阴影
1106
+ if (typeof browserWindowOptions.hasShadow === 'boolean') {
1107
+ win.setHasShadow(browserWindowOptions.hasShadow);
1108
+ }
1109
+ // 设置窗口最小尺寸
1110
+ if (typeof browserWindowOptions.minWidth === 'number' && typeof browserWindowOptions.minHeight === 'number') {
1111
+ win.setMinimumSize(browserWindowOptions.minWidth, browserWindowOptions.minHeight);
1112
+ }
1113
+ // 设置窗口最大尺寸
1114
+ if (typeof browserWindowOptions.maxWidth === 'number' && typeof browserWindowOptions.maxHeight === 'number') {
1115
+ win.setMaximumSize(browserWindowOptions.maxWidth, browserWindowOptions.maxHeight);
1116
+ }
1117
+ // 设置窗口位置
1118
+ if (typeof browserWindowOptions.x === 'number' && typeof browserWindowOptions.y === 'number') {
1119
+ win.setPosition(browserWindowOptions.x, browserWindowOptions.y);
1120
+ }
1121
+ // 设置窗口标题
1122
+ if (typeof browserWindowOptions.title === 'string') {
1123
+ win.setTitle(browserWindowOptions.title);
1124
+ }
1125
+ // 设置窗口图标
1126
+ if (typeof browserWindowOptions.icon === 'string') {
1127
+ win.setIcon(browserWindowOptions.icon);
1128
+ }
1129
+ // 设置窗口菜单栏可见性
1130
+ if (typeof browserWindowOptions.autoHideMenuBar === 'boolean') {
1131
+ win.setAutoHideMenuBar(browserWindowOptions.autoHideMenuBar);
1132
+ }
1133
+ // 设置窗口最小化按钮
1134
+ if (browserWindowOptions.minimizable === false) {
1135
+ win.setMinimizable(false);
1136
+ }
1137
+ // 设置窗口最大化按钮
1138
+ if (browserWindowOptions.maximizable === false) {
1139
+ win.setMaximizable(false);
1140
+ }
1141
+ // 设置窗口关闭按钮
1142
+ if (browserWindowOptions.closable === false) {
1143
+ win.setClosable(false);
1144
+ }
1145
+ // 设置窗口焦点
1146
+ if (browserWindowOptions.focusable === false) {
1147
+ win.setFocusable(false);
1148
+ }
1149
+ // 设置窗口全屏
1150
+ if (browserWindowOptions.fullscreen === true) {
1151
+ win.setFullScreen(true);
1152
+ }
1153
+ // 设置窗口背景材质
1154
+ if (typeof browserWindowOptions.vibrancy === 'string') {
1155
+ win.setVibrancy(browserWindowOptions.vibrancy);
1156
+ }
1157
+ // 设置窗口透明度
1158
+ if (typeof browserWindowOptions.opacity === 'number') {
1159
+ win.setOpacity(browserWindowOptions.opacity);
1160
+ }
1161
+ // 设置窗口显示状态
1162
+ if (browserWindowOptions.show === false) {
1163
+ win.hide();
1164
+ }
878
1165
  // 可继续扩展其他动态属性
879
1166
  }
880
1167
  _applyBrowserViewOptions(view, options) {
@@ -890,12 +1177,26 @@ class WindowsManager {
890
1177
  if (typeof browserWindowOptions.width === 'number' && typeof browserWindowOptions.height === 'number') {
891
1178
  view.setBounds({ x: 0, y: 0, width: browserWindowOptions.width, height: browserWindowOptions.height });
892
1179
  }
1180
+ // 设置视图位置
1181
+ if (typeof browserWindowOptions.x === 'number' && typeof browserWindowOptions.y === 'number') {
1182
+ const bounds = view.getBounds();
1183
+ view.setBounds({
1184
+ x: browserWindowOptions.x,
1185
+ y: browserWindowOptions.y,
1186
+ width: bounds.width,
1187
+ height: bounds.height
1188
+ });
1189
+ }
1190
+ // 设置视图背景颜色
1191
+ if (typeof browserWindowOptions.backgroundColor === 'string') {
1192
+ view.setBackgroundColor(browserWindowOptions.backgroundColor);
1193
+ }
893
1194
  // 可继续扩展其他动态属性
894
1195
  }
895
1196
  // 生成一个bv 做为预加载资源窗口,加载完成后销毁
896
1197
  async createPreloadWebContents(url) {
897
1198
  return new Promise(async (resolve, reject) => {
898
- let bv = this.create({
1199
+ let bv = await this.create({
899
1200
  type: 'BV',
900
1201
  url,
901
1202
  name: `preload-web-contents-${md5(url)}`,
@@ -914,11 +1215,108 @@ class WindowsManager {
914
1215
  bv.webContents.loadURL(url);
915
1216
  });
916
1217
  }
1218
+ async getWindowForWebContentsId(wcId) {
1219
+ const wc = electron.webContents.fromId(wcId);
1220
+ if (!wc)
1221
+ return undefined;
1222
+ // Case 1: BrowserView
1223
+ for (const win of electron.BrowserWindow.getAllWindows()) {
1224
+ for (const view of win.getBrowserViews()) {
1225
+ if (view.webContents.id === wcId) {
1226
+ return win;
1227
+ }
1228
+ }
1229
+ }
1230
+ // Case 2: WebView
1231
+ // webview 有 hostWebContents,指向它所在的 BrowserWindow 的 webContents
1232
+ if (wc.hostWebContents) {
1233
+ return electron.BrowserWindow.fromWebContents(wc.hostWebContents);
1234
+ }
1235
+ // Case 3: 普通 window 本身
1236
+ const win = electron.BrowserWindow.fromWebContents(wc);
1237
+ if (win)
1238
+ return win;
1239
+ return undefined;
1240
+ }
1241
+ /**
1242
+ * 手动对BrowserView进行排序
1243
+ * @param windowId 窗口ID或名称
1244
+ */
1245
+ sortBrowserViews(windowId) {
1246
+ const window = this.get(windowId);
1247
+ if (window && window._type === 'BW') {
1248
+ this._sortBrowserViews(window);
1249
+ }
1250
+ }
1251
+ /**
1252
+ * 对BrowserView进行排序
1253
+ * @param window 目标窗口
1254
+ */
1255
+ _sortBrowserViews(window, addView) {
1256
+ try {
1257
+ const views = window.getBrowserViews() || [];
1258
+ if (views.length <= 1)
1259
+ return;
1260
+ log('log', 'sortBrowserViews', views?.map(i => i.webContents.id));
1261
+ // 按zIndex层级排序,数值小的在前
1262
+ const sortedViews = views.sort((a, b) => {
1263
+ const zIndexA = a._zIndex ?? 0;
1264
+ const zIndexB = b._zIndex ?? 0;
1265
+ return zIndexA - zIndexB;
1266
+ });
1267
+ // 检查是否已经按正确顺序排列
1268
+ let needsReorder = false;
1269
+ for (let i = 0; i < views.length; i++) {
1270
+ // @ts-ignore
1271
+ if (views[i].webContents.id !== sortedViews[i].webContents.id) {
1272
+ needsReorder = true;
1273
+ break;
1274
+ }
1275
+ }
1276
+ log('log', 'sortBrowserViews needsReorder', needsReorder, sortedViews?.map(i => i.webContents.id));
1277
+ // 如果已经按正确顺序排列,则不需要重新排序
1278
+ if (!needsReorder)
1279
+ return;
1280
+ // 使用批量操作来减少闪烁
1281
+ // 临时隐藏窗口内容
1282
+ const wasVisible = window.isVisible();
1283
+ if (wasVisible) {
1284
+ // window.hide();
1285
+ }
1286
+ // 移除所有BrowserView
1287
+ views.forEach(view => {
1288
+ if (addView?.webContents?.id !== view.webContents.id) {
1289
+ // @ts-ignore
1290
+ window.removeBrowserView(view, false);
1291
+ }
1292
+ });
1293
+ // 按正确顺序重新添加
1294
+ sortedViews.forEach((view, index) => {
1295
+ if (index === 0) {
1296
+ // 第一个设置为当前视图
1297
+ // @ts-ignore
1298
+ window.setBrowserView(view, false);
1299
+ }
1300
+ else {
1301
+ // 其他视图添加到后面
1302
+ // @ts-ignore
1303
+ window.addBrowserView(view, false);
1304
+ }
1305
+ });
1306
+ // 恢复窗口显示
1307
+ if (wasVisible) {
1308
+ // window.show();
1309
+ }
1310
+ }
1311
+ catch (error) {
1312
+ log('error', 'sortBrowserViews error:', error);
1313
+ }
1314
+ }
917
1315
  }
918
1316
  // @ts-ignore
919
1317
  global['__ELECTRON_WINDOWS_MANAGER__'] = undefined;
920
1318
  exports.isInitialized = false;
921
- const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl, webviewDomainWhiteList) => {
1319
+ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList) => {
922
1320
  // @ts-ignore
923
1321
  if (exports.isInitialized && global['__ELECTRON_WINDOWS_MANAGER__']) {
924
1322
  // @ts-ignore
@@ -926,7 +1324,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
926
1324
  }
927
1325
  exports.isInitialized = true;
928
1326
  // @ts-ignore
929
- const wm = global['__ELECTRON_WINDOWS_MANAGER__'] = new WindowsManager(preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl, webviewDomainWhiteList);
1327
+ const wm = global['__ELECTRON_WINDOWS_MANAGER__'] = new WindowsManager(preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList);
930
1328
  eIpc.mainIPC.handleRenderer('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', async (data) => {
931
1329
  if (data?.type === 'create') {
932
1330
  const opt = data;
@@ -942,21 +1340,39 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
942
1340
  findWin.webContents.reload();
943
1341
  }, 100);
944
1342
  }
1343
+ if (opt.data.browserWindow?.parent) {
1344
+ try {
1345
+ if (findWin._type === 'BW') {
1346
+ findWin.setParentWindow(electron.BrowserWindow.fromId(Number(opt.data.browserWindow.parent)));
1347
+ }
1348
+ if (findWin._type === 'BV') {
1349
+ electron.BrowserWindow.fromId(Number(opt.data.browserWindow.parent))?.addBrowserView(findWin);
1350
+ }
1351
+ }
1352
+ catch (error) {
1353
+ log('error', 'setParentWindow error:', error);
1354
+ }
1355
+ }
1356
+ if (findWin?._type === 'BV' && opt.data.zIndex) {
1357
+ findWin._zIndex = opt.data.zIndex;
1358
+ }
945
1359
  return {
946
1360
  winId: Number(`${findWin?.id || findWin?._id || -1}`),
947
1361
  winName: `${findWin?._name || ''}`,
948
1362
  winType: `${findWin?._type || ''}`,
949
1363
  winExtraData: `${findWin?._extraData || ''}`,
950
1364
  winInitUrl: `${findWin?._initUrl || ''}`,
1365
+ winZIndex: `${findWin._zIndex || 0}`,
951
1366
  };
952
1367
  }
953
- const res = wm.create(opt.data);
1368
+ const res = await wm.create(opt.data);
954
1369
  return {
955
1370
  winId: Number(`${res.id || res._id || -1}`),
956
1371
  winName: `${res?._name || ''}`,
957
1372
  winType: `${res?._type || ''}`,
958
1373
  winExtraData: `${res?._extraData || ''}`,
959
1374
  winInitUrl: `${res?._initUrl || ''}`,
1375
+ winZIndex: `${res?._zIndex || 0}`,
960
1376
  };
961
1377
  }
962
1378
  if (data?.type === 'get') {
@@ -968,6 +1384,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
968
1384
  winType: `${res?._type || ''}`,
969
1385
  winExtraData: `${res?._extraData || ''}`,
970
1386
  winInitUrl: `${res?._initUrl || ''}`,
1387
+ winZIndex: `${res?._zIndex || 0}`,
971
1388
  };
972
1389
  }
973
1390
  if (data?.type === 'getAll') {
@@ -981,6 +1398,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
981
1398
  winType: `${i?._type || ''}`,
982
1399
  winExtraData: `${i?._extraData || ''}`,
983
1400
  winInitUrl: `${i?._initUrl || ''}`,
1401
+ winZIndex: `${i?._zIndex || 0}`,
984
1402
  };
985
1403
  });
986
1404
  return obj;
@@ -1004,6 +1422,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
1004
1422
  winType: `${res?._type || ''}`,
1005
1423
  winExtraData: `${res?._extraData || ''}`,
1006
1424
  winInitUrl: `${res?._initUrl || ''}`,
1425
+ winZIndex: `${res?._zIndex || 0}`,
1007
1426
  };
1008
1427
  }
1009
1428
  return undefined;
@@ -1018,34 +1437,16 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
1018
1437
  winType: `${res?._type || ''}`,
1019
1438
  winExtraData: `${res?._extraData || ''}`,
1020
1439
  winInitUrl: `${res?._initUrl || ''}`,
1440
+ winZIndex: `${res?._zIndex || 0}`,
1021
1441
  };
1022
1442
  }
1023
1443
  return undefined;
1024
1444
  }
1025
- if (data?.type === 'getWindowForWebContentId') {
1445
+ if (data?.type === 'getWindowForWebContentsId') {
1026
1446
  const opt = data;
1027
- const targetWebContents = electron.webContents.fromId(opt.data);
1028
- if (targetWebContents) {
1029
- let win = electron.BrowserWindow.fromWebContents(targetWebContents);
1030
- if (!win) {
1031
- // 获取所有的 BrowserWindows
1032
- let allWindows = electron.BrowserWindow.getAllWindows();
1033
- // 遍历所有窗口,检查每个窗口的 BrowserView
1034
- for (let _win of allWindows) {
1035
- let views = _win.getBrowserViews();
1036
- // 遍历窗口的所有 BrowserView
1037
- for (let view of views) {
1038
- if (view.webContents === targetWebContents) {
1039
- win = _win;
1040
- break;
1041
- }
1042
- }
1043
- if (win)
1044
- break;
1045
- }
1046
- }
1047
- // @ts-ignore
1048
- return win?.id || win?._id;
1447
+ const res = await wm.getWindowForWebContentsId(opt.data);
1448
+ if (res) {
1449
+ return res?.id;
1049
1450
  }
1050
1451
  return undefined;
1051
1452
  }
@@ -1086,15 +1487,20 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
1086
1487
  return undefined;
1087
1488
  }
1088
1489
  // 是否开启预加载窗口
1089
- if (data?.type === 'setPreloadWebContentsUrl') {
1490
+ if (data?.type === 'setPreloadWebContentsConfig') {
1090
1491
  const opt = data;
1091
- wm.setPreloadWebContentsUrl(opt.data);
1492
+ wm.setPreloadWebContentsConfig(opt.data);
1092
1493
  }
1093
1494
  if (data?.type === 'createPreloadWebContents') {
1094
1495
  const opt = data;
1095
1496
  const res = await wm.createPreloadWebContents(opt.data);
1096
1497
  return res;
1097
1498
  }
1499
+ if (data?.type === 'sortBrowserViews') {
1500
+ const opt = data;
1501
+ wm.sortBrowserViews(opt.data);
1502
+ return true;
1503
+ }
1098
1504
  return undefined;
1099
1505
  });
1100
1506
  return wm;