@lynker-desktop/electron-window-manager 0.0.9-alpha.5 → 0.0.9-alpha.51
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 +918 -363
- 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 +925 -370
- 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,21 +268,30 @@ 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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
281
|
+
const webContentsId = instance.webContents?.id;
|
|
282
|
+
log('log', '创建预BW: ', webContentsId, 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
|
|
186
290
|
instance.loadURL(url ? `${url}` : 'about:blank');
|
|
291
|
+
resolve(instance);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
resolve(null);
|
|
187
295
|
}
|
|
188
296
|
});
|
|
189
297
|
}
|
|
@@ -191,20 +299,24 @@ class WindowsManager {
|
|
|
191
299
|
* 创建预加载的浏览器视图
|
|
192
300
|
* @returns 预加载的浏览器视图
|
|
193
301
|
*/
|
|
194
|
-
_createPreloadBV() {
|
|
302
|
+
_createPreloadBV(options = {}) {
|
|
195
303
|
return new Promise((resolve) => {
|
|
196
304
|
const preload = this.preload;
|
|
197
|
-
const url = this.
|
|
198
|
-
if (this.
|
|
305
|
+
const url = this.preloadWebContentsConfig?.url;
|
|
306
|
+
if (this.preloadWebContentsConfig?.url) {
|
|
307
|
+
const webPreferences = (options.webPreferences || {});
|
|
199
308
|
const instance = new BrowserView({
|
|
200
309
|
webPreferences: {
|
|
310
|
+
...webPreferences,
|
|
311
|
+
sandbox: false,
|
|
201
312
|
webviewTag: true,
|
|
202
313
|
plugins: true,
|
|
203
|
-
nodeIntegration: true,
|
|
204
|
-
contextIsolation: false,
|
|
314
|
+
nodeIntegration: this.preloadWebContentsConfig?.nodeIntegration ?? true,
|
|
315
|
+
contextIsolation: this.preloadWebContentsConfig?.contextIsolation ?? false,
|
|
205
316
|
// backgroundThrottling: false,
|
|
206
317
|
webSecurity: false,
|
|
207
|
-
preload: preload,
|
|
318
|
+
preload: webPreferences.preload || preload,
|
|
319
|
+
defaultEncoding: 'utf-8',
|
|
208
320
|
}
|
|
209
321
|
});
|
|
210
322
|
try {
|
|
@@ -214,100 +326,117 @@ class WindowsManager {
|
|
|
214
326
|
log('error', '预加载 BV 设置 remote 失败', error);
|
|
215
327
|
}
|
|
216
328
|
try {
|
|
217
|
-
|
|
218
|
-
|
|
329
|
+
const webContentsId = instance.webContents?.id;
|
|
330
|
+
if (webContentsId !== undefined) {
|
|
331
|
+
// @ts-ignore
|
|
332
|
+
instance._id = webContentsId;
|
|
333
|
+
}
|
|
334
|
+
// 设置默认zIndex层级
|
|
335
|
+
instance._zIndex = 0;
|
|
219
336
|
}
|
|
220
337
|
catch (error) {
|
|
221
338
|
log('error', '预加载 BV 设置 _id 失败', error);
|
|
222
339
|
}
|
|
223
340
|
// @ts-ignore
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
341
|
+
const webContentsId = instance.webContents?.id;
|
|
342
|
+
log('log', '创建预BV: ', webContentsId, this.preloadWebContentsConfig?.url);
|
|
343
|
+
// instance.webContents.once('did-finish-load', () => {
|
|
344
|
+
// resolve(instance as BVItem);
|
|
345
|
+
// });
|
|
346
|
+
// instance.webContents.once('did-fail-load', () => {
|
|
347
|
+
// resolve(instance as BVItem);
|
|
348
|
+
// });
|
|
349
|
+
// @ts-ignore
|
|
231
350
|
instance.webContents.loadURL(url || 'about:blank');
|
|
351
|
+
resolve(instance);
|
|
232
352
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
create(options) {
|
|
236
|
-
return new Promise((resolve, reject) => {
|
|
237
|
-
// 将创建请求添加到队列
|
|
238
|
-
this.createQueue.push({ options, resolve, reject });
|
|
239
|
-
// 如果当前没有在创建,则开始处理队列
|
|
240
|
-
if (!this.isCreating) {
|
|
241
|
-
this.processCreateQueue();
|
|
353
|
+
else {
|
|
354
|
+
resolve(null);
|
|
242
355
|
}
|
|
243
356
|
});
|
|
244
357
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
this.isCreating = true;
|
|
253
|
-
while (this.createQueue.length > 0) {
|
|
254
|
-
const { options, resolve, reject } = this.createQueue.shift();
|
|
255
|
-
try {
|
|
256
|
-
const window = await this._createWindow(options);
|
|
257
|
-
resolve(window);
|
|
258
|
-
}
|
|
259
|
-
catch (error) {
|
|
260
|
-
log('error', 'create window failed:', error);
|
|
261
|
-
reject(error);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
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;
|
|
265
365
|
}
|
|
266
366
|
/**
|
|
267
367
|
* 实际的窗口创建逻辑
|
|
268
368
|
*/
|
|
269
369
|
async _createWindow(options) {
|
|
270
370
|
let window;
|
|
271
|
-
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
|
+
}
|
|
272
381
|
options.type = type;
|
|
273
382
|
// 优先复用预创建实例
|
|
274
383
|
let preloadWin = null;
|
|
275
|
-
if (type === 'BW' && usePreload && this.
|
|
384
|
+
if (type === 'BW' && usePreload && this.preloadWebContentsConfig?.url) {
|
|
276
385
|
const bwOptions = browserWindowOptions || {};
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
386
|
+
if (bwOptions.frame === false) {
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
400
|
+
else {
|
|
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
|
+
}
|
|
290
412
|
}
|
|
291
413
|
}
|
|
292
414
|
else {
|
|
293
|
-
if (this.preloadedBW) {
|
|
415
|
+
if (this.preloadWebContentsConfig.enableBW !== false && this.preloadedBW) {
|
|
294
416
|
preloadWin = this.preloadedBW;
|
|
295
|
-
this.preloadedBW =
|
|
296
|
-
|
|
417
|
+
this.preloadedBW = await this._createPreloadBW({
|
|
418
|
+
webPreferences: {
|
|
419
|
+
preload: bwOptions?.webPreferences?.preload || this.preload,
|
|
420
|
+
}
|
|
421
|
+
});
|
|
297
422
|
}
|
|
298
423
|
}
|
|
299
424
|
}
|
|
300
|
-
if (type === 'BV' && usePreload && this.
|
|
301
|
-
|
|
425
|
+
if (type === 'BV' && usePreload && this.preloadWebContentsConfig?.url) {
|
|
426
|
+
const bvOptions = browserWindowOptions || {};
|
|
427
|
+
if (this.preloadWebContentsConfig.enableBV !== false && this.preloadedBV) {
|
|
302
428
|
preloadWin = this.preloadedBV;
|
|
303
|
-
this.preloadedBV =
|
|
304
|
-
|
|
429
|
+
this.preloadedBV = await this._createPreloadBV({
|
|
430
|
+
webPreferences: {
|
|
431
|
+
preload: bvOptions?.webPreferences?.preload || this.preload,
|
|
432
|
+
}
|
|
433
|
+
});
|
|
305
434
|
}
|
|
306
435
|
}
|
|
307
436
|
if (preloadWin) {
|
|
308
437
|
const win = preloadWin;
|
|
309
|
-
log('log', `${name} 使用预加载窗口(${type})`, win
|
|
310
|
-
win._type =
|
|
438
|
+
log('log', `${name} 使用预加载窗口(${type})`, this._getWebContentsId(win));
|
|
439
|
+
win._type = type;
|
|
311
440
|
win._name = options.name || 'anonymous';
|
|
312
441
|
win._extraData = `${options?.extraData || ''}`;
|
|
313
442
|
win._initUrl = `${options?.url || ''}`;
|
|
@@ -321,27 +450,101 @@ class WindowsManager {
|
|
|
321
450
|
if (type === 'BV') {
|
|
322
451
|
this._applyBrowserViewOptions(win, options);
|
|
323
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
|
+
}
|
|
324
527
|
window = win;
|
|
325
528
|
}
|
|
326
529
|
try {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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 {
|
|
345
548
|
let parentWin = undefined;
|
|
346
549
|
if (typeof browserWindowOptions?.parent === 'number') {
|
|
347
550
|
parentWin = BrowserWindow.fromId(browserWindowOptions?.parent) || undefined;
|
|
@@ -359,6 +562,7 @@ class WindowsManager {
|
|
|
359
562
|
window = type === 'BV' ?
|
|
360
563
|
new BrowserView(lodash.merge((browserWindowOptions || {}), {
|
|
361
564
|
webPreferences: lodash.merge({
|
|
565
|
+
sandbox: false,
|
|
362
566
|
webviewTag: true,
|
|
363
567
|
// session: getCustomSession(),
|
|
364
568
|
plugins: true,
|
|
@@ -369,6 +573,7 @@ class WindowsManager {
|
|
|
369
573
|
nativeWindowOpen: true,
|
|
370
574
|
webSecurity: false,
|
|
371
575
|
preload: preload,
|
|
576
|
+
defaultEncoding: 'utf-8',
|
|
372
577
|
}, browserWindowOptions?.webPreferences || {})
|
|
373
578
|
}))
|
|
374
579
|
: new BrowserWindow(lodash.merge({
|
|
@@ -376,6 +581,7 @@ class WindowsManager {
|
|
|
376
581
|
}, (browserWindowOptions || {}), {
|
|
377
582
|
parent: parentWin,
|
|
378
583
|
webPreferences: lodash.merge({
|
|
584
|
+
sandbox: false,
|
|
379
585
|
webviewTag: true,
|
|
380
586
|
// session: getCustomSession(),
|
|
381
587
|
plugins: true,
|
|
@@ -385,6 +591,7 @@ class WindowsManager {
|
|
|
385
591
|
nativeWindowOpen: true,
|
|
386
592
|
webSecurity: false,
|
|
387
593
|
preload: preload,
|
|
594
|
+
defaultEncoding: 'utf-8',
|
|
388
595
|
}, browserWindowOptions?.webPreferences || {})
|
|
389
596
|
}));
|
|
390
597
|
log('log', `${name} 不使用 ${type === 'BV' ? 'preloadedBV' : 'preloadedBW'}`, window?.webContents?.id);
|
|
@@ -395,16 +602,15 @@ class WindowsManager {
|
|
|
395
602
|
log('error', 'enable: ', error);
|
|
396
603
|
}
|
|
397
604
|
}
|
|
605
|
+
// 停止加载
|
|
606
|
+
// window.webContents?.stop?.();
|
|
398
607
|
// @ts-ignore
|
|
399
|
-
try {
|
|
400
|
-
window.id = Number(`${window.id || window.webContents.id}`);
|
|
401
|
-
}
|
|
402
|
-
catch (error) {
|
|
403
|
-
// log('error', 'set id: ', error)
|
|
404
|
-
}
|
|
405
608
|
// @ts-ignore
|
|
406
609
|
try {
|
|
407
|
-
|
|
610
|
+
const webContentsId = this._getWebContentsId(window);
|
|
611
|
+
if (webContentsId !== undefined) {
|
|
612
|
+
window._id = Number(`${webContentsId}`);
|
|
613
|
+
}
|
|
408
614
|
}
|
|
409
615
|
catch (error) {
|
|
410
616
|
// log('error', 'set id: ', error)
|
|
@@ -413,14 +619,16 @@ class WindowsManager {
|
|
|
413
619
|
window._name = name;
|
|
414
620
|
window._extraData = `${options?.extraData || ''}`;
|
|
415
621
|
window._initUrl = `${options?.url || ''}`;
|
|
416
|
-
|
|
417
|
-
|
|
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') {
|
|
418
626
|
if (type === 'BW') {
|
|
419
627
|
// @ts-ignore
|
|
420
628
|
this._setLoadingView(window, options);
|
|
421
629
|
}
|
|
422
630
|
}
|
|
423
|
-
if (errorView?.url) {
|
|
631
|
+
if (errorView?.url && errorView?.url !== 'about:blank') {
|
|
424
632
|
if (type === 'BW') {
|
|
425
633
|
const showErrorView = lodash.debounce(() => {
|
|
426
634
|
const _url = window._initUrl;
|
|
@@ -472,46 +680,45 @@ class WindowsManager {
|
|
|
472
680
|
}
|
|
473
681
|
window.webContents.on('did-attach-webview', (_event, webContents) => {
|
|
474
682
|
const tryEnable = () => {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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);
|
|
496
707
|
}
|
|
497
708
|
else {
|
|
498
|
-
|
|
499
|
-
return hostname === domain;
|
|
709
|
+
log('log', 'webview 域名未在白名单,未启用 remote', url);
|
|
500
710
|
}
|
|
501
|
-
}) || isLocalhost(hostname); // 允许本地回环和内网地址
|
|
502
|
-
if (isWhiteListed) {
|
|
503
|
-
enable(webContents);
|
|
504
711
|
}
|
|
505
|
-
|
|
506
|
-
log('log', 'webview
|
|
712
|
+
catch {
|
|
713
|
+
log('log', 'webview url 解析失败,未启用 remote', url);
|
|
507
714
|
}
|
|
508
715
|
}
|
|
509
|
-
|
|
510
|
-
|
|
716
|
+
else {
|
|
717
|
+
enable(webContents); // 没有配置白名单则全部允许
|
|
511
718
|
}
|
|
512
719
|
}
|
|
513
|
-
|
|
514
|
-
|
|
720
|
+
catch (error) {
|
|
721
|
+
log('error', 'tryEnable webview error:', error);
|
|
515
722
|
}
|
|
516
723
|
};
|
|
517
724
|
// 只监听一次,防止多次触发
|
|
@@ -523,11 +730,16 @@ class WindowsManager {
|
|
|
523
730
|
webContents.on('did-navigate', onDidNavigate);
|
|
524
731
|
webContents.on('did-finish-load', onDidNavigate);
|
|
525
732
|
});
|
|
526
|
-
window.webContents.on('close', () => {
|
|
527
|
-
|
|
528
|
-
})
|
|
733
|
+
// window.webContents.on('close', () => {
|
|
734
|
+
// this.windows.delete(window.id || window._id)
|
|
735
|
+
// })
|
|
529
736
|
window.webContents.on('destroyed', () => {
|
|
530
|
-
this.
|
|
737
|
+
const winId = this._getWebContentsId(window);
|
|
738
|
+
if (winId !== undefined) {
|
|
739
|
+
this.windows.delete(winId);
|
|
740
|
+
}
|
|
741
|
+
// 同步清理名称索引
|
|
742
|
+
this.windowsByName.delete(window._name);
|
|
531
743
|
});
|
|
532
744
|
window.webContents.on('dom-ready', () => {
|
|
533
745
|
if (openDevTools) {
|
|
@@ -541,23 +753,33 @@ class WindowsManager {
|
|
|
541
753
|
}
|
|
542
754
|
// @ts-ignore
|
|
543
755
|
window.on('closed', () => {
|
|
544
|
-
|
|
545
|
-
|
|
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);
|
|
546
763
|
});
|
|
547
764
|
}
|
|
548
765
|
if (type === 'BV') {
|
|
549
766
|
parentWin?.addBrowserView(window);
|
|
550
767
|
log('log', 'create - addBrowserView');
|
|
551
768
|
}
|
|
552
|
-
|
|
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
|
+
}
|
|
553
775
|
log('log', 'create', this.windows.keys());
|
|
554
776
|
// 初始化值
|
|
555
777
|
window.webContents.on('did-finish-load', () => {
|
|
556
|
-
|
|
778
|
+
log('log', 'did-finish-load', this._getWebContentsId(window));
|
|
557
779
|
initWebContentsVal(window, `${preload || ''}`);
|
|
558
780
|
});
|
|
559
781
|
window.webContents.on('did-start-loading', () => {
|
|
560
|
-
|
|
782
|
+
log('log', 'did-start-loading', this._getWebContentsId(window));
|
|
561
783
|
initWebContentsVal(window, `${preload || ''}`);
|
|
562
784
|
});
|
|
563
785
|
if (type === 'BW') {
|
|
@@ -568,7 +790,7 @@ class WindowsManager {
|
|
|
568
790
|
try {
|
|
569
791
|
window.dispatchEvent(new Event('focus'));
|
|
570
792
|
} catch (error) {
|
|
571
|
-
|
|
793
|
+
// 忽略错误,避免影响主流程
|
|
572
794
|
}
|
|
573
795
|
`);
|
|
574
796
|
}
|
|
@@ -582,7 +804,7 @@ class WindowsManager {
|
|
|
582
804
|
try {
|
|
583
805
|
window.dispatchEvent(new Event('blur'));
|
|
584
806
|
} catch (error) {
|
|
585
|
-
|
|
807
|
+
// 忽略错误,避免影响主流程
|
|
586
808
|
}
|
|
587
809
|
`);
|
|
588
810
|
}
|
|
@@ -618,39 +840,58 @@ class WindowsManager {
|
|
|
618
840
|
});
|
|
619
841
|
try {
|
|
620
842
|
const _addBrowserView = window.addBrowserView;
|
|
621
|
-
window.addBrowserView = (view) => {
|
|
843
|
+
window.addBrowserView = (view, isSort = false) => {
|
|
622
844
|
_addBrowserView.call(window, view);
|
|
623
845
|
handleBrowserViewFocus(view);
|
|
846
|
+
// 添加BrowserView后重新排序(如果未禁用自动排序)
|
|
847
|
+
log('log', 'addBrowserView-sort', isSort, window.getBrowserViews());
|
|
848
|
+
if (isSort) {
|
|
849
|
+
this.sortBrowserViewsDebounced(window, view);
|
|
850
|
+
}
|
|
624
851
|
};
|
|
625
852
|
const _removeBrowserView = window.removeBrowserView;
|
|
626
|
-
window.removeBrowserView = (view) => {
|
|
853
|
+
window.removeBrowserView = (view, isSort = false) => {
|
|
627
854
|
_removeBrowserView.call(window, view);
|
|
628
855
|
handleBrowserViewBlur(view);
|
|
856
|
+
// 移除BrowserView后重新排序(如果未禁用自动排序)
|
|
857
|
+
log('log', 'removeBrowserView-sort', isSort);
|
|
858
|
+
if (isSort) {
|
|
859
|
+
this.sortBrowserViewsDebounced(window, view);
|
|
860
|
+
}
|
|
629
861
|
};
|
|
630
862
|
const _setBrowserView = window.setBrowserView;
|
|
631
|
-
window.setBrowserView = (view) => {
|
|
863
|
+
window.setBrowserView = (view, isSort = false) => {
|
|
632
864
|
const views = window.getBrowserViews() || [];
|
|
633
|
-
for (const
|
|
634
|
-
handleBrowserViewBlur(
|
|
865
|
+
for (const existingView of views) {
|
|
866
|
+
handleBrowserViewBlur(existingView);
|
|
635
867
|
}
|
|
636
868
|
_setBrowserView.call(window, view);
|
|
637
869
|
handleBrowserViewFocus(view);
|
|
870
|
+
log('log', 'setBrowserView-sort', isSort);
|
|
871
|
+
if (isSort) {
|
|
872
|
+
this.sortBrowserViewsDebounced(window, view);
|
|
873
|
+
}
|
|
638
874
|
};
|
|
639
875
|
}
|
|
640
876
|
catch (error) {
|
|
641
877
|
log('error', 'focus', error);
|
|
642
878
|
}
|
|
643
879
|
}
|
|
644
|
-
console.log('message xxxx', options.url);
|
|
645
880
|
if (options.url) {
|
|
646
881
|
// @ts-ignore
|
|
647
882
|
window.loadURL ? window.loadURL(options.url) : window.webContents.loadURL(options.url);
|
|
648
|
-
|
|
649
|
-
|
|
883
|
+
if (options.browserWindow?.focusable !== false) {
|
|
884
|
+
window?.focus?.();
|
|
885
|
+
}
|
|
886
|
+
window?.webContents?.focus?.();
|
|
650
887
|
}
|
|
651
888
|
}
|
|
652
889
|
catch (error) {
|
|
653
890
|
log('error', 'create', error);
|
|
891
|
+
throw error;
|
|
892
|
+
}
|
|
893
|
+
if (!window) {
|
|
894
|
+
throw new Error(`Failed to create window: ${name}`);
|
|
654
895
|
}
|
|
655
896
|
return window;
|
|
656
897
|
}
|
|
@@ -659,6 +900,7 @@ class WindowsManager {
|
|
|
659
900
|
const { loadingView, preventOriginNavigate = false, } = createOptions;
|
|
660
901
|
let _loadingView = new BrowserView({
|
|
661
902
|
webPreferences: {
|
|
903
|
+
sandbox: false,
|
|
662
904
|
// session: getCustomSession(),
|
|
663
905
|
contextIsolation: false,
|
|
664
906
|
nodeIntegration: true,
|
|
@@ -671,7 +913,7 @@ class WindowsManager {
|
|
|
671
913
|
}
|
|
672
914
|
const loadLoadingView = () => {
|
|
673
915
|
const [viewWidth, viewHeight] = window.getSize();
|
|
674
|
-
window.
|
|
916
|
+
window.addBrowserView(_loadingView);
|
|
675
917
|
_loadingView.setBounds({
|
|
676
918
|
x: 0,
|
|
677
919
|
y: 0,
|
|
@@ -711,7 +953,7 @@ class WindowsManager {
|
|
|
711
953
|
return;
|
|
712
954
|
}
|
|
713
955
|
if (_loadingView.webContents && !_loadingView.webContents.isDestroyed()) {
|
|
714
|
-
window.
|
|
956
|
+
window.addBrowserView(_loadingView);
|
|
715
957
|
}
|
|
716
958
|
else {
|
|
717
959
|
// if loadingView has been destroyed
|
|
@@ -727,148 +969,231 @@ class WindowsManager {
|
|
|
727
969
|
window.webContents.on('did-stop-loading', onFailure);
|
|
728
970
|
}
|
|
729
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
|
+
}
|
|
730
1028
|
get(idOrName) {
|
|
731
1029
|
log('log', 'get', idOrName);
|
|
732
1030
|
let win;
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
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);
|
|
738
1040
|
}
|
|
739
|
-
|
|
740
|
-
|
|
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
|
+
});
|
|
741
1053
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
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);
|
|
746
1064
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
win = i;
|
|
750
|
-
}
|
|
1065
|
+
if (win._name) {
|
|
1066
|
+
this.windowsByName.delete(win._name);
|
|
751
1067
|
}
|
|
752
|
-
});
|
|
753
|
-
// @ts-ignore
|
|
754
|
-
if (win && win?.webContents?.isDestroyed && !win?.webContents?.isDestroyed?.()) {
|
|
755
|
-
return win;
|
|
756
1068
|
}
|
|
1069
|
+
// 延迟清理其他已销毁的窗口
|
|
1070
|
+
this.cleanupDestroyedWindowsDebounced();
|
|
757
1071
|
return undefined;
|
|
758
1072
|
}
|
|
759
1073
|
getAll(type) {
|
|
760
1074
|
log('log', 'getAll');
|
|
761
|
-
|
|
762
|
-
const
|
|
1075
|
+
// 先清理已销毁的窗口
|
|
1076
|
+
const toDelete = [];
|
|
763
1077
|
this.windows.forEach((win, key) => {
|
|
764
|
-
if (
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
}
|
|
770
|
-
if (win?._type === 'BV') {
|
|
771
|
-
bvWindows.set(key, win);
|
|
1078
|
+
if (this._isWindowDestroyed(win)) {
|
|
1079
|
+
toDelete.push(key);
|
|
1080
|
+
if (win?._name) {
|
|
1081
|
+
this.windowsByName.delete(win._name);
|
|
1082
|
+
}
|
|
772
1083
|
}
|
|
773
1084
|
});
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
if (type === 'BV') {
|
|
778
|
-
return bvWindows;
|
|
1085
|
+
toDelete.forEach(key => this.windows.delete(key));
|
|
1086
|
+
if (!type) {
|
|
1087
|
+
return this.windows;
|
|
779
1088
|
}
|
|
780
|
-
|
|
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;
|
|
781
1097
|
}
|
|
782
1098
|
close(idOrName) {
|
|
783
1099
|
log('log', 'close', idOrName);
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
});
|
|
797
|
-
// @ts-ignore
|
|
798
|
-
win && this.windows.delete(win?.id);
|
|
799
|
-
// @ts-ignore
|
|
800
|
-
if (win) {
|
|
801
|
-
// @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 {
|
|
802
1112
|
if (win._type === 'BV') {
|
|
1113
|
+
// 从所有 BW 窗口中移除该 BV
|
|
803
1114
|
this.windows.forEach(i => {
|
|
804
|
-
if (i?._type === 'BW') {
|
|
1115
|
+
if (i?._type === 'BW' && !this._isWindowDestroyed(i)) {
|
|
805
1116
|
const _win = i;
|
|
806
|
-
|
|
1117
|
+
try {
|
|
1118
|
+
_win.removeBrowserView(win);
|
|
1119
|
+
}
|
|
1120
|
+
catch (error) {
|
|
1121
|
+
// 忽略错误,可能已经移除
|
|
1122
|
+
}
|
|
807
1123
|
}
|
|
808
1124
|
});
|
|
809
1125
|
}
|
|
810
1126
|
// @ts-ignore
|
|
811
|
-
win
|
|
1127
|
+
win.webContents?.destroy?.();
|
|
812
1128
|
// @ts-ignore
|
|
813
|
-
win
|
|
1129
|
+
win.close?.();
|
|
814
1130
|
// @ts-ignore
|
|
815
|
-
win
|
|
1131
|
+
win.destroy?.();
|
|
1132
|
+
}
|
|
1133
|
+
catch (error) {
|
|
1134
|
+
log('error', 'close window error:', error);
|
|
816
1135
|
}
|
|
817
1136
|
return true;
|
|
818
1137
|
}
|
|
819
1138
|
closeAll() {
|
|
820
1139
|
log('log', 'closeAll');
|
|
821
|
-
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) => {
|
|
822
1160
|
try {
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
if (win._type === 'BV') {
|
|
826
|
-
this.windows.forEach(i => {
|
|
827
|
-
if (i?._type === 'BW') {
|
|
828
|
-
const _win = i;
|
|
829
|
-
_win.removeBrowserView(win);
|
|
830
|
-
}
|
|
831
|
-
});
|
|
1161
|
+
if (this._isWindowDestroyed(win)) {
|
|
1162
|
+
return;
|
|
832
1163
|
}
|
|
833
1164
|
// @ts-ignore
|
|
834
|
-
win
|
|
1165
|
+
win.webContents?.destroy?.();
|
|
835
1166
|
// @ts-ignore
|
|
836
|
-
win
|
|
1167
|
+
win.close?.();
|
|
837
1168
|
// @ts-ignore
|
|
838
|
-
win
|
|
1169
|
+
win.destroy?.();
|
|
839
1170
|
}
|
|
840
1171
|
catch (error) {
|
|
1172
|
+
log('error', 'closeAll error:', error);
|
|
841
1173
|
}
|
|
842
1174
|
});
|
|
843
1175
|
}
|
|
844
1176
|
rename(idOrName, newName) {
|
|
845
1177
|
log('log', 'rename', idOrName, newName);
|
|
846
|
-
|
|
847
|
-
// 先查找目标窗口
|
|
848
|
-
if (typeof idOrName === 'number') {
|
|
849
|
-
win = this.get(idOrName);
|
|
850
|
-
}
|
|
851
|
-
else if (typeof idOrName === 'string') {
|
|
852
|
-
this.windows.forEach(i => {
|
|
853
|
-
if (i?._name === idOrName) {
|
|
854
|
-
win = i;
|
|
855
|
-
}
|
|
856
|
-
});
|
|
857
|
-
}
|
|
1178
|
+
const win = this.get(idOrName);
|
|
858
1179
|
if (!win) {
|
|
859
|
-
// 没有找到目标窗口
|
|
860
1180
|
return undefined;
|
|
861
1181
|
}
|
|
862
|
-
//
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
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;
|
|
867
1189
|
}
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
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);
|
|
872
1197
|
}
|
|
873
1198
|
// 修改名字并同步 webContents
|
|
874
1199
|
win._name = newName;
|
|
@@ -877,12 +1202,7 @@ class WindowsManager {
|
|
|
877
1202
|
}
|
|
878
1203
|
reInitUrl(idOrName, url) {
|
|
879
1204
|
log('log', 'reInitUrl', idOrName, url);
|
|
880
|
-
|
|
881
|
-
this.windows.forEach(i => {
|
|
882
|
-
if (i?._name === idOrName) {
|
|
883
|
-
win = i;
|
|
884
|
-
}
|
|
885
|
-
});
|
|
1205
|
+
const win = this.get(idOrName);
|
|
886
1206
|
if (!win) {
|
|
887
1207
|
return undefined;
|
|
888
1208
|
}
|
|
@@ -911,6 +1231,86 @@ class WindowsManager {
|
|
|
911
1231
|
if (typeof browserWindowOptions.alwaysOnTop === 'boolean') {
|
|
912
1232
|
win.setAlwaysOnTop(browserWindowOptions.alwaysOnTop);
|
|
913
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
|
+
}
|
|
914
1314
|
// 可继续扩展其他动态属性
|
|
915
1315
|
}
|
|
916
1316
|
_applyBrowserViewOptions(view, options) {
|
|
@@ -926,35 +1326,175 @@ class WindowsManager {
|
|
|
926
1326
|
if (typeof browserWindowOptions.width === 'number' && typeof browserWindowOptions.height === 'number') {
|
|
927
1327
|
view.setBounds({ x: 0, y: 0, width: browserWindowOptions.width, height: browserWindowOptions.height });
|
|
928
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
|
+
}
|
|
929
1343
|
// 可继续扩展其他动态属性
|
|
930
1344
|
}
|
|
931
1345
|
// 生成一个bv 做为预加载资源窗口,加载完成后销毁
|
|
932
1346
|
async createPreloadWebContents(url) {
|
|
933
|
-
|
|
934
|
-
|
|
1347
|
+
let bv = null;
|
|
1348
|
+
try {
|
|
1349
|
+
bv = await this.create({
|
|
935
1350
|
type: 'BV',
|
|
936
1351
|
url,
|
|
937
1352
|
name: `preload-web-contents-${md5(url)}`,
|
|
938
1353
|
extraData: `${url}`
|
|
939
1354
|
});
|
|
940
|
-
|
|
941
|
-
this.
|
|
942
|
-
|
|
943
|
-
|
|
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);
|
|
944
1376
|
});
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
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;
|
|
949
1439
|
});
|
|
950
|
-
|
|
951
|
-
|
|
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
|
+
}
|
|
952
1492
|
}
|
|
953
1493
|
}
|
|
954
1494
|
// @ts-ignore
|
|
955
1495
|
global['__ELECTRON_WINDOWS_MANAGER__'] = undefined;
|
|
956
1496
|
let isInitialized = false;
|
|
957
|
-
const initialize = (preload, loadingViewUrl, errorViewUrl,
|
|
1497
|
+
const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList) => {
|
|
958
1498
|
// @ts-ignore
|
|
959
1499
|
if (isInitialized && global['__ELECTRON_WINDOWS_MANAGER__']) {
|
|
960
1500
|
// @ts-ignore
|
|
@@ -962,7 +1502,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
|
|
|
962
1502
|
}
|
|
963
1503
|
isInitialized = true;
|
|
964
1504
|
// @ts-ignore
|
|
965
|
-
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);
|
|
966
1506
|
eIpc.mainIPC.handleRenderer('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', async (data) => {
|
|
967
1507
|
if (data?.type === 'create') {
|
|
968
1508
|
const opt = data;
|
|
@@ -978,46 +1518,71 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
|
|
|
978
1518
|
findWin.webContents.reload();
|
|
979
1519
|
}, 100);
|
|
980
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);
|
|
981
1538
|
return {
|
|
982
|
-
winId: Number(`${
|
|
1539
|
+
winId: Number(`${winId || -1}`),
|
|
983
1540
|
winName: `${findWin?._name || ''}`,
|
|
984
1541
|
winType: `${findWin?._type || ''}`,
|
|
985
1542
|
winExtraData: `${findWin?._extraData || ''}`,
|
|
986
1543
|
winInitUrl: `${findWin?._initUrl || ''}`,
|
|
1544
|
+
winZIndex: `${findWin._zIndex || 0}`,
|
|
987
1545
|
};
|
|
988
1546
|
}
|
|
989
1547
|
const res = await wm.create(opt.data);
|
|
1548
|
+
const winId = wm._getWebContentsId(res);
|
|
990
1549
|
return {
|
|
991
|
-
winId: Number(`${
|
|
1550
|
+
winId: Number(`${winId || -1}`),
|
|
992
1551
|
winName: `${res?._name || ''}`,
|
|
993
1552
|
winType: `${res?._type || ''}`,
|
|
994
1553
|
winExtraData: `${res?._extraData || ''}`,
|
|
995
1554
|
winInitUrl: `${res?._initUrl || ''}`,
|
|
1555
|
+
winZIndex: `${res?._zIndex || 0}`,
|
|
996
1556
|
};
|
|
997
1557
|
}
|
|
998
1558
|
if (data?.type === 'get') {
|
|
999
1559
|
const opt = data;
|
|
1000
1560
|
const res = wm.get(opt?.data);
|
|
1561
|
+
const winId = wm._getWebContentsId(res);
|
|
1001
1562
|
return {
|
|
1002
|
-
winId: Number(`${
|
|
1563
|
+
winId: Number(`${winId || -1}`),
|
|
1003
1564
|
winName: `${res?._name || ''}`,
|
|
1004
1565
|
winType: `${res?._type || ''}`,
|
|
1005
1566
|
winExtraData: `${res?._extraData || ''}`,
|
|
1006
1567
|
winInitUrl: `${res?._initUrl || ''}`,
|
|
1568
|
+
winZIndex: `${res?._zIndex || 0}`,
|
|
1007
1569
|
};
|
|
1008
1570
|
}
|
|
1009
1571
|
if (data?.type === 'getAll') {
|
|
1010
1572
|
const res = wm.getAll();
|
|
1011
1573
|
const obj = {};
|
|
1012
1574
|
res.forEach(i => {
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
winId
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
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
|
+
}
|
|
1021
1586
|
});
|
|
1022
1587
|
return obj;
|
|
1023
1588
|
}
|
|
@@ -1034,12 +1599,14 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
|
|
|
1034
1599
|
const opt = data;
|
|
1035
1600
|
const res = wm.rename(opt.data.idOrName, opt.data.newName);
|
|
1036
1601
|
if (res) {
|
|
1602
|
+
const winId = wm._getWebContentsId(res);
|
|
1037
1603
|
return {
|
|
1038
|
-
winId: Number(`${
|
|
1604
|
+
winId: Number(`${winId || -1}`),
|
|
1039
1605
|
winName: `${res?._name || ''}`,
|
|
1040
1606
|
winType: `${res?._type || ''}`,
|
|
1041
1607
|
winExtraData: `${res?._extraData || ''}`,
|
|
1042
1608
|
winInitUrl: `${res?._initUrl || ''}`,
|
|
1609
|
+
winZIndex: `${res?._zIndex || 0}`,
|
|
1043
1610
|
};
|
|
1044
1611
|
}
|
|
1045
1612
|
return undefined;
|
|
@@ -1048,40 +1615,23 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
|
|
|
1048
1615
|
const opt = data;
|
|
1049
1616
|
const res = wm.reInitUrl(opt.data.idOrName, opt.data.url);
|
|
1050
1617
|
if (res) {
|
|
1618
|
+
const winId = wm._getWebContentsId(res);
|
|
1051
1619
|
return {
|
|
1052
|
-
winId: Number(`${
|
|
1620
|
+
winId: Number(`${winId || -1}`),
|
|
1053
1621
|
winName: `${res?._name || ''}`,
|
|
1054
1622
|
winType: `${res?._type || ''}`,
|
|
1055
1623
|
winExtraData: `${res?._extraData || ''}`,
|
|
1056
1624
|
winInitUrl: `${res?._initUrl || ''}`,
|
|
1625
|
+
winZIndex: `${res?._zIndex || 0}`,
|
|
1057
1626
|
};
|
|
1058
1627
|
}
|
|
1059
1628
|
return undefined;
|
|
1060
1629
|
}
|
|
1061
|
-
if (data?.type === '
|
|
1630
|
+
if (data?.type === 'getWindowForWebContentsId') {
|
|
1062
1631
|
const opt = data;
|
|
1063
|
-
const
|
|
1064
|
-
if (
|
|
1065
|
-
|
|
1066
|
-
if (!win) {
|
|
1067
|
-
// 获取所有的 BrowserWindows
|
|
1068
|
-
let allWindows = BrowserWindow.getAllWindows();
|
|
1069
|
-
// 遍历所有窗口,检查每个窗口的 BrowserView
|
|
1070
|
-
for (let _win of allWindows) {
|
|
1071
|
-
let views = _win.getBrowserViews();
|
|
1072
|
-
// 遍历窗口的所有 BrowserView
|
|
1073
|
-
for (let view of views) {
|
|
1074
|
-
if (view.webContents === targetWebContents) {
|
|
1075
|
-
win = _win;
|
|
1076
|
-
break;
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
if (win)
|
|
1080
|
-
break;
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
// @ts-ignore
|
|
1084
|
-
return win?.id || win?._id;
|
|
1632
|
+
const res = await wm.getWindowForWebContentsId(opt.data);
|
|
1633
|
+
if (res) {
|
|
1634
|
+
return wm._getWebContentsId(res);
|
|
1085
1635
|
}
|
|
1086
1636
|
return undefined;
|
|
1087
1637
|
}
|
|
@@ -1122,15 +1672,20 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
|
|
|
1122
1672
|
return undefined;
|
|
1123
1673
|
}
|
|
1124
1674
|
// 是否开启预加载窗口
|
|
1125
|
-
if (data?.type === '
|
|
1675
|
+
if (data?.type === 'setPreloadWebContentsConfig') {
|
|
1126
1676
|
const opt = data;
|
|
1127
|
-
wm.
|
|
1677
|
+
wm.setPreloadWebContentsConfig(opt.data);
|
|
1128
1678
|
}
|
|
1129
1679
|
if (data?.type === 'createPreloadWebContents') {
|
|
1130
1680
|
const opt = data;
|
|
1131
1681
|
const res = await wm.createPreloadWebContents(opt.data);
|
|
1132
1682
|
return res;
|
|
1133
1683
|
}
|
|
1684
|
+
if (data?.type === 'sortBrowserViews') {
|
|
1685
|
+
const opt = data;
|
|
1686
|
+
wm.sortBrowserViews(opt.data);
|
|
1687
|
+
return true;
|
|
1688
|
+
}
|
|
1134
1689
|
return undefined;
|
|
1135
1690
|
});
|
|
1136
1691
|
return wm;
|