Implement a postMessage function and an onMessage event for webviews …
Summary: JS API very similar to web workers and node's child process. Work has been done by somebody else for the Android implementation over at #7020, so we'd need to have these in sync before anything gets merged. I've made a prop `messagingEnabled` to be more explicit about creating globals—it might be sufficient to just check for an onMessage handler though. ![screen shot 2016-09-06 at 10 28 23](https://cloud.githubusercontent.com/assets/7275322/18268669/b1a12348-741c-11e6-91a1-ad39d5a8bc03.png) Closes https://github.com/facebook/react-native/pull/9762 Differential Revision: D4008260 fbshipit-source-id: 84b1afafbc0ab1edc3dfbf1a8fb870218e171a4c
This commit is contained in:
parent
6ea26c01de
commit
abb8ea3aea
|
@ -217,6 +217,53 @@ class ScaledWebView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
class MessagingTest extends React.Component {
|
||||
webview = null
|
||||
|
||||
state = {
|
||||
messagesReceivedFromWebView: 0,
|
||||
message: '',
|
||||
}
|
||||
|
||||
onMessage = e => this.setState({
|
||||
messagesReceivedFromWebView: this.state.messagesReceivedFromWebView + 1,
|
||||
message: e.nativeEvent.data,
|
||||
})
|
||||
|
||||
postMessage = () => {
|
||||
if (this.webview) {
|
||||
this.webview.postMessage('"Hello" from React Native!');
|
||||
}
|
||||
}
|
||||
|
||||
render(): ReactElement<any> {
|
||||
const {messagesReceivedFromWebView, message} = this.state;
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { height: 200 }]}>
|
||||
<View style={styles.container}>
|
||||
<Text>Messages received from web view: {messagesReceivedFromWebView}</Text>
|
||||
<Text>{message || '(No message)'}</Text>
|
||||
<View style={styles.buttons}>
|
||||
<Button text="Send Message to Web View" enabled onPress={this.postMessage} />
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.container}>
|
||||
<WebView
|
||||
ref={webview => { this.webview = webview; }}
|
||||
style={{
|
||||
backgroundColor: BGWASH,
|
||||
height: 100,
|
||||
}}
|
||||
source={require('./messagingtest.html')}
|
||||
onMessage={this.onMessage}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
|
@ -391,5 +438,9 @@ exports.examples = [
|
|||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Mesaging Test',
|
||||
render(): ReactElement<any> { return <MessagingTest />; }
|
||||
}
|
||||
];
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Messaging Test</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=320, user-scalable=no">
|
||||
</head>
|
||||
<body>
|
||||
<p>Messages recieved from React Native: 0</p>
|
||||
<p>(No messages)</p>
|
||||
<button type="button">
|
||||
Send message to React Native
|
||||
</button>
|
||||
</body>
|
||||
<script>
|
||||
var messagesReceivedFromReactNative = 0;
|
||||
document.addEventListener('message', function(e) {
|
||||
messagesReceivedFromReactNative += 1;
|
||||
document.getElementsByTagName('p')[0].innerHTML =
|
||||
'Messages recieved from React Native: ' + messagesReceivedFromReactNative;
|
||||
document.getElementsByTagName('p')[1].innerHTML = e.data;
|
||||
});
|
||||
|
||||
document.getElementsByTagName('button')[0].addEventListener('click', function() {
|
||||
window.postMessage('"Hello" from the web view');
|
||||
});
|
||||
</script>
|
||||
</html>
|
|
@ -58,6 +58,7 @@ class WebView extends React.Component {
|
|||
automaticallyAdjustContentInsets: PropTypes.bool,
|
||||
contentInset: EdgeInsetsPropType,
|
||||
onNavigationStateChange: PropTypes.func,
|
||||
onMessage: PropTypes.func,
|
||||
onContentSizeChange: PropTypes.func,
|
||||
startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load
|
||||
style: View.propTypes.style,
|
||||
|
@ -218,6 +219,8 @@ class WebView extends React.Component {
|
|||
userAgent={this.props.userAgent}
|
||||
javaScriptEnabled={this.props.javaScriptEnabled}
|
||||
domStorageEnabled={this.props.domStorageEnabled}
|
||||
messagingEnabled={typeof this.props.onMessage === 'function'}
|
||||
onMessage={this.onMessage}
|
||||
contentInset={this.props.contentInset}
|
||||
automaticallyAdjustContentInsets={this.props.automaticallyAdjustContentInsets}
|
||||
onContentSizeChange={this.props.onContentSizeChange}
|
||||
|
@ -268,6 +271,14 @@ class WebView extends React.Component {
|
|||
);
|
||||
};
|
||||
|
||||
postMessage = (data) => {
|
||||
UIManager.dispatchViewManagerCommand(
|
||||
this.getWebViewHandle(),
|
||||
UIManager.RCTWebView.Commands.postMessage,
|
||||
[String(data)]
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* We return an event with a bunch of fields including:
|
||||
* url, title, loading, canGoBack, canGoForward
|
||||
|
@ -310,9 +321,18 @@ class WebView extends React.Component {
|
|||
});
|
||||
this.updateNavigationState(event);
|
||||
};
|
||||
|
||||
onMessage = (event: Event) => {
|
||||
var {onMessage} = this.props;
|
||||
onMessage && onMessage(event);
|
||||
}
|
||||
}
|
||||
|
||||
var RCTWebView = requireNativeComponent('RCTWebView', WebView);
|
||||
var RCTWebView = requireNativeComponent('RCTWebView', WebView, {
|
||||
nativeOnly: {
|
||||
messagingEnabled: PropTypes.bool,
|
||||
},
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
container: {
|
||||
|
|
|
@ -235,6 +235,16 @@ class WebView extends React.Component {
|
|||
* Function that is invoked when the `WebView` loading starts or ends.
|
||||
*/
|
||||
onNavigationStateChange: PropTypes.func,
|
||||
/**
|
||||
* A function that is invoked when the webview calls `window.postMessage`.
|
||||
* Setting this property will inject a `postMessage` global into your
|
||||
* webview, but will still call pre-existing values of `postMessage`.
|
||||
*
|
||||
* `window.postMessage` accepts one argument, `data`, which will be
|
||||
* available on the event object, `event.nativeEvent.data`. `data`
|
||||
* must be a string.
|
||||
*/
|
||||
onMessage: PropTypes.func,
|
||||
/**
|
||||
* Boolean value that forces the `WebView` to show the loading view
|
||||
* on the first load.
|
||||
|
@ -382,6 +392,8 @@ class WebView extends React.Component {
|
|||
source.uri = this.props.url;
|
||||
}
|
||||
|
||||
const messagingEnabled = typeof this.props.onMessage === 'function';
|
||||
|
||||
var webView =
|
||||
<RCTWebView
|
||||
ref={RCT_WEBVIEW_REF}
|
||||
|
@ -397,6 +409,8 @@ class WebView extends React.Component {
|
|||
onLoadingStart={this._onLoadingStart}
|
||||
onLoadingFinish={this._onLoadingFinish}
|
||||
onLoadingError={this._onLoadingError}
|
||||
messagingEnabled={messagingEnabled}
|
||||
onMessage={this._onMessage}
|
||||
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
|
||||
scalesPageToFit={this.props.scalesPageToFit}
|
||||
allowsInlineMediaPlayback={this.props.allowsInlineMediaPlayback}
|
||||
|
@ -457,6 +471,24 @@ class WebView extends React.Component {
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Posts a message to the web view, which will emit a `message` event.
|
||||
* Accepts one argument, `data`, which must be a string.
|
||||
*
|
||||
* In your webview, you'll need to something like the following.
|
||||
*
|
||||
* ```js
|
||||
* document.addEventListener('message', e => { document.title = e.data; });
|
||||
* ```
|
||||
*/
|
||||
postMessage = (data) => {
|
||||
UIManager.dispatchViewManagerCommand(
|
||||
this.getWebViewHandle(),
|
||||
UIManager.RCTWebView.Commands.postMessage,
|
||||
[String(data)]
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* We return an event with a bunch of fields including:
|
||||
* url, title, loading, canGoBack, canGoForward
|
||||
|
@ -502,6 +534,11 @@ class WebView extends React.Component {
|
|||
});
|
||||
this._updateNavigationState(event);
|
||||
};
|
||||
|
||||
_onMessage = (event: Event) => {
|
||||
var {onMessage} = this.props;
|
||||
onMessage && onMessage(event);
|
||||
}
|
||||
}
|
||||
|
||||
var RCTWebView = requireNativeComponent('RCTWebView', WebView, {
|
||||
|
@ -509,6 +546,8 @@ var RCTWebView = requireNativeComponent('RCTWebView', WebView, {
|
|||
onLoadingStart: true,
|
||||
onLoadingError: true,
|
||||
onLoadingFinish: true,
|
||||
onMessage: true,
|
||||
messagingEnabled: PropTypes.bool,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
|
|||
@property (nonatomic, copy) NSDictionary *source;
|
||||
@property (nonatomic, assign) UIEdgeInsets contentInset;
|
||||
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
|
||||
@property (nonatomic, assign) BOOL messagingEnabled;
|
||||
@property (nonatomic, copy) NSString *injectedJavaScript;
|
||||
@property (nonatomic, assign) BOOL scalesPageToFit;
|
||||
|
||||
|
@ -41,5 +42,6 @@ shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
|
|||
- (void)goBack;
|
||||
- (void)reload;
|
||||
- (void)stopLoading;
|
||||
- (void)postMessage:(NSString *)message;
|
||||
|
||||
@end
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#import "UIView+React.h"
|
||||
|
||||
NSString *const RCTJSNavigationScheme = @"react-js-navigation";
|
||||
NSString *const RCTJSPostMessageHost = @"postMessage";
|
||||
|
||||
@interface RCTWebView () <UIWebViewDelegate, RCTAutoInsetsProtocol>
|
||||
|
||||
|
@ -27,6 +28,7 @@ NSString *const RCTJSNavigationScheme = @"react-js-navigation";
|
|||
@property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onLoadingError;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest;
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onMessage;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -82,6 +84,18 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
|||
[_webView stopLoading];
|
||||
}
|
||||
|
||||
- (void)postMessage:(NSString *)message
|
||||
{
|
||||
NSDictionary *eventInitDict = @{
|
||||
@"data": message,
|
||||
};
|
||||
NSString *source = [NSString
|
||||
stringWithFormat:@"document.dispatchEvent(new MessageEvent('message', %@));",
|
||||
RCTJSONStringify(eventInitDict, NULL)
|
||||
];
|
||||
[_webView stringByEvaluatingJavaScriptFromString:source];
|
||||
}
|
||||
|
||||
- (void)setSource:(NSDictionary *)source
|
||||
{
|
||||
if (![_source isEqualToDictionary:source]) {
|
||||
|
@ -221,6 +235,18 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
|||
}
|
||||
}
|
||||
|
||||
if (isJSNavigation && [request.URL.host isEqualToString:RCTJSPostMessageHost]) {
|
||||
NSString *data = request.URL.query;
|
||||
data = [data stringByReplacingOccurrencesOfString:@"+" withString:@" "];
|
||||
data = [data stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
NSMutableDictionary<NSString *, id> *event = [self baseEvent];
|
||||
[event addEntriesFromDictionary: @{
|
||||
@"data": data,
|
||||
}];
|
||||
_onMessage(event);
|
||||
}
|
||||
|
||||
// JS Navigation handler
|
||||
return !isJSNavigation;
|
||||
}
|
||||
|
@ -248,6 +274,26 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
|||
|
||||
- (void)webViewDidFinishLoad:(UIWebView *)webView
|
||||
{
|
||||
if (_messagingEnabled) {
|
||||
#if RCT_DEV
|
||||
// See isNative in lodash
|
||||
NSString *testPostMessageNative = @"String(window.postMessage) === String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage')";
|
||||
BOOL postMessageIsNative = [
|
||||
[webView stringByEvaluatingJavaScriptFromString:testPostMessageNative]
|
||||
isEqualToString:@"true"
|
||||
];
|
||||
if (!postMessageIsNative) {
|
||||
RCTLogError(@"Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined");
|
||||
}
|
||||
#endif
|
||||
NSString *source = [NSString stringWithFormat:
|
||||
@"window.originalPostMessage = window.postMessage;"
|
||||
"window.postMessage = function(data) {"
|
||||
"window.location = '%@://%@?' + encodeURIComponent(String(data));"
|
||||
"};", RCTJSNavigationScheme, RCTJSPostMessageHost
|
||||
];
|
||||
[webView stringByEvaluatingJavaScriptFromString:source];
|
||||
}
|
||||
if (_injectedJavaScript != nil) {
|
||||
NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString:_injectedJavaScript];
|
||||
|
||||
|
|
|
@ -38,12 +38,14 @@ RCT_REMAP_VIEW_PROPERTY(bounces, _webView.scrollView.bounces, BOOL)
|
|||
RCT_REMAP_VIEW_PROPERTY(scrollEnabled, _webView.scrollView.scrollEnabled, BOOL)
|
||||
RCT_REMAP_VIEW_PROPERTY(decelerationRate, _webView.scrollView.decelerationRate, CGFloat)
|
||||
RCT_EXPORT_VIEW_PROPERTY(scalesPageToFit, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(messagingEnabled, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
|
||||
RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onMessage, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock)
|
||||
RCT_REMAP_VIEW_PROPERTY(allowsInlineMediaPlayback, _webView.allowsInlineMediaPlayback, BOOL)
|
||||
RCT_REMAP_VIEW_PROPERTY(mediaPlaybackRequiresUserAction, _webView.mediaPlaybackRequiresUserAction, BOOL)
|
||||
|
@ -97,6 +99,18 @@ RCT_EXPORT_METHOD(stopLoading:(nonnull NSNumber *)reactTag)
|
|||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(postMessage:(nonnull NSNumber *)reactTag message:(NSString *)message)
|
||||
{
|
||||
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWebView *> *viewRegistry) {
|
||||
RCTWebView *view = viewRegistry[reactTag];
|
||||
if (![view isKindOfClass:[RCTWebView class]]) {
|
||||
RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
|
||||
} else {
|
||||
[view postMessage:message];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Exported synchronous methods
|
||||
|
||||
- (BOOL)webView:(__unused RCTWebView *)webView
|
||||
|
|
|
@ -77,6 +77,7 @@ import com.facebook.react.uimanager.events.TouchEventType;
|
|||
.put("topLoadingFinish", MapBuilder.of("registrationName", "onLoadingFinish"))
|
||||
.put("topLoadingStart", MapBuilder.of("registrationName", "onLoadingStart"))
|
||||
.put("topSelectionChange", MapBuilder.of("registrationName", "onSelectionChange"))
|
||||
.put("topMessage", MapBuilder.of("registrationName", "onMessage"))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ android_library(
|
|||
name = 'webview',
|
||||
srcs = glob(['**/*.java']),
|
||||
deps = [
|
||||
react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
|
||||
react_native_target('java/com/facebook/react/bridge:bridge'),
|
||||
react_native_target('java/com/facebook/react/uimanager:uimanager'),
|
||||
react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
|
||||
|
|
|
@ -26,7 +26,11 @@ import android.webkit.GeolocationPermissions;
|
|||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.webkit.JavascriptInterface;
|
||||
import android.webkit.ValueCallback;
|
||||
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.LifecycleEventListener;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
|
@ -46,6 +50,10 @@ import com.facebook.react.uimanager.events.EventDispatcher;
|
|||
import com.facebook.react.views.webview.events.TopLoadingErrorEvent;
|
||||
import com.facebook.react.views.webview.events.TopLoadingFinishEvent;
|
||||
import com.facebook.react.views.webview.events.TopLoadingStartEvent;
|
||||
import com.facebook.react.views.webview.events.TopMessageEvent;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONException;
|
||||
|
||||
/**
|
||||
* Manages instances of {@link WebView}
|
||||
|
@ -74,6 +82,7 @@ public class ReactWebViewManager extends SimpleViewManager<WebView> {
|
|||
|
||||
private static final String HTML_ENCODING = "UTF-8";
|
||||
private static final String HTML_MIME_TYPE = "text/html; charset=utf-8";
|
||||
private static final String BRIDGE_NAME = "__REACT_WEB_VIEW_BRIDGE";
|
||||
|
||||
private static final String HTTP_METHOD_POST = "POST";
|
||||
|
||||
|
@ -81,6 +90,7 @@ public class ReactWebViewManager extends SimpleViewManager<WebView> {
|
|||
public static final int COMMAND_GO_FORWARD = 2;
|
||||
public static final int COMMAND_RELOAD = 3;
|
||||
public static final int COMMAND_STOP_LOADING = 4;
|
||||
public static final int COMMAND_POST_MESSAGE = 5;
|
||||
|
||||
// Use `webView.loadUrl("about:blank")` to reliably reset the view
|
||||
// state and release page resources (including any running JavaScript).
|
||||
|
@ -100,6 +110,7 @@ public class ReactWebViewManager extends SimpleViewManager<WebView> {
|
|||
if (!mLastLoadFailed) {
|
||||
ReactWebView reactWebView = (ReactWebView) webView;
|
||||
reactWebView.callInjectedJavaScript();
|
||||
reactWebView.linkBridge();
|
||||
emitFinishEvent(webView, url);
|
||||
}
|
||||
}
|
||||
|
@ -190,6 +201,20 @@ public class ReactWebViewManager extends SimpleViewManager<WebView> {
|
|||
*/
|
||||
private static class ReactWebView extends WebView implements LifecycleEventListener {
|
||||
private @Nullable String injectedJS;
|
||||
private boolean messagingEnabled = false;
|
||||
|
||||
private class ReactWebViewBridge {
|
||||
ReactWebView mContext;
|
||||
|
||||
ReactWebViewBridge(ReactWebView c) {
|
||||
mContext = c;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void postMessage(String message) {
|
||||
mContext.onMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebView must be created with an context of the current activity
|
||||
|
@ -221,6 +246,20 @@ public class ReactWebViewManager extends SimpleViewManager<WebView> {
|
|||
injectedJS = js;
|
||||
}
|
||||
|
||||
public void setMessagingEnabled(boolean enabled) {
|
||||
if (messagingEnabled == enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
messagingEnabled = enabled;
|
||||
if (enabled) {
|
||||
addJavascriptInterface(new ReactWebViewBridge(this), BRIDGE_NAME);
|
||||
linkBridge();
|
||||
} else {
|
||||
removeJavascriptInterface(BRIDGE_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
public void callInjectedJavaScript() {
|
||||
if (getSettings().getJavaScriptEnabled() &&
|
||||
injectedJS != null &&
|
||||
|
@ -229,6 +268,34 @@ public class ReactWebViewManager extends SimpleViewManager<WebView> {
|
|||
}
|
||||
}
|
||||
|
||||
public void linkBridge() {
|
||||
if (messagingEnabled) {
|
||||
if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
// See isNative in lodash
|
||||
String testPostMessageNative = "String(window.postMessage) === String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage')";
|
||||
evaluateJavascript(testPostMessageNative, new ValueCallback<String>() {
|
||||
@Override
|
||||
public void onReceiveValue(String value) {
|
||||
if (value.equals("true")) {
|
||||
FLog.w(ReactConstants.TAG, "Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadUrl("javascript:(" +
|
||||
"window.originalPostMessage = window.postMessage," +
|
||||
"window.postMessage = function(data) {" +
|
||||
BRIDGE_NAME + ".postMessage(String(data));" +
|
||||
"}" +
|
||||
")");
|
||||
}
|
||||
}
|
||||
|
||||
public void onMessage(String message) {
|
||||
dispatchEvent(this, new TopMessageEvent(this.getId(), message));
|
||||
}
|
||||
|
||||
private void cleanupCallbacksAndDestroy() {
|
||||
setWebViewClient(null);
|
||||
destroy();
|
||||
|
@ -310,6 +377,11 @@ public class ReactWebViewManager extends SimpleViewManager<WebView> {
|
|||
((ReactWebView) view).setInjectedJavaScript(injectedJavaScript);
|
||||
}
|
||||
|
||||
@ReactProp(name = "messagingEnabled")
|
||||
public void setMessagingEnabled(WebView view, boolean enabled) {
|
||||
((ReactWebView) view).setMessagingEnabled(enabled);
|
||||
}
|
||||
|
||||
@ReactProp(name = "source")
|
||||
public void setSource(WebView view, @Nullable ReadableMap source) {
|
||||
if (source != null) {
|
||||
|
@ -385,7 +457,8 @@ public class ReactWebViewManager extends SimpleViewManager<WebView> {
|
|||
"goBack", COMMAND_GO_BACK,
|
||||
"goForward", COMMAND_GO_FORWARD,
|
||||
"reload", COMMAND_RELOAD,
|
||||
"stopLoading", COMMAND_STOP_LOADING);
|
||||
"stopLoading", COMMAND_STOP_LOADING,
|
||||
"postMessage", COMMAND_POST_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -403,6 +476,15 @@ public class ReactWebViewManager extends SimpleViewManager<WebView> {
|
|||
case COMMAND_STOP_LOADING:
|
||||
root.stopLoading();
|
||||
break;
|
||||
case COMMAND_POST_MESSAGE:
|
||||
try {
|
||||
JSONObject eventInitDict = new JSONObject();
|
||||
eventInitDict.put("data", args.getString(0));
|
||||
root.loadUrl("javascript:(document.dispatchEvent(new MessageEvent('message', " + eventInitDict.toString() + ")))");
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.webview.events;
|
||||
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
|
||||
/**
|
||||
* Event emitted when there is an error in loading.
|
||||
*/
|
||||
public class TopMessageEvent extends Event<TopMessageEvent> {
|
||||
|
||||
public static final String EVENT_NAME = "topMessage";
|
||||
private final String mData;
|
||||
|
||||
public TopMessageEvent(int viewId, String data) {
|
||||
super(viewId);
|
||||
mData = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return EVENT_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canCoalesce() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getCoalescingKey() {
|
||||
// All events for a given view can be coalesced.
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
WritableMap data = Arguments.createMap();
|
||||
data.putString("data", mData);
|
||||
rctEventEmitter.receiveEvent(getViewTag(), EVENT_NAME, data);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue