react-native-webview.git

RNCWebView.m 41KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066
  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. #import "RNCWebView.h"
  8. #import <React/RCTConvert.h>
  9. #import <React/RCTAutoInsetsProtocol.h>
  10. #import "RNCWKProcessPoolManager.h"
  11. #import <UIKit/UIKit.h>
  12. #import "objc/runtime.h"
  13. static NSTimer *keyboardTimer;
  14. static NSString *const MessageHandlerName = @"ReactNativeWebView";
  15. static NSURLCredential* clientAuthenticationCredential;
  16. static NSDictionary* customCertificatesForHost;
  17. // runtime trick to remove WKWebView keyboard default toolbar
  18. // see: http://stackoverflow.com/questions/19033292/ios-7-uiwebview-keyboard-issue/19042279#19042279
  19. @interface _SwizzleHelperWK : UIView
  20. @property (nonatomic, copy) WKWebView *webView;
  21. @end
  22. @implementation _SwizzleHelperWK
  23. -(id)inputAccessoryView
  24. {
  25. if (_webView == nil) {
  26. return nil;
  27. }
  28. if ([_webView respondsToSelector:@selector(inputAssistantItem)]) {
  29. UITextInputAssistantItem *inputAssistantItem = [_webView inputAssistantItem];
  30. inputAssistantItem.leadingBarButtonGroups = @[];
  31. inputAssistantItem.trailingBarButtonGroups = @[];
  32. }
  33. return nil;
  34. }
  35. @end
  36. @interface RNCWebView () <WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, UIScrollViewDelegate, RCTAutoInsetsProtocol>
  37. @property (nonatomic, copy) RCTDirectEventBlock onLoadingStart;
  38. @property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish;
  39. @property (nonatomic, copy) RCTDirectEventBlock onLoadingError;
  40. @property (nonatomic, copy) RCTDirectEventBlock onLoadingProgress;
  41. @property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest;
  42. @property (nonatomic, copy) RCTDirectEventBlock onHttpError;
  43. @property (nonatomic, copy) RCTDirectEventBlock onMessage;
  44. @property (nonatomic, copy) RCTDirectEventBlock onScroll;
  45. @property (nonatomic, copy) RCTDirectEventBlock onContentProcessDidTerminate;
  46. @property (nonatomic, copy) WKWebView *webView;
  47. @end
  48. @implementation RNCWebView
  49. {
  50. UIColor * _savedBackgroundColor;
  51. BOOL _savedHideKeyboardAccessoryView;
  52. BOOL _savedKeyboardDisplayRequiresUserAction;
  53. // Workaround for StatusBar appearance bug for iOS 12
  54. // https://github.com/react-native-community/react-native-webview/issues/62
  55. BOOL _isFullScreenVideoOpen;
  56. UIStatusBarStyle _savedStatusBarStyle;
  57. BOOL _savedStatusBarHidden;
  58. #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
  59. UIScrollViewContentInsetAdjustmentBehavior _savedContentInsetAdjustmentBehavior;
  60. #endif
  61. #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* __IPHONE_13_0 */
  62. BOOL _savedAutomaticallyAdjustsScrollIndicatorInsets;
  63. #endif
  64. }
  65. - (instancetype)initWithFrame:(CGRect)frame
  66. {
  67. if ((self = [super initWithFrame:frame])) {
  68. super.backgroundColor = [UIColor clearColor];
  69. _bounces = YES;
  70. _scrollEnabled = YES;
  71. _showsHorizontalScrollIndicator = YES;
  72. _showsVerticalScrollIndicator = YES;
  73. _directionalLockEnabled = YES;
  74. _automaticallyAdjustContentInsets = YES;
  75. _contentInset = UIEdgeInsetsZero;
  76. _savedKeyboardDisplayRequiresUserAction = YES;
  77. _savedStatusBarStyle = RCTSharedApplication().statusBarStyle;
  78. _savedStatusBarHidden = RCTSharedApplication().statusBarHidden;
  79. #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
  80. _savedContentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
  81. #endif
  82. #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* __IPHONE_13_0 */
  83. _savedAutomaticallyAdjustsScrollIndicatorInsets = NO;
  84. #endif
  85. }
  86. if (@available(iOS 12.0, *)) {
  87. // Workaround for a keyboard dismissal bug present in iOS 12
  88. // https://openradar.appspot.com/radar?id=5018321736957952
  89. [[NSNotificationCenter defaultCenter]
  90. addObserver:self
  91. selector:@selector(keyboardWillHide)
  92. name:UIKeyboardWillHideNotification object:nil];
  93. [[NSNotificationCenter defaultCenter]
  94. addObserver:self
  95. selector:@selector(keyboardWillShow)
  96. name:UIKeyboardWillShowNotification object:nil];
  97. // Workaround for StatusBar appearance bug for iOS 12
  98. // https://github.com/react-native-community/react-native-webview/issues/62
  99. [[NSNotificationCenter defaultCenter] addObserver:self
  100. selector:@selector(showFullScreenVideoStatusBars)
  101. name:UIWindowDidBecomeVisibleNotification
  102. object:nil];
  103. [[NSNotificationCenter defaultCenter] addObserver:self
  104. selector:@selector(hideFullScreenVideoStatusBars)
  105. name:UIWindowDidBecomeHiddenNotification
  106. object:nil];
  107. }
  108. return self;
  109. }
  110. - (void)dealloc
  111. {
  112. [[NSNotificationCenter defaultCenter] removeObserver:self];
  113. }
  114. /**
  115. * See https://stackoverflow.com/questions/25713069/why-is-wkwebview-not-opening-links-with-target-blank/25853806#25853806 for details.
  116. */
  117. - (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
  118. {
  119. if (!navigationAction.targetFrame.isMainFrame) {
  120. [webView loadRequest:navigationAction.request];
  121. }
  122. return nil;
  123. }
  124. - (void)didMoveToWindow
  125. {
  126. if (self.window != nil && _webView == nil) {
  127. WKWebViewConfiguration *wkWebViewConfig = [WKWebViewConfiguration new];
  128. WKPreferences *prefs = [[WKPreferences alloc]init];
  129. BOOL _prefsUsed = NO;
  130. if (!_javaScriptEnabled) {
  131. prefs.javaScriptEnabled = NO;
  132. _prefsUsed = YES;
  133. }
  134. if (_allowFileAccessFromFileURLs) {
  135. [prefs setValue:@TRUE forKey:@"allowFileAccessFromFileURLs"];
  136. _prefsUsed = YES;
  137. }
  138. if (_prefsUsed) {
  139. wkWebViewConfig.preferences = prefs;
  140. }
  141. if (_incognito) {
  142. wkWebViewConfig.websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore];
  143. } else if (_cacheEnabled) {
  144. wkWebViewConfig.websiteDataStore = [WKWebsiteDataStore defaultDataStore];
  145. }
  146. if(self.useSharedProcessPool) {
  147. wkWebViewConfig.processPool = [[RNCWKProcessPoolManager sharedManager] sharedProcessPool];
  148. }
  149. wkWebViewConfig.userContentController = [WKUserContentController new];
  150. if (_messagingEnabled) {
  151. [wkWebViewConfig.userContentController addScriptMessageHandler:self name:MessageHandlerName];
  152. NSString *source = [NSString stringWithFormat:
  153. @"window.%@ = {"
  154. " postMessage: function (data) {"
  155. " window.webkit.messageHandlers.%@.postMessage(String(data));"
  156. " }"
  157. "};", MessageHandlerName, MessageHandlerName
  158. ];
  159. WKUserScript *script = [[WKUserScript alloc] initWithSource:source injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
  160. [wkWebViewConfig.userContentController addUserScript:script];
  161. if (_injectedJavaScriptBeforeContentLoaded) {
  162. // If user has provided an injectedJavascript prop, execute it at the start of the document
  163. WKUserScript *injectedScript = [[WKUserScript alloc] initWithSource:_injectedJavaScriptBeforeContentLoaded injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
  164. [wkWebViewConfig.userContentController addUserScript:injectedScript];
  165. }
  166. }
  167. wkWebViewConfig.allowsInlineMediaPlayback = _allowsInlineMediaPlayback;
  168. #if WEBKIT_IOS_10_APIS_AVAILABLE
  169. wkWebViewConfig.mediaTypesRequiringUserActionForPlayback = _mediaPlaybackRequiresUserAction
  170. ? WKAudiovisualMediaTypeAll
  171. : WKAudiovisualMediaTypeNone;
  172. wkWebViewConfig.dataDetectorTypes = _dataDetectorTypes;
  173. #else
  174. wkWebViewConfig.mediaPlaybackRequiresUserAction = _mediaPlaybackRequiresUserAction;
  175. #endif
  176. if (_applicationNameForUserAgent) {
  177. wkWebViewConfig.applicationNameForUserAgent = [NSString stringWithFormat:@"%@ %@", wkWebViewConfig.applicationNameForUserAgent, _applicationNameForUserAgent];
  178. }
  179. if(_sharedCookiesEnabled) {
  180. // More info to sending cookies with WKWebView
  181. // https://stackoverflow.com/questions/26573137/can-i-set-the-cookies-to-be-used-by-a-wkwebview/26577303#26577303
  182. if (@available(iOS 11.0, *)) {
  183. // Set Cookies in iOS 11 and above, initialize websiteDataStore before setting cookies
  184. // See also https://forums.developer.apple.com/thread/97194
  185. // check if websiteDataStore has not been initialized before
  186. if(!_incognito && !_cacheEnabled) {
  187. wkWebViewConfig.websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore];
  188. }
  189. for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
  190. [wkWebViewConfig.websiteDataStore.httpCookieStore setCookie:cookie completionHandler:nil];
  191. }
  192. } else {
  193. NSMutableString *script = [NSMutableString string];
  194. // Clear all existing cookies in a direct called function. This ensures that no
  195. // javascript error will break the web content javascript.
  196. // We keep this code here, if someone requires that Cookies are also removed within the
  197. // the WebView and want to extends the current sharedCookiesEnabled option with an
  198. // additional property.
  199. // Generates JS: document.cookie = "key=; Expires=Thu, 01 Jan 1970 00:00:01 GMT;"
  200. // for each cookie which is already available in the WebView context.
  201. /*
  202. [script appendString:@"(function () {\n"];
  203. [script appendString:@" var cookies = document.cookie.split('; ');\n"];
  204. [script appendString:@" for (var i = 0; i < cookies.length; i++) {\n"];
  205. [script appendString:@" if (cookies[i].indexOf('=') !== -1) {\n"];
  206. [script appendString:@" document.cookie = cookies[i].split('=')[0] + '=; Expires=Thu, 01 Jan 1970 00:00:01 GMT';\n"];
  207. [script appendString:@" }\n"];
  208. [script appendString:@" }\n"];
  209. [script appendString:@"})();\n\n"];
  210. */
  211. // Set cookies in a direct called function. This ensures that no
  212. // javascript error will break the web content javascript.
  213. // Generates JS: document.cookie = "key=value; Path=/; Expires=Thu, 01 Jan 20xx 00:00:01 GMT;"
  214. // for each cookie which is available in the application context.
  215. [script appendString:@"(function () {\n"];
  216. for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
  217. [script appendFormat:@"document.cookie = %@ + '=' + %@",
  218. RCTJSONStringify(cookie.name, NULL),
  219. RCTJSONStringify(cookie.value, NULL)];
  220. if (cookie.path) {
  221. [script appendFormat:@" + '; Path=' + %@", RCTJSONStringify(cookie.path, NULL)];
  222. }
  223. if (cookie.expiresDate) {
  224. [script appendFormat:@" + '; Expires=' + new Date(%f).toUTCString()",
  225. cookie.expiresDate.timeIntervalSince1970 * 1000
  226. ];
  227. }
  228. [script appendString:@";\n"];
  229. }
  230. [script appendString:@"})();\n"];
  231. WKUserScript* cookieInScript = [[WKUserScript alloc] initWithSource:script
  232. injectionTime:WKUserScriptInjectionTimeAtDocumentStart
  233. forMainFrameOnly:YES];
  234. [wkWebViewConfig.userContentController addUserScript:cookieInScript];
  235. }
  236. }
  237. _webView = [[WKWebView alloc] initWithFrame:self.bounds configuration: wkWebViewConfig];
  238. [self setBackgroundColor: _savedBackgroundColor];
  239. _webView.scrollView.delegate = self;
  240. _webView.UIDelegate = self;
  241. _webView.navigationDelegate = self;
  242. _webView.scrollView.scrollEnabled = _scrollEnabled;
  243. _webView.scrollView.pagingEnabled = _pagingEnabled;
  244. _webView.scrollView.bounces = _bounces;
  245. _webView.scrollView.showsHorizontalScrollIndicator = _showsHorizontalScrollIndicator;
  246. _webView.scrollView.showsVerticalScrollIndicator = _showsVerticalScrollIndicator;
  247. _webView.scrollView.directionalLockEnabled = _directionalLockEnabled;
  248. _webView.allowsLinkPreview = _allowsLinkPreview;
  249. [_webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
  250. _webView.allowsBackForwardNavigationGestures = _allowsBackForwardNavigationGestures;
  251. if (_userAgent) {
  252. _webView.customUserAgent = _userAgent;
  253. }
  254. #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
  255. if ([_webView.scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
  256. _webView.scrollView.contentInsetAdjustmentBehavior = _savedContentInsetAdjustmentBehavior;
  257. }
  258. #endif
  259. #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* __IPHONE_13_0 */
  260. _webView.scrollView.automaticallyAdjustsScrollIndicatorInsets = _savedAutomaticallyAdjustsScrollIndicatorInsets;
  261. #endif
  262. [self addSubview:_webView];
  263. [self setHideKeyboardAccessoryView: _savedHideKeyboardAccessoryView];
  264. [self setKeyboardDisplayRequiresUserAction: _savedKeyboardDisplayRequiresUserAction];
  265. [self visitSource];
  266. }
  267. }
  268. // Update webview property when the component prop changes.
  269. - (void)setAllowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures {
  270. _allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures;
  271. _webView.allowsBackForwardNavigationGestures = _allowsBackForwardNavigationGestures;
  272. }
  273. - (void)removeFromSuperview
  274. {
  275. if (_webView) {
  276. [_webView.configuration.userContentController removeScriptMessageHandlerForName:MessageHandlerName];
  277. [_webView removeObserver:self forKeyPath:@"estimatedProgress"];
  278. [_webView removeFromSuperview];
  279. _webView.scrollView.delegate = nil;
  280. _webView = nil;
  281. }
  282. [super removeFromSuperview];
  283. }
  284. -(void)showFullScreenVideoStatusBars
  285. {
  286. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  287. _isFullScreenVideoOpen = YES;
  288. RCTUnsafeExecuteOnMainQueueSync(^{
  289. [RCTSharedApplication() setStatusBarStyle:UIStatusBarStyleLightContent animated:YES];
  290. });
  291. #pragma clang diagnostic pop
  292. }
  293. -(void)hideFullScreenVideoStatusBars
  294. {
  295. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  296. _isFullScreenVideoOpen = NO;
  297. RCTUnsafeExecuteOnMainQueueSync(^{
  298. [RCTSharedApplication() setStatusBarHidden:self->_savedStatusBarHidden animated:YES];
  299. [RCTSharedApplication() setStatusBarStyle:self->_savedStatusBarStyle animated:YES];
  300. });
  301. #pragma clang diagnostic pop
  302. }
  303. -(void)keyboardWillHide
  304. {
  305. keyboardTimer = [NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(keyboardDisplacementFix) userInfo:nil repeats:false];
  306. [[NSRunLoop mainRunLoop] addTimer:keyboardTimer forMode:NSRunLoopCommonModes];
  307. }
  308. -(void)keyboardWillShow
  309. {
  310. if (keyboardTimer != nil) {
  311. [keyboardTimer invalidate];
  312. }
  313. }
  314. -(void)keyboardDisplacementFix
  315. {
  316. // Additional viewport checks to prevent unintentional scrolls
  317. UIScrollView *scrollView = self.webView.scrollView;
  318. double maxContentOffset = scrollView.contentSize.height - scrollView.frame.size.height;
  319. if (maxContentOffset < 0) {
  320. maxContentOffset = 0;
  321. }
  322. if (scrollView.contentOffset.y > maxContentOffset) {
  323. // https://stackoverflow.com/a/9637807/824966
  324. [UIView animateWithDuration:.25 animations:^{
  325. scrollView.contentOffset = CGPointMake(0, maxContentOffset);
  326. }];
  327. }
  328. }
  329. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
  330. if ([keyPath isEqual:@"estimatedProgress"] && object == self.webView) {
  331. if(_onLoadingProgress){
  332. NSMutableDictionary<NSString *, id> *event = [self baseEvent];
  333. [event addEntriesFromDictionary:@{@"progress":[NSNumber numberWithDouble:self.webView.estimatedProgress]}];
  334. _onLoadingProgress(event);
  335. }
  336. }else{
  337. [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
  338. }
  339. }
  340. - (void)setBackgroundColor:(UIColor *)backgroundColor
  341. {
  342. _savedBackgroundColor = backgroundColor;
  343. if (_webView == nil) {
  344. return;
  345. }
  346. CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor);
  347. self.opaque = _webView.opaque = (alpha == 1.0);
  348. _webView.scrollView.backgroundColor = backgroundColor;
  349. _webView.backgroundColor = backgroundColor;
  350. }
  351. #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
  352. - (void)setContentInsetAdjustmentBehavior:(UIScrollViewContentInsetAdjustmentBehavior)behavior
  353. {
  354. _savedContentInsetAdjustmentBehavior = behavior;
  355. if (_webView == nil) {
  356. return;
  357. }
  358. if ([_webView.scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
  359. CGPoint contentOffset = _webView.scrollView.contentOffset;
  360. _webView.scrollView.contentInsetAdjustmentBehavior = behavior;
  361. _webView.scrollView.contentOffset = contentOffset;
  362. }
  363. }
  364. #endif
  365. #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* __IPHONE_13_0 */
  366. - (void)setAutomaticallyAdjustsScrollIndicatorInsets:(BOOL)automaticallyAdjustsScrollIndicatorInsets{
  367. _savedAutomaticallyAdjustsScrollIndicatorInsets = automaticallyAdjustsScrollIndicatorInsets;
  368. if (_webView == nil) {
  369. return;
  370. }
  371. if ([_webView.scrollView respondsToSelector:@selector(setAutomaticallyAdjustsScrollIndicatorInsets:)]) {
  372. _webView.scrollView.automaticallyAdjustsScrollIndicatorInsets = automaticallyAdjustsScrollIndicatorInsets;
  373. }
  374. }
  375. #endif
  376. /**
  377. * This method is called whenever JavaScript running within the web view calls:
  378. * - window.webkit.messageHandlers[MessageHandlerName].postMessage
  379. */
  380. - (void)userContentController:(WKUserContentController *)userContentController
  381. didReceiveScriptMessage:(WKScriptMessage *)message
  382. {
  383. if (_onMessage != nil) {
  384. NSMutableDictionary<NSString *, id> *event = [self baseEvent];
  385. [event addEntriesFromDictionary: @{@"data": message.body}];
  386. _onMessage(event);
  387. }
  388. }
  389. - (void)setSource:(NSDictionary *)source
  390. {
  391. if (![_source isEqualToDictionary:source]) {
  392. _source = [source copy];
  393. if (_webView != nil) {
  394. [self visitSource];
  395. }
  396. }
  397. }
  398. - (void)setAllowingReadAccessToURL:(NSString *)allowingReadAccessToURL
  399. {
  400. if (![_allowingReadAccessToURL isEqualToString:allowingReadAccessToURL]) {
  401. _allowingReadAccessToURL = [allowingReadAccessToURL copy];
  402. if (_webView != nil) {
  403. [self visitSource];
  404. }
  405. }
  406. }
  407. - (void)setContentInset:(UIEdgeInsets)contentInset
  408. {
  409. _contentInset = contentInset;
  410. [RCTView autoAdjustInsetsForView:self
  411. withScrollView:_webView.scrollView
  412. updateOffset:NO];
  413. }
  414. - (void)refreshContentInset
  415. {
  416. [RCTView autoAdjustInsetsForView:self
  417. withScrollView:_webView.scrollView
  418. updateOffset:YES];
  419. }
  420. - (void)visitSource
  421. {
  422. // Check for a static html source first
  423. NSString *html = [RCTConvert NSString:_source[@"html"]];
  424. if (html) {
  425. NSURL *baseURL = [RCTConvert NSURL:_source[@"baseUrl"]];
  426. if (!baseURL) {
  427. baseURL = [NSURL URLWithString:@"about:blank"];
  428. }
  429. [_webView loadHTMLString:html baseURL:baseURL];
  430. return;
  431. }
  432. NSURLRequest *request = [self requestForSource:_source];
  433. // Because of the way React works, as pages redirect, we actually end up
  434. // passing the redirect urls back here, so we ignore them if trying to load
  435. // the same url. We'll expose a call to 'reload' to allow a user to load
  436. // the existing page.
  437. if ([request.URL isEqual:_webView.URL]) {
  438. return;
  439. }
  440. if (!request.URL) {
  441. // Clear the webview
  442. [_webView loadHTMLString:@"" baseURL:nil];
  443. return;
  444. }
  445. if (request.URL.host) {
  446. [_webView loadRequest:request];
  447. }
  448. else {
  449. NSURL* readAccessUrl = _allowingReadAccessToURL ? [RCTConvert NSURL:_allowingReadAccessToURL] : request.URL;
  450. [_webView loadFileURL:request.URL allowingReadAccessToURL:readAccessUrl];
  451. }
  452. }
  453. -(void)setKeyboardDisplayRequiresUserAction:(BOOL)keyboardDisplayRequiresUserAction
  454. {
  455. if (_webView == nil) {
  456. _savedKeyboardDisplayRequiresUserAction = keyboardDisplayRequiresUserAction;
  457. return;
  458. }
  459. if (_savedKeyboardDisplayRequiresUserAction == true) {
  460. return;
  461. }
  462. UIView* subview;
  463. for (UIView* view in _webView.scrollView.subviews) {
  464. if([[view.class description] hasPrefix:@"WK"])
  465. subview = view;
  466. }
  467. if(subview == nil) return;
  468. Class class = subview.class;
  469. NSOperatingSystemVersion iOS_11_3_0 = (NSOperatingSystemVersion){11, 3, 0};
  470. NSOperatingSystemVersion iOS_12_2_0 = (NSOperatingSystemVersion){12, 2, 0};
  471. NSOperatingSystemVersion iOS_13_0_0 = (NSOperatingSystemVersion){13, 0, 0};
  472. Method method;
  473. IMP override;
  474. if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_13_0_0]) {
  475. // iOS 13.0.0 - Future
  476. SEL selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:");
  477. method = class_getInstanceMethod(class, selector);
  478. IMP original = method_getImplementation(method);
  479. override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) {
  480. ((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4);
  481. });
  482. }
  483. else if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_12_2_0]) {
  484. // iOS 12.2.0 - iOS 13.0.0
  485. SEL selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:changingActivityState:userObject:");
  486. method = class_getInstanceMethod(class, selector);
  487. IMP original = method_getImplementation(method);
  488. override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) {
  489. ((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4);
  490. });
  491. }
  492. else if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_11_3_0]) {
  493. // iOS 11.3.0 - 12.2.0
  494. SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:");
  495. method = class_getInstanceMethod(class, selector);
  496. IMP original = method_getImplementation(method);
  497. override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) {
  498. ((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4);
  499. });
  500. } else {
  501. // iOS 9.0 - 11.3.0
  502. SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:");
  503. method = class_getInstanceMethod(class, selector);
  504. IMP original = method_getImplementation(method);
  505. override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, id arg3) {
  506. ((void (*)(id, SEL, void*, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3);
  507. });
  508. }
  509. method_setImplementation(method, override);
  510. }
  511. -(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView
  512. {
  513. if (_webView == nil) {
  514. _savedHideKeyboardAccessoryView = hideKeyboardAccessoryView;
  515. return;
  516. }
  517. if (_savedHideKeyboardAccessoryView == false) {
  518. return;
  519. }
  520. UIView* subview;
  521. for (UIView* view in _webView.scrollView.subviews) {
  522. if([[view.class description] hasPrefix:@"WK"])
  523. subview = view;
  524. }
  525. if(subview == nil) return;
  526. NSString* name = [NSString stringWithFormat:@"%@_SwizzleHelperWK", subview.class.superclass];
  527. Class newClass = NSClassFromString(name);
  528. if(newClass == nil)
  529. {
  530. newClass = objc_allocateClassPair(subview.class, [name cStringUsingEncoding:NSASCIIStringEncoding], 0);
  531. if(!newClass) return;
  532. Method method = class_getInstanceMethod([_SwizzleHelperWK class], @selector(inputAccessoryView));
  533. class_addMethod(newClass, @selector(inputAccessoryView), method_getImplementation(method), method_getTypeEncoding(method));
  534. objc_registerClassPair(newClass);
  535. }
  536. object_setClass(subview, newClass);
  537. }
  538. - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
  539. {
  540. scrollView.decelerationRate = _decelerationRate;
  541. }
  542. - (void)setScrollEnabled:(BOOL)scrollEnabled
  543. {
  544. _scrollEnabled = scrollEnabled;
  545. _webView.scrollView.scrollEnabled = scrollEnabled;
  546. }
  547. - (void)scrollViewDidScroll:(UIScrollView *)scrollView
  548. {
  549. // Don't allow scrolling the scrollView.
  550. if (!_scrollEnabled) {
  551. scrollView.bounds = _webView.bounds;
  552. }
  553. else if (_onScroll != nil) {
  554. NSDictionary *event = @{
  555. @"contentOffset": @{
  556. @"x": @(scrollView.contentOffset.x),
  557. @"y": @(scrollView.contentOffset.y)
  558. },
  559. @"contentInset": @{
  560. @"top": @(scrollView.contentInset.top),
  561. @"left": @(scrollView.contentInset.left),
  562. @"bottom": @(scrollView.contentInset.bottom),
  563. @"right": @(scrollView.contentInset.right)
  564. },
  565. @"contentSize": @{
  566. @"width": @(scrollView.contentSize.width),
  567. @"height": @(scrollView.contentSize.height)
  568. },
  569. @"layoutMeasurement": @{
  570. @"width": @(scrollView.frame.size.width),
  571. @"height": @(scrollView.frame.size.height)
  572. },
  573. @"zoomScale": @(scrollView.zoomScale ?: 1),
  574. };
  575. _onScroll(event);
  576. }
  577. }
  578. - (void)setDirectionalLockEnabled:(BOOL)directionalLockEnabled
  579. {
  580. _directionalLockEnabled = directionalLockEnabled;
  581. _webView.scrollView.directionalLockEnabled = directionalLockEnabled;
  582. }
  583. - (void)setShowsHorizontalScrollIndicator:(BOOL)showsHorizontalScrollIndicator
  584. {
  585. _showsHorizontalScrollIndicator = showsHorizontalScrollIndicator;
  586. _webView.scrollView.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator;
  587. }
  588. - (void)setShowsVerticalScrollIndicator:(BOOL)showsVerticalScrollIndicator
  589. {
  590. _showsVerticalScrollIndicator = showsVerticalScrollIndicator;
  591. _webView.scrollView.showsVerticalScrollIndicator = showsVerticalScrollIndicator;
  592. }
  593. - (void)postMessage:(NSString *)message
  594. {
  595. NSDictionary *eventInitDict = @{@"data": message};
  596. NSString *source = [NSString
  597. stringWithFormat:@"window.dispatchEvent(new MessageEvent('message', %@));",
  598. RCTJSONStringify(eventInitDict, NULL)
  599. ];
  600. [self injectJavaScript: source];
  601. }
  602. - (void)layoutSubviews
  603. {
  604. [super layoutSubviews];
  605. // Ensure webview takes the position and dimensions of RNCWebView
  606. _webView.frame = self.bounds;
  607. _webView.scrollView.contentInset = _contentInset;
  608. }
  609. - (NSMutableDictionary<NSString *, id> *)baseEvent
  610. {
  611. NSDictionary *event = @{
  612. @"url": _webView.URL.absoluteString ?: @"",
  613. @"title": _webView.title ?: @"",
  614. @"loading" : @(_webView.loading),
  615. @"canGoBack": @(_webView.canGoBack),
  616. @"canGoForward" : @(_webView.canGoForward)
  617. };
  618. return [[NSMutableDictionary alloc] initWithDictionary: event];
  619. }
  620. + (void)setClientAuthenticationCredential:(nullable NSURLCredential*)credential {
  621. clientAuthenticationCredential = credential;
  622. }
  623. + (void)setCustomCertificatesForHost:(nullable NSDictionary*)certificates {
  624. customCertificatesForHost = certificates;
  625. }
  626. - (void) webView:(WKWebView *)webView
  627. didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
  628. completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable))completionHandler
  629. {
  630. NSString* host = nil;
  631. if (webView.URL != nil) {
  632. host = webView.URL.host;
  633. }
  634. if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate) {
  635. completionHandler(NSURLSessionAuthChallengeUseCredential, clientAuthenticationCredential);
  636. return;
  637. }
  638. if ([[challenge protectionSpace] serverTrust] != nil && customCertificatesForHost != nil && host != nil) {
  639. SecCertificateRef localCertificate = (__bridge SecCertificateRef)([customCertificatesForHost objectForKey:host]);
  640. if (localCertificate != nil) {
  641. NSData *localCertificateData = (NSData*) CFBridgingRelease(SecCertificateCopyData(localCertificate));
  642. SecTrustRef trust = [[challenge protectionSpace] serverTrust];
  643. long count = SecTrustGetCertificateCount(trust);
  644. for (long i = 0; i < count; i++) {
  645. SecCertificateRef serverCertificate = SecTrustGetCertificateAtIndex(trust, i);
  646. if (serverCertificate == nil) { continue; }
  647. NSData *serverCertificateData = (NSData *) CFBridgingRelease(SecCertificateCopyData(serverCertificate));
  648. if ([serverCertificateData isEqualToData:localCertificateData]) {
  649. NSURLCredential *useCredential = [NSURLCredential credentialForTrust:trust];
  650. if (challenge.sender != nil) {
  651. [challenge.sender useCredential:useCredential forAuthenticationChallenge:challenge];
  652. }
  653. completionHandler(NSURLSessionAuthChallengeUseCredential, useCredential);
  654. return;
  655. }
  656. }
  657. }
  658. }
  659. completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
  660. }
  661. #pragma mark - WKNavigationDelegate methods
  662. /**
  663. * alert
  664. */
  665. - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
  666. {
  667. UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert];
  668. [alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
  669. completionHandler();
  670. }]];
  671. [[self topViewController] presentViewController:alert animated:YES completion:NULL];
  672. }
  673. /**
  674. * confirm
  675. */
  676. - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
  677. UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert];
  678. [alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
  679. completionHandler(YES);
  680. }]];
  681. [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
  682. completionHandler(NO);
  683. }]];
  684. [[self topViewController] presentViewController:alert animated:YES completion:NULL];
  685. }
  686. /**
  687. * prompt
  688. */
  689. - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler{
  690. UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:prompt preferredStyle:UIAlertControllerStyleAlert];
  691. [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
  692. textField.text = defaultText;
  693. }];
  694. UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
  695. completionHandler([[alert.textFields lastObject] text]);
  696. }];
  697. [alert addAction:okAction];
  698. UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
  699. completionHandler(nil);
  700. }];
  701. [alert addAction:cancelAction];
  702. alert.preferredAction = okAction;
  703. [[self topViewController] presentViewController:alert animated:YES completion:NULL];
  704. }
  705. /**
  706. * topViewController
  707. */
  708. -(UIViewController *)topViewController{
  709. UIViewController *controller = [self topViewControllerWithRootViewController:[self getCurrentWindow].rootViewController];
  710. return controller;
  711. }
  712. /**
  713. * topViewControllerWithRootViewController
  714. */
  715. -(UIViewController *)topViewControllerWithRootViewController:(UIViewController *)viewController{
  716. if (viewController==nil) return nil;
  717. if (viewController.presentedViewController!=nil) {
  718. return [self topViewControllerWithRootViewController:viewController.presentedViewController];
  719. } else if ([viewController isKindOfClass:[UITabBarController class]]){
  720. return [self topViewControllerWithRootViewController:[(UITabBarController *)viewController selectedViewController]];
  721. } else if ([viewController isKindOfClass:[UINavigationController class]]){
  722. return [self topViewControllerWithRootViewController:[(UINavigationController *)viewController visibleViewController]];
  723. } else {
  724. return viewController;
  725. }
  726. }
  727. /**
  728. * getCurrentWindow
  729. */
  730. -(UIWindow *)getCurrentWindow{
  731. UIWindow *window = [UIApplication sharedApplication].keyWindow;
  732. if (window.windowLevel!=UIWindowLevelNormal) {
  733. for (UIWindow *wid in [UIApplication sharedApplication].windows) {
  734. if (window.windowLevel==UIWindowLevelNormal) {
  735. window = wid;
  736. break;
  737. }
  738. }
  739. }
  740. return window;
  741. }
  742. /**
  743. * Decides whether to allow or cancel a navigation.
  744. * @see https://fburl.com/42r9fxob
  745. */
  746. - (void) webView:(WKWebView *)webView
  747. decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
  748. decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
  749. {
  750. static NSDictionary<NSNumber *, NSString *> *navigationTypes;
  751. static dispatch_once_t onceToken;
  752. dispatch_once(&onceToken, ^{
  753. navigationTypes = @{
  754. @(WKNavigationTypeLinkActivated): @"click",
  755. @(WKNavigationTypeFormSubmitted): @"formsubmit",
  756. @(WKNavigationTypeBackForward): @"backforward",
  757. @(WKNavigationTypeReload): @"reload",
  758. @(WKNavigationTypeFormResubmitted): @"formresubmit",
  759. @(WKNavigationTypeOther): @"other",
  760. };
  761. });
  762. WKNavigationType navigationType = navigationAction.navigationType;
  763. NSURLRequest *request = navigationAction.request;
  764. if (_onShouldStartLoadWithRequest) {
  765. NSMutableDictionary<NSString *, id> *event = [self baseEvent];
  766. [event addEntriesFromDictionary: @{
  767. @"url": (request.URL).absoluteString,
  768. @"mainDocumentURL": (request.mainDocumentURL).absoluteString,
  769. @"navigationType": navigationTypes[@(navigationType)]
  770. }];
  771. if (![self.delegate webView:self
  772. shouldStartLoadForRequest:event
  773. withCallback:_onShouldStartLoadWithRequest]) {
  774. decisionHandler(WKNavigationActionPolicyCancel);
  775. return;
  776. }
  777. }
  778. if (_onLoadingStart) {
  779. // We have this check to filter out iframe requests and whatnot
  780. BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL];
  781. if (isTopFrame) {
  782. NSMutableDictionary<NSString *, id> *event = [self baseEvent];
  783. [event addEntriesFromDictionary: @{
  784. @"url": (request.URL).absoluteString,
  785. @"navigationType": navigationTypes[@(navigationType)]
  786. }];
  787. _onLoadingStart(event);
  788. }
  789. }
  790. // Allow all navigation by default
  791. decisionHandler(WKNavigationActionPolicyAllow);
  792. }
  793. /**
  794. * Called when the web view’s content process is terminated.
  795. * @see https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455639-webviewwebcontentprocessdidtermi?language=objc
  796. */
  797. - (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView
  798. {
  799. RCTLogWarn(@"Webview Process Terminated");
  800. if (_onContentProcessDidTerminate) {
  801. NSMutableDictionary<NSString *, id> *event = [self baseEvent];
  802. _onContentProcessDidTerminate(event);
  803. }
  804. }
  805. /**
  806. * Decides whether to allow or cancel a navigation after its response is known.
  807. * @see https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455643-webview?language=objc
  808. */
  809. - (void) webView:(WKWebView *)webView
  810. decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse
  811. decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
  812. {
  813. if (_onHttpError && navigationResponse.forMainFrame) {
  814. if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) {
  815. NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;
  816. NSInteger statusCode = response.statusCode;
  817. if (statusCode >= 400) {
  818. NSMutableDictionary<NSString *, id> *event = [self baseEvent];
  819. [event addEntriesFromDictionary: @{
  820. @"url": response.URL.absoluteString,
  821. @"statusCode": @(statusCode)
  822. }];
  823. _onHttpError(event);
  824. }
  825. }
  826. }
  827. decisionHandler(WKNavigationResponsePolicyAllow);
  828. }
  829. /**
  830. * Called when an error occurs while the web view is loading content.
  831. * @see https://fburl.com/km6vqenw
  832. */
  833. - (void) webView:(WKWebView *)webView
  834. didFailProvisionalNavigation:(WKNavigation *)navigation
  835. withError:(NSError *)error
  836. {
  837. if (_onLoadingError) {
  838. if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) {
  839. // NSURLErrorCancelled is reported when a page has a redirect OR if you load
  840. // a new URL in the WebView before the previous one came back. We can just
  841. // ignore these since they aren't real errors.
  842. // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os
  843. return;
  844. }
  845. if ([error.domain isEqualToString:@"WebKitErrorDomain"] && error.code == 102) {
  846. // Error code 102 "Frame load interrupted" is raised by the WKWebView
  847. // when the URL is from an http redirect. This is a common pattern when
  848. // implementing OAuth with a WebView.
  849. return;
  850. }
  851. NSMutableDictionary<NSString *, id> *event = [self baseEvent];
  852. [event addEntriesFromDictionary:@{
  853. @"didFailProvisionalNavigation": @YES,
  854. @"domain": error.domain,
  855. @"code": @(error.code),
  856. @"description": error.localizedDescription,
  857. }];
  858. _onLoadingError(event);
  859. }
  860. }
  861. - (void)evaluateJS:(NSString *)js
  862. thenCall: (void (^)(NSString*)) callback
  863. {
  864. [self.webView evaluateJavaScript: js completionHandler: ^(id result, NSError *error) {
  865. if (callback != nil) {
  866. callback([NSString stringWithFormat:@"%@", result]);
  867. }
  868. if (error != nil) {
  869. 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]);
  870. }
  871. }];
  872. }
  873. /**
  874. * Called when the navigation is complete.
  875. * @see https://fburl.com/rtys6jlb
  876. */
  877. - (void)webView:(WKWebView *)webView
  878. didFinishNavigation:(WKNavigation *)navigation
  879. {
  880. if (_injectedJavaScript) {
  881. [self evaluateJS: _injectedJavaScript thenCall: ^(NSString *jsEvaluationValue) {
  882. NSMutableDictionary *event = [self baseEvent];
  883. event[@"jsEvaluationValue"] = jsEvaluationValue;
  884. if (self.onLoadingFinish) {
  885. self.onLoadingFinish(event);
  886. }
  887. }];
  888. } else if (_onLoadingFinish) {
  889. _onLoadingFinish([self baseEvent]);
  890. }
  891. }
  892. - (void)injectJavaScript:(NSString *)script
  893. {
  894. [self evaluateJS: script thenCall: nil];
  895. }
  896. - (void)goForward
  897. {
  898. [_webView goForward];
  899. }
  900. - (void)goBack
  901. {
  902. [_webView goBack];
  903. }
  904. - (void)reload
  905. {
  906. /**
  907. * When the initial load fails due to network connectivity issues,
  908. * [_webView reload] doesn't reload the webpage. Therefore, we must
  909. * manually call [_webView loadRequest:request].
  910. */
  911. NSURLRequest *request = [self requestForSource:self.source];
  912. if (request.URL && !_webView.URL.absoluteString.length) {
  913. [_webView loadRequest:request];
  914. } else {
  915. [_webView reload];
  916. }
  917. }
  918. - (void)stopLoading
  919. {
  920. [_webView stopLoading];
  921. }
  922. - (void)setBounces:(BOOL)bounces
  923. {
  924. _bounces = bounces;
  925. _webView.scrollView.bounces = bounces;
  926. }
  927. - (NSURLRequest *)requestForSource:(id)json {
  928. NSURLRequest *request = [RCTConvert NSURLRequest:self.source];
  929. // If sharedCookiesEnabled we automatically add all application cookies to the
  930. // http request. This is automatically done on iOS 11+ in the WebView constructor.
  931. // Se we need to manually add these shared cookies here only for iOS versions < 11.
  932. if (_sharedCookiesEnabled) {
  933. if (@available(iOS 11.0, *)) {
  934. // see WKWebView initialization for added cookies
  935. } else {
  936. NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:request.URL];
  937. NSDictionary<NSString *, NSString *> *cookieHeader = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
  938. NSMutableURLRequest *mutableRequest = [request mutableCopy];
  939. [mutableRequest setAllHTTPHeaderFields:cookieHeader];
  940. return mutableRequest;
  941. }
  942. }
  943. return request;
  944. }
  945. @end