aboutsummaryrefslogtreecommitdiffstats
path: root/packages/main/src/infrastructure/electron/impl/ElectronServiceView.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/main/src/infrastructure/electron/impl/ElectronServiceView.ts')
-rw-r--r--packages/main/src/infrastructure/electron/impl/ElectronServiceView.ts130
1 files changed, 113 insertions, 17 deletions
diff --git a/packages/main/src/infrastructure/electron/impl/ElectronServiceView.ts b/packages/main/src/infrastructure/electron/impl/ElectronServiceView.ts
index c4f7e4d..91247c8 100644
--- a/packages/main/src/infrastructure/electron/impl/ElectronServiceView.ts
+++ b/packages/main/src/infrastructure/electron/impl/ElectronServiceView.ts
@@ -18,15 +18,17 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import type { BrowserViewBounds } from '@sophie/shared';
22import { BrowserView } from 'electron'; 21import { BrowserView } from 'electron';
23 22
24import type Service from '../../../stores/Service'; 23import type Service from '../../../stores/Service.js';
25import type Resources from '../../resources/Resources'; 24import getLogger from '../../../utils/getLogger.js';
26import type { ServiceView } from '../types'; 25import type Resources from '../../resources/Resources.js';
26import type { ServiceView } from '../types.js';
27 27
28import ElectronPartition from './ElectronPartition'; 28import ElectronPartition from './ElectronPartition.js';
29import type ElectronViewFactory from './ElectronViewFactory'; 29import type ElectronViewFactory from './ElectronViewFactory.js';
30
31const log = getLogger('ElectronServiceView');
30 32
31export default class ElectronServiceView implements ServiceView { 33export default class ElectronServiceView implements ServiceView {
32 readonly id: string; 34 readonly id: string;
@@ -36,7 +38,7 @@ export default class ElectronServiceView implements ServiceView {
36 readonly browserView: BrowserView; 38 readonly browserView: BrowserView;
37 39
38 constructor( 40 constructor(
39 service: Service, 41 private readonly service: Service,
40 resources: Resources, 42 resources: Resources,
41 partition: ElectronPartition, 43 partition: ElectronPartition,
42 private readonly parent: ElectronViewFactory, 44 private readonly parent: ElectronViewFactory,
@@ -52,6 +54,15 @@ export default class ElectronServiceView implements ServiceView {
52 }, 54 },
53 }); 55 });
54 56
57 this.browserView.setBackgroundColor('#fff');
58 this.browserView.setAutoResize({
59 width: true,
60 height: true,
61 });
62 // Util we first attach `browserView` to a `BrowserWindow`,
63 // `setBounds` calls will be ignored, so there's no point in callind `updateBounds` here.
64 // It will be called by `ElectronMainWindow` when we first attach this service to it.
65
55 const { webContents } = this.browserView; 66 const { webContents } = this.browserView;
56 67
57 function setLocation(url: string) { 68 function setLocation(url: string) {
@@ -72,20 +83,83 @@ export default class ElectronServiceView implements ServiceView {
72 } 83 }
73 }); 84 });
74 85
86 webContents.on(
87 'did-fail-load',
88 (_event, errorCode, errorDesc, url, isMainFrame) => {
89 if (errorCode === -3) {
90 // Do not signal error on ABORTED, since that corresponds to an action requested by the user.
91 // Other events (`did-start-loading` or `did-stop-loading`) will cause service state changes
92 // that are appropriate for the user action.
93 log.debug('Loading', url, 'in service', this.id, 'aborted by user');
94 return;
95 }
96 if (isMainFrame) {
97 setLocation(url);
98 service.setFailed(errorCode, errorDesc);
99 log.warn(
100 'Failed to load',
101 url,
102 'in service',
103 this.id,
104 errorCode,
105 errorDesc,
106 );
107 }
108 },
109 );
110
111 /**
112 * We use the `'certificate-error'` event instead of `session.setCertificateVerifyProc`
113 * because:
114 *
115 * 1. `'certificate-error'` is bound to the `webContents`, so we can display the certificate
116 * in the place of the correct service. Note that chromium still manages certificate trust
117 * per session, so we can't have different trusted certificates for each service of a
118 * profile.
119 * 2. The results of `'certificate-error'` are _not_ cached, so we can initially reject
120 * the certificate but we can still accept it once the user trusts it temporarily.
121 */
122 webContents.on(
123 'certificate-error',
124 (event, url, error, certificate, callback, isMainFrame) => {
125 if (service.isCertificateTemporarilyTrusted(certificate)) {
126 event.preventDefault();
127 callback(true);
128 return;
129 }
130 if (isMainFrame) {
131 setLocation(url);
132 service.setCertificateError(error, certificate);
133 }
134 callback(false);
135 },
136 );
137
75 webContents.on('page-title-updated', (_event, title) => { 138 webContents.on('page-title-updated', (_event, title) => {
76 service.setTitle(title); 139 service.setTitle(title);
77 }); 140 });
78 141
79 webContents.on('did-start-loading', () => { 142 webContents.on('did-start-loading', () => {
80 service.startedLoading(); 143 service.startLoading();
144 });
145
146 webContents.on('did-stop-loading', () => {
147 service.finishLoading();
81 }); 148 });
82 149
83 webContents.on('did-finish-load', () => { 150 webContents.on('render-process-gone', (_event, details) => {
84 service.finishedLoading(); 151 const { reason, exitCode } = details;
152 service.setCrashed(reason, exitCode);
85 }); 153 });
86 154
87 webContents.on('render-process-gone', () => { 155 webContents.setWindowOpenHandler(({ url }) => {
88 service.crashed(); 156 // TODO Add filtering (allowlist) by URL.
157 // TODO Handle `new-window` disposition where the service wants an object returned by
158 // `window.open`.
159 // TODO Handle downloads with `save-to-disk` disposition.
160 // TODO Handle POST bodies where the window must be allowed to open or the data is lost.
161 service.addBlockedPopup(url);
162 return { action: 'deny' };
89 }); 163 });
90 } 164 }
91 165
@@ -93,8 +167,10 @@ export default class ElectronServiceView implements ServiceView {
93 return this.browserView.webContents.id; 167 return this.browserView.webContents.id;
94 } 168 }
95 169
96 loadURL(url: string): Promise<void> { 170 loadURL(url: string): void {
97 return this.browserView.webContents.loadURL(url); 171 this.browserView.webContents.loadURL(url).catch((error) => {
172 log.warn('Error while loading', url, 'in service', this.id, error);
173 });
98 } 174 }
99 175
100 goBack(): void { 176 goBack(): void {
@@ -105,13 +181,33 @@ export default class ElectronServiceView implements ServiceView {
105 this.browserView.webContents.goForward(); 181 this.browserView.webContents.goForward();
106 } 182 }
107 183
108 setBounds(bounds: BrowserViewBounds): void { 184 reload(ignoreCache: boolean): void {
109 this.browserView.setBounds(bounds); 185 if (ignoreCache) {
186 this.browserView.webContents.reloadIgnoringCache();
187 } else {
188 this.browserView.webContents.reload();
189 }
190 }
191
192 stop(): void {
193 this.browserView.webContents.stop();
194 }
195
196 toggleDeveloperTools(): void {
197 this.browserView.webContents.toggleDevTools();
198 }
199
200 updateBounds(): void {
201 const { x, y, width, height, hasBounds } = this.service;
202 if (!hasBounds) {
203 return;
204 }
205 this.browserView.setBounds({ x, y, width, height });
110 } 206 }
111 207
112 dispose(): void { 208 dispose(): void {
113 this.parent.unregisterServiceView(this.webContentsId);
114 setImmediate(() => { 209 setImmediate(() => {
210 this.parent.unregisterServiceView(this.webContentsId);
115 // Undocumented electron API, see e.g., https://github.com/electron/electron/issues/29626 211 // Undocumented electron API, see e.g., https://github.com/electron/electron/issues/29626
116 ( 212 (
117 this.browserView.webContents as unknown as { destroy(): void } 213 this.browserView.webContents as unknown as { destroy(): void }