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