mirror of
https://github.com/status-im/react-native-webview.git
synced 2025-02-23 17:28:37 +00:00
This PR fixes two crashes when enableWebKit is true (which was the default) on iOS 9.x. The first one when the WebView component was mounted (fixes issue #150) and the second one, when the component was unmounted (fixes issue #213). The first problem happen when the RNCWKWebView.m was loaded: The programming guide for WebKit (version 2006) ([pdf](http://mirror.informatimago.com/next/developer.apple.com/documentation/Cocoa/Conceptual/DisplayWebContent/WebKit_DisplayWebContent.pdf)) from Apple said, that it is (was) required to check if the 'new' WebKit Framework was available. This was required when the WebKit framework was only available on Mac OS X (10.2) when the Safari was installed. 😆 Because WebKit is (currently..) a fix part of iOS we didn't need this check this anymore to determinate if WebKit is available. I also see no other reference that this is required in iOS so I just remove this code. Without this code the WebView works also on iOS 9.x. The second issue happen when the WKWebView was removed from the native view hierarchy. Its required (only on iOS 9) to remove the UIScrollView delegate before cleaning up the webview. Its possible to try this PR with: ``` npm install --save "react-native-webview@jerolimov/react-native-webview#fix-ios9-crash" # or yarn add "react-native-webview@jerolimov/react-native-webview#fix-ios9-crash" ``` If you use CocoaPods, you should also update your Pods with ``` cd ios && pod update && cd .. ```
663 lines
22 KiB
Objective-C
663 lines
22 KiB
Objective-C
/**
|
||
* 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 "RNCWKWebView.h"
|
||
#import <React/RCTConvert.h>
|
||
#import <React/RCTAutoInsetsProtocol.h>
|
||
#import "RNCWKProcessPoolManager.h"
|
||
#import <UIKit/UIKit.h>
|
||
|
||
#import "objc/runtime.h"
|
||
|
||
static NSTimer *keyboardTimer;
|
||
static NSString *const MessageHandlerName = @"ReactNativeWebView";
|
||
static NSURLCredential* clientAuthenticationCredential;
|
||
|
||
// runtime trick to remove WKWebView keyboard default toolbar
|
||
// see: http://stackoverflow.com/questions/19033292/ios-7-uiwebview-keyboard-issue/19042279#19042279
|
||
@interface _SwizzleHelperWK : NSObject @end
|
||
@implementation _SwizzleHelperWK
|
||
-(id)inputAccessoryView
|
||
{
|
||
return nil;
|
||
}
|
||
@end
|
||
|
||
@interface RNCWKWebView () <WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, UIScrollViewDelegate, RCTAutoInsetsProtocol>
|
||
@property (nonatomic, copy) RCTDirectEventBlock onLoadingStart;
|
||
@property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish;
|
||
@property (nonatomic, copy) RCTDirectEventBlock onLoadingError;
|
||
@property (nonatomic, copy) RCTDirectEventBlock onLoadingProgress;
|
||
@property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest;
|
||
@property (nonatomic, copy) RCTDirectEventBlock onMessage;
|
||
@property (nonatomic, copy) WKWebView *webView;
|
||
@end
|
||
|
||
@implementation RNCWKWebView
|
||
{
|
||
UIColor * _savedBackgroundColor;
|
||
BOOL _savedHideKeyboardAccessoryView;
|
||
}
|
||
|
||
- (instancetype)initWithFrame:(CGRect)frame
|
||
{
|
||
if ((self = [super initWithFrame:frame])) {
|
||
super.backgroundColor = [UIColor clearColor];
|
||
_bounces = YES;
|
||
_scrollEnabled = YES;
|
||
_showsHorizontalScrollIndicator = YES;
|
||
_showsVerticalScrollIndicator = YES;
|
||
_automaticallyAdjustContentInsets = YES;
|
||
_contentInset = UIEdgeInsetsZero;
|
||
}
|
||
|
||
// Workaround for a keyboard dismissal bug present in iOS 12
|
||
// https://openradar.appspot.com/radar?id=5018321736957952
|
||
if (@available(iOS 12.0, *)) {
|
||
[[NSNotificationCenter defaultCenter]
|
||
addObserver:self
|
||
selector:@selector(keyboardWillHide)
|
||
name:UIKeyboardWillHideNotification object:nil];
|
||
[[NSNotificationCenter defaultCenter]
|
||
addObserver:self
|
||
selector:@selector(keyboardWillShow)
|
||
name:UIKeyboardWillShowNotification object:nil];
|
||
}
|
||
|
||
return self;
|
||
}
|
||
|
||
- (void)dealloc
|
||
{
|
||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||
}
|
||
|
||
/**
|
||
* See https://stackoverflow.com/questions/25713069/why-is-wkwebview-not-opening-links-with-target-blank/25853806#25853806 for details.
|
||
*/
|
||
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
|
||
{
|
||
if (!navigationAction.targetFrame.isMainFrame) {
|
||
[webView loadRequest:navigationAction.request];
|
||
}
|
||
return nil;
|
||
}
|
||
|
||
- (void)didMoveToWindow
|
||
{
|
||
if (self.window != nil && _webView == nil) {
|
||
WKWebViewConfiguration *wkWebViewConfig = [WKWebViewConfiguration new];
|
||
if (_incognito) {
|
||
wkWebViewConfig.websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore];
|
||
} else if (_cacheEnabled) {
|
||
wkWebViewConfig.websiteDataStore = [WKWebsiteDataStore defaultDataStore];
|
||
}
|
||
if(self.useSharedProcessPool) {
|
||
wkWebViewConfig.processPool = [[RNCWKProcessPoolManager sharedManager] sharedProcessPool];
|
||
}
|
||
wkWebViewConfig.userContentController = [WKUserContentController new];
|
||
|
||
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
|
||
? WKAudiovisualMediaTypeAll
|
||
: WKAudiovisualMediaTypeNone;
|
||
wkWebViewConfig.dataDetectorTypes = _dataDetectorTypes;
|
||
#else
|
||
wkWebViewConfig.mediaPlaybackRequiresUserAction = _mediaPlaybackRequiresUserAction;
|
||
#endif
|
||
|
||
_webView = [[WKWebView alloc] initWithFrame:self.bounds configuration: wkWebViewConfig];
|
||
_webView.scrollView.delegate = self;
|
||
_webView.UIDelegate = self;
|
||
_webView.navigationDelegate = self;
|
||
_webView.scrollView.scrollEnabled = _scrollEnabled;
|
||
_webView.scrollView.pagingEnabled = _pagingEnabled;
|
||
_webView.scrollView.bounces = _bounces;
|
||
_webView.scrollView.showsHorizontalScrollIndicator = _showsHorizontalScrollIndicator;
|
||
_webView.scrollView.showsVerticalScrollIndicator = _showsVerticalScrollIndicator;
|
||
_webView.allowsLinkPreview = _allowsLinkPreview;
|
||
[_webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
|
||
_webView.allowsBackForwardNavigationGestures = _allowsBackForwardNavigationGestures;
|
||
|
||
if (_userAgent) {
|
||
_webView.customUserAgent = _userAgent;
|
||
}
|
||
#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];
|
||
[self setHideKeyboardAccessoryView: _savedHideKeyboardAccessoryView];
|
||
[self visitSource];
|
||
}
|
||
}
|
||
|
||
// Update webview property when the component prop changes.
|
||
- (void)setAllowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures {
|
||
_allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures;
|
||
_webView.allowsBackForwardNavigationGestures = _allowsBackForwardNavigationGestures;
|
||
}
|
||
|
||
|
||
- (void)removeFromSuperview
|
||
{
|
||
if (_webView) {
|
||
[_webView.configuration.userContentController removeScriptMessageHandlerForName:MessageHandlerName];
|
||
[_webView removeObserver:self forKeyPath:@"estimatedProgress"];
|
||
[_webView removeFromSuperview];
|
||
_webView.scrollView.delegate = nil;
|
||
_webView = nil;
|
||
}
|
||
|
||
[super removeFromSuperview];
|
||
}
|
||
|
||
-(void)keyboardWillHide
|
||
{
|
||
keyboardTimer = [NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(keyboardDisplacementFix) userInfo:nil repeats:false];
|
||
[[NSRunLoop mainRunLoop] addTimer:keyboardTimer forMode:NSRunLoopCommonModes];
|
||
}
|
||
-(void)keyboardWillShow
|
||
{
|
||
if (keyboardTimer != nil) {
|
||
[keyboardTimer invalidate];
|
||
}
|
||
}
|
||
-(void)keyboardDisplacementFix
|
||
{
|
||
// Additional viewport checks to prevent unintentional scrolls
|
||
UIScrollView *scrollView = self.webView.scrollView;
|
||
double maxContentOffset = scrollView.contentSize.height - scrollView.frame.size.height;
|
||
if (maxContentOffset < 0) {
|
||
maxContentOffset = 0;
|
||
}
|
||
if (scrollView.contentOffset.y > maxContentOffset) {
|
||
// https://stackoverflow.com/a/9637807/824966
|
||
[UIView animateWithDuration:.25 animations:^{
|
||
scrollView.contentOffset = CGPointMake(0, maxContentOffset);
|
||
}];
|
||
}
|
||
}
|
||
|
||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
|
||
if ([keyPath isEqual:@"estimatedProgress"] && object == self.webView) {
|
||
if(_onLoadingProgress){
|
||
NSMutableDictionary<NSString *, id> *event = [self baseEvent];
|
||
[event addEntriesFromDictionary:@{@"progress":[NSNumber numberWithDouble:self.webView.estimatedProgress]}];
|
||
_onLoadingProgress(event);
|
||
}
|
||
}else{
|
||
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
||
}
|
||
}
|
||
|
||
- (void)setBackgroundColor:(UIColor *)backgroundColor
|
||
{
|
||
_savedBackgroundColor = backgroundColor;
|
||
if (_webView == nil) {
|
||
return;
|
||
}
|
||
|
||
CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor);
|
||
self.opaque = _webView.opaque = (alpha == 1.0);
|
||
_webView.scrollView.backgroundColor = backgroundColor;
|
||
_webView.backgroundColor = backgroundColor;
|
||
}
|
||
|
||
/**
|
||
* This method is called whenever JavaScript running within the web view calls:
|
||
* - window.webkit.messageHandlers[MessageHandlerName].postMessage
|
||
*/
|
||
- (void)userContentController:(WKUserContentController *)userContentController
|
||
didReceiveScriptMessage:(WKScriptMessage *)message
|
||
{
|
||
if (_onMessage != nil) {
|
||
NSMutableDictionary<NSString *, id> *event = [self baseEvent];
|
||
[event addEntriesFromDictionary: @{@"data": message.body}];
|
||
_onMessage(event);
|
||
}
|
||
}
|
||
|
||
- (void)setSource:(NSDictionary *)source
|
||
{
|
||
if (![_source isEqualToDictionary:source]) {
|
||
_source = [source copy];
|
||
|
||
if (_webView != nil) {
|
||
[self visitSource];
|
||
}
|
||
}
|
||
}
|
||
|
||
- (void)setContentInset:(UIEdgeInsets)contentInset
|
||
{
|
||
_contentInset = contentInset;
|
||
[RCTView autoAdjustInsetsForView:self
|
||
withScrollView:_webView.scrollView
|
||
updateOffset:NO];
|
||
}
|
||
|
||
- (void)refreshContentInset
|
||
{
|
||
[RCTView autoAdjustInsetsForView:self
|
||
withScrollView:_webView.scrollView
|
||
updateOffset:YES];
|
||
}
|
||
|
||
- (void)visitSource
|
||
{
|
||
// 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.URL]) {
|
||
return;
|
||
}
|
||
if (!request.URL) {
|
||
// Clear the webview
|
||
[_webView loadHTMLString:@"" baseURL:nil];
|
||
return;
|
||
}
|
||
[_webView loadRequest:request];
|
||
}
|
||
|
||
-(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView
|
||
{
|
||
if (_webView == nil) {
|
||
_savedHideKeyboardAccessoryView = hideKeyboardAccessoryView;
|
||
return;
|
||
}
|
||
|
||
if (_savedHideKeyboardAccessoryView == false) {
|
||
return;
|
||
}
|
||
|
||
UIView* subview;
|
||
|
||
for (UIView* view in _webView.scrollView.subviews) {
|
||
if([[view.class description] hasPrefix:@"WK"])
|
||
subview = view;
|
||
}
|
||
|
||
if(subview == nil) return;
|
||
|
||
NSString* name = [NSString stringWithFormat:@"%@_SwizzleHelperWK", subview.class.superclass];
|
||
Class newClass = NSClassFromString(name);
|
||
|
||
if(newClass == nil)
|
||
{
|
||
newClass = objc_allocateClassPair(subview.class, [name cStringUsingEncoding:NSASCIIStringEncoding], 0);
|
||
if(!newClass) return;
|
||
|
||
Method method = class_getInstanceMethod([_SwizzleHelperWK class], @selector(inputAccessoryView));
|
||
class_addMethod(newClass, @selector(inputAccessoryView), method_getImplementation(method), method_getTypeEncoding(method));
|
||
|
||
objc_registerClassPair(newClass);
|
||
}
|
||
|
||
object_setClass(subview, newClass);
|
||
}
|
||
|
||
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
|
||
{
|
||
scrollView.decelerationRate = _decelerationRate;
|
||
}
|
||
|
||
- (void)setScrollEnabled:(BOOL)scrollEnabled
|
||
{
|
||
_scrollEnabled = scrollEnabled;
|
||
_webView.scrollView.scrollEnabled = scrollEnabled;
|
||
}
|
||
|
||
- (void)setShowsHorizontalScrollIndicator:(BOOL)showsHorizontalScrollIndicator
|
||
{
|
||
_showsHorizontalScrollIndicator = showsHorizontalScrollIndicator;
|
||
_webView.scrollView.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator;
|
||
}
|
||
|
||
- (void)setShowsVerticalScrollIndicator:(BOOL)showsVerticalScrollIndicator
|
||
{
|
||
_showsVerticalScrollIndicator = showsVerticalScrollIndicator;
|
||
_webView.scrollView.showsVerticalScrollIndicator = showsVerticalScrollIndicator;
|
||
}
|
||
|
||
- (void)postMessage:(NSString *)message
|
||
{
|
||
NSDictionary *eventInitDict = @{@"data": message};
|
||
NSString *source = [NSString
|
||
stringWithFormat:@"window.dispatchEvent(new MessageEvent('message', %@));",
|
||
RCTJSONStringify(eventInitDict, NULL)
|
||
];
|
||
[self injectJavaScript: source];
|
||
}
|
||
|
||
- (void)layoutSubviews
|
||
{
|
||
[super layoutSubviews];
|
||
|
||
// Ensure webview takes the position and dimensions of RNCWKWebView
|
||
_webView.frame = self.bounds;
|
||
}
|
||
|
||
- (NSMutableDictionary<NSString *, id> *)baseEvent
|
||
{
|
||
NSDictionary *event = @{
|
||
@"url": _webView.URL.absoluteString ?: @"",
|
||
@"title": _webView.title,
|
||
@"loading" : @(_webView.loading),
|
||
@"canGoBack": @(_webView.canGoBack),
|
||
@"canGoForward" : @(_webView.canGoForward)
|
||
};
|
||
return [[NSMutableDictionary alloc] initWithDictionary: event];
|
||
}
|
||
|
||
+ (void)setClientAuthenticationCredential:(nullable NSURLCredential*)credential {
|
||
clientAuthenticationCredential = credential;
|
||
}
|
||
|
||
- (void) webView:(WKWebView *)webView
|
||
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
|
||
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable))completionHandler
|
||
{
|
||
if (!clientAuthenticationCredential) {
|
||
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
|
||
return;
|
||
}
|
||
if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate) {
|
||
completionHandler(NSURLSessionAuthChallengeUseCredential, clientAuthenticationCredential);
|
||
} else {
|
||
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
|
||
}
|
||
}
|
||
|
||
#pragma mark - WKNavigationDelegate methods
|
||
|
||
/**
|
||
* alert
|
||
*/
|
||
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
|
||
{
|
||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert];
|
||
[alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||
completionHandler();
|
||
}]];
|
||
[[self topViewController] presentViewController:alert animated:YES completion:NULL];
|
||
|
||
}
|
||
|
||
/**
|
||
* confirm
|
||
*/
|
||
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
|
||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert];
|
||
[alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||
completionHandler(YES);
|
||
}]];
|
||
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
|
||
completionHandler(NO);
|
||
}]];
|
||
[[self topViewController] presentViewController:alert animated:YES completion:NULL];
|
||
}
|
||
|
||
/**
|
||
* prompt
|
||
*/
|
||
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler{
|
||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:prompt preferredStyle:UIAlertControllerStyleAlert];
|
||
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||
textField.textColor = [UIColor lightGrayColor];
|
||
textField.placeholder = defaultText;
|
||
}];
|
||
[alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||
completionHandler([[alert.textFields lastObject] text]);
|
||
}]];
|
||
[[self topViewController] presentViewController:alert animated:YES completion:NULL];
|
||
}
|
||
|
||
/**
|
||
* topViewController
|
||
*/
|
||
-(UIViewController *)topViewController{
|
||
UIViewController *controller = [self topViewControllerWithRootViewController:[self getCurrentWindow].rootViewController];
|
||
return controller;
|
||
}
|
||
|
||
/**
|
||
* topViewControllerWithRootViewController
|
||
*/
|
||
-(UIViewController *)topViewControllerWithRootViewController:(UIViewController *)viewController{
|
||
if (viewController==nil) return nil;
|
||
if (viewController.presentedViewController!=nil) {
|
||
return [self topViewControllerWithRootViewController:viewController.presentedViewController];
|
||
} else if ([viewController isKindOfClass:[UITabBarController class]]){
|
||
return [self topViewControllerWithRootViewController:[(UITabBarController *)viewController selectedViewController]];
|
||
} else if ([viewController isKindOfClass:[UINavigationController class]]){
|
||
return [self topViewControllerWithRootViewController:[(UINavigationController *)viewController visibleViewController]];
|
||
} else {
|
||
return viewController;
|
||
}
|
||
}
|
||
/**
|
||
* getCurrentWindow
|
||
*/
|
||
-(UIWindow *)getCurrentWindow{
|
||
UIWindow *window = [UIApplication sharedApplication].keyWindow;
|
||
if (window.windowLevel!=UIWindowLevelNormal) {
|
||
for (UIWindow *wid in [UIApplication sharedApplication].windows) {
|
||
if (window.windowLevel==UIWindowLevelNormal) {
|
||
window = wid;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
return window;
|
||
}
|
||
|
||
|
||
/**
|
||
* Decides whether to allow or cancel a navigation.
|
||
* @see https://fburl.com/42r9fxob
|
||
*/
|
||
- (void) webView:(WKWebView *)webView
|
||
decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
|
||
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
|
||
{
|
||
static NSDictionary<NSNumber *, NSString *> *navigationTypes;
|
||
static dispatch_once_t onceToken;
|
||
|
||
dispatch_once(&onceToken, ^{
|
||
navigationTypes = @{
|
||
@(WKNavigationTypeLinkActivated): @"click",
|
||
@(WKNavigationTypeFormSubmitted): @"formsubmit",
|
||
@(WKNavigationTypeBackForward): @"backforward",
|
||
@(WKNavigationTypeReload): @"reload",
|
||
@(WKNavigationTypeFormResubmitted): @"formresubmit",
|
||
@(WKNavigationTypeOther): @"other",
|
||
};
|
||
});
|
||
|
||
WKNavigationType navigationType = navigationAction.navigationType;
|
||
NSURLRequest *request = navigationAction.request;
|
||
|
||
if (_onShouldStartLoadWithRequest) {
|
||
NSMutableDictionary<NSString *, id> *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];
|
||
if (isTopFrame) {
|
||
NSMutableDictionary<NSString *, id> *event = [self baseEvent];
|
||
[event addEntriesFromDictionary: @{
|
||
@"url": (request.URL).absoluteString,
|
||
@"navigationType": navigationTypes[@(navigationType)]
|
||
}];
|
||
_onLoadingStart(event);
|
||
}
|
||
}
|
||
|
||
// Allow all navigation by default
|
||
decisionHandler(WKNavigationResponsePolicyAllow);
|
||
}
|
||
|
||
/**
|
||
* Called when an error occurs while the web view is loading content.
|
||
* @see https://fburl.com/km6vqenw
|
||
*/
|
||
- (void) webView:(WKWebView *)webView
|
||
didFailProvisionalNavigation:(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
|
||
// 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 WKWebView
|
||
// when the URL is from an http redirect. This is a common pattern when
|
||
// implementing OAuth with a WebView.
|
||
return;
|
||
}
|
||
|
||
NSMutableDictionary<NSString *, id> *event = [self baseEvent];
|
||
[event addEntriesFromDictionary:@{
|
||
@"didFailProvisionalNavigation": @YES,
|
||
@"domain": error.domain,
|
||
@"code": @(error.code),
|
||
@"description": error.localizedDescription,
|
||
}];
|
||
_onLoadingError(event);
|
||
}
|
||
|
||
[self setBackgroundColor: _savedBackgroundColor];
|
||
}
|
||
|
||
- (void)evaluateJS:(NSString *)js
|
||
thenCall: (void (^)(NSString*)) callback
|
||
{
|
||
[self.webView evaluateJavaScript: js completionHandler: ^(id result, NSError *error) {
|
||
if (error == nil) {
|
||
if (callback != nil) {
|
||
callback([NSString stringWithFormat:@"%@", result]);
|
||
}
|
||
} else {
|
||
RCTLogError(@"Error evaluating injectedJavaScript: This is possibly due to an unsupported return type. Try adding true to the end of your injectedJavaScript string.");
|
||
}
|
||
}];
|
||
}
|
||
|
||
/**
|
||
* Called when the navigation is complete.
|
||
* @see https://fburl.com/rtys6jlb
|
||
*/
|
||
- (void) webView:(WKWebView *)webView
|
||
didFinishNavigation:(WKNavigation *)navigation
|
||
{
|
||
if (_injectedJavaScript) {
|
||
[self evaluateJS: _injectedJavaScript thenCall: ^(NSString *jsEvaluationValue) {
|
||
NSMutableDictionary *event = [self baseEvent];
|
||
event[@"jsEvaluationValue"] = jsEvaluationValue;
|
||
|
||
if (self.onLoadingFinish) {
|
||
self.onLoadingFinish(event);
|
||
}
|
||
}];
|
||
} else if (_onLoadingFinish) {
|
||
_onLoadingFinish([self baseEvent]);
|
||
}
|
||
|
||
[self setBackgroundColor: _savedBackgroundColor];
|
||
}
|
||
|
||
- (void)injectJavaScript:(NSString *)script
|
||
{
|
||
[self evaluateJS: script thenCall: nil];
|
||
}
|
||
|
||
- (void)goForward
|
||
{
|
||
[_webView goForward];
|
||
}
|
||
|
||
- (void)goBack
|
||
{
|
||
[_webView goBack];
|
||
}
|
||
|
||
- (void)reload
|
||
{
|
||
/**
|
||
* When the initial load fails due to network connectivity issues,
|
||
* [_webView reload] doesn't reload the webpage. Therefore, we must
|
||
* manually call [_webView loadRequest:request].
|
||
*/
|
||
NSURLRequest *request = [RCTConvert NSURLRequest:self.source];
|
||
if (request.URL && !_webView.URL.absoluteString.length) {
|
||
[_webView loadRequest:request];
|
||
}
|
||
else {
|
||
[_webView reload];
|
||
}
|
||
}
|
||
|
||
- (void)stopLoading
|
||
{
|
||
[_webView stopLoading];
|
||
}
|
||
|
||
- (void)setBounces:(BOOL)bounces
|
||
{
|
||
_bounces = bounces;
|
||
_webView.scrollView.bounces = bounces;
|
||
}
|
||
@end
|