Няма описание

WebView.android.js 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. 'use strict';
  2. import React from 'react';
  3. import PropTypes from 'prop-types';
  4. import ReactNative from 'react-native';
  5. import {
  6. ActivityIndicator,
  7. EdgeInsetsPropType,
  8. StyleSheet,
  9. UIManager,
  10. View,
  11. ViewPropTypes,
  12. Image,
  13. requireNativeComponent
  14. } from 'react-native';
  15. import deprecatedPropType from 'deprecated-prop-type';
  16. import keyMirror from 'fbjs/lib/keyMirror';
  17. import WebViewShared from './WebViewShared';
  18. const resolveAssetSource = Image.resolveAssetSource;
  19. const RNC_WEBVIEW_REF = 'webview';
  20. const WebViewState = keyMirror({
  21. IDLE: null,
  22. LOADING: null,
  23. ERROR: null,
  24. });
  25. const defaultRenderLoading = () => (
  26. <View style={styles.loadingView}>
  27. <ActivityIndicator style={styles.loadingProgressBar} />
  28. </View>
  29. );
  30. /**
  31. * Renders a native WebView.
  32. */
  33. class WebView extends React.Component {
  34. static propTypes = {
  35. ...ViewPropTypes,
  36. renderError: PropTypes.func,
  37. renderLoading: PropTypes.func,
  38. onLoad: PropTypes.func,
  39. onLoadEnd: PropTypes.func,
  40. onLoadStart: PropTypes.func,
  41. onError: PropTypes.func,
  42. automaticallyAdjustContentInsets: PropTypes.bool,
  43. contentInset: EdgeInsetsPropType,
  44. onNavigationStateChange: PropTypes.func,
  45. onMessage: PropTypes.func,
  46. onContentSizeChange: PropTypes.func,
  47. startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load
  48. style: ViewPropTypes.style,
  49. html: deprecatedPropType(
  50. PropTypes.string,
  51. 'Use the `source` prop instead.',
  52. ),
  53. url: deprecatedPropType(PropTypes.string, 'Use the `source` prop instead.'),
  54. /**
  55. * Loads static html or a uri (with optional headers) in the WebView.
  56. */
  57. source: PropTypes.oneOfType([
  58. PropTypes.shape({
  59. /*
  60. * The URI to load in the WebView. Can be a local or remote file.
  61. */
  62. uri: PropTypes.string,
  63. /*
  64. * The HTTP Method to use. Defaults to GET if not specified.
  65. * NOTE: On Android, only GET and POST are supported.
  66. */
  67. method: PropTypes.oneOf(['GET', 'POST']),
  68. /*
  69. * Additional HTTP headers to send with the request.
  70. * NOTE: On Android, this can only be used with GET requests.
  71. */
  72. headers: PropTypes.object,
  73. /*
  74. * The HTTP body to send with the request. This must be a valid
  75. * UTF-8 string, and will be sent exactly as specified, with no
  76. * additional encoding (e.g. URL-escaping or base64) applied.
  77. * NOTE: On Android, this can only be used with POST requests.
  78. */
  79. body: PropTypes.string,
  80. }),
  81. PropTypes.shape({
  82. /*
  83. * A static HTML page to display in the WebView.
  84. */
  85. html: PropTypes.string,
  86. /*
  87. * The base URL to be used for any relative links in the HTML.
  88. */
  89. baseUrl: PropTypes.string,
  90. }),
  91. /*
  92. * Used internally by packager.
  93. */
  94. PropTypes.number,
  95. ]),
  96. /**
  97. * Used on Android only, JS is enabled by default for WebView on iOS
  98. * @platform android
  99. */
  100. javaScriptEnabled: PropTypes.bool,
  101. /**
  102. * Used on Android Lollipop and above only, third party cookies are enabled
  103. * by default for WebView on Android Kitkat and below and on iOS
  104. * @platform android
  105. */
  106. thirdPartyCookiesEnabled: PropTypes.bool,
  107. /**
  108. * Used on Android only, controls whether DOM Storage is enabled or not
  109. * @platform android
  110. */
  111. domStorageEnabled: PropTypes.bool,
  112. /**
  113. * Sets whether Geolocation is enabled. The default is false.
  114. * @platform android
  115. */
  116. geolocationEnabled: PropTypes.bool,
  117. /**
  118. * Sets the JS to be injected when the webpage loads.
  119. */
  120. injectedJavaScript: PropTypes.string,
  121. /**
  122. * Sets whether the webpage scales to fit the view and the user can change the scale.
  123. */
  124. scalesPageToFit: PropTypes.bool,
  125. /**
  126. * Sets the user-agent for this WebView. The user-agent can also be set in native using
  127. * WebViewConfig. This prop will overwrite that config.
  128. */
  129. userAgent: PropTypes.string,
  130. /**
  131. * Used to locate this view in end-to-end tests.
  132. */
  133. testID: PropTypes.string,
  134. /**
  135. * Determines whether HTML5 audio & videos require the user to tap before they can
  136. * start playing. The default value is `false`.
  137. */
  138. mediaPlaybackRequiresUserAction: PropTypes.bool,
  139. /**
  140. * Boolean that sets whether JavaScript running in the context of a file
  141. * scheme URL should be allowed to access content from any origin.
  142. * Including accessing content from other file scheme URLs
  143. * @platform android
  144. */
  145. allowUniversalAccessFromFileURLs: PropTypes.bool,
  146. /**
  147. * List of origin strings to allow being navigated to. The strings allow
  148. * wildcards and get matched against *just* the origin (not the full URL).
  149. * If the user taps to navigate to a new page but the new page is not in
  150. * this whitelist, the URL will be opened by the Android OS.
  151. * The default whitelisted origins are "http://*" and "https://*".
  152. */
  153. originWhitelist: PropTypes.arrayOf(PropTypes.string),
  154. /**
  155. * Function that accepts a string that will be passed to the WebView and
  156. * executed immediately as JavaScript.
  157. */
  158. injectJavaScript: PropTypes.func,
  159. /**
  160. * Specifies the mixed content mode. i.e WebView will allow a secure origin to load content from any other origin.
  161. *
  162. * Possible values for `mixedContentMode` are:
  163. *
  164. * - `'never'` (default) - WebView will not allow a secure origin to load content from an insecure origin.
  165. * - `'always'` - WebView will allow a secure origin to load content from any other origin, even if that origin is insecure.
  166. * - `'compatibility'` - WebView will attempt to be compatible with the approach of a modern web browser with regard to mixed content.
  167. * @platform android
  168. */
  169. mixedContentMode: PropTypes.oneOf(['never', 'always', 'compatibility']),
  170. /**
  171. * Used on Android only, controls whether form autocomplete data should be saved
  172. * @platform android
  173. */
  174. saveFormDataDisabled: PropTypes.bool,
  175. /**
  176. * Override the native component used to render the WebView. Enables a custom native
  177. * WebView which uses the same JavaScript as the original WebView.
  178. */
  179. nativeConfig: PropTypes.shape({
  180. /*
  181. * The native component used to render the WebView.
  182. */
  183. component: PropTypes.any,
  184. /*
  185. * Set props directly on the native component WebView. Enables custom props which the
  186. * original WebView doesn't pass through.
  187. */
  188. props: PropTypes.object,
  189. /*
  190. * Set the ViewManager to use for communication with the native side.
  191. * @platform ios
  192. */
  193. viewManager: PropTypes.object,
  194. }),
  195. /*
  196. * Used on Android only, controls whether the given list of URL prefixes should
  197. * make {@link com.facebook.react.views.webview.ReactWebViewClient} to launch a
  198. * default activity intent for those URL instead of loading it within the webview.
  199. * Use this to list URLs that WebView cannot handle, e.g. a PDF url.
  200. * @platform android
  201. */
  202. urlPrefixesForDefaultIntent: PropTypes.arrayOf(PropTypes.string),
  203. };
  204. static defaultProps = {
  205. javaScriptEnabled: true,
  206. thirdPartyCookiesEnabled: true,
  207. scalesPageToFit: true,
  208. saveFormDataDisabled: false,
  209. originWhitelist: WebViewShared.defaultOriginWhitelist,
  210. };
  211. state = {
  212. viewState: WebViewState.IDLE,
  213. lastErrorEvent: null,
  214. startInLoadingState: true,
  215. };
  216. UNSAFE_componentWillMount() {
  217. if (this.props.startInLoadingState) {
  218. this.setState({ viewState: WebViewState.LOADING });
  219. }
  220. }
  221. render() {
  222. let otherView = null;
  223. if (this.state.viewState === WebViewState.LOADING) {
  224. otherView = (this.props.renderLoading || defaultRenderLoading)();
  225. } else if (this.state.viewState === WebViewState.ERROR) {
  226. const errorEvent = this.state.lastErrorEvent;
  227. otherView =
  228. this.props.renderError &&
  229. this.props.renderError(
  230. errorEvent.domain,
  231. errorEvent.code,
  232. errorEvent.description,
  233. );
  234. } else if (this.state.viewState !== WebViewState.IDLE) {
  235. console.error(
  236. 'RNCWebView invalid state encountered: ' + this.state.loading,
  237. );
  238. }
  239. const webViewStyles = [styles.container, this.props.style];
  240. if (
  241. this.state.viewState === WebViewState.LOADING ||
  242. this.state.viewState === WebViewState.ERROR
  243. ) {
  244. // if we're in either LOADING or ERROR states, don't show the webView
  245. webViewStyles.push(styles.hidden);
  246. }
  247. const source = this.props.source || {};
  248. if (this.props.html) {
  249. source.html = this.props.html;
  250. } else if (this.props.url) {
  251. source.uri = this.props.url;
  252. }
  253. if (source.method === 'POST' && source.headers) {
  254. console.warn(
  255. 'WebView: `source.headers` is not supported when using POST.',
  256. );
  257. } else if (source.method === 'GET' && source.body) {
  258. console.warn('WebView: `source.body` is not supported when using GET.');
  259. }
  260. const nativeConfig = this.props.nativeConfig || {};
  261. const originWhitelist = (this.props.originWhitelist || []).map(
  262. WebViewShared.originWhitelistToRegex,
  263. );
  264. let NativeWebView = nativeConfig.component || RNCWebView;
  265. const webView = (
  266. <NativeWebView
  267. ref={RNC_WEBVIEW_REF}
  268. key="webViewKey"
  269. style={webViewStyles}
  270. source={resolveAssetSource(source)}
  271. scalesPageToFit={this.props.scalesPageToFit}
  272. injectedJavaScript={this.props.injectedJavaScript}
  273. userAgent={this.props.userAgent}
  274. javaScriptEnabled={this.props.javaScriptEnabled}
  275. thirdPartyCookiesEnabled={this.props.thirdPartyCookiesEnabled}
  276. domStorageEnabled={this.props.domStorageEnabled}
  277. messagingEnabled={typeof this.props.onMessage === 'function'}
  278. onMessage={this.onMessage}
  279. contentInset={this.props.contentInset}
  280. automaticallyAdjustContentInsets={
  281. this.props.automaticallyAdjustContentInsets
  282. }
  283. onContentSizeChange={this.props.onContentSizeChange}
  284. onLoadingStart={this.onLoadingStart}
  285. onLoadingFinish={this.onLoadingFinish}
  286. onLoadingError={this.onLoadingError}
  287. testID={this.props.testID}
  288. geolocationEnabled={this.props.geolocationEnabled}
  289. mediaPlaybackRequiresUserAction={
  290. this.props.mediaPlaybackRequiresUserAction
  291. }
  292. allowUniversalAccessFromFileURLs={
  293. this.props.allowUniversalAccessFromFileURLs
  294. }
  295. originWhitelist={originWhitelist}
  296. mixedContentMode={this.props.mixedContentMode}
  297. saveFormDataDisabled={this.props.saveFormDataDisabled}
  298. urlPrefixesForDefaultIntent={this.props.urlPrefixesForDefaultIntent}
  299. {...nativeConfig.props}
  300. />
  301. );
  302. return (
  303. <View style={styles.container}>
  304. {webView}
  305. {otherView}
  306. </View>
  307. );
  308. }
  309. goForward = () => {
  310. UIManager.dispatchViewManagerCommand(
  311. this.getWebViewHandle(),
  312. UIManager.RNCWebView.Commands.goForward,
  313. null,
  314. );
  315. };
  316. goBack = () => {
  317. UIManager.dispatchViewManagerCommand(
  318. this.getWebViewHandle(),
  319. UIManager.RNCWebView.Commands.goBack,
  320. null,
  321. );
  322. };
  323. reload = () => {
  324. this.setState({
  325. viewState: WebViewState.LOADING,
  326. });
  327. UIManager.dispatchViewManagerCommand(
  328. this.getWebViewHandle(),
  329. UIManager.RNCWebView.Commands.reload,
  330. null,
  331. );
  332. };
  333. stopLoading = () => {
  334. UIManager.dispatchViewManagerCommand(
  335. this.getWebViewHandle(),
  336. UIManager.RNCWebView.Commands.stopLoading,
  337. null,
  338. );
  339. };
  340. postMessage = data => {
  341. UIManager.dispatchViewManagerCommand(
  342. this.getWebViewHandle(),
  343. UIManager.RNCWebView.Commands.postMessage,
  344. [String(data)],
  345. );
  346. };
  347. /**
  348. * Injects a javascript string into the referenced WebView. Deliberately does not
  349. * return a response because using eval() to return a response breaks this method
  350. * on pages with a Content Security Policy that disallows eval(). If you need that
  351. * functionality, look into postMessage/onMessage.
  352. */
  353. injectJavaScript = data => {
  354. UIManager.dispatchViewManagerCommand(
  355. this.getWebViewHandle(),
  356. UIManager.RNCWebView.Commands.injectJavaScript,
  357. [data],
  358. );
  359. };
  360. /**
  361. * We return an event with a bunch of fields including:
  362. * url, title, loading, canGoBack, canGoForward
  363. */
  364. updateNavigationState = event => {
  365. if (this.props.onNavigationStateChange) {
  366. this.props.onNavigationStateChange(event.nativeEvent);
  367. }
  368. };
  369. getWebViewHandle = () => {
  370. return ReactNative.findNodeHandle(this.refs[RNC_WEBVIEW_REF]);
  371. };
  372. onLoadingStart = event => {
  373. const onLoadStart = this.props.onLoadStart;
  374. onLoadStart && onLoadStart(event);
  375. this.updateNavigationState(event);
  376. };
  377. onLoadingError = event => {
  378. event.persist(); // persist this event because we need to store it
  379. const { onError, onLoadEnd } = this.props;
  380. onError && onError(event);
  381. onLoadEnd && onLoadEnd(event);
  382. console.warn('Encountered an error loading page', event.nativeEvent);
  383. this.setState({
  384. lastErrorEvent: event.nativeEvent,
  385. viewState: WebViewState.ERROR,
  386. });
  387. };
  388. onLoadingFinish = event => {
  389. const { onLoad, onLoadEnd } = this.props;
  390. onLoad && onLoad(event);
  391. onLoadEnd && onLoadEnd(event);
  392. this.setState({
  393. viewState: WebViewState.IDLE,
  394. });
  395. this.updateNavigationState(event);
  396. };
  397. onMessage = (event) => {
  398. const { onMessage } = this.props;
  399. onMessage && onMessage(event);
  400. };
  401. }
  402. const RNCWebView = requireNativeComponent('RNCWebView');
  403. const styles = StyleSheet.create({
  404. container: {
  405. flex: 1,
  406. },
  407. hidden: {
  408. height: 0,
  409. flex: 0, // disable 'flex:1' when hiding a View
  410. },
  411. loadingView: {
  412. flex: 1,
  413. justifyContent: 'center',
  414. alignItems: 'center',
  415. },
  416. loadingProgressBar: {
  417. height: 20,
  418. },
  419. });
  420. module.exports = WebView;