Ei kuvausta

WebView.android.tsx 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. import React from 'react';
  2. import {
  3. Image,
  4. requireNativeComponent,
  5. UIManager as NotTypedUIManager,
  6. View,
  7. NativeModules,
  8. ImageSourcePropType,
  9. findNodeHandle,
  10. } from 'react-native';
  11. import BatchedBridge from 'react-native/Libraries/BatchedBridge/BatchedBridge';
  12. import invariant from 'invariant';
  13. import {
  14. defaultOriginWhitelist,
  15. createOnShouldStartLoadWithRequest,
  16. defaultRenderError,
  17. defaultRenderLoading,
  18. } from './WebViewShared';
  19. import {
  20. WebViewRenderProcessGoneEvent,
  21. WebViewErrorEvent,
  22. WebViewHttpErrorEvent,
  23. WebViewMessageEvent,
  24. WebViewNavigationEvent,
  25. WebViewProgressEvent,
  26. AndroidWebViewProps,
  27. NativeWebViewAndroid,
  28. State,
  29. RNCWebViewUIManagerAndroid,
  30. } from './WebViewTypes';
  31. import styles from './WebView.styles';
  32. const UIManager = NotTypedUIManager as RNCWebViewUIManagerAndroid;
  33. const RNCWebView = requireNativeComponent(
  34. 'RNCWebView',
  35. ) as typeof NativeWebViewAndroid;
  36. const { resolveAssetSource } = Image;
  37. /**
  38. * A simple counter to uniquely identify WebView instances. Do not use this for anything else.
  39. */
  40. let uniqueRef = 0;
  41. /**
  42. * Renders a native WebView.
  43. */
  44. class WebView extends React.Component<AndroidWebViewProps, State> {
  45. static defaultProps = {
  46. overScrollMode: 'always',
  47. javaScriptEnabled: true,
  48. thirdPartyCookiesEnabled: true,
  49. scalesPageToFit: true,
  50. allowsFullscreenVideo: false,
  51. allowFileAccess: false,
  52. saveFormDataDisabled: false,
  53. cacheEnabled: true,
  54. androidHardwareAccelerationDisabled: false,
  55. androidLayerType: 'none',
  56. originWhitelist: defaultOriginWhitelist,
  57. };
  58. static isFileUploadSupported = async () => {
  59. // native implementation should return "true" only for Android 5+
  60. return NativeModules.RNCWebView.isFileUploadSupported();
  61. };
  62. startUrl: string | null = null;
  63. state: State = {
  64. viewState: this.props.startInLoadingState ? 'LOADING' : 'IDLE',
  65. lastErrorEvent: null,
  66. };
  67. onShouldStartLoadWithRequest: ReturnType<typeof createOnShouldStartLoadWithRequest> | null = null;
  68. webViewRef = React.createRef<NativeWebViewAndroid>();
  69. messagingModuleName = `WebViewMessageHandler${uniqueRef+=1}`;
  70. componentDidMount = () => {
  71. BatchedBridge.registerCallableModule(this.messagingModuleName, this);
  72. };
  73. getCommands = () => UIManager.getViewManagerConfig('RNCWebView').Commands;
  74. goForward = () => {
  75. UIManager.dispatchViewManagerCommand(
  76. this.getWebViewHandle(),
  77. this.getCommands().goForward,
  78. undefined
  79. );
  80. };
  81. goBack = () => {
  82. UIManager.dispatchViewManagerCommand(
  83. this.getWebViewHandle(),
  84. this.getCommands().goBack,
  85. undefined
  86. );
  87. };
  88. reload = () => {
  89. this.setState({
  90. viewState: 'LOADING',
  91. });
  92. UIManager.dispatchViewManagerCommand(
  93. this.getWebViewHandle(),
  94. this.getCommands().reload,
  95. undefined
  96. );
  97. };
  98. stopLoading = () => {
  99. UIManager.dispatchViewManagerCommand(
  100. this.getWebViewHandle(),
  101. this.getCommands().stopLoading,
  102. undefined
  103. );
  104. };
  105. requestFocus = () => {
  106. UIManager.dispatchViewManagerCommand(
  107. this.getWebViewHandle(),
  108. this.getCommands().requestFocus,
  109. undefined
  110. );
  111. };
  112. postMessage = (data: string) => {
  113. UIManager.dispatchViewManagerCommand(
  114. this.getWebViewHandle(),
  115. this.getCommands().postMessage,
  116. [String(data)],
  117. );
  118. };
  119. clearFormData = () => {
  120. UIManager.dispatchViewManagerCommand(
  121. this.getWebViewHandle(),
  122. this.getCommands().clearFormData,
  123. undefined,
  124. );
  125. }
  126. clearCache = (includeDiskFiles: boolean) => {
  127. UIManager.dispatchViewManagerCommand(
  128. this.getWebViewHandle(),
  129. this.getCommands().clearCache,
  130. [includeDiskFiles],
  131. );
  132. };
  133. clearHistory = () => {
  134. UIManager.dispatchViewManagerCommand(
  135. this.getWebViewHandle(),
  136. this.getCommands().clearHistory,
  137. undefined,
  138. );
  139. };
  140. /**
  141. * Injects a javascript string into the referenced WebView. Deliberately does not
  142. * return a response because using eval() to return a response breaks this method
  143. * on pages with a Content Security Policy that disallows eval(). If you need that
  144. * functionality, look into postMessage/onMessage.
  145. */
  146. injectJavaScript = (data: string) => {
  147. UIManager.dispatchViewManagerCommand(
  148. this.getWebViewHandle(),
  149. this.getCommands().injectJavaScript,
  150. [data],
  151. );
  152. };
  153. /**
  154. * We return an event with a bunch of fields including:
  155. * url, title, loading, canGoBack, canGoForward
  156. */
  157. updateNavigationState = (event: WebViewNavigationEvent) => {
  158. if (this.props.onNavigationStateChange) {
  159. this.props.onNavigationStateChange(event.nativeEvent);
  160. }
  161. };
  162. /**
  163. * Returns the native `WebView` node.
  164. */
  165. getWebViewHandle = () => {
  166. const nodeHandle = findNodeHandle(this.webViewRef.current);
  167. invariant(nodeHandle != null, 'nodeHandle expected to be non-null');
  168. return nodeHandle as number;
  169. };
  170. onLoadingStart = (event: WebViewNavigationEvent) => {
  171. const { onLoadStart } = this.props;
  172. const { nativeEvent: { url } } = event;
  173. this.startUrl = url;
  174. if (onLoadStart) {
  175. onLoadStart(event);
  176. }
  177. this.updateNavigationState(event);
  178. };
  179. onLoadingError = (event: WebViewErrorEvent) => {
  180. event.persist(); // persist this event because we need to store it
  181. const { onError, onLoadEnd } = this.props;
  182. if (onError) {
  183. onError(event);
  184. }
  185. if (onLoadEnd) {
  186. onLoadEnd(event);
  187. }
  188. console.warn('Encountered an error loading page', event.nativeEvent);
  189. this.setState({
  190. lastErrorEvent: event.nativeEvent,
  191. viewState: 'ERROR',
  192. });
  193. };
  194. onHttpError = (event: WebViewHttpErrorEvent) => {
  195. const { onHttpError } = this.props;
  196. if (onHttpError) {
  197. onHttpError(event);
  198. }
  199. }
  200. onRenderProcessGone = (event: WebViewRenderProcessGoneEvent) => {
  201. const { onRenderProcessGone } = this.props;
  202. if (onRenderProcessGone) {
  203. onRenderProcessGone(event);
  204. }
  205. }
  206. onLoadingFinish = (event: WebViewNavigationEvent) => {
  207. const { onLoad, onLoadEnd } = this.props;
  208. const { nativeEvent: { url } } = event;
  209. if (onLoad) {
  210. onLoad(event);
  211. }
  212. if (onLoadEnd) {
  213. onLoadEnd(event);
  214. }
  215. if (url === this.startUrl) {
  216. this.setState({
  217. viewState: 'IDLE',
  218. });
  219. }
  220. this.updateNavigationState(event);
  221. };
  222. onMessage = (event: WebViewMessageEvent) => {
  223. const { onMessage } = this.props;
  224. if (onMessage) {
  225. onMessage(event);
  226. }
  227. };
  228. onLoadingProgress = (event: WebViewProgressEvent) => {
  229. const { onLoadProgress } = this.props;
  230. const { nativeEvent: { progress } } = event;
  231. if (progress === 1) {
  232. this.setState((state) => {
  233. if (state.viewState === 'LOADING') {
  234. return { viewState: 'IDLE' };
  235. }
  236. return null;
  237. });
  238. }
  239. if (onLoadProgress) {
  240. onLoadProgress(event);
  241. }
  242. };
  243. onShouldStartLoadWithRequestCallback = (
  244. shouldStart: boolean,
  245. url: string,
  246. lockIdentifier?: number,
  247. ) => {
  248. if (lockIdentifier) {
  249. NativeModules.RNCWebView.onShouldStartLoadWithRequestCallback(shouldStart, lockIdentifier);
  250. } else if (shouldStart) {
  251. UIManager.dispatchViewManagerCommand(
  252. this.getWebViewHandle(),
  253. this.getCommands().loadUrl,
  254. [String(url)],
  255. );
  256. }
  257. };
  258. render() {
  259. const {
  260. onMessage,
  261. onShouldStartLoadWithRequest: onShouldStartLoadWithRequestProp,
  262. originWhitelist,
  263. renderError,
  264. renderLoading,
  265. source,
  266. style,
  267. containerStyle,
  268. nativeConfig = {},
  269. ...otherProps
  270. } = this.props;
  271. let otherView = null;
  272. if (this.state.viewState === 'LOADING') {
  273. otherView = (renderLoading || defaultRenderLoading)();
  274. } else if (this.state.viewState === 'ERROR') {
  275. const errorEvent = this.state.lastErrorEvent;
  276. invariant(errorEvent != null, 'lastErrorEvent expected to be non-null');
  277. otherView = (renderError || defaultRenderError)(
  278. errorEvent.domain,
  279. errorEvent.code,
  280. errorEvent.description,
  281. );
  282. } else if (this.state.viewState !== 'IDLE') {
  283. console.error(
  284. `RNCWebView invalid state encountered: ${this.state.viewState}`,
  285. );
  286. }
  287. const webViewStyles = [styles.container, styles.webView, style];
  288. const webViewContainerStyle = [styles.container, containerStyle];
  289. if (typeof source !== "number" && source && 'method' in source) {
  290. if (source.method === 'POST' && source.headers) {
  291. console.warn(
  292. 'WebView: `source.headers` is not supported when using POST.',
  293. );
  294. } else if (source.method === 'GET' && source.body) {
  295. console.warn('WebView: `source.body` is not supported when using GET.');
  296. }
  297. }
  298. const NativeWebView
  299. = (nativeConfig.component as typeof NativeWebViewAndroid) || RNCWebView;
  300. this.onShouldStartLoadWithRequest = createOnShouldStartLoadWithRequest(
  301. this.onShouldStartLoadWithRequestCallback,
  302. // casting cause it's in the default props
  303. originWhitelist as readonly string[],
  304. onShouldStartLoadWithRequestProp,
  305. );
  306. const webView = (
  307. <NativeWebView
  308. key="webViewKey"
  309. {...otherProps}
  310. messagingEnabled={typeof onMessage === 'function'}
  311. messagingModuleName={this.messagingModuleName}
  312. onLoadingError={this.onLoadingError}
  313. onLoadingFinish={this.onLoadingFinish}
  314. onLoadingProgress={this.onLoadingProgress}
  315. onLoadingStart={this.onLoadingStart}
  316. onHttpError={this.onHttpError}
  317. onRenderProcessGone={this.onRenderProcessGone}
  318. onMessage={this.onMessage}
  319. onShouldStartLoadWithRequest={this.onShouldStartLoadWithRequest}
  320. ref={this.webViewRef}
  321. // TODO: find a better way to type this.
  322. source={resolveAssetSource(source as ImageSourcePropType)}
  323. style={webViewStyles}
  324. {...nativeConfig.props}
  325. />
  326. );
  327. return (
  328. <View style={webViewContainerStyle}>
  329. {webView}
  330. {otherView}
  331. </View>
  332. );
  333. }
  334. }
  335. export default WebView;