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