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