react-native-webview.git

RNCWebView.m 48KB

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