@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/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');
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
- });
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
228
284
  instance.webContents.loadURL(url || 'about:blank');
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,87 @@ 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
+ console.error('useNativeLoadURL win.loadURL');
411
+ return originLoadURL.call(win, url);
412
+ }
413
+ try {
414
+ console.error('customLoadURL win.loadURL');
415
+ // @ts-ignore
416
+ await this.preloadWebContentsConfig.customLoadURL(url || 'about:blank', (url) => originLoadURL.call(win, url), win.webContents);
417
+ try {
418
+ win.emit('ready-to-show');
419
+ }
420
+ catch (error) {
421
+ log('error', 'emit ready-to-show event failed:', error);
422
+ }
423
+ resolve(undefined);
424
+ }
425
+ catch (error) {
426
+ reject(error);
427
+ }
428
+ });
429
+ };
430
+ }
431
+ const originWebContentsLoadURL = win.webContents.loadURL;
432
+ // @ts-ignore
433
+ win.webContents.loadURL = async (url, useNativeLoadURL = false) => {
434
+ return new Promise(async (resolve, reject) => {
435
+ if (useNativeLoadURL) {
436
+ console.error('useNativeLoadURL win.webContents.loadURL');
437
+ return originWebContentsLoadURL.call(win.webContents, url);
438
+ }
439
+ try {
440
+ console.error('customLoadURL win.webContents.loadURL');
441
+ // @ts-ignore
442
+ await this.preloadWebContentsConfig.customLoadURL(url || 'about:blank', (url) => originWebContentsLoadURL.call(win.webContents, url), win.webContents);
443
+ try {
444
+ win.webContents.emit('ready-to-show');
445
+ }
446
+ catch (error) {
447
+ log('error', 'emit ready-to-show event failed:', error);
448
+ }
449
+ resolve(undefined);
450
+ }
451
+ catch (error) {
452
+ reject(error);
453
+ }
454
+ });
455
+ };
456
+ }
457
+ catch (error) {
458
+ console.error('customLoadURL error', error);
459
+ }
460
+ }
278
461
  window = win;
279
462
  }
280
463
  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
- }
464
+ loadingView.url = `${loadingView?.url ?? this.loadingViewUrl}`;
465
+ lodash.merge(options, {
466
+ loadingView,
467
+ });
468
+ }
469
+ catch (error) {
470
+ log('error', 'loadingView error:', loadingView, this.loadingViewUrl);
471
+ }
472
+ try {
473
+ errorView.url = `${errorView?.url ?? this.errorViewUrl}`;
474
+ lodash.merge(options, {
475
+ errorView,
476
+ });
477
+ }
478
+ catch (error) {
479
+ log('error', 'errorView error:', errorView, this.errorViewUrl);
480
+ }
481
+ try {
299
482
  let parentWin = undefined;
300
483
  if (typeof browserWindowOptions?.parent === 'number') {
301
484
  parentWin = BrowserWindow.fromId(browserWindowOptions?.parent) || undefined;
@@ -323,6 +506,7 @@ class WindowsManager {
323
506
  nativeWindowOpen: true,
324
507
  webSecurity: false,
325
508
  preload: preload,
509
+ defaultEncoding: 'utf-8',
326
510
  }, browserWindowOptions?.webPreferences || {})
327
511
  }))
328
512
  : new BrowserWindow(lodash.merge({
@@ -339,6 +523,7 @@ class WindowsManager {
339
523
  nativeWindowOpen: true,
340
524
  webSecurity: false,
341
525
  preload: preload,
526
+ defaultEncoding: 'utf-8',
342
527
  }, browserWindowOptions?.webPreferences || {})
343
528
  }));
344
529
  log('log', `${name} 不使用 ${type === 'BV' ? 'preloadedBV' : 'preloadedBW'}`, window?.webContents?.id);
@@ -349,6 +534,8 @@ class WindowsManager {
349
534
  log('error', 'enable: ', error);
350
535
  }
351
536
  }
537
+ // 停止加载
538
+ // window.webContents?.stop?.();
352
539
  // @ts-ignore
