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