diff --git a/ios/RCTWebViewBridge.h b/ios/RCTWebViewBridge.h index 09006303..c7d34222 100644 --- a/ios/RCTWebViewBridge.h +++ b/ios/RCTWebViewBridge.h @@ -10,7 +10,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import "RCTView.h" +#import @class RCTWebViewBridge; diff --git a/ios/RCTWebViewBridge.m b/ios/RCTWebViewBridge.m index ce7868c4..12702332 100644 --- a/ios/RCTWebViewBridge.m +++ b/ios/RCTWebViewBridge.m @@ -14,14 +14,16 @@ #import -#import "RCTAutoInsetsProtocol.h" -#import "RCTConvert.h" -#import "RCTEventDispatcher.h" -#import "RCTLog.h" -#import "RCTUtils.h" -#import "RCTView.h" -#import "UIView+React.h" + +#import +#import +#import +#import +#import + +#import #import +#import //This is a very elegent way of defining multiline string in objective-c. //source: http://stackoverflow.com/a/23387659/828487 @@ -41,7 +43,7 @@ -(id)inputAccessoryView } @end -@interface RCTWebViewBridge () +@interface RCTWebViewBridge () @property (nonatomic, copy) RCTDirectEventBlock onLoadingStart; @property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish; @@ -53,8 +55,9 @@ @interface RCTWebViewBridge () @implementation RCTWebViewBridge { - UIWebView *_webView; + WKWebView *_webView; NSString *_injectedJavaScript; + bool _shouldTrackLoadingStart; } - (instancetype)initWithFrame:(CGRect)frame @@ -63,8 +66,8 @@ - (instancetype)initWithFrame:(CGRect)frame super.backgroundColor = [UIColor clearColor]; _automaticallyAdjustContentInsets = YES; _contentInset = UIEdgeInsetsZero; - _webView = [[UIWebView alloc] initWithFrame:self.bounds]; - _webView.delegate = self; + _shouldTrackLoadingStart = NO; + [self setupWebview]; [self addSubview:_webView]; } return self; @@ -100,12 +103,16 @@ - (void)sendToBridge:(NSString *)message ); NSString *command = [NSString stringWithFormat: format, message]; - [_webView stringByEvaluatingJavaScriptFromString:command]; + [_webView evaluateJavaScript:command completionHandler:^(id result, NSError * _Nullable error) { + if (error) { + NSLog(@"WKWebview sendToBridge evaluateJavaScript Error: %@", error); + } + }]; } - (NSURL *)URL { - return _webView.request.URL; + return _webView.URL; } - (void)setSource:(NSDictionary *)source @@ -126,7 +133,7 @@ - (void)setSource:(NSDictionary *)source // passing the redirect urls back here, so we ignore them if trying to load // the same url. We'll expose a call to 'reload' to allow a user to load // the existing page. - if ([request.URL isEqual:_webView.request.URL]) { + if ([request.URL isEqual:_webView.URL]) { return; } if (!request.URL) { @@ -167,9 +174,9 @@ - (UIColor *)backgroundColor - (NSMutableDictionary *)baseEvent { NSMutableDictionary *event = [[NSMutableDictionary alloc] initWithDictionary:@{ - @"url": _webView.request.URL.absoluteString ?: @"", + @"url": _webView.URL.absoluteString ?: @"", @"loading" : @(_webView.loading), - @"title": [_webView stringByEvaluatingJavaScriptFromString:@"document.title"], + @"title": _webView.title, @"canGoBack": @(_webView.canGoBack), @"canGoForward" : @(_webView.canGoForward), }]; @@ -215,58 +222,80 @@ -(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView object_setClass(subview, newClass); } -#pragma mark - UIWebViewDelegate methods +#pragma mark - WebKit WebView Setup and JS Handler -- (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request - navigationType:(UIWebViewNavigationType)navigationType -{ - BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme]; +-(void)setupWebview { + WKWebViewConfiguration *theConfiguration = [[WKWebViewConfiguration alloc] init]; + WKUserContentController *controller = [[WKUserContentController alloc]init]; + [controller addScriptMessageHandler:self name:@"observe"]; + + [theConfiguration setUserContentController:controller]; + theConfiguration.allowsInlineMediaPlayback = NO; - if (!isJSNavigation && [request.URL.scheme isEqualToString:RCTWebViewBridgeSchema]) { - NSString* message = [webView stringByEvaluatingJavaScriptFromString:@"WebViewBridge.__fetch__()"]; + _webView = [[WKWebView alloc] initWithFrame:self.bounds configuration:theConfiguration]; + _webView.UIDelegate = self; + _webView.navigationDelegate = self; + [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways]; +} + +-(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{ + if ([message.body rangeOfString:RCTWebViewBridgeSchema].location == NSNotFound) { NSMutableDictionary *onBridgeMessageEvent = [[NSMutableDictionary alloc] initWithDictionary:@{ - @"messages": [self stringArrayJsonToArray: message] + @"messages": [self stringArrayJsonToArray: message.body] }]; _onBridgeMessage(onBridgeMessageEvent); - isJSNavigation = YES; + return; } - // skip this for the JS Navigation handler - if (!isJSNavigation && _onShouldStartLoadWithRequest) { + [_webView evaluateJavaScript:@"WebViewBridge.__fetch__()" completionHandler:^(id result, NSError * _Nullable error) { + if (!error) { + NSMutableDictionary *onBridgeMessageEvent = [[NSMutableDictionary alloc] initWithDictionary:@{ + @"messages": [self stringArrayJsonToArray: result] + }]; + + _onBridgeMessage(onBridgeMessageEvent); + } + }]; +} + +#pragma mark - WebKit WebView Delegate methods + +- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation +{ + _shouldTrackLoadingStart = YES; +} + +-(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{ + if (_onLoadingStart && _shouldTrackLoadingStart) { + _shouldTrackLoadingStart = NO; NSMutableDictionary *event = [self baseEvent]; [event addEntriesFromDictionary: @{ - @"url": (request.URL).absoluteString, - @"navigationType": @(navigationType) + @"url": (navigationAction.request.URL).absoluteString, + @"navigationType": @(navigationAction.navigationType) }]; - if (![self.delegate webView:self - shouldStartLoadForRequest:event - withCallback:_onShouldStartLoadWithRequest]) { - return NO; - } + _onLoadingStart(event); } - if (_onLoadingStart) { - // We have this check to filter out iframe requests and whatnot - BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL]; - if (isTopFrame) { - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary: @{ - @"url": (request.URL).absoluteString, - @"navigationType": @(navigationType) - }]; - _onLoadingStart(event); + if (_onShouldStartLoadWithRequest) { + NSMutableDictionary *event = [self baseEvent]; + [event addEntriesFromDictionary: @{ + @"url": (navigationAction.request.URL).absoluteString, + @"navigationType": @(navigationAction.navigationType) + }]; + + if (![self.delegate webView:self shouldStartLoadForRequest:event withCallback:_onShouldStartLoadWithRequest]) { + decisionHandler(WKNavigationActionPolicyCancel); + }else{ + decisionHandler(WKNavigationActionPolicyAllow); } } - - // JS Navigation handler - return !isJSNavigation; + decisionHandler(WKNavigationActionPolicyAllow); } -- (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)error -{ +-(void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error{ if (_onLoadingError) { if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { // NSURLErrorCancelled is reported when a page has a redirect OR if you load @@ -286,27 +315,25 @@ - (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)er } } -- (void)webViewDidFinishLoad:(UIWebView *)webView -{ - //injecting WebViewBridge Script +-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{ NSString *webViewBridgeScriptContent = [self webViewBridgeScript]; - [webView stringByEvaluatingJavaScriptFromString:webViewBridgeScriptContent]; - ////////////////////////////////////////////////////////////////////////////// - - if (_injectedJavaScript != nil) { - NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString:_injectedJavaScript]; + [webView evaluateJavaScript:webViewBridgeScriptContent completionHandler:^(id result, NSError * _Nullable error) { + _onLoadingFinish([self baseEvent]); + }]; +} - NSMutableDictionary *event = [self baseEvent]; - event[@"jsEvaluationValue"] = jsEvaluationValue; +- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures +{ - _onLoadingFinish(event); - } - // we only need the final 'finishLoad' call so only fire the event when we're actually done loading. - else if (_onLoadingFinish && !webView.loading && ![webView.request.URL.absoluteString isEqualToString:@"about:blank"]) { - _onLoadingFinish([self baseEvent]); + if (!navigationAction.targetFrame.isMainFrame) { + [webView loadRequest:navigationAction.request]; } + + return nil; } +#pragma mark - WebviewBridge helpers + - (NSArray*)stringArrayJsonToArray:(NSString *)message { return [NSJSONSerialization JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding] @@ -326,8 +353,6 @@ - (NSString *)webViewBridgeScript { return NSStringMultiline( (function (window) { - 'use strict'; - //Make sure that if WebViewBridge already in scope we don't override it. if (window.WebViewBridge) { return; @@ -339,6 +364,29 @@ - (NSString *)webViewBridgeScript { var doc = window.document; var customEvent = doc.createEvent('Event'); + function wkWebViewBridgeAvailable() { + return ( + window.webkit && + window.webkit.messageHandlers && + window.webkit.messageHandlers.observe && + window.webkit.messageHandlers.observe.postMessage + ); + } + + function wkWebViewSend(event) { + if (!wkWebViewBridgeAvailable()) { + return; + } + try { + window.webkit.messageHandlers.observe.postMessage(event); + } catch (e) { + console.error('wkWebViewSend error', e.message); + if (window.WebViewBridge.onError) { + window.WebViewBridge.onError(e); + } + } + } + function callFunc(func, message) { if ('function' === typeof func) { func(message); @@ -346,7 +394,12 @@ function callFunc(func, message) { } function signalNative() { - window.location = RNWBSchema + '://message' + new Date().getTime(); + if (wkWebViewBridgeAvailable()) { + var event = window.WebViewBridge.__fetch__(); + wkWebViewSend(event); + } else { // iOS UIWebview + window.location = RNWBSchema + '://message' + new Date().getTime(); + } } //I made the private function ugly signiture so user doesn't called them accidently. @@ -396,7 +449,7 @@ function signalNative() { //dispatch event customEvent.initEvent('WebViewBridge', true, true); doc.dispatchEvent(customEvent); - }(window)); + })(this); ); } diff --git a/ios/RCTWebViewBridgeManager.h b/ios/RCTWebViewBridgeManager.h index e0659a8d..00578f3f 100644 --- a/ios/RCTWebViewBridgeManager.h +++ b/ios/RCTWebViewBridgeManager.h @@ -10,7 +10,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import "RCTViewManager.h" +#import @interface RCTWebViewBridgeManager : RCTViewManager diff --git a/ios/RCTWebViewBridgeManager.m b/ios/RCTWebViewBridgeManager.m index ad15e14b..1b9b9016 100644 --- a/ios/RCTWebViewBridgeManager.m +++ b/ios/RCTWebViewBridgeManager.m @@ -11,11 +11,12 @@ */ #import "RCTWebViewBridgeManager.h" - -#import "RCTBridge.h" -#import "RCTUIManager.h" #import "RCTWebViewBridge.h" -#import "UIView+React.h" + +#import +#import + +#import @interface RCTWebViewBridgeManager ()