diff --git a/Examples/UIExplorer/js/messagingtest.html b/Examples/UIExplorer/js/messagingtest.html index c9258dbf5104..61300ec6f789 100644 --- a/Examples/UIExplorer/js/messagingtest.html +++ b/Examples/UIExplorer/js/messagingtest.html @@ -15,6 +15,7 @@ diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js index 7961d777f7fc..0d6ae00c3de3 100644 --- a/Libraries/Components/WebView/WebView.ios.js +++ b/Libraries/Components/WebView/WebView.ios.js @@ -236,13 +236,17 @@ class WebView extends React.Component { */ 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`. + * A function that will be invoked when the webview calls `window.postMessage`. + * Setting this property will alter `postMessage` to allow posting to React + * Native. `postMessage accepts the arguments `data`, `targetOrigin`, and + * `transfer`. * - * `window.postMessage` accepts one argument, `data`, which will be - * available on the event object, `event.nativeEvent.data`. `data` - * must be a string. + * Calling `postMessage` with a `targetOrigin` of `react-native` will post + * to React Native. All other values for `targetOrigin` will use the default + * HTML5 behaviour. + * + * Note that when posting to React Native, `data` must be a string. The data + * can be retrieved from this handler with `event.nativeEvent.data`. */ onMessage: PropTypes.func, /** @@ -475,10 +479,15 @@ 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. * + * The message event will have the properties `data` and `origin`, where + * `data` is the string provided to `postMessage` and `origin` is `react-native` + * * In your webview, you'll need to something like the following. * * ```js - * document.addEventListener('message', e => { document.title = e.data; }); + * document.addEventListener('message', e => { + * if (e.origin === 'react-native') doSomethingWith(e.data); + * }); * ``` */ postMessage = (data) => { diff --git a/React/Views/RCTWebView.m b/React/Views/RCTWebView.m index 23fe37b84dca..64d8d12424c7 100644 --- a/React/Views/RCTWebView.m +++ b/React/Views/RCTWebView.m @@ -87,10 +87,15 @@ - (void)stopLoading - (void)postMessage:(NSString *)message { NSDictionary *eventInitDict = @{ + @"origin": @"react-native", @"data": message, }; NSString *source = [NSString - stringWithFormat:@"document.dispatchEvent(new MessageEvent('message', %@));", + stringWithFormat: + @"document.dispatchEvent(new MessageEvent('message', %@));" + "if (window.native && typeof window.native.onmessage === 'function') {" + "window.native.onmessage(new MessageEvent('message', %@))" + "}", RCTJSONStringify(eventInitDict, NULL) ]; [_webView stringByEvaluatingJavaScriptFromString:source]; @@ -275,21 +280,20 @@ - (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)er - (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));" + @"window.native = {" + "postMessage: function(data) {" + "window.location = '%@://%@?' + encodeURIComponent(String(data));" + "}," + "onmessage: null" + "};" + "window.originalPostMessage = window.postMessage;" + "window.postMessage = function(data, targetOrigin) {" + "if (targetOrigin === 'react-native') {" + "window.location = '%@://%@?' + encodeURIComponent(String(data));" + "} else {" + "window.originalPostMessage.apply(arguments);" + "}" "};", RCTJSNavigationScheme, RCTJSPostMessageHost ]; [webView stringByEvaluatingJavaScriptFromString:source]; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java index 1a80722fe00b..b33e84ea5581 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java @@ -273,23 +273,20 @@ public void callInjectedJavaScript() { 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() { - @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.native = {" + + "postMessage: function(data) {" + + BRIDGE_NAME + ".postMessage(String(data));" + + "}," + + "onmessage: null" + + "}," + "window.originalPostMessage = window.postMessage," + - "window.postMessage = function(data) {" + - BRIDGE_NAME + ".postMessage(String(data));" + + "window.postMessage = function(data, targetOrigin) {" + + "if (targetOrigin === 'react-native') {" + + BRIDGE_NAME + ".postMessage(String(data));" + + "} else {" + + "window.originalPostMessage.apply(arguments);" + + "}" + "}" + ")"); } @@ -493,8 +490,13 @@ public void receiveCommand(WebView root, int commandId, @Nullable ReadableArray case COMMAND_POST_MESSAGE: try { JSONObject eventInitDict = new JSONObject(); + eventInitDict.put("origin", "react-native"); eventInitDict.put("data", args.getString(0)); - root.loadUrl("javascript:(document.dispatchEvent(new MessageEvent('message', " + eventInitDict.toString() + ")))"); + root.loadUrl("javascript:(" + + "document.dispatchEvent(new MessageEvent('message', " + eventInitDict.toString() + "))," + + "window.native && typeof window.native.onmessage === 'function' &&" + + "window.native.onmessage(new MessageEvent('message', " + eventInitDict.toString() + "))" + + ")"); } catch (JSONException e) { throw new RuntimeException(e); }