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