1/** 2 * Copyright (c) 2015-present, Facebook, Inc. 3 * 4 * This source code is licensed under the MIT license found in the 5 * LICENSE file in the root directory of this source tree. 6 */ 7 8#import "RNCWebViewImpl.h" 9#import <React/RCTConvert.h> 10#import <React/RCTAutoInsetsProtocol.h> 11#import "RNCWKProcessPoolManager.h" 12#if !TARGET_OS_OSX 13#import <UIKit/UIKit.h> 14#else 15#import <React/RCTUIKit.h> 16#endif // !TARGET_OS_OSX 17 18#import "objc/runtime.h" 19 20static NSTimer *keyboardTimer; 21static NSString *const HistoryShimName = @"ReactNativeHistoryShim"; 22static NSString *const MessageHandlerName = @"ReactNativeWebView"; 23static NSURLCredential* clientAuthenticationCredential; 24static NSDictionary* customCertificatesForHost; 25 26NSString *const CUSTOM_SELECTOR = @"_CUSTOM_SELECTOR_"; 27 28#if !TARGET_OS_OSX 29// runtime trick to remove WKWebView keyboard default toolbar 30// see: http://stackoverflow.com/questions/19033292/ios-7-uiwebview-keyboard-issue/19042279#19042279 31@interface _SwizzleHelperWK : UIView 32@property (nonatomic, copy) WKWebView *webView; 33@end 34@implementation _SwizzleHelperWK 35-(id)inputAccessoryView 36{ 37 if (_webView == nil) { 38 return nil; 39 } 40 41 if ([_webView respondsToSelector:@selector(inputAssistantItem)]) { 42 UITextInputAssistantItem *inputAssistantItem = [_webView inputAssistantItem]; 43 inputAssistantItem.leadingBarButtonGroups = @[]; 44 inputAssistantItem.trailingBarButtonGroups = @[]; 45 } 46 return nil; 47} 48@end 49#endif // !TARGET_OS_OSX 50 51@interface RNCWKWebView : WKWebView 52#if !TARGET_OS_OSX 53@property (nonatomic, copy) NSArray<NSDictionary *> * _Nullable menuItems; 54#endif // !TARGET_OS_OSX 55@end 56@implementation RNCWKWebView 57#if !TARGET_OS_OSX 58- (BOOL)canPerformAction:(SEL)action 59 withSender:(id)sender{ 60 61 if (!self.menuItems) { 62 return [super canPerformAction:action withSender:sender]; 63 } 64 65 return NO; 66} 67- (void)buildMenuWithBuilder:(id<UIMenuBuilder>)builder API_AVAILABLE(ios(13.0)) { 68 if (@available(iOS 16.0, *)) { 69 if(self.menuItems){ 70 [builder removeMenuForIdentifier:UIMenuLookup]; 71 } 72 } 73 [super buildMenuWithBuilder:builder]; 74} 75#else // TARGET_OS_OSX 76- (void)scrollWheel:(NSEvent *)theEvent { 77 RNCWebViewImpl *rncWebView = (RNCWebViewImpl *)[self superview]; 78 RCTAssert([rncWebView isKindOfClass:[rncWebView class]], @"superview must be an RNCWebViewImpl"); 79 if (![rncWebView scrollEnabled]) { 80 [[self nextResponder] scrollWheel:theEvent]; 81 return; 82 } 83 [super scrollWheel:theEvent]; 84} 85#endif // TARGET_OS_OSX 86@end 87 88@interface RNCWebViewImpl () <WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, WKHTTPCookieStoreObserver, 89#if !TARGET_OS_OSX 90UIScrollViewDelegate, 91#endif // !TARGET_OS_OSX 92RCTAutoInsetsProtocol> 93 94@property (nonatomic, copy) RNCWKWebView *webView; 95@property (nonatomic, strong) WKUserScript *postMessageScript; 96@property (nonatomic, strong) WKUserScript *atStartScript; 97@property (nonatomic, strong) WKUserScript *atEndScript; 98@end 99 100@implementation RNCWebViewImpl 101{ 102#if !TARGET_OS_OSX 103 UIColor * _savedBackgroundColor; 104#else 105 RCTUIColor * _savedBackgroundColor; 106#endif // !TARGET_OS_OSX 107 BOOL _savedHideKeyboardAccessoryView; 108 BOOL _savedKeyboardDisplayRequiresUserAction; 109 110 // Workaround for StatusBar appearance bug for iOS 12 111 // https://github.com/react-native-webview/react-native-webview/issues/62 112 BOOL _isFullScreenVideoOpen; 113#if !TARGET_OS_OSX 114 UIStatusBarStyle _savedStatusBarStyle; 115#endif // !TARGET_OS_OSX 116 BOOL _savedStatusBarHidden; 117 118#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ 119 UIScrollViewContentInsetAdjustmentBehavior _savedContentInsetAdjustmentBehavior; 120#endif 121#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* __IPHONE_13_0 */ 122 BOOL _savedAutomaticallyAdjustsScrollIndicatorInsets; 123#endif 124} 125 126- (instancetype)initWithFrame:(CGRect)frame 127{ 128 if ((self = [super initWithFrame:frame])) { 129#if !TARGET_OS_OSX 130 super.backgroundColor = [UIColor clearColor]; 131#else 132 super.backgroundColor = [RCTUIColor clearColor]; 133#endif // !TARGET_OS_OSX 134 _bounces = YES; 135 _scrollEnabled = YES; 136 _showsHorizontalScrollIndicator = YES; 137 _showsVerticalScrollIndicator = YES; 138 _directionalLockEnabled = YES; 139 _automaticallyAdjustContentInsets = YES; 140 _autoManageStatusBarEnabled = YES; 141 _contentInset = UIEdgeInsetsZero; 142 _savedKeyboardDisplayRequiresUserAction = YES; 143#if !TARGET_OS_OSX 144 _savedStatusBarStyle = RCTSharedApplication().statusBarStyle; 145 _savedStatusBarHidden = RCTSharedApplication().statusBarHidden; 146#endif // !TARGET_OS_OSX 147 _injectedJavaScript = nil; 148 _injectedJavaScriptForMainFrameOnly = YES; 149 _injectedJavaScriptBeforeContentLoaded = nil; 150 _injectedJavaScriptBeforeContentLoadedForMainFrameOnly = YES; 151 152#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ 153 _savedContentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; 154#endif 155#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* __IPHONE_13_0 */ 156 _savedAutomaticallyAdjustsScrollIndicatorInsets = NO; 157#endif 158 _enableApplePay = NO; 159#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000 /* iOS 15 */ 160 _mediaCapturePermissionGrantType = RNCWebViewPermissionGrantType_Prompt; 161#endif 162 } 163 164#if !TARGET_OS_OSX 165 [[NSNotificationCenter defaultCenter]addObserver:self 166 selector:@selector(appDidBecomeActive) 167 name:UIApplicationDidBecomeActiveNotification 168 object:nil]; 169 170 [[NSNotificationCenter defaultCenter]addObserver:self 171 selector:@selector(appWillResignActive) 172 name:UIApplicationWillResignActiveNotification 173 object:nil]; 174 if (@available(iOS 12.0, *)) { 175 // Workaround for a keyboard dismissal bug present in iOS 12 176 // https://openradar.appspot.com/radar?id=5018321736957952 177 [[NSNotificationCenter defaultCenter] 178 addObserver:self 179 selector:@selector(keyboardWillHide) 180 name:UIKeyboardWillHideNotification object:nil]; 181 [[NSNotificationCenter defaultCenter] 182 addObserver:self 183 selector:@selector(keyboardWillShow) 184 name:UIKeyboardWillShowNotification object:nil]; 185 186 // Workaround for StatusBar appearance bug for iOS 12 187 // https://github.com/react-native-webview/react-native-webview/issues/62 188 [[NSNotificationCenter defaultCenter] addObserver:self 189 selector:@selector(showFullScreenVideoStatusBars) 190 name:UIWindowDidBecomeVisibleNotification 191 object:nil]; 192 193 [[NSNotificationCenter defaultCenter] addObserver:self 194 selector:@selector(hideFullScreenVideoStatusBars) 195 name:UIWindowDidBecomeHiddenNotification 196 object:nil]; 197 198 } 199#endif // !TARGET_OS_OSX 200 return self; 201} 202 203#if !TARGET_OS_OSX 204- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { 205 // Only allow long press gesture 206 if ([otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) { 207 return YES; 208 }else{ 209 return NO; 210 } 211} 212 213// Listener for long presses 214- (void)startLongPress:(UILongPressGestureRecognizer *)pressSender 215{ 216 if (pressSender.state != UIGestureRecognizerStateEnded || !self.menuItems) { 217 return; 218 } 219 // When a long press ends, bring up our custom UIMenu if defined 220 if (self.menuItems.count == 0) { 221 UIMenuController *menuController = [UIMenuController sharedMenuController]; 222 menuController.menuItems = nil; 223 [menuController setMenuVisible:NO animated:YES]; 224 return; 225 } 226 UIMenuController *menuController = [UIMenuController sharedMenuController]; 227 NSMutableArray *menuControllerItems = [NSMutableArray arrayWithCapacity:self.menuItems.count]; 228 229 for(NSDictionary *menuItem in self.menuItems) { 230 NSString *menuItemLabel = [RCTConvert NSString:menuItem[@"label"]]; 231 NSString *menuItemKey = [RCTConvert NSString:menuItem[@"key"]]; 232 NSString *sel = [NSString stringWithFormat:@"%@%@", CUSTOM_SELECTOR, menuItemKey]; 233 UIMenuItem *item = [[UIMenuItem alloc] initWithTitle: menuItemLabel 234 action: NSSelectorFromString(sel)]; 235 [menuControllerItems addObject: item]; 236 } 237 238 menuController.menuItems = menuControllerItems; 239 [menuController setMenuVisible:YES animated:YES]; 240} 241 242#endif // !TARGET_OS_OSX 243 244- (void)dealloc 245{ 246 [[NSNotificationCenter defaultCenter] removeObserver:self]; 247 if (@available(iOS 11.0, *)) { 248 [self.webView.configuration.websiteDataStore.httpCookieStore removeObserver:self]; 249 } 250} 251 252- (void)tappedMenuItem:(NSString *)eventType 253{ 254 // Get the selected text 255 // NOTE: selecting text in an iframe or shadow DOM will not work 256 [self.webView evaluateJavaScript: @"window.getSelection().toString()" completionHandler: ^(id result, NSError *error) { 257 if (error != nil) { 258 RCTLogWarn(@"%@", [NSString stringWithFormat:@"Error evaluating injectedJavaScript: This is possibly due to an unsupported return type. Try adding true to the end of your injectedJavaScript string. %@", error]); 259 } else { 260 if (self.onCustomMenuSelection) { 261 NSPredicate *filter = [NSPredicate predicateWithFormat:@"key contains[c] %@ ",eventType]; 262 NSArray *filteredMenuItems = [self.menuItems filteredArrayUsingPredicate:filter]; 263 NSDictionary *selectedMenuItem = filteredMenuItems[0]; 264 NSString *label = [RCTConvert NSString:selectedMenuItem[@"label"]]; 265 self.onCustomMenuSelection(@{ 266 @"key": eventType, 267 @"label": label, 268 @"selectedText": result 269 }); 270 } else { 271 RCTLogWarn(@"Error evaluating onCustomMenuSelection: You must implement an `onCustomMenuSelection` callback when using custom menu items"); 272 } 273 } 274 }]; 275} 276 277// Overwrite method that interprets which action to call upon UIMenu Selection 278// https://developer.apple.com/documentation/objectivec/nsobject/1571960-methodsignatureforselector 279- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel 280{ 281 NSMethodSignature *existingSelector = [super methodSignatureForSelector:sel]; 282 if (existingSelector) { 283 return existingSelector; 284 } 285 return [super methodSignatureForSelector:@selector(tappedMenuItem:)]; 286} 287 288// Needed to forward messages to other objects 289// https://developer.apple.com/documentation/objectivec/nsobject/1571955-forwardinvocation 290- (void)forwardInvocation:(NSInvocation *)invocation 291{ 292 NSString *sel = NSStringFromSelector([invocation selector]); 293 NSRange match = [sel rangeOfString:CUSTOM_SELECTOR]; 294 if (match.location == 0) { 295 [self tappedMenuItem:[sel substringFromIndex:17]]; 296 } else { 297 [super forwardInvocation:invocation]; 298 } 299} 300 301// Allows the instance to respond to UIMenuController Actions 302- (BOOL)canBecomeFirstResponder 303{ 304 return YES; 305} 306 307// Control which items show up on the UIMenuController 308- (BOOL)canPerformAction:(SEL)action withSender:(id)sender 309{ 310 NSString *sel = NSStringFromSelector(action); 311 // Do any of them have our custom keys? 312 NSRange match = [sel rangeOfString:CUSTOM_SELECTOR]; 313 314 if (match.location == 0) { 315 return YES; 316 } 317 return NO; 318} 319 320/** 321 * See https://stackoverflow.com/questions/25713069/why-is-wkwebview-not-opening-links-with-target-blank/25853806#25853806 for details. 322 */ 323- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures 324{ 325 if (!navigationAction.targetFrame.isMainFrame) { 326 [webView loadRequest:navigationAction.request]; 327 } 328 return nil; 329} 330 331/** 332 * Enables file input on macos, see https://developer.apple.com/documentation/webkit/wkuidelegate/1641952-webview 333 */ 334#if TARGET_OS_OSX 335- (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray<NSURL *> *URLs))completionHandler 336{ 337 NSOpenPanel *openPanel = [NSOpenPanel openPanel]; 338 openPanel.allowsMultipleSelection = parameters.allowsMultipleSelection; 339 [openPanel beginSheetModalForWindow:webView.window completionHandler:^(NSInteger result) { 340 if (result == NSModalResponseOK) 341 completionHandler(openPanel.URLs); 342 else 343 completionHandler(nil); 344 }]; 345} 346#endif //Target_OS_OSX 347 348- (WKWebViewConfiguration *)setUpWkWebViewConfig 349{ 350 WKWebViewConfiguration *wkWebViewConfig = [WKWebViewConfiguration new]; 351 WKPreferences *prefs = [[WKPreferences alloc]init]; 352 BOOL _prefsUsed = NO; 353 if (!_javaScriptEnabled) { 354 prefs.javaScriptEnabled = NO; 355 _prefsUsed = YES; 356 } 357#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* iOS 13 */ 358 if (@available(iOS 13.0, *)) { 359 if (!_fraudulentWebsiteWarningEnabled) { 360 prefs.fraudulentWebsiteWarningEnabled = NO; 361 _prefsUsed = YES; 362 } 363 } 364#endif 365 if (_allowUniversalAccessFromFileURLs) { 366 [wkWebViewConfig setValue:@TRUE forKey:@"allowUniversalAccessFromFileURLs"]; 367 } 368 if (_allowFileAccessFromFileURLs) { 369 [prefs setValue:@TRUE forKey:@"allowFileAccessFromFileURLs"]; 370 _prefsUsed = YES; 371 } 372 if (_javaScriptCanOpenWindowsAutomatically) { 373 [prefs setValue:@TRUE forKey:@"javaScriptCanOpenWindowsAutomatically"]; 374 _prefsUsed = YES; 375 } 376#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140500 /* iOS 14.5 */ 377 if (@available(iOS 14.5, *)) { 378 if (!_textInteractionEnabled) { 379 [prefs setValue:@FALSE forKey:@"textInteractionEnabled"]; 380 _prefsUsed = YES; 381 } 382 } 383#endif 384 if (_prefsUsed) { 385 wkWebViewConfig.preferences = prefs; 386 } 387 if (_incognito) { 388 wkWebViewConfig.websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore]; 389 } else if (_cacheEnabled) { 390 wkWebViewConfig.websiteDataStore = [WKWebsiteDataStore defaultDataStore]; 391 } 392 if(self.useSharedProcessPool) { 393 wkWebViewConfig.processPool = [[RNCWKProcessPoolManager sharedManager] sharedProcessPoolForScopeKey:self.scopeKey]; 394 } 395 wkWebViewConfig.userContentController = [WKUserContentController new]; 396 397#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* iOS 13 */ 398 if (@available(iOS 13.0, *)) { 399 WKWebpagePreferences *pagePrefs = [[WKWebpagePreferences alloc]init]; 400 pagePrefs.preferredContentMode = _contentMode; 401 wkWebViewConfig.defaultWebpagePreferences = pagePrefs; 402 } 403#endif 404 405#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 /* iOS 14 */ 406 if (@available(iOS 14.0, *)) { 407 if ([wkWebViewConfig respondsToSelector:@selector(limitsNavigationsToAppBoundDomains)]) { 408 if (_limitsNavigationsToAppBoundDomains) { 409 wkWebViewConfig.limitsNavigationsToAppBoundDomains = YES; 410 } 411 } 412 } 413#endif 414 415 // Shim the HTML5 history API: 416 [wkWebViewConfig.userContentController addScriptMessageHandler:[[RNCWeakScriptMessageDelegate alloc] initWithDelegate:self] 417 name:HistoryShimName]; 418 [self resetupScripts:wkWebViewConfig]; 419 420 if(@available(macos 10.11, ios 9.0, *)) { 421 wkWebViewConfig.allowsAirPlayForMediaPlayback = _allowsAirPlayForMediaPlayback; 422 } 423 424#if !TARGET_OS_OSX 425 wkWebViewConfig.allowsInlineMediaPlayback = _allowsInlineMediaPlayback; 426 wkWebViewConfig.mediaTypesRequiringUserActionForPlayback = _mediaPlaybackRequiresUserAction 427 ? WKAudiovisualMediaTypeAll 428 : WKAudiovisualMediaTypeNone; 429 wkWebViewConfig.dataDetectorTypes = _dataDetectorTypes; 430#endif // !TARGET_OS_OSX 431 432 if (_applicationNameForUserAgent) { 433 wkWebViewConfig.applicationNameForUserAgent = [NSString stringWithFormat:@"%@ %@", wkWebViewConfig.applicationNameForUserAgent, _applicationNameForUserAgent]; 434 } 435 436 return wkWebViewConfig; 437} 438 439// react-native-mac os does not support didMoveToSuperView https://github.com/microsoft/react-native-macos/blob/main/React/Base/RCTUIKit.h#L388 440#if !TARGET_OS_OSX 441- (void)didMoveToSuperview 442{ 443 if (_webView == nil) { 444#else 445- (void)didMoveToWindow 446{ 447 if (self.window != nil && _webView == nil) { 448#endif // !TARGET_OS_OSX 449 WKWebViewConfiguration *wkWebViewConfig = [self setUpWkWebViewConfig]; 450 _webView = [[RNCWKWebView alloc] initWithFrame:self.bounds configuration: wkWebViewConfig]; 451 [self setBackgroundColor: _savedBackgroundColor]; 452#if !TARGET_OS_OSX 453 _webView.menuItems = _menuItems; 454 _webView.scrollView.delegate = self; 455#endif // !TARGET_OS_OSX 456 _webView.UIDelegate = self; 457 _webView.navigationDelegate = self; 458#if !TARGET_OS_OSX 459 if (_pullToRefreshEnabled) { 460 [self addPullToRefreshControl]; 461 } 462 _webView.scrollView.scrollEnabled = _scrollEnabled; 463 _webView.scrollView.pagingEnabled = _pagingEnabled; 464 //For UIRefreshControl to work correctly, the bounces should always be true 465 _webView.scrollView.bounces = _pullToRefreshEnabled || _bounces; 466 _webView.scrollView.showsHorizontalScrollIndicator = _showsHorizontalScrollIndicator; 467 _webView.scrollView.showsVerticalScrollIndicator = _showsVerticalScrollIndicator; 468 _webView.scrollView.directionalLockEnabled = _directionalLockEnabled; 469#endif // !TARGET_OS_OSX 470 _webView.allowsLinkPreview = _allowsLinkPreview; 471 [_webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil]; 472 _webView.allowsBackForwardNavigationGestures = _allowsBackForwardNavigationGestures; 473 474 _webView.customUserAgent = _userAgent; 475 476#if !TARGET_OS_OSX 477 if ([_webView.scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) { 478 _webView.scrollView.contentInsetAdjustmentBehavior = _savedContentInsetAdjustmentBehavior; 479 } 480#endif // !TARGET_OS_OSX 481 482#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* __IPHONE_13_0 */ 483 if (@available(iOS 13.0, *)) { 484 _webView.scrollView.automaticallyAdjustsScrollIndicatorInsets = _savedAutomaticallyAdjustsScrollIndicatorInsets; 485 } 486#endif 487 488#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 130300 || \ 489 __IPHONE_OS_VERSION_MAX_ALLOWED >= 160400 || \ 490 __TV_OS_VERSION_MAX_ALLOWED >= 160400 491 if (@available(macOS 13.3, iOS 16.4, tvOS 16.4, *)) 492 _webView.inspectable = _webviewDebuggingEnabled; 493#endif 494 495 [self addSubview:_webView]; 496 [self setHideKeyboardAccessoryView: _savedHideKeyboardAccessoryView]; 497 [self setKeyboardDisplayRequiresUserAction: _savedKeyboardDisplayRequiresUserAction]; 498 [self visitSource]; 499 } 500#if !TARGET_OS_OSX 501 // Allow this object to recognize gestures 502 if (self.menuItems != nil) { 503 UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(startLongPress:)]; 504 longPress.delegate = self; 505 506 longPress.minimumPressDuration = 0.4f; 507 longPress.numberOfTouchesRequired = 1; 508 longPress.cancelsTouchesInView = YES; 509 [self addGestureRecognizer:longPress]; 510 } 511#endif // !TARGET_OS_OSX 512} 513 514// Update webview property when the component prop changes. 515- (void)setAllowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures { 516 _allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures; 517 _webView.allowsBackForwardNavigationGestures = _allowsBackForwardNavigationGestures; 518} 519 520#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 130300 || \ 521 __IPHONE_OS_VERSION_MAX_ALLOWED >= 160400 || \ 522 __TV_OS_VERSION_MAX_ALLOWED >= 160400 523- (void)setWebviewDebuggingEnabled:(BOOL)webviewDebuggingEnabled { 524 _webviewDebuggingEnabled = webviewDebuggingEnabled; 525 if (@available(macOS 13.3, iOS 16.4, tvOS 16.4, *)) 526 _webView.inspectable = _webviewDebuggingEnabled; 527} 528#endif 529 530#ifdef RCT_NEW_ARCH_ENABLED 531- (void)destroyWebView 532#else 533- (void)removeFromSuperview 534#endif 535{ 536 if (_webView) { 537 [_webView.configuration.userContentController removeScriptMessageHandlerForName:HistoryShimName]; 538 [_webView.configuration.userContentController removeScriptMessageHandlerForName:MessageHandlerName]; 539 [_webView removeObserver:self forKeyPath:@"estimatedProgress"]; 540 [_webView removeFromSuperview]; 541#if !TARGET_OS_OSX 542 _webView.scrollView.delegate = nil; 543 if (_menuItems) { 544 UIMenuController *menuController = [UIMenuController sharedMenuController]; 545 menuController.menuItems = nil; 546 } 547#endif // !TARGET_OS_OSX 548 _webView = nil; 549 if (_onContentProcessDidTerminate) { 550 NSMutableDictionary<NSString *, id> *event = [self baseEvent]; 551 _onContentProcessDidTerminate(event); 552 } 553 } 554 555#ifndef RCT_NEW_ARCH_ENABLED 556 [super removeFromSuperview]; 557#endif 558} 559 560#if !TARGET_OS_OSX 561-(void)showFullScreenVideoStatusBars 562{ 563#pragma clang diagnostic ignored "-Wdeprecated-declarations" 564 if (!_autoManageStatusBarEnabled) { 565 return; 566 } 567 568 _isFullScreenVideoOpen = YES; 569 RCTUnsafeExecuteOnMainQueueSync(^{ 570 [RCTSharedApplication() setStatusBarStyle:self->_savedStatusBarStyle animated:YES]; 571 }); 572#pragma clang diagnostic pop 573} 574 575-(void)hideFullScreenVideoStatusBars 576{ 577#pragma clang diagnostic ignored "-Wdeprecated-declarations" 578 if (!_autoManageStatusBarEnabled) { 579 return; 580 } 581 582 _isFullScreenVideoOpen = NO; 583 RCTUnsafeExecuteOnMainQueueSync(^{ 584 [RCTSharedApplication() setStatusBarHidden:self->_savedStatusBarHidden animated:YES]; 585 [RCTSharedApplication() setStatusBarStyle:self->_savedStatusBarStyle animated:YES]; 586 }); 587#pragma clang diagnostic pop 588} 589 590-(void)keyboardWillHide 591{ 592 keyboardTimer = [NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(keyboardDisplacementFix) userInfo:nil repeats:false]; 593 [[NSRunLoop mainRunLoop] addTimer:keyboardTimer forMode:NSRunLoopCommonModes]; 594} 595-(void)keyboardWillShow 596{ 597 if (keyboardTimer != nil) { 598 [keyboardTimer invalidate]; 599 } 600} 601-(void)keyboardDisplacementFix 602{ 603 // Additional viewport checks to prevent unintentional scrolls 604 UIScrollView *scrollView = self.webView.scrollView; 605 double maxContentOffset = scrollView.contentSize.height - scrollView.frame.size.height; 606 if (maxContentOffset < 0) { 607 maxContentOffset = 0; 608 } 609 if (scrollView.contentOffset.y > maxContentOffset) { 610 // https://stackoverflow.com/a/9637807/824966 611 [UIView animateWithDuration:.25 animations:^{ 612 scrollView.contentOffset = CGPointMake(0, maxContentOffset); 613 }]; 614 } 615} 616#endif // !TARGET_OS_OSX 617 618- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ 619 if ([keyPath isEqual:@"estimatedProgress"] && object == self.webView) { 620 if(_onLoadingProgress){ 621 NSMutableDictionary<NSString *, id> *event = [self baseEvent]; 622 [event addEntriesFromDictionary:@{@"progress":[NSNumber numberWithDouble:self.webView.estimatedProgress]}]; 623 _onLoadingProgress(event); 624 } 625 }else{ 626 [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 627 } 628} 629 630#if !TARGET_OS_OSX 631- (void)setBackgroundColor:(UIColor *)backgroundColor 632#else 633- (void)setBackgroundColor:(RCTUIColor *)backgroundColor 634#endif // !TARGET_OS_OSX 635{ 636 _savedBackgroundColor = backgroundColor; 637 if (_webView == nil) { 638 return; 639 } 640 641 CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor); 642 BOOL opaque = (alpha == 1.0); 643#if !TARGET_OS_OSX 644 self.opaque = _webView.opaque = opaque; 645 _webView.scrollView.backgroundColor = backgroundColor; 646 _webView.backgroundColor = backgroundColor; 647#else 648 // https://stackoverflow.com/questions/40007753/macos-wkwebview-background-transparency 649 NSOperatingSystemVersion version = { 10, 12, 0 }; 650 if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version]) { 651 [_webView setValue:@(opaque) forKey: @"drawsBackground"]; 652 } else { 653 [_webView setValue:@(!opaque) forKey: @"drawsTransparentBackground"]; 654 } 655#endif // !TARGET_OS_OSX 656} 657 658#if !TARGET_OS_OSX 659- (void)setContentInsetAdjustmentBehavior:(UIScrollViewContentInsetAdjustmentBehavior)behavior 660{ 661 _savedContentInsetAdjustmentBehavior = behavior; 662 if (_webView == nil) { 663 return; 664 } 665 666 if ([_webView.scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) { 667 CGPoint contentOffset = _webView.scrollView.contentOffset; 668 _webView.scrollView.contentInsetAdjustmentBehavior = behavior; 669 _webView.scrollView.contentOffset = contentOffset; 670 } 671} 672#endif // !TARGET_OS_OSX 673 674#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* __IPHONE_13_0 */ 675- (void)setAutomaticallyAdjustsScrollIndicatorInsets:(BOOL)automaticallyAdjustsScrollIndicatorInsets{ 676 _savedAutomaticallyAdjustsScrollIndicatorInsets = automaticallyAdjustsScrollIndicatorInsets; 677 if (_webView == nil) { 678 return; 679 } 680 if ([_webView.scrollView respondsToSelector:@selector(setAutomaticallyAdjustsScrollIndicatorInsets:)]) { 681 _webView.scrollView.automaticallyAdjustsScrollIndicatorInsets = automaticallyAdjustsScrollIndicatorInsets; 682 } 683} 684#endif 685 686/** 687 * This method is called whenever JavaScript running within the web view calls: 688 * - window.webkit.messageHandlers[MessageHandlerName].postMessage 689 */ 690- (void)userContentController:(WKUserContentController *)userContentController 691 didReceiveScriptMessage:(WKScriptMessage *)message 692{ 693 if ([message.name isEqualToString:HistoryShimName]) { 694 if (_onLoadingFinish) { 695 NSMutableDictionary<NSString *, id> *event = [self baseEvent]; 696 [event addEntriesFromDictionary: @{@"navigationType": message.body}]; 697 _onLoadingFinish(event); 698 } 699 } else if ([message.name isEqualToString:MessageHandlerName]) { 700 if (_onMessage) { 701 NSMutableDictionary<NSString *, id> *event = [self baseEvent]; 702 [event addEntriesFromDictionary: @{@"data": message.body}]; 703 _onMessage(event); 704 } 705 } 706} 707 708- (void)setSource:(NSDictionary *)source 709{ 710 if (![_source isEqualToDictionary:source]) { 711 _source = [source copy]; 712 713 if (_webView != nil) { 714 [self visitSource]; 715 } 716 } 717} 718 719- (void)setAllowingReadAccessToURL:(NSString *)allowingReadAccessToURL 720{ 721 if (![_allowingReadAccessToURL isEqualToString:allowingReadAccessToURL]) { 722 _allowingReadAccessToURL = [allowingReadAccessToURL copy]; 723 724 if (_webView != nil) { 725 [self visitSource]; 726 } 727 } 728} 729 730#if !TARGET_OS_OSX 731- (void)setContentInset:(UIEdgeInsets)contentInset 732{ 733 _contentInset = contentInset; 734 [RCTView autoAdjustInsetsForView:self 735 withScrollView:_webView.scrollView 736 updateOffset:NO]; 737} 738 739- (void)refreshContentInset 740{ 741 [RCTView autoAdjustInsetsForView:self 742 withScrollView:_webView.scrollView 743 updateOffset:YES]; 744} 745#endif // !TARGET_OS_OSX 746 747- (void)visitSource 748{ 749 // Check for a static html source first 750 NSString *html = [RCTConvert NSString:_source[@"html"]]; 751 if (html) { 752 NSURL *baseURL = [RCTConvert NSURL:_source[@"baseUrl"]]; 753 if (!baseURL) { 754 baseURL = [NSURL URLWithString:@"about:blank"]; 755 } 756 [_webView loadHTMLString:html baseURL:baseURL]; 757 return; 758 } 759 // Add cookie for subsequent resource requests sent by page itself, if cookie was set in headers on WebView 760 NSString *headerCookie = [RCTConvert NSString:_source[@"headers"][@"cookie"]]; 761 if(headerCookie) { 762 NSDictionary *headers = [NSDictionary dictionaryWithObjectsAndKeys:headerCookie,@"Set-Cookie",nil]; 763 NSURL *urlString = [NSURL URLWithString:_source[@"uri"]]; 764 NSArray *httpCookies = [NSHTTPCookie cookiesWithResponseHeaderFields:headers forURL:urlString]; 765 [self writeCookiesToWebView:httpCookies completion:nil]; 766 } 767 768 NSURLRequest *request = [self requestForSource:_source]; 769 __weak WKWebView *webView = _webView; 770 NSString *allowingReadAccessToURL = _allowingReadAccessToURL; 771 772 [self syncCookiesToWebView:^{ 773 // Because of the way React works, as pages redirect, we actually end up 774 // passing the redirect urls back here, so we ignore them if trying to load 775 // the same url. We'll expose a call to 'reload' to allow a user to load 776 // the existing page. 777 if ([request.URL isEqual:webView.URL]) { 778 return; 779 } 780 if (!request.URL) { 781 // Clear the webview 782 [webView loadHTMLString:@"" baseURL:nil]; 783 return; 784 } 785 if (request.URL.host) { 786 [webView loadRequest:request]; 787 } 788 else { 789 NSURL* readAccessUrl = allowingReadAccessToURL ? [RCTConvert NSURL:allowingReadAccessToURL] : request.URL; 790 [webView loadFileURL:request.URL allowingReadAccessToURL:readAccessUrl]; 791 } 792 }]; 793} 794 795#if !TARGET_OS_OSX 796-(void)setMenuItems:(NSArray<NSDictionary *> *)menuItems { 797 _menuItems = menuItems; 798 _webView.menuItems = menuItems; 799} 800 801-(void)setKeyboardDisplayRequiresUserAction:(BOOL)keyboardDisplayRequiresUserAction 802{ 803 if (_webView == nil) { 804 _savedKeyboardDisplayRequiresUserAction = keyboardDisplayRequiresUserAction; 805 return; 806 } 807 808 if (_savedKeyboardDisplayRequiresUserAction == true) { 809 return; 810 } 811 812 UIView* subview; 813 814 for (UIView* view in _webView.scrollView.subviews) { 815 if([[view.class description] hasPrefix:@"WK"]) 816 subview = view; 817 } 818 819 if(subview == nil) return; 820 821 Class class = subview.class; 822 823 NSOperatingSystemVersion iOS_11_3_0 = (NSOperatingSystemVersion){11, 3, 0}; 824 NSOperatingSystemVersion iOS_12_2_0 = (NSOperatingSystemVersion){12, 2, 0}; 825 NSOperatingSystemVersion iOS_13_0_0 = (NSOperatingSystemVersion){13, 0, 0}; 826 827 Method method; 828 IMP override; 829 830 if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_13_0_0]) { 831 // iOS 13.0.0 - Future 832 SEL selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:"); 833 method = class_getInstanceMethod(class, selector); 834 IMP original = method_getImplementation(method); 835 override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) { 836 ((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4); 837 }); 838 } 839 else if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_12_2_0]) { 840 // iOS 12.2.0 - iOS 13.0.0 841 SEL selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:changingActivityState:userObject:"); 842 method = class_getInstanceMethod(class, selector); 843 IMP original = method_getImplementation(method); 844 override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) { 845 ((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4); 846 }); 847 } 848 else if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_11_3_0]) { 849 // iOS 11.3.0 - 12.2.0 850 SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:"); 851 method = class_getInstanceMethod(class, selector); 852 IMP original = method_getImplementation(method); 853 override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) { 854 ((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4); 855 }); 856 } else { 857 // iOS 9.0 - 11.3.0 858 SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:"); 859 method = class_getInstanceMethod(class, selector); 860 IMP original = method_getImplementation(method); 861 override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, id arg3) { 862 ((void (*)(id, SEL, void*, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3); 863 }); 864 } 865 866 method_setImplementation(method, override); 867} 868 869-(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView 870{ 871 if (_webView == nil) { 872 _savedHideKeyboardAccessoryView = hideKeyboardAccessoryView; 873 return; 874 } 875 876 if (_savedHideKeyboardAccessoryView == false) { 877 return; 878 } 879 880 UIView* subview; 881 882 for (UIView* view in _webView.scrollView.subviews) { 883 if([[view.class description] hasPrefix:@"WK"]) 884 subview = view; 885 } 886 887 if(subview == nil) return; 888 889 NSString* name = [NSString stringWithFormat:@"%@_SwizzleHelperWK", subview.class.superclass]; 890 Class newClass = NSClassFromString(name); 891 892 if(newClass == nil) 893 { 894 newClass = objc_allocateClassPair(subview.class, [name cStringUsingEncoding:NSASCIIStringEncoding], 0); 895 if(!newClass) return; 896 897 Method method = class_getInstanceMethod([_SwizzleHelperWK class], @selector(inputAccessoryView)); 898 class_addMethod(newClass, @selector(inputAccessoryView), method_getImplementation(method), method_getTypeEncoding(method)); 899 900 objc_registerClassPair(newClass); 901 } 902 903 object_setClass(subview, newClass); 904} 905 906// UIScrollViewDelegate method 907- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView 908{ 909 scrollView.decelerationRate = _decelerationRate; 910} 911#endif // !TARGET_OS_OSX 912 913- (void)setUserAgent:(NSString*)userAgent 914{ 915 _userAgent = userAgent; 916 _webView.customUserAgent = userAgent; 917} 918 919- (void)setScrollEnabled:(BOOL)scrollEnabled 920{ 921 _scrollEnabled = scrollEnabled; 922#if !TARGET_OS_OSX 923 _webView.scrollView.scrollEnabled = scrollEnabled; 924#endif // !TARGET_OS_OSX 925} 926 927#if !TARGET_OS_OSX 928// UIScrollViewDelegate method 929- (void)scrollViewDidScroll:(UIScrollView *)scrollView 930{ 931 // Don't allow scrolling the scrollView. 932 if (!_scrollEnabled) { 933 scrollView.bounds = _webView.bounds; 934 } 935 else if (_onScroll != nil) { 936 NSDictionary *event = @{ 937 @"contentOffset": @{ 938 @"x": @(scrollView.contentOffset.x), 939 @"y": @(scrollView.contentOffset.y) 940 }, 941 @"contentInset": @{ 942 @"top": @(scrollView.contentInset.top), 943 @"left": @(scrollView.contentInset.left), 944 @"bottom": @(scrollView.contentInset.bottom), 945 @"right": @(scrollView.contentInset.right) 946 }, 947 @"contentSize": @{ 948 @"width": @(scrollView.contentSize.width), 949 @"height": @(scrollView.contentSize.height) 950 }, 951 @"layoutMeasurement": @{ 952 @"width": @(scrollView.frame.size.width), 953 @"height": @(scrollView.frame.size.height) 954 }, 955 @"zoomScale": @(scrollView.zoomScale ?: 1), 956 }; 957 _onScroll(event); 958 } 959} 960 961- (void)setDirectionalLockEnabled:(BOOL)directionalLockEnabled 962{ 963 _directionalLockEnabled = directionalLockEnabled; 964 _webView.scrollView.directionalLockEnabled = directionalLockEnabled; 965} 966 967- (void)setShowsHorizontalScrollIndicator:(BOOL)showsHorizontalScrollIndicator 968{ 969 _showsHorizontalScrollIndicator = showsHorizontalScrollIndicator; 970 _webView.scrollView.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator; 971} 972 973- (void)setShowsVerticalScrollIndicator:(BOOL)showsVerticalScrollIndicator 974{ 975 _showsVerticalScrollIndicator = showsVerticalScrollIndicator; 976 _webView.scrollView.showsVerticalScrollIndicator = showsVerticalScrollIndicator; 977} 978#endif // !TARGET_OS_OSX 979 980- (void)postMessage:(NSString *)message 981{ 982 NSDictionary *eventInitDict = @{@"data": message}; 983 NSString *source = [NSString 984 stringWithFormat:@"window.dispatchEvent(new MessageEvent('message', %@));", 985 RCTJSONStringify(eventInitDict, NULL) 986 ]; 987 [self injectJavaScript: source]; 988} 989 990- (void)layoutSubviews 991{ 992 [super layoutSubviews]; 993 994 // Ensure webview takes the position and dimensions of RNCWebViewImpl 995 _webView.frame = self.bounds; 996#if !TARGET_OS_OSX 997 _webView.scrollView.contentInset = _contentInset; 998#endif // !TARGET_OS_OSX 999} 1000 1001- (NSMutableDictionary<NSString *, id> *)baseEvent 1002{ 1003 NSDictionary *event = @{ 1004 @"url": _webView.URL.absoluteString ?: @"", 1005 @"title": _webView.title ?: @"", 1006 @"loading" : @(_webView.loading), 1007 @"canGoBack": @(_webView.canGoBack), 1008 @"canGoForward" : @(_webView.canGoForward) 1009 }; 1010 return [[NSMutableDictionary alloc] initWithDictionary: event]; 1011} 1012 1013+ (void)setClientAuthenticationCredential:(nullable NSURLCredential*)credential { 1014 clientAuthenticationCredential = credential; 1015} 1016 1017+ (void)setCustomCertificatesForHost:(nullable NSDictionary*)certificates { 1018 customCertificatesForHost = certificates; 1019} 1020 1021- (void) webView:(WKWebView *)webView 1022 didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 1023 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable))completionHandler 1024{ 1025 NSString* host = nil; 1026 if (webView.URL != nil) { 1027 host = webView.URL.host; 1028 } 1029 if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate) { 1030 completionHandler(NSURLSessionAuthChallengeUseCredential, clientAuthenticationCredential); 1031 return; 1032 } 1033 if ([[challenge protectionSpace] serverTrust] != nil && customCertificatesForHost != nil && host != nil) { 1034 SecCertificateRef localCertificate = (__bridge SecCertificateRef)([customCertificatesForHost objectForKey:host]); 1035 if (localCertificate != nil) { 1036 NSData *localCertificateData = (NSData*) CFBridgingRelease(SecCertificateCopyData(localCertificate)); 1037 SecTrustRef trust = [[challenge protectionSpace] serverTrust]; 1038 long count = SecTrustGetCertificateCount(trust); 1039 for (long i = 0; i < count; i++) { 1040 SecCertificateRef serverCertificate = SecTrustGetCertificateAtIndex(trust, i); 1041 if (serverCertificate == nil) { continue; } 1042 NSData *serverCertificateData = (NSData *) CFBridgingRelease(SecCertificateCopyData(serverCertificate)); 1043 if ([serverCertificateData isEqualToData:localCertificateData]) { 1044 NSURLCredential *useCredential = [NSURLCredential credentialForTrust:trust]; 1045 if (challenge.sender != nil) { 1046 [challenge.sender useCredential:useCredential forAuthenticationChallenge:challenge]; 1047 } 1048 completionHandler(NSURLSessionAuthChallengeUseCredential, useCredential); 1049 return; 1050 } 1051 } 1052 } 1053 } 1054 if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodHTTPBasic) { 1055 NSString *username = [_basicAuthCredential valueForKey:@"username"]; 1056 NSString *password = [_basicAuthCredential valueForKey:@"password"]; 1057 if (username && password) { 1058 NSURLCredential *credential = [NSURLCredential credentialWithUser:username password:password persistence:NSURLCredentialPersistenceNone]; 1059 completionHandler(NSURLSessionAuthChallengeUseCredential, credential); 1060 return; 1061 } 1062 } 1063 completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); 1064} 1065 1066#pragma mark - WKNavigationDelegate methods 1067 1068/** 1069 * alert 1070 */ 1071- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler 1072{ 1073#if !TARGET_OS_OSX 1074 UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert]; 1075 [alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { 1076 completionHandler(); 1077 }]]; 1078 [[self topViewController] presentViewController:alert animated:YES completion:NULL]; 1079#else 1080 NSAlert *alert = [[NSAlert alloc] init]; 1081 [alert setMessageText:message]; 1082 [alert beginSheetModalForWindow:[NSApp keyWindow] completionHandler:^(__unused NSModalResponse response){ 1083 completionHandler(); 1084 }]; 1085#endif // !TARGET_OS_OSX 1086} 1087 1088/** 1089 * confirm 1090 */ 1091- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{ 1092#if !TARGET_OS_OSX 1093 UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert]; 1094 [alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { 1095 completionHandler(YES); 1096 }]]; 1097 [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { 1098 completionHandler(NO); 1099 }]]; 1100 [[self topViewController] presentViewController:alert animated:YES completion:NULL]; 1101#else 1102 NSAlert *alert = [[NSAlert alloc] init]; 1103 [alert setMessageText:message]; 1104 [alert addButtonWithTitle:NSLocalizedString(@"OK", @"OK button")]; 1105 [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"Cancel button")]; 1106 void (^callbacksHandlers)(NSModalResponse response) = ^void(NSModalResponse response) { 1107 completionHandler(response == NSAlertFirstButtonReturn); 1108 }; 1109 [alert beginSheetModalForWindow:[NSApp keyWindow] completionHandler:callbacksHandlers]; 1110#endif // !TARGET_OS_OSX 1111} 1112 1113/** 1114 * prompt 1115 */ 1116- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler{ 1117#if !TARGET_OS_OSX 1118 UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:prompt preferredStyle:UIAlertControllerStyleAlert]; 1119 [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) { 1120 textField.text = defaultText; 1121 }]; 1122 UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { 1123 completionHandler([[alert.textFields lastObject] text]); 1124 }]; 1125 [alert addAction:okAction]; 1126 UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { 1127 completionHandler(nil); 1128 }]; 1129 [alert addAction:cancelAction]; 1130 alert.preferredAction = okAction; 1131 [[self topViewController] presentViewController:alert animated:YES completion:NULL]; 1132#else 1133 NSAlert *alert = [[NSAlert alloc] init]; 1134 [alert setMessageText:prompt]; 1135 1136 const NSRect RCTSingleTextFieldFrame = NSMakeRect(0.0, 0.0, 275.0, 22.0); 1137 NSTextField *textField = [[NSTextField alloc] initWithFrame:RCTSingleTextFieldFrame]; 1138 textField.cell.scrollable = YES; 1139 if (@available(macOS 10.11, *)) { 1140 textField.maximumNumberOfLines = 1; 1141 } 1142 textField.stringValue = defaultText; 1143 [alert setAccessoryView:textField]; 1144 1145 [alert addButtonWithTitle:NSLocalizedString(@"OK", @"OK button")]; 1146 [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"Cancel button")]; 1147 [alert beginSheetModalForWindow:[NSApp keyWindow] completionHandler:^(NSModalResponse response) { 1148 if (response == NSAlertFirstButtonReturn) { 1149 completionHandler([textField stringValue]); 1150 } else { 1151 completionHandler(nil); 1152 } 1153 }]; 1154#endif // !TARGET_OS_OSX 1155} 1156 1157#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000 /* iOS 15 */ 1158/** 1159 * Media capture permissions (prevent multiple prompts) 1160 */ 1161- (void) webView:(WKWebView *)webView 1162 requestMediaCapturePermissionForOrigin:(WKSecurityOrigin *)origin 1163 initiatedByFrame:(WKFrameInfo *)frame 1164 type:(WKMediaCaptureType)type 1165 decisionHandler:(void (^)(WKPermissionDecision decision))decisionHandler { 1166 if (_mediaCapturePermissionGrantType == RNCWebViewPermissionGrantType_GrantIfSameHost_ElsePrompt || _mediaCapturePermissionGrantType == RNCWebViewPermissionGrantType_GrantIfSameHost_ElseDeny) { 1167 if ([origin.host isEqualToString:webView.URL.host]) { 1168 decisionHandler(WKPermissionDecisionGrant); 1169 } else { 1170 WKPermissionDecision decision = _mediaCapturePermissionGrantType == RNCWebViewPermissionGrantType_GrantIfSameHost_ElsePrompt ? WKPermissionDecisionPrompt : WKPermissionDecisionDeny; 1171 decisionHandler(decision); 1172 } 1173 } else if (_mediaCapturePermissionGrantType == RNCWebViewPermissionGrantType_Deny) { 1174 decisionHandler(WKPermissionDecisionDeny); 1175 } else if (_mediaCapturePermissionGrantType == RNCWebViewPermissionGrantType_Grant) { 1176 decisionHandler(WKPermissionDecisionGrant); 1177 } else { 1178 decisionHandler(WKPermissionDecisionPrompt); 1179 } 1180} 1181#endif 1182 1183#if !TARGET_OS_OSX 1184/** 1185 * topViewController 1186 */ 1187-(UIViewController *)topViewController{ 1188 return RCTPresentedViewController(); 1189} 1190 1191#endif // !TARGET_OS_OSX 1192 1193/** 1194 * Decides whether to allow or cancel a navigation. 1195 * @see https://fburl.com/42r9fxob 1196 */ 1197- (void) webView:(WKWebView *)webView 1198 decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction 1199 decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler 1200{ 1201 static NSDictionary<NSNumber *, NSString *> *navigationTypes; 1202 static dispatch_once_t onceToken; 1203 1204 dispatch_once(&onceToken, ^{ 1205 navigationTypes = @{ 1206 @(WKNavigationTypeLinkActivated): @"click", 1207 @(WKNavigationTypeFormSubmitted): @"formsubmit", 1208 @(WKNavigationTypeBackForward): @"backforward", 1209 @(WKNavigationTypeReload): @"reload", 1210 @(WKNavigationTypeFormResubmitted): @"formresubmit", 1211 @(WKNavigationTypeOther): @"other", 1212 }; 1213 }); 1214 1215 WKNavigationType navigationType = navigationAction.navigationType; 1216 NSURLRequest *request = navigationAction.request; 1217 BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL]; 1218 1219 if (_onShouldStartLoadWithRequest) { 1220 NSMutableDictionary<NSString *, id> *event = [self baseEvent]; 1221 int lockIdentifier = [[RNCWebViewDecisionManager getInstance] setDecisionHandler: ^(BOOL shouldStart){ 1222 dispatch_async(dispatch_get_main_queue(), ^{ 1223 if (!shouldStart) { 1224 decisionHandler(WKNavigationActionPolicyCancel); 1225 return; 1226 } 1227 if (self->_onLoadingStart) { 1228 // We have this check to filter out iframe requests and whatnot 1229 if (isTopFrame) { 1230 NSMutableDictionary<NSString *, id> *event = [self baseEvent]; 1231 [event addEntriesFromDictionary: @{ 1232 @"url": (request.URL).absoluteString, 1233 @"navigationType": navigationTypes[@(navigationType)] 1234 }]; 1235 self->_onLoadingStart(event); 1236 } 1237 } 1238 1239 // Allow all navigation by default 1240 decisionHandler(WKNavigationActionPolicyAllow); 1241 }); 1242 1243 }]; 1244 if (request.mainDocumentURL) { 1245 [event addEntriesFromDictionary: @{ 1246 @"mainDocumentURL": (request.mainDocumentURL).absoluteString, 1247 }]; 1248 } 1249 [event addEntriesFromDictionary: @{ 1250 @"url": (request.URL).absoluteString, 1251 @"navigationType": navigationTypes[@(navigationType)], 1252 @"isTopFrame": @(isTopFrame), 1253 @"lockIdentifier": @(lockIdentifier) 1254 }]; 1255 _onShouldStartLoadWithRequest(event); 1256 // decisionHandler(WKNavigationActionPolicyAllow); 1257 return; 1258 } 1259 1260 if (_onLoadingStart) { 1261 // We have this check to filter out iframe requests and whatnot 1262 if (isTopFrame) { 1263 NSMutableDictionary<NSString *, id> *event = [self baseEvent]; 1264 [event addEntriesFromDictionary: @{ 1265 @"url": (request.URL).absoluteString, 1266 @"navigationType": navigationTypes[@(navigationType)] 1267 }]; 1268 _onLoadingStart(event); 1269 } 1270 } 1271 1272 // Allow all navigation by default 1273 decisionHandler(WKNavigationActionPolicyAllow); 1274} 1275 1276/** 1277 * Called when the web view’s content process is terminated. 1278 * @see https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455639-webviewwebcontentprocessdidtermi?language=objc 1279 */ 1280- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView 1281{ 1282 RCTLogWarn(@"Webview Process Terminated"); 1283 if (_onContentProcessDidTerminate) { 1284 NSMutableDictionary<NSString *, id> *event = [self baseEvent]; 1285 _onContentProcessDidTerminate(event); 1286 } 1287} 1288 1289/** 1290 * Decides whether to allow or cancel a navigation after its response is known. 1291 * @see https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455643-webview?language=objc 1292 */ 1293- (void) webView:(WKWebView *)webView 1294 decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse 1295 decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler 1296{ 1297 WKNavigationResponsePolicy policy = WKNavigationResponsePolicyAllow; 1298 if (_onHttpError && navigationResponse.forMainFrame) { 1299 if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) { 1300 NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response; 1301 NSInteger statusCode = response.statusCode; 1302 1303 if (statusCode >= 400) { 1304 NSMutableDictionary<NSString *, id> *httpErrorEvent = [self baseEvent]; 1305 [httpErrorEvent addEntriesFromDictionary: @{ 1306 @"url": response.URL.absoluteString, 1307 @"statusCode": @(statusCode) 1308 }]; 1309 1310 _onHttpError(httpErrorEvent); 1311 } 1312 1313 NSString *disposition = nil; 1314 if (@available(iOS 13, macOS 10.15, *)) { 1315 disposition = [response valueForHTTPHeaderField:@"Content-Disposition"]; 1316 } 1317 BOOL isAttachment = disposition != nil && [disposition hasPrefix:@"attachment"]; 1318 if (isAttachment || !navigationResponse.canShowMIMEType) { 1319 if (_onFileDownload) { 1320 policy = WKNavigationResponsePolicyCancel; 1321 1322 NSMutableDictionary<NSString *, id> *downloadEvent = [self baseEvent]; 1323 [downloadEvent addEntriesFromDictionary: @{ 1324 @"downloadUrl": (response.URL).absoluteString, 1325 }]; 1326 _onFileDownload(downloadEvent); 1327 } 1328 } 1329 } 1330 } 1331 1332 decisionHandler(policy); 1333} 1334 1335/** 1336 * Called when an error occurs while the web view is loading content. 1337 * @see https://fburl.com/km6vqenw 1338 */ 1339- (void) webView:(WKWebView *)webView 1340 didFailProvisionalNavigation:(WKNavigation *)navigation 1341 withError:(NSError *)error 1342{ 1343 if (_onLoadingError) { 1344 if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { 1345 // NSURLErrorCancelled is reported when a page has a redirect OR if you load 1346 // a new URL in the WebView before the previous one came back. We can just 1347 // ignore these since they aren't real errors. 1348 // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os 1349 return; 1350 } 1351 1352 if ([error.domain isEqualToString:@"WebKitErrorDomain"] && 1353 (error.code == 102 || error.code == 101)) { 1354 // Error code 102 "Frame load interrupted" is raised by the WKWebView 1355 // when the URL is from an http redirect. This is a common pattern when 1356 // implementing OAuth with a WebView. 1357 return; 1358 } 1359 1360 NSMutableDictionary<NSString *, id> *event = [self baseEvent]; 1361 [event addEntriesFromDictionary:@{ 1362 @"didFailProvisionalNavigation": @YES, 1363 @"domain": error.domain, 1364 @"code": @(error.code), 1365 @"description": error.localizedDescription, 1366 }]; 1367 _onLoadingError(event); 1368 } 1369} 1370 1371- (void)evaluateJS:(NSString *)js 1372 thenCall: (void (^)(NSString*)) callback 1373{ 1374 if (self.enableApplePay) { 1375 RCTLogWarn(@"Cannot run javascript when apple pay is enabled"); 1376 return; 1377 } 1378 [self.webView evaluateJavaScript: js completionHandler: ^(id result, NSError *error) { 1379 if (callback != nil) { 1380 callback([NSString stringWithFormat:@"%@", result]); 1381 } 1382 if (error != nil) { 1383 RCTLogWarn(@"%@", [NSString stringWithFormat:@"Error evaluating injectedJavaScript: This is possibly due to an unsupported return type. Try adding true to the end of your injectedJavaScript string. %@", error]); 1384 } 1385 }]; 1386} 1387 1388-(void)forceIgnoreSilentHardwareSwitch:(BOOL)initialSetup 1389{ 1390 NSString *mp3Str = @"data:audio/mp3;base64,//tAxAAAAAAAAAAAAAAAAAAAAAAASW5mbwAAAA8AAAAFAAAESAAzMzMzMzMzMzMzMzMzMzMzMzMzZmZmZmZmZmZmZmZmZmZmZmZmZmaZmZmZmZmZmZmZmZmZmZmZmZmZmczMzMzMzMzMzMzMzMzMzMzMzMzM//////////////////////////8AAAA5TEFNRTMuMTAwAZYAAAAAAAAAABQ4JAMGQgAAOAAABEhNIZS0AAAAAAD/+0DEAAPH3Yz0AAR8CPqyIEABp6AxjG/4x/XiInE4lfQDFwIIRE+uBgZoW4RL0OLMDFn6E5v+/u5ehf76bu7/6bu5+gAiIQGAABQIUJ0QolFghEn/9PhZQpcUTpXMjo0OGzRCZXyKxoIQzB2KhCtGobpT9TRVj/3Pmfp+f8X7Pu1B04sTnc3s0XhOlXoGVCMNo9X//9/r6a10TZEY5DsxqvO7mO5qFvpFCmKIjhpSItGsUYcRO//7QsQRgEiljQIAgLFJAbIhNBCa+JmorCbOi5q9nVd2dKnusTMQg4MFUlD6DQ4OFijwGAijRMfLbHG4nLVTjydyPlJTj8pfPflf9/5GD950A5e+jsrmNZSjSirjs1R7hnkia8vr//l/7Nb+crvr9Ok5ZJOylUKRxf/P9Zn0j2P4pJYXyKkeuy5wUYtdmOu6uobEtFqhIJViLEKIjGxchGev/L3Y0O3bwrIOszTBAZ7Ih28EUaSOZf/7QsQfg8fpjQIADN0JHbGgQBAZ8T//y//t/7d/2+f5m7MdCeo/9tdkMtGLbt1tqnabRroO1Qfvh20yEbei8nfDXP7btW7f9/uO9tbe5IvHQbLlxpf3DkAk0ojYcv///5/u3/7PTfGjPEPUvt5D6f+/3Lea4lz4tc4TnM/mFPrmalWbboeNiNyeyr+vufttZuvrVrt/WYv3T74JFo8qEDiJqJrmDTs///v99xDku2xG02jjunrICP/7QsQtA8kpkQAAgNMA/7FgQAGnobgfghgqA+uXwWQ3XFmGimSbe2X3ksY//KzK1a2k6cnNWOPJnPWUsYbKqkh8RJzrVf///P///////4vyhLKHLrCb5nIrYIUss4cthigL1lQ1wwNAc6C1pf1TIKRSkt+a//z+yLVcwlXKSqeSuCVQFLng2h4AFAFgTkH+Z/8jTX/zr//zsJV/5f//5UX/0ZNCNCCaf5lTCTRkaEdhNP//n/KUjf/7QsQ5AEhdiwAAjN7I6jGddBCO+WGTQ1mXrYatSAgaykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqg=="; 1391 NSString *scr; 1392 if (initialSetup) { 1393 scr = [NSString stringWithFormat:@"var s=new Audio('%@');s.id='wkwebviewAudio';s.controls=false;s.loop=true;s.play();document.body.appendChild(s);true", mp3Str]; 1394 } else { 1395 scr = [NSString stringWithFormat:@"var s=document.getElementById('wkwebviewAudio');s.src=null;s.parentNode.removeChild(s);s=null;s=new Audio('%@');s.id='wkwebviewAudio';s.controls=false;s.loop=true;s.play();document.body.appendChild(s);true", mp3Str]; 1396 } 1397 [self evaluateJS: scr thenCall: nil]; 1398} 1399 1400-(void)disableIgnoreSilentSwitch 1401{ 1402 [self evaluateJS: @"document.getElementById('wkwebviewAudio').src=null;true" thenCall: nil]; 1403} 1404 1405-(void)appDidBecomeActive 1406{ 1407 if (_ignoreSilentHardwareSwitch) { 1408 [self forceIgnoreSilentHardwareSwitch:false]; 1409 } 1410} 1411 1412-(void)appWillResignActive 1413{ 1414 if (_ignoreSilentHardwareSwitch) { 1415 [self disableIgnoreSilentSwitch]; 1416 } 1417} 1418 1419/** 1420 * Called when the navigation is complete. 1421 * @see https://fburl.com/rtys6jlb 1422 */ 1423- (void)webView:(WKWebView *)webView 1424didFinishNavigation:(WKNavigation *)navigation 1425{ 1426 if (_ignoreSilentHardwareSwitch) { 1427 [self forceIgnoreSilentHardwareSwitch:true]; 1428 } 1429 1430 if (_onLoadingFinish) { 1431 _onLoadingFinish([self baseEvent]); 1432 } 1433} 1434 1435- (void)cookiesDidChangeInCookieStore:(WKHTTPCookieStore *)cookieStore 1436{ 1437 if (@available(iOS 11.0, *)) { 1438 if(_sharedCookiesEnabled) { 1439 // Write all cookies from WKWebView back to sharedHTTPCookieStorage 1440 [cookieStore getAllCookies:^(NSArray* cookies) { 1441 for (NSHTTPCookie *cookie in cookies) { 1442 [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie]; 1443 } 1444 }]; 1445 } 1446 } 1447} 1448 1449- (void)injectJavaScript:(NSString *)script 1450{ 1451 [self evaluateJS: script thenCall: nil]; 1452} 1453 1454- (void)goForward 1455{ 1456 [_webView goForward]; 1457} 1458 1459- (void)goBack 1460{ 1461 [_webView goBack]; 1462} 1463 1464- (void)reload 1465{ 1466 /** 1467 * When the initial load fails due to network connectivity issues, 1468 * [_webView reload] doesn't reload the webpage. Therefore, we must 1469 * manually call [_webView loadRequest:request]. 1470 */ 1471 NSURLRequest *request = [self requestForSource:self.source]; 1472 1473 if (request.URL && !_webView.URL.absoluteString.length) { 1474 [_webView loadRequest:request]; 1475 } else { 1476 [_webView reload]; 1477 } 1478} 1479#if !TARGET_OS_OSX 1480- (void)addPullToRefreshControl 1481{ 1482 UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init]; 1483 _refreshControl = refreshControl; 1484 [_webView.scrollView addSubview: refreshControl]; 1485 [refreshControl addTarget:self action:@selector(pullToRefresh:) forControlEvents: UIControlEventValueChanged]; 1486} 1487 1488- (void)pullToRefresh:(UIRefreshControl *)refreshControl 1489{ 1490 [self reload]; 1491 [refreshControl endRefreshing]; 1492} 1493 1494 1495- (void)setPullToRefreshEnabled:(BOOL)pullToRefreshEnabled 1496{ 1497 _pullToRefreshEnabled = pullToRefreshEnabled; 1498 1499 if (pullToRefreshEnabled) { 1500 [self addPullToRefreshControl]; 1501 } else { 1502 [_refreshControl removeFromSuperview]; 1503 } 1504 1505 [self setBounces:_bounces]; 1506} 1507#endif // !TARGET_OS_OSX 1508 1509- (void)stopLoading 1510{ 1511 [_webView stopLoading]; 1512} 1513 1514- (void)requestFocus 1515{ 1516#if !TARGET_OS_OSX 1517 [_webView becomeFirstResponder]; 1518#endif // !TARGET_OS_OSX 1519} 1520 1521#if !TARGET_OS_OSX 1522- (void)setBounces:(BOOL)bounces 1523{ 1524 _bounces = bounces; 1525 //For UIRefreshControl to work correctly, the bounces should always be true 1526 _webView.scrollView.bounces = _pullToRefreshEnabled || bounces; 1527} 1528#endif // !TARGET_OS_OSX 1529 1530- (void)setInjectedJavaScript:(NSString *)source { 1531 _injectedJavaScript = source; 1532 1533 self.atEndScript = source == nil ? nil : [[WKUserScript alloc] initWithSource:source 1534 injectionTime:WKUserScriptInjectionTimeAtDocumentEnd 1535 forMainFrameOnly:_injectedJavaScriptForMainFrameOnly]; 1536 1537 if(_webView != nil){ 1538 [self resetupScripts:_webView.configuration]; 1539 } 1540} 1541 1542- (void)setEnableApplePay:(BOOL)enableApplePay { 1543 _enableApplePay = enableApplePay; 1544 if(_webView != nil){ 1545 [self resetupScripts:_webView.configuration]; 1546 } 1547} 1548 1549- (void)setInjectedJavaScriptBeforeContentLoaded:(NSString *)source { 1550 _injectedJavaScriptBeforeContentLoaded = source; 1551 1552 self.atStartScript = source == nil ? nil : [[WKUserScript alloc] initWithSource:source 1553 injectionTime:WKUserScriptInjectionTimeAtDocumentStart 1554 forMainFrameOnly:_injectedJavaScriptBeforeContentLoadedForMainFrameOnly]; 1555 1556 if(_webView != nil){ 1557 [self resetupScripts:_webView.configuration]; 1558 } 1559} 1560 1561- (void)setInjectedJavaScriptForMainFrameOnly:(BOOL)mainFrameOnly { 1562 _injectedJavaScriptForMainFrameOnly = mainFrameOnly; 1563 [self setInjectedJavaScript:_injectedJavaScript]; 1564} 1565 1566- (void)setInjectedJavaScriptBeforeContentLoadedForMainFrameOnly:(BOOL)mainFrameOnly { 1567 _injectedJavaScriptBeforeContentLoadedForMainFrameOnly = mainFrameOnly; 1568 [self setInjectedJavaScriptBeforeContentLoaded:_injectedJavaScriptBeforeContentLoaded]; 1569} 1570 1571- (void)setMessagingEnabled:(BOOL)messagingEnabled { 1572 _messagingEnabled = messagingEnabled; 1573 1574 self.postMessageScript = _messagingEnabled ? 1575 [ 1576 [WKUserScript alloc] 1577 initWithSource: [ 1578 NSString 1579 stringWithFormat: 1580 @"window.%@ = {" 1581 " postMessage: function (data) {" 1582 " window.webkit.messageHandlers.%@.postMessage(String(data));" 1583 " }" 1584 "};", MessageHandlerName, MessageHandlerName 1585 ] 1586 injectionTime:WKUserScriptInjectionTimeAtDocumentStart 1587 /* TODO: For a separate (minor) PR: use logic like this (as react-native-wkwebview does) so that messaging can be used in all frames if desired. 1588 * I am keeping it as YES for consistency with previous behaviour. */ 1589 // forMainFrameOnly:_messagingEnabledForMainFrameOnly 1590 forMainFrameOnly:YES 1591 ] : 1592 nil; 1593 1594 if(_webView != nil){ 1595 [self resetupScripts:_webView.configuration]; 1596 } 1597} 1598 1599- (void)writeCookiesToWebView:(NSArray<NSHTTPCookie *>*)cookies completion:(void (^)(void))completion { 1600 // The required cookie APIs only became available on iOS 11 1601 if (@available(iOS 11.0, *)) { 1602 if (_sharedCookiesEnabled) { 1603 __weak WKWebView *webView = _webView; 1604 dispatch_async(dispatch_get_main_queue(), ^{ 1605 dispatch_group_t group = dispatch_group_create(); 1606 for (NSHTTPCookie *cookie in cookies) { 1607 dispatch_group_enter(group); 1608 [webView.configuration.websiteDataStore.httpCookieStore setCookie:cookie completionHandler:^{ 1609 dispatch_group_leave(group); 1610 }]; 1611 } 1612 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ 1613 if (completion) { 1614 completion(); 1615 } 1616 }); 1617 }); 1618 return; 1619 } 1620 } 1621 1622 if (completion) { 1623 completion(); 1624 } 1625} 1626 1627- (void)syncCookiesToWebView:(void (^)(void))completion { 1628 NSArray<NSHTTPCookie *> *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]; 1629 [self writeCookiesToWebView:cookies completion:completion]; 1630} 1631 1632- (void)resetupScripts:(WKWebViewConfiguration *)wkWebViewConfig { 1633 [wkWebViewConfig.userContentController removeAllUserScripts]; 1634 [wkWebViewConfig.userContentController removeScriptMessageHandlerForName:MessageHandlerName]; 1635 if(self.enableApplePay){ 1636 if (self.postMessageScript){ 1637 [wkWebViewConfig.userContentController addScriptMessageHandler:[[RNCWeakScriptMessageDelegate alloc] initWithDelegate:self] 1638 name:MessageHandlerName]; 1639 } 1640 return; 1641 } 1642 1643 NSString *html5HistoryAPIShimSource = [NSString stringWithFormat: 1644 @"(function(history) {\n" 1645 " function notify(type) {\n" 1646 " setTimeout(function() {\n" 1647 " window.webkit.messageHandlers.%@.postMessage(type)\n" 1648 " }, 0)\n" 1649 " }\n" 1650 " function shim(f) {\n" 1651 " return function pushState() {\n" 1652 " notify('other')\n" 1653 " return f.apply(history, arguments)\n" 1654 " }\n" 1655 " }\n" 1656 " history.pushState = shim(history.pushState)\n" 1657 " history.replaceState = shim(history.replaceState)\n" 1658 " window.addEventListener('popstate', function() {\n" 1659 " notify('backforward')\n" 1660 " })\n" 1661 "})(window.history)\n", HistoryShimName 1662 ]; 1663 WKUserScript *script = [[WKUserScript alloc] initWithSource:html5HistoryAPIShimSource injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]; 1664 [wkWebViewConfig.userContentController addUserScript:script]; 1665 1666 if(_sharedCookiesEnabled) { 1667 // More info to sending cookies with WKWebView 1668 // https://stackoverflow.com/questions/26573137/can-i-set-the-cookies-to-be-used-by-a-wkwebview/26577303#26577303 1669 if (@available(iOS 11.0, *)) { 1670 // Set Cookies in iOS 11 and above, initialize websiteDataStore before setting cookies 1671 // See also https://forums.developer.apple.com/thread/97194 1672 // check if websiteDataStore has not been initialized before 1673 if(!_incognito && !_cacheEnabled) { 1674 wkWebViewConfig.websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore]; 1675 } 1676 [self syncCookiesToWebView:^{ 1677 [wkWebViewConfig.websiteDataStore.httpCookieStore addObserver:self]; 1678 }]; 1679 } else { 1680 NSMutableString *script = [NSMutableString string]; 1681 1682 // Clear all existing cookies in a direct called function. This ensures that no 1683 // javascript error will break the web content javascript. 1684 // We keep this code here, if someone requires that Cookies are also removed within the 1685 // the WebView and want to extends the current sharedCookiesEnabled option with an 1686 // additional property. 1687 // Generates JS: document.cookie = "key=; Expires=Thu, 01 Jan 1970 00:00:01 GMT;" 1688 // for each cookie which is already available in the WebView context. 1689 /* 1690 [script appendString:@"(function () {\n"]; 1691 [script appendString:@" var cookies = document.cookie.split('; ');\n"]; 1692 [script appendString:@" for (var i = 0; i < cookies.length; i++) {\n"]; 1693 [script appendString:@" if (cookies[i].indexOf('=') !== -1) {\n"]; 1694 [script appendString:@" document.cookie = cookies[i].split('=')[0] + '=; Expires=Thu, 01 Jan 1970 00:00:01 GMT';\n"]; 1695 [script appendString:@" }\n"]; 1696 [script appendString:@" }\n"]; 1697 [script appendString:@"})();\n\n"]; 1698 */ 1699 1700 // Set cookies in a direct called function. This ensures that no 1701 // javascript error will break the web content javascript. 1702 // Generates JS: document.cookie = "key=value; Path=/; Expires=Thu, 01 Jan 20xx 00:00:01 GMT;" 1703 // for each cookie which is available in the application context. 1704 [script appendString:@"(function () {\n"]; 1705 for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) { 1706 [script appendFormat:@"document.cookie = %@ + '=' + %@", 1707 RCTJSONStringify(cookie.name, NULL), 1708 RCTJSONStringify(cookie.value, NULL)]; 1709 if (cookie.path) { 1710 [script appendFormat:@" + '; Path=' + %@", RCTJSONStringify(cookie.path, NULL)]; 1711 } 1712 if (cookie.expiresDate) { 1713 [script appendFormat:@" + '; Expires=' + new Date(%f).toUTCString()", 1714 cookie.expiresDate.timeIntervalSince1970 * 1000 1715 ]; 1716 } 1717 [script appendString:@";\n"]; 1718 } 1719 [script appendString:@"})();\n"]; 1720 1721 WKUserScript* cookieInScript = [[WKUserScript alloc] initWithSource:script 1722 injectionTime:WKUserScriptInjectionTimeAtDocumentStart 1723 forMainFrameOnly:YES]; 1724 [wkWebViewConfig.userContentController addUserScript:cookieInScript]; 1725 } 1726 } 1727 1728 if(_messagingEnabled){ 1729 if (self.postMessageScript){ 1730 [wkWebViewConfig.userContentController addScriptMessageHandler:[[RNCWeakScriptMessageDelegate alloc] initWithDelegate:self] 1731 name:MessageHandlerName]; 1732 [wkWebViewConfig.userContentController addUserScript:self.postMessageScript]; 1733 } 1734 if (self.atEndScript) { 1735 [wkWebViewConfig.userContentController addUserScript:self.atEndScript]; 1736 } 1737 } 1738 // Whether or not messaging is enabled, add the startup script if it exists. 1739 if (self.atStartScript) { 1740 [wkWebViewConfig.userContentController addUserScript:self.atStartScript]; 1741 } 1742} 1743 1744- (NSURLRequest *)requestForSource:(id)json { 1745 NSURLRequest *request = [RCTConvert NSURLRequest:self.source]; 1746 1747 // If sharedCookiesEnabled we automatically add all application cookies to the 1748 // http request. This is automatically done on iOS 11+ in the WebView constructor. 1749 // Se we need to manually add these shared cookies here only for iOS versions < 11. 1750 if (_sharedCookiesEnabled) { 1751 if (@available(iOS 11.0, *)) { 1752 // see WKWebView initialization for added cookies 1753 } else if (request != nil) { 1754 NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:request.URL]; 1755 NSDictionary<NSString *, NSString *> *cookieHeader = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies]; 1756 NSMutableURLRequest *mutableRequest = [request mutableCopy]; 1757 [mutableRequest setAllHTTPHeaderFields:cookieHeader]; 1758 return mutableRequest; 1759 } 1760 } 1761 return request; 1762} 1763 1764@end 1765 1766@implementation RNCWeakScriptMessageDelegate 1767 1768- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate { 1769 self = [super init]; 1770 if (self) { 1771 _scriptDelegate = scriptDelegate; 1772 } 1773 return self; 1774} 1775 1776- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { 1777 [self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message]; 1778} 1779 1780@end 1781