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

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,7 +56,7 @@ 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
61
  this.preloadedBW = null;
62
62
  // 预加载的窗口(无边框,有按钮)
@@ -67,29 +67,39 @@ class WindowsManager {
67
67
  this.preloadedBV = null;
68
68
  this.preloading = false;
69
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);
70
80
  this.preload = preload;
71
81
  this.windows = new Map();
72
82
  this.loadingViewUrl = `${loadingViewUrl ?? ''}`;
73
83
  this.errorViewUrl = `${errorViewUrl ?? ''}`;
74
- this.preloadWebContentsUrl = `${preloadWebContentsUrl ?? ''}`;
84
+ this.preloadWebContentsConfig = preloadWebContentsConfig;
75
85
  this.webviewDomainWhiteList = webviewDomainWhiteList || [];
76
- log('log', 'preloadWebContentsUrl: ', this.preloadWebContentsUrl);
77
- if (this.preloadWebContentsUrl) {
78
- app.on('ready', () => {
79
- if (this.preloadWebContentsUrl) {
80
- this.setPreloadWebContentsUrl(this.preloadWebContentsUrl);
86
+ log('log', 'preloadWebContentsConfig: ', this.preloadWebContentsConfig);
87
+ if (this.preloadWebContentsConfig) {
88
+ app.whenReady().then(() => {
89
+ if (this.preloadWebContentsConfig) {
90
+ this.setPreloadWebContentsConfig(this.preloadWebContentsConfig);
81
91
  }
82
92
  });
83
93
  }
84
94
  }
85
95
  /**
86
- * 设置预加载的webContents的url
87
- * @param preloadWebContentsUrl 预加载的webContents的url
96
+ * 设置预加载的webContents配置
97
+ * @param preloadWebContentsConfig 预加载的webContents配置
88
98
  */
89
- setPreloadWebContentsUrl(preloadWebContentsUrl) {
99
+ setPreloadWebContentsConfig(preloadWebContentsConfig) {
90
100
  try {
91
- this.preloadWebContentsUrl = preloadWebContentsUrl;
92
- if (this.preloadWebContentsUrl) {
101
+ this.preloadWebContentsConfig = preloadWebContentsConfig;
102
+ if (this.preloadWebContentsConfig) {
93
103
  this._preloadInstances();
94
104
  }
95
105
  else {
@@ -100,7 +110,7 @@ class WindowsManager {
100
110
  }
101
111
  }
102
112
  catch (error) {
103
- log('error', 'setPreloadWebContentsUrl error:', error);
113
+ log('error', 'setPreloadWebContentsConfig error:', error);
104
114
  }
105
115
  }
106
116
  /**
@@ -111,23 +121,54 @@ class WindowsManager {
111
121
  return;
112
122
  this.preloading = true;
113
123
  try {
114
- if (this.preloadWebContentsUrl) {
115
- // 预加载的窗口
116
- this.preloadedBW = this.preloadedBW || await this._createPreloadBW({});
117
- // 预加载的窗口(无边框,有按钮)
118
- this.preloadedBW_FramelessWithButtons = this.preloadedBW_FramelessWithButtons || await this._createPreloadBW({
119
- frame: false,
120
- transparent: true,
121
- titleBarStyle: 'hidden',
122
- });
123
- // 预加载的窗口(无边框,无按钮)
124
- this.preloadedBW_FramelessNoButtons = this.preloadedBW_FramelessNoButtons || await this._createPreloadBW({
125
- frame: false,
126
- transparent: true,
127
- titleBarStyle: 'customButtonsOnHover',
128
- });
129
- // 预加载的BV
130
- 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
+ }
131
172
  }
132
173
  }
133
174
  catch (e) {
@@ -143,20 +184,24 @@ class WindowsManager {
143
184
  _createPreloadBW(options = {}) {
144
185
  return new Promise((resolve) => {
145
186
  const preload = this.preload;
146
- const url = this.preloadWebContentsUrl;
147
- if (this.preloadWebContentsUrl) {
187
+ const url = this.preloadWebContentsConfig?.url;
188
+ if (this.preloadWebContentsConfig?.url) {
189
+ const webPreferences = (options.webPreferences || {});
148
190
  const instance = new BrowserWindow({
191
+ useContentSize: true,
149
192
  show: false,
193
+ backgroundColor: '#ffffff',
150
194
  ...options,
151
195
  webPreferences: {
196
+ ...webPreferences,
152
197
  webviewTag: true,
153
198
  plugins: true,
154
199
  nodeIntegration: true,
155
200
  contextIsolation: false,
156
201
  backgroundThrottling: false,
157
202
  webSecurity: false,
158
- preload: preload,
159
- ...(options.webPreferences || {}),
203
+ preload: webPreferences.preload || preload,
204
+ defaultEncoding: 'utf-8',
160
205
  }
161
206
  });
162
207
  try {
@@ -173,14 +218,19 @@ class WindowsManager {
173
218
  log('error', '预加载 BW 设置 _id 失败', error);
174
219
  }
175
220
  // @ts-ignore
176
- log('log', '创建预BW: ', instance._id, this.preloadWebContentsUrl);
177
- instance.webContents.once('did-finish-load', () => {
178
- resolve(instance);
179
- });
180
- instance.webContents.once('did-fail-load', () => {
181
- resolve(instance);
182
- });
183
- 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
+ // @ts-ignore
229
+ instance.loadURL(url ? `${url}` : 'about:blank', true);
230
+ resolve(instance);
231
+ }
232
+ else {
233
+ resolve(null);
184
234
  }
185
235
  });
186
236
  }
@@ -188,20 +238,23 @@ class WindowsManager {
188
238
  * 创建预加载的浏览器视图
189
239
  * @returns 预加载的浏览器视图
190
240
  */
191
- _createPreloadBV() {
241
+ _createPreloadBV(options = {}) {
192
242
  return new Promise((resolve) => {
193
243
  const preload = this.preload;
194
- const url = this.preloadWebContentsUrl;
195
- if (this.preloadWebContentsUrl) {
244
+ const url = this.preloadWebContentsConfig?.url;
245
+ if (this.preloadWebContentsConfig?.url) {
246
+ const webPreferences = (options.webPreferences || {});
196
247
  const instance = new BrowserView({
197
248
  webPreferences: {
249
+ ...webPreferences,
198
250
  webviewTag: true,
199
251
  plugins: true,
200
252
  nodeIntegration: true,
201
253
  contextIsolation: false,
202
254
  // backgroundThrottling: false,
203
255
  webSecurity: false,
204
- preload: preload,
256
+ preload: webPreferences.preload || preload,
257
+ defaultEncoding: 'utf-8',
205
258
  }
206
259
  });
207
260
  try {
@@ -213,61 +266,131 @@ class WindowsManager {
213
266
  try {
214
267
  // @ts-ignore
215
268
  instance._id = instance.webContents.id;
269
+ // 设置默认zIndex层级
270
+ instance._zIndex = 0;
216
271
  }
217
272
  catch (error) {
218
273
  log('error', '预加载 BV 设置 _id 失败', error);
219
274
  }
220
275
  // @ts-ignore
221
- log('log', '创建预BV: ', instance._id, this.preloadWebContentsUrl);
222
- instance.webContents.once('did-finish-load', () => {
223
- resolve(instance);
224
- });
225
- instance.webContents.once('did-fail-load', () => {
226
- resolve(instance);
227
- });
228
- instance.webContents.loadURL(url || 'about:blank');
276
+ log('log', '创建预BV: ', instance._id, this.preloadWebContentsConfig?.url);
277
+ // instance.webContents.once('did-finish-load', () => {
278
+ // resolve(instance as BVItem);
279
+ // });
280
+ // instance.webContents.once('did-fail-load', () => {
281
+ // resolve(instance as BVItem);
282
+ // });
283
+ // @ts-ignore
284
+ instance.webContents.loadURL(url || 'about:blank', true);
285
+ resolve(instance);
286
+ }
287
+ else {
288
+ resolve(null);
229
289
  }
230
290
  });
231
291
  }
232
292
  create(options) {
293
+ return new Promise((resolve, reject) => {
294
+ // 将创建请求添加到队列
295
+ this.createQueue.push({ options, resolve, reject });
296
+ // 如果当前没有在创建,则开始处理队列
297
+ if (!this.isCreating) {
298
+ this.processCreateQueue();
299
+ }
300
+ });
301
+ }
302
+ /**
303
+ * 处理创建队列
304
+ */
305
+ async processCreateQueue() {
306
+ if (this.isCreating || this.createQueue.length === 0) {
307
+ return;
308
+ }
309
+ this.isCreating = true;
310
+ while (this.createQueue.length > 0) {
311
+ const { options, resolve, reject } = this.createQueue.shift();
312
+ try {
313
+ const window = await this._createWindow(options);
314
+ resolve(window);
315
+ }
316
+ catch (error) {
317
+ log('error', 'create window failed:', error);
318
+ reject(error);
319
+ }
320
+ }
321
+ this.isCreating = false;
322
+ }
323
+ /**
324
+ * 实际的窗口创建逻辑
325
+ */
326
+ async _createWindow(options) {
233
327
  let window;
234
- const { usePreload = true, type = 'BW', name = 'anonymous', url, loadingView = { url: undefined }, errorView = { url: undefined }, browserWindow: browserWindowOptions, openDevTools = false, preventOriginClose = false, } = options;
328
+ const { usePreload = true, type = 'BW', name = 'anonymous', url, loadingView = { url: undefined }, errorView = { url: undefined }, browserWindow: browserWindowOptions, openDevTools = false, preventOriginClose = false, zIndex = 0, } = options;
235
329
  options.type = type;
236
330
  // 优先复用预创建实例
237
331
  let preloadWin = null;
238
- if (type === 'BW' && usePreload && this.preloadWebContentsUrl) {
332
+ if (type === 'BW' && usePreload && this.preloadWebContentsConfig?.url) {
239
333
  const bwOptions = browserWindowOptions || {};
240
- if (bwOptions.frame === false && bwOptions.titleBarStyle === 'hidden') {
241
- preloadWin = this.preloadedBW_FramelessWithButtons;
242
- this.preloadedBW_FramelessWithButtons = null;
243
- setTimeout(() => this._createPreloadBW({ frame: false, transparent: true, titleBarStyle: 'hidden' }), 0);
244
- }
245
- else if (bwOptions.frame === false && bwOptions.titleBarStyle === 'customButtonsOnHover') {
246
- preloadWin = this.preloadedBW_FramelessNoButtons;
247
- this.preloadedBW_FramelessNoButtons = null;
248
- setTimeout(() => this._createPreloadBW({ frame: false, transparent: true, titleBarStyle: 'customButtonsOnHover' }), 0);
334
+ if (bwOptions.frame === false) {
335
+ if (bwOptions.titleBarStyle === 'default' || !bwOptions.titleBarStyle) {
336
+ if (this.preloadWebContentsConfig.enableBW_FramelessNoButtons !== false && this.preloadedBW_FramelessNoButtons) {
337
+ preloadWin = this.preloadedBW_FramelessNoButtons;
338
+ this.preloadedBW_FramelessNoButtons = await this._createPreloadBW({
339
+ frame: false,
340
+ // transparent: true,
341
+ titleBarStyle: 'default',
342
+ webPreferences: {
343
+ preload: bwOptions?.webPreferences?.preload || this.preload,
344
+ }
345
+ });
346
+ }
347
+ }
348
+ else {
349
+ if (this.preloadWebContentsConfig.enableBW_FramelessWithButtons !== false && this.preloadedBW_FramelessWithButtons) {
350
+ preloadWin = this.preloadedBW_FramelessWithButtons;
351
+ this.preloadedBW_FramelessWithButtons = await this._createPreloadBW({
352
+ frame: false,
353
+ // transparent: true,
354
+ titleBarStyle: 'hidden',
355
+ webPreferences: {
356
+ preload: this.preload,
357
+ }
358
+ });
359
+ }
360
+ }
249
361
  }
250
362
  else {
251
- preloadWin = this.preloadedBW;
252
- this.preloadedBW = null;
253
- setTimeout(() => this._createPreloadBW({}), 0);
363
+ if (this.preloadWebContentsConfig.enableBW !== false && this.preloadedBW) {
364
+ preloadWin = this.preloadedBW;
365
+ this.preloadedBW = await this._createPreloadBW({
366
+ webPreferences: {
367
+ preload: bwOptions?.webPreferences?.preload || this.preload,
368
+ }
369
+ });
370
+ }
254
371
  }
255
372
  }
256
- if (type === 'BV' && usePreload && this.preloadWebContentsUrl) {
257
- preloadWin = this.preloadedBV;
258
- this.preloadedBV = null;
259
- setTimeout(() => this._createPreloadBV(), 0);
373
+ if (type === 'BV' && usePreload && this.preloadWebContentsConfig?.url) {
374
+ const bvOptions = browserWindowOptions || {};
375
+ if (this.preloadWebContentsConfig.enableBV !== false && this.preloadedBV) {
376
+ preloadWin = this.preloadedBV;
377
+ this.preloadedBV = await this._createPreloadBV({
378
+ webPreferences: {
379
+ preload: bvOptions?.webPreferences?.preload || this.preload,
380
+ }
381
+ });
382
+ }
260
383
  }
261
384
  if (preloadWin) {
262
385
  const win = preloadWin;
263
- log('log', `${name} 使用预加载窗口`, win._id);
386
+ log('log', `${name} 使用预加载窗口(${type})`, win._id);
264
387
  win._type = 'BW';
265
388
  win._name = options.name || 'anonymous';
266
389
  win._extraData = `${options?.extraData || ''}`;
267
390
  win._initUrl = `${options?.url || ''}`;
268
391
  // @ts-ignore
269
- win?.removeAllListeners && win?.removeAllListeners?.();
270
- win.webContents.removeAllListeners && win.webContents.removeAllListeners();
392
+ // win?.removeAllListeners && win?.removeAllListeners?.();
393
+ // win.webContents.removeAllListeners && win.webContents.removeAllListeners();
271
394
  if (type === 'BW') {
272
395
  // @ts-ignore
273
396
  this._applyBrowserWindowOptions(win, options);
@@ -275,27 +398,85 @@ class WindowsManager {
275
398
  if (type === 'BV') {
276
399
  this._applyBrowserViewOptions(win, options);
277
400
  }
401
+ if (typeof this.preloadWebContentsConfig?.customLoadURL === 'function') {
402
+ try {
403
+ if (type === 'BW') {
404
+ // @ts-ignore
405
+ const originLoadURL = win.loadURL;
406
+ // @ts-ignore
407
+ win.loadURL = async (url, useNativeLoadURL = false) => {
408
+ return new Promise(async (resolve, reject) => {
409
+ if (useNativeLoadURL) {
410
+ return originLoadURL.call(win, url);
411
+ }
412
+ try {
413
+ console.error('customLoadURL win.loadURL');
414
+ // @ts-ignore
415
+ await this.preloadWebContentsConfig.customLoadURL(url || 'about:blank', (url) => originLoadURL.call(win, url), win.webContents);
416
+ try {
417
+ win.emit('ready-to-show');
418
+ }
419
+ catch (error) {
420
+ log('error', 'emit ready-to-show event failed:', error);
421
+ }
422
+ resolve(undefined);
423
+ }
424
+ catch (error) {
425
+ reject(error);
426
+ }
427
+ });
428
+ };
429
+ }
430
+ const originWebContentsLoadURL = win.webContents.loadURL;
431
+ // @ts-ignore
432
+ win.webContents.loadURL = async (url, useNativeLoadURL = false) => {
433
+ return new Promise(async (resolve, reject) => {
434
+ if (useNativeLoadURL) {
435
+ return originWebContentsLoadURL.call(win.webContents, url);
436
+ }
437
+ try {
438
+ console.error('customLoadURL win.webContents.loadURL');
439
+ // @ts-ignore
440
+ await this.preloadWebContentsConfig.customLoadURL(url || 'about:blank', (url) => originWebContentsLoadURL.call(win.webContents, url), win.webContents);
441
+ try {
442
+ win.webContents.emit('ready-to-show');
443
+ }
444
+ catch (error) {
445
+ log('error', 'emit ready-to-show event failed:', error);
446
+ }
447
+ resolve(undefined);
448
+ }
449
+ catch (error) {
450
+ reject(error);
451
+ }
452
+ });
453
+ };
454
+ }
455
+ catch (error) {
456
+ console.error('customLoadURL error', error);
457
+ }
458
+ }
278
459
  window = win;
279
460
  }
280
461
  try {
281
- try {
282
- loadingView.url = `${loadingView?.url ?? this.loadingViewUrl}`;
283
- lodash.merge(options, {
284
- loadingView,
285
- });
286
- }
287
- catch (error) {
288
- log('error', 'loadingView error:', loadingView, this.loadingViewUrl);
289
- }
290
- try {
291
- errorView.url = `${errorView?.url ?? this.errorViewUrl}`;
292
- lodash.merge(options, {
293
- errorView,
294
- });
295
- }
296
- catch (error) {
297
- log('error', 'errorView error:', errorView, this.errorViewUrl);
298
- }
462
+ loadingView.url = `${loadingView?.url ?? this.loadingViewUrl}`;
463
+ lodash.merge(options, {
464
+ loadingView,
465
+ });
466
+ }
467
+ catch (error) {
468
+ log('error', 'loadingView error:', loadingView, this.loadingViewUrl);
469
+ }
470
+ try {
471
+ errorView.url = `${errorView?.url ?? this.errorViewUrl}`;
472
+ lodash.merge(options, {
473
+ errorView,
474
+ });
475
+ }
476
+ catch (error) {
477
+ log('error', 'errorView error:', errorView, this.errorViewUrl);
478
+ }
479
+ try {
299
480
  let parentWin = undefined;
300
481
  if (typeof browserWindowOptions?.parent === 'number') {
301
482
  parentWin = BrowserWindow.fromId(browserWindowOptions?.parent) || undefined;
@@ -323,6 +504,7 @@ class WindowsManager {
323
504
  nativeWindowOpen: true,
324
505
  webSecurity: false,
325
506
  preload: preload,
507
+ defaultEncoding: 'utf-8',
326
508
  }, browserWindowOptions?.webPreferences || {})
327
509
  }))
328
510
  : new BrowserWindow(lodash.merge({
@@ -339,6 +521,7 @@ class WindowsManager {
339
521
  nativeWindowOpen: true,
340
522
  webSecurity: false,
341
523
  preload: preload,
524
+ defaultEncoding: 'utf-8',
342
525
  }, browserWindowOptions?.webPreferences || {})
343
526
  }));
344
527
  log('log', `${name} 不使用 ${type === 'BV' ? 'preloadedBV' : 'preloadedBW'}`, window?.webContents?.id);
@@ -349,6 +532,8 @@ class WindowsManager {
349
532
  log('error', 'enable: ', error);
350
533
  }
351
534
  }
535
+ // 停止加载
536
+ // window.webContents?.stop?.();
352
537
  // @ts-ignore
353
538
  try {
354
539
  window.id = Number(`${window.id || window.webContents.id}`);
@@ -367,14 +552,16 @@ class WindowsManager {
367
552
  window._name = name;
368
553
  window._extraData = `${options?.extraData || ''}`;
369
554
  window._initUrl = `${options?.url || ''}`;
555
+ // 设置zIndex层级
556
+ window._zIndex = options.zIndex ?? 0;
370
557
  log('log', 'create 5: ', window.id, window._id, window._name);
371
- if (loadingView?.url) {
558
+ if (loadingView?.url && loadingView?.url !== 'about:blank') {
372
559
  if (type === 'BW') {
373
560
  // @ts-ignore
374
561
  this._setLoadingView(window, options);
375
562
  }
376
563
  }
377
- if (errorView?.url) {
564
+ if (errorView?.url && errorView?.url !== 'about:blank') {
378
565
  if (type === 'BW') {
379
566
  const showErrorView = lodash.debounce(() => {
380
567
  const _url = window._initUrl;
@@ -503,10 +690,6 @@ class WindowsManager {
503
690
  parentWin?.addBrowserView(window);
504
691
  log('log', 'create - addBrowserView');
505
692
  }
506
- // @ts-ignore
507
- window.loadURL ? window.loadURL(options.url) : window.webContents.loadURL(options.url);
508
- // @ts-ignore
509
- window.focus ? window.focus() : window.webContents.focus();
510
693
  this.windows.set(window.id || window._id || window.webContents.id, window);
511
694
  log('log', 'create', this.windows.keys());
512
695
  // 初始化值
@@ -576,29 +759,51 @@ class WindowsManager {
576
759
  });
577
760
  try {
578
761
  const _addBrowserView = window.addBrowserView;
579
- window.addBrowserView = (view) => {
762
+ window.addBrowserView = (view, isSort = false) => {
580
763
  _addBrowserView.call(window, view);
581
764
  handleBrowserViewFocus(view);
765
+ // 添加BrowserView后重新排序(如果未禁用自动排序)
766
+ log('log', 'addBrowserView-sort', isSort, window.getBrowserViews());
767
+ if (isSort) {
768
+ this.sortBrowserViewsDebounced(window, view);
769
+ }
582
770
  };
583
771
  const _removeBrowserView = window.removeBrowserView;
584
- window.removeBrowserView = (view) => {
772
+ window.removeBrowserView = (view, isSort = false) => {
585
773
  _removeBrowserView.call(window, view);
586
774
  handleBrowserViewBlur(view);
775
+ // 移除BrowserView后重新排序(如果未禁用自动排序)
776
+ log('log', 'removeBrowserView-sort', isSort);
777
+ if (isSort) {
778
+ this.sortBrowserViewsDebounced(window, view);
779
+ }
587
780
  };
588
781
  const _setBrowserView = window.setBrowserView;
589
- window.setBrowserView = (view) => {
782
+ window.setBrowserView = (view, isSort = false) => {
590
783
  const views = window.getBrowserViews() || [];
591
784
  for (const view of views) {
592
785
  handleBrowserViewBlur(view);
593
786
  }
594
787
  _setBrowserView.call(window, view);
595
788
  handleBrowserViewFocus(view);
789
+ log('log', 'setBrowserView-sort', isSort);
790
+ if (isSort) {
791
+ this.sortBrowserViewsDebounced(window, view);
792
+ }
596
793
  };
597
794
  }
598
795
  catch (error) {
599
796
  log('error', 'focus', error);
600
797
  }
601
798
  }
799
+ if (options.url) {
800
+ // @ts-ignore
801
+ window.loadURL ? window.loadURL(options.url) : window.webContents.loadURL(options.url);
802
+ if (options.browserWindow?.focusable !== false) {
803
+ window?.focus?.();
804
+ }
805
+ window?.webContents?.focus?.();
806
+ }
602
807
  }
603
808
  catch (error) {
604
809
  log('error', 'create', error);
@@ -622,7 +827,7 @@ class WindowsManager {
622
827
  }
623
828
  const loadLoadingView = () => {
624
829
  const [viewWidth, viewHeight] = window.getSize();
625
- window.setBrowserView(_loadingView);
830
+ window.addBrowserView(_loadingView);
626
831
  _loadingView.setBounds({
627
832
  x: 0,
628
833
  y: 0,
@@ -662,7 +867,7 @@ class WindowsManager {
662
867
  return;
663
868
  }
664
869
  if (_loadingView.webContents && !_loadingView.webContents.isDestroyed()) {
665
- window.setBrowserView(_loadingView);
870
+ window.addBrowserView(_loadingView);
666
871
  }
667
872
  else {
668
873
  // if loadingView has been destroyed
@@ -862,6 +1067,86 @@ class WindowsManager {
862
1067
  if (typeof browserWindowOptions.alwaysOnTop === 'boolean') {
863
1068
  win.setAlwaysOnTop(browserWindowOptions.alwaysOnTop);
864
1069
  }
1070
+ // 设置背景颜色
1071
+ if (typeof browserWindowOptions.backgroundColor === 'string') {
1072
+ win.setBackgroundColor(browserWindowOptions.backgroundColor);
1073
+ }
1074
+ // 居中
1075
+ if (browserWindowOptions?.center !== false) {
1076
+ win.center();
1077
+ }
1078
+ // 设置窗口移动
1079
+ if (typeof browserWindowOptions.movable === 'boolean') {
1080
+ win.setMovable(browserWindowOptions.movable);
1081
+ }
1082
+ // 设置窗口大小调整
1083
+ if (typeof browserWindowOptions.resizable === 'boolean') {
1084
+ win.setResizable(browserWindowOptions.resizable);
1085
+ }
1086
+ // 设置全屏模式
1087
+ if (typeof browserWindowOptions.fullscreenable === 'boolean') {
1088
+ win.setFullScreenable(browserWindowOptions.fullscreenable);
1089
+ }
1090
+ // 设置窗口阴影
1091
+ if (typeof browserWindowOptions.hasShadow === 'boolean') {
1092
+ win.setHasShadow(browserWindowOptions.hasShadow);
1093
+ }
1094
+ // 设置窗口最小尺寸
1095
+ if (typeof browserWindowOptions.minWidth === 'number' && typeof browserWindowOptions.minHeight === 'number') {
1096
+ win.setMinimumSize(browserWindowOptions.minWidth, browserWindowOptions.minHeight);
1097
+ }
1098
+ // 设置窗口最大尺寸
1099
+ if (typeof browserWindowOptions.maxWidth === 'number' && typeof browserWindowOptions.maxHeight === 'number') {
1100
+ win.setMaximumSize(browserWindowOptions.maxWidth, browserWindowOptions.maxHeight);
1101
+ }
1102
+ // 设置窗口位置
1103
+ if (typeof browserWindowOptions.x === 'number' && typeof browserWindowOptions.y === 'number') {
1104
+ win.setPosition(browserWindowOptions.x, browserWindowOptions.y);
1105
+ }
1106
+ // 设置窗口标题
1107
+ if (typeof browserWindowOptions.title === 'string') {
1108
+ win.setTitle(browserWindowOptions.title);
1109
+ }
1110
+ // 设置窗口图标
1111
+ if (typeof browserWindowOptions.icon === 'string') {
1112
+ win.setIcon(browserWindowOptions.icon);
1113
+ }
1114
+ // 设置窗口菜单栏可见性
1115
+ if (typeof browserWindowOptions.autoHideMenuBar === 'boolean') {
1116
+ win.setAutoHideMenuBar(browserWindowOptions.autoHideMenuBar);
1117
+ }
1118
+ // 设置窗口最小化按钮
1119
+ if (browserWindowOptions.minimizable === false) {
1120
+ win.setMinimizable(false);
1121
+ }
1122
+ // 设置窗口最大化按钮
1123
+ if (browserWindowOptions.maximizable === false) {
1124
+ win.setMaximizable(false);
1125
+ }
1126
+ // 设置窗口关闭按钮
1127
+ if (browserWindowOptions.closable === false) {
1128
+ win.setClosable(false);
1129
+ }
1130
+ // 设置窗口焦点
1131
+ if (browserWindowOptions.focusable === false) {
1132
+ win.setFocusable(false);
1133
+ }
1134
+ // 设置窗口全屏
1135
+ if (browserWindowOptions.fullscreen === true) {
1136
+ win.setFullScreen(true);
1137
+ }
1138
+ // 设置窗口背景材质
1139
+ if (typeof browserWindowOptions.vibrancy === 'string') {
1140
+ win.setVibrancy(browserWindowOptions.vibrancy);
1141
+ }
1142
+ // 设置窗口透明度
1143
+ if (typeof browserWindowOptions.opacity === 'number') {
1144
+ win.setOpacity(browserWindowOptions.opacity);
1145
+ }
1146
+ // 设置窗口显示状态
1147
+ if (browserWindowOptions.show === false) {
1148
+ win.hide();
1149
+ }
865
1150
  // 可继续扩展其他动态属性
866
1151
  }
867
1152
  _applyBrowserViewOptions(view, options) {
@@ -877,12 +1162,26 @@ class WindowsManager {
877
1162
  if (typeof browserWindowOptions.width === 'number' && typeof browserWindowOptions.height === 'number') {
878
1163
  view.setBounds({ x: 0, y: 0, width: browserWindowOptions.width, height: browserWindowOptions.height });
879
1164
  }
1165
+ // 设置视图位置
1166
+ if (typeof browserWindowOptions.x === 'number' && typeof browserWindowOptions.y === 'number') {
1167
+ const bounds = view.getBounds();
1168
+ view.setBounds({
1169
+ x: browserWindowOptions.x,
1170
+ y: browserWindowOptions.y,
1171
+ width: bounds.width,
1172
+ height: bounds.height
1173
+ });
1174
+ }
1175
+ // 设置视图背景颜色
1176
+ if (typeof browserWindowOptions.backgroundColor === 'string') {
1177
+ view.setBackgroundColor(browserWindowOptions.backgroundColor);
1178
+ }
880
1179
  // 可继续扩展其他动态属性
881
1180
  }
882
1181
  // 生成一个bv 做为预加载资源窗口,加载完成后销毁
883
1182
  async createPreloadWebContents(url) {
884
1183
  return new Promise(async (resolve, reject) => {
885
- let bv = this.create({
1184
+ let bv = await this.create({
886
1185
  type: 'BV',
887
1186
  url,
888
1187
  name: `preload-web-contents-${md5(url)}`,
@@ -901,11 +1200,108 @@ class WindowsManager {
901
1200
  bv.webContents.loadURL(url);
902
1201
  });
903
1202
  }
1203
+ async getWindowForWebContentsId(wcId) {
1204
+ const wc = webContents.fromId(wcId);
1205
+ if (!wc)
1206
+ return undefined;
1207
+ // Case 1: BrowserView
1208
+ for (const win of BrowserWindow.getAllWindows()) {
1209
+ for (const view of win.getBrowserViews()) {
1210
+ if (view.webContents.id === wcId) {
1211
+ return win;
1212
+ }
1213
+ }
1214
+ }
1215
+ // Case 2: WebView
1216
+ // webview 有 hostWebContents,指向它所在的 BrowserWindow 的 webContents
1217
+ if (wc.hostWebContents) {
1218
+ return BrowserWindow.fromWebContents(wc.hostWebContents);
1219
+ }
1220
+ // Case 3: 普通 window 本身
1221
+ const win = BrowserWindow.fromWebContents(wc);
1222
+ if (win)
1223
+ return win;
1224
+ return undefined;
1225
+ }
1226
+ /**
1227
+ * 手动对BrowserView进行排序
1228
+ * @param windowId 窗口ID或名称
1229
+ */
1230
+ sortBrowserViews(windowId) {
1231
+ const window = this.get(windowId);
1232
+ if (window && window._type === 'BW') {
1233
+ this._sortBrowserViews(window);
1234
+ }
1235
+ }
1236
+ /**
1237
+ * 对BrowserView进行排序
1238
+ * @param window 目标窗口
1239
+ */
1240
+ _sortBrowserViews(window, addView) {
1241
+ try {
1242
+ const views = window.getBrowserViews() || [];
1243
+ if (views.length <= 1)
1244
+ return;
1245
+ log('log', 'sortBrowserViews', views?.map(i => i.webContents.id));
1246
+ // 按zIndex层级排序,数值小的在前
1247
+ const sortedViews = views.sort((a, b) => {
1248
+ const zIndexA = a._zIndex ?? 0;
1249
+ const zIndexB = b._zIndex ?? 0;
1250
+ return zIndexA - zIndexB;
1251
+ });
1252
+ // 检查是否已经按正确顺序排列
1253
+ let needsReorder = false;
1254
+ for (let i = 0; i < views.length; i++) {
1255
+ // @ts-ignore
1256
+ if (views[i].webContents.id !== sortedViews[i].webContents.id) {
1257
+ needsReorder = true;
1258
+ break;
1259
+ }
1260
+ }
1261
+ log('log', 'sortBrowserViews needsReorder', needsReorder, sortedViews?.map(i => i.webContents.id));
1262
+ // 如果已经按正确顺序排列,则不需要重新排序
1263
+ if (!needsReorder)
1264
+ return;
1265
+ // 使用批量操作来减少闪烁
1266
+ // 临时隐藏窗口内容
1267
+ const wasVisible = window.isVisible();
1268
+ if (wasVisible) {
1269
+ // window.hide();
1270
+ }
1271
+ // 移除所有BrowserView
1272
+ views.forEach(view => {
1273
+ if (addView?.webContents?.id !== view.webContents.id) {
1274
+ // @ts-ignore
1275
+ window.removeBrowserView(view, false);
1276
+ }
1277
+ });
1278
+ // 按正确顺序重新添加
1279
+ sortedViews.forEach((view, index) => {
1280
+ if (index === 0) {
1281
+ // 第一个设置为当前视图
1282
+ // @ts-ignore
1283
+ window.setBrowserView(view, false);
1284
+ }
1285
+ else {
1286
+ // 其他视图添加到后面
1287
+ // @ts-ignore
1288
+ window.addBrowserView(view, false);
1289
+ }
1290
+ });
1291
+ // 恢复窗口显示
1292
+ if (wasVisible) {
1293
+ // window.show();
1294
+ }
1295
+ }
1296
+ catch (error) {
1297
+ log('error', 'sortBrowserViews error:', error);
1298
+ }
1299
+ }
904
1300
  }
905
1301
  // @ts-ignore
906
1302
  global['__ELECTRON_WINDOWS_MANAGER__'] = undefined;
907
1303
  let isInitialized = false;
908
- const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl, webviewDomainWhiteList) => {
1304
+ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList) => {
909
1305
  // @ts-ignore
910
1306
  if (isInitialized && global['__ELECTRON_WINDOWS_MANAGER__']) {
911
1307
  // @ts-ignore
@@ -913,7 +1309,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
913
1309
  }
914
1310
  isInitialized = true;
915
1311
  // @ts-ignore
916
- const wm = global['__ELECTRON_WINDOWS_MANAGER__'] = new WindowsManager(preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl, webviewDomainWhiteList);
1312
+ const wm = global['__ELECTRON_WINDOWS_MANAGER__'] = new WindowsManager(preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList);
917
1313
  eIpc.mainIPC.handleRenderer('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', async (data) => {
918
1314
  if (data?.type === 'create') {
919
1315
  const opt = data;
@@ -929,21 +1325,39 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
929
1325
  findWin.webContents.reload();
930
1326
  }, 100);
931
1327
  }
1328
+ if (opt.data.browserWindow?.parent) {
1329
+ try {
1330
+ if (findWin._type === 'BW') {
1331
+ findWin.setParentWindow(BrowserWindow.fromId(Number(opt.data.browserWindow.parent)));
1332
+ }
1333
+ if (findWin._type === 'BV') {
1334
+ BrowserWindow.fromId(Number(opt.data.browserWindow.parent))?.addBrowserView(findWin);
1335
+ }
1336
+ }
1337
+ catch (error) {
1338
+ log('error', 'setParentWindow error:', error);
1339
+ }
1340
+ }
1341
+ if (findWin?._type === 'BV' && opt.data.zIndex) {
1342
+ findWin._zIndex = opt.data.zIndex;
1343
+ }
932
1344
  return {
933
1345
  winId: Number(`${findWin?.id || findWin?._id || -1}`),
934
1346
  winName: `${findWin?._name || ''}`,
935
1347
  winType: `${findWin?._type || ''}`,
936
1348
  winExtraData: `${findWin?._extraData || ''}`,
937
1349
  winInitUrl: `${findWin?._initUrl || ''}`,
1350
+ winZIndex: `${findWin._zIndex || 0}`,
938
1351
  };
939
1352
  }
940
- const res = wm.create(opt.data);
1353
+ const res = await wm.create(opt.data);
941
1354
  return {
942
1355
  winId: Number(`${res.id || res._id || -1}`),
943
1356
  winName: `${res?._name || ''}`,
944
1357
  winType: `${res?._type || ''}`,
945
1358
  winExtraData: `${res?._extraData || ''}`,
946
1359
  winInitUrl: `${res?._initUrl || ''}`,
1360
+ winZIndex: `${res?._zIndex || 0}`,
947
1361
  };
948
1362
  }
949
1363
  if (data?.type === 'get') {
@@ -955,6 +1369,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
955
1369
  winType: `${res?._type || ''}`,
956
1370
  winExtraData: `${res?._extraData || ''}`,
957
1371
  winInitUrl: `${res?._initUrl || ''}`,
1372
+ winZIndex: `${res?._zIndex || 0}`,
958
1373
  };
959
1374
  }
960
1375
  if (data?.type === 'getAll') {
@@ -968,6 +1383,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
968
1383
  winType: `${i?._type || ''}`,
969
1384
  winExtraData: `${i?._extraData || ''}`,
970
1385
  winInitUrl: `${i?._initUrl || ''}`,
1386
+ winZIndex: `${i?._zIndex || 0}`,
971
1387
  };
972
1388
  });
973
1389
  return obj;
@@ -991,6 +1407,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
991
1407
  winType: `${res?._type || ''}`,
992
1408
  winExtraData: `${res?._extraData || ''}`,
993
1409
  winInitUrl: `${res?._initUrl || ''}`,
1410
+ winZIndex: `${res?._zIndex || 0}`,
994
1411
  };
995
1412
  }
996
1413
  return undefined;
@@ -1005,34 +1422,16 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
1005
1422
  winType: `${res?._type || ''}`,
1006
1423
  winExtraData: `${res?._extraData || ''}`,
1007
1424
  winInitUrl: `${res?._initUrl || ''}`,
1425
+ winZIndex: `${res?._zIndex || 0}`,
1008
1426
  };
1009
1427
  }
1010
1428
  return undefined;
1011
1429
  }
1012
- if (data?.type === 'getWindowForWebContentId') {
1430
+ if (data?.type === 'getWindowForWebContentsId') {
1013
1431
  const opt = data;
1014
- const targetWebContents = webContents.fromId(opt.data);
1015
- if (targetWebContents) {
1016
- let win = BrowserWindow.fromWebContents(targetWebContents);
1017
- if (!win) {
1018
- // 获取所有的 BrowserWindows
1019
- let allWindows = BrowserWindow.getAllWindows();
1020
- // 遍历所有窗口,检查每个窗口的 BrowserView
1021
- for (let _win of allWindows) {
1022
- let views = _win.getBrowserViews();
1023
- // 遍历窗口的所有 BrowserView
1024
- for (let view of views) {
1025
- if (view.webContents === targetWebContents) {
1026
- win = _win;
1027
- break;
1028
- }
1029
- }
1030
- if (win)
1031
- break;
1032
- }
1033
- }
1034
- // @ts-ignore
1035
- return win?.id || win?._id;
1432
+ const res = await wm.getWindowForWebContentsId(opt.data);
1433
+ if (res) {
1434
+ return res?.id;
1036
1435
  }
1037
1436
  return undefined;
1038
1437
  }
@@ -1073,15 +1472,20 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
1073
1472
  return undefined;
1074
1473
  }
1075
1474
  // 是否开启预加载窗口
1076
- if (data?.type === 'setPreloadWebContentsUrl') {
1475
+ if (data?.type === 'setPreloadWebContentsConfig') {
1077
1476
  const opt = data;
1078
- wm.setPreloadWebContentsUrl(opt.data);
1477
+ wm.setPreloadWebContentsConfig(opt.data);
1079
1478
  }
1080
1479
  if (data?.type === 'createPreloadWebContents') {
1081
1480
  const opt = data;
1082
1481
  const res = await wm.createPreloadWebContents(opt.data);
1083
1482
  return res;
1084
1483
  }
1484
+ if (data?.type === 'sortBrowserViews') {
1485
+ const opt = data;
1486
+ wm.sortBrowserViews(opt.data);
1487
+ return true;
1488
+ }
1085
1489
  return undefined;
1086
1490
  });
1087
1491
  return wm;