@raydiant/playlist-window-manager 0.0.0-electron-upgrade-1
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/.eslintrc +6 -0
- package/README.md +16 -0
- package/babel.config.js +13 -0
- package/build/BrowserViewRenderer.d.ts +19 -0
- package/build/BrowserViewRenderer.js +213 -0
- package/build/BrowserWindowRenderer.d.ts +47 -0
- package/build/BrowserWindowRenderer.js +524 -0
- package/build/IFrameRenderer.d.ts +20 -0
- package/build/IFrameRenderer.js +226 -0
- package/build/PlaylistWindowManager.d.ts +111 -0
- package/build/PlaylistWindowManager.js +1056 -0
- package/build/PlaylistWindowManagerState.d.ts +28 -0
- package/build/PlaylistWindowManagerState.js +60 -0
- package/build/PresentationWindow.d.ts +149 -0
- package/build/PresentationWindow.js +1235 -0
- package/build/PresentationWindow.test.d.ts +1 -0
- package/build/PresentationWindow.test.js +340 -0
- package/build/WindowLoadError.d.ts +3 -0
- package/build/WindowLoadError.js +29 -0
- package/build/WindowTimeoutError.d.ts +3 -0
- package/build/WindowTimeoutError.js +29 -0
- package/build/WindowUnresponsiveError.d.ts +3 -0
- package/build/WindowUnresponsiveError.js +29 -0
- package/build/embeddedApps.d.ts +2 -0
- package/build/embeddedApps.js +59 -0
- package/build/embeddedApps.test.d.ts +1 -0
- package/build/embeddedApps.test.js +123 -0
- package/build/index.d.ts +10 -0
- package/build/index.js +30 -0
- package/build/preload.d.ts +1 -0
- package/build/preload.js +20 -0
- package/build/testUtils.d.ts +43 -0
- package/build/testUtils.js +209 -0
- package/build/types.d.ts +162 -0
- package/build/types.js +2 -0
- package/jest.config.js +6 -0
- package/package.json +30 -0
- package/src/BrowserViewRenderer.ts +180 -0
- package/src/BrowserWindowRenderer.ts +595 -0
- package/src/IFrameRenderer.ts +251 -0
- package/src/PlaylistWindowManager.ts +1094 -0
- package/src/PlaylistWindowManagerState.ts +70 -0
- package/src/PresentationWindow.test.ts +333 -0
- package/src/PresentationWindow.ts +1434 -0
- package/src/WindowLoadError.ts +8 -0
- package/src/WindowTimeoutError.ts +8 -0
- package/src/WindowUnresponsiveError.ts +8 -0
- package/src/embeddedApps.test.ts +231 -0
- package/src/embeddedApps.ts +74 -0
- package/src/index.ts +13 -0
- package/src/preload.ts +27 -0
- package/src/testUtils.ts +160 -0
- package/src/types.ts +199 -0
- package/tasks/npm-publish.sh +13 -0
- package/tsconfig.json +7 -0
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import {
|
|
3
|
+
BrowserWindow,
|
|
4
|
+
BrowserView,
|
|
5
|
+
ipcMain,
|
|
6
|
+
IpcMainEvent,
|
|
7
|
+
Event,
|
|
8
|
+
BrowserWindowConstructorOptions,
|
|
9
|
+
BrowserViewConstructorOptions,
|
|
10
|
+
RenderProcessGoneDetails,
|
|
11
|
+
} from 'electron';
|
|
12
|
+
import {
|
|
13
|
+
PresentationWindowRenderer,
|
|
14
|
+
CreateWindowOptions,
|
|
15
|
+
WindowBounds,
|
|
16
|
+
Logger,
|
|
17
|
+
} from './types';
|
|
18
|
+
import WindowUnresponsiveError from './WindowUnresponsiveError';
|
|
19
|
+
|
|
20
|
+
interface ProxySettings {
|
|
21
|
+
proxyRules: string;
|
|
22
|
+
proxyBypassRules: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface BrowserWindowRendererWindow {
|
|
26
|
+
parentWindowId?: string;
|
|
27
|
+
view: BrowserWindow | BrowserView;
|
|
28
|
+
bounds: WindowBounds;
|
|
29
|
+
receiveMessageCallback: (
|
|
30
|
+
event: IpcMainEvent,
|
|
31
|
+
type: string,
|
|
32
|
+
payload?: any
|
|
33
|
+
) => void;
|
|
34
|
+
unresponsiveCallback: () => void;
|
|
35
|
+
renderProcessGoneCallback: (
|
|
36
|
+
event: Event,
|
|
37
|
+
details: RenderProcessGoneDetails
|
|
38
|
+
) => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface BrowserWindowRendererOptions {
|
|
42
|
+
bounds: WindowBounds;
|
|
43
|
+
frame: boolean;
|
|
44
|
+
hasShadow: boolean;
|
|
45
|
+
renderChildWindowsAsBrowserViews: boolean;
|
|
46
|
+
useWindowPool: boolean;
|
|
47
|
+
logger: Logger;
|
|
48
|
+
proxySettings?: ProxySettings;
|
|
49
|
+
windowCrashHandler?: () => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default class BrowserWindowRenderer
|
|
53
|
+
implements PresentationWindowRenderer
|
|
54
|
+
{
|
|
55
|
+
private windows: {
|
|
56
|
+
[windowId: string]: BrowserWindowRendererWindow;
|
|
57
|
+
} = {};
|
|
58
|
+
private windowCrashHandler: (reason: string) => void = (_: string) => {};
|
|
59
|
+
|
|
60
|
+
private browserWindowPool = new WindowPool<BrowserWindow>();
|
|
61
|
+
private browserViewPool = new WindowPool<BrowserView>();
|
|
62
|
+
|
|
63
|
+
// Create a hidden window to preload BrowserViews.
|
|
64
|
+
private offscreenWindow = new BrowserWindow({
|
|
65
|
+
show: false,
|
|
66
|
+
frame: this.opts.frame,
|
|
67
|
+
hasShadow: this.opts.hasShadow,
|
|
68
|
+
webPreferences: {
|
|
69
|
+
preload: path.join(__dirname, 'preload.js'),
|
|
70
|
+
contextIsolation: false,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
private proxySettings: ProxySettings | null = null;
|
|
75
|
+
|
|
76
|
+
private logger: Logger;
|
|
77
|
+
|
|
78
|
+
constructor(private opts: BrowserWindowRendererOptions) {
|
|
79
|
+
this.offscreenWindow.setBounds(opts.bounds);
|
|
80
|
+
if (opts.proxySettings) {
|
|
81
|
+
this.proxySettings = opts.proxySettings;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.logger = opts.logger;
|
|
85
|
+
|
|
86
|
+
if (opts.windowCrashHandler) {
|
|
87
|
+
this.windowCrashHandler = opts.windowCrashHandler;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public async createWindow({
|
|
92
|
+
windowId,
|
|
93
|
+
parentWindowId,
|
|
94
|
+
bounds = this.opts.bounds,
|
|
95
|
+
backgroundColor,
|
|
96
|
+
receiveMessageCallback,
|
|
97
|
+
errorCallback,
|
|
98
|
+
}: CreateWindowOptions) {
|
|
99
|
+
const window: BrowserWindowRendererWindow = {
|
|
100
|
+
parentWindowId,
|
|
101
|
+
// If we are creating a child window and renderChildWindowsAsBrowserViews is true,
|
|
102
|
+
// render all child windows as browser views.
|
|
103
|
+
view:
|
|
104
|
+
parentWindowId && this.opts.renderChildWindowsAsBrowserViews
|
|
105
|
+
? this.createOrGetPooledBrowserView(windowId, {
|
|
106
|
+
webPreferences: {
|
|
107
|
+
preload: path.join(__dirname, 'preload.js'),
|
|
108
|
+
contextIsolation: false,
|
|
109
|
+
},
|
|
110
|
+
})
|
|
111
|
+
: this.createOrGetPooledBrowserWindow(windowId, {
|
|
112
|
+
show: false,
|
|
113
|
+
frame: this.opts.frame,
|
|
114
|
+
// NOTE: useWindowPool: true does not work with renderChildWindowsAsBrowserViews: false
|
|
115
|
+
// because the parent can't be changed after creating the BrowserWindow.
|
|
116
|
+
parent: parentWindowId
|
|
117
|
+
? this.getParentBrowserView(parentWindowId)
|
|
118
|
+
: undefined,
|
|
119
|
+
transparent: backgroundColor === 'transparent',
|
|
120
|
+
hasShadow: this.opts.hasShadow,
|
|
121
|
+
webPreferences: {
|
|
122
|
+
preload: path.join(__dirname, 'preload.js'),
|
|
123
|
+
contextIsolation: false,
|
|
124
|
+
},
|
|
125
|
+
}),
|
|
126
|
+
// If we are creating a child window and renderChildWindowsAsBrowserViews is false that means
|
|
127
|
+
// we are rendering child windows with BrowserWindow. BrowserWindows are positioned absolutely so
|
|
128
|
+
// to place them in the correct spot relative to their parent we need to add the main window's x / y.
|
|
129
|
+
// BrowserViews are relatively positioned so we don't need to do that.
|
|
130
|
+
bounds:
|
|
131
|
+
parentWindowId && !this.opts.renderChildWindowsAsBrowserViews
|
|
132
|
+
? {
|
|
133
|
+
...bounds,
|
|
134
|
+
x: bounds.x + this.opts.bounds.x,
|
|
135
|
+
y: bounds.y + this.opts.bounds.y,
|
|
136
|
+
}
|
|
137
|
+
: bounds,
|
|
138
|
+
|
|
139
|
+
receiveMessageCallback: (
|
|
140
|
+
_: IpcMainEvent,
|
|
141
|
+
type: string,
|
|
142
|
+
payload?: any
|
|
143
|
+
) => {
|
|
144
|
+
if (receiveMessageCallback) {
|
|
145
|
+
receiveMessageCallback(type, payload);
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
unresponsiveCallback: () => {
|
|
150
|
+
this.logger.error(`Window received 'unresponsive' event`, { windowId });
|
|
151
|
+
this.windowCrashHandler('unresponsive');
|
|
152
|
+
errorCallback(
|
|
153
|
+
new WindowUnresponsiveError(`Window '${windowId}' is unresponsive`)
|
|
154
|
+
);
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
renderProcessGoneCallback: (
|
|
158
|
+
_: Event,
|
|
159
|
+
details: RenderProcessGoneDetails
|
|
160
|
+
) => {
|
|
161
|
+
this.logger.error(
|
|
162
|
+
`Window received 'render-process-gone' event. Reason: ${details.reason}`,
|
|
163
|
+
{ windowId }
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// TODO: Handle different reasons for render-process-gone?
|
|
167
|
+
// https://www.electronjs.org/docs/api/web-contents#event-render-process-gone
|
|
168
|
+
|
|
169
|
+
this.windowCrashHandler('render-process-gone');
|
|
170
|
+
|
|
171
|
+
errorCallback(
|
|
172
|
+
new WindowUnresponsiveError(
|
|
173
|
+
`Window '${windowId}' render process gone`
|
|
174
|
+
)
|
|
175
|
+
);
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// Attached crash handlers to skip presentation on error.
|
|
180
|
+
window.view.webContents.on(
|
|
181
|
+
'render-process-gone',
|
|
182
|
+
window.renderProcessGoneCallback
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
window.view.webContents.on('unresponsive', window.unresponsiveCallback);
|
|
186
|
+
|
|
187
|
+
if (window.view instanceof BrowserView) {
|
|
188
|
+
this.moveWindowOffscreen(window);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
this.windows[windowId] = window;
|
|
192
|
+
// Listen for messages from window.
|
|
193
|
+
ipcMain.on(windowId, window.receiveMessageCallback);
|
|
194
|
+
|
|
195
|
+
if (backgroundColor) {
|
|
196
|
+
// To make a transparent window you need to set it's value to #00000000
|
|
197
|
+
// Electron issue: https://github.com/electron/electron/issues/20357
|
|
198
|
+
window.view.setBackgroundColor(
|
|
199
|
+
backgroundColor === 'transparent' ? '#00000000' : backgroundColor
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
window.view.setBounds(window.bounds);
|
|
204
|
+
|
|
205
|
+
if (this.proxySettings) {
|
|
206
|
+
await window.view.webContents.session.setProxy(this.proxySettings);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (this.opts.useWindowPool) {
|
|
210
|
+
this.logPoolStats();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
public async loadWindow(windowId: string, url: string) {
|
|
215
|
+
const window = this.getWindow(windowId);
|
|
216
|
+
if (!window) {
|
|
217
|
+
throw new Error(`Failed to load window, missing window id '${windowId}'`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
await window.view.webContents.loadURL(url);
|
|
221
|
+
// window.view.webContents.openDevTools();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
public updateWindowBounds(windowId: string, bounds: WindowBounds) {
|
|
225
|
+
const window = this.getWindow(windowId);
|
|
226
|
+
if (!window) {
|
|
227
|
+
throw new Error(
|
|
228
|
+
`Failed to update window bounds, missing window id '${windowId}'`
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
window.bounds = bounds;
|
|
232
|
+
|
|
233
|
+
if (window.view instanceof BrowserWindow && window.parentWindowId) {
|
|
234
|
+
// Child BrowserWindows are absolutely positioned so we need to add the main windows
|
|
235
|
+
// x/y to when updating the child window's bounds.
|
|
236
|
+
window.view.setBounds({
|
|
237
|
+
...window.bounds,
|
|
238
|
+
x: window.bounds.x + this.opts.bounds.x,
|
|
239
|
+
y: window.bounds.y + this.opts.bounds.y,
|
|
240
|
+
});
|
|
241
|
+
} else {
|
|
242
|
+
window.view.setBounds(window.bounds);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
public showWindow(windowId: string, isFocused: boolean) {
|
|
247
|
+
const window = this.getWindow(windowId);
|
|
248
|
+
if (!window) {
|
|
249
|
+
throw new Error(`Failed to show window, missing window id '${windowId}'`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (window.view instanceof BrowserWindow) {
|
|
253
|
+
if (isFocused) {
|
|
254
|
+
window.view.show();
|
|
255
|
+
} else {
|
|
256
|
+
window.view.showInactive();
|
|
257
|
+
}
|
|
258
|
+
} else if (window.view instanceof BrowserView && window.parentWindowId) {
|
|
259
|
+
this.addWindowToParent(window.parentWindowId, window);
|
|
260
|
+
if (isFocused) {
|
|
261
|
+
// The setTimeout is required to set focus after the BrowserView has
|
|
262
|
+
// been added to the parent BrowserWindow.
|
|
263
|
+
setTimeout(() => window.view.webContents.focus(), 0);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
public hideWindow(windowId: string) {
|
|
269
|
+
const window = this.getWindow(windowId);
|
|
270
|
+
if (!window) {
|
|
271
|
+
throw new Error(`Failed to hide window, missing window id '${windowId}'`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (window.view instanceof BrowserWindow) {
|
|
275
|
+
window.view.hide();
|
|
276
|
+
} else if (window.view instanceof BrowserView) {
|
|
277
|
+
this.moveWindowOffscreen(window);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
public async removeWindow(windowId: string) {
|
|
282
|
+
const window = this.getWindow(windowId);
|
|
283
|
+
if (!window) {
|
|
284
|
+
throw new Error(
|
|
285
|
+
`Failed to remove window, missing window id '${windowId}'`
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Remove crash handlers
|
|
290
|
+
window.view.webContents.removeListener(
|
|
291
|
+
'render-process-gone',
|
|
292
|
+
window.renderProcessGoneCallback
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
window.view.webContents.removeListener(
|
|
296
|
+
'unresponsive',
|
|
297
|
+
window.unresponsiveCallback
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
// Remove message handler
|
|
301
|
+
ipcMain.removeListener(windowId, window.receiveMessageCallback);
|
|
302
|
+
|
|
303
|
+
if (window.view instanceof BrowserView) {
|
|
304
|
+
if (window.parentWindowId) {
|
|
305
|
+
this.removeWindowFromParent(window.parentWindowId, window);
|
|
306
|
+
}
|
|
307
|
+
await this.destroyOrUnloadPooledBrowserView(windowId, window.view);
|
|
308
|
+
} else if (window.view instanceof BrowserWindow) {
|
|
309
|
+
await this.destroyOrUnloadPooledBrowserWindow(windowId, window.view);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
delete this.windows[windowId];
|
|
313
|
+
|
|
314
|
+
if (this.opts.useWindowPool) {
|
|
315
|
+
this.logPoolStats();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
public sendMessageString(windowId: string, message: string) {
|
|
320
|
+
const window = this.getWindow(windowId);
|
|
321
|
+
if (!window) {
|
|
322
|
+
throw new Error(
|
|
323
|
+
`Failed to send message to window, missing window id '${windowId}'`
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
window.view.webContents.send('electron-main', message);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
public sendMessageObject(windowId: string, type: string, payload?: any) {
|
|
331
|
+
const window = this.getWindow(windowId);
|
|
332
|
+
if (!window) {
|
|
333
|
+
throw new Error(
|
|
334
|
+
`Failed to send message to window, missing window id '${windowId}'`
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
window.view.webContents.send('electron-main', { type, payload });
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
public muteWindow(windowId: string) {
|
|
342
|
+
const window = this.getWindow(windowId);
|
|
343
|
+
if (!window) {
|
|
344
|
+
throw new Error(`Failed to mute window, missing window id '${windowId}'`);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
window.view.webContents.setAudioMuted(true);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
public unMuteWindow(windowId: string) {
|
|
351
|
+
const window = this.getWindow(windowId);
|
|
352
|
+
if (!window) {
|
|
353
|
+
throw new Error(
|
|
354
|
+
`Failed to unmute window, missing window id '${windowId}'`
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
window.view.webContents.setAudioMuted(false);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
private getWindow(windowId: string): BrowserWindowRendererWindow | null {
|
|
362
|
+
return this.windows[windowId];
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private moveWindowOffscreen(window: BrowserWindowRendererWindow) {
|
|
366
|
+
if (!(window.view instanceof BrowserView)) {
|
|
367
|
+
throw new Error(
|
|
368
|
+
`Failed move window offscreen, window is not a BrowserView`
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (window.parentWindowId) {
|
|
373
|
+
this.removeWindowFromParent(window.parentWindowId, window);
|
|
374
|
+
}
|
|
375
|
+
this.offscreenWindow.addBrowserView(window.view);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
private addWindowToParent(
|
|
379
|
+
parentWindowId: string,
|
|
380
|
+
window: BrowserWindowRendererWindow
|
|
381
|
+
) {
|
|
382
|
+
const parentWindow = this.getWindow(parentWindowId);
|
|
383
|
+
if (!parentWindow) {
|
|
384
|
+
throw new Error(
|
|
385
|
+
`Failed to add window to parent, missing parent window id '${parentWindowId}'`
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (parentWindow.view instanceof BrowserWindow) {
|
|
390
|
+
if (window.view instanceof BrowserView) {
|
|
391
|
+
this.offscreenWindow.removeBrowserView(window.view);
|
|
392
|
+
parentWindow.view.addBrowserView(window.view);
|
|
393
|
+
} else {
|
|
394
|
+
throw new Error(
|
|
395
|
+
`Failed add window to parent window id '${parentWindowId}', child window is not a BrowserView`
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
} else {
|
|
399
|
+
throw new Error(
|
|
400
|
+
`Failed add window to parent window id '${parentWindowId}', parent window is not a BrowserWindow`
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
private removeWindowFromParent(
|
|
406
|
+
parentWindowId: string,
|
|
407
|
+
window: BrowserWindowRendererWindow
|
|
408
|
+
) {
|
|
409
|
+
const parentWindow = this.getWindow(parentWindowId);
|
|
410
|
+
if (!parentWindow) {
|
|
411
|
+
throw new Error(
|
|
412
|
+
`Failed to remove window from parent, missing parent window id '${parentWindowId}'`
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (parentWindow.view instanceof BrowserWindow) {
|
|
417
|
+
if (window.view instanceof BrowserView) {
|
|
418
|
+
parentWindow.view.removeBrowserView(window.view);
|
|
419
|
+
} else {
|
|
420
|
+
throw new Error(
|
|
421
|
+
`Failed remove window from parent window id '${parentWindowId}', child window is not a BrowserView`
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
} else {
|
|
425
|
+
throw new Error(
|
|
426
|
+
`Failed remove window from parent window id '${parentWindowId}', parent window is not a BrowserWindow`
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private getParentBrowserView(parentWindowId: string): BrowserWindow {
|
|
432
|
+
const parentWindow = this.getWindow(parentWindowId);
|
|
433
|
+
if (!parentWindow) {
|
|
434
|
+
throw new Error(
|
|
435
|
+
`Failed to get parent BrowserView, missing parent window id '${parentWindowId}'`
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (!(parentWindow.view instanceof BrowserWindow)) {
|
|
440
|
+
throw new Error(
|
|
441
|
+
`Failed to get parent BrowserView, window is not a BrowserView`
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return parentWindow.view;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
private createOrGetPooledBrowserWindow(
|
|
449
|
+
windowId: string,
|
|
450
|
+
options: BrowserWindowConstructorOptions
|
|
451
|
+
): BrowserWindow {
|
|
452
|
+
let browserWindow: BrowserWindow | null = null;
|
|
453
|
+
|
|
454
|
+
if (this.opts.useWindowPool) {
|
|
455
|
+
browserWindow = this.browserWindowPool.get();
|
|
456
|
+
if (browserWindow) {
|
|
457
|
+
this.logger.info(`Found free browser window from pool`, { windowId });
|
|
458
|
+
} else {
|
|
459
|
+
this.logger.info(`No free browser windows, adding new window to pool`, {
|
|
460
|
+
windowId,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (!browserWindow) {
|
|
466
|
+
browserWindow = new BrowserWindow(options);
|
|
467
|
+
if (this.opts.useWindowPool) {
|
|
468
|
+
this.browserWindowPool.add(browserWindow);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return browserWindow;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
private async destroyOrUnloadPooledBrowserWindow(
|
|
476
|
+
windowId: string,
|
|
477
|
+
browserWindow: BrowserWindow
|
|
478
|
+
) {
|
|
479
|
+
if (this.opts.useWindowPool) {
|
|
480
|
+
this.hideWindow(windowId);
|
|
481
|
+
await browserWindow.webContents.loadURL('about:blank');
|
|
482
|
+
this.browserWindowPool.release(browserWindow);
|
|
483
|
+
} else {
|
|
484
|
+
if (browserWindow.isDestroyed()) {
|
|
485
|
+
this.logger.warn(
|
|
486
|
+
`Attempted to destroy BrowserWindow but it's already destroyed`,
|
|
487
|
+
{ windowId }
|
|
488
|
+
);
|
|
489
|
+
} else {
|
|
490
|
+
// https://github.com/electron/electron/issues/26929#issuecomment-763837953
|
|
491
|
+
setImmediate(() => {
|
|
492
|
+
browserWindow.destroy();
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
private createOrGetPooledBrowserView(
|
|
499
|
+
windowId: string,
|
|
500
|
+
options: BrowserViewConstructorOptions
|
|
501
|
+
): BrowserView {
|
|
502
|
+
let browserView: BrowserView | null = null;
|
|
503
|
+
|
|
504
|
+
if (this.opts.useWindowPool) {
|
|
505
|
+
browserView = this.browserViewPool.get();
|
|
506
|
+
if (browserView) {
|
|
507
|
+
this.logger.info(`Found free browser view from pool`, { windowId });
|
|
508
|
+
} else {
|
|
509
|
+
this.logger.info(`No free browser views, adding new view to pool`, {
|
|
510
|
+
windowId,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (!browserView) {
|
|
516
|
+
browserView = new BrowserView(options);
|
|
517
|
+
if (this.opts.useWindowPool) {
|
|
518
|
+
this.browserViewPool.add(browserView);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return browserView;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
private async destroyOrUnloadPooledBrowserView(
|
|
526
|
+
windowId: string,
|
|
527
|
+
browserView: BrowserView
|
|
528
|
+
) {
|
|
529
|
+
if (this.opts.useWindowPool) {
|
|
530
|
+
this.hideWindow(windowId);
|
|
531
|
+
await browserView.webContents.loadURL('about:blank');
|
|
532
|
+
this.browserViewPool.release(browserView);
|
|
533
|
+
} else {
|
|
534
|
+
if (browserView.webContents.isDestroyed()) {
|
|
535
|
+
this.logger.warn(
|
|
536
|
+
`Attempted to destroy BrowserView but it's already destroyed`,
|
|
537
|
+
{ windowId }
|
|
538
|
+
);
|
|
539
|
+
} else {
|
|
540
|
+
// Call destroy on webContents as workaround for BrowserView.destroy() being deprecated.
|
|
541
|
+
// https://github.com/electron/electron/pull/23578
|
|
542
|
+
// https://github.com/electron/electron/issues/26929
|
|
543
|
+
setImmediate(() => {
|
|
544
|
+
(browserView.webContents as any).destroy();
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
private logPoolStats() {
|
|
551
|
+
const bwSize = this.browserWindowPool.size();
|
|
552
|
+
const bvSize = this.browserViewPool.size();
|
|
553
|
+
this.logger.info(
|
|
554
|
+
`BrowserWindow pool total=${bwSize.total} inuse=${bwSize.inuse} free=${bwSize.free}`
|
|
555
|
+
);
|
|
556
|
+
this.logger.info(
|
|
557
|
+
`BrowserView pool total=${bvSize.total} inuse=${bvSize.inuse} free=${bvSize.free}`
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
class WindowPool<T extends BrowserWindow | BrowserView> {
|
|
563
|
+
private inuse: Set<T> = new Set();
|
|
564
|
+
private free: Set<T> = new Set();
|
|
565
|
+
|
|
566
|
+
add(window: T) {
|
|
567
|
+
this.inuse.add(window);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
get(): T | null {
|
|
571
|
+
const firstFreeWindow = this.free.values().next().value;
|
|
572
|
+
if (!firstFreeWindow) return null;
|
|
573
|
+
|
|
574
|
+
// Mark window as inuse, call poo.release(window) to mark as free to use.
|
|
575
|
+
this.free.delete(firstFreeWindow);
|
|
576
|
+
this.inuse.add(firstFreeWindow);
|
|
577
|
+
|
|
578
|
+
return firstFreeWindow;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
release(window: T) {
|
|
582
|
+
this.inuse.delete(window);
|
|
583
|
+
this.free.add(window);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
size() {
|
|
587
|
+
const inuse = this.inuse.size;
|
|
588
|
+
const free = this.free.size;
|
|
589
|
+
return {
|
|
590
|
+
total: inuse + free,
|
|
591
|
+
inuse,
|
|
592
|
+
free,
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
}
|