353
540
  try {
354
541
  window.id = Number(`${window.id || window.webContents.id}`);
@@ -367,14 +554,16 @@ class WindowsManager {
367
554
  window._name = name;
368
555
  window._extraData = `${options?.extraData || ''}`;
369
556
  window._initUrl = `${options?.url || ''}`;
557
+ // 设置zIndex层级
558
+ window._zIndex = options.zIndex ?? 0;
370
559
  log('log', 'create 5: ', window.id, window._id, window._name);
371
- if (loadingView?.url) {
560
+ if (loadingView?.url && loadingView?.url !== 'about:blank') {
372
561
  if (type === 'BW') {
373
562
  // @ts-ignore
374
563
  this._setLoadingView(window, options);
375
564
  }
376
565
  }
377
- if (errorView?.url) {
566
+ if (errorView?.url && errorView?.url !== 'about:blank') {
378
567
  if (type === 'BW') {
379
568
  const showErrorView = lodash.debounce(() => {
380
569
  const _url = window._initUrl;
@@ -503,10 +692,6 @@ class WindowsManager {
503
692
  parentWin?.addBrowserView(window);
504
693
  log('log', 'create - addBrowserView');
505
694
  }
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
695
  this.windows.set(window.id || window._id || window.webContents.id, window);
511
696
  log('log', 'create', this.windows.keys());
512
697
  // 初始化值
@@ -576,29 +761,51 @@ class WindowsManager {
576
761
  });
577
762
  try {
578
763
  const _addBrowserView = window.addBrowserView;
579
- window.addBrowserView = (view) => {
764
+ window.addBrowserView = (view, isSort = false) => {
580
765
  _addBrowserView.call(window, view);
581
766
  handleBrowserViewFocus(view);
767
+ // 添加BrowserView后重新排序(如果未禁用自动排序)
768
+ log('log', 'addBrowserView-sort', isSort, window.getBrowserViews());
769
+ if (isSort) {
770
+ this.sortBrowserViewsDebounced(window, view);
771
+ }
582
772
  };
583
773
  const _removeBrowserView = window.removeBrowserView;
584
- window.removeBrowserView = (view) => {
774
+ window.removeBrowserView = (view, isSort = false) => {
585
775
  _removeBrowserView.call(window, view);
586
776
  handleBrowserViewBlur(view);
777
+ // 移除BrowserView后重新排序(如果未禁用自动排序)
778
+ log('log', 'removeBrowserView-sort', isSort);
779
+ if (isSort) {
780
+ this.sortBrowserViewsDebounced(window, view);
781
+ }
587
782
  };
588
783
  const _setBrowserView = window.setBrowserView;
589
- window.setBrowserView = (view) => {
784
+ window.setBrowserView = (view, isSort = false) => {
590
785
  const views = window.getBrowserViews() || [];
591
786
  for (const view of views) {
592
787
  handleBrowserViewBlur(view);
593
788
  }
594
789
  _setBrowserView.call(window, view);
595
790
  handleBrowserViewFocus(view);
791
+ log('log', 'setBrowserView-sort', isSort);
792
+ if (isSort) {
793
+ this.sortBrowserViewsDebounced(window, view);
794
+ }
596
795
  };
597
796
  }
598
797
  catch (error) {
599
798
  log('error', 'focus', error);
600
799
  }
601
800
  }
801
+ if (options.url) {
802
+ // @ts-ignore
803
+ window.loadURL ? window.loadURL(options.url) : window.webContents.loadURL(options.url);
804
+ if (options.browserWindow?.focusable !== false) {
805
+ window?.focus?.();
806
+ }
807
+ window?.webContents?.focus?.();
808
+ }
602
809
  }
603
810
  catch (error) {
604
811
  log('error', 'create', error);
@@ -622,7 +829,7 @@ class WindowsManager {
622
829
  }
623
830
  const loadLoadingView = () => {
624
831
  const [viewWidth, viewHeight] = window.getSize();
625
- window.setBrowserView(_loadingView);
832
+ window.addBrowserView(_loadingView);
626
833
  _loadingView.setBounds({
627
834
  x: 0,
628
835
  y: 0,
@@ -662,7 +869,7 @@ class WindowsManager {
662
869
  return;
663
870
  }
664
871
  if (_loadingView.webContents && !_loadingView.webContents.isDestroyed()) {
665
- window.setBrowserView(_loadingView);
872
+ window.addBrowserView(_loadingView);
666
873
  }
667
874
  else {
668
875
  // if loadingView has been destroyed
@@ -862,6 +1069,86 @@ class WindowsManager {
862
1069
  if (typeof browserWindowOptions.alwaysOnTop === 'boolean') {
863
1070
  win.setAlwaysOnTop(browserWindowOptions.alwaysOnTop);
864
1071
  }
1072
+ // 设置背景颜色
1073
+ if (typeof browserWindowOptions.backgroundColor === 'string') {
1074
+ win.setBackgroundColor(browserWindowOptions.backgroundColor);
1075
+ }
1076
+ // 居中
1077
+ if (browserWindowOptions?.center !== false) {
1078
+ win.center();
1079
+ }
1080
+ // 设置窗口移动
1081
+ if (typeof browserWindowOptions.movable === 'boolean') {
1082
+ win.setMovable(browserWindowOptions.movable);
1083
+ }
1084
+ // 设置窗口大小调整
1085
+ if (typeof browserWindowOptions.resizable === 'boolean') {
1086
+ win.setResizable(browserWindowOptions.resizable);
1087
+ }
1088
+ // 设置全屏模式
1089
+ if (typeof browserWindowOptions.fullscreenable === 'boolean') {
1090
+ win.setFullScreenable(browserWindowOptions.fullscreenable);
1091
+ }
1092
+ // 设置窗口阴影
1093
+ if (typeof browserWindowOptions.hasShadow === 'boolean') {
1094
+ win.setHasShadow(browserWindowOptions.hasShadow);
1095
+ }
1096
+ // 设置窗口最小尺寸
1097
+ if (typeof browserWindowOptions.minWidth === 'number' && typeof browserWindowOptions.minHeight === 'number') {
1098
+ win.setMinimumSize(browserWindowOptions.minWidth, browserWindowOptions.minHeight);
1099
+ }
1100
+ // 设置窗口最大尺寸
1101
+ if (typeof browserWindowOptions.maxWidth === 'number' && typeof browserWindowOptions.maxHeight === 'number') {
1102
+ win.setMaximumSize(browserWindowOptions.maxWidth, browserWindowOptions.maxHeight);
1103
+ }
1104
+ // 设置窗口位置
1105
+ if (typeof browserWindowOptions.x === 'number' && typeof browserWindowOptions.y === 'number') {
1106
+ win.setPosition(browserWindowOptions.x, browserWindowOptions.y);
1107
+ }
1108
+ // 设置窗口标题
1109
+ if (typeof browserWindowOptions.title === 'string') {
1110
+ win.setTitle(browserWindowOptions.title);
1111
+ }
1112
+ // 设置窗口图标
1113
+ if (typeof browserWindowOptions.icon === 'string') {
1114
+ win.setIcon(browserWindowOptions.icon);
1115
+ }
1116
+ // 设置窗口菜单栏可见性
1117
+ if (typeof browserWindowOptions.autoHideMenuBar === 'boolean') {
1118
+ win.setAutoHideMenuBar(browserWindowOptions.autoHideMenuBar);
1119
+ }
1120
+ // 设置窗口最小化按钮
1121
+ if (browserWindowOptions.minimizable === false) {
1122
+ win.setMinimizable(false);
1123
+ }
1124
+ // 设置窗口最大化按钮
1125
+ if (browserWindowOptions.maximizable === false) {
1126
+ win.setMaximizable(false);
1127
+ }
1128
+ // 设置窗口关闭按钮
1129
+ if (browserWindowOptions.closable === false) {
1130
+ win.setClosable(false);
1131
+ }
1132
+ // 设置窗口焦点
1133
+ if (browserWindowOptions.focusable === false) {
1134
+ win.setFocusable(false);
1135
+ }
1136
+ // 设置窗口全屏
1137
+ if (browserWindowOptions.fullscreen === true) {
1138
+ win.setFullScreen(true);
1139
+ }
1140
+ // 设置窗口背景材质
1141
+ if (typeof browserWindowOptions.vibrancy === 'string') {
1142
+ win.setVibrancy(browserWindowOptions.vibrancy);
1143
+ }
1144
+ // 设置窗口透明度
1145
+ if (typeof browserWindowOptions.opacity === 'number') {
1146
+ win.setOpacity(browserWindowOptions.opacity);
1147
+ }
1148
+ // 设置窗口显示状态
1149
+ if (browserWindowOptions.show === false) {
1150
+ win.hide();
1151
+ }
865
1152
  // 可继续扩展其他动态属性
866
1153
  }
867
1154
  _applyBrowserViewOptions(view, options) {
@@ -877,12 +1164,26 @@ class WindowsManager {
877
1164
  if (typeof browserWindowOptions.width === 'number' && typeof browserWindowOptions.height === 'number') {
878
1165
  view.setBounds({ x: 0, y: 0, width: browserWindowOptions.width, height: browserWindowOptions.height });
879
1166
  }
1167
+ // 设置视图位置
1168
+ if (typeof browserWindowOptions.x === 'number' && typeof browserWindowOptions.y === 'number') {
1169
+ const bounds = view.getBounds();
1170
+ view.setBounds({
1171
+ x: browserWindowOptions.x,
1172
+ y: browserWindowOptions.y,
1173
+ width: bounds.width,
1174
+ height: bounds.height
1175
+ });
1176
+ }
1177
+ // 设置视图背景颜色
1178
+ if (typeof browserWindowOptions.backgroundColor === 'string') {
1179
+ view.setBackgroundColor(browserWindowOptions.backgroundColor);
1180
+ }
880
1181
  // 可继续扩展其他动态属性
881
1182
  }
882
1183
  // 生成一个bv 做为预加载资源窗口,加载完成后销毁
883
1184
  async createPreloadWebContents(url) {
884
1185
  return new Promise(async (resolve, reject) => {
885
- let bv = this.create({
1186
+ let bv = await this.create({
886
1187
  type: 'BV',
887
1188
  url,
888
1189
  name: `preload-web-contents-${md5(url)}`,
@@ -901,11 +1202,108 @@ class WindowsManager {
901
1202
  bv.webContents.loadURL(url);
902
1203
  });
903
1204
  }
1205
+ async getWindowForWebContentsId(wcId) {
1206
+ const wc = webContents.fromId(wcId);
1207
+ if (!wc)
1208
+ return undefined;
1209
+ // Case 1: BrowserView
1210
+ for (const win of BrowserWindow.getAllWindows()) {
1211
+ for (const view of win.getBrowserViews()) {
1212
+ if (view.webContents.id === wcId) {
1213
+ return win;
1214
+ }
1215
+ }
1216
+ }
1217
+ // Case 2: WebView
1218
+ // webview 有 hostWebContents,指向它所在的 BrowserWindow 的 webContents
1219
+ if (wc.hostWebContents) {
1220
+ return BrowserWindow.fromWebContents(wc.hostWebContents);
1221
+ }
1222
+ // Case 3: 普通 window 本身
1223
+ const win = BrowserWindow.fromWebContents(wc);
1224
+ if (win)
1225
+ return win;
1226
+ return undefined;
1227
+ }
1228
+ /**
1229
+ * 手动对BrowserView进行排序
1230
+ * @param windowId 窗口ID或名称
1231
+ */
1232
+ sortBrowserViews(windowId) {
1233
+ const window = this.get(windowId);
1234
+ if (window && window._type === 'BW') {
1235
+ this._sortBrowserViews(window);
1236
+ }
1237
+ }
1238
+ /**
1239
+ * 对BrowserView进行排序
1240
+ * @param window 目标窗口
1241
+ */
1242
+ _sortBrowserViews(window, addView) {
1243
+ try {
1244
+ const views = window.getBrowserViews() || [];
1245
+ if (views.length <= 1)
1246
+ return;
1247
+ log('log', 'sortBrowserViews', views?.map(i => i.webContents.id));
1248
+ // 按zIndex层级排序,数值小的在前
1249
+ const sortedViews = views.sort((a, b) => {
1250
+ const zIndexA = a._zIndex ?? 0;
1251
+ const zIndexB = b._zIndex ?? 0;
1252
+ return zIndexA - zIndexB;
1253
+ });
1254
+ // 检查是否已经按正确顺序排列
1255
+ let needsReorder = false;
1256
+ for (let i = 0; i < views.length; i++) {
1257
+ // @ts-ignore
1258
+ if (views[i].webContents.id !== sortedViews[i].webContents.id) {
1259
+ needsReorder = true;
1260
+ break;
1261
+ }
1262
+ }
1263
+ log('log', 'sortBrowserViews needsReorder', needsReorder, sortedViews?.map(i => i.webContents.id));
1264
+ // 如果已经按正确顺序排列,则不需要重新排序
1265
+ if (!needsReorder)
1266
+ return;
1267
+ // 使用批量操作来减少闪烁
1268
+ // 临时隐藏窗口内容
1269
+ const wasVisible = window.isVisible();
1270
+ if (wasVisible) {
1271
+ // window.hide();
1272
+ }
1273
+ // 移除所有BrowserView
1274
+ views.forEach(view => {
1275
+ if (addView?.webContents?.id !== view.webContents.id) {
1276
+ // @ts-ignore
1277
+ window.removeBrowserView(view, false);
1278
+ }
1279
+ });
1280
+ // 按正确顺序重新添加
1281
+ sortedViews.forEach((view, index) => {
1282
+ if (index === 0) {
1283
+ // 第一个设置为当前视图
1284
+ // @ts-ignore
1285
+ window.setBrowserView(view, false);
1286
+ }
1287
+ else {
1288
+ // 其他视图添加到后面
1289
+ // @ts-ignore
1290
+ window.addBrowserView(view, false);
1291
+ }
1292
+ });
1293
+ // 恢复窗口显示
1294
+ if (wasVisible) {
1295
+ // window.show();
1296
+ }
1297
+ }
1298
+ catch (error) {
1299
+ log('error', 'sortBrowserViews error:', error);
1300
+ }
1301
+ }
904
1302
  }
905
1303
  // @ts-ignore
906
1304
  global['__ELECTRON_WINDOWS_MANAGER__'] = undefined;
907
1305
  let isInitialized = false;
908
- const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl, webviewDomainWhiteList) => {
1306
+ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList) => {
909
1307
  // @ts-ignore
910
1308
  if (isInitialized && global['__ELECTRON_WINDOWS_MANAGER__']) {
911
1309
  // @ts-ignore
@@ -913,7 +1311,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
913
1311
  }
914
1312
  isInitialized = true;
915
1313
  // @ts-ignore
916
- const wm = global['__ELECTRON_WINDOWS_MANAGER__'] = new WindowsManager(preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl, webviewDomainWhiteList);
1314
+ const wm = global['__ELECTRON_WINDOWS_MANAGER__'] = new WindowsManager(preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList);
917
1315
  eIpc.mainIPC.handleRenderer('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', async (data) => {
918
1316
  if (data?.type === 'create') {
919
1317
  const opt = data;
@@ -929,21 +1327,39 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
929
1327
  findWin.webContents.reload();
930
1328
  }, 100);
931
1329
  }
1330
+ if (opt.data.browserWindow?.parent) {
1331
+ try {
1332
+ if (findWin._type === 'BW') {
1333
+ findWin.setParentWindow(BrowserWindow.fromId(Number(opt.data.browserWindow.parent)));
1334
+ }
1335
+ if (findWin._type === 'BV') {
1336
+ BrowserWindow.fromId(Number(opt.data.browserWindow.parent))?.addBrowserView(findWin);
1337
+ }
1338
+ }
1339
+ catch (error) {
1340
+ log('error', 'setParentWindow error:', error);
1341
+ }
1342
+ }
1343
+ if (findWin?._type === 'BV' && opt.data.zIndex) {
1344
+ findWin._zIndex = opt.data.zIndex;
1345
+ }
932
1346
  return {
933
1347
  winId: Number(`${findWin?.id || findWin?._id || -1}`),
934
1348
  winName: `${findWin?._name || ''}`,
935
1349
  winType: `${findWin?._type || ''}`,
936
1350
  winExtraData: `${findWin?._extraData || ''}`,
937
1351
  winInitUrl: `${findWin?._initUrl || ''}`,
1352
+ winZIndex: `${findWin._zIndex || 0}`,
938
1353
  };
939
1354
  }
940
- const res = wm.create(opt.data);
1355
+ const res = await wm.create(opt.data);
941
1356
  return {
942
1357
  winId: Number(`${res.id || res._id || -1}`),
943
1358
  winName: `${res?._name || ''}`,
944
1359
  winType: `${res?._type || ''}`,
945
1360
  winExtraData: `${res?._extraData || ''}`,
946
1361
  winInitUrl: `${res?._initUrl || ''}`,
1362
+ winZIndex: `${res?._zIndex || 0}`,
947
1363
  };
948
1364
  }
949
1365
  if (data?.type === 'get') {
@@ -955,6 +1371,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
955
1371
  winType: `${res?._type || ''}`,
956
1372
  winExtraData: `${res?._extraData || ''}`,
957
1373
  winInitUrl: `${res?._initUrl || ''}`,
1374
+ winZIndex: `${res?._zIndex || 0}`,
958
1375
  };
959
1376
  }
960
1377
  if (data?.type === 'getAll') {
@@ -968,6 +1385,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
968
1385
  winType: `${i?._type || ''}`,
969
1386
  winExtraData: `${i?._extraData || ''}`,
970
1387
  winInitUrl: `${i?._initUrl || ''}`,
1388
+ winZIndex: `${i?._zIndex || 0}`,
971
1389
  };
972
1390
  });
973
1391
  return obj;
@@ -991,6 +1409,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
991
1409
  winType: `${res?._type || ''}`,
992
1410
  winExtraData: `${res?._extraData || ''}`,
993
1411
  winInitUrl: `${res?._initUrl || ''}`,
1412
+ winZIndex: `${res?._zIndex || 0}`,
994
1413
  };
995
1414
  }
996
1415
  return undefined;
@@ -1005,34 +1424,16 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
1005
1424
  winType: `${res?._type || ''}`,
1006
1425
  winExtraData: `${res?._extraData || ''}`,
1007
1426
  winInitUrl: `${res?._initUrl || ''}`,
1427
+ winZIndex: `${res?._zIndex || 0}`,
1008
1428
  };
1009
1429
  }
1010
1430
  return undefined;
1011
1431
  }
1012
- if (data?.type === 'getWindowForWebContentId') {
1432
+ if (data?.type === 'getWindowForWebContentsId') {
1013
1433
  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;
1434
+ const res = await wm.getWindowForWebContentsId(opt.data);
1435
+ if (res) {
1436
+ return res?.id;
1036
1437
  }
1037
1438
  return undefined;
1038
1439
  }
@@ -1073,15 +1474,20 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
1073
1474
  return undefined;
1074
1475
  }
1075
1476
  // 是否开启预加载窗口
1076
- if (data?.type === 'setPreloadWebContentsUrl') {
1477
+ if (data?.type === 'setPreloadWebContentsConfig') {
1077
1478
  const opt = data;
1078
- wm.setPreloadWebContentsUrl(opt.data);
1479
+ wm.setPreloadWebContentsConfig(opt.data);
1079
1480
  }
1080
1481
  if (data?.type === 'createPreloadWebContents') {
1081
1482
  const opt = data;
1082
1483
  const res = await wm.createPreloadWebContents(opt.data);
1083
1484
  return res;
1084
1485
  }
1486
+ if (data?.type === 'sortBrowserViews') {
1487
+ const opt = data;
1488
+ wm.sortBrowserViews(opt.data);
1489
+ return true;
1490
+ }
1085
1491
  return undefined;
1086
1492
  });
1087
1493
  return wm;