react-native-webview.git

RNCWKWebView.m 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  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 "RNCWKWebView.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. // runtime trick to remove WKWebView keyboard default toolbar
  17. // see: http://stackoverflow.com/questions/19033292/ios-7-uiwebview-keyboard-issue/19042279#19042279
  18. @interface _SwizzleHelperWK : NSObject @end
  19. @implementation _SwizzleHelperWK
  20. -(id)inputAccessoryView
  21. {
  22. return nil;
  23. }
  24. @end
  25. @interface RNCWKWebView () <WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, UIScrollViewDelegate, RCTAutoInsetsProtocol>
  26. @property (nonatomic, copy) RCTDirectEventBlock onLoadingStart;
  27. @property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish;
  28. @property (nonatomic, copy) RCTDirectEventBlock onLoadingError;
  29. @property (nonatomic, copy) RCTDirectEventBlock onLoadingProgress;
  30. @property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest;
  31. @property (nonatomic, copy) RCTDirectEventBlock onMessage;
  32. @property (nonatomic, copy) WKWebView *webView;
  33. @end
  34. @implementation RNCWKWebView
  35. {
  36. UIColor * _savedBackgroundColor;
  37. BOOL _savedHideKeyboardAccessoryView;
  38. BOOL _savedKeyboardDisplayRequiresUserAction;
  39. }
  40. - (instancetype)initWithFrame:(CGRect)frame
  41. {
  42. if ((self = [super initWithFrame:frame])) {
  43. super.backgroundColor = [UIColor clearColor];
  44. _bounces = YES;
  45. _scrollEnabled = YES;
  46. _showsHorizontalScrollIndicator = YES;
  47. _showsVerticalScrollIndicator = YES;
  48. _directionalLockEnabled = YES;
  49. _automaticallyAdjustContentInsets = YES;
  50. _contentInset = UIEdgeInsetsZero;
  51. _savedKeyboardDisplayRequiresUserAction = YES;
  52. }
  53. // Workaround for a keyboard dismissal bug present in iOS 12
  54. // https://openradar.appspot.com/radar?id=5018321736957952
  55. if (@available(iOS 12.0, *)) {
  56. [[NSNotificationCenter defaultCenter]
  57. addObserver:self
  58. selector:@selector(keyboardWillHide)
  59. name:UIKeyboardWillHideNotification object:nil];
  60. [[NSNotificationCenter defaultCenter]
  61. addObserver:self
  62. selector:@selector(keyboardWillShow)
  63. name:UIKeyboardWillShowNotification object:nil];
  64. }
  65. return self;
  66. }
  67. - (void)dealloc
  68. {
  69. [[NSNotificationCenter defaultCenter] removeObserver:self];
  70. }
  71. /**
  72. * See https://stackoverflow.com/questions/25713069/why-is-wkwebview-not-opening-links-with-target-blank/25853806#25853806 for details.
  73. */
  74. - (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
  75. {
  76. if (!navigationAction.targetFrame.isMainFrame) {
  77. [webView loadRequest:navigationAction.request];
  78. }
  79. return nil;
  80. }
  81. - (void)didMoveToWindow
  82. {
  83. if (self.window != nil && _webView == nil) {
  84. WKWebViewConfiguration *wkWebViewConfig = [WKWebViewConfiguration new];
  85. if (_incognito) {
  86. wkWebViewConfig.websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore];
  87. } else if (_cacheEnabled) {
  88. wkWebViewConfig.websiteDataStore = [WKWebsiteDataStore defaultDataStore];
  89. }
  90. if(self.useSharedProcessPool) {
  91. wkWebViewConfig.processPool = [[RNCWKProcessPoolManager sharedManager] sharedProcessPool];
  92. }
  93. wkWebViewConfig.userContentController = [WKUserContentController new];
  94. if (_messagingEnabled) {
  95. [wkWebViewConfig.userContentController addScriptMessageHandler:self name:MessageHandlerName];
  96. NSString *source = [NSString stringWithFormat:
  97. @"window.%@ = {"
  98. " postMessage: function (data) {"
  99. " window.webkit.messageHandlers.%@.postMessage(String(data));"
  100. " }"
  101. "};", MessageHandlerName, MessageHandlerName
  102. ];
  103. WKUserScript *script = [[WKUserScript alloc] initWithSource:source injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
  104. [wkWebViewConfig.userContentController addUserScript:script];
  105. }
  106. wkWebViewConfig.allowsInlineMediaPlayback = _allowsInlineMediaPlayback;
  107. #if WEBKIT_IOS_10_APIS_AVAILABLE
  108. wkWebViewConfig.mediaTypesRequiringUserActionForPlayback = _mediaPlaybackRequiresUserAction
  109. ? WKAudiovisualMediaTypeAll
  110. : WKAudiovisualMediaTypeNone;
  111. wkWebViewConfig.dataDetectorTypes = _dataDetectorTypes;
  112. #else
  113. wkWebViewConfig.mediaPlaybackRequiresUserAction = _mediaPlaybackRequiresUserAction;
  114. #endif
  115. if(_sharedCookiesEnabled) {
  116. // More info to sending cookies with WKWebView
  117. // https://stackoverflow.com/questions/26573137/can-i-set-the-cookies-to-be-used-by-a-wkwebview/26577303#26577303
  118. if (@available(iOS 11.0, *)) {
  119. // Set Cookies in iOS 11 and above, initialize websiteDataStore before setting cookies
  120. // See also https://forums.developer.apple.com/thread/97194
  121. // check if websiteDataStore has not been initialized before
  122. if(!_incognito && !_cacheEnabled) {
  123. wkWebViewConfig.websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore];
  124. }
  125. for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
  126. [wkWebViewConfig.websiteDataStore.httpCookieStore setCookie:cookie completionHandler:nil];
  127. }
  128. } else {
  129. NSMutableString *script = [NSMutableString string];
  130. // Clear all existing cookies in a direct called function. This ensures that no
  131. // javascript error will break the web content javascript.
  132. // We keep this code here, if someone requires that Cookies are also removed within the
  133. // the WebView and want to extends the current sharedCookiesEnabled option with an
  134. // additional property.
  135. // Generates JS: document.cookie = "key=; Expires=Thu, 01 Jan 1970 00:00:01 GMT;"
  136. // for each cookie which is already available in the WebView context.
  137. /*
  138. [script appendString:@"(function () {\n"];
  139. [script appendString:@" var cookies = document.cookie.split('; ');\n"];
  140. [script appendString:@" for (var i = 0; i < cookies.length; i++) {\n"];
  141. [script appendString:@" if (cookies[i].indexOf('=') !== -1) {\n"];
  142. [script appendString:@" document.cookie = cookies[i].split('=')[0] + '=; Expires=Thu, 01 Jan 1970 00:00:01 GMT';\n"];
  143. [script appendString:@" }\n"];
  144. [script appendString:@" }\n"];
  145. [script appendString:@"})();\n\n"];
  146. */
  147. // Set cookies in a direct called function. This ensures that no
  148. // javascript error will break the web content javascript.
  149. // Generates JS: document.cookie = "key=value; Path=/; Expires=Thu, 01 Jan 20xx 00:00:01 GMT;"
  150. // for each cookie which is available in the application context.
  151. [script appendString:@"(function () {\n"];
  152. for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
  153. [script appendFormat:@"document.cookie = %@ + '=' + %@",
  154. RCTJSONStringify(cookie.name, NULL),
  155. RCTJSONStringify(cookie.value, NULL)];
  156. if (cookie.path) {
  157. [script appendFormat:@" + '; Path=' + %@", RCTJSONStringify(cookie.path, NULL)];
  158. }
  159. if (cookie.expiresDate) {
  160. [script appendFormat:@" + '; Expires=' + new Date(%f).toUTCString()",
  161. cookie.expiresDate.timeIntervalSince1970 * 1000
  162. ];
  163. }
  164. [script appendString:@";\n"];
  165. }
  166. [script appendString:@"})();\n"];
  167. WKUserScript* cookieInScript = [[WKUserScript alloc] initWithSource:script
  168. injectionTime:WKUserScriptInjectionTimeAtDocumentStart
  169. forMainFrameOnly:YES];
  170. [wkWebViewConfig.userContentController addUserScript:cookieInScript];
  171. }
  172. }
  173. _webView = [[WKWebView alloc] initWithFrame:self.bounds configuration: wkWebViewConfig];
  174. _webView.scrollView.delegate = self;
  175. _webView.UIDelegate = self;
  176. _webView.navigationDelegate = self;
  177. _webView.scrollView.scrollEnabled = _scrollEnabled;
  178. _webView.scrollView.pagingEnabled = _pagingEnabled;
  179. _webView.scrollView.bounces = _bounces;
  180. _webView.scrollView.showsHorizontalScrollIndicator = _showsHorizontalScrollIndicator;
  181. _webView.scrollView.showsVerticalScrollIndicator = _showsVerticalScrollIndicator;
  182. _webView.scrollView.directionalLockEnabled = _directionalLockEnabled;
  183. _webView.allowsLinkPreview = _allowsLinkPreview;
  184. [_webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
  185. _webView.allowsBackForwardNavigationGestures = _allowsBackForwardNavigationGestures;
  186. if (_userAgent) {
  187. _webView.customUserAgent = _userAgent;
  188. }
  189. #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
  190. if ([_webView.scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
  191. _webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
  192. }
  193. #endif
  194. [self addSubview:_webView];
  195. [self setHideKeyboardAccessoryView: _savedHideKeyboardAccessoryView];
  196. [self setKeyboardDisplayRequiresUserAction: _savedKeyboardDisplayRequiresUserAction];
  197. [self visitSource];
  198. }
  199. }
  200. // Update webview property when the component prop changes.
  201. - (void)setAllowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures {
  202. _allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures;
  203. _webView.allowsBackForwardNavigationGestures = _allowsBackForwardNavigationGestures;
  204. }
  205. - (void)removeFromSuperview
  206. {
  207. if (_webView) {
  208. [_webView.configuration.userContentController removeScriptMessageHandlerForName:MessageHandlerName];
  209. [_webView removeObserver:self forKeyPath:@"estimatedProgress"];
  210. [_webView removeFromSuperview];
  211. _webView.scrollView.delegate = nil;
  212. _webView = nil;
  213. }
  214. [super removeFromSuperview];
  215. }
  216. -(void)keyboardWillHide
  217. {
  218. keyboardTimer = [NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(keyboardDisplacementFix) userInfo:nil repeats:false];
  219. [[NSRunLoop mainRunLoop] addTimer:keyboardTimer forMode:NSRunLoopCommonModes];
  220. }
  221. -(void)keyboardWillShow
  222. {
  223. if (keyboardTimer != nil) {
  224. [keyboardTimer invalidate];
  225. }
  226. }
  227. -(void)keyboardDisplacementFix
  228. {
  229. // Additional viewport checks to prevent unintentional scrolls
  230. UIScrollView *scrollView = self.webView.scrollView;
  231. double maxContentOffset = scrollView.contentSize.height - scrollView.frame.size.height;
  232. if (maxContentOffset < 0) {
  233. maxContentOffset = 0;
  234. }
  235. if (scrollView.contentOffset.y > maxContentOffset) {
  236. // https://stackoverflow.com/a/9637807/824966
  237. [UIView animateWithDuration:.25 animations:^{
  238. scrollView.contentOffset = CGPointMake(0, maxContentOffset);
  239. }];
  240. }
  241. }
  242. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
  243. if ([keyPath isEqual:@"estimatedProgress"] && object == self.webView) {
  244. if(_onLoadingProgress){
  245. NSMutableDictionary<NSString *, id> *event = [self baseEvent];
  246. [event addEntriesFromDictionary:@{@"progress":[NSNumber numberWithDouble:self.webView.estimatedProgress]}];
  247. _onLoadingProgress(event);
  248. }
  249. }else{
  250. [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
  251. }
  252. }
  253. - (void)setBackgroundColor:(UIColor *)backgroundColor
  254. {
  255. _savedBackgroundColor = backgroundColor;
  256. if (_webView == nil) {
  257. return;
  258. }
  259. CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor);
  260. self.opaque = _webView.opaque = (alpha == 1.0);
  261. _webView.scrollView.backgroundColor = backgroundColor;
  262. _webView.backgroundColor = backgroundColor;
  263. }
  264. /**
  265. * This method is called whenever JavaScript running within the web view calls:
  266. * - window.webkit.messageHandlers[MessageHandlerName].postMessage
  267. */
  268. - (void)userContentController:(WKUserContentController *)userContentController
  269. didReceiveScriptMessage:(WKScriptMessage *)message
  270. {
  271. if (_onMessage != nil) {
  272. NSMutableDictionary<NSString *, id> *event = [self baseEvent];
  273. [event addEntriesFromDictionary: @{@"data": message.body}];
  274. _onMessage(event);
  275. }
  276. }
  277. - (void)setSource:(NSDictionary *)source
  278. {
  279. if (![_source isEqualToDictionary:source]) {
  280. _source = [source copy];
  281. if (_webView != nil) {
  282. [self visitSource];
  283. }
  284. }
  285. }
  286. - (void)setContentInset:(UIEdgeInsets)contentInset
  287. {
  288. _contentInset = contentInset;
  289. [RCTView autoAdjustInsetsForView:self
  290. withScrollView:_webView.scrollView
  291. updateOffset:NO];
  292. }
  293. - (void)refreshContentInset
  294. {
  295. [RCTView autoAdjustInsetsForView:self
  296. withScrollView:_webView.scrollView
  297. updateOffset:YES];
  298. }
  299. - (void)visitSource
  300. {
  301. // Check for a static html source first
  302. NSString *html = [RCTConvert NSString:_source[@"html"]];
  303. if (html) {
  304. NSURL *baseURL = [RCTConvert NSURL:_source[@"baseUrl"]];
  305. if (!baseURL) {
  306. baseURL = [NSURL URLWithString:@"about:blank"];
  307. }
  308. [_webView loadHTMLString:html baseURL:baseURL];
  309. return;
  310. }
  311. NSURLRequest *request = [self requestForSource:_source];
  312. // Because of the way React works, as pages redirect, we actually end up
  313. // passing the redirect urls back here, so we ignore them if trying to load
  314. // the same url. We'll expose a call to 'reload' to allow a user to load
  315. // the existing page.
  316. if ([request.URL isEqual:_webView.URL]) {
  317. return;
  318. }
  319. if (!request.URL) {
  320. // Clear the webview
  321. [_webView loadHTMLString:@"" baseURL:nil];
  322. return;
  323. }
  324. if (request.URL.host) {
  325. [_webView loadRequest:request];
  326. }
  327. else {
  328. [_webView loadFileURL:request.URL allowingReadAccessToURL:request.URL];
  329. }
  330. }
  331. -(void)setKeyboardDisplayRequiresUserAction:(BOOL)keyboardDisplayRequiresUserAction
  332. {
  333. if (_webView == nil) {
  334. _savedKeyboardDisplayRequiresUserAction = keyboardDisplayRequiresUserAction;
  335. return;
  336. }
  337. if (_savedKeyboardDisplayRequiresUserAction == true) {
  338. return;
  339. }
  340. UIView* subview;
  341. for (UIView* view in _webView.scrollView.subviews) {
  342. if([[view.class description] hasPrefix:@"WK"])
  343. subview = view;
  344. }
  345. if(subview == nil) return;
  346. Class class = subview.class;
  347. NSOperatingSystemVersion iOS_11_3_0 = (NSOperatingSystemVersion){11, 3, 0};
  348. NSOperatingSystemVersion iOS_12_2_0 = (NSOperatingSystemVersion){12, 2, 0};
  349. Method method;
  350. IMP override;
  351. if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_12_2_0]) {
  352. // iOS 12.2.0 - Future
  353. SEL selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:changingActivityState:userObject:");
  354. method = class_getInstanceMethod(class, selector);
  355. IMP original = method_getImplementation(method);
  356. override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) {
  357. ((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4);
  358. });
  359. }
  360. else if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_11_3_0]) {
  361. // iOS 11.3.0 - 12.2.0
  362. SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:");
  363. method = class_getInstanceMethod(class, selector);
  364. IMP original = method_getImplementation(method);
  365. override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) {
  366. ((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4);
  367. });
  368. } else {
  369. // iOS 9.0 - 11.3.0
  370. SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:");
  371. method = class_getInstanceMethod(class, selector);
  372. IMP original = method_getImplementation(method);
  373. override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, id arg3) {
  374. ((void (*)(id, SEL, void*, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3);
  375. });
  376. }
  377. method_setImplementation(method, override);
  378. }
  379. -(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView
  380. {
  381. if (_webView == nil) {
  382. _savedHideKeyboardAccessoryView = hideKeyboardAccessoryView;
  383. return;
  384. }
  385. if (_savedHideKeyboardAccessoryView == false) {
  386. return;
  387. }
  388. UIView* subview;
  389. for (UIView* view in _webView.scrollView.subviews) {
  390. if([[view.class description] hasPrefix:@"WK"])
  391. subview = view;
  392. }
  393. if(subview == nil) return;
  394. NSString* name = [NSString stringWithFormat:@"%@_SwizzleHelperWK", subview.class.superclass];
  395. Class newClass = NSClassFromString(name);
  396. if(newClass == nil)
  397. {
  398. newClass = objc_allocateClassPair(subview.class, [name cStringUsingEncoding:NSASCIIStringEncoding], 0);
  399. if(!newClass) return;
  400. Method method = class_getInstanceMethod([_SwizzleHelperWK class], @selector(inputAccessoryView));
  401. class_addMethod(newClass, @selector(inputAccessoryView), method_getImplementation(method), method_getTypeEncoding(method));
  402. objc_registerClassPair(newClass);
  403. }
  404. object_setClass(subview, newClass);
  405. }
  406. - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
  407. {
  408. scrollView.decelerationRate = _decelerationRate;
  409. }
  410. - (void)setScrollEnabled:(BOOL)scrollEnabled
  411. {
  412. _scrollEnabled = scrollEnabled;
  413. _webView.scrollView.scrollEnabled = scrollEnabled;
  414. }
  415. - (void)scrollViewDidScroll:(UIScrollView *)scrollView
  416. {
  417. // Don't allow scrolling the scrollView.
  418. if (!_scrollEnabled) {
  419. scrollView.bounds = _webView.bounds;
  420. }
  421. }
  422. - (void)setDirectionalLockEnabled:(BOOL)directionalLockEnabled
  423. {
  424. _directionalLockEnabled = directionalLockEnabled;
  425. _webView.scrollView.directionalLockEnabled = directionalLockEnabled;
  426. }
  427. - (void)setShowsHorizontalScrollIndicator:(BOOL)showsHorizontalScrollIndicator
  428. {
  429. _showsHorizontalScrollIndicator = showsHorizontalScrollIndicator;
  430. _webView.scrollView.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator;
  431. }
  432. - (void)setShowsVerticalScrollIndicator:(BOOL)showsVerticalScrollIndicator
  433. {
  434. _showsVerticalScrollIndicator = showsVerticalScrollIndicator;
  435. _webView.scrollView.showsVerticalScrollIndicator = showsVerticalScrollIndicator;
  436. }
  437. - (void)postMessage:(NSString *)message
  438. {
  439. NSDictionary *eventInitDict = @{@"data": message};
  440. NSString *source = [NSString
  441. stringWithFormat:@"window.dispatchEvent(new MessageEvent('message', %@));",
  442. RCTJSONStringify(eventInitDict, NULL)
  443. ];
  444. [self injectJavaScript: source];
  445. }
  446. - (void)layoutSubviews
  447. {
  448. [super layoutSubviews];
  449. // Ensure webview takes the position and dimensions of RNCWKWebView
  450. _webView.frame = self.bounds;
  451. }
  452. - (NSMutableDictionary<NSString *, id> *)baseEvent
  453. {
  454. NSDictionary *event = @{
  455. @"url": _webView.URL.absoluteString ?: @"",
  456. @"title": _webView.title ?: @"",
  457. @"loading" : @(_webView.loading),
  458. @"canGoBack": @(_webView.canGoBack),
  459. @"canGoForward" : @(_webView.canGoForward)
  460. };
  461. return [[NSMutableDictionary alloc] initWithDictionary: event];
  462. }
  463. + (void)setClientAuthenticationCredential:(nullable NSURLCredential*)credential {
  464. clientAuthenticationCredential = credential;
  465. }
  466. - (void) webView:(WKWebView *)webView
  467. didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
  468. completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable))completionHandler
  469. {
  470. if (!clientAuthenticationCredential) {
  471. completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
  472. return;
  473. }
  474. if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate) {
  475. completionHandler(NSURLSessionAuthChallengeUseCredential, clientAuthenticationCredential);
  476. } else {
  477. completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
  478. }
  479. }
  480. #pragma mark - WKNavigationDelegate methods
  481. /**
  482. * alert
  483. */
  484. - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
  485. {
  486. UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert];
  487. [alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
  488. completionHandler();
  489. }]];
  490. [[self topViewController] presentViewController:alert animated:YES completion:NULL];
  491. }
  492. /**
  493. * confirm
  494. */
  495. - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
  496. UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert];
  497. [alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
  498. completionHandler(YES);
  499. }]];
  500. [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
  501. completionHandler(NO);
  502. }]];
  503. [[self topViewController] presentViewController:alert animated:YES completion:NULL];
  504. }
  505. /**
  506. * prompt
  507. */
  508. - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler{
  509. UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:prompt preferredStyle:UIAlertControllerStyleAlert];
  510. [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
  511. textField.textColor = [UIColor lightGrayColor];
  512. textField.placeholder = defaultText;
  513. }];
  514. [alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
  515. completionHandler([[alert.textFields lastObject] text]);
  516. }]];
  517. [[self topViewController] presentViewController:alert animated:YES completion:NULL];
  518. }
  519. /**
  520. * topViewController
  521. */
  522. -(UIViewController *)topViewController{
  523.    UIViewController *controller = [self topViewControllerWithRootViewController:[self getCurrentWindow].rootViewController];
  524.    return controller;
  525. }
  526. /**
  527. * topViewControllerWithRootViewController
  528. */
  529. -(UIViewController *)topViewControllerWithRootViewController:(UIViewController *)viewController{
  530. if (viewController==nil) return nil;
  531. if (viewController.presentedViewController!=nil) {
  532. return [self topViewControllerWithRootViewController:viewController.presentedViewController];
  533. } else if ([viewController isKindOfClass:[UITabBarController class]]){
  534. return [self topViewControllerWithRootViewController:[(UITabBarController *)viewController selectedViewController]];
  535. } else if ([viewController isKindOfClass:[UINavigationController class]]){
  536. return [self topViewControllerWithRootViewController:[(UINavigationController *)viewController visibleViewController]];
  537. } else {
  538. return viewController;
  539. }
  540. }
  541. /**
  542. * getCurrentWindow
  543. */
  544. -(UIWindow *)getCurrentWindow{
  545. UIWindow *window = [UIApplication sharedApplication].keyWindow;
  546. if (window.windowLevel!=UIWindowLevelNormal) {
  547. for (UIWindow *wid in [UIApplication sharedApplication].windows) {
  548. if (window.windowLevel==UIWindowLevelNormal) {
  549. window = wid;
  550. break;
  551. }
  552. }
  553. }
  554. return window;
  555. }
  556. /**
  557. * Decides whether to allow or cancel a navigation.
  558. * @see https://fburl.com/42r9fxob
  559. */
  560. - (void) webView:(WKWebView *)webView
  561. decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
  562. decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
  563. {
  564. static NSDictionary<NSNumber *, NSString *> *navigationTypes;
  565. static dispatch_once_t onceToken;
  566. dispatch_once(&onceToken, ^{
  567. navigationTypes = @{
  568. @(WKNavigationTypeLinkActivated): @"click",
  569. @(WKNavigationTypeFormSubmitted): @"formsubmit",
  570. @(WKNavigationTypeBackForward): @"backforward",
  571. @(WKNavigationTypeReload): @"reload",
  572. @(WKNavigationTypeFormResubmitted): @"formresubmit",
  573. @(WKNavigationTypeOther): @"other",
  574. };
  575. });
  576. WKNavigationType navigationType = navigationAction.navigationType;
  577. NSURLRequest *request = navigationAction.request;
  578. if (_onShouldStartLoadWithRequest) {
  579. NSMutableDictionary<NSString *, id> *event = [self baseEvent];
  580. [event addEntriesFromDictionary: @{
  581. @"url": (request.URL).absoluteString,
  582. @"navigationType": navigationTypes[@(navigationType)]
  583. }];
  584. if (![self.delegate webView:self
  585. shouldStartLoadForRequest:event
  586. withCallback:_onShouldStartLoadWithRequest]) {
  587. decisionHandler(WKNavigationResponsePolicyCancel);
  588. return;
  589. }
  590. }
  591. if (_onLoadingStart) {
  592. // We have this check to filter out iframe requests and whatnot
  593. BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL];
  594. if (isTopFrame) {
  595. NSMutableDictionary<NSString *, id> *event = [self baseEvent];
  596. [event addEntriesFromDictionary: @{
  597. @"url": (request.URL).absoluteString,
  598. @"navigationType": navigationTypes[@(navigationType)]
  599. }];
  600. _onLoadingStart(event);
  601. }
  602. }
  603. // Allow all navigation by default
  604. decisionHandler(WKNavigationResponsePolicyAllow);
  605. }
  606. /**
  607. * Called when an error occurs while the web view is loading content.
  608. * @see https://fburl.com/km6vqenw
  609. */
  610. - (void) webView:(WKWebView *)webView
  611. didFailProvisionalNavigation:(WKNavigation *)navigation
  612. withError:(NSError *)error
  613. {
  614. if (_onLoadingError) {
  615. if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) {
  616. // NSURLErrorCancelled is reported when a page has a redirect OR if you load
  617. // a new URL in the WebView before the previous one came back. We can just
  618. // ignore these since they aren't real errors.
  619. // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os
  620. return;
  621. }
  622. if ([error.domain isEqualToString:@"WebKitErrorDomain"] && error.code == 102) {
  623. // Error code 102 "Frame load interrupted" is raised by the WKWebView
  624. // when the URL is from an http redirect. This is a common pattern when
  625. // implementing OAuth with a WebView.
  626. return;
  627. }
  628. NSMutableDictionary<NSString *, id> *event = [self baseEvent];
  629. [event addEntriesFromDictionary:@{
  630. @"didFailProvisionalNavigation": @YES,
  631. @"domain": error.domain,
  632. @"code": @(error.code),
  633. @"description": error.localizedDescription,
  634. }];
  635. _onLoadingError(event);
  636. }
  637. [self setBackgroundColor: _savedBackgroundColor];
  638. }
  639. - (void)evaluateJS:(NSString *)js
  640. thenCall: (void (^)(NSString*)) callback
  641. {
  642. [self.webView evaluateJavaScript: js completionHandler: ^(id result, NSError *error) {
  643. if (error == nil) {
  644. if (callback != nil) {
  645. callback([NSString stringWithFormat:@"%@", result]);
  646. }
  647. } else {
  648. RCTLogError(@"Error evaluating injectedJavaScript: This is possibly due to an unsupported return type. Try adding true to the end of your injectedJavaScript string.");
  649. }
  650. }];
  651. }
  652. /**
  653. * Called when the navigation is complete.
  654. * @see https://fburl.com/rtys6jlb
  655. */
  656. - (void) webView:(WKWebView *)webView
  657. didFinishNavigation:(WKNavigation *)navigation
  658. {
  659. if (_injectedJavaScript) {
  660. [self evaluateJS: _injectedJavaScript thenCall: ^(NSString *jsEvaluationValue) {
  661. NSMutableDictionary *event = [self baseEvent];
  662. event[@"jsEvaluationValue"] = jsEvaluationValue;
  663. if (self.onLoadingFinish) {
  664. self.onLoadingFinish(event);
  665. }
  666. }];
  667. } else if (_onLoadingFinish) {
  668. _onLoadingFinish([self baseEvent]);
  669. }
  670. [self setBackgroundColor: _savedBackgroundColor];
  671. }
  672. - (void)injectJavaScript:(NSString *)script
  673. {
  674. [self evaluateJS: script thenCall: nil];
  675. }
  676. - (void)goForward
  677. {
  678. [_webView goForward];
  679. }
  680. - (void)goBack
  681. {
  682. [_webView goBack];
  683. }
  684. - (void)reload
  685. {
  686. /**
  687. * When the initial load fails due to network connectivity issues,
  688. * [_webView reload] doesn't reload the webpage. Therefore, we must
  689. * manually call [_webView loadRequest:request].
  690. */
  691. NSURLRequest *request = [self requestForSource:self.source];
  692. if (request.URL && !_webView.URL.absoluteString.length) {
  693. [_webView loadRequest:request];
  694. } else {
  695. [_webView reload];
  696. }
  697. }
  698. - (void)stopLoading
  699. {
  700. [_webView stopLoading];
  701. }
  702. - (void)setBounces:(BOOL)bounces
  703. {
  704. _bounces = bounces;
  705. _webView.scrollView.bounces = bounces;
  706. }
  707. - (NSURLRequest *)requestForSource:(id)json {
  708. NSURLRequest *request = [RCTConvert NSURLRequest:self.source];
  709. // If sharedCookiesEnabled we automatically add all application cookies to the
  710. // http request. This is automatically done on iOS 11+ in the WebView constructor.
  711. // Se we need to manually add these shared cookies here only for iOS versions < 11.
  712. if (_sharedCookiesEnabled) {
  713. if (@available(iOS 11.0, *)) {
  714. // see WKWebView initialization for added cookies
  715. } else {
  716. NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:request.URL];
  717. NSDictionary<NSString *, NSString *> *cookieHeader = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
  718. NSMutableURLRequest *mutableRequest = [request mutableCopy];
  719. [mutableRequest setAllHTTPHeaderFields:cookieHeader];
  720. return mutableRequest;
  721. }
  722. }
  723. return request;
  724. }
  725. @end