From 12a9299f0aec05264743627bbdf5778960ed0d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Chmiela?= Date: Mon, 9 Sep 2019 15:18:25 +0200 Subject: [PATCH] Remove UIWebView usage --- React/Views/RCTWebView.h | 46 ----- React/Views/RCTWebView.m | 351 -------------------------------- React/Views/RCTWebViewManager.m | 108 +--------- 3 files changed, 5 insertions(+), 500 deletions(-) delete mode 100644 React/Views/RCTWebView.h delete mode 100644 React/Views/RCTWebView.m diff --git a/React/Views/RCTWebView.h b/React/Views/RCTWebView.h deleted file mode 100644 index 4fa9d62d8a83..000000000000 --- a/React/Views/RCTWebView.h +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import - -@class RCTWebView; - -/** - * Special scheme used to pass messages to the injectedJavaScript - * code without triggering a page load. Usage: - * - * window.location.href = RCTJSNavigationScheme + '://hello' - */ -extern NSString *const RCTJSNavigationScheme; - -@protocol RCTWebViewDelegate - -- (BOOL)webView:(RCTWebView *)webView -shouldStartLoadForRequest:(NSMutableDictionary *)request - withCallback:(RCTDirectEventBlock)callback; - -@end - -@interface RCTWebView : RCTView - -@property (nonatomic, weak) id delegate; - -@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; - -- (void)goForward; -- (void)goBack; -- (void)reload; -- (void)stopLoading; -- (void)postMessage:(NSString *)message; -- (void)injectJavaScript:(NSString *)script; - -@end diff --git a/React/Views/RCTWebView.m b/React/Views/RCTWebView.m deleted file mode 100644 index 8f61e3804143..000000000000 --- a/React/Views/RCTWebView.m +++ /dev/null @@ -1,351 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import "RCTWebView.h" - -#import - -#import "RCTAutoInsetsProtocol.h" -#import "RCTConvert.h" -#import "RCTEventDispatcher.h" -#import "RCTLog.h" -#import "RCTUtils.h" -#import "RCTView.h" -#import "UIView+React.h" - -NSString *const RCTJSNavigationScheme = @"react-js-navigation"; - -static NSString *const kPostMessageHost = @"postMessage"; - -@interface RCTWebView () - -@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; - -@end - -@implementation RCTWebView -{ - UIWebView *_webView; - NSString *_injectedJavaScript; -} - -- (void)dealloc -{ - _webView.delegate = nil; -} - -- (instancetype)initWithFrame:(CGRect)frame -{ - if ((self = [super initWithFrame:frame])) { - super.backgroundColor = [UIColor clearColor]; - _automaticallyAdjustContentInsets = YES; - _contentInset = UIEdgeInsetsZero; - _webView = [[UIWebView alloc] initWithFrame:self.bounds]; - _webView.delegate = self; -#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ - if ([_webView.scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) { - _webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; - } -#endif - [self addSubview:_webView]; - } - return self; -} - -RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) - -- (void)goForward -{ - [_webView goForward]; -} - -- (void)goBack -{ - [_webView goBack]; -} - -- (void)reload -{ - NSURLRequest *request = [RCTConvert NSURLRequest:self.source]; - if (request.URL && !_webView.request.URL.absoluteString.length) { - [_webView loadRequest:request]; - } - else { - [_webView reload]; - } -} - -- (void)stopLoading -{ - [_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)injectJavaScript:(NSString *)script -{ - [_webView stringByEvaluatingJavaScriptFromString:script]; -} - -- (void)setSource:(NSDictionary *)source -{ - if (![_source isEqualToDictionary:source]) { - _source = [source copy]; - - // Check for a static html source first - NSString *html = [RCTConvert NSString:source[@"html"]]; - if (html) { - NSURL *baseURL = [RCTConvert NSURL:source[@"baseUrl"]]; - if (!baseURL) { - baseURL = [NSURL URLWithString:@"about:blank"]; - } - [_webView loadHTMLString:html baseURL:baseURL]; - return; - } - - NSURLRequest *request = [RCTConvert NSURLRequest:source]; - // Because of the way React works, as pages redirect, we actually end up - // 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]) { - return; - } - if (!request.URL) { - // Clear the webview - [_webView loadHTMLString:@"" baseURL:nil]; - return; - } - [_webView loadRequest:request]; - } -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - _webView.frame = self.bounds; -} - -- (void)setContentInset:(UIEdgeInsets)contentInset -{ - _contentInset = contentInset; - [RCTView autoAdjustInsetsForView:self - withScrollView:_webView.scrollView - updateOffset:NO]; -} - -- (void)setScalesPageToFit:(BOOL)scalesPageToFit -{ - if (_webView.scalesPageToFit != scalesPageToFit) { - _webView.scalesPageToFit = scalesPageToFit; - [_webView reload]; - } -} - -- (BOOL)scalesPageToFit -{ - return _webView.scalesPageToFit; -} - -- (void)setBackgroundColor:(UIColor *)backgroundColor -{ - CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor); - self.opaque = _webView.opaque = (alpha == 1.0); - _webView.backgroundColor = backgroundColor; -} - -- (UIColor *)backgroundColor -{ - return _webView.backgroundColor; -} - -- (NSMutableDictionary *)baseEvent -{ - NSMutableDictionary *event = [[NSMutableDictionary alloc] initWithDictionary:@{ - @"url": _webView.request.URL.absoluteString ?: @"", - @"loading" : @(_webView.loading), - @"title": [_webView stringByEvaluatingJavaScriptFromString:@"document.title"], - @"canGoBack": @(_webView.canGoBack), - @"canGoForward" : @(_webView.canGoForward), - }]; - - return event; -} - -- (void)refreshContentInset -{ - [RCTView autoAdjustInsetsForView:self - withScrollView:_webView.scrollView - updateOffset:YES]; -} - -#pragma mark - UIWebViewDelegate methods - -- (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request - navigationType:(UIWebViewNavigationType)navigationType -{ - BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme]; - - static NSDictionary *navigationTypes; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - navigationTypes = @{ - @(UIWebViewNavigationTypeLinkClicked): @"click", - @(UIWebViewNavigationTypeFormSubmitted): @"formsubmit", - @(UIWebViewNavigationTypeBackForward): @"backforward", - @(UIWebViewNavigationTypeReload): @"reload", - @(UIWebViewNavigationTypeFormResubmitted): @"formresubmit", - @(UIWebViewNavigationTypeOther): @"other", - }; - }); - - // skip this for the JS Navigation handler - if (!isJSNavigation && _onShouldStartLoadWithRequest) { - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary: @{ - @"url": (request.URL).absoluteString, - @"navigationType": navigationTypes[@(navigationType)] - }]; - if (![self.delegate webView:self - shouldStartLoadForRequest:event - withCallback:_onShouldStartLoadWithRequest]) { - return NO; - } - } - - 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": navigationTypes[@(navigationType)] - }]; - _onLoadingStart(event); - } - } - - if (isJSNavigation && [request.URL.host isEqualToString:kPostMessageHost]) { - NSString *data = request.URL.query; - data = [data stringByReplacingOccurrencesOfString:@"+" withString:@" "]; - data = [data stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet]; - - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary: @{ - @"data": data, - }]; - - NSString *source = @"document.dispatchEvent(new MessageEvent('message:received'));"; - - [_webView stringByEvaluatingJavaScriptFromString:source]; - - _onMessage(event); - } - - // JS Navigation handler - return !isJSNavigation; -} - -- (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(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 - // a new URL in the WebView before the previous one came back. We can just - // ignore these since they aren't real errors. - // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os - return; - } - - if ([error.domain isEqualToString:@"WebKitErrorDomain"] && error.code == 102) { - // Error code 102 "Frame load interrupted" is raised by the UIWebView if - // its delegate returns FALSE from webView:shouldStartLoadWithRequest:navigationType - // when the URL is from an http redirect. This is a common pattern when - // implementing OAuth with a WebView. - return; - } - - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary:@{ - @"domain": error.domain, - @"code": @(error.code), - @"description": error.localizedDescription, - }]; - _onLoadingError(event); - } -} - -- (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;" - - "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();" - "});" - "})();", RCTJSNavigationScheme, kPostMessageHost - ]; - [webView stringByEvaluatingJavaScriptFromString:source]; - } - if (_injectedJavaScript != nil) { - NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString:_injectedJavaScript]; - - NSMutableDictionary *event = [self baseEvent]; - event[@"jsEvaluationValue"] = jsEvaluationValue; - - _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]); - } -} - -@end diff --git a/React/Views/RCTWebViewManager.m b/React/Views/RCTWebViewManager.m index fc39f7cb1382..152c9ff05178 100644 --- a/React/Views/RCTWebViewManager.m +++ b/React/Views/RCTWebViewManager.m @@ -9,150 +9,52 @@ #import "RCTBridge.h" #import "RCTUIManager.h" -#import "RCTWebView.h" #import "UIView+React.h" -@interface RCTWebViewManager () +@interface RCTWebViewManager () @end @implementation RCTWebViewManager -{ - NSConditionLock *_shouldStartLoadLock; - BOOL _shouldStartLoad; -} RCT_EXPORT_MODULE() - (UIView *)view { - RCTWebView *webView = [RCTWebView new]; - webView.delegate = self; - return webView; + RCTLogWarn(@"RCTWebView had to be removed from SDK35-compatible " + "Expo Client in order to comply with latest App Review" + "guidelines. Plain empty view will be rendered instead."); + return [[UIView alloc] initWithFrame:CGRectZero]; } -RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary) -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) -RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, _webView.dataDetectorTypes, UIDataDetectorTypes) - RCT_EXPORT_METHOD(goBack:(nonnull NSNumber *)reactTag) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTWebView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RCTWebView class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); - } else { - [view goBack]; - } - }]; } RCT_EXPORT_METHOD(goForward:(nonnull NSNumber *)reactTag) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTWebView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RCTWebView class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); - } else { - [view goForward]; - } - }]; } RCT_EXPORT_METHOD(reload:(nonnull NSNumber *)reactTag) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTWebView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RCTWebView class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); - } else { - [view reload]; - } - }]; } RCT_EXPORT_METHOD(stopLoading:(nonnull NSNumber *)reactTag) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTWebView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RCTWebView class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); - } else { - [view stopLoading]; - } - }]; } RCT_EXPORT_METHOD(postMessage:(nonnull NSNumber *)reactTag message:(NSString *)message) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTWebView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RCTWebView class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); - } else { - [view postMessage:message]; - } - }]; } RCT_EXPORT_METHOD(injectJavaScript:(nonnull NSNumber *)reactTag script:(NSString *)script) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTWebView *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RCTWebView class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); - } else { - [view injectJavaScript:script]; - } - }]; } #pragma mark - Exported synchronous methods -- (BOOL)webView:(__unused RCTWebView *)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