react-native-webview/js/WebView.android.js

336 lines
9.1 KiB
JavaScript
Raw Normal View History

/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
2018-09-10 04:40:32 +00:00
* @flow
*/
2018-08-08 06:02:40 +00:00
import React from 'react';
2018-07-31 05:34:16 +00:00
import ReactNative, {
2018-07-31 05:34:16 +00:00
ActivityIndicator,
Image,
requireNativeComponent,
2018-07-31 05:34:16 +00:00
StyleSheet,
UIManager,
View,
NativeModules
2018-07-31 05:34:16 +00:00
} from 'react-native';
2018-09-10 04:40:32 +00:00
import invariant from 'fbjs/lib/invariant';
2018-07-31 05:34:16 +00:00
import keyMirror from 'fbjs/lib/keyMirror';
import {
defaultOriginWhitelist,
createOnShouldStartLoadWithRequest,
} from './WebViewShared';
2018-09-10 04:40:32 +00:00
import type {
2018-09-10 18:13:20 +00:00
WebViewError,
WebViewErrorEvent,
WebViewMessageEvent,
WebViewNavigationEvent,
WebViewProgressEvent,
2018-09-10 04:40:32 +00:00
WebViewSharedProps,
WebViewSource,
} from './WebViewTypes';
2018-07-31 03:21:48 +00:00
2018-08-08 06:02:40 +00:00
const resolveAssetSource = Image.resolveAssetSource;
2018-07-31 03:21:48 +00:00
const WebViewState = keyMirror({
IDLE: null,
LOADING: null,
ERROR: null,
});
const defaultRenderLoading = () => (
<View style={styles.loadingView}>
<ActivityIndicator style={styles.loadingProgressBar} />
</View>
);
2018-09-10 04:40:32 +00:00
type State = {|
viewState: WebViewState,
2018-09-10 18:13:20 +00:00
lastErrorEvent: ?WebViewError,
2018-09-10 04:40:32 +00:00
|};
2018-07-31 03:21:48 +00:00
/**
* Renders a native WebView.
*/
2018-09-11 00:34:46 +00:00
class WebView extends React.Component<WebViewSharedProps, State> {
2018-07-31 03:21:48 +00:00
static defaultProps = {
overScrollMode: 'always',
2018-07-31 03:21:48 +00:00
javaScriptEnabled: true,
thirdPartyCookiesEnabled: true,
scalesPageToFit: true,
allowFileAccess: false,
2018-07-31 03:21:48 +00:00
saveFormDataDisabled: false,
originWhitelist: defaultOriginWhitelist,
2018-07-31 03:21:48 +00:00
};
static isFileUploadSupported = async () => {
// native implementation should return "true" only for Android 5+
return NativeModules.RNCWebView.isFileUploadSupported();
}
2018-07-31 03:21:48 +00:00
state = {
viewState: this.props.startInLoadingState
? WebViewState.LOADING
: WebViewState.IDLE,
2018-09-10 18:13:20 +00:00
lastErrorEvent: null,
2018-07-31 03:21:48 +00:00
};
2018-09-18 23:24:06 +00:00
webViewRef = React.createRef();
2018-07-31 03:21:48 +00:00
render() {
let otherView = null;
if (this.state.viewState === WebViewState.LOADING) {
otherView = (this.props.renderLoading || defaultRenderLoading)();
} else if (this.state.viewState === WebViewState.ERROR) {
const errorEvent = this.state.lastErrorEvent;
2018-09-10 04:40:32 +00:00
invariant(errorEvent != null, 'lastErrorEvent expected to be non-null');
2018-07-31 03:21:48 +00:00
otherView =
this.props.renderError &&
this.props.renderError(
errorEvent.domain,
errorEvent.code,
errorEvent.description,
);
} else if (this.state.viewState !== WebViewState.IDLE) {
console.error(
'RNCWebView invalid state encountered: ' + this.state.viewState,
2018-07-31 03:21:48 +00:00
);
}
const webViewStyles = [styles.container, this.props.style];
if (
this.state.viewState === WebViewState.LOADING ||
this.state.viewState === WebViewState.ERROR
) {
// if we're in either LOADING or ERROR states, don't show the webView
webViewStyles.push(styles.hidden);
}
2018-09-10 18:13:20 +00:00
let source: WebViewSource = this.props.source || {};
2018-09-10 04:40:32 +00:00
if (!this.props.source && this.props.html) {
source = { html: this.props.html };
} else if (!this.props.source && this.props.url) {
source = { uri: this.props.url };
2018-07-31 03:21:48 +00:00
}
if (source.method === 'POST' && source.headers) {
console.warn(
'WebView: `source.headers` is not supported when using POST.',
);
} else if (source.method === 'GET' && source.body) {
console.warn('WebView: `source.body` is not supported when using GET.');
}
const nativeConfig = this.props.nativeConfig || {};
let NativeWebView = nativeConfig.component || RNCWebView;
2018-07-31 03:21:48 +00:00
const onShouldStartLoadWithRequest = createOnShouldStartLoadWithRequest(
this.onShouldStartLoadWithRequestCallback,
this.props.originWhitelist,
this.props.onShouldStartLoadWithRequest,
);
2018-07-31 03:21:48 +00:00
const webView = (
<NativeWebView
2018-09-18 23:24:06 +00:00
ref={this.webViewRef}
2018-07-31 03:21:48 +00:00
key="webViewKey"
style={webViewStyles}
source={resolveAssetSource(source)}
scalesPageToFit={this.props.scalesPageToFit}
allowFileAccess={this.props.allowFileAccess}
2018-07-31 03:21:48 +00:00
injectedJavaScript={this.props.injectedJavaScript}
userAgent={this.props.userAgent}
javaScriptEnabled={this.props.javaScriptEnabled}
thirdPartyCookiesEnabled={this.props.thirdPartyCookiesEnabled}
domStorageEnabled={this.props.domStorageEnabled}
messagingEnabled={typeof this.props.onMessage === 'function'}
onMessage={this.onMessage}
overScrollMode={this.props.overScrollMode}
2018-07-31 03:21:48 +00:00
contentInset={this.props.contentInset}
automaticallyAdjustContentInsets={
this.props.automaticallyAdjustContentInsets
}
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
2018-07-31 03:21:48 +00:00
onContentSizeChange={this.props.onContentSizeChange}
onLoadingStart={this.onLoadingStart}
onLoadingFinish={this.onLoadingFinish}
onLoadingError={this.onLoadingError}
onLoadingProgress={this.onLoadingProgress}
2018-07-31 03:21:48 +00:00
testID={this.props.testID}
geolocationEnabled={this.props.geolocationEnabled}
mediaPlaybackRequiresUserAction={
this.props.mediaPlaybackRequiresUserAction
}
allowUniversalAccessFromFileURLs={
this.props.allowUniversalAccessFromFileURLs
}
mixedContentMode={this.props.mixedContentMode}
saveFormDataDisabled={this.props.saveFormDataDisabled}
urlPrefixesForDefaultIntent={this.props.urlPrefixesForDefaultIntent}
{...nativeConfig.props}
/>
);
return (
<View style={styles.container}>
{webView}
{otherView}
</View>
);
}
goForward = () => {
UIManager.dispatchViewManagerCommand(
this.getWebViewHandle(),
UIManager.RNCWebView.Commands.goForward,
2018-07-31 03:21:48 +00:00
null,
);
};
goBack = () => {
UIManager.dispatchViewManagerCommand(
this.getWebViewHandle(),
UIManager.RNCWebView.Commands.goBack,
2018-07-31 03:21:48 +00:00
null,
);
};
reload = () => {
this.setState({
viewState: WebViewState.LOADING,
});
UIManager.dispatchViewManagerCommand(
this.getWebViewHandle(),
UIManager.RNCWebView.Commands.reload,
2018-07-31 03:21:48 +00:00
null,
);
};
stopLoading = () => {
UIManager.dispatchViewManagerCommand(
this.getWebViewHandle(),
UIManager.RNCWebView.Commands.stopLoading,
2018-07-31 03:21:48 +00:00
null,
);
};
2018-09-10 04:40:32 +00:00
postMessage = (data: string) => {
2018-07-31 03:21:48 +00:00
UIManager.dispatchViewManagerCommand(
this.getWebViewHandle(),
UIManager.RNCWebView.Commands.postMessage,
2018-07-31 03:21:48 +00:00
[String(data)],
);
};
/**
* Injects a javascript string into the referenced WebView. Deliberately does not
* return a response because using eval() to return a response breaks this method
* on pages with a Content Security Policy that disallows eval(). If you need that
* functionality, look into postMessage/onMessage.
*/
2018-09-10 04:40:32 +00:00
injectJavaScript = (data: string) => {
2018-07-31 03:21:48 +00:00
UIManager.dispatchViewManagerCommand(
this.getWebViewHandle(),
UIManager.RNCWebView.Commands.injectJavaScript,
2018-07-31 03:21:48 +00:00
[data],
);
};
/**
* We return an event with a bunch of fields including:
* url, title, loading, canGoBack, canGoForward
*/
2018-09-10 18:13:20 +00:00
updateNavigationState = (event: WebViewNavigationEvent) => {
2018-07-31 03:21:48 +00:00
if (this.props.onNavigationStateChange) {
this.props.onNavigationStateChange(event.nativeEvent);
}
};
getWebViewHandle = () => {
2018-09-18 23:24:06 +00:00
return ReactNative.findNodeHandle(this.webViewRef.current);
2018-07-31 03:21:48 +00:00
};
2018-09-10 18:13:20 +00:00
onLoadingStart = (event: WebViewNavigationEvent) => {
2018-07-31 03:21:48 +00:00
const onLoadStart = this.props.onLoadStart;
onLoadStart && onLoadStart(event);
this.updateNavigationState(event);
};
2018-09-10 18:13:20 +00:00
onLoadingError = (event: WebViewErrorEvent) => {
2018-07-31 03:21:48 +00:00
event.persist(); // persist this event because we need to store it
const { onError, onLoadEnd } = this.props;
onError && onError(event);
onLoadEnd && onLoadEnd(event);
console.warn('Encountered an error loading page', event.nativeEvent);
this.setState({
lastErrorEvent: event.nativeEvent,
viewState: WebViewState.ERROR,
});
};
2018-09-10 18:13:20 +00:00
onLoadingFinish = (event: WebViewNavigationEvent) => {
2018-07-31 03:21:48 +00:00
const { onLoad, onLoadEnd } = this.props;
onLoad && onLoad(event);
onLoadEnd && onLoadEnd(event);
this.setState({
viewState: WebViewState.IDLE,
});
this.updateNavigationState(event);
};
2018-09-10 18:13:20 +00:00
onMessage = (event: WebViewMessageEvent) => {
2018-07-31 03:21:48 +00:00
const { onMessage } = this.props;
onMessage && onMessage(event);
};
onLoadingProgress = (event: WebViewProgressEvent) => {
const { onLoadProgress } = this.props;
onLoadProgress && onLoadProgress(event);
};
onShouldStartLoadWithRequestCallback = (
shouldStart: boolean,
url: string,
) => {
if (shouldStart) {
UIManager.dispatchViewManagerCommand(
this.getWebViewHandle(),
UIManager.RNCWebView.Commands.loadUrl,
[String(url)],
);
}
};
2018-07-31 03:21:48 +00:00
}
const RNCWebView = requireNativeComponent('RNCWebView');
2018-07-31 03:21:48 +00:00
const styles = StyleSheet.create({
container: {
flex: 1,
},
hidden: {
height: 0,
flex: 0, // disable 'flex:1' when hiding a View
},
loadingView: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
loadingProgressBar: {
height: 20,
},
});
module.exports = WebView;