From a997c0ac16d8863333d057269a8b5e28994b84eb Mon Sep 17 00:00:00 2001 From: Ramanpreet Nara Date: Thu, 16 Aug 2018 13:34:16 -0700 Subject: [PATCH] Implement 'onShouldStartLoadWithRequest' prop Summary: @public This diff introduces the native backend for a new WKWebView prop: `onShouldStartLoadWithRequest`. In the final component, the behaviour will be as follows: Whenever the user navigates around in the web view, we call `onShouldStartLoadWithRequest` with the navigation event. If `onShouldStartLoadWithRequest` returns `true`, we continue the navigation. Otherwise, we abort it. Reviewed By: shergin Differential Revision: D6370317 fbshipit-source-id: e3cdd7e2a755125aebdb6df67e7b39116228bdfb --- React/Views/RCTWKWebView.h | 5 ++++ React/Views/RCTWKWebView.m | 15 ++++++++++ React/Views/RCTWKWebViewManager.m | 46 ++++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/React/Views/RCTWKWebView.h b/React/Views/RCTWKWebView.h index 4e749d59c..cb4971537 100644 --- a/React/Views/RCTWKWebView.h +++ b/React/Views/RCTWKWebView.h @@ -12,6 +12,11 @@ @class RCTWKWebView; @protocol RCTWKWebViewDelegate + +- (BOOL)webView:(RCTWKWebView *)webView +shouldStartLoadForRequest:(NSMutableDictionary *)request + withCallback:(RCTDirectEventBlock)callback; + @end @interface RCTWKWebView : RCTView diff --git a/React/Views/RCTWKWebView.m b/React/Views/RCTWKWebView.m index b33dffefe..15fdd844e 100644 --- a/React/Views/RCTWKWebView.m +++ b/React/Views/RCTWKWebView.m @@ -9,6 +9,7 @@ static NSString *const MessageHanderName = @"ReactNative"; @property (nonatomic, copy) RCTDirectEventBlock onLoadingStart; @property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish; @property (nonatomic, copy) RCTDirectEventBlock onLoadingError; +@property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest; @property (nonatomic, copy) RCTDirectEventBlock onMessage; @property (nonatomic, copy) WKWebView *webView; @end @@ -154,6 +155,20 @@ static NSString *const MessageHanderName = @"ReactNative"; WKNavigationType navigationType = navigationAction.navigationType; NSURLRequest *request = navigationAction.request; + if (_onShouldStartLoadWithRequest) { + NSMutableDictionary *event = [self baseEvent]; + [event addEntriesFromDictionary: @{ + @"url": (request.URL).absoluteString, + @"navigationType": navigationTypes[@(navigationType)] + }]; + if (![self.delegate webView:self + shouldStartLoadForRequest:event + withCallback:_onShouldStartLoadWithRequest]) { + decisionHandler(WKNavigationResponsePolicyCancel); + return; + } + } + if (_onLoadingStart) { // We have this check to filter out iframe requests and whatnot BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL]; diff --git a/React/Views/RCTWKWebViewManager.m b/React/Views/RCTWKWebViewManager.m index 918bc736a..20244c11f 100644 --- a/React/Views/RCTWKWebViewManager.m +++ b/React/Views/RCTWKWebViewManager.m @@ -3,19 +3,29 @@ #import "RCTUIManager.h" #import "RCTWKWebView.h" +@interface RCTWKWebViewManager () +@end + @implementation RCTWKWebViewManager +{ + NSConditionLock *_shouldStartLoadLock; + BOOL _shouldStartLoad; +} RCT_EXPORT_MODULE() - (UIView *)view { - return [RCTWKWebView new]; + RCTWKWebView *webView = [RCTWKWebView new]; + webView.delegate = self; + return webView; } RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary) RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString) /** @@ -105,4 +115,38 @@ RCT_EXPORT_METHOD(stopLoading:(nonnull NSNumber *)reactTag) }]; } +#pragma mark - Exported synchronous methods + +- (BOOL) webView:(RCTWKWebView *)webView +shouldStartLoadForRequest:(NSMutableDictionary *)request + withCallback:(RCTDirectEventBlock)callback +{ + _shouldStartLoadLock = [[NSConditionLock alloc] initWithCondition:arc4random()]; + _shouldStartLoad = YES; + request[@"lockIdentifier"] = @(_shouldStartLoadLock.condition); + callback(request); + + // Block the main thread for a maximum of 250ms until the JS thread returns + if ([_shouldStartLoadLock lockWhenCondition:0 beforeDate:[NSDate dateWithTimeIntervalSinceNow:.25]]) { + BOOL returnValue = _shouldStartLoad; + [_shouldStartLoadLock unlock]; + _shouldStartLoadLock = nil; + return returnValue; + } else { + RCTLogWarn(@"Did not receive response to shouldStartLoad in time, defaulting to YES"); + return YES; + } +} + +RCT_EXPORT_METHOD(startLoadWithResult:(BOOL)result lockIdentifier:(NSInteger)lockIdentifier) +{ + if ([_shouldStartLoadLock tryLockWhenCondition:lockIdentifier]) { + _shouldStartLoad = result; + [_shouldStartLoadLock unlockWithCondition:0]; + } else { + RCTLogWarn(@"startLoadWithResult invoked with invalid lockIdentifier: " + "got %lld, expected %lld", (long long)lockIdentifier, (long long)_shouldStartLoadLock.condition); + } +} + @end