No Description

WebView.ios.tsx 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. import React from 'react';
  2. import {
  3. UIManager as NotTypedUIManager,
  4. View,
  5. requireNativeComponent,
  6. NativeModules,
  7. Image,
  8. findNodeHandle,
  9. ImageSourcePropType,
  10. } from 'react-native';
  11. import invariant from 'invariant';
  12. import {
  13. defaultOriginWhitelist,
  14. createOnShouldStartLoadWithRequest,
  15. defaultRenderError,
  16. defaultRenderLoading,
  17. } from './WebViewShared';
  18. import {
  19. WebViewErrorEvent,
  20. WebViewMessageEvent,
  21. WebViewNavigationEvent,
  22. WebViewProgressEvent,
  23. IOSWebViewProps,
  24. DecelerationRateConstant,
  25. NativeWebViewIOS,
  26. ViewManager,
  27. State,
  28. RNCWebViewUIManager,
  29. } from './WebViewTypes';
  30. import styles from './WebView.styles';
  31. const UIManager = NotTypedUIManager as RNCWebViewUIManager;
  32. const { resolveAssetSource } = Image;
  33. const processDecelerationRate = (
  34. decelerationRate: DecelerationRateConstant | number | undefined,
  35. ) => {
  36. let newDecelerationRate = decelerationRate;
  37. if (newDecelerationRate === 'normal') {
  38. newDecelerationRate = 0.998;
  39. } else if (newDecelerationRate === 'fast') {
  40. newDecelerationRate = 0.99;
  41. }
  42. return newDecelerationRate;
  43. };
  44. const RNCWebViewManager = NativeModules.RNCWebViewManager as ViewManager;
  45. const RNCWebView: typeof NativeWebViewIOS = requireNativeComponent(
  46. 'RNCWebView',
  47. );
  48. class WebView extends React.Component<IOSWebViewProps, State> {
  49. static defaultProps = {
  50. javaScriptEnabled: true,
  51. cacheEnabled: true,
  52. originWhitelist: defaultOriginWhitelist,
  53. useSharedProcessPool: true,
  54. };
  55. static isFileUploadSupported = async () => {
  56. // no native implementation for iOS, depends only on permissions
  57. return true;
  58. };
  59. state: State = {
  60. viewState: this.props.startInLoadingState ? 'LOADING' : 'IDLE',
  61. lastErrorEvent: null,
  62. };
  63. webViewRef = React.createRef<NativeWebViewIOS>();
  64. // eslint-disable-next-line react/sort-comp
  65. getCommands = () => UIManager.getViewManagerConfig('RNCWebView').Commands;
  66. /**
  67. * Go forward one page in the web view's history.
  68. */
  69. goForward = () => {
  70. UIManager.dispatchViewManagerCommand(
  71. this.getWebViewHandle(),
  72. this.getCommands().goForward,
  73. );
  74. };
  75. /**
  76. * Go back one page in the web view's history.
  77. */
  78. goBack = () => {
  79. UIManager.dispatchViewManagerCommand(
  80. this.getWebViewHandle(),
  81. this.getCommands().goBack,
  82. );
  83. };
  84. /**
  85. * Reloads the current page.
  86. */
  87. reload = () => {
  88. this.setState({ viewState: 'LOADING' });
  89. UIManager.dispatchViewManagerCommand(
  90. this.getWebViewHandle(),
  91. this.getCommands().reload,
  92. );
  93. };
  94. /**
  95. * Stop loading the current page.
  96. */
  97. stopLoading = () => {
  98. UIManager.dispatchViewManagerCommand(
  99. this.getWebViewHandle(),
  100. this.getCommands().stopLoading,
  101. );
  102. };
  103. /**
  104. * Request focus on WebView rendered page.
  105. */
  106. requestFocus = () => {
  107. UIManager.dispatchViewManagerCommand(
  108. this.getWebViewHandle(),
  109. this.getCommands().requestFocus,
  110. );
  111. };
  112. /**
  113. * Posts a message to the web view, which will emit a `message` event.
  114. * Accepts one argument, `data`, which must be a string.
  115. *
  116. * In your webview, you'll need to something like the following.
  117. *
  118. * ```js
  119. * document.addEventListener('message', e => { document.title = e.data; });
  120. * ```
  121. */
  122. postMessage = (data: string) => {
  123. UIManager.dispatchViewManagerCommand(
  124. this.getWebViewHandle(),
  125. this.getCommands().postMessage,
  126. [String(data)],
  127. );
  128. };
  129. /**
  130. * Injects a javascript string into the referenced WebView. Deliberately does not
  131. * return a response because using eval() to return a response breaks this method
  132. * on pages with a Content Security Policy that disallows eval(). If you need that
  133. * functionality, look into postMessage/onMessage.
  134. */
  135. injectJavaScript = (data: string) => {
  136. UIManager.dispatchViewManagerCommand(
  137. this.getWebViewHandle(),
  138. this.getCommands().injectJavaScript,
  139. [data],
  140. );
  141. };
  142. /**
  143. * We return an event with a bunch of fields including:
  144. * url, title, loading, canGoBack, canGoForward
  145. */
  146. updateNavigationState = (event: WebViewNavigationEvent) => {
  147. if (this.props.onNavigationStateChange) {
  148. this.props.onNavigationStateChange(event.nativeEvent);
  149. }
  150. };
  151. /**
  152. * Returns the native `WebView` node.
  153. */
  154. getWebViewHandle = () => {
  155. const nodeHandle = findNodeHandle(this.webViewRef.current);
  156. invariant(nodeHandle != null, 'nodeHandle expected to be non-null');
  157. return nodeHandle as number;
  158. };
  159. onLoadingStart = (event: WebViewNavigationEvent) => {
  160. const { onLoadStart } = this.props;
  161. if (onLoadStart) {
  162. onLoadStart(event);
  163. }
  164. this.updateNavigationState(event);
  165. };
  166. onLoadingError = (event: WebViewErrorEvent) => {
  167. event.persist(); // persist this event because we need to store it
  168. const { onError, onLoadEnd } = this.props;
  169. if (onLoadEnd) {
  170. onLoadEnd(event);
  171. }
  172. if (onError) {
  173. onError(event);
  174. }
  175. console.warn('Encountered an error loading page', event.nativeEvent);
  176. this.setState({
  177. lastErrorEvent: event.nativeEvent,
  178. viewState: 'ERROR',
  179. });
  180. };
  181. onLoadingFinish = (event: WebViewNavigationEvent) => {
  182. const { onLoad, onLoadEnd } = this.props;
  183. if (onLoad) {
  184. onLoad(event);
  185. }
  186. if (onLoadEnd) {
  187. onLoadEnd(event);
  188. }
  189. this.setState({
  190. viewState: 'IDLE',
  191. });
  192. this.updateNavigationState(event);
  193. };
  194. onMessage = (event: WebViewMessageEvent) => {
  195. const { onMessage } = this.props;
  196. if (onMessage) {
  197. onMessage(event);
  198. }
  199. };
  200. onLoadingProgress = (event: WebViewProgressEvent) => {
  201. const { onLoadProgress } = this.props;
  202. if (onLoadProgress) {
  203. onLoadProgress(event);
  204. }
  205. };
  206. onShouldStartLoadWithRequestCallback = (
  207. shouldStart: boolean,
  208. _url: string,
  209. lockIdentifier: number,
  210. ) => {
  211. const viewManager
  212. = (this.props.nativeConfig && this.props.nativeConfig.viewManager)
  213. || RNCWebViewManager;
  214. viewManager.startLoadWithResult(!!shouldStart, lockIdentifier);
  215. };
  216. componentDidUpdate(prevProps: IOSWebViewProps) {
  217. this.showRedboxOnPropChanges(prevProps, 'allowsInlineMediaPlayback');
  218. this.showRedboxOnPropChanges(prevProps, 'incognito');
  219. this.showRedboxOnPropChanges(prevProps, 'mediaPlaybackRequiresUserAction');
  220. this.showRedboxOnPropChanges(prevProps, 'dataDetectorTypes');
  221. }
  222. showRedboxOnPropChanges(
  223. prevProps: IOSWebViewProps,
  224. propName: keyof IOSWebViewProps,
  225. ) {
  226. if (this.props[propName] !== prevProps[propName]) {
  227. console.error(
  228. `Changes to property ${propName} do nothing after the initial render.`,
  229. );
  230. }
  231. }
  232. render() {
  233. const {
  234. decelerationRate: decelerationRateProp,
  235. nativeConfig = {},
  236. onMessage,
  237. onShouldStartLoadWithRequest: onShouldStartLoadWithRequestProp,
  238. originWhitelist,
  239. renderError,
  240. renderLoading,
  241. style,
  242. ...otherProps
  243. } = this.props;
  244. let otherView = null;
  245. if (this.state.viewState === 'LOADING') {
  246. otherView = (renderLoading || defaultRenderLoading)();
  247. } else if (this.state.viewState === 'ERROR') {
  248. const errorEvent = this.state.lastErrorEvent;
  249. invariant(errorEvent != null, 'lastErrorEvent expected to be non-null');
  250. otherView = (renderError || defaultRenderError)(
  251. errorEvent.domain,
  252. errorEvent.code,
  253. errorEvent.description,
  254. );
  255. } else if (this.state.viewState !== 'IDLE') {
  256. console.error(
  257. `RNCWebView invalid state encountered: ${this.state.viewState}`,
  258. );
  259. }
  260. const webViewStyles = [styles.container, styles.webView, style];
  261. const onShouldStartLoadWithRequest = createOnShouldStartLoadWithRequest(
  262. this.onShouldStartLoadWithRequestCallback,
  263. // casting cause it's in the default props
  264. originWhitelist as readonly string[],
  265. onShouldStartLoadWithRequestProp,
  266. );
  267. const decelerationRate = processDecelerationRate(decelerationRateProp);
  268. const NativeWebView
  269. = (nativeConfig.component as typeof NativeWebViewIOS | undefined)
  270. || RNCWebView;
  271. const webView = (
  272. <NativeWebView
  273. key="webViewKey"
  274. {...otherProps}
  275. decelerationRate={decelerationRate}
  276. messagingEnabled={typeof onMessage === 'function'}
  277. onLoadingError={this.onLoadingError}
  278. onLoadingFinish={this.onLoadingFinish}
  279. onLoadingProgress={this.onLoadingProgress}
  280. onLoadingStart={this.onLoadingStart}
  281. onMessage={this.onMessage}
  282. onScroll={this.props.onScroll}
  283. onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
  284. ref={this.webViewRef}
  285. // TODO: find a better way to type this.
  286. source={resolveAssetSource(this.props.source as ImageSourcePropType)}
  287. style={webViewStyles}
  288. {...nativeConfig.props}
  289. />
  290. );
  291. return (
  292. <View style={styles.container}>
  293. {webView}
  294. {otherView}
  295. </View>
  296. );
  297. }
  298. }
  299. export default WebView;