@exodus/react-native-webview 9.4.0-no-android.0 → 11.26.1-exodus.1

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