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