/** * Copyright (c) 2015-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "RNCWebViewManager.h" #import #import #import "RNCWebView.h" @interface RNCWebViewManager () @end @implementation RCTConvert (WKWebView) #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* iOS 13 */ RCT_ENUM_CONVERTER(WKContentMode, (@{ @"recommended": @(WKContentModeRecommended), @"mobile": @(WKContentModeMobile), @"desktop": @(WKContentModeDesktop), }), WKContentModeRecommended, integerValue) #endif @end @implementation RNCWebViewManager { NSConditionLock *_shouldStartLoadLock; BOOL _shouldStartLoad; } RCT_EXPORT_MODULE() #if !TARGET_OS_OSX - (UIView *)view #else - (RCTUIView *)view #endif // !TARGET_OS_OSX { RNCWebView *webView = [RNCWebView new]; webView.delegate = self; return webView; } RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary) RCT_EXPORT_VIEW_PROPERTY(onFileDownload, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onLoadingProgress, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onHttpError, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onContentProcessDidTerminate, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString) RCT_EXPORT_VIEW_PROPERTY(injectedJavaScriptBeforeContentLoaded, NSString) RCT_EXPORT_VIEW_PROPERTY(injectedJavaScriptForMainFrameOnly, BOOL) RCT_EXPORT_VIEW_PROPERTY(injectedJavaScriptBeforeContentLoadedForMainFrameOnly, BOOL) RCT_EXPORT_VIEW_PROPERTY(javaScriptEnabled, BOOL) RCT_EXPORT_VIEW_PROPERTY(javaScriptCanOpenWindowsAutomatically, BOOL) RCT_EXPORT_VIEW_PROPERTY(allowFileAccessFromFileURLs, BOOL) RCT_EXPORT_VIEW_PROPERTY(allowsInlineMediaPlayback, BOOL) RCT_EXPORT_VIEW_PROPERTY(mediaPlaybackRequiresUserAction, BOOL) #if WEBKIT_IOS_10_APIS_AVAILABLE RCT_EXPORT_VIEW_PROPERTY(dataDetectorTypes, WKDataDetectorTypes) #endif RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets) RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL) RCT_EXPORT_VIEW_PROPERTY(autoManageStatusBarEnabled, BOOL) RCT_EXPORT_VIEW_PROPERTY(hideKeyboardAccessoryView, BOOL) RCT_EXPORT_VIEW_PROPERTY(allowsBackForwardNavigationGestures, BOOL) RCT_EXPORT_VIEW_PROPERTY(incognito, BOOL) RCT_EXPORT_VIEW_PROPERTY(pagingEnabled, BOOL) RCT_EXPORT_VIEW_PROPERTY(userAgent, NSString) RCT_EXPORT_VIEW_PROPERTY(applicationNameForUserAgent, NSString) RCT_EXPORT_VIEW_PROPERTY(cacheEnabled, BOOL) RCT_EXPORT_VIEW_PROPERTY(allowsLinkPreview, BOOL) RCT_EXPORT_VIEW_PROPERTY(allowingReadAccessToURL, NSString) #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ RCT_EXPORT_VIEW_PROPERTY(contentInsetAdjustmentBehavior, UIScrollViewContentInsetAdjustmentBehavior) #endif #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* iOS 13 */ RCT_EXPORT_VIEW_PROPERTY(contentMode, WKContentMode) #endif /** * Expose methods to enable messaging the webview. */ RCT_EXPORT_VIEW_PROPERTY(messagingEnabled, BOOL) RCT_EXPORT_VIEW_PROPERTY(onMessage, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock) RCT_EXPORT_METHOD(postMessage:(nonnull NSNumber *)reactTag message:(NSString *)message) { [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { RNCWebView *view = viewRegistry[reactTag]; if (![view isKindOfClass:[RNCWebView class]]) { RCTLogError(@"Invalid view returned from registry, expecting RNCWebView, got: %@", view); } else { [view postMessage:message]; } }]; } RCT_CUSTOM_VIEW_PROPERTY(pullToRefreshEnabled, BOOL, RNCWebView) { view.pullToRefreshEnabled = json == nil ? false : [RCTConvert BOOL: json]; } RCT_CUSTOM_VIEW_PROPERTY(bounces, BOOL, RNCWebView) { view.bounces = json == nil ? true : [RCTConvert BOOL: json]; } RCT_CUSTOM_VIEW_PROPERTY(useSharedProcessPool, BOOL, RNCWebView) { view.useSharedProcessPool = json == nil ? true : [RCTConvert BOOL: json]; } RCT_CUSTOM_VIEW_PROPERTY(scrollEnabled, BOOL, RNCWebView) { view.scrollEnabled = json == nil ? true : [RCTConvert BOOL: json]; } RCT_CUSTOM_VIEW_PROPERTY(sharedCookiesEnabled, BOOL, RNCWebView) { view.sharedCookiesEnabled = json == nil ? false : [RCTConvert BOOL: json]; } #if !TARGET_OS_OSX RCT_CUSTOM_VIEW_PROPERTY(decelerationRate, CGFloat, RNCWebView) { view.decelerationRate = json == nil ? UIScrollViewDecelerationRateNormal : [RCTConvert CGFloat: json]; } #endif // !TARGET_OS_OSX RCT_CUSTOM_VIEW_PROPERTY(directionalLockEnabled, BOOL, RNCWebView) { view.directionalLockEnabled = json == nil ? true : [RCTConvert BOOL: json]; } RCT_CUSTOM_VIEW_PROPERTY(showsHorizontalScrollIndicator, BOOL, RNCWebView) { view.showsHorizontalScrollIndicator = json == nil ? true : [RCTConvert BOOL: json]; } RCT_CUSTOM_VIEW_PROPERTY(showsVerticalScrollIndicator, BOOL, RNCWebView) { view.showsVerticalScrollIndicator = json == nil ? true : [RCTConvert BOOL: json]; } RCT_CUSTOM_VIEW_PROPERTY(keyboardDisplayRequiresUserAction, BOOL, RNCWebView) { view.keyboardDisplayRequiresUserAction = json == nil ? true : [RCTConvert BOOL: json]; } RCT_EXPORT_METHOD(injectJavaScript:(nonnull NSNumber *)reactTag script:(NSString *)script) { [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { RNCWebView *view = viewRegistry[reactTag]; if (![view isKindOfClass:[RNCWebView class]]) { RCTLogError(@"Invalid view returned from registry, expecting RNCWebView, got: %@", view); } else { [view injectJavaScript:script]; } }]; } RCT_EXPORT_METHOD(goBack:(nonnull NSNumber *)reactTag) { [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { RNCWebView *view = viewRegistry[reactTag]; if (![view isKindOfClass:[RNCWebView class]]) { RCTLogError(@"Invalid view returned from registry, expecting RNCWebView, got: %@", view); } else { [view goBack]; } }]; } RCT_EXPORT_METHOD(goForward:(nonnull NSNumber *)reactTag) { [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { RNCWebView *view = viewRegistry[reactTag]; if (![view isKindOfClass:[RNCWebView class]]) { RCTLogError(@"Invalid view returned from registry, expecting RNCWebView, got: %@", view); } else { [view goForward]; } }]; } RCT_EXPORT_METHOD(reload:(nonnull NSNumber *)reactTag) { [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { RNCWebView *view = viewRegistry[reactTag]; if (![view isKindOfClass:[RNCWebView class]]) { RCTLogError(@"Invalid view returned from registry, expecting RNCWebView, got: %@", view); } else { [view reload]; } }]; } RCT_EXPORT_METHOD(stopLoading:(nonnull NSNumber *)reactTag) { [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { RNCWebView *view = viewRegistry[reactTag]; if (![view isKindOfClass:[RNCWebView class]]) { RCTLogError(@"Invalid view returned from registry, expecting RNCWebView, got: %@", view); } else { [view stopLoading]; } }]; } #pragma mark - Exported synchronous methods - (BOOL) webView:(RNCWebView *)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