2018-09-08 04:11:49 +00:00
/ * *
* Copyright ( c ) 2015 - present , Facebook , Inc .
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree .
* /
2018-09-18 16:32:16 +00:00
# import "RNCWKWebView.h"
2018-09-08 04:11:49 +00:00
# import < React / RCTConvert . h >
# import < React / RCTAutoInsetsProtocol . h >
2019-01-07 14:19:32 +00:00
# import "RNCWKProcessPoolManager.h"
2018-12-14 10:18:48 +00:00
# import < UIKit / UIKit . h >
2018-09-08 04:11:49 +00:00
2018-10-17 14:59:19 +00:00
# import "objc/runtime.h"
2019-02-06 14:29:59 +00:00
static NSTimer * keyboardTimer ;
2019-02-01 21:04:11 +00:00
static NSString * const MessageHandlerName = @ "ReactNativeWebView" ;
2019-02-12 09:35:14 +00:00
static NSURLCredential * clientAuthenticationCredential ;
2018-09-08 04:11:49 +00:00
2018-10-17 14:59:19 +00:00
// runtime trick to remove WKWebView keyboard default toolbar
// see : http : // stackoverflow . com / questions / 19033292 / ios -7 - uiwebview - keyboard - issue / 19042279 #19042279
@ interface _SwizzleHelperWK : NSObject @ end
@ implementation _SwizzleHelperWK
- ( id ) inputAccessoryView
{
return nil ;
}
@ end
2018-09-18 16:32:16 +00:00
@ interface RNCWKWebView ( ) < WKUIDelegate , WKNavigationDelegate , WKScriptMessageHandler , UIScrollViewDelegate , RCTAutoInsetsProtocol >
2018-09-08 04:11:49 +00:00
@ property ( nonatomic , copy ) RCTDirectEventBlock onLoadingStart ;
@ property ( nonatomic , copy ) RCTDirectEventBlock onLoadingFinish ;
@ property ( nonatomic , copy ) RCTDirectEventBlock onLoadingError ;
2018-10-17 13:08:52 +00:00
@ property ( nonatomic , copy ) RCTDirectEventBlock onLoadingProgress ;
2018-09-08 04:11:49 +00:00
@ property ( nonatomic , copy ) RCTDirectEventBlock onShouldStartLoadWithRequest ;
@ property ( nonatomic , copy ) RCTDirectEventBlock onMessage ;
2019-05-16 22:27:16 +00:00
@ property ( nonatomic , copy ) RCTDirectEventBlock onScroll ;
2018-09-08 04:11:49 +00:00
@ property ( nonatomic , copy ) WKWebView * webView ;
@ end
2018-09-18 16:32:16 +00:00
@ implementation RNCWKWebView
2018-09-08 04:11:49 +00:00
{
UIColor * _savedBackgroundColor ;
2018-10-17 14:59:19 +00:00
BOOL _savedHideKeyboardAccessoryView ;
2019-04-29 15:46:07 +00:00
BOOL _savedKeyboardDisplayRequiresUserAction ;
2019-04-30 08:08:41 +00:00
// Workaround for StatusBar appearance bug for iOS 12
// https : // github . com / react - native - community / react - native - webview / issues / 62
BOOL _isFullScreenVideoOpen ;
UIStatusBarStyle _savedStatusBarStyle ;
BOOL _savedStatusBarHidden ;
2018-09-08 04:11:49 +00:00
}
- ( instancetype ) initWithFrame : ( CGRect ) frame
{
if ( ( self = [ super initWithFrame : frame ] ) ) {
super . backgroundColor = [ UIColor clearColor ] ;
_bounces = YES ;
_scrollEnabled = YES ;
2019-02-12 13:47:24 +00:00
_showsHorizontalScrollIndicator = YES ;
_showsVerticalScrollIndicator = YES ;
2019-03-07 09:27:29 +00:00
_directionalLockEnabled = YES ;
2018-09-08 04:11:49 +00:00
_automaticallyAdjustContentInsets = YES ;
_contentInset = UIEdgeInsetsZero ;
2019-04-29 15:46:07 +00:00
_savedKeyboardDisplayRequiresUserAction = YES ;
2019-04-30 08:08:41 +00:00
_savedStatusBarStyle = RCTSharedApplication ( ) . statusBarStyle ;
_savedStatusBarHidden = RCTSharedApplication ( ) . statusBarHidden ;
2018-09-08 04:11:49 +00:00
}
2019-02-06 14:29:59 +00:00
if ( @ available ( iOS 12.0 , * ) ) {
2019-04-30 08:08:41 +00:00
// Workaround for a keyboard dismissal bug present in iOS 12
// https : // openradar . appspot . com / radar ? id = 5018321736957952
2019-02-06 14:29:59 +00:00
[ [ NSNotificationCenter defaultCenter ]
addObserver : self
selector : @ selector ( keyboardWillHide )
name : UIKeyboardWillHideNotification object : nil ] ;
[ [ NSNotificationCenter defaultCenter ]
addObserver : self
selector : @ selector ( keyboardWillShow )
name : UIKeyboardWillShowNotification object : nil ] ;
2019-04-30 08:08:41 +00:00
// Workaround for StatusBar appearance bug for iOS 12
// https : // github . com / react - native - community / react - native - webview / issues / 62
[ [ NSNotificationCenter defaultCenter ] addObserver : self selector : @ selector ( toggleFullScreenVideoStatusBars ) name : @ "_MRMediaRemotePlayerSupportedCommandsDidChangeNotification" object : nil ] ;
2019-02-06 14:29:59 +00:00
}
2019-04-30 08:08:41 +00:00
2018-09-08 04:11:49 +00:00
return self ;
}
2019-02-14 14:18:15 +00:00
- ( void ) dealloc
{
[ [ NSNotificationCenter defaultCenter ] removeObserver : self ] ;
}
2019-02-01 01:18:02 +00:00
/ * *
* See https : // stackoverflow . com / questions / 25713069 / why - is - wkwebview - not - opening - links - with - target - blank / 25853806 #25853806 for details .
* /
- ( WKWebView * ) webView : ( WKWebView * ) webView createWebViewWithConfiguration : ( WKWebViewConfiguration * ) configuration forNavigationAction : ( WKNavigationAction * ) navigationAction windowFeatures : ( WKWindowFeatures * ) windowFeatures
{
if ( ! navigationAction . targetFrame . isMainFrame ) {
[ webView loadRequest : navigationAction . request ] ;
}
return nil ;
}
2018-09-08 04:11:49 +00:00
- ( void ) didMoveToWindow
{
2018-10-18 06:34:00 +00:00
if ( self . window ! = nil && _webView = = nil ) {
2018-09-08 04:11:49 +00:00
WKWebViewConfiguration * wkWebViewConfig = [ WKWebViewConfiguration new ] ;
2019-01-11 13:59:03 +00:00
if ( _incognito ) {
wkWebViewConfig . websiteDataStore = [ WKWebsiteDataStore nonPersistentDataStore ] ;
2019-01-30 09:32:46 +00:00
} else if ( _cacheEnabled ) {
wkWebViewConfig . websiteDataStore = [ WKWebsiteDataStore defaultDataStore ] ;
2019-01-11 13:59:03 +00:00
}
2019-01-07 14:19:32 +00:00
if ( self . useSharedProcessPool ) {
2019-01-30 09:32:46 +00:00
wkWebViewConfig . processPool = [ [ RNCWKProcessPoolManager sharedManager ] sharedProcessPool ] ;
2019-01-07 14:19:32 +00:00
}
2018-09-08 04:11:49 +00:00
wkWebViewConfig . userContentController = [ WKUserContentController new ] ;
2019-02-01 17:37:28 +00:00
if ( _messagingEnabled ) {
[ wkWebViewConfig . userContentController addScriptMessageHandler : self name : MessageHandlerName ] ;
NSString * source = [ NSString stringWithFormat :
@ "window.%@ = {"
" postMessage: function (data) {"
" window.webkit.messageHandlers.%@.postMessage(String(data));"
" }"
"};" , MessageHandlerName , MessageHandlerName
] ;
WKUserScript * script = [ [ WKUserScript alloc ] initWithSource : source injectionTime : WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly : YES ] ;
[ wkWebViewConfig . userContentController addUserScript : script ] ;
}
2018-09-08 04:11:49 +00:00
wkWebViewConfig . allowsInlineMediaPlayback = _allowsInlineMediaPlayback ;
# if WEBKIT_IOS _10 _APIS _AVAILABLE
wkWebViewConfig . mediaTypesRequiringUserActionForPlayback = _mediaPlaybackRequiresUserAction
? WKAudiovisualMediaTypeAll
: WKAudiovisualMediaTypeNone ;
2018-11-19 10:17:57 +00:00
wkWebViewConfig . dataDetectorTypes = _dataDetectorTypes ;
# else
wkWebViewConfig . mediaPlaybackRequiresUserAction = _mediaPlaybackRequiresUserAction ;
2018-09-08 04:11:49 +00:00
# endif
2019-05-20 22:01:22 +00:00
if ( _applicationNameForUserAgent ) {
wkWebViewConfig . applicationNameForUserAgent = [ NSString stringWithFormat : @ "%@ %@" , wkWebViewConfig . applicationNameForUserAgent , _applicationNameForUserAgent ] ;
}
2019-04-15 08:19:10 +00:00
if ( _sharedCookiesEnabled ) {
// More info to sending cookies with WKWebView
// https : // stackoverflow . com / questions / 26573137 / can - i - set - the - cookies - to - be - used - by - a - wkwebview / 26577303 #26577303
if ( @ available ( iOS 11.0 , * ) ) {
// Set Cookies in iOS 11 and above , initialize websiteDataStore before setting cookies
// See also https : // forums . developer . apple . com / thread / 97194
// check if websiteDataStore has not been initialized before
if ( ! _incognito && ! _cacheEnabled ) {
wkWebViewConfig . websiteDataStore = [ WKWebsiteDataStore nonPersistentDataStore ] ;
}
for ( NSHTTPCookie * cookie in [ [ NSHTTPCookieStorage sharedHTTPCookieStorage ] cookies ] ) {
[ wkWebViewConfig . websiteDataStore . httpCookieStore setCookie : cookie completionHandler : nil ] ;
}
} else {
NSMutableString * script = [ NSMutableString string ] ;
// Clear all existing cookies in a direct called function . This ensures that no
// javascript error will break the web content javascript .
// We keep this code here , if someone requires that Cookies are also removed within the
// the WebView and want to extends the current sharedCookiesEnabled option with an
// additional property .
// Generates JS : document . cookie = "key=; Expires=Thu, 01 Jan 1970 00:00:01 GMT;"
// for each cookie which is already available in the WebView context .
/ *
[ script appendString : @ "(function () {\n" ] ;
[ script appendString : @ " var cookies = document.cookie.split('; ');\n" ] ;
[ script appendString : @ " for (var i = 0; i < cookies.length; i++) {\n" ] ;
[ script appendString : @ " if (cookies[i].indexOf('=') !== -1) {\n" ] ;
[ script appendString : @ " document.cookie = cookies[i].split('=')[0] + '=; Expires=Thu, 01 Jan 1970 00:00:01 GMT';\n" ] ;
[ script appendString : @ " }\n" ] ;
[ script appendString : @ " }\n" ] ;
[ script appendString : @ "})();\n\n" ] ;
* /
// Set cookies in a direct called function . This ensures that no
// javascript error will break the web content javascript .
// Generates JS : document . cookie = "key=value; Path=/; Expires=Thu, 01 Jan 20xx 00:00:01 GMT;"
// for each cookie which is available in the application context .
[ script appendString : @ "(function () {\n" ] ;
for ( NSHTTPCookie * cookie in [ [ NSHTTPCookieStorage sharedHTTPCookieStorage ] cookies ] ) {
[ script appendFormat : @ "document.cookie = %@ + '=' + %@" ,
RCTJSONStringify ( cookie . name , NULL ) ,
RCTJSONStringify ( cookie . value , NULL ) ] ;
if ( cookie . path ) {
[ script appendFormat : @ " + '; Path=' + %@" , RCTJSONStringify ( cookie . path , NULL ) ] ;
}
if ( cookie . expiresDate ) {
[ script appendFormat : @ " + '; Expires=' + new Date(%f).toUTCString()" ,
cookie . expiresDate . timeIntervalSince1970 * 1000
] ;
}
[ script appendString : @ ";\n" ] ;
}
[ script appendString : @ "})();\n" ] ;
WKUserScript * cookieInScript = [ [ WKUserScript alloc ] initWithSource : script
injectionTime : WKUserScriptInjectionTimeAtDocumentStart
forMainFrameOnly : YES ] ;
[ wkWebViewConfig . userContentController addUserScript : cookieInScript ] ;
}
}
2018-09-08 04:11:49 +00:00
_webView = [ [ WKWebView alloc ] initWithFrame : self . bounds configuration : wkWebViewConfig ] ;
_webView . scrollView . delegate = self ;
_webView . UIDelegate = self ;
_webView . navigationDelegate = self ;
_webView . scrollView . scrollEnabled = _scrollEnabled ;
2018-11-22 12:58:07 +00:00
_webView . scrollView . pagingEnabled = _pagingEnabled ;
2018-09-08 04:11:49 +00:00
_webView . scrollView . bounces = _bounces ;
2019-02-12 13:47:24 +00:00
_webView . scrollView . showsHorizontalScrollIndicator = _showsHorizontalScrollIndicator ;
_webView . scrollView . showsVerticalScrollIndicator = _showsVerticalScrollIndicator ;
2019-03-07 09:27:29 +00:00
_webView . scrollView . directionalLockEnabled = _directionalLockEnabled ;
2018-11-25 10:17:28 +00:00
_webView . allowsLinkPreview = _allowsLinkPreview ;
2018-10-17 13:08:52 +00:00
[ _webView addObserver : self forKeyPath : @ "estimatedProgress" options : NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context : nil ] ;
2018-10-21 21:12:59 +00:00
_webView . allowsBackForwardNavigationGestures = _allowsBackForwardNavigationGestures ;
2018-11-25 10:17:28 +00:00
2018-11-19 10:13:31 +00:00
if ( _userAgent ) {
_webView . customUserAgent = _userAgent ;
}
2018-09-08 04:11:49 +00:00
# if defined ( __IPHONE _OS _VERSION _MAX _ALLOWED ) && __IPHONE _OS _VERSION _MAX _ALLOWED >= 110000 / * __IPHONE _11 _0 * /
if ( [ _webView . scrollView respondsToSelector : @ selector ( setContentInsetAdjustmentBehavior : ) ] ) {
_webView . scrollView . contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever ;
}
# endif
[ self addSubview : _webView ] ;
2018-10-17 14:59:19 +00:00
[ self setHideKeyboardAccessoryView : _savedHideKeyboardAccessoryView ] ;
2019-04-29 15:46:07 +00:00
[ self setKeyboardDisplayRequiresUserAction : _savedKeyboardDisplayRequiresUserAction ] ;
2018-09-08 04:11:49 +00:00
[ self visitSource ] ;
}
}
2019-01-11 18:53:55 +00:00
// Update webview property when the component prop changes .
- ( void ) setAllowsBackForwardNavigationGestures : ( BOOL ) allowsBackForwardNavigationGestures {
_allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures ;
_webView . allowsBackForwardNavigationGestures = _allowsBackForwardNavigationGestures ;
}
2018-11-20 00:48:26 +00:00
- ( void ) removeFromSuperview
{
if ( _webView ) {
2019-02-01 17:37:28 +00:00
[ _webView . configuration . userContentController removeScriptMessageHandlerForName : MessageHandlerName ] ;
2018-11-20 00:48:26 +00:00
[ _webView removeObserver : self forKeyPath : @ "estimatedProgress" ] ;
[ _webView removeFromSuperview ] ;
2019-02-14 14:18:15 +00:00
_webView . scrollView . delegate = nil ;
2018-11-20 00:48:26 +00:00
_webView = nil ;
}
2018-11-22 12:58:07 +00:00
2018-11-20 00:48:26 +00:00
[ super removeFromSuperview ] ;
}
2019-04-30 08:08:41 +00:00
- ( void ) toggleFullScreenVideoStatusBars
{
# pragma clang diagnostic ignored "-Wdeprecated-declarations"
if ( ! _isFullScreenVideoOpen ) {
_isFullScreenVideoOpen = YES ;
RCTUnsafeExecuteOnMainQueueSync ( ^ {
[ RCTSharedApplication ( ) setStatusBarStyle : UIStatusBarStyleLightContent animated : YES ] ;
} ) ;
} else {
_isFullScreenVideoOpen = NO ;
RCTUnsafeExecuteOnMainQueueSync ( ^ {
[ RCTSharedApplication ( ) setStatusBarHidden : _savedStatusBarHidden animated : YES ] ;
[ RCTSharedApplication ( ) setStatusBarStyle : _savedStatusBarStyle animated : YES ] ;
} ) ;
}
# pragma clang diagnostic pop
}
2019-02-06 14:29:59 +00:00
- ( void ) keyboardWillHide
{
keyboardTimer = [ NSTimer scheduledTimerWithTimeInterval : 0 target : self selector : @ selector ( keyboardDisplacementFix ) userInfo : nil repeats : false ] ;
[ [ NSRunLoop mainRunLoop ] addTimer : keyboardTimer forMode : NSRunLoopCommonModes ] ;
}
- ( void ) keyboardWillShow
{
if ( keyboardTimer ! = nil ) {
[ keyboardTimer invalidate ] ;
}
}
- ( void ) keyboardDisplacementFix
{
// Additional viewport checks to prevent unintentional scrolls
UIScrollView * scrollView = self . webView . scrollView ;
double maxContentOffset = scrollView . contentSize . height - scrollView . frame . size . height ;
if ( maxContentOffset < 0 ) {
maxContentOffset = 0 ;
}
if ( scrollView . contentOffset . y > maxContentOffset ) {
// https : // stackoverflow . com / a / 9637807 / 824966
[ UIView animateWithDuration : .25 animations : ^ {
scrollView . contentOffset = CGPointMake ( 0 , maxContentOffset ) ;
} ] ;
}
}
2018-10-17 13:08:52 +00:00
- ( void ) observeValueForKeyPath : ( NSString * ) keyPath ofObject : ( id ) object change : ( NSDictionary < NSKeyValueChangeKey , id > * ) change context : ( void * ) context {
if ( [ keyPath isEqual : @ "estimatedProgress" ] && object = = self . webView ) {
if ( _onLoadingProgress ) {
NSMutableDictionary < NSString * , id > * event = [ self baseEvent ] ;
[ event addEntriesFromDictionary : @ { @ "progress" : [ NSNumber numberWithDouble : self . webView . estimatedProgress ] } ] ;
_onLoadingProgress ( event ) ;
}
} else {
[ super observeValueForKeyPath : keyPath ofObject : object change : change context : context ] ;
}
}
2018-09-08 04:11:49 +00:00
- ( void ) setBackgroundColor : ( UIColor * ) backgroundColor
{
_savedBackgroundColor = backgroundColor ;
if ( _webView = = nil ) {
return ;
}
CGFloat alpha = CGColorGetAlpha ( backgroundColor . CGColor ) ;
self . opaque = _webView . opaque = ( alpha = = 1.0 ) ;
_webView . scrollView . backgroundColor = backgroundColor ;
_webView . backgroundColor = backgroundColor ;
}
/ * *
* This method is called whenever JavaScript running within the web view calls :
2019-02-01 17:37:28 +00:00
* - window . webkit . messageHandlers [ MessageHandlerName ] . postMessage
2018-09-08 04:11:49 +00:00
* /
- ( void ) userContentController : ( WKUserContentController * ) userContentController
didReceiveScriptMessage : ( WKScriptMessage * ) message
{
if ( _onMessage ! = nil ) {
NSMutableDictionary < NSString * , id > * event = [ self baseEvent ] ;
[ event addEntriesFromDictionary : @ { @ "data" : message . body } ] ;
_onMessage ( event ) ;
}
}
- ( void ) setSource : ( NSDictionary * ) source
{
if ( ! [ _source isEqualToDictionary : source ] ) {
_source = [ source copy ] ;
if ( _webView ! = nil ) {
[ self visitSource ] ;
}
}
}
- ( void ) setContentInset : ( UIEdgeInsets ) contentInset
{
_contentInset = contentInset ;
[ RCTView autoAdjustInsetsForView : self
withScrollView : _webView . scrollView
updateOffset : NO ] ;
}
- ( void ) refreshContentInset
{
[ RCTView autoAdjustInsetsForView : self
withScrollView : _webView . scrollView
updateOffset : YES ] ;
}
- ( void ) visitSource
{
2019-04-15 08:19:10 +00:00
// Check for a static html source first
NSString * html = [ RCTConvert NSString : _source [ @ "html" ] ] ;
if ( html ) {
NSURL * baseURL = [ RCTConvert NSURL : _source [ @ "baseUrl" ] ] ;
if ( ! baseURL ) {
baseURL = [ NSURL URLWithString : @ "about:blank" ] ;
}
[ _webView loadHTMLString : html baseURL : baseURL ] ;
return ;
2018-09-08 04:11:49 +00:00
}
2019-04-02 13:46:00 +00:00
2019-04-15 08:19:10 +00:00
NSURLRequest * request = [ self requestForSource : _source ] ;
// Because of the way React works , as pages redirect , we actually end up
// passing the redirect urls back here , so we ignore them if trying to load
// the same url . We ' ll expose a call to ' reload ' to allow a user to load
// the existing page .
if ( [ request . URL isEqual : _webView . URL ] ) {
return ;
}
if ( ! request . URL ) {
// Clear the webview
[ _webView loadHTMLString : @ "" baseURL : nil ] ;
return ;
}
if ( request . URL . host ) {
[ _webView loadRequest : request ] ;
}
else {
[ _webView loadFileURL : request . URL allowingReadAccessToURL : request . URL ] ;
}
2018-09-08 04:11:49 +00:00
}
2019-04-29 15:46:07 +00:00
- ( void ) setKeyboardDisplayRequiresUserAction : ( BOOL ) keyboardDisplayRequiresUserAction
{
if ( _webView = = nil ) {
_savedKeyboardDisplayRequiresUserAction = keyboardDisplayRequiresUserAction ;
return ;
}
if ( _savedKeyboardDisplayRequiresUserAction = = true ) {
return ;
}
UIView * subview ;
for ( UIView * view in _webView . scrollView . subviews ) {
if ( [ [ view . class description ] hasPrefix : @ "WK" ] )
subview = view ;
}
if ( subview = = nil ) return ;
Class class = subview . class ;
NSOperatingSystemVersion iOS_11 _3 _0 = ( NSOperatingSystemVersion ) { 11 , 3 , 0 } ;
NSOperatingSystemVersion iOS_12 _2 _0 = ( NSOperatingSystemVersion ) { 12 , 2 , 0 } ;
Method method ;
IMP override ;
if ( [ [ NSProcessInfo processInfo ] isOperatingSystemAtLeastVersion : iOS_12 _2 _0 ] ) {
// iOS 12.2 .0 - Future
SEL selector = sel_getUid ( "_elementDidFocus:userIsInteracting:blurPreviousNode:changingActivityState:userObject:" ) ;
method = class_getInstanceMethod ( class , selector ) ;
IMP original = method_getImplementation ( method ) ;
override = imp_implementationWithBlock ( ^ void ( id me , void * arg0 , BOOL arg1 , BOOL arg2 , BOOL arg3 , id arg4 ) {
( ( void ( * ) ( id , SEL , void * , BOOL , BOOL , BOOL , id ) ) original ) ( me , selector , arg0 , TRUE , arg2 , arg3 , arg4 ) ;
} ) ;
}
else if ( [ [ NSProcessInfo processInfo ] isOperatingSystemAtLeastVersion : iOS_11 _3 _0 ] ) {
// iOS 11.3 .0 - 12.2 .0
SEL selector = sel_getUid ( "_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:" ) ;
method = class_getInstanceMethod ( class , selector ) ;
IMP original = method_getImplementation ( method ) ;
override = imp_implementationWithBlock ( ^ void ( id me , void * arg0 , BOOL arg1 , BOOL arg2 , BOOL arg3 , id arg4 ) {
( ( void ( * ) ( id , SEL , void * , BOOL , BOOL , BOOL , id ) ) original ) ( me , selector , arg0 , TRUE , arg2 , arg3 , arg4 ) ;
} ) ;
} else {
// iOS 9.0 - 11.3 .0
SEL selector = sel_getUid ( "_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:" ) ;
method = class_getInstanceMethod ( class , selector ) ;
IMP original = method_getImplementation ( method ) ;
override = imp_implementationWithBlock ( ^ void ( id me , void * arg0 , BOOL arg1 , BOOL arg2 , id arg3 ) {
( ( void ( * ) ( id , SEL , void * , BOOL , BOOL , id ) ) original ) ( me , selector , arg0 , TRUE , arg2 , arg3 ) ;
} ) ;
}
method_setImplementation ( method , override ) ;
}
2018-10-17 14:59:19 +00:00
- ( void ) setHideKeyboardAccessoryView : ( BOOL ) hideKeyboardAccessoryView
{
if ( _webView = = nil ) {
_savedHideKeyboardAccessoryView = hideKeyboardAccessoryView ;
return ;
}
if ( _savedHideKeyboardAccessoryView = = false ) {
return ;
}
2018-11-19 10:17:57 +00:00
2018-10-17 14:59:19 +00:00
UIView * subview ;
2019-02-01 17:37:28 +00:00
2018-10-17 14:59:19 +00:00
for ( UIView * view in _webView . scrollView . subviews ) {
if ( [ [ view . class description ] hasPrefix : @ "WK" ] )
subview = view ;
}
2018-11-19 10:17:57 +00:00
2018-10-17 14:59:19 +00:00
if ( subview = = nil ) return ;
2018-11-19 10:17:57 +00:00
2018-10-17 14:59:19 +00:00
NSString * name = [ NSString stringWithFormat : @ "%@_SwizzleHelperWK" , subview . class . superclass ] ;
Class newClass = NSClassFromString ( name ) ;
2018-11-19 10:17:57 +00:00
2018-10-17 14:59:19 +00:00
if ( newClass = = nil )
{
newClass = objc_allocateClassPair ( subview . class , [ name cStringUsingEncoding : NSASCIIStringEncoding ] , 0 ) ;
if ( ! newClass ) return ;
2018-11-19 10:17:57 +00:00
2018-10-17 14:59:19 +00:00
Method method = class_getInstanceMethod ( [ _SwizzleHelperWK class ] , @ selector ( inputAccessoryView ) ) ;
class_addMethod ( newClass , @ selector ( inputAccessoryView ) , method_getImplementation ( method ) , method_getTypeEncoding ( method ) ) ;
2018-11-19 10:17:57 +00:00
2018-10-17 14:59:19 +00:00
objc_registerClassPair ( newClass ) ;
}
2018-11-19 10:17:57 +00:00
2018-10-17 14:59:19 +00:00
object_setClass ( subview , newClass ) ;
}
2018-09-08 04:11:49 +00:00
- ( void ) scrollViewWillBeginDragging : ( UIScrollView * ) scrollView
{
scrollView . decelerationRate = _decelerationRate ;
}
- ( void ) setScrollEnabled : ( BOOL ) scrollEnabled
{
_scrollEnabled = scrollEnabled ;
_webView . scrollView . scrollEnabled = scrollEnabled ;
2019-02-28 21:01:42 +00:00
}
- ( void ) scrollViewDidScroll : ( UIScrollView * ) scrollView
{
// Don ' t allow scrolling the scrollView .
2019-03-01 09:46:53 +00:00
if ( ! _scrollEnabled ) {
scrollView . bounds = _webView . bounds ;
}
2019-05-16 22:27:16 +00:00
else if ( _onScroll ! = nil ) {
NSDictionary * event = @ {
@ "contentOffset" : @ {
@ "x" : @ ( scrollView . contentOffset . x ) ,
@ "y" : @ ( scrollView . contentOffset . y )
} ,
@ "contentInset" : @ {
@ "top" : @ ( scrollView . contentInset . top ) ,
@ "left" : @ ( scrollView . contentInset . left ) ,
@ "bottom" : @ ( scrollView . contentInset . bottom ) ,
@ "right" : @ ( scrollView . contentInset . right )
} ,
@ "contentSize" : @ {
@ "width" : @ ( scrollView . contentSize . width ) ,
@ "height" : @ ( scrollView . contentSize . height )
} ,
@ "layoutMeasurement" : @ {
@ "width" : @ ( scrollView . frame . size . width ) ,
@ "height" : @ ( scrollView . frame . size . height )
} ,
@ "zoomScale" : @ ( scrollView . zoomScale ? : 1 ) ,
} ;
_onScroll ( event ) ;
}
2018-09-08 04:11:49 +00:00
}
2019-03-07 09:27:29 +00:00
- ( void ) setDirectionalLockEnabled : ( BOOL ) directionalLockEnabled
{
_directionalLockEnabled = directionalLockEnabled ;
_webView . scrollView . directionalLockEnabled = directionalLockEnabled ;
}
2019-02-12 13:47:24 +00:00
- ( void ) setShowsHorizontalScrollIndicator : ( BOOL ) showsHorizontalScrollIndicator
{
_showsHorizontalScrollIndicator = showsHorizontalScrollIndicator ;
_webView . scrollView . showsHorizontalScrollIndicator = showsHorizontalScrollIndicator ;
}
- ( void ) setShowsVerticalScrollIndicator : ( BOOL ) showsVerticalScrollIndicator
{
_showsVerticalScrollIndicator = showsVerticalScrollIndicator ;
_webView . scrollView . showsVerticalScrollIndicator = showsVerticalScrollIndicator ;
}
2018-09-08 04:11:49 +00:00
- ( void ) postMessage : ( NSString * ) message
{
NSDictionary * eventInitDict = @ { @ "data" : message } ;
NSString * source = [ NSString
2019-02-01 17:37:28 +00:00
stringWithFormat : @ "window.dispatchEvent(new MessageEvent('message', %@));" ,
2018-09-08 04:11:49 +00:00
RCTJSONStringify ( eventInitDict , NULL )
] ;
2019-02-01 17:37:28 +00:00
[ self injectJavaScript : source ] ;
2018-09-08 04:11:49 +00:00
}
- ( void ) layoutSubviews
{
[ super layoutSubviews ] ;
2018-09-18 16:32:16 +00:00
// Ensure webview takes the position and dimensions of RNCWKWebView
2018-09-08 04:11:49 +00:00
_webView . frame = self . bounds ;
}
- ( NSMutableDictionary < NSString * , id > * ) baseEvent
{
NSDictionary * event = @ {
@ "url" : _webView . URL . absoluteString ? : @ "" ,
2019-04-23 09:16:04 +00:00
@ "title" : _webView . title ? : @ "" ,
2018-09-08 04:11:49 +00:00
@ "loading" : @ ( _webView . loading ) ,
@ "canGoBack" : @ ( _webView . canGoBack ) ,
@ "canGoForward" : @ ( _webView . canGoForward )
} ;
return [ [ NSMutableDictionary alloc ] initWithDictionary : event ] ;
}
2019-02-12 09:35:14 +00:00
+ ( void ) setClientAuthenticationCredential : ( nullable NSURLCredential * ) credential {
clientAuthenticationCredential = credential ;
}
- ( void ) webView : ( WKWebView * ) webView
didReceiveAuthenticationChallenge : ( NSURLAuthenticationChallenge * ) challenge
completionHandler : ( void ( ^ ) ( NSURLSessionAuthChallengeDisposition disposition , NSURLCredential * _Nullable ) ) completionHandler
{
if ( ! clientAuthenticationCredential ) {
completionHandler ( NSURLSessionAuthChallengePerformDefaultHandling , nil ) ;
return ;
}
if ( [ [ challenge protectionSpace ] authenticationMethod ] = = NSURLAuthenticationMethodClientCertificate ) {
completionHandler ( NSURLSessionAuthChallengeUseCredential , clientAuthenticationCredential ) ;
} else {
completionHandler ( NSURLSessionAuthChallengePerformDefaultHandling , nil ) ;
}
}
2018-09-08 04:11:49 +00:00
# pragma mark - WKNavigationDelegate methods
2018-12-14 10:18:48 +00:00
/ * *
* alert
* /
2019-01-07 14:19:32 +00:00
- ( void ) webView : ( WKWebView * ) webView runJavaScriptAlertPanelWithMessage : ( NSString * ) message initiatedByFrame : ( WKFrameInfo * ) frame completionHandler : ( void ( ^ ) ( void ) ) completionHandler
{
2018-12-14 10:18:48 +00:00
UIAlertController * alert = [ UIAlertController alertControllerWithTitle : @ "" message : message preferredStyle : UIAlertControllerStyleAlert ] ;
[ alert addAction : [ UIAlertAction actionWithTitle : @ "Ok" style : UIAlertActionStyleDefault handler : ^ ( UIAlertAction * action ) {
completionHandler ( ) ;
} ] ] ;
[ [ self topViewController ] presentViewController : alert animated : YES completion : NULL ] ;
}
/ * *
* confirm
* /
- ( void ) webView : ( WKWebView * ) webView runJavaScriptConfirmPanelWithMessage : ( NSString * ) message initiatedByFrame : ( WKFrameInfo * ) frame completionHandler : ( void ( ^ ) ( BOOL ) ) completionHandler {
UIAlertController * alert = [ UIAlertController alertControllerWithTitle : @ "" message : message preferredStyle : UIAlertControllerStyleAlert ] ;
[ alert addAction : [ UIAlertAction actionWithTitle : @ "Ok" style : UIAlertActionStyleDefault handler : ^ ( UIAlertAction * action ) {
completionHandler ( YES ) ;
} ] ] ;
[ alert addAction : [ UIAlertAction actionWithTitle : @ "Cancel" style : UIAlertActionStyleCancel handler : ^ ( UIAlertAction * action ) {
completionHandler ( NO ) ;
} ] ] ;
[ [ self topViewController ] presentViewController : alert animated : YES completion : NULL ] ;
}
/ * *
* prompt
* /
- ( void ) webView : ( WKWebView * ) webView runJavaScriptTextInputPanelWithPrompt : ( NSString * ) prompt defaultText : ( NSString * ) defaultText initiatedByFrame : ( WKFrameInfo * ) frame completionHandler : ( void ( ^ ) ( NSString * ) ) completionHandler {
UIAlertController * alert = [ UIAlertController alertControllerWithTitle : @ "" message : prompt preferredStyle : UIAlertControllerStyleAlert ] ;
[ alert addTextFieldWithConfigurationHandler : ^ ( UITextField * textField ) {
textField . textColor = [ UIColor lightGrayColor ] ;
textField . placeholder = defaultText ;
} ] ;
[ alert addAction : [ UIAlertAction actionWithTitle : @ "Ok" style : UIAlertActionStyleDefault handler : ^ ( UIAlertAction * action ) {
completionHandler ( [ [ alert . textFields lastObject ] text ] ) ;
} ] ] ;
[ [ self topViewController ] presentViewController : alert animated : YES completion : NULL ] ;
}
/ * *
* topViewController
* /
- ( UIViewController * ) topViewController {
UIViewController * controller = [ self topViewControllerWithRootViewController : [ self getCurrentWindow ] . rootViewController ] ;
return controller ;
}
/ * *
* topViewControllerWithRootViewController
* /
- ( UIViewController * ) topViewControllerWithRootViewController : ( UIViewController * ) viewController {
if ( viewController = = nil ) return nil ;
if ( viewController . presentedViewController ! = nil ) {
return [ self topViewControllerWithRootViewController : viewController . presentedViewController ] ;
} else if ( [ viewController isKindOfClass : [ UITabBarController class ] ] ) {
return [ self topViewControllerWithRootViewController : [ ( UITabBarController * ) viewController selectedViewController ] ] ;
} else if ( [ viewController isKindOfClass : [ UINavigationController class ] ] ) {
return [ self topViewControllerWithRootViewController : [ ( UINavigationController * ) viewController visibleViewController ] ] ;
} else {
return viewController ;
}
}
/ * *
* getCurrentWindow
* /
- ( UIWindow * ) getCurrentWindow {
UIWindow * window = [ UIApplication sharedApplication ] . keyWindow ;
if ( window . windowLevel ! = UIWindowLevelNormal ) {
for ( UIWindow * wid in [ UIApplication sharedApplication ] . windows ) {
if ( window . windowLevel = = UIWindowLevelNormal ) {
window = wid ;
break ;
}
}
}
return window ;
}
2018-09-08 04:11:49 +00:00
/ * *
* Decides whether to allow or cancel a navigation .
* @ see https : // fburl . com / 42 r9fxob
* /
- ( void ) webView : ( WKWebView * ) webView
decidePolicyForNavigationAction : ( WKNavigationAction * ) navigationAction
decisionHandler : ( void ( ^ ) ( WKNavigationActionPolicy ) ) decisionHandler
{
static NSDictionary < NSNumber * , NSString * > * navigationTypes ;
static dispatch_once _t onceToken ;
dispatch_once ( & onceToken , ^ {
navigationTypes = @ {
@ ( WKNavigationTypeLinkActivated ) : @ "click" ,
@ ( WKNavigationTypeFormSubmitted ) : @ "formsubmit" ,
@ ( WKNavigationTypeBackForward ) : @ "backforward" ,
@ ( WKNavigationTypeReload ) : @ "reload" ,
@ ( WKNavigationTypeFormResubmitted ) : @ "formresubmit" ,
@ ( WKNavigationTypeOther ) : @ "other" ,
} ;
} ) ;
WKNavigationType navigationType = navigationAction . navigationType ;
NSURLRequest * request = navigationAction . request ;
if ( _onShouldStartLoadWithRequest ) {
NSMutableDictionary < NSString * , id > * event = [ self baseEvent ] ;
[ event addEntriesFromDictionary : @ {
@ "url" : ( request . URL ) . absoluteString ,
@ "navigationType" : navigationTypes [ @ ( navigationType ) ]
} ] ;
if ( ! [ self . delegate webView : self
shouldStartLoadForRequest : event
withCallback : _onShouldStartLoadWithRequest ] ) {
decisionHandler ( WKNavigationResponsePolicyCancel ) ;
return ;
}
}
if ( _onLoadingStart ) {
// We have this check to filter out iframe requests and whatnot
BOOL isTopFrame = [ request . URL isEqual : request . mainDocumentURL ] ;
if ( isTopFrame ) {
NSMutableDictionary < NSString * , id > * event = [ self baseEvent ] ;
[ event addEntriesFromDictionary : @ {
@ "url" : ( request . URL ) . absoluteString ,
@ "navigationType" : navigationTypes [ @ ( navigationType ) ]
} ] ;
_onLoadingStart ( event ) ;
}
}
// Allow all navigation by default
decisionHandler ( WKNavigationResponsePolicyAllow ) ;
}
/ * *
* Called when an error occurs while the web view is loading content .
* @ see https : // fburl . com / km6vqenw
* /
- ( void ) webView : ( WKWebView * ) webView
didFailProvisionalNavigation : ( WKNavigation * ) navigation
withError : ( NSError * ) error
{
if ( _onLoadingError ) {
if ( [ error . domain isEqualToString : NSURLErrorDomain ] && error . code = = NSURLErrorCancelled ) {
// NSURLErrorCancelled is reported when a page has a redirect OR if you load
// a new URL in the WebView before the previous one came back . We can just
// ignore these since they aren ' t real errors .
// http : // stackoverflow . com / questions / 1024748 / how - do - i - fix - nsurlerrordomain - error -999 - in - iphone -3 -0 - os
return ;
}
2019-01-11 14:01:49 +00:00
if ( [ error . domain isEqualToString : @ "WebKitErrorDomain" ] && error . code = = 102 ) {
// Error code 102 "Frame load interrupted" is raised by the WKWebView
// when the URL is from an http redirect . This is a common pattern when
// implementing OAuth with a WebView .
return ;
}
2018-09-08 04:11:49 +00:00
NSMutableDictionary < NSString * , id > * event = [ self baseEvent ] ;
[ event addEntriesFromDictionary : @ {
@ "didFailProvisionalNavigation" : @ YES ,
@ "domain" : error . domain ,
@ "code" : @ ( error . code ) ,
@ "description" : error . localizedDescription ,
} ] ;
_onLoadingError ( event ) ;
}
[ self setBackgroundColor : _savedBackgroundColor ] ;
}
- ( void ) evaluateJS : ( NSString * ) js
thenCall : ( void ( ^ ) ( NSString * ) ) callback
{
[ self . webView evaluateJavaScript : js completionHandler : ^ ( id result , NSError * error ) {
2018-12-14 10:24:52 +00:00
if ( error = = nil ) {
if ( callback ! = nil ) {
callback ( [ NSString stringWithFormat : @ "%@" , result ] ) ;
}
} else {
RCTLogError ( @ "Error evaluating injectedJavaScript: This is possibly due to an unsupported return type. Try adding true to the end of your injectedJavaScript string." ) ;
2018-09-08 04:11:49 +00:00
}
} ] ;
}
/ * *
* Called when the navigation is complete .
* @ see https : // fburl . com / rtys6jlb
* /
- ( void ) webView : ( WKWebView * ) webView
didFinishNavigation : ( WKNavigation * ) navigation
{
if ( _injectedJavaScript ) {
[ self evaluateJS : _injectedJavaScript thenCall : ^ ( NSString * jsEvaluationValue ) {
NSMutableDictionary * event = [ self baseEvent ] ;
event [ @ "jsEvaluationValue" ] = jsEvaluationValue ;
2019-02-01 17:37:28 +00:00
2018-09-08 04:11:49 +00:00
if ( self . onLoadingFinish ) {
self . onLoadingFinish ( event ) ;
}
} ] ;
} else if ( _onLoadingFinish ) {
_onLoadingFinish ( [ self baseEvent ] ) ;
}
[ self setBackgroundColor : _savedBackgroundColor ] ;
}
- ( void ) injectJavaScript : ( NSString * ) script
{
[ self evaluateJS : script thenCall : nil ] ;
}
- ( void ) goForward
{
[ _webView goForward ] ;
}
- ( void ) goBack
{
[ _webView goBack ] ;
}
- ( void ) reload
{
/ * *
* When the initial load fails due to network connectivity issues ,
* [ _webView reload ] doesn ' t reload the webpage . Therefore , we must
* manually call [ _webView loadRequest : request ] .
* /
2019-04-15 08:19:10 +00:00
NSURLRequest * request = [ self requestForSource : self . source ] ;
2018-09-08 04:11:49 +00:00
if ( request . URL && ! _webView . URL . absoluteString . length ) {
[ _webView loadRequest : request ] ;
2019-04-15 08:19:10 +00:00
} else {
2018-09-08 04:11:49 +00:00
[ _webView reload ] ;
}
}
- ( void ) stopLoading
{
[ _webView stopLoading ] ;
}
- ( void ) setBounces : ( BOOL ) bounces
{
_bounces = bounces ;
_webView . scrollView . bounces = bounces ;
}
2019-04-15 08:19:10 +00:00
- ( NSURLRequest * ) requestForSource : ( id ) json {
NSURLRequest * request = [ RCTConvert NSURLRequest : self . source ] ;
// If sharedCookiesEnabled we automatically add all application cookies to the
// http request . This is automatically done on iOS 11 + in the WebView constructor .
// Se we need to manually add these shared cookies here only for iOS versions < 11.
if ( _sharedCookiesEnabled ) {
if ( @ available ( iOS 11.0 , * ) ) {
// see WKWebView initialization for added cookies
} else {
NSArray * cookies = [ [ NSHTTPCookieStorage sharedHTTPCookieStorage ] cookiesForURL : request . URL ] ;
NSDictionary < NSString * , NSString * > * cookieHeader = [ NSHTTPCookie requestHeaderFieldsWithCookies : cookies ] ;
NSMutableURLRequest * mutableRequest = [ request mutableCopy ] ;
[ mutableRequest setAllHTTPHeaderFields : cookieHeader ] ;
return mutableRequest ;
}
}
return request ;
}
2018-09-08 04:11:49 +00:00
@ end