diff --git a/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java b/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java index 98ae73c..c075d53 100644 --- a/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java +++ b/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java @@ -109,7 +109,7 @@ public class RNCWebViewManager extends SimpleViewManager { protected static final String HTML_ENCODING = "UTF-8"; protected static final String HTML_MIME_TYPE = "text/html"; - protected static final String BRIDGE_NAME = "__REACT_WEB_VIEW_BRIDGE"; + protected static final String JAVASCRIPT_INTERFACE = "ReactNativeWebview"; protected static final String HTTP_METHOD_POST = "POST"; @@ -138,8 +138,9 @@ public class RNCWebViewManager extends SimpleViewManager { if (!mLastLoadFailed) { RNCWebView reactWebView = (RNCWebView) webView; + reactWebView.callInjectedJavaScript(); - reactWebView.linkBridge(); + emitFinishEvent(webView, url); } } @@ -239,6 +240,10 @@ public class RNCWebViewManager extends SimpleViewManager { mContext = c; } + /** + * This method is called whenever JavaScript running within the web view calls: + * - window[JAVASCRIPT_INTERFACE].postMessage + */ @JavascriptInterface public void postMessage(String message) { mContext.onMessage(message); @@ -312,11 +317,11 @@ public class RNCWebViewManager extends SimpleViewManager { } messagingEnabled = enabled; + if (enabled) { - addJavascriptInterface(createRNCWebViewBridge(this), BRIDGE_NAME); - linkBridge(); + addJavascriptInterface(createRNCWebViewBridge(this), JAVASCRIPT_INTERFACE); } else { - removeJavascriptInterface(BRIDGE_NAME); + removeJavascriptInterface(JAVASCRIPT_INTERFACE); } } @@ -342,30 +347,6 @@ public class RNCWebViewManager extends SimpleViewManager { } } - 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"); - } - } - }); - } - - evaluateJavascriptWithFallback("(" + - "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)); } diff --git a/docs/Reference.md b/docs/Reference.md index d64114b..dbfc8b6 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -196,9 +196,9 @@ Function that is invoked when the `WebView` is loading. ### `onMessage` -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`. +Function that is invoked when the webview calls `window.ReactNativeWebview.postMessage`. Setting this property will inject this global into your webview. -`window.postMessage` accepts one argument, `data`, which will be available on the event object, `event.nativeEvent.data`. `data` must be a string. +`window.ReactNativeWebview.postMessage` accepts one argument, `data`, which will be available on the event object, `event.nativeEvent.data`. `data` must be a string. | Type | Required | | -------- | -------- | diff --git a/ios/RNCUIWebView.m b/ios/RNCUIWebView.m index 1c565c9..83a64e0 100644 --- a/ios/RNCUIWebView.m +++ b/ios/RNCUIWebView.m @@ -11,7 +11,7 @@ NSString *const RNCJSNavigationScheme = @"react-js-navigation"; -static NSString *const kPostMessageHost = @"postMessage"; +static NSString *const MessageHandlerName = @"ReactNativeWebview"; @interface RNCUIWebView () @@ -86,7 +86,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) @"data": message, }; NSString *source = [NSString - stringWithFormat:@"document.dispatchEvent(new MessageEvent('message', %@));", + stringWithFormat:@"window.dispatchEvent(new MessageEvent('message', %@));", RCTJSONStringify(eventInitDict, NULL) ]; [_webView stringByEvaluatingJavaScriptFromString:source]; @@ -236,7 +236,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) } } - if (isJSNavigation && [request.URL.host isEqualToString:kPostMessageHost]) { + if (isJSNavigation && [request.URL.host isEqualToString:MessageHandlerName]) { NSString *data = request.URL.query; data = [data stringByReplacingOccurrencesOfString:@"+" withString:@" "]; data = [data stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; @@ -246,7 +246,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) @"data": data, }]; - NSString *source = @"document.dispatchEvent(new MessageEvent('message:received'));"; + NSString *source = [NSString stringWithFormat:@"window.%@.messageReceived();", MessageHandlerName]; [_webView stringByEvaluatingJavaScriptFromString:source]; @@ -289,40 +289,28 @@ 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: @"(function() {" - "window.originalPostMessage = window.postMessage;" + " var messageQueue = [];" + " var messagePending = false;" - "var messageQueue = [];" - "var messagePending = false;" + " function processQueue () {" + " if (!messageQueue.length || messagePending) return;" + " messagePending = true;" + " document.location = '%@://%@?' + encodeURIComponent(messageQueue.shift());" + " }" - "function processQueue() {" - "if (!messageQueue.length || messagePending) return;" - "messagePending = true;" - "window.location = '%@://%@?' + encodeURIComponent(messageQueue.shift());" - "}" - - "window.postMessage = function(data) {" - "messageQueue.push(String(data));" - "processQueue();" - "};" - - "document.addEventListener('message:received', function(e) {" - "messagePending = false;" - "processQueue();" - "});" - "})();", RNCJSNavigationScheme, kPostMessageHost + " window.%@ = {" + " postMessage: function (data) {" + " messageQueue.push(String(data));" + " processQueue();" + " }," + " messageReceived: function () {" + " messagePending = false;" + " processQueue();" + " }" + " };" + "})();", RNCJSNavigationScheme, MessageHandlerName, MessageHandlerName ]; [webView stringByEvaluatingJavaScriptFromString:source]; } diff --git a/ios/RNCWKWebView.m b/ios/RNCWKWebView.m index 37a468d..1abd144 100644 --- a/ios/RNCWKWebView.m +++ b/ios/RNCWKWebView.m @@ -13,7 +13,7 @@ #import "objc/runtime.h" -static NSString *const MessageHanderName = @"ReactNative"; +static NSString *const MessageHandlerName = @"ReactNativeWebview"; // runtime trick to remove WKWebView keyboard default toolbar // see: http://stackoverflow.com/questions/19033292/ios-7-uiwebview-keyboard-issue/19042279#19042279 @@ -101,7 +101,22 @@ static NSString *const MessageHanderName = @"ReactNative"; wkWebViewConfig.processPool = [[RNCWKProcessPoolManager sharedManager] sharedProcessPool]; } wkWebViewConfig.userContentController = [WKUserContentController new]; - [wkWebViewConfig.userContentController addScriptMessageHandler: self name: MessageHanderName]; + + if (_messagingEnabled) { + [wkWebViewConfig.userContentController addScriptMessageHandler:self name:MessageHandlerName]; + + NSString *source = [NSString stringWithFormat: + @"window.%@ = {" + " postMessage: function (data) {" + " window.webkit.messageHandlers.%@.postMessage(String(data));" + " }" + "};", MessageHandlerName, MessageHandlerName + ]; + + WKUserScript *script = [[WKUserScript alloc] initWithSource:source injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]; + [wkWebViewConfig.userContentController addUserScript:script]; + } + wkWebViewConfig.allowsInlineMediaPlayback = _allowsInlineMediaPlayback; #if WEBKIT_IOS_10_APIS_AVAILABLE wkWebViewConfig.mediaTypesRequiringUserActionForPlayback = _mediaPlaybackRequiresUserAction @@ -148,7 +163,7 @@ static NSString *const MessageHanderName = @"ReactNative"; - (void)removeFromSuperview { if (_webView) { - [_webView.configuration.userContentController removeScriptMessageHandlerForName:MessageHanderName]; + [_webView.configuration.userContentController removeScriptMessageHandlerForName:MessageHandlerName]; [_webView removeObserver:self forKeyPath:@"estimatedProgress"]; [_webView removeFromSuperview]; _webView = nil; @@ -184,7 +199,7 @@ static NSString *const MessageHanderName = @"ReactNative"; /** * This method is called whenever JavaScript running within the web view calls: - * - window.webkit.messageHandlers.[MessageHanderName].postMessage + * - window.webkit.messageHandlers[MessageHandlerName].postMessage */ - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message @@ -253,7 +268,6 @@ static NSString *const MessageHanderName = @"ReactNative"; -(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView { - if (_webView == nil) { _savedHideKeyboardAccessoryView = hideKeyboardAccessoryView; return; @@ -264,6 +278,7 @@ static NSString *const MessageHanderName = @"ReactNative"; } UIView* subview; + for (UIView* view in _webView.scrollView.subviews) { if([[view.class description] hasPrefix:@"WK"]) subview = view; @@ -303,10 +318,10 @@ static NSString *const MessageHanderName = @"ReactNative"; { NSDictionary *eventInitDict = @{@"data": message}; NSString *source = [NSString - stringWithFormat:@"document.dispatchEvent(new MessageEvent('message', %@));", + stringWithFormat:@"window.dispatchEvent(new MessageEvent('message', %@));", RCTJSONStringify(eventInitDict, NULL) ]; - [self evaluateJS: source thenCall: nil]; + [self injectJavaScript: source]; } - (void)layoutSubviews @@ -520,7 +535,6 @@ static NSString *const MessageHanderName = @"ReactNative"; }]; } - /** * Called when the navigation is complete. * @see https://fburl.com/rtys6jlb @@ -528,35 +542,11 @@ static NSString *const MessageHanderName = @"ReactNative"; - (void) webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { - if (_messagingEnabled) { - #if RCT_DEV - - // Implementation inspired by Lodash.isNative. - NSString *isPostMessageNative = @"String(String(window.postMessage) === String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage'))"; - [self evaluateJS: isPostMessageNative thenCall: ^(NSString *result) { - if (! [result isEqualToString:@"true"]) { - RCTLogError(@"Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined"); - } - }]; - #endif - - NSString *source = [NSString stringWithFormat: - @"(function() {" - "window.originalPostMessage = window.postMessage;" - - "window.postMessage = function(data) {" - "window.webkit.messageHandlers.%@.postMessage(String(data));" - "};" - "})();", - MessageHanderName - ]; - [self evaluateJS: source thenCall: nil]; - } - if (_injectedJavaScript) { [self evaluateJS: _injectedJavaScript thenCall: ^(NSString *jsEvaluationValue) { NSMutableDictionary *event = [self baseEvent]; event[@"jsEvaluationValue"] = jsEvaluationValue; + if (self.onLoadingFinish) { self.onLoadingFinish(event); } diff --git a/ios/RNCWebView.xcworkspace/contents.xcworkspacedata b/ios/RNCWebView.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 68c336e..0000000 --- a/ios/RNCWebView.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,8 +0,0 @@ -// !$*UTF8*$! - - - - - diff --git a/js/WebView.android.js b/js/WebView.android.js index 408ca62..defef94 100644 --- a/js/WebView.android.js +++ b/js/WebView.android.js @@ -157,9 +157,9 @@ class WebView extends React.Component { } thirdPartyCookiesEnabled={this.props.thirdPartyCookiesEnabled} domStorageEnabled={this.props.domStorageEnabled} - messagingEnabled={typeof this.props.onMessage === 'function'} cacheEnabled={this.props.cacheEnabled} onMessage={this.onMessage} + messagingEnabled={typeof this.props.onMessage === 'function'} overScrollMode={this.props.overScrollMode} contentInset={this.props.contentInset} automaticallyAdjustContentInsets={ diff --git a/js/WebView.ios.js b/js/WebView.ios.js index 27b6e3b..88adb79 100644 --- a/js/WebView.ios.js +++ b/js/WebView.ios.js @@ -232,8 +232,6 @@ class WebView extends React.Component { source = { uri: this.props.url }; } - const messagingEnabled = typeof this.props.onMessage === 'function'; - let NativeWebView = nativeConfig.component; if (this.props.useWebKit) { @@ -268,8 +266,8 @@ class WebView extends React.Component { onLoadingFinish={this._onLoadingFinish} onLoadingError={this._onLoadingError} onLoadingProgress={this._onLoadingProgress} - messagingEnabled={messagingEnabled} onMessage={this._onMessage} + messagingEnabled={typeof this.props.onMessage === 'function'} onShouldStartLoadWithRequest={onShouldStartLoadWithRequest} scalesPageToFit={scalesPageToFit} allowsInlineMediaPlayback={this.props.allowsInlineMediaPlayback} diff --git a/js/WebViewTypes.js b/js/WebViewTypes.js index 1e6f076..b4d2916 100644 --- a/js/WebViewTypes.js +++ b/js/WebViewTypes.js @@ -424,13 +424,11 @@ export type WebViewSharedProps = $ReadOnly<{| onNavigationStateChange?: (event: WebViewNavigation) => mixed, /** - * 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`. + * Function that is invoked when the webview calls `window.ReactNativeWebview.postMessage`. + * Setting this property will inject this global into your webview. * - * `window.postMessage` accepts one argument, `data`, which will be - * available on the event object, `event.nativeEvent.data`. `data` - * must be a string. + * `window.ReactNativeWebview.postMessage` accepts one argument, `data`, which will be + * available on the event object, `event.nativeEvent.data`. `data` must be a string. */ onMessage?: (event: WebViewMessageEvent) => mixed,