Files
react-native-webview-bridge/lib/WebViewBridge/index.android.js
T

380 lines
10 KiB
JavaScript
Raw Normal View History

2016-02-10 14:04:21 -05:00
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
2016-09-30 01:44:36 -04:00
* @providesModule WebView
2016-02-10 14:04:21 -05:00
*/
'use strict';
2016-09-30 01:44:36 -04:00
import React, { PropTypes } from 'react'
import {
ActivityIndicator,
2016-02-10 14:04:21 -05:00
EdgeInsetsPropType,
StyleSheet,
Text,
2016-09-30 01:44:36 -04:00
UIManager,
2016-02-10 14:04:21 -05:00
View,
2016-09-30 01:44:36 -04:00
ScrollView,
NativeModules,
2016-02-10 14:04:21 -05:00
requireNativeComponent,
2016-09-30 01:44:36 -04:00
findNodeHandle
} from 'react-native'
2016-02-10 14:04:21 -05:00
2016-09-30 01:44:36 -04:00
import invariant from 'fbjs/lib/invariant'
import keyMirror from 'fbjs/lib/keyMirror'
2016-02-10 14:04:21 -05:00
2016-09-30 01:44:36 -04:00
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
import processDecelerationRate from 'react-native/Libraries/Components/ScrollView/processDecelerationRate'
import deprecatedPropType from 'react-native/Libraries/Utilities/deprecatedPropType'
import { encode, decode } from '../core/base64'
const { WebViewBridgeManager } = NativeModules
var RCT_WEBVIEW_REF = 'webview';
var WebViewState = keyMirror({
2016-02-10 14:04:21 -05:00
IDLE: null,
LOADING: null,
ERROR: null,
});
2016-09-30 01:44:36 -04:00
var defaultRenderLoading = () => (
<View style={styles.loadingView}>
<ActivityIndicator
style={styles.loadingProgressBar}
/>
</View>
);
2016-02-10 14:04:21 -05:00
/**
* Renders a native WebView.
*/
2016-09-30 01:44:36 -04:00
class WebView extends React.Component {
static propTypes = {
...View.propTypes,
renderError: PropTypes.func,
renderLoading: PropTypes.func,
onLoad: PropTypes.func,
onLoadEnd: PropTypes.func,
onLoadStart: PropTypes.func,
onError: PropTypes.func,
automaticallyAdjustContentInsets: PropTypes.bool,
contentInset: EdgeInsetsPropType,
onNavigationStateChange: PropTypes.func,
onContentSizeChange: PropTypes.func,
startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load
style: View.propTypes.style,
2016-02-10 14:04:21 -05:00
2016-09-30 01:44:36 -04:00
html: deprecatedPropType(
PropTypes.string,
'Use the `source` prop instead.'
),
url: deprecatedPropType(
PropTypes.string,
'Use the `source` prop instead.'
),
2016-02-10 14:04:21 -05:00
/**
2016-09-30 01:44:36 -04:00
* Loads static html or a uri (with optional headers) in the WebView.
2016-02-10 14:04:21 -05:00
*/
2016-09-30 01:44:36 -04:00
source: PropTypes.oneOfType([
PropTypes.shape({
/*
* The URI to load in the WebView. Can be a local or remote file.
*/
uri: PropTypes.string,
/*
* The HTTP Method to use. Defaults to GET if not specified.
* NOTE: On Android, only GET and POST are supported.
*/
method: PropTypes.oneOf(['GET', 'POST']),
/*
* Additional HTTP headers to send with the request.
* NOTE: On Android, this can only be used with GET requests.
*/
headers: PropTypes.object,
/*
* The HTTP body to send with the request. This must be a valid
* UTF-8 string, and will be sent exactly as specified, with no
* additional encoding (e.g. URL-escaping or base64) applied.
* NOTE: On Android, this can only be used with POST requests.
*/
body: PropTypes.string,
}),
PropTypes.shape({
/*
* A static HTML page to display in the WebView.
*/
html: PropTypes.string,
/*
* The base URL to be used for any relative links in the HTML.
*/
baseUrl: PropTypes.string,
}),
/*
* Used internally by packager.
*/
PropTypes.number,
]),
/**
* Used on Android only, JS is enabled by default for WebView on iOS
* @platform android
*/
javaScriptEnabled: PropTypes.bool,
/**
* Used on Android only, controls whether DOM Storage is enabled or not
* @platform android
*/
domStorageEnabled: PropTypes.bool,
/**
* Sets the JS to be injected when the webpage loads.
*/
injectedJavaScript: PropTypes.string,
/**
* Sets whether the webpage scales to fit the view and the user can change the scale.
*/
scalesPageToFit: PropTypes.bool,
/**
* Sets the user-agent for this WebView. The user-agent can also be set in native using
* WebViewConfig. This prop will overwrite that config.
*/
userAgent: PropTypes.string,
/**
* Used to locate this view in end-to-end tests.
*/
testID: PropTypes.string,
/**
* Determines whether HTML5 audio & videos require the user to tap before they can
* start playing. The default value is `false`.
*/
mediaPlaybackRequiresUserAction: PropTypes.bool,
2016-02-10 14:04:21 -05:00
onBridgeMessage: PropTypes.func,
2016-09-30 01:44:36 -04:00
rpc: PropTypes.bool
};
2016-02-10 14:04:21 -05:00
2016-09-30 01:44:36 -04:00
static defaultProps = {
javaScriptEnabled : true,
scalesPageToFit: true,
};
2016-02-10 14:04:21 -05:00
2016-09-30 01:44:36 -04:00
state = {
viewState: WebViewState.IDLE,
lastErrorEvent: null,
startInLoadingState: true,
};
2016-02-11 16:13:50 -05:00
2016-09-30 01:44:36 -04:00
componentWillMount() {
2016-02-10 14:04:21 -05:00
if (this.props.startInLoadingState) {
2016-09-30 01:44:36 -04:00
this.setState({viewState: WebViewState.LOADING});
2016-02-10 14:04:21 -05:00
}
2016-09-30 01:44:36 -04:00
}
2016-02-10 14:04:21 -05:00
2016-09-30 01:44:36 -04:00
render() {
2016-02-10 14:04:21 -05:00
var otherView = null;
2016-09-30 01:44:36 -04:00
if (this.state.viewState === WebViewState.LOADING) {
otherView = (this.props.renderLoading || defaultRenderLoading)();
} else if (this.state.viewState === WebViewState.ERROR) {
2016-02-10 14:04:21 -05:00
var errorEvent = this.state.lastErrorEvent;
otherView = this.props.renderError && this.props.renderError(
errorEvent.domain,
errorEvent.code,
errorEvent.description);
2016-09-30 01:44:36 -04:00
} else if (this.state.viewState !== WebViewState.IDLE) {
2016-02-10 14:04:21 -05:00
console.error('RCTWebViewBridge invalid state encountered: ' + this.state.loading);
}
var webViewStyles = [styles.container, this.props.style];
2016-09-30 01:44:36 -04:00
if (this.state.viewState === WebViewState.LOADING ||
this.state.viewState === WebViewState.ERROR) {
2016-02-10 14:04:21 -05:00
// if we're in either LOADING or ERROR states, don't show the webView
webViewStyles.push(styles.hidden);
}
2016-09-30 01:44:36 -04:00
var source = this.props.source || {};
if (this.props.html) {
source.html = this.props.html;
} else if (this.props.url) {
source.uri = this.props.url;
2016-02-10 14:04:21 -05:00
}
2016-09-30 01:44:36 -04: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.');
}
2016-03-02 20:24:15 +05:30
2016-02-10 14:04:21 -05:00
var webView =
<RCTWebViewBridge
2016-09-30 01:44:36 -04:00
ref={RCT_WEBVIEW_REF}
2016-02-10 14:04:21 -05:00
key="webViewKey"
style={webViewStyles}
2016-09-30 01:44:36 -04:00
source={resolveAssetSource(source)}
scalesPageToFit={this.props.scalesPageToFit}
injectedJavaScript={this.props.injectedJavaScript}
userAgent={this.props.userAgent}
javaScriptEnabled={this.props.javaScriptEnabled}
domStorageEnabled={this.props.domStorageEnabled}
contentInset={this.props.contentInset}
automaticallyAdjustContentInsets={this.props.automaticallyAdjustContentInsets}
onContentSizeChange={this.props.onContentSizeChange}
2016-02-10 14:04:21 -05:00
onLoadingStart={this.onLoadingStart}
onLoadingFinish={this.onLoadingFinish}
onLoadingError={this.onLoadingError}
2016-09-30 01:44:36 -04:00
testID={this.props.testID}
mediaPlaybackRequiresUserAction={this.props.mediaPlaybackRequiresUserAction}
onBridgeMessage={this.onBridgeMessage}
rpc={!!this.props.rpc}
2016-02-10 14:04:21 -05:00
/>;
return (
<View style={styles.container}>
{webView}
{otherView}
</View>
);
2016-09-30 01:44:36 -04:00
}
2016-02-10 14:04:21 -05:00
2016-09-30 01:44:36 -04:00
goForward = () => {
2016-02-10 14:04:21 -05:00
UIManager.dispatchViewManagerCommand(
2016-09-30 01:44:36 -04:00
this.getWebViewHandle(),
2016-02-10 14:04:21 -05:00
UIManager.RCTWebViewBridge.Commands.goForward,
null
);
2016-09-30 01:44:36 -04:00
};
2016-02-10 14:04:21 -05:00
2016-09-30 01:44:36 -04:00
goBack = () => {
2016-02-10 14:04:21 -05:00
UIManager.dispatchViewManagerCommand(
2016-09-30 01:44:36 -04:00
this.getWebViewHandle(),
2016-02-10 14:04:21 -05:00
UIManager.RCTWebViewBridge.Commands.goBack,
null
);
2016-09-30 01:44:36 -04:00
};
2016-02-10 14:04:21 -05:00
2016-09-30 01:44:36 -04:00
reload = () => {
2016-02-10 14:04:21 -05:00
UIManager.dispatchViewManagerCommand(
2016-09-30 01:44:36 -04:00
this.getWebViewHandle(),
2016-02-10 14:04:21 -05:00
UIManager.RCTWebViewBridge.Commands.reload,
null
);
2016-09-30 01:44:36 -04:00
};
2016-02-10 14:04:21 -05:00
2016-09-30 01:44:36 -04:00
stopLoading = () => {
2016-02-11 16:13:50 -05:00
UIManager.dispatchViewManagerCommand(
2016-09-30 01:44:36 -04:00
this.getWebViewHandle(),
UIManager.RCTWebViewBridge.Commands.stopLoading,
null
2016-02-11 16:13:50 -05:00
);
2016-09-30 01:44:36 -04:00
};
2016-02-11 16:13:50 -05:00
2016-02-10 14:04:21 -05:00
/**
* We return an event with a bunch of fields including:
* url, title, loading, canGoBack, canGoForward
*/
2016-09-30 01:44:36 -04:00
updateNavigationState = (event) => {
2016-02-10 14:04:21 -05:00
if (this.props.onNavigationStateChange) {
this.props.onNavigationStateChange(event.nativeEvent);
}
2016-09-30 01:44:36 -04:00
};
2016-02-10 14:04:21 -05:00
2016-09-30 01:44:36 -04:00
onBridgeMessage = (event: Event) => {
const { onBridgeMessage } = this.props;
const messages = event.nativeEvent.messages;
if (onBridgeMessage) {
messages.forEach((message) => {
onBridgeMessage(decode(message));
});
}
};
2016-02-10 14:04:21 -05:00
2016-09-30 01:44:36 -04:00
sendToBridge = (message: any) => {
WebViewBridgeManager.sendToBridge(this.getWebViewHandle(), encode(message));
};
getWebViewHandle = () => {
return findNodeHandle(this.refs[RCT_WEBVIEW_REF]);
};
onLoadingStart = (event) => {
2016-02-10 14:04:21 -05:00
var onLoadStart = this.props.onLoadStart;
onLoadStart && onLoadStart(event);
this.updateNavigationState(event);
2016-09-30 01:44:36 -04:00
};
2016-02-10 14:04:21 -05:00
2016-09-30 01:44:36 -04:00
onLoadingError = (event) => {
2016-02-10 14:04:21 -05:00
event.persist(); // persist this event because we need to store it
var {onError, onLoadEnd} = this.props;
onError && onError(event);
onLoadEnd && onLoadEnd(event);
2016-09-30 01:44:36 -04:00
console.warn('Encountered an error loading page', event.nativeEvent);
2016-02-10 14:04:21 -05:00
this.setState({
lastErrorEvent: event.nativeEvent,
2016-09-30 01:44:36 -04:00
viewState: WebViewState.ERROR
2016-02-10 14:04:21 -05:00
});
2016-09-30 01:44:36 -04:00
};
onLoadingFinish = (event) => {
var { onLoad, onLoadEnd, rpc } = this.props;
//we need to inject WebViewBridge and RPC once the page is loaded
UIManager.dispatchViewManagerCommand(
this.getWebViewHandle(),
UIManager.RCTWebViewBridge.Commands.injectWebViewBridge,
null
);
//check if RPC falg is also on
if (rps) {
UIManager.dispatchViewManagerCommand(
this.getWebViewHandle(),
UIManager.RCTWebViewBridge.Commands.injectRPC,
null
);
}
2016-02-10 14:04:21 -05:00
onLoad && onLoad(event);
onLoadEnd && onLoadEnd(event);
this.setState({
2016-09-30 01:44:36 -04:00
viewState: WebViewState.IDLE,
2016-02-10 14:04:21 -05:00
});
this.updateNavigationState(event);
2016-09-30 01:44:36 -04:00
};
}
2016-02-10 14:04:21 -05:00
2016-09-30 01:44:36 -04:00
var RCTWebViewBridge = requireNativeComponent('RCTWebViewBridge', WebView);
2016-02-10 14:04:21 -05:00
var styles = StyleSheet.create({
container: {
flex: 1,
},
hidden: {
height: 0,
flex: 0, // disable 'flex:1' when hiding a View
},
2016-09-30 01:44:36 -04:00
loadingView: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
loadingProgressBar: {
height: 20,
},
2016-02-10 14:04:21 -05:00
});
2016-09-30 01:44:36 -04:00
module.exports = WebView;