@lynker-desktop/electron-window-manager 0.0.9-alpha.8 → 0.0.9-alpha.80

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
@@ -1,22 +1,34 @@
1
1
  const lodash = require('lodash');
2
+ const events = require('events');
2
3
  const electron = require('electron');
3
4
  const remote = require('@electron/remote/main');
4
5
  const eIpc = require('@lynker-desktop/electron-ipc/main');
5
6
  const md5 = require('md5');
7
+ const PQueue = require('p-queue');
6
8
 
7
9
  function _interopNamespaceDefault(e) {
8
- const n = Object.create(null);
9
- if (e) {
10
- for (const k in e) {
11
- n[k] = e[k];
10
+ const n = Object.create(null);
11
+ if (e) {
12
+ for (const k in e) {
13
+ n[k] = e[k];
14
+ }
12
15
  }
13
- }
14
- n.default = e;
15
- return n;
16
+ n.default = e;
17
+ return n;
16
18
  }
17
19
 
18
20
  const remote__namespace = /*#__PURE__*/_interopNamespaceDefault(remote);
19
21
 
22
+ const getQueue = (() => {
23
+ let queue;
24
+ return async () => {
25
+ if (!queue) {
26
+ // const { default: PQueue } = await import('p-queue')
27
+ queue = new PQueue({ concurrency: 1 });
28
+ }
29
+ return queue;
30
+ };
31
+ })();
20
32
  const getCustomSession = (() => {
21
33
  let customSession;
22
34
  return () => {
@@ -36,23 +48,32 @@ const enable = (win) => {
36
48
  remote__namespace.enable(win);
37
49
  };
38
50
  const initWebContentsVal = (win, preload) => {
39
- win.webContents.executeJavaScript(`
40
51
  try {
41
- const data = {
42
- __ELECTRON_WINDOW_MANAGER_WEB_CONTENTS_ID__: ${JSON.stringify(win.webContents.id)},
43
- __ELECTRON_WINDOW_MANAGER_TYPE__: ${JSON.stringify(win._type)},
44
- __ELECTRON_WINDOW_MANAGER_NAME__: ${JSON.stringify(win._name)},
45
- __ELECTRON_WINDOW_MANAGER_EXTRA_DATA__: ${JSON.stringify(win._extraData || '')},
46
- __ELECTRON_WINDOW_MANAGER_PRELOAD__: ${JSON.stringify(preload)},
47
- __ELECTRON_WINDOW_MANAGER_INIT_URL__: ${JSON.stringify(win._initUrl || '')}
48
- };
49
- Object.entries(data).forEach(([key, value]) => {
50
- window[key] = value;
51
- });
52
- } catch (error) {}
53
- `);
52
+ if (!win?.webContents || win.webContents.isDestroyed()) {
53
+ return;
54
+ }
55
+ const webContentsId = win.webContents.id;
56
+ win.webContents.executeJavaScript(`
57
+ try {
58
+ const data = {
59
+ __ELECTRON_WINDOW_MANAGER_WEB_CONTENTS_ID__: ${JSON.stringify(webContentsId)},
60
+ __ELECTRON_WINDOW_MANAGER_TYPE__: ${JSON.stringify(win._type)},
61
+ __ELECTRON_WINDOW_MANAGER_NAME__: ${JSON.stringify(win._name)},
62
+ __ELECTRON_WINDOW_MANAGER_EXTRA_DATA__: ${JSON.stringify(win._extraData || '')},
63
+ __ELECTRON_WINDOW_MANAGER_PRELOAD__: ${JSON.stringify(preload)},
64
+ __ELECTRON_WINDOW_MANAGER_INIT_URL__: ${JSON.stringify(win._initUrl || '')}
65
+ };
66
+ Object.entries(data).forEach(([key, value]) => {
67
+ window[key] = value;
68
+ });
69
+ } catch (error) {}
70
+ `);
71
+ }
72
+ catch (error) {
73
+ // 忽略错误,webContents 可能已销毁
74
+ }
54
75
  };
55
- class WindowsManager {
76
+ class WindowsManager extends events.EventEmitter {
56
77
  /**
57
78
  * webview 域名白名单
58
79
  * 传入格式示例:
@@ -69,7 +90,10 @@ class WindowsManager {
69
90
  * - 不带点的(如 example.com)只匹配主域名。
70
91
  * - 'localhost'、'127.0.0.1'、'::1' 以及局域网 IP(如 192.168.x.x、10.x.x.x、172.16.x.x~172.31.x.x)都视为本地白名单。
71
92
  */
72
- constructor(preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl, webviewDomainWhiteList) {
93
+ constructor(preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList) {
94
+ super();
95
+ // 按名称索引的 Map,用于加速查找
96
+ this.windowsByName = new Map();
73
97
  // 预加载的窗口
74
98
  this.preloadedBW = null;
75
99
  // 预加载的窗口(无边框,有按钮)
@@ -80,33 +104,75 @@ class WindowsManager {
80
104
  this.preloadedBV = null;
81
105
  this.preloading = false;
82
106
  this.webviewDomainWhiteList = [];
83
- // 创建队列相关属性
84
- this.createQueue = [];
85
- this.isCreating = false;
107
+ // 窗口销毁检查的防抖函数,避免频繁检查
108
+ this.cleanupDestroyedWindowsDebounced = lodash.debounce(() => {
109
+ this._cleanupDestroyedWindows();
110
+ }, 500);
111
+ /**
112
+ * 防抖的排序方法
113
+ * @param window 目标窗口
114
+ */
115
+ this.sortBrowserViewsDebounced = lodash.debounce((window, view) => {
116
+ this._sortBrowserViews(window, view);
117
+ }, 50);
86
118
  this.preload = preload;
87
- this.windows = new Map();
119
+ // 使用 Proxy 监听 windows Map 的变化
120
+ const windowsMap = new Map();
121
+ this.windows = new Proxy(windowsMap, {
122
+ set: (target, prop, value) => {
123
+ const result = Reflect.set(target, prop, value);
124
+ this._emitDidAnyChange();
125
+ return result;
126
+ },
127
+ get: (target, prop) => {
128
+ const value = Reflect.get(target, prop);
129
+ // 如果是函数,需要绑定到原始 Map 实例
130
+ if (typeof value === 'function') {
131
+ // 拦截会修改 Map 的方法
132
+ if (prop === 'set' || prop === 'delete' || prop === 'clear') {
133
+ return (...args) => {
134
+ const result = value.apply(target, args);
135
+ this._emitDidAnyChange();
136
+ return result;
137
+ };
138
+ }
139
+ // 其他方法(如 forEach, get, has 等)直接绑定到 target
140
+ return value.bind(target);
141
+ }
142
+ return value;
143
+ }
144
+ });
88
145
  this.loadingViewUrl = `${loadingViewUrl ?? ''}`;
89
146
  this.errorViewUrl = `${errorViewUrl ?? ''}`;
90
- this.preloadWebContentsUrl = `${preloadWebContentsUrl ?? ''}`;
147
+ this.preloadWebContentsConfig = preloadWebContentsConfig;
91
148
  this.webviewDomainWhiteList = webviewDomainWhiteList || [];
92
- log('log', 'preloadWebContentsUrl: ', this.preloadWebContentsUrl);
93
- if (this.preloadWebContentsUrl) {
94
- electron.app.on('ready', () => {
95
- if (this.preloadWebContentsUrl) {
96
- this.setPreloadWebContentsUrl(this.preloadWebContentsUrl);
149
+ log('log', 'preloadWebContentsConfig: ', this.preloadWebContentsConfig);
150
+ if (this.preloadWebContentsConfig) {
151
+ if (this.preloadWebContentsConfig.nodeIntegration === undefined) {
152
+ this.preloadWebContentsConfig.nodeIntegration = true;
153
+ }
154
+ if (this.preloadWebContentsConfig.contextIsolation === undefined) {
155
+ this.preloadWebContentsConfig.contextIsolation = false;
156
+ }
157
+ getQueue();
158
+ electron.app.whenReady().then(() => {
159
+ if (this.preloadWebContentsConfig) {
160
+ this.setPreloadWebContentsConfig(this.preloadWebContentsConfig);
97
161
  }
98
162
  });
99
163
  }
100
164
  }
101
165
  /**
102
- * 设置预加载的webContents的url
103
- * @param preloadWebContentsUrl 预加载的webContents的url
166
+ * 设置预加载的webContents配置
167
+ * @param preloadWebContentsConfig 预加载的webContents配置
104
168
  */
105
- setPreloadWebContentsUrl(preloadWebContentsUrl) {
169
+ setPreloadWebContentsConfig(preloadWebContentsConfig) {
106
170
  try {
107
- this.preloadWebContentsUrl = preloadWebContentsUrl;
108
- if (this.preloadWebContentsUrl) {
109
- this._preloadInstances();
171
+ this.preloadWebContentsConfig = preloadWebContentsConfig;
172
+ if (this.preloadWebContentsConfig) {
173
+ getQueue().then(q => q.add(async () => {
174
+ return await this._preloadInstances();
175
+ }));
110
176
  }
111
177
  else {
112
178
  this.preloadedBW = null;
@@ -116,9 +182,23 @@ class WindowsManager {
116
182
  }
117
183
  }
118
184
  catch (error) {
119
- log('error', 'setPreloadWebContentsUrl error:', error);
185
+ log('error', 'setPreloadWebContentsConfig error:', error);
120
186
  }
121
187
  }
188
+ /**
189
+ * Promise 超时包装函数
190
+ * @param promise 要包装的 Promise
191
+ * @param timeout 超时时间(毫秒)
192
+ * @param errorMessage 超时错误信息
193
+ */
194
+ async _withTimeout(promise, timeout, errorMessage) {
195
+ const timeoutPromise = new Promise((_, reject) => {
196
+ setTimeout(() => {
197
+ reject(new Error(errorMessage));
198
+ }, timeout);
199
+ });
200
+ return Promise.race([promise, timeoutPromise]);
201
+ }
122
202
  /**
123
203
  * 预加载实例
124
204
  */
@@ -127,23 +207,64 @@ class WindowsManager {
127
207
  return;
128
208
  this.preloading = true;
129
209
  try {
130
- if (this.preloadWebContentsUrl) {
131
- // 预加载的窗口
132
- this.preloadedBW = this.preloadedBW || await this._createPreloadBW({});
133
- // 预加载的窗口(无边框,有按钮)
134
- this.preloadedBW_FramelessWithButtons = this.preloadedBW_FramelessWithButtons || await this._createPreloadBW({
135
- frame: false,
136
- transparent: true,
137
- titleBarStyle: 'hidden',
138
- });
139
- // 预加载的窗口(无边框,无按钮)
140
- this.preloadedBW_FramelessNoButtons = this.preloadedBW_FramelessNoButtons || await this._createPreloadBW({
141
- frame: false,
142
- transparent: true,
143
- titleBarStyle: 'customButtonsOnHover',
144
- });
145
- // 预加载的BV
146
- this.preloadedBV = this.preloadedBV || await this._createPreloadBV();
210
+ if (this.preloadWebContentsConfig) {
211
+ log('log', 'preloadWebContentsConfig: ', this.preloadWebContentsConfig);
212
+ const preloadPromises = [];
213
+ // 根据配置决定是否预加载普通窗口
214
+ if (this.preloadWebContentsConfig.enableBW !== false && !this.preloadedBW) {
215
+ preloadPromises.push(this._createPreloadBW({}).then(i => {
216
+ this.preloadedBW = i;
217
+ log('log', 'init preloadedBW: ', !!this.preloadedBW);
218
+ }).catch(error => {
219
+ log('error', '预加载 BW 失败:', error);
220
+ }));
221
+ }
222
+ // 根据配置决定是否预加载无边框有按钮的窗口
223
+ if (this.preloadWebContentsConfig.enableBW_FramelessWithButtons !== false && !this.preloadedBW_FramelessWithButtons) {
224
+ preloadPromises.push(this._createPreloadBW({
225
+ frame: false,
226
+ autoHideMenuBar: true,
227
+ titleBarStyle: 'hidden',
228
+ }).then(i => {
229
+ this.preloadedBW_FramelessWithButtons = i;
230
+ log('log', 'init preloadedBW_FramelessWithButtons: ', !!this.preloadedBW_FramelessWithButtons);
231
+ }).catch(error => {
232
+ log('error', '预加载 BW_FramelessWithButtons 失败:', error);
233
+ }));
234
+ }
235
+ // 根据配置决定是否预加载无边框无按钮的窗口
236
+ if (this.preloadWebContentsConfig.enableBW_FramelessNoButtons !== false && !this.preloadedBW_FramelessNoButtons) {
237
+ preloadPromises.push(this._createPreloadBW({
238
+ frame: false,
239
+ autoHideMenuBar: true,
240
+ titleBarStyle: 'default',
241
+ }).then(i => {
242
+ this.preloadedBW_FramelessNoButtons = i;
243
+ log('log', 'init preloadedBW_FramelessNoButtons: ', !!this.preloadedBW_FramelessNoButtons);
244
+ }).catch(error => {
245
+ log('error', '预加载 BW_FramelessNoButtons 失败:', error);
246
+ }));
247
+ }
248
+ // 根据配置决定是否预加载浏览器视图
249
+ if (this.preloadWebContentsConfig.enableBV !== false && !this.preloadedBV) {
250
+ preloadPromises.push(this._createPreloadBV().then(i => {
251
+ this.preloadedBV = i;
252
+ log('log', 'init preloadedBV: ', !!this.preloadedBV);
253
+ }).catch(error => {
254
+ log('error', '预加载 BV 失败:', error);
255
+ }));
256
+ }
257
+ if (preloadPromises.length > 0) {
258
+ // 添加超时机制,默认 10 秒超时
259
+ const timeout = 10000; // 10 秒
260
+ try {
261
+ await this._withTimeout(Promise.allSettled(preloadPromises), timeout, `预加载超时(${timeout}ms)`);
262
+ }
263
+ catch (error) {
264
+ log('error', '预加载超时:', error);
265
+ // 超时后继续执行,不阻塞后续流程
266
+ }
267
+ }
147
268
  }
148
269
  }
149
270
  catch (e) {
@@ -159,20 +280,25 @@ class WindowsManager {
159
280
  _createPreloadBW(options = {}) {
160
281
  return new Promise((resolve) => {
161
282
  const preload = this.preload;
162
- const url = this.preloadWebContentsUrl;
163
- if (this.preloadWebContentsUrl) {
283
+ const url = this.preloadWebContentsConfig?.url;
284
+ if (this.preloadWebContentsConfig?.url) {
285
+ const webPreferences = (options.webPreferences || {});
164
286
  const instance = new electron.BrowserWindow({
165
- ...options,
287
+ useContentSize: true,
166
288
  show: false,
289
+ backgroundColor: '#ffffff',
290
+ ...options,
167
291
  webPreferences: {
168
- ...(options.webPreferences || {}),
292
+ ...webPreferences,
293
+ sandbox: false,
169
294
  webviewTag: true,
170
295
  plugins: true,
171
- nodeIntegration: true,
172
- contextIsolation: false,
296
+ nodeIntegration: this.preloadWebContentsConfig?.nodeIntegration ?? true,
297
+ contextIsolation: this.preloadWebContentsConfig?.contextIsolation ?? false,
173
298
  backgroundThrottling: false,
174
299
  webSecurity: false,
175
- preload: preload,
300
+ preload: webPreferences.preload || preload,
301
+ defaultEncoding: 'utf-8',
176
302
  }
177
303
  });
178
304
  try {
@@ -182,43 +308,55 @@ class WindowsManager {
182
308
  log('error', '预加载 BW 设置 remote 失败', error);
183
309
  }
184
310
  try {
185
- // @ts-ignore
186
- instance._id = instance.webContents.id;
311
+ const webContentsId = instance.webContents?.id;
312
+ if (webContentsId !== undefined) {
313
+ // @ts-ignore
314
+ instance._id = webContentsId;
315
+ }
187
316
  }
188
317
  catch (error) {
189
318
  log('error', '预加载 BW 设置 _id 失败', error);
190
319
  }
191
320
  // @ts-ignore
192
- log('log', '创建预BW: ', instance._id, this.preloadWebContentsUrl);
321
+ const webContentsId = instance.webContents?.id;
322
+ log('log', '创建预BW: ', webContentsId, this.preloadWebContentsConfig?.url);
193
323
  // instance.webContents.once('did-finish-load', () => {
194
324
  // resolve(instance as BWItem);
195
325
  // });
196
326
  // instance.webContents.once('did-fail-load', () => {
197
327
  // resolve(instance as BWItem);
198
328
  // });
329
+ // @ts-ignore
199
330
  instance.loadURL(url ? `${url}` : 'about:blank');
200
331
  resolve(instance);
201
332
  }
333
+ else {
334
+ resolve(null);
335
+ }
202
336
  });
203
337
  }
204
338
  /**
205
339
  * 创建预加载的浏览器视图
206
340
  * @returns 预加载的浏览器视图
207
341
  */
208
- _createPreloadBV() {
342
+ _createPreloadBV(options = {}) {
209
343
  return new Promise((resolve) => {
210
344
  const preload = this.preload;
211
- const url = this.preloadWebContentsUrl;
212
- if (this.preloadWebContentsUrl) {
345
+ const url = this.preloadWebContentsConfig?.url;
346
+ if (this.preloadWebContentsConfig?.url) {
347
+ const webPreferences = (options.webPreferences || {});
213
348
  const instance = new electron.BrowserView({
214
349
  webPreferences: {
350
+ ...webPreferences,
351
+ sandbox: false,
215
352
  webviewTag: true,
216
353
  plugins: true,
217
- nodeIntegration: true,
218
- contextIsolation: false,
354
+ nodeIntegration: this.preloadWebContentsConfig?.nodeIntegration ?? true,
355
+ contextIsolation: this.preloadWebContentsConfig?.contextIsolation ?? false,
219
356
  // backgroundThrottling: false,
220
357
  webSecurity: false,
221
- preload: preload,
358
+ preload: webPreferences.preload || preload,
359
+ defaultEncoding: 'utf-8',
222
360
  }
223
361
  });
224
362
  try {
@@ -228,92 +366,117 @@ class WindowsManager {
228
366
  log('error', '预加载 BV 设置 remote 失败', error);
229
367
  }
230
368
  try {
231
- // @ts-ignore
232
- instance._id = instance.webContents.id;
369
+ const webContentsId = instance.webContents?.id;
370
+ if (webContentsId !== undefined) {
371
+ // @ts-ignore
372
+ instance._id = webContentsId;
373
+ }
374
+ // 设置默认zIndex层级
375
+ instance._zIndex = 0;
233
376
  }
234
377
  catch (error) {
235
378
  log('error', '预加载 BV 设置 _id 失败', error);
236
379
  }
237
380
  // @ts-ignore
238
- log('log', '创建预BV: ', instance._id, this.preloadWebContentsUrl);
381
+ const webContentsId = instance.webContents?.id;
382
+ log('log', '创建预BV: ', webContentsId, this.preloadWebContentsConfig?.url);
239
383
  // instance.webContents.once('did-finish-load', () => {
240
384
  // resolve(instance as BVItem);
241
385
  // });
242
386
  // instance.webContents.once('did-fail-load', () => {
243
387
  // resolve(instance as BVItem);
244
388
  // });
389
+ // @ts-ignore
245
390
  instance.webContents.loadURL(url || 'about:blank');
246
391
  resolve(instance);
247
392
  }
248
- });
249
- }
250
- create(options) {
251
- return new Promise((resolve, reject) => {
252
- // 将创建请求添加到队列
253
- this.createQueue.push({ options, resolve, reject });
254
- // 如果当前没有在创建,则开始处理队列
255
- if (!this.isCreating) {
256
- this.processCreateQueue();
393
+ else {
394
+ resolve(null);
257
395
  }
258
396
  });
259
397
  }
260
- /**
261
- * 处理创建队列
262
- */
263
- async processCreateQueue() {
264
- if (this.isCreating || this.createQueue.length === 0) {
265
- return;
266
- }
267
- this.isCreating = true;
268
- while (this.createQueue.length > 0) {
269
- const { options, resolve, reject } = this.createQueue.shift();
270
- try {
271
- const window = await this._createWindow(options);
272
- resolve(window);
273
- }
274
- catch (error) {
275
- log('error', 'create window failed:', error);
276
- reject(error);
277
- }
278
- }
279
- this.isCreating = false;
398
+ async create(options) {
399
+ const queue = await getQueue();
400
+ const win = await queue.add(async () => {
401
+ const window = await this._createWindow(options);
402
+ return window;
403
+ });
404
+ return win;
280
405
  }
281
406
  /**
282
407
  * 实际的窗口创建逻辑
283
408
  */
284
409
  async _createWindow(options) {
285
410
  let window;
286
- const { usePreload = true, type = 'BW', name = 'anonymous', url, loadingView = { url: undefined }, errorView = { url: undefined }, browserWindow: browserWindowOptions, openDevTools = false, preventOriginClose = false, } = options;
411
+ const { usePreload = true, type = 'BW', name = 'anonymous', url, loadingView = { url: undefined }, errorView = { url: undefined }, browserWindow: browserWindowOptions, openDevTools = false, preventOriginClose = false, zIndex = 0, } = options;
412
+ const existingWinId = this.windowsByName.get(options.name);
413
+ if (existingWinId) {
414
+ window = this.windows.get(existingWinId);
415
+ if (window) {
416
+ return window;
417
+ }
418
+ // 清理无效的引用
419
+ this.windowsByName.delete(options.name);
420
+ }
287
421
  options.type = type;
288
422
  // 优先复用预创建实例
289
423
  let preloadWin = null;
290
- if (type === 'BW' && usePreload && this.preloadWebContentsUrl) {
424
+ if (type === 'BW' && usePreload && this.preloadWebContentsConfig?.url) {
291
425
  const bwOptions = browserWindowOptions || {};
292
426
  if (bwOptions.frame === false) {
293
- if (bwOptions.titleBarStyle === 'customButtonsOnHover') {
294
- preloadWin = this.preloadedBW_FramelessNoButtons;
295
- this.preloadedBW_FramelessNoButtons = await this._createPreloadBW({ frame: false, transparent: true, titleBarStyle: 'customButtonsOnHover' });
427
+ if (bwOptions.titleBarStyle === 'default' || !bwOptions.titleBarStyle) {
428
+ if (this.preloadWebContentsConfig.enableBW_FramelessNoButtons !== false && this.preloadedBW_FramelessNoButtons) {
429
+ preloadWin = this.preloadedBW_FramelessNoButtons;
430
+ this.preloadedBW_FramelessNoButtons = await this._createPreloadBW({
431
+ frame: false,
432
+ // transparent: true,
433
+ titleBarStyle: 'default',
434
+ webPreferences: {
435
+ preload: bwOptions?.webPreferences?.preload || this.preload,
436
+ }
437
+ });
438
+ }
296
439
  }
297
440
  else {
298
- preloadWin = this.preloadedBW_FramelessWithButtons;
299
- this.preloadedBW_FramelessWithButtons = await this._createPreloadBW({ frame: false, transparent: true, titleBarStyle: 'hidden' });
441
+ if (this.preloadWebContentsConfig.enableBW_FramelessWithButtons !== false && this.preloadedBW_FramelessWithButtons) {
442
+ preloadWin = this.preloadedBW_FramelessWithButtons;
443
+ this.preloadedBW_FramelessWithButtons = await this._createPreloadBW({
444
+ frame: false,
445
+ // transparent: true,
446
+ titleBarStyle: 'hidden',
447
+ webPreferences: {
448
+ preload: this.preload,
449
+ }
450
+ });
451
+ }
300
452
  }
301
453
  }
302
454
  else {
303
- preloadWin = this.preloadedBW;
304
- this.preloadedBW = await this._createPreloadBW({});
455
+ if (this.preloadWebContentsConfig.enableBW !== false && this.preloadedBW) {
456
+ preloadWin = this.preloadedBW;
457
+ this.preloadedBW = await this._createPreloadBW({
458
+ webPreferences: {
459
+ preload: bwOptions?.webPreferences?.preload || this.preload,
460
+ }
461
+ });
462
+ }
305
463
  }
306
464
  }
307
- if (type === 'BV' && usePreload && this.preloadWebContentsUrl) {
308
- if (this.preloadedBV) {
465
+ if (type === 'BV' && usePreload && this.preloadWebContentsConfig?.url) {
466
+ const bvOptions = browserWindowOptions || {};
467
+ if (this.preloadWebContentsConfig.enableBV !== false && this.preloadedBV) {
309
468
  preloadWin = this.preloadedBV;
310
- this.preloadedBV = await this._createPreloadBV();
469
+ this.preloadedBV = await this._createPreloadBV({
470
+ webPreferences: {
471
+ preload: bvOptions?.webPreferences?.preload || this.preload,
472
+ }
473
+ });
311
474
  }
312
475
  }
313
476
  if (preloadWin) {
314
477
  const win = preloadWin;
315
- log('log', `${name} 使用预加载窗口(${type})`, win._id);
316
- win._type = 'BW';
478
+ log('log', `${name} 使用预加载窗口(${type})`, this._getWebContentsId(win));
479
+ win._type = type;
317
480
  win._name = options.name || 'anonymous';
318
481
  win._extraData = `${options?.extraData || ''}`;
319
482
  win._initUrl = `${options?.url || ''}`;
@@ -327,27 +490,101 @@ class WindowsManager {
327
490
  if (type === 'BV') {
328
491
  this._applyBrowserViewOptions(win, options);
329
492
  }
493
+ if (typeof this.preloadWebContentsConfig?.customLoadURL === 'function') {
494
+ try {
495
+ if (type === 'BW') {
496
+ // @ts-ignore
497
+ const originLoadURL = win.loadURL;
498
+ // @ts-ignore
499
+ win.loadURL = async (url, useNativeLoadURL = false) => {
500
+ return new Promise(async (resolve, reject) => {
501
+ if (useNativeLoadURL) {
502
+ log('log', 'useNativeLoadURL win.loadURL');
503
+ try {
504
+ await originLoadURL.call(win, url);
505
+ resolve(undefined);
506
+ }
507
+ catch (error) {
508
+ reject(error);
509
+ }
510
+ return;
511
+ }
512
+ try {
513
+ log('log', 'customLoadURL win.loadURL');
514
+ // @ts-ignore
515
+ await this.preloadWebContentsConfig.customLoadURL(url || 'about:blank', (url) => originLoadURL.call(win, url), win.webContents);
516
+ try {
517
+ win.emit('ready-to-show');
518
+ }
519
+ catch (error) {
520
+ log('error', 'emit ready-to-show event failed:', error);
521
+ }
522
+ resolve(undefined);
523
+ }
524
+ catch (error) {
525
+ reject(error);
526
+ }
527
+ });
528
+ };
529
+ }
530
+ const originWebContentsLoadURL = win.webContents.loadURL;
531
+ // @ts-ignore
532
+ win.webContents.loadURL = async (url, useNativeLoadURL = false) => {
533
+ return new Promise(async (resolve, reject) => {
534
+ if (useNativeLoadURL) {
535
+ log('log', 'useNativeLoadURL win.webContents.loadURL');
536
+ try {
537
+ await originWebContentsLoadURL.call(win.webContents, url);
538
+ resolve(undefined);
539
+ }
540
+ catch (error) {
541
+ reject(error);
542
+ }
543
+ return;
544
+ }
545
+ try {
546
+ log('log', 'customLoadURL win.webContents.loadURL');
547
+ // @ts-ignore
548
+ await this.preloadWebContentsConfig.customLoadURL(url || 'about:blank', (url) => originWebContentsLoadURL.call(win.webContents, url), win.webContents);
549
+ try {
550
+ win.webContents.emit('ready-to-show');
551
+ }
552
+ catch (error) {
553
+ log('error', 'emit ready-to-show event failed:', error);
554
+ }
555
+ resolve(undefined);
556
+ }
557
+ catch (error) {
558
+ reject(error);
559
+ }
560
+ });
561
+ };
562
+ }
563
+ catch (error) {
564
+ log('error', 'customLoadURL error', error);
565
+ }
566
+ }
330
567
  window = win;
331
568
  }
332
569
  try {
333
- try {
334
- loadingView.url = `${loadingView?.url ?? this.loadingViewUrl}`;
335
- lodash.merge(options, {
336
- loadingView,
337
- });
338
- }
339
- catch (error) {
340
- log('error', 'loadingView error:', loadingView, this.loadingViewUrl);
341
- }
342
- try {
343
- errorView.url = `${errorView?.url ?? this.errorViewUrl}`;
344
- lodash.merge(options, {
345
- errorView,
346
- });
347
- }
348
- catch (error) {
349
- log('error', 'errorView error:', errorView, this.errorViewUrl);
350
- }
570
+ loadingView.url = `${loadingView?.url ?? this.loadingViewUrl}`;
571
+ lodash.merge(options, {
572
+ loadingView,
573
+ });
574
+ }
575
+ catch (error) {
576
+ log('error', 'loadingView error:', loadingView, this.loadingViewUrl);
577
+ }
578
+ try {
579
+ errorView.url = `${errorView?.url ?? this.errorViewUrl}`;
580
+ lodash.merge(options, {
581
+ errorView,
582
+ });
583
+ }
584
+ catch (error) {
585
+ log('error', 'errorView error:', errorView, this.errorViewUrl);
586
+ }
587
+ try {
351
588
  let parentWin = undefined;
352
589
  if (typeof browserWindowOptions?.parent === 'number') {
353
590
  parentWin = electron.BrowserWindow.fromId(browserWindowOptions?.parent) || undefined;
@@ -365,6 +602,7 @@ class WindowsManager {
365
602
  window = type === 'BV' ?
366
603
  new electron.BrowserView(lodash.merge((browserWindowOptions || {}), {
367
604
  webPreferences: lodash.merge({
605
+ sandbox: false,
368
606
  webviewTag: true,
369
607
  // session: getCustomSession(),
370
608
  plugins: true,
@@ -375,6 +613,7 @@ class WindowsManager {
375
613
  nativeWindowOpen: true,
376
614
  webSecurity: false,
377
615
  preload: preload,
616
+ defaultEncoding: 'utf-8',
378
617
  }, browserWindowOptions?.webPreferences || {})
379
618
  }))
380
619
  : new electron.BrowserWindow(lodash.merge({
@@ -382,6 +621,7 @@ class WindowsManager {
382
621
  }, (browserWindowOptions || {}), {
383
622
  parent: parentWin,
384
623
  webPreferences: lodash.merge({
624
+ sandbox: false,
385
625
  webviewTag: true,
386
626
  // session: getCustomSession(),
387
627
  plugins: true,
@@ -391,6 +631,7 @@ class WindowsManager {
391
631
  nativeWindowOpen: true,
392
632
  webSecurity: false,
393
633
  preload: preload,
634
+ defaultEncoding: 'utf-8',
394
635
  }, browserWindowOptions?.webPreferences || {})
395
636
  }));
396
637
  log('log', `${name} 不使用 ${type === 'BV' ? 'preloadedBV' : 'preloadedBW'}`, window?.webContents?.id);
@@ -402,17 +643,14 @@ class WindowsManager {
402
643
  }
403
644
  }
404
645
  // 停止加载
405
- window.webContents?.stop?.();
646
+ // window.webContents?.stop?.();
406
647
  // @ts-ignore
407
- try {
408
- window.id = Number(`${window.id || window.webContents.id}`);
409
- }
410
- catch (error) {
411
- // log('error', 'set id: ', error)
412
- }
413
648
  // @ts-ignore
414
649
  try {
415
- window._id = Number(`${window.id || window.webContents.id}`);
650
+ const webContentsId = this._getWebContentsId(window);
651
+ if (webContentsId !== undefined) {
652
+ window._id = Number(`${webContentsId}`);
653
+ }
416
654
  }
417
655
  catch (error) {
418
656
  // log('error', 'set id: ', error)
@@ -421,14 +659,16 @@ class WindowsManager {
421
659
  window._name = name;
422
660
  window._extraData = `${options?.extraData || ''}`;
423
661
  window._initUrl = `${options?.url || ''}`;
424
- log('log', 'create 5: ', window.id, window._id, window._name);
425
- if (loadingView?.url) {
662
+ // 设置zIndex层级
663
+ window._zIndex = options.zIndex ?? 0;
664
+ log('log', 'create 5: ', this._getWebContentsId(window), window._name);
665
+ if (loadingView?.url && loadingView?.url !== 'about:blank') {
426
666
  if (type === 'BW') {
427
667
  // @ts-ignore
428
668
  this._setLoadingView(window, options);
429
669
  }
430
670
  }
431
- if (errorView?.url) {
671
+ if (errorView?.url && errorView?.url !== 'about:blank') {
432
672
  if (type === 'BW') {
433
673
  const showErrorView = lodash.debounce(() => {
434
674
  const _url = window._initUrl;
@@ -480,46 +720,45 @@ class WindowsManager {
480
720
  }
481
721
  window.webContents.on('did-attach-webview', (_event, webContents) => {
482
722
  const tryEnable = () => {
483
- const url = webContents.getURL();
484
- // 判断是否本地/内网IP
485
- const isLocalhost = (hostname) => {
486
- return (hostname === 'localhost' ||
487
- hostname === '127.0.0.1' ||
488
- hostname === '::1' ||
489
- /^192\.168\./.test(hostname) ||
490
- /^10\./.test(hostname) ||
491
- /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname));
492
- };
493
- if (this.webviewDomainWhiteList && this.webviewDomainWhiteList.length > 0) {
494
- try {
495
- const { hostname } = new URL(url);
496
- // 优化白名单判断,支持 .example.com 形式的子域名通配和本地/内网IP
497
- const isWhiteListed = this.webviewDomainWhiteList.some(domain => {
498
- if (domain === 'localhost' || domain === '127.0.0.1' || domain === '::1') {
499
- return isLocalhost(hostname);
500
- }
501
- if (domain.startsWith('.')) {
502
- // .example.com 允许所有 *.example.com
503
- return hostname === domain.slice(1) || hostname.endsWith(domain);
723
+ try {
724
+ const url = webContents.getURL();
725
+ if (!url || url === 'about:blank') {
726
+ return;
727
+ }
728
+ if (this.webviewDomainWhiteList && this.webviewDomainWhiteList.length > 0) {
729
+ try {
730
+ const { hostname } = new URL(url);
731
+ // 优化白名单判断,支持 .example.com 形式的子域名通配和本地/内网IP
732
+ const isWhiteListed = this.webviewDomainWhiteList.some(domain => {
733
+ if (domain === 'localhost' || domain === '127.0.0.1' || domain === '::1') {
734
+ return this._isLocalhost(hostname);
735
+ }
736
+ if (domain.startsWith('.')) {
737
+ // .example.com 允许所有 *.example.com
738
+ return hostname === domain.slice(1) || hostname.endsWith(domain);
739
+ }
740
+ else {
741
+ // 精确匹配
742
+ return hostname === domain;
743
+ }
744
+ }) || this._isLocalhost(hostname); // 允许本地回环和内网地址
745
+ if (isWhiteListed) {
746
+ enable(webContents);
504
747
  }
505
748
  else {
506
- // 精确匹配
507
- return hostname === domain;
749
+ log('log', 'webview 域名未在白名单,未启用 remote', url);
508
750
  }
509
- }) || isLocalhost(hostname); // 允许本地回环和内网地址
510
- if (isWhiteListed) {
511
- enable(webContents);
512
751
  }
513
- else {
514
- log('log', 'webview 域名未在白名单,未启用 remote', url);
752
+ catch {
753
+ log('log', 'webview url 解析失败,未启用 remote', url);
515
754
  }
516
755
  }
517
- catch {
518
- log('log', 'webview url 解析失败,未启用 remote', url);
756
+ else {
757
+ enable(webContents); // 没有配置白名单则全部允许
519
758
  }
520
759
  }
521
- else {
522
- enable(webContents); // 没有配置白名单则全部允许
760
+ catch (error) {
761
+ log('error', 'tryEnable webview error:', error);
523
762
  }
524
763
  };
525
764
  // 只监听一次,防止多次触发
@@ -531,11 +770,16 @@ class WindowsManager {
531
770
  webContents.on('did-navigate', onDidNavigate);
532
771
  webContents.on('did-finish-load', onDidNavigate);
533
772
  });
534
- window.webContents.on('close', () => {
535
- this.windows.delete(window.id || window._id);
536
- });
773
+ // window.webContents.on('close', () => {
774
+ // this.windows.delete(window.id || window._id)
775
+ // })
537
776
  window.webContents.on('destroyed', () => {
538
- this.windows.delete(window.id || window._id);
777
+ const winId = this._getWebContentsId(window);
778
+ if (winId !== undefined) {
779
+ this.windows.delete(winId);
780
+ }
781
+ // 同步清理名称索引
782
+ this.windowsByName.delete(window._name);
539
783
  });
540
784
  window.webContents.on('dom-ready', () => {
541
785
  if (openDevTools) {
@@ -549,23 +793,33 @@ class WindowsManager {
549
793
  }
550
794
  // @ts-ignore
551
795
  window.on('closed', () => {
552
- log('log', 'closed', window.id, window._name);
553
- this.windows.delete(window.id || window._id);
796
+ const winId = this._getWebContentsId(window);
797
+ log('log', 'closed', winId, window._name);
798
+ if (winId !== undefined) {
799
+ this.windows.delete(winId);
800
+ }
801
+ // 同步清理名称索引
802
+ this.windowsByName.delete(window._name);
554
803
  });
555
804
  }
556
805
  if (type === 'BV') {
557
806
  parentWin?.addBrowserView(window);
558
807
  log('log', 'create - addBrowserView');
559
808
  }
560
- this.windows.set(window.id || window._id || window.webContents.id, window);
809
+ const winId = this._getWebContentsId(window);
810
+ if (winId !== undefined) {
811
+ this.windows.set(winId, window);
812
+ // 同步更新名称索引
813
+ this.windowsByName.set(window._name, winId);
814
+ }
561
815
  log('log', 'create', this.windows.keys());
562
816
  // 初始化值
563
817
  window.webContents.on('did-finish-load', () => {
564
- console.error('did-finish-load', window.webContents.id);
818
+ log('log', 'did-finish-load', this._getWebContentsId(window));
565
819
  initWebContentsVal(window, `${preload || ''}`);
566
820
  });
567
821
  window.webContents.on('did-start-loading', () => {
568
- console.error('did-start-loading', window.webContents.id);
822
+ log('log', 'did-start-loading', this._getWebContentsId(window));
569
823
  initWebContentsVal(window, `${preload || ''}`);
570
824
  });
571
825
  if (type === 'BW') {
@@ -576,7 +830,7 @@ class WindowsManager {
576
830
  try {
577
831
  window.dispatchEvent(new Event('focus'));
578
832
  } catch (error) {
579
- console.error('focus', error);
833
+ // 忽略错误,避免影响主流程
580
834
  }
581
835
  `);
582
836
  }
@@ -590,7 +844,7 @@ class WindowsManager {
590
844
  try {
591
845
  window.dispatchEvent(new Event('blur'));
592
846
  } catch (error) {
593
- console.error('blur', error);
847
+ // 忽略错误,避免影响主流程
594
848
  }
595
849
  `);
596
850
  }
@@ -626,23 +880,37 @@ class WindowsManager {
626
880
  });
627
881
  try {
628
882
  const _addBrowserView = window.addBrowserView;
629
- window.addBrowserView = (view) => {
883
+ window.addBrowserView = (view, isSort = false) => {
630
884
  _addBrowserView.call(window, view);
631
885
  handleBrowserViewFocus(view);
886
+ // 添加BrowserView后重新排序(如果未禁用自动排序)
887
+ log('log', 'addBrowserView-sort', isSort, window.getBrowserViews());
888
+ if (isSort) {
889
+ this.sortBrowserViewsDebounced(window, view);
890
+ }
632
891
  };
633
892
  const _removeBrowserView = window.removeBrowserView;
634
- window.removeBrowserView = (view) => {
893
+ window.removeBrowserView = (view, isSort = false) => {
635
894
  _removeBrowserView.call(window, view);
636
895
  handleBrowserViewBlur(view);
896
+ // 移除BrowserView后重新排序(如果未禁用自动排序)
897
+ log('log', 'removeBrowserView-sort', isSort);
898
+ if (isSort) {
899
+ this.sortBrowserViewsDebounced(window, view);
900
+ }
637
901
  };
638
902
  const _setBrowserView = window.setBrowserView;
639
- window.setBrowserView = (view) => {
903
+ window.setBrowserView = (view, isSort = false) => {
640
904
  const views = window.getBrowserViews() || [];
641
- for (const view of views) {
642
- handleBrowserViewBlur(view);
905
+ for (const existingView of views) {
906
+ handleBrowserViewBlur(existingView);
643
907
  }
644
908
  _setBrowserView.call(window, view);
645
909
  handleBrowserViewFocus(view);
910
+ log('log', 'setBrowserView-sort', isSort);
911
+ if (isSort) {
912
+ this.sortBrowserViewsDebounced(window, view);
913
+ }
646
914
  };
647
915
  }
648
916
  catch (error) {
@@ -652,12 +920,18 @@ class WindowsManager {
652
920
  if (options.url) {
653
921
  // @ts-ignore
654
922
  window.loadURL ? window.loadURL(options.url) : window.webContents.loadURL(options.url);
655
- // @ts-ignore
656
- window.focus ? window.focus() : window.webContents.focus();
923
+ if (options.browserWindow?.focusable !== false) {
924
+ window?.focus?.();
925
+ }
926
+ window?.webContents?.focus?.();
657
927
  }
658
928
  }
659
929
  catch (error) {
660
930
  log('error', 'create', error);
931
+ throw error;
932
+ }
933
+ if (!window) {
934
+ throw new Error(`Failed to create window: ${name}`);
661
935
  }
662
936
  return window;
663
937
  }
@@ -666,6 +940,7 @@ class WindowsManager {
666
940
  const { loadingView, preventOriginNavigate = false, } = createOptions;
667
941
  let _loadingView = new electron.BrowserView({
668
942
  webPreferences: {
943
+ sandbox: false,
669
944
  // session: getCustomSession(),
670
945
  contextIsolation: false,
671
946
  nodeIntegration: true,
@@ -678,7 +953,7 @@ class WindowsManager {
678
953
  }
679
954
  const loadLoadingView = () => {
680
955
  const [viewWidth, viewHeight] = window.getSize();
681
- window.setBrowserView(_loadingView);
956
+ window.addBrowserView(_loadingView);
682
957
  _loadingView.setBounds({
683
958
  x: 0,
684
959
  y: 0,
@@ -718,7 +993,7 @@ class WindowsManager {
718
993
  return;
719
994
  }
720
995
  if (_loadingView.webContents && !_loadingView.webContents.isDestroyed()) {
721
- window.setBrowserView(_loadingView);
996
+ window.addBrowserView(_loadingView);
722
997
  }
723
998
  else {
724
999
  // if loadingView has been destroyed
@@ -734,148 +1009,253 @@ class WindowsManager {
734
1009
  window.webContents.on('did-stop-loading', onFailure);
735
1010
  }
736
1011
  }
737
- get(idOrName) {
738
- log('log', 'get', idOrName);
739
- let win;
740
- this.windows.forEach((i, key) => {
741
- try {
742
- if (!(i && i?.webContents?.isDestroyed && !i?.webContents?.isDestroyed?.())) {
743
- this.windows.delete(key);
744
- }
1012
+ /**
1013
+ * 检查窗口是否已销毁
1014
+ */
1015
+ _isWindowDestroyed(win) {
1016
+ try {
1017
+ return !win || !win.webContents || win.webContents.isDestroyed();
1018
+ }
1019
+ catch {
1020
+ return true;
1021
+ }
1022
+ }
1023
+ /**
1024
+ * 安全地获取窗口的 webContents.id
1025
+ * 如果 webContents 已销毁,返回 undefined
1026
+ */
1027
+ _getWebContentsId(win) {
1028
+ try {
1029
+ if (!win || !win.webContents) {
1030
+ return undefined;
745
1031
  }
746
- catch (error) {
747
- log('error', 'get');
1032
+ if (win.webContents.isDestroyed()) {
1033
+ return undefined;
748
1034
  }
749
- if (typeof idOrName === 'number') {
750
- if (i?.id === idOrName || i?._id === idOrName) {
751
- win = i;
1035
+ return win.webContents.id;
1036
+ }
1037
+ catch {
1038
+ return undefined;
1039
+ }
1040
+ }
1041
+ /**
1042
+ * 判断是否本地/内网IP
1043
+ */
1044
+ _isLocalhost(hostname) {
1045
+ return (hostname === 'localhost' ||
1046
+ hostname === '127.0.0.1' ||
1047
+ hostname === '::1' ||
1048
+ /^192\.168\./.test(hostname) ||
1049
+ /^10\./.test(hostname) ||
1050
+ /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname));
1051
+ }
1052
+ /**
1053
+ * 清理已销毁的窗口(延迟执行,避免频繁检查)
1054
+ */
1055
+ _cleanupDestroyedWindows() {
1056
+ const toDelete = [];
1057
+ this.windows.forEach((win, key) => {
1058
+ if (this._isWindowDestroyed(win)) {
1059
+ toDelete.push(key);
1060
+ // 同步清理名称索引
1061
+ if (win?._name) {
1062
+ this.windowsByName.delete(win._name);
752
1063
  }
753
1064
  }
754
- else if (typeof idOrName === 'string') {
755
- if (i?._name === idOrName) {
756
- win = i;
1065
+ });
1066
+ toDelete.forEach(key => this.windows.delete(key));
1067
+ }
1068
+ /**
1069
+ * 触发 didAnyChange 事件,返回最新的 windows(只包含核心数据)
1070
+ */
1071
+ _emitDidAnyChange() {
1072
+ const windowsData = {};
1073
+ this.windows.forEach((win, key) => {
1074
+ if (!this._isWindowDestroyed(win)) {
1075
+ const webContentsId = this._getWebContentsId(win);
1076
+ if (webContentsId !== undefined) {
1077
+ windowsData[webContentsId] = {
1078
+ webContentsId,
1079
+ name: win._name || 'anonymous',
1080
+ type: win._type || 'BW',
1081
+ extraData: win._extraData,
1082
+ initUrl: win._initUrl,
1083
+ zIndex: win._zIndex,
1084
+ };
757
1085
  }
758
1086
  }
759
1087
  });
760
- // @ts-ignore
761
- if (win && win?.webContents?.isDestroyed && !win?.webContents?.isDestroyed?.()) {
1088
+ this.emit('didAnyChange', windowsData);
1089
+ }
1090
+ get(idOrName) {
1091
+ log('log', 'get', idOrName);
1092
+ let win;
1093
+ if (typeof idOrName === 'number') {
1094
+ // 按 ID 查找(O(1)),使用 webContents.id
1095
+ win = this.windows.get(idOrName);
1096
+ }
1097
+ else if (typeof idOrName === 'string') {
1098
+ // 按名称查找(O(1),使用索引)
1099
+ const winId = this.windowsByName.get(idOrName);
1100
+ if (winId !== undefined) {
1101
+ win = this.windows.get(winId);
1102
+ }
1103
+ // 如果索引中找不到,回退到遍历查找(兼容旧数据)
1104
+ if (!win) {
1105
+ this.windows.forEach(w => {
1106
+ if (w._name === idOrName) {
1107
+ win = w;
1108
+ // 更新索引,使用 webContents.id
1109
+ const webContentsId = this._getWebContentsId(w);
1110
+ if (webContentsId !== undefined) {
1111
+ this.windowsByName.set(idOrName, webContentsId);
1112
+ }
1113
+ }
1114
+ });
1115
+ }
1116
+ }
1117
+ // 检查找到的窗口是否已销毁
1118
+ if (win && !this._isWindowDestroyed(win)) {
762
1119
  return win;
763
1120
  }
1121
+ // 窗口已销毁,触发清理
1122
+ if (win) {
1123
+ const winId = this._getWebContentsId(win);
1124
+ if (winId !== undefined) {
1125
+ this.windows.delete(winId);
1126
+ }
1127
+ if (win._name) {
1128
+ this.windowsByName.delete(win._name);
1129
+ }
1130
+ }
1131
+ // 延迟清理其他已销毁的窗口
1132
+ this.cleanupDestroyedWindowsDebounced();
764
1133
  return undefined;
765
1134
  }
766
1135
  getAll(type) {
767
1136
  log('log', 'getAll');
768
- const bwWindows = new Map();
769
- const bvWindows = new Map();
1137
+ // 先清理已销毁的窗口
1138
+ const toDelete = [];
770
1139
  this.windows.forEach((win, key) => {
771
- if (!(win && win?.webContents?.isDestroyed && !win?.webContents?.isDestroyed?.())) {
772
- this.windows.delete(key);
773
- }
774
- if (win?._type === 'BW') {
775
- bwWindows.set(key, win);
776
- }
777
- if (win?._type === 'BV') {
778
- bvWindows.set(key, win);
1140
+ if (this._isWindowDestroyed(win)) {
1141
+ toDelete.push(key);
1142
+ if (win?._name) {
1143
+ this.windowsByName.delete(win._name);
1144
+ }
779
1145
  }
780
1146
  });
781
- if (type === 'BW') {
782
- return bwWindows;
783
- }
784
- if (type === 'BV') {
785
- return bvWindows;
1147
+ toDelete.forEach(key => this.windows.delete(key));
1148
+ if (!type) {
1149
+ return this.windows;
786
1150
  }
787
- return this.windows;
1151
+ const result = new Map();
1152
+ this.windows.forEach((win, key) => {
1153
+ if (!this._isWindowDestroyed(win) && win._type === type) {
1154
+ result.set(key, win);
1155
+ }
1156
+ });
1157
+ // 使用类型断言,TypeScript 会通过方法重载确保类型正确
1158
+ return result;
788
1159
  }
789
1160
  close(idOrName) {
790
1161
  log('log', 'close', idOrName);
791
- let win = undefined;
792
- this.windows.forEach((i) => {
793
- if (typeof idOrName === 'number') {
794
- if (i?.id === idOrName || i?._id === idOrName) {
795
- win = i;
796
- }
797
- }
798
- else if (typeof idOrName === 'string') {
799
- if (i?._name === idOrName) {
800
- win = i;
801
- }
802
- }
803
- });
804
- // @ts-ignore
805
- win && this.windows.delete(win?.id);
806
- // @ts-ignore
807
- if (win) {
808
- // @ts-ignore
1162
+ const win = this.get(idOrName);
1163
+ if (!win) {
1164
+ return false;
1165
+ }
1166
+ const winId = this._getWebContentsId(win);
1167
+ if (winId !== undefined) {
1168
+ this.windows.delete(winId);
1169
+ }
1170
+ if (win._name) {
1171
+ this.windowsByName.delete(win._name);
1172
+ }
1173
+ try {
809
1174
  if (win._type === 'BV') {
1175
+ // 从所有 BW 窗口中移除该 BV
810
1176
  this.windows.forEach(i => {
811
- if (i?._type === 'BW') {
1177
+ if (i?._type === 'BW' && !this._isWindowDestroyed(i)) {
812
1178
  const _win = i;
813
- _win.removeBrowserView(win);
1179
+ try {
1180
+ _win.removeBrowserView(win);
1181
+ }
1182
+ catch (error) {
1183
+ // 忽略错误,可能已经移除
1184
+ }
814
1185
  }
815
1186
  });
816
1187
  }
817
1188
  // @ts-ignore
818
- win?.webContents?.destroy?.();
1189
+ win.webContents?.destroy?.();
819
1190
  // @ts-ignore
820
- win?.close?.();
1191
+ win.close?.();
821
1192
  // @ts-ignore
822
- win?.destroy?.();
1193
+ win.destroy?.();
1194
+ }
1195
+ catch (error) {
1196
+ log('error', 'close window error:', error);
823
1197
  }
824
1198
  return true;
825
1199
  }
826
1200
  closeAll() {
827
1201
  log('log', 'closeAll');
828
- this.windows.forEach((win) => {
1202
+ const windowsToClose = Array.from(this.windows.values());
1203
+ // 先清空 Map,避免在遍历过程中修改
1204
+ this.windows.clear();
1205
+ this.windowsByName.clear();
1206
+ // 收集所有 BW 窗口,用于批量移除 BV
1207
+ const bwWindows = windowsToClose.filter(w => w._type === 'BW' && !this._isWindowDestroyed(w));
1208
+ const bvWindows = windowsToClose.filter(w => w._type === 'BV');
1209
+ // 先从所有 BW 窗口中移除 BV
1210
+ bvWindows.forEach(bv => {
1211
+ bwWindows.forEach(bw => {
1212
+ try {
1213
+ bw.removeBrowserView(bv);
1214
+ }
1215
+ catch (error) {
1216
+ // 忽略错误,可能已经移除
1217
+ }
1218
+ });
1219
+ });
1220
+ // 然后销毁所有窗口
1221
+ windowsToClose.forEach((win) => {
829
1222
  try {
830
- win && this.windows.delete(win?.id);
831
- // @ts-ignore
832
- if (win._type === 'BV') {
833
- this.windows.forEach(i => {
834
- if (i?._type === 'BW') {
835
- const _win = i;
836
- _win.removeBrowserView(win);
837
- }
838
- });
1223
+ if (this._isWindowDestroyed(win)) {
1224
+ return;
839
1225
  }
840
1226
  // @ts-ignore
841
- win?.webContents?.destroy?.();
1227
+ win.webContents?.destroy?.();
842
1228
  // @ts-ignore
843
- win?.close?.();
1229
+ win.close?.();
844
1230
  // @ts-ignore
845
- win?.destroy?.();
1231
+ win.destroy?.();
846
1232
  }
847
1233
  catch (error) {
1234
+ log('error', 'closeAll error:', error);
848
1235
  }
849
1236
  });
850
1237
  }
851
1238
  rename(idOrName, newName) {
852
1239
  log('log', 'rename', idOrName, newName);
853
- let win = undefined;
854
- // 先查找目标窗口
855
- if (typeof idOrName === 'number') {
856
- win = this.get(idOrName);
857
- }
858
- else if (typeof idOrName === 'string') {
859
- this.windows.forEach(i => {
860
- if (i?._name === idOrName) {
861
- win = i;
862
- }
863
- });
864
- }
1240
+ const win = this.get(idOrName);
865
1241
  if (!win) {
866
- // 没有找到目标窗口
867
1242
  return undefined;
868
1243
  }
869
- // 检查新名字是否已存在
870
- let nameExists = false;
871
- this.windows.forEach(i => {
872
- if (i !== win && i?._name === newName) {
873
- nameExists = true;
1244
+ // 检查新名字是否已存在(使用索引加速)
1245
+ const existingWinId = this.windowsByName.get(newName);
1246
+ if (existingWinId !== undefined) {
1247
+ const existingWin = this.windows.get(existingWinId);
1248
+ if (existingWin && existingWin !== win && !this._isWindowDestroyed(existingWin)) {
1249
+ // 新名字已被占用
1250
+ return undefined;
874
1251
  }
875
- });
876
- if (nameExists) {
877
- // 新名字已被占用
878
- return undefined;
1252
+ }
1253
+ // 更新名称索引
1254
+ const oldName = win._name;
1255
+ const winId = this._getWebContentsId(win);
1256
+ if (winId !== undefined) {
1257
+ this.windowsByName.delete(oldName);
1258
+ this.windowsByName.set(newName, winId);
879
1259
  }
880
1260
  // 修改名字并同步 webContents
881
1261
  win._name = newName;
@@ -884,12 +1264,7 @@ class WindowsManager {
884
1264
  }
885
1265
  reInitUrl(idOrName, url) {
886
1266
  log('log', 'reInitUrl', idOrName, url);
887
- let win = undefined;
888
- this.windows.forEach(i => {
889
- if (i?._name === idOrName) {
890
- win = i;
891
- }
892
- });
1267
+ const win = this.get(idOrName);
893
1268
  if (!win) {
894
1269
  return undefined;
895
1270
  }
@@ -918,6 +1293,86 @@ class WindowsManager {
918
1293
  if (typeof browserWindowOptions.alwaysOnTop === 'boolean') {
919
1294
  win.setAlwaysOnTop(browserWindowOptions.alwaysOnTop);
920
1295
  }
1296
+ // 设置背景颜色
1297
+ if (typeof browserWindowOptions.backgroundColor === 'string') {
1298
+ win.setBackgroundColor(browserWindowOptions.backgroundColor);
1299
+ }
1300
+ // 居中
1301
+ if (browserWindowOptions?.center !== false) {
1302
+ win.center();
1303
+ }
1304
+ // 设置窗口移动
1305
+ if (typeof browserWindowOptions.movable === 'boolean') {
1306
+ win.setMovable(browserWindowOptions.movable);
1307
+ }
1308
+ // 设置窗口大小调整
1309
+ if (typeof browserWindowOptions.resizable === 'boolean') {
1310
+ win.setResizable(browserWindowOptions.resizable);
1311
+ }
1312
+ // 设置全屏模式
1313
+ if (typeof browserWindowOptions.fullscreenable === 'boolean') {
1314
+ win.setFullScreenable(browserWindowOptions.fullscreenable);
1315
+ }
1316
+ // 设置窗口阴影
1317
+ if (typeof browserWindowOptions.hasShadow === 'boolean') {
1318
+ win.setHasShadow(browserWindowOptions.hasShadow);
1319
+ }
1320
+ // 设置窗口最小尺寸
1321
+ if (typeof browserWindowOptions.minWidth === 'number' && typeof browserWindowOptions.minHeight === 'number') {
1322
+ win.setMinimumSize(browserWindowOptions.minWidth, browserWindowOptions.minHeight);
1323
+ }
1324
+ // 设置窗口最大尺寸
1325
+ if (typeof browserWindowOptions.maxWidth === 'number' && typeof browserWindowOptions.maxHeight === 'number') {
1326
+ win.setMaximumSize(browserWindowOptions.maxWidth, browserWindowOptions.maxHeight);
1327
+ }
1328
+ // 设置窗口位置
1329
+ if (typeof browserWindowOptions.x === 'number' && typeof browserWindowOptions.y === 'number') {
1330
+ win.setPosition(browserWindowOptions.x, browserWindowOptions.y);
1331
+ }
1332
+ // 设置窗口标题
1333
+ if (typeof browserWindowOptions.title === 'string') {
1334
+ win.setTitle(browserWindowOptions.title);
1335
+ }
1336
+ // 设置窗口图标
1337
+ if (typeof browserWindowOptions.icon === 'string') {
1338
+ win.setIcon(browserWindowOptions.icon);
1339
+ }
1340
+ // 设置窗口菜单栏可见性
1341
+ if (typeof browserWindowOptions.autoHideMenuBar === 'boolean') {
1342
+ win.setAutoHideMenuBar(browserWindowOptions.autoHideMenuBar);
1343
+ }
1344
+ // 设置窗口最小化按钮
1345
+ if (browserWindowOptions.minimizable === false) {
1346
+ win.setMinimizable(false);
1347
+ }
1348
+ // 设置窗口最大化按钮
1349
+ if (browserWindowOptions.maximizable === false) {
1350
+ win.setMaximizable(false);
1351
+ }
1352
+ // 设置窗口关闭按钮
1353
+ if (browserWindowOptions.closable === false) {
1354
+ win.setClosable(false);
1355
+ }
1356
+ // 设置窗口焦点
1357
+ if (browserWindowOptions.focusable === false) {
1358
+ win.setFocusable(false);
1359
+ }
1360
+ // 设置窗口全屏
1361
+ if (browserWindowOptions.fullscreen === true) {
1362
+ win.setFullScreen(true);
1363
+ }
1364
+ // 设置窗口背景材质
1365
+ if (typeof browserWindowOptions.vibrancy === 'string') {
1366
+ win.setVibrancy(browserWindowOptions.vibrancy);
1367
+ }
1368
+ // 设置窗口透明度
1369
+ if (typeof browserWindowOptions.opacity === 'number') {
1370
+ win.setOpacity(browserWindowOptions.opacity);
1371
+ }
1372
+ // 设置窗口显示状态
1373
+ if (browserWindowOptions.show === false) {
1374
+ win.hide();
1375
+ }
921
1376
  // 可继续扩展其他动态属性
922
1377
  }
923
1378
  _applyBrowserViewOptions(view, options) {
@@ -933,35 +1388,175 @@ class WindowsManager {
933
1388
  if (typeof browserWindowOptions.width === 'number' && typeof browserWindowOptions.height === 'number') {
934
1389
  view.setBounds({ x: 0, y: 0, width: browserWindowOptions.width, height: browserWindowOptions.height });
935
1390
  }
1391
+ // 设置视图位置
1392
+ if (typeof browserWindowOptions.x === 'number' && typeof browserWindowOptions.y === 'number') {
1393
+ const bounds = view.getBounds();
1394
+ view.setBounds({
1395
+ x: browserWindowOptions.x,
1396
+ y: browserWindowOptions.y,
1397
+ width: bounds.width,
1398
+ height: bounds.height
1399
+ });
1400
+ }
1401
+ // 设置视图背景颜色
1402
+ if (typeof browserWindowOptions.backgroundColor === 'string') {
1403
+ view.setBackgroundColor(browserWindowOptions.backgroundColor);
1404
+ }
936
1405
  // 可继续扩展其他动态属性
937
1406
  }
938
1407
  // 生成一个bv 做为预加载资源窗口,加载完成后销毁
939
1408
  async createPreloadWebContents(url) {
940
- return new Promise(async (resolve, reject) => {
941
- let bv = await this.create({
1409
+ let bv = null;
1410
+ try {
1411
+ bv = await this.create({
942
1412
  type: 'BV',
943
1413
  url,
944
1414
  name: `preload-web-contents-${md5(url)}`,
945
1415
  extraData: `${url}`
946
1416
  });
947
- bv.webContents.on('did-finish-load', () => {
948
- this.close(bv.id || bv._id);
949
- resolve(true);
950
- bv = null;
1417
+ return new Promise((resolve, reject) => {
1418
+ const winId = this._getWebContentsId(bv);
1419
+ if (!winId) {
1420
+ reject(new Error('Failed to get window ID'));
1421
+ return;
1422
+ }
1423
+ bv.webContents.on('did-finish-load', () => {
1424
+ if (winId) {
1425
+ this.close(winId);
1426
+ }
1427
+ resolve(true);
1428
+ bv = null;
1429
+ });
1430
+ bv.webContents.on('did-fail-load', () => {
1431
+ if (winId) {
1432
+ this.close(winId);
1433
+ }
1434
+ reject(new Error('Failed to load web contents'));
1435
+ bv = null;
1436
+ });
1437
+ bv.webContents.loadURL(url);
951
1438
  });
952
- bv.webContents.on('did-fail-load', () => {
953
- this.close(bv.id || bv._id);
954
- reject(false);
955
- bv = null;
1439
+ }
1440
+ catch (error) {
1441
+ if (bv) {
1442
+ const winId = this._getWebContentsId(bv);
1443
+ if (winId) {
1444
+ this.close(winId);
1445
+ }
1446
+ }
1447
+ throw error;
1448
+ }
1449
+ }
1450
+ async getWindowForWebContentsId(wcId) {
1451
+ const wc = electron.webContents.fromId(wcId);
1452
+ if (!wc)
1453
+ return undefined;
1454
+ // Case 1: BrowserView
1455
+ for (const win of electron.BrowserWindow.getAllWindows()) {
1456
+ for (const view of win.getBrowserViews()) {
1457
+ if (view.webContents.id === wcId) {
1458
+ return win;
1459
+ }
1460
+ }
1461
+ }
1462
+ // Case 2: WebView
1463
+ // webview 有 hostWebContents,指向它所在的 BrowserWindow 的 webContents
1464
+ if (wc.hostWebContents) {
1465
+ return electron.BrowserWindow.fromWebContents(wc.hostWebContents);
1466
+ }
1467
+ // Case 3: 普通 window 本身
1468
+ const win = electron.BrowserWindow.fromWebContents(wc);
1469
+ if (win)
1470
+ return win;
1471
+ return undefined;
1472
+ }
1473
+ /**
1474
+ * 手动对BrowserView进行排序
1475
+ * @param windowId 窗口ID或名称
1476
+ */
1477
+ sortBrowserViews(windowId) {
1478
+ const window = this.get(windowId);
1479
+ if (window && window._type === 'BW') {
1480
+ this._sortBrowserViews(window);
1481
+ }
1482
+ }
1483
+ /**
1484
+ * 对BrowserView进行排序
1485
+ * @param window 目标窗口
1486
+ */
1487
+ _sortBrowserViews(window, addView) {
1488
+ try {
1489
+ if (window.isDestroyed()) {
1490
+ return;
1491
+ }
1492
+ const views = window.getBrowserViews() || [];
1493
+ if (views.length <= 1)
1494
+ return;
1495
+ log('log', 'sortBrowserViews', views?.map(i => this._getWebContentsId(i)));
1496
+ // 创建排序后的视图数组(不修改原数组)
1497
+ const sortedViews = [...views].sort((a, b) => {
1498
+ const zIndexA = a._zIndex ?? 0;
1499
+ const zIndexB = b._zIndex ?? 0;
1500
+ return zIndexA - zIndexB;
956
1501
  });
957
- bv.webContents.loadURL(url);
958
- });
1502
+ // 检查是否已经按正确顺序排列(优化:提前退出)
1503
+ let needsReorder = false;
1504
+ for (let i = 0; i < views.length; i++) {
1505
+ const view = views[i];
1506
+ const sortedView = sortedViews[i];
1507
+ const viewId = this._getWebContentsId(view);
1508
+ const sortedViewId = this._getWebContentsId(sortedView);
1509
+ if (!view || !sortedView || viewId !== sortedViewId) {
1510
+ needsReorder = true;
1511
+ break;
1512
+ }
1513
+ }
1514
+ log('log', 'sortBrowserViews needsReorder', needsReorder, sortedViews?.map(i => this._getWebContentsId(i)));
1515
+ // 如果已经按正确顺序排列,则不需要重新排序
1516
+ if (!needsReorder)
1517
+ return;
1518
+ // 移除所有BrowserView(排除刚添加的视图,避免不必要的操作)
1519
+ const addViewId = this._getWebContentsId(addView);
1520
+ views.forEach(view => {
1521
+ const viewId = this._getWebContentsId(view);
1522
+ if (addViewId !== viewId) {
1523
+ try {
1524
+ // @ts-ignore
1525
+ window.removeBrowserView(view, false);
1526
+ }
1527
+ catch (error) {
1528
+ // 忽略错误,视图可能已经被移除
1529
+ }
1530
+ }
1531
+ });
1532
+ // 按正确顺序重新添加
1533
+ sortedViews.forEach((view, index) => {
1534
+ try {
1535
+ if (index === 0) {
1536
+ // 第一个设置为当前视图
1537
+ // @ts-ignore
1538
+ window.setBrowserView(view, false);
1539
+ }
1540
+ else {
1541
+ // 其他视图添加到后面
1542
+ // @ts-ignore
1543
+ window.addBrowserView(view, false);
1544
+ }
1545
+ }
1546
+ catch (error) {
1547
+ log('error', 'addBrowserView in sortBrowserViews error:', error);
1548
+ }
1549
+ });
1550
+ }
1551
+ catch (error) {
1552
+ log('error', 'sortBrowserViews error:', error);
1553
+ }
959
1554
  }
960
1555
  }
961
1556
  // @ts-ignore
962
1557
  global['__ELECTRON_WINDOWS_MANAGER__'] = undefined;
963
1558
  exports.isInitialized = false;
964
- const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl, webviewDomainWhiteList) => {
1559
+ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList) => {
965
1560
  // @ts-ignore
966
1561
  if (exports.isInitialized && global['__ELECTRON_WINDOWS_MANAGER__']) {
967
1562
  // @ts-ignore
@@ -969,7 +1564,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
969
1564
  }
970
1565
  exports.isInitialized = true;
971
1566
  // @ts-ignore
972
- const wm = global['__ELECTRON_WINDOWS_MANAGER__'] = new WindowsManager(preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl, webviewDomainWhiteList);
1567
+ const wm = global['__ELECTRON_WINDOWS_MANAGER__'] = new WindowsManager(preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList);
973
1568
  eIpc.mainIPC.handleRenderer('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', async (data) => {
974
1569
  if (data?.type === 'create') {
975
1570
  const opt = data;
@@ -985,46 +1580,71 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
985
1580
  findWin.webContents.reload();
986
1581
  }, 100);
987
1582
  }
1583
+ if (opt.data.browserWindow?.parent) {
1584
+ try {
1585
+ if (findWin._type === 'BW') {
1586
+ findWin.setParentWindow(electron.BrowserWindow.fromId(Number(opt.data.browserWindow.parent)));
1587
+ }
1588
+ if (findWin._type === 'BV') {
1589
+ electron.BrowserWindow.fromId(Number(opt.data.browserWindow.parent))?.addBrowserView(findWin);
1590
+ }
1591
+ }
1592
+ catch (error) {
1593
+ log('error', 'setParentWindow error:', error);
1594
+ }
1595
+ }
1596
+ if (findWin?._type === 'BV' && opt.data.zIndex) {
1597
+ findWin._zIndex = opt.data.zIndex;
1598
+ }
1599
+ const winId = wm._getWebContentsId(findWin);
988
1600
  return {
989
- winId: Number(`${findWin?.id || findWin?._id || -1}`),
1601
+ winId: Number(`${winId || -1}`),
990
1602
  winName: `${findWin?._name || ''}`,
991
1603
  winType: `${findWin?._type || ''}`,
992
1604
  winExtraData: `${findWin?._extraData || ''}`,
993
1605
  winInitUrl: `${findWin?._initUrl || ''}`,
1606
+ winZIndex: `${findWin._zIndex || 0}`,
994
1607
  };
995
1608
  }
996
1609
  const res = await wm.create(opt.data);
1610
+ const winId = wm._getWebContentsId(res);
997
1611
  return {
998
- winId: Number(`${res.id || res._id || -1}`),
1612
+ winId: Number(`${winId || -1}`),
999
1613
  winName: `${res?._name || ''}`,
1000
1614
  winType: `${res?._type || ''}`,
1001
1615
  winExtraData: `${res?._extraData || ''}`,
1002
1616
  winInitUrl: `${res?._initUrl || ''}`,
1617
+ winZIndex: `${res?._zIndex || 0}`,
1003
1618
  };
1004
1619
  }
1005
1620
  if (data?.type === 'get') {
1006
1621
  const opt = data;
1007
1622
  const res = wm.get(opt?.data);
1623
+ const winId = wm._getWebContentsId(res);
1008
1624
  return {
1009
- winId: Number(`${res?.id || res?._id || -1}`),
1625
+ winId: Number(`${winId || -1}`),
1010
1626
  winName: `${res?._name || ''}`,
1011
1627
  winType: `${res?._type || ''}`,
1012
1628
  winExtraData: `${res?._extraData || ''}`,
1013
1629
  winInitUrl: `${res?._initUrl || ''}`,
1630
+ winZIndex: `${res?._zIndex || 0}`,
1014
1631
  };
1015
1632
  }
1016
1633
  if (data?.type === 'getAll') {
1017
1634
  const res = wm.getAll();
1018
1635
  const obj = {};
1019
1636
  res.forEach(i => {
1020
- // @ts-ignore
1021
- obj[i.id || i._id] = {
1022
- winId: Number(`${i?.id || i?._id || -1}`),
1023
- winName: `${i?._name || ''}`,
1024
- winType: `${i?._type || ''}`,
1025
- winExtraData: `${i?._extraData || ''}`,
1026
- winInitUrl: `${i?._initUrl || ''}`,
1027
- };
1637
+ const winId = wm._getWebContentsId(i);
1638
+ if (winId !== undefined) {
1639
+ obj[winId] = {
1640
+ winId: Number(`${winId}`),
1641
+ winName: `${i?._name || ''}`,
1642
+ winType: `${i?._type || ''}`,
1643
+ winExtraData: `${i?._extraData || ''}`,
1644
+ winInitUrl: `${i?._initUrl || ''}`,
1645
+ winZIndex: `${i?._zIndex || 0}`,
1646
+ };
1647
+ }
1028
1648
  });
1029
1649
  return obj;
1030
1650
  }
@@ -1041,12 +1661,14 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
1041
1661
  const opt = data;
1042
1662
  const res = wm.rename(opt.data.idOrName, opt.data.newName);
1043
1663
  if (res) {
1664
+ const winId = wm._getWebContentsId(res);
1044
1665
  return {
1045
- winId: Number(`${res?.id || res?._id || -1}`),
1666
+ winId: Number(`${winId || -1}`),
1046
1667
  winName: `${res?._name || ''}`,
1047
1668
  winType: `${res?._type || ''}`,
1048
1669
  winExtraData: `${res?._extraData || ''}`,
1049
1670
  winInitUrl: `${res?._initUrl || ''}`,
1671
+ winZIndex: `${res?._zIndex || 0}`,
1050
1672
  };
1051
1673
  }
1052
1674
  return undefined;
@@ -1055,40 +1677,23 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
1055
1677
  const opt = data;
1056
1678
  const res = wm.reInitUrl(opt.data.idOrName, opt.data.url);
1057
1679
  if (res) {
1680
+ const winId = wm._getWebContentsId(res);
1058
1681
  return {
1059
- winId: Number(`${res?.id || res?._id || -1}`),
1682
+ winId: Number(`${winId || -1}`),
1060
1683
  winName: `${res?._name || ''}`,
1061
1684
  winType: `${res?._type || ''}`,
1062
1685
  winExtraData: `${res?._extraData || ''}`,
1063
1686
  winInitUrl: `${res?._initUrl || ''}`,
1687
+ winZIndex: `${res?._zIndex || 0}`,
1064
1688
  };
1065
1689
  }
1066
1690
  return undefined;
1067
1691
  }
1068
- if (data?.type === 'getWindowForWebContentId') {
1692
+ if (data?.type === 'getWindowForWebContentsId') {
1069
1693
  const opt = data;
1070
- const targetWebContents = electron.webContents.fromId(opt.data);
1071
- if (targetWebContents) {
1072
- let win = electron.BrowserWindow.fromWebContents(targetWebContents);
1073
- if (!win) {
1074
- // 获取所有的 BrowserWindows
1075
- let allWindows = electron.BrowserWindow.getAllWindows();
1076
- // 遍历所有窗口,检查每个窗口的 BrowserView
1077
- for (let _win of allWindows) {
1078
- let views = _win.getBrowserViews();
1079
- // 遍历窗口的所有 BrowserView
1080
- for (let view of views) {
1081
- if (view.webContents === targetWebContents) {
1082
- win = _win;
1083
- break;
1084
- }
1085
- }
1086
- if (win)
1087
- break;
1088
- }
1089
- }
1090
- // @ts-ignore
1091
- return win?.id || win?._id;
1694
+ const res = await wm.getWindowForWebContentsId(opt.data);
1695
+ if (res) {
1696
+ return wm._getWebContentsId(res);
1092
1697
  }
1093
1698
  return undefined;
1094
1699
  }
@@ -1129,15 +1734,20 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
1129
1734
  return undefined;
1130
1735
  }
1131
1736
  // 是否开启预加载窗口
1132
- if (data?.type === 'setPreloadWebContentsUrl') {
1737
+ if (data?.type === 'setPreloadWebContentsConfig') {
1133
1738
  const opt = data;
1134
- wm.setPreloadWebContentsUrl(opt.data);
1739
+ wm.setPreloadWebContentsConfig(opt.data);
1135
1740
  }
1136
1741
  if (data?.type === 'createPreloadWebContents') {
1137
1742
  const opt = data;
1138
1743
  const res = await wm.createPreloadWebContents(opt.data);
1139
1744
  return res;
1140
1745
  }
1746
+ if (data?.type === 'sortBrowserViews') {
1747
+ const opt = data;
1748
+ wm.sortBrowserViews(opt.data);
1749
+ return true;
1750
+ }
1141
1751
  return undefined;
1142
1752
  });
1143
1753
  return wm;