-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathweb-view.component.tsx
153 lines (139 loc) · 5.42 KB
/
web-view.component.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import { useRef } from 'react';
import {
WebViewContentType,
WebViewDefinition,
SavedWebViewDefinition,
} from '@shared/models/web-view.model';
import { SavedTabInfo, TabInfo, WebViewTabProps } from '@shared/models/docking-framework.model';
import {
convertWebViewDefinitionToSaved,
getWebView,
saveTabInfoBase,
IFRAME_SANDBOX_ALLOW_SAME_ORIGIN,
IFRAME_SANDBOX_ALLOW_SCRIPTS,
WEBVIEW_IFRAME_SRC_SANDBOX,
WEBVIEW_IFRAME_SRCDOC_SANDBOX,
} from '@renderer/services/web-view.service-host';
import logger from '@shared/services/logger.service';
import { serialize } from '@shared/utils/papi-util';
export const TAB_TYPE_WEBVIEW = 'webView';
export function getTitle({ webViewType, title, contentType }: Partial<WebViewTabProps>): string {
return title || `${webViewType || contentType} Web View`;
}
export default function WebView({
webViewType,
content,
title,
contentType,
allowScripts,
allowSameOrigin,
}: WebViewTabProps) {
// This ref will always be defined
// eslint-disable-next-line no-type-assertion/no-type-assertion
const iframeRef = useRef<HTMLIFrameElement>(undefined!);
/** Whether this webview's iframe will be populated by `src` as opposed to `srcdoc` */
const shouldUseSrc = contentType === WebViewContentType.URL;
// TODO: We may be catching iframe exceptions moving forward by posting messages from the child
// iframe to the parent, so it might be good to figure out how it works to add and remove a
// handler of some sort. Maybe the post message handler can more easily handle this kind of
// situation, though. We just don't want to leak memory by leaving an event handler on an iframe
// when it gets removed (if that does leak memory).
return (
<iframe
ref={iframeRef}
title={getTitle({ webViewType, title, contentType })}
/**
* Sandbox attribute for the webview - controls what resources scripts and other things can
* access. See `ALLOWED_IFRAME_SRC_SANDBOX_VALUES` in `web-view.service.ts` for more info.
*
* Note that this does NOT change after the iframe is first added to the dom. We are relying
* on the `MutationObserver` in `web-view.service.ts` to catch if an extension tries to change
* its WebViewContentType and src/srcdoc to some combination that is forbidden (unless
* changing src or srcdoc re-adds the iframe to the dom, in which case it is fine)
*
* TODO: test this sometime to see what happens
*
* DO NOT CHANGE THIS WITHOUT A SERIOUS REASON
*/
sandbox={`${shouldUseSrc ? WEBVIEW_IFRAME_SRC_SANDBOX : WEBVIEW_IFRAME_SRCDOC_SANDBOX}${
allowSameOrigin ? ` ${IFRAME_SANDBOX_ALLOW_SAME_ORIGIN}` : ''
}${allowScripts ? ` ${IFRAME_SANDBOX_ALLOW_SCRIPTS}` : ''}`}
// TODO: csp?
// TODO: credentialless?
// TODO: referrerpolicy?
src={shouldUseSrc ? content : undefined}
srcDoc={shouldUseSrc ? undefined : content}
// Allow WebViews to go fullscreen because why not (fullscreen YouTube video of Psalms LBL)
allow="fullscreen;"
/>
);
}
/**
* Tell the web view service to load the web view with the provided saved definition
*
* @param data Web view definition to load
*/
async function retrieveWebViewContent(data: SavedWebViewDefinition): Promise<void> {
const loadedId = await getWebView(data.webViewType, undefined, {
existingId: data.id,
createNewIfNotFound: false,
});
if (loadedId !== data.id)
logger.error(
`WebView with type ${data.webViewType} and id ${data.id} loaded into id ${loadedId}!`,
);
}
export function updateWebViewTab(savedTabInfo: SavedTabInfo, data: WebViewDefinition): TabInfo {
return {
...savedTabInfo,
data,
tabIconUrl: data.iconUrl,
tabTitle: data.title ?? 'Unknown',
tabTooltip: data.tooltip ?? '',
content: <WebView {...data} />,
};
}
export function loadWebViewTab(savedTabInfo: SavedTabInfo): TabInfo {
if (!savedTabInfo.id) throw new Error('"id" is missing.');
let data: WebViewTabProps;
if (savedTabInfo.data) {
// We need to make sure that the data is of the correct type
// eslint-disable-next-line no-type-assertion/no-type-assertion
data = savedTabInfo.data as WebViewTabProps;
if (savedTabInfo.id !== data.id) throw new Error('"id" does not match webView id.');
if (!data.webViewType) throw new Error('WebView does not have a webViewType. Cannot restore');
// If the webview is saved aka doesn't have content, tell the web view service to load the web
// view. It will asynchronously go get the content and re-run this function
if (!data.content && data.content !== '') {
(async () => {
try {
await retrieveWebViewContent(data);
} catch (e) {
logger.error(
`web-view.component failed to retrieve web view content for ${serialize(
savedTabInfo,
)}: ${e}`,
);
}
})();
}
} else {
// placeholder data
data = {
id: savedTabInfo.id,
webViewType: 'Unknown',
title: 'Unknown',
content: '',
contentType: WebViewContentType.HTML,
};
}
return updateWebViewTab(savedTabInfo, data);
}
export function saveWebViewTab(tabInfo: TabInfo): SavedTabInfo {
return {
...saveTabInfoBase(tabInfo),
// Assert what the unknown `data` type is.
// eslint-disable-next-line no-type-assertion/no-type-assertion
data: convertWebViewDefinitionToSaved(tabInfo.data as WebViewDefinition),
};
}