@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/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,21 +281,30 @@ 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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
294
|
+
const webContentsId = instance.webContents?.id;
|
|
295
|
+
log('log', '创建预BW: ', webContentsId, this.preloadWebContentsConfig?.url);
|
|
296
|
+
// instance.webContents.once('did-finish-load', () => {
|
|
297
|
+
// resolve(instance as BWItem);
|
|
298
|
+
// });
|
|
299
|
+
// instance.webContents.once('did-fail-load', () => {
|
|
300
|
+
// resolve(instance as BWItem);
|
|
301
|
+
// });
|
|
302
|
+
// @ts-ignore
|
|
199
303
|
instance.loadURL(url ? `${url}` : 'about:blank');
|
|
304
|
+
resolve(instance);
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
resolve(null);
|
|
200
308
|
}
|
|
201
309
|
});
|
|
202
310
|
}
|
|
@@ -204,20 +312,24 @@ class WindowsManager {
|
|
|
204
312
|
* 创建预加载的浏览器视图
|
|
205
313
|
* @returns 预加载的浏览器视图
|
|
206
314
|
*/
|
|
207
|
-
_createPreloadBV() {
|
|
315
|
+
_createPreloadBV(options = {}) {
|
|
208
316
|
return new Promise((resolve) => {
|
|
209
317
|
const preload = this.preload;
|
|
210
|
-
const url = this.
|
|
211
|
-
if (this.
|
|
318
|
+
const url = this.preloadWebContentsConfig?.url;
|
|
319
|
+
if (this.preloadWebContentsConfig?.url) {
|
|
320
|
+
const webPreferences = (options.webPreferences || {});
|
|
212
321
|
const instance = new electron.BrowserView({
|
|
213
322
|
webPreferences: {
|
|
323
|
+
...webPreferences,
|
|
324
|
+
sandbox: false,
|
|
214
325
|
webviewTag: true,
|
|
215
326
|
plugins: true,
|
|
216
|
-
nodeIntegration: true,
|
|
217
|
-
contextIsolation: false,
|
|
327
|
+
nodeIntegration: this.preloadWebContentsConfig?.nodeIntegration ?? true,
|
|
328
|
+
contextIsolation: this.preloadWebContentsConfig?.contextIsolation ?? false,
|
|
218
329
|
// backgroundThrottling: false,
|
|
219
330
|
webSecurity: false,
|
|
220
|
-
preload: preload,
|
|
331
|
+
preload: webPreferences.preload || preload,
|
|
332
|
+
defaultEncoding: 'utf-8',
|
|
221
333
|
}
|
|
222
334
|
});
|
|
223
335
|
try {
|
|
@@ -227,100 +339,117 @@ class WindowsManager {
|
|
|
227
339
|
log('error', '预加载 BV 设置 remote 失败', error);
|
|
228
340
|
}
|
|
229
341
|
try {
|
|
230
|
-
|
|
231
|
-
|
|
342
|
+
const webContentsId = instance.webContents?.id;
|
|
343
|
+
if (webContentsId !== undefined) {
|
|
344
|
+
// @ts-ignore
|
|
345
|
+
instance._id = webContentsId;
|
|
346
|
+
}
|
|
347
|
+
// 设置默认zIndex层级
|
|
348
|
+
instance._zIndex = 0;
|
|
232
349
|
}
|
|
233
350
|
catch (error) {
|
|
234
351
|
log('error', '预加载 BV 设置 _id 失败', error);
|
|
235
352
|
}
|
|
236
353
|
// @ts-ignore
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
354
|
+
const webContentsId = instance.webContents?.id;
|
|
355
|
+
log('log', '创建预BV: ', webContentsId, this.preloadWebContentsConfig?.url);
|
|
356
|
+
// instance.webContents.once('did-finish-load', () => {
|
|
357
|
+
// resolve(instance as BVItem);
|
|
358
|
+
// });
|
|
359
|
+
// instance.webContents.once('did-fail-load', () => {
|
|
360
|
+
// resolve(instance as BVItem);
|
|
361
|
+
// });
|
|
362
|
+
// @ts-ignore
|
|
244
363
|
instance.webContents.loadURL(url || 'about:blank');
|
|
364
|
+
resolve(instance);
|
|
245
365
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
create(options) {
|
|
249
|
-
return new Promise((resolve, reject) => {
|
|
250
|
-
// 将创建请求添加到队列
|
|
251
|
-
this.createQueue.push({ options, resolve, reject });
|
|
252
|
-
// 如果当前没有在创建,则开始处理队列
|
|
253
|
-
if (!this.isCreating) {
|
|
254
|
-
this.processCreateQueue();
|
|
366
|
+
else {
|
|
367
|
+
resolve(null);
|
|
255
368
|
}
|
|
256
369
|
});
|
|
257
370
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
this.isCreating = true;
|
|
266
|
-
while (this.createQueue.length > 0) {
|
|
267
|
-
const { options, resolve, reject } = this.createQueue.shift();
|
|
268
|
-
try {
|
|
269
|
-
const window = await this._createWindow(options);
|
|
270
|
-
resolve(window);
|
|
271
|
-
}
|
|
272
|
-
catch (error) {
|
|
273
|
-
log('error', 'create window failed:', error);
|
|
274
|
-
reject(error);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
this.isCreating = false;
|
|
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;
|
|
278
378
|
}
|
|
279
379
|
/**
|
|
280
380
|
* 实际的窗口创建逻辑
|
|
281
381
|
*/
|
|
282
382
|
async _createWindow(options) {
|
|
283
383
|
let window;
|
|
284
|
-
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
|
+
}
|
|
285
394
|
options.type = type;
|
|
286
395
|
// 优先复用预创建实例
|
|
287
396
|
let preloadWin = null;
|
|
288
|
-
if (type === 'BW' && usePreload && this.
|
|
397
|
+
if (type === 'BW' && usePreload && this.preloadWebContentsConfig?.url) {
|
|
289
398
|
const bwOptions = browserWindowOptions || {};
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
399
|
+
if (bwOptions.frame === false) {
|
|
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
|
+
}
|
|
296
412
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
413
|
+
else {
|
|
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
|
+
}
|
|
303
425
|
}
|
|
304
426
|
}
|
|
305
427
|
else {
|
|
306
|
-
if (this.preloadedBW) {
|
|
428
|
+
if (this.preloadWebContentsConfig.enableBW !== false && this.preloadedBW) {
|
|
307
429
|
preloadWin = this.preloadedBW;
|
|
308
|
-
this.preloadedBW =
|
|
309
|
-
|
|
430
|
+
this.preloadedBW = await this._createPreloadBW({
|
|
431
|
+
webPreferences: {
|
|
432
|
+
preload: bwOptions?.webPreferences?.preload || this.preload,
|
|
433
|
+
}
|
|
434
|
+
});
|
|
310
435
|
}
|
|
311
436
|
}
|
|
312
437
|
}
|
|
313
|
-
if (type === 'BV' && usePreload && this.
|
|
314
|
-
|
|
438
|
+
if (type === 'BV' && usePreload && this.preloadWebContentsConfig?.url) {
|
|
439
|
+
const bvOptions = browserWindowOptions || {};
|
|
440
|
+
if (this.preloadWebContentsConfig.enableBV !== false && this.preloadedBV) {
|
|
315
441
|
preloadWin = this.preloadedBV;
|
|
316
|
-
this.preloadedBV =
|
|
317
|
-
|
|
442
|
+
this.preloadedBV = await this._createPreloadBV({
|
|
443
|
+
webPreferences: {
|
|
444
|
+
preload: bvOptions?.webPreferences?.preload || this.preload,
|
|
445
|
+
}
|
|
446
|
+
});
|
|
318
447
|
}
|
|
319
448
|
}
|
|
320
449
|
if (preloadWin) {
|
|
321
450
|
const win = preloadWin;
|
|
322
|
-
log('log', `${name} 使用预加载窗口(${type})`, win
|
|
323
|
-
win._type =
|
|
451
|
+
log('log', `${name} 使用预加载窗口(${type})`, this._getWebContentsId(win));
|
|
452
|
+
win._type = type;
|
|
324
453
|
win._name = options.name || 'anonymous';
|
|
325
454
|
win._extraData = `${options?.extraData || ''}`;
|
|
326
455
|
win._initUrl = `${options?.url || ''}`;
|
|
@@ -334,27 +463,101 @@ class WindowsManager {
|
|
|
334
463
|
if (type === 'BV') {
|
|
335
464
|
this._applyBrowserViewOptions(win, options);
|
|
336
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
|
+
}
|
|
337
540
|
window = win;
|
|
338
541
|
}
|
|
339
542
|
try {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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 {
|
|
358
561
|
let parentWin = undefined;
|
|
359
562
|
if (typeof browserWindowOptions?.parent === 'number') {
|
|
360
563
|
parentWin = electron.BrowserWindow.fromId(browserWindowOptions?.parent) || undefined;
|
|
@@ -372,6 +575,7 @@ class WindowsManager {
|
|
|
372
575
|
window = type === 'BV' ?
|
|
373
576
|
new electron.BrowserView(lodash.merge((browserWindowOptions || {}), {
|
|
374
577
|
webPreferences: lodash.merge({
|
|
578
|
+
sandbox: false,
|
|
375
579
|
webviewTag: true,
|
|
376
580
|
// session: getCustomSession(),
|
|
377
581
|
plugins: true,
|
|
@@ -382,6 +586,7 @@ class WindowsManager {
|
|
|
382
586
|
nativeWindowOpen: true,
|
|
383
587
|
webSecurity: false,
|
|
384
588
|
preload: preload,
|
|
589
|
+
defaultEncoding: 'utf-8',
|
|
385
590
|
}, browserWindowOptions?.webPreferences || {})
|
|
386
591
|
}))
|
|
387
592
|
: new electron.BrowserWindow(lodash.merge({
|
|
@@ -389,6 +594,7 @@ class WindowsManager {
|
|
|
389
594
|
}, (browserWindowOptions || {}), {
|
|
390
595
|
parent: parentWin,
|
|
391
596
|
webPreferences: lodash.merge({
|
|
597
|
+
sandbox: false,
|
|
392
598
|
webviewTag: true,
|
|
393
599
|
// session: getCustomSession(),
|
|
394
600
|
plugins: true,
|
|
@@ -398,6 +604,7 @@ class WindowsManager {
|
|
|
398
604
|
nativeWindowOpen: true,
|
|
399
605
|
webSecurity: false,
|
|
400
606
|
preload: preload,
|
|
607
|
+
defaultEncoding: 'utf-8',
|
|
401
608
|
}, browserWindowOptions?.webPreferences || {})
|
|
402
609
|
}));
|
|
403
610
|
log('log', `${name} 不使用 ${type === 'BV' ? 'preloadedBV' : 'preloadedBW'}`, window?.webContents?.id);
|
|
@@ -408,16 +615,15 @@ class WindowsManager {
|
|
|
408
615
|
log('error', 'enable: ', error);
|
|
409
616
|
}
|
|
410
617
|
}
|
|
618
|
+
// 停止加载
|
|
619
|
+
// window.webContents?.stop?.();
|
|
411
620
|
// @ts-ignore
|
|
412
|
-
try {
|
|
413
|
-
window.id = Number(`${window.id || window.webContents.id}`);
|
|
414
|
-
}
|
|
415
|
-
catch (error) {
|
|
416
|
-
// log('error', 'set id: ', error)
|
|
417
|
-
}
|
|
418
621
|
// @ts-ignore
|
|
419
622
|
try {
|
|
420
|
-
|
|
623
|
+
const webContentsId = this._getWebContentsId(window);
|
|
624
|
+
if (webContentsId !== undefined) {
|
|
625
|
+
window._id = Number(`${webContentsId}`);
|
|
626
|
+
}
|
|
421
627
|
}
|
|
422
628
|
catch (error) {
|
|
423
629
|
// log('error', 'set id: ', error)
|
|
@@ -426,14 +632,16 @@ class WindowsManager {
|
|
|
426
632
|
window._name = name;
|
|
427
633
|
window._extraData = `${options?.extraData || ''}`;
|
|
428
634
|
window._initUrl = `${options?.url || ''}`;
|
|
429
|
-
|
|
430
|
-
|
|
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') {
|
|
431
639
|
if (type === 'BW') {
|
|
432
640
|
// @ts-ignore
|
|
433
641
|
this._setLoadingView(window, options);
|
|
434
642
|
}
|
|
435
643
|
}
|
|
436
|
-
if (errorView?.url) {
|
|
644
|
+
if (errorView?.url && errorView?.url !== 'about:blank') {
|
|
437
645
|
if (type === 'BW') {
|
|
438
646
|
const showErrorView = lodash.debounce(() => {
|
|
439
647
|
const _url = window._initUrl;
|
|
@@ -485,46 +693,45 @@ class WindowsManager {
|
|
|
485
693
|
}
|
|
486
694
|
window.webContents.on('did-attach-webview', (_event, webContents) => {
|
|
487
695
|
const tryEnable = () => {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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);
|
|
509
720
|
}
|
|
510
721
|
else {
|
|
511
|
-
|
|
512
|
-
return hostname === domain;
|
|
722
|
+
log('log', 'webview 域名未在白名单,未启用 remote', url);
|
|
513
723
|
}
|
|
514
|
-
}) || isLocalhost(hostname); // 允许本地回环和内网地址
|
|
515
|
-
if (isWhiteListed) {
|
|
516
|
-
enable(webContents);
|
|
517
724
|
}
|
|
518
|
-
|
|
519
|
-
log('log', 'webview
|
|
725
|
+
catch {
|
|
726
|
+
log('log', 'webview url 解析失败,未启用 remote', url);
|
|
520
727
|
}
|
|
521
728
|
}
|
|
522
|
-
|
|
523
|
-
|
|
729
|
+
else {
|
|
730
|
+
enable(webContents); // 没有配置白名单则全部允许
|
|
524
731
|
}
|
|
525
732
|
}
|
|
526
|
-
|
|
527
|
-
|
|
733
|
+
catch (error) {
|
|
734
|
+
log('error', 'tryEnable webview error:', error);
|
|
528
735
|
}
|
|
529
736
|
};
|
|
530
737
|
// 只监听一次,防止多次触发
|
|
@@ -536,11 +743,16 @@ class WindowsManager {
|
|
|
536
743
|
webContents.on('did-navigate', onDidNavigate);
|
|
537
744
|
webContents.on('did-finish-load', onDidNavigate);
|
|
538
745
|
});
|
|
539
|
-
window.webContents.on('close', () => {
|
|
540
|
-
|
|
541
|
-
})
|
|
746
|
+
// window.webContents.on('close', () => {
|
|
747
|
+
// this.windows.delete(window.id || window._id)
|
|
748
|
+
// })
|
|
542
749
|
window.webContents.on('destroyed', () => {
|
|
543
|
-
this.
|
|
750
|
+
const winId = this._getWebContentsId(window);
|
|
751
|
+
if (winId !== undefined) {
|
|
752
|
+
this.windows.delete(winId);
|
|
753
|
+
}
|
|
754
|
+
// 同步清理名称索引
|
|
755
|
+
this.windowsByName.delete(window._name);
|
|
544
756
|
});
|
|
545
757
|
window.webContents.on('dom-ready', () => {
|
|
546
758
|
if (openDevTools) {
|
|
@@ -554,23 +766,33 @@ class WindowsManager {
|
|
|
554
766
|
}
|
|
555
767
|
// @ts-ignore
|
|
556
768
|
window.on('closed', () => {
|
|
557
|
-
|
|
558
|
-
|
|
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);
|
|
559
776
|
});
|
|
560
777
|
}
|
|
561
778
|
if (type === 'BV') {
|
|
562
779
|
parentWin?.addBrowserView(window);
|
|
563
780
|
log('log', 'create - addBrowserView');
|
|
564
781
|
}
|
|
565
|
-
|
|
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
|
+
}
|
|
566
788
|
log('log', 'create', this.windows.keys());
|
|
567
789
|
// 初始化值
|
|
568
790
|
window.webContents.on('did-finish-load', () => {
|
|
569
|
-
|
|
791
|
+
log('log', 'did-finish-load', this._getWebContentsId(window));
|
|
570
792
|
initWebContentsVal(window, `${preload || ''}`);
|
|
571
793
|
});
|
|
572
794
|
window.webContents.on('did-start-loading', () => {
|
|
573
|
-
|
|
795
|
+
log('log', 'did-start-loading', this._getWebContentsId(window));
|
|
574
796
|
initWebContentsVal(window, `${preload || ''}`);
|
|
575
797
|
});
|
|
576
798
|
if (type === 'BW') {
|
|
@@ -581,7 +803,7 @@ class WindowsManager {
|
|
|
581
803
|
try {
|
|
582
804
|
window.dispatchEvent(new Event('focus'));
|
|
583
805
|
} catch (error) {
|
|
584
|
-
|
|
806
|
+
// 忽略错误,避免影响主流程
|
|
585
807
|
}
|
|
586
808
|
`);
|
|
587
809
|
}
|
|
@@ -595,7 +817,7 @@ class WindowsManager {
|
|
|
595
817
|
try {
|
|
596
818
|
window.dispatchEvent(new Event('blur'));
|
|
597
819
|
} catch (error) {
|
|
598
|
-
|
|
820
|
+
// 忽略错误,避免影响主流程
|
|
599
821
|
}
|
|
600
822
|
`);
|
|
601
823
|
}
|
|
@@ -631,39 +853,58 @@ class WindowsManager {
|
|
|
631
853
|
});
|
|
632
854
|
try {
|
|
633
855
|
const _addBrowserView = window.addBrowserView;
|
|
634
|
-
window.addBrowserView = (view) => {
|
|
856
|
+
window.addBrowserView = (view, isSort = false) => {
|
|
635
857
|
_addBrowserView.call(window, view);
|
|
636
858
|
handleBrowserViewFocus(view);
|
|
859
|
+
// 添加BrowserView后重新排序(如果未禁用自动排序)
|
|
860
|
+
log('log', 'addBrowserView-sort', isSort, window.getBrowserViews());
|
|
861
|
+
if (isSort) {
|
|
862
|
+
this.sortBrowserViewsDebounced(window, view);
|
|
863
|
+
}
|
|
637
864
|
};
|
|
638
865
|
const _removeBrowserView = window.removeBrowserView;
|
|
639
|
-
window.removeBrowserView = (view) => {
|
|
866
|
+
window.removeBrowserView = (view, isSort = false) => {
|
|
640
867
|
_removeBrowserView.call(window, view);
|
|
641
868
|
handleBrowserViewBlur(view);
|
|
869
|
+
// 移除BrowserView后重新排序(如果未禁用自动排序)
|
|
870
|
+
log('log', 'removeBrowserView-sort', isSort);
|
|
871
|
+
if (isSort) {
|
|
872
|
+
this.sortBrowserViewsDebounced(window, view);
|
|
873
|
+
}
|
|
642
874
|
};
|
|
643
875
|
const _setBrowserView = window.setBrowserView;
|
|
644
|
-
window.setBrowserView = (view) => {
|
|
876
|
+
window.setBrowserView = (view, isSort = false) => {
|
|
645
877
|
const views = window.getBrowserViews() || [];
|
|
646
|
-
for (const
|
|
647
|
-
handleBrowserViewBlur(
|
|
878
|
+
for (const existingView of views) {
|
|
879
|
+
handleBrowserViewBlur(existingView);
|
|
648
880
|
}
|
|
649
881
|
_setBrowserView.call(window, view);
|
|
650
882
|
handleBrowserViewFocus(view);
|
|
883
|
+
log('log', 'setBrowserView-sort', isSort);
|
|
884
|
+
if (isSort) {
|
|
885
|
+
this.sortBrowserViewsDebounced(window, view);
|
|
886
|
+
}
|
|
651
887
|
};
|
|
652
888
|
}
|
|
653
889
|
catch (error) {
|
|
654
890
|
log('error', 'focus', error);
|
|
655
891
|
}
|
|
656
892
|
}
|
|
657
|
-
console.log('message xxxx', options.url);
|
|
658
893
|
if (options.url) {
|
|
659
894
|
// @ts-ignore
|
|
660
895
|
window.loadURL ? window.loadURL(options.url) : window.webContents.loadURL(options.url);
|
|
661
|
-
|
|
662
|
-
|
|
896
|
+
if (options.browserWindow?.focusable !== false) {
|
|
897
|
+
window?.focus?.();
|
|
898
|
+
}
|
|
899
|
+
window?.webContents?.focus?.();
|
|
663
900
|
}
|
|
664
901
|
}
|
|
665
902
|
catch (error) {
|
|
666
903
|
log('error', 'create', error);
|
|
904
|
+
throw error;
|
|
905
|
+
}
|
|
906
|
+
if (!window) {
|
|
907
|
+
throw new Error(`Failed to create window: ${name}`);
|
|
667
908
|
}
|
|
668
909
|
return window;
|
|
669
910
|
}
|
|
@@ -672,6 +913,7 @@ class WindowsManager {
|
|
|
672
913
|
const { loadingView, preventOriginNavigate = false, } = createOptions;
|
|
673
914
|
let _loadingView = new electron.BrowserView({
|
|
674
915
|
webPreferences: {
|
|
916
|
+
sandbox: false,
|
|
675
917
|
// session: getCustomSession(),
|
|
676
918
|
contextIsolation: false,
|
|
677
919
|
nodeIntegration: true,
|
|
@@ -684,7 +926,7 @@ class WindowsManager {
|
|
|
684
926
|
}
|
|
685
927
|
const loadLoadingView = () => {
|
|
686
928
|
const [viewWidth, viewHeight] = window.getSize();
|
|
687
|
-
window.
|
|
929
|
+
window.addBrowserView(_loadingView);
|
|
688
930
|
_loadingView.setBounds({
|
|
689
931
|
x: 0,
|
|
690
932
|
y: 0,
|
|
@@ -724,7 +966,7 @@ class WindowsManager {
|
|
|
724
966
|
return;
|
|
725
967
|
}
|
|
726
968
|
if (_loadingView.webContents && !_loadingView.webContents.isDestroyed()) {
|
|
727
|
-
window.
|
|
969
|
+
window.addBrowserView(_loadingView);
|
|
728
970
|
}
|
|
729
971
|
else {
|
|
730
972
|
// if loadingView has been destroyed
|
|
@@ -740,148 +982,231 @@ class WindowsManager {
|
|
|
740
982
|
window.webContents.on('did-stop-loading', onFailure);
|
|
741
983
|
}
|
|
742
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
|
+
}
|
|
743
1041
|
get(idOrName) {
|
|
744
1042
|
log('log', 'get', idOrName);
|
|
745
1043
|
let win;
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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);
|
|
751
1053
|
}
|
|
752
|
-
|
|
753
|
-
|
|
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
|
+
});
|
|
754
1066
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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);
|
|
759
1077
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
win = i;
|
|
763
|
-
}
|
|
1078
|
+
if (win._name) {
|
|
1079
|
+
this.windowsByName.delete(win._name);
|
|
764
1080
|
}
|
|
765
|
-
});
|
|
766
|
-
// @ts-ignore
|
|
767
|
-
if (win && win?.webContents?.isDestroyed && !win?.webContents?.isDestroyed?.()) {
|
|
768
|
-
return win;
|
|
769
1081
|
}
|
|
1082
|
+
// 延迟清理其他已销毁的窗口
|
|
1083
|
+
this.cleanupDestroyedWindowsDebounced();
|
|
770
1084
|
return undefined;
|
|
771
1085
|
}
|
|
772
1086
|
getAll(type) {
|
|
773
1087
|
log('log', 'getAll');
|
|
774
|
-
|
|
775
|
-
const
|
|
1088
|
+
// 先清理已销毁的窗口
|
|
1089
|
+
const toDelete = [];
|
|
776
1090
|
this.windows.forEach((win, key) => {
|
|
777
|
-
if (
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
}
|
|
783
|
-
if (win?._type === 'BV') {
|
|
784
|
-
bvWindows.set(key, win);
|
|
1091
|
+
if (this._isWindowDestroyed(win)) {
|
|
1092
|
+
toDelete.push(key);
|
|
1093
|
+
if (win?._name) {
|
|
1094
|
+
this.windowsByName.delete(win._name);
|
|
1095
|
+
}
|
|
785
1096
|
}
|
|
786
1097
|
});
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
if (type === 'BV') {
|
|
791
|
-
return bvWindows;
|
|
1098
|
+
toDelete.forEach(key => this.windows.delete(key));
|
|
1099
|
+
if (!type) {
|
|
1100
|
+
return this.windows;
|
|
792
1101
|
}
|
|
793
|
-
|
|
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;
|
|
794
1110
|
}
|
|
795
1111
|
close(idOrName) {
|
|
796
1112
|
log('log', 'close', idOrName);
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
});
|
|
810
|
-
// @ts-ignore
|
|
811
|
-
win && this.windows.delete(win?.id);
|
|
812
|
-
// @ts-ignore
|
|
813
|
-
if (win) {
|
|
814
|
-
// @ts-ignore
|
|
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 {
|
|
815
1125
|
if (win._type === 'BV') {
|
|
1126
|
+
// 从所有 BW 窗口中移除该 BV
|
|
816
1127
|
this.windows.forEach(i => {
|
|
817
|
-
if (i?._type === 'BW') {
|
|
1128
|
+
if (i?._type === 'BW' && !this._isWindowDestroyed(i)) {
|
|
818
1129
|
const _win = i;
|
|
819
|
-
|
|
1130
|
+
try {
|
|
1131
|
+
_win.removeBrowserView(win);
|
|
1132
|
+
}
|
|
1133
|
+
catch (error) {
|
|
1134
|
+
// 忽略错误,可能已经移除
|
|
1135
|
+
}
|
|
820
1136
|
}
|
|
821
1137
|
});
|
|
822
1138
|
}
|
|
823
1139
|
// @ts-ignore
|
|
824
|
-
win
|
|
1140
|
+
win.webContents?.destroy?.();
|
|
825
1141
|
// @ts-ignore
|
|
826
|
-
win
|
|
1142
|
+
win.close?.();
|
|
827
1143
|
// @ts-ignore
|
|
828
|
-
win
|
|
1144
|
+
win.destroy?.();
|
|
1145
|
+
}
|
|
1146
|
+
catch (error) {
|
|
1147
|
+
log('error', 'close window error:', error);
|
|
829
1148
|
}
|
|
830
1149
|
return true;
|
|
831
1150
|
}
|
|
832
1151
|
closeAll() {
|
|
833
1152
|
log('log', 'closeAll');
|
|
834
|
-
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) => {
|
|
835
1173
|
try {
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
if (win._type === 'BV') {
|
|
839
|
-
this.windows.forEach(i => {
|
|
840
|
-
if (i?._type === 'BW') {
|
|
841
|
-
const _win = i;
|
|
842
|
-
_win.removeBrowserView(win);
|
|
843
|
-
}
|
|
844
|
-
});
|
|
1174
|
+
if (this._isWindowDestroyed(win)) {
|
|
1175
|
+
return;
|
|
845
1176
|
}
|
|
846
1177
|
// @ts-ignore
|
|
847
|
-
win
|
|
1178
|
+
win.webContents?.destroy?.();
|
|
848
1179
|
// @ts-ignore
|
|
849
|
-
win
|
|
1180
|
+
win.close?.();
|
|
850
1181
|
// @ts-ignore
|
|
851
|
-
win
|
|
1182
|
+
win.destroy?.();
|
|
852
1183
|
}
|
|
853
1184
|
catch (error) {
|
|
1185
|
+
log('error', 'closeAll error:', error);
|
|
854
1186
|
}
|
|
855
1187
|
});
|
|
856
1188
|
}
|
|
857
1189
|
rename(idOrName, newName) {
|
|
858
1190
|
log('log', 'rename', idOrName, newName);
|
|
859
|
-
|
|
860
|
-
// 先查找目标窗口
|
|
861
|
-
if (typeof idOrName === 'number') {
|
|
862
|
-
win = this.get(idOrName);
|
|
863
|
-
}
|
|
864
|
-
else if (typeof idOrName === 'string') {
|
|
865
|
-
this.windows.forEach(i => {
|
|
866
|
-
if (i?._name === idOrName) {
|
|
867
|
-
win = i;
|
|
868
|
-
}
|
|
869
|
-
});
|
|
870
|
-
}
|
|
1191
|
+
const win = this.get(idOrName);
|
|
871
1192
|
if (!win) {
|
|
872
|
-
// 没有找到目标窗口
|
|
873
1193
|
return undefined;
|
|
874
1194
|
}
|
|
875
|
-
//
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
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;
|
|
880
1202
|
}
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
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);
|
|
885
1210
|
}
|
|
886
1211
|
// 修改名字并同步 webContents
|
|
887
1212
|
win._name = newName;
|
|
@@ -890,12 +1215,7 @@ class WindowsManager {
|
|
|
890
1215
|
}
|
|
891
1216
|
reInitUrl(idOrName, url) {
|
|
892
1217
|
log('log', 'reInitUrl', idOrName, url);
|
|
893
|
-
|
|
894
|
-
this.windows.forEach(i => {
|
|
895
|
-
if (i?._name === idOrName) {
|
|
896
|
-
win = i;
|
|
897
|
-
}
|
|
898
|
-
});
|
|
1218
|
+
const win = this.get(idOrName);
|
|
899
1219
|
if (!win) {
|
|
900
1220
|
return undefined;
|
|
901
1221
|
}
|
|
@@ -924,6 +1244,86 @@ class WindowsManager {
|
|
|
924
1244
|
if (typeof browserWindowOptions.alwaysOnTop === 'boolean') {
|
|
925
1245
|
win.setAlwaysOnTop(browserWindowOptions.alwaysOnTop);
|
|
926
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
|
+
}
|
|
927
1327
|
// 可继续扩展其他动态属性
|
|
928
1328
|
}
|
|
929
1329
|
_applyBrowserViewOptions(view, options) {
|
|
@@ -939,35 +1339,175 @@ class WindowsManager {
|
|
|
939
1339
|
if (typeof browserWindowOptions.width === 'number' && typeof browserWindowOptions.height === 'number') {
|
|
940
1340
|
view.setBounds({ x: 0, y: 0, width: browserWindowOptions.width, height: browserWindowOptions.height });
|
|
941
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
|
+
}
|
|
942
1356
|
// 可继续扩展其他动态属性
|
|
943
1357
|
}
|
|
944
1358
|
// 生成一个bv 做为预加载资源窗口,加载完成后销毁
|
|
945
1359
|
async createPreloadWebContents(url) {
|
|
946
|
-
|
|
947
|
-
|
|
1360
|
+
let bv = null;
|
|
1361
|
+
try {
|
|
1362
|
+
bv = await this.create({
|
|
948
1363
|
type: 'BV',
|
|
949
1364
|
url,
|
|
950
1365
|
name: `preload-web-contents-${md5(url)}`,
|
|
951
1366
|
extraData: `${url}`
|
|
952
1367
|
});
|
|
953
|
-
|
|
954
|
-
this.
|
|
955
|
-
|
|
956
|
-
|
|
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);
|
|
957
1389
|
});
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
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;
|
|
962
1452
|
});
|
|
963
|
-
|
|
964
|
-
|
|
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
|
+
}
|
|
965
1505
|
}
|
|
966
1506
|
}
|
|
967
1507
|
// @ts-ignore
|
|
968
1508
|
global['__ELECTRON_WINDOWS_MANAGER__'] = undefined;
|
|
969
1509
|
exports.isInitialized = false;
|
|
970
|
-
const initialize = (preload, loadingViewUrl, errorViewUrl,
|
|
1510
|
+
const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsConfig, webviewDomainWhiteList) => {
|
|
971
1511
|
// @ts-ignore
|
|
972
1512
|
if (exports.isInitialized && global['__ELECTRON_WINDOWS_MANAGER__']) {
|
|
973
1513
|
// @ts-ignore
|
|
@@ -975,7 +1515,7 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
|
|
|
975
1515
|
}
|
|
976
1516
|
exports.isInitialized = true;
|
|
977
1517
|
// @ts-ignore
|
|
978
|
-
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);
|
|
979
1519
|
eIpc.mainIPC.handleRenderer('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', async (data) => {
|
|
980
1520
|
if (data?.type === 'create') {
|
|
981
1521
|
const opt = data;
|
|
@@ -991,46 +1531,71 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
|
|
|
991
1531
|
findWin.webContents.reload();
|
|
992
1532
|
}, 100);
|
|
993
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);
|
|
994
1551
|
return {
|
|
995
|
-
winId: Number(`${
|
|
1552
|
+
winId: Number(`${winId || -1}`),
|
|
996
1553
|
winName: `${findWin?._name || ''}`,
|
|
997
1554
|
winType: `${findWin?._type || ''}`,
|
|
998
1555
|
winExtraData: `${findWin?._extraData || ''}`,
|
|
999
1556
|
winInitUrl: `${findWin?._initUrl || ''}`,
|
|
1557
|
+
winZIndex: `${findWin._zIndex || 0}`,
|
|
1000
1558
|
};
|
|
1001
1559
|
}
|
|
1002
1560
|
const res = await wm.create(opt.data);
|
|
1561
|
+
const winId = wm._getWebContentsId(res);
|
|
1003
1562
|
return {
|
|
1004
|
-
winId: Number(`${
|
|
1563
|
+
winId: Number(`${winId || -1}`),
|
|
1005
1564
|
winName: `${res?._name || ''}`,
|
|
1006
1565
|
winType: `${res?._type || ''}`,
|
|
1007
1566
|
winExtraData: `${res?._extraData || ''}`,
|
|
1008
1567
|
winInitUrl: `${res?._initUrl || ''}`,
|
|
1568
|
+
winZIndex: `${res?._zIndex || 0}`,
|
|
1009
1569
|
};
|
|
1010
1570
|
}
|
|
1011
1571
|
if (data?.type === 'get') {
|
|
1012
1572
|
const opt = data;
|
|
1013
1573
|
const res = wm.get(opt?.data);
|
|
1574
|
+
const winId = wm._getWebContentsId(res);
|
|
1014
1575
|
return {
|
|
1015
|
-
winId: Number(`${
|
|
1576
|
+
winId: Number(`${winId || -1}`),
|
|
1016
1577
|
winName: `${res?._name || ''}`,
|
|
1017
1578
|
winType: `${res?._type || ''}`,
|
|
1018
1579
|
winExtraData: `${res?._extraData || ''}`,
|
|
1019
1580
|
winInitUrl: `${res?._initUrl || ''}`,
|
|
1581
|
+
winZIndex: `${res?._zIndex || 0}`,
|
|
1020
1582
|
};
|
|
1021
1583
|
}
|
|
1022
1584
|
if (data?.type === 'getAll') {
|
|
1023
1585
|
const res = wm.getAll();
|
|
1024
1586
|
const obj = {};
|
|
1025
1587
|
res.forEach(i => {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
winId
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
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
|
+
}
|
|
1034
1599
|
});
|
|
1035
1600
|
return obj;
|
|
1036
1601
|
}
|
|
@@ -1047,12 +1612,14 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
|
|
|
1047
1612
|
const opt = data;
|
|
1048
1613
|
const res = wm.rename(opt.data.idOrName, opt.data.newName);
|
|
1049
1614
|
if (res) {
|
|
1615
|
+
const winId = wm._getWebContentsId(res);
|
|
1050
1616
|
return {
|
|
1051
|
-
winId: Number(`${
|
|
1617
|
+
winId: Number(`${winId || -1}`),
|
|
1052
1618
|
winName: `${res?._name || ''}`,
|
|
1053
1619
|
winType: `${res?._type || ''}`,
|
|
1054
1620
|
winExtraData: `${res?._extraData || ''}`,
|
|
1055
1621
|
winInitUrl: `${res?._initUrl || ''}`,
|
|
1622
|
+
winZIndex: `${res?._zIndex || 0}`,
|
|
1056
1623
|
};
|
|
1057
1624
|
}
|
|
1058
1625
|
return undefined;
|
|
@@ -1061,40 +1628,23 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
|
|
|
1061
1628
|
const opt = data;
|
|
1062
1629
|
const res = wm.reInitUrl(opt.data.idOrName, opt.data.url);
|
|
1063
1630
|
if (res) {
|
|
1631
|
+
const winId = wm._getWebContentsId(res);
|
|
1064
1632
|
return {
|
|
1065
|
-
winId: Number(`${
|
|
1633
|
+
winId: Number(`${winId || -1}`),
|
|
1066
1634
|
winName: `${res?._name || ''}`,
|
|
1067
1635
|
winType: `${res?._type || ''}`,
|
|
1068
1636
|
winExtraData: `${res?._extraData || ''}`,
|
|
1069
1637
|
winInitUrl: `${res?._initUrl || ''}`,
|
|
1638
|
+
winZIndex: `${res?._zIndex || 0}`,
|
|
1070
1639
|
};
|
|
1071
1640
|
}
|
|
1072
1641
|
return undefined;
|
|
1073
1642
|
}
|
|
1074
|
-
if (data?.type === '
|
|
1643
|
+
if (data?.type === 'getWindowForWebContentsId') {
|
|
1075
1644
|
const opt = data;
|
|
1076
|
-
const
|
|
1077
|
-
if (
|
|
1078
|
-
|
|
1079
|
-
if (!win) {
|
|
1080
|
-
// 获取所有的 BrowserWindows
|
|
1081
|
-
let allWindows = electron.BrowserWindow.getAllWindows();
|
|
1082
|
-
// 遍历所有窗口,检查每个窗口的 BrowserView
|
|
1083
|
-
for (let _win of allWindows) {
|
|
1084
|
-
let views = _win.getBrowserViews();
|
|
1085
|
-
// 遍历窗口的所有 BrowserView
|
|
1086
|
-
for (let view of views) {
|
|
1087
|
-
if (view.webContents === targetWebContents) {
|
|
1088
|
-
win = _win;
|
|
1089
|
-
break;
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
if (win)
|
|
1093
|
-
break;
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
// @ts-ignore
|
|
1097
|
-
return win?.id || win?._id;
|
|
1645
|
+
const res = await wm.getWindowForWebContentsId(opt.data);
|
|
1646
|
+
if (res) {
|
|
1647
|
+
return wm._getWebContentsId(res);
|
|
1098
1648
|
}
|
|
1099
1649
|
return undefined;
|
|
1100
1650
|
}
|
|
@@ -1135,15 +1685,20 @@ const initialize = (preload, loadingViewUrl, errorViewUrl, preloadWebContentsUrl
|
|
|
1135
1685
|
return undefined;
|
|
1136
1686
|
}
|
|
1137
1687
|
// 是否开启预加载窗口
|
|
1138
|
-
if (data?.type === '
|
|
1688
|
+
if (data?.type === 'setPreloadWebContentsConfig') {
|
|
1139
1689
|
const opt = data;
|
|
1140
|
-
wm.
|
|
1690
|
+
wm.setPreloadWebContentsConfig(opt.data);
|
|
1141
1691
|
}
|
|
1142
1692
|
if (data?.type === 'createPreloadWebContents') {
|
|
1143
1693
|
const opt = data;
|
|
1144
1694
|
const res = await wm.createPreloadWebContents(opt.data);
|
|
1145
1695
|
return res;
|
|
1146
1696
|
}
|
|
1697
|
+
if (data?.type === 'sortBrowserViews') {
|
|
1698
|
+
const opt = data;
|
|
1699
|
+
wm.sortBrowserViews(opt.data);
|
|
1700
|
+
return true;
|
|
1701
|
+
}
|
|
1147
1702
|
return undefined;
|
|
1148
1703
|
});
|
|
1149
1704
|
return wm;
|