-
-
Notifications
You must be signed in to change notification settings - Fork 676
msglist [nfc]: Separate out "single-page webview" code from "message list" code #5523
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7d669d6
c7f97b0
6082f4f
2aeb11e
f07e3ba
c0bf408
4d9a54a
6e5b5eb
e7fa85a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| // @flow strict-local | ||
| import * as React from 'react'; | ||
| import { Platform } from 'react-native'; | ||
| import { WebView } from 'react-native-webview'; | ||
|
|
||
| import * as logging from '../utils/logging'; | ||
| import { tryParseUrl } from '../utils/url'; | ||
| import type { ElementConfigFull } from '../reactUtils'; | ||
|
|
||
| /** | ||
| * Return a suitable onShouldStartLoadWithRequest for a single-page WebView. | ||
| * | ||
| * When passed as the onShouldStartLoadWithRequest prop to a WebView, the | ||
| * returned callback will ensure that the webview never navigates away from | ||
| * `baseUrl`. | ||
| * | ||
| * This is a hardening measure for our message-list WebView. We already | ||
| * intercept clicks/touches and open links in a separate browser, but this | ||
| * ensures that if something slips through that it still doesn't break our | ||
| * security assumptions. | ||
| */ | ||
| // See upstream docs for this WebView prop: | ||
| // https://github.com/react-native-webview/react-native-webview/blob/v11.22.2/docs/Reference.md#onshouldstartloadwithrequest | ||
| function makeOnShouldStartLoadWithRequest( | ||
| baseUrl: URL, | ||
| ): React.ElementConfig<typeof WebView>['onShouldStartLoadWithRequest'] { | ||
| let loaded_once = false; | ||
|
|
||
| return event => { | ||
| // eslint-disable-next-line no-use-before-define | ||
| const ok = urlIsOk(event.url); | ||
| if (!ok) { | ||
| logging.warn('webview: rejected navigation event', { | ||
| navigation_event: { ...event }, | ||
| expected_url: baseUrl.toString(), | ||
| }); | ||
| } | ||
| return ok; | ||
| }; | ||
|
|
||
| function urlIsOk(url: string): boolean { | ||
| // On Android the onShouldStartLoadWithRequest prop is documented to be | ||
| // skipped on first load; therefore, simply never return true. | ||
| if (Platform.OS === 'android') { | ||
| return false; | ||
| } | ||
|
|
||
| // Otherwise (for iOS), return `true` only if the URL looks like what | ||
| // we're expecting, and only the first such time. | ||
| const parsedUrl = tryParseUrl(url); | ||
| if (!loaded_once && parsedUrl && parsedUrl.toString() === baseUrl.toString()) { | ||
| loaded_once = true; | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Render a WebView that shows the given HTML at the given base URL, only. | ||
| * | ||
| * The WebView will show the page described by the HTML string `html`. Any | ||
| * attempts to navigate to a new page will be rejected. | ||
| * | ||
| * Relative URL references to other resources (scripts, images, etc.) will | ||
| * be resolved relative to `baseUrl`. | ||
| * | ||
| * Assumes `baseUrl` has the scheme `file:`. No actual file need exist at | ||
| * `baseUrl` itself, because the page is taken from the string `html`. | ||
| */ | ||
| // TODO: This should ideally be a proper React component of its own. The | ||
| // thing that may require care when doing that is our use of | ||
| // `shouldComponentUpdate` in its caller, `MessageList`. | ||
|
Comment on lines
+71
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, testing might reveal some special care needed, and that seems fine to postpone with this TODO in place. I'd hope we could just make a "proper React component" that doesn't add or subscribe to state that would cause it to rerender. Then, hopefully, it wouldn't rerender except when its parent, In #4173, which attempted a similar refactor in 2020-07, I suggested we could help ensure that by using a "stateless function component" instead of a class component. Before React Hooks, IIUC all function components were understood to be "stateless". With Hooks, I'd say the thing to try (now or later) is a function component that doesn't call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I actually have a draft branch that does this, and then goes on to make |
||
| export const renderSinglePageWebView = ( | ||
| html: string, | ||
| baseUrl: URL, | ||
| moreProps: $Rest< | ||
| ElementConfigFull<typeof WebView>, | ||
| {| source: mixed, originWhitelist: mixed, onShouldStartLoadWithRequest: mixed |}, | ||
| >, | ||
| ): React.Node => ( | ||
| // The `originWhitelist` and `onShouldStartLoadWithRequest` props are | ||
| // meant to mitigate possible XSS bugs, by interrupting an attempted | ||
| // exploit if it tries to navigate to a new URL by e.g. setting | ||
| // `window.location`. | ||
| // | ||
| // Note that neither of them is a hard security barrier; they're checked | ||
| // only against the URL of the document itself. They cannot be used to | ||
| // validate the URL of other resources the WebView loads. | ||
| // | ||
| // Worse, the `originWhitelist` parameter is completely broken. See: | ||
| // https://github.com/react-native-community/react-native-webview/pull/697 | ||
| <WebView | ||
| source={{ baseUrl: (baseUrl.toString(): string), html: (html: string) }} | ||
| originWhitelist={['file://']} | ||
| onShouldStartLoadWithRequest={makeOnShouldStartLoadWithRequest(baseUrl)} | ||
| {...moreProps} | ||
| /> | ||
| ); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that assumption holds on Android because we hard-code a URL with
file:.I think it holds on iOS too. Instead of hard-coding the value, we grab it with iOS native code in our
ZLPConstants.mfile and send it over the RN bridge (starting in commit 762ce28).We decided against pointing to a file served by the packager in development, and presumably such a reference would have used an
http:URL.Apple's doc for the API we ended up using doesn't exactly say the scheme is
file:, but it does say "file URL", as in "The file URL of the bundle’s subdirectory containing resource files.".Our Jest mock (in jest/jestSetup.js) for the
ZLPConstantsnative module useswhich looks like a real value we've actually observed at some point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for tracing that. I went and added a commit documenting that
assetsUrlis a file URL and explaining why, based on this.Yeah, I think by "file URL" they have to mean "URL whose scheme is
file". I don't think there's another meaning that could make it make sense for that to be the phrase they choose.