1414
1515#import < UIKit/UIKit.h>
1616
17- #import " RCTAutoInsetsProtocol.h"
18- #import " RCTConvert.h"
19- #import " RCTEventDispatcher.h"
20- #import " RCTLog.h"
21- #import " RCTUtils.h"
22- #import " RCTView.h"
23- #import " UIView+React.h"
17+
18+ #import < React/RCTAutoInsetsProtocol.h>
19+ #import < React/RCTConvert.h>
20+ #import < React/RCTEventDispatcher.h>
21+ #import < React/RCTLog.h>
22+ #import < React/RCTUtils.h>
23+
24+ #import < React/UIView+React.h>
2425#import < objc/runtime.h>
26+ #import < WebKit/WebKit.h>
2527
2628// This is a very elegent way of defining multiline string in objective-c.
2729// source: http://stackoverflow.com/a/23387659/828487
@@ -41,7 +43,7 @@ -(id)inputAccessoryView
4143}
4244@end
4345
44- @interface RCTWebViewBridge () <UIWebViewDelegate , RCTAutoInsetsProtocol>
46+ @interface RCTWebViewBridge () <WKUIDelegate , WKNavigationDelegate , WKScriptMessageHandler , RCTAutoInsetsProtocol>
4547
4648@property (nonatomic , copy ) RCTDirectEventBlock onLoadingStart;
4749@property (nonatomic , copy ) RCTDirectEventBlock onLoadingFinish;
@@ -53,8 +55,9 @@ @interface RCTWebViewBridge () <UIWebViewDelegate, RCTAutoInsetsProtocol>
5355
5456@implementation RCTWebViewBridge
5557{
56- UIWebView *_webView;
58+ WKWebView *_webView;
5759 NSString *_injectedJavaScript;
60+ bool _shouldTrackLoadingStart;
5861}
5962
6063- (instancetype )initWithFrame : (CGRect)frame
@@ -63,8 +66,8 @@ - (instancetype)initWithFrame:(CGRect)frame
6366 super.backgroundColor = [UIColor clearColor ];
6467 _automaticallyAdjustContentInsets = YES ;
6568 _contentInset = UIEdgeInsetsZero;
66- _webView = [[UIWebView alloc ] initWithFrame: self .bounds] ;
67- _webView. delegate = self;
69+ _shouldTrackLoadingStart = NO ;
70+ [ self setupWebview ] ;
6871 [self addSubview: _webView];
6972 }
7073 return self;
@@ -100,12 +103,16 @@ - (void)sendToBridge:(NSString *)message
100103 );
101104
102105 NSString *command = [NSString stringWithFormat: format, message];
103- [_webView stringByEvaluatingJavaScriptFromString: command];
106+ [_webView evaluateJavaScript: command completionHandler: ^(id result, NSError * _Nullable error) {
107+ if (error) {
108+ NSLog (@" WKWebview sendToBridge evaluateJavaScript Error: %@ " , error);
109+ }
110+ }];
104111}
105112
106113- (NSURL *)URL
107114{
108- return _webView.request . URL ;
115+ return _webView.URL ;
109116}
110117
111118- (void )setSource : (NSDictionary *)source
@@ -126,7 +133,7 @@ - (void)setSource:(NSDictionary *)source
126133 // passing the redirect urls back here, so we ignore them if trying to load
127134 // the same url. We'll expose a call to 'reload' to allow a user to load
128135 // the existing page.
129- if ([request.URL isEqual: _webView.request. URL]) {
136+ if ([request.URL isEqual: _webView.URL]) {
130137 return ;
131138 }
132139 if (!request.URL ) {
@@ -167,9 +174,9 @@ - (UIColor *)backgroundColor
167174- (NSMutableDictionary <NSString *, id> *)baseEvent
168175{
169176 NSMutableDictionary <NSString *, id > *event = [[NSMutableDictionary alloc ] initWithDictionary: @{
170- @" url" : _webView.request . URL .absoluteString ?: @" " ,
177+ @" url" : _webView.URL .absoluteString ?: @" " ,
171178 @" loading" : @(_webView.loading ),
172- @" title" : [ _webView stringByEvaluatingJavaScriptFromString: @" document .title" ] ,
179+ @" title" : _webView.title ,
173180 @" canGoBack" : @(_webView.canGoBack ),
174181 @" canGoForward" : @(_webView.canGoForward ),
175182 }];
@@ -215,58 +222,80 @@ -(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView
215222 object_setClass (subview, newClass);
216223}
217224
218- #pragma mark - UIWebViewDelegate methods
225+ #pragma mark - WebKit WebView Setup and JS Handler
219226
220- - (BOOL )webView : (__unused UIWebView *)webView shouldStartLoadWithRequest : (NSURLRequest *)request
221- navigationType : (UIWebViewNavigationType)navigationType
222- {
223- BOOL isJSNavigation = [request.URL.scheme isEqualToString: RCTJSNavigationScheme];
227+ -(void )setupWebview {
228+ WKWebViewConfiguration *theConfiguration = [[WKWebViewConfiguration alloc ] init ];
229+ WKUserContentController *controller = [[WKUserContentController alloc ]init];
230+ [controller addScriptMessageHandler: self name: @" observe" ];
231+
232+ [theConfiguration setUserContentController: controller];
233+ theConfiguration.allowsInlineMediaPlayback = NO ;
224234
225- if (!isJSNavigation && [request.URL.scheme isEqualToString: RCTWebViewBridgeSchema]) {
226- NSString * message = [webView stringByEvaluatingJavaScriptFromString: @" WebViewBridge.__fetch__()" ];
235+ _webView = [[WKWebView alloc ] initWithFrame: self .bounds configuration: theConfiguration];
236+ _webView.UIDelegate = self;
237+ _webView.navigationDelegate = self;
227238
239+ [[NSHTTPCookieStorage sharedHTTPCookieStorage ] setCookieAcceptPolicy: NSHTTPCookieAcceptPolicyAlways];
240+ }
241+
242+ -(void )userContentController : (WKUserContentController *)userContentController didReceiveScriptMessage : (WKScriptMessage *)message {
243+ if ([message.body rangeOfString: RCTWebViewBridgeSchema].location == NSNotFound ) {
228244 NSMutableDictionary <NSString *, id > *onBridgeMessageEvent = [[NSMutableDictionary alloc ] initWithDictionary: @{
229- @" messages" : [self stringArrayJsonToArray: message]
245+ @" messages" : [self stringArrayJsonToArray: message.body ]
230246 }];
231247
232248 _onBridgeMessage (onBridgeMessageEvent);
233249
234- isJSNavigation = YES ;
250+ return ;
235251 }
236252
237- // skip this for the JS Navigation handler
238- if (!isJSNavigation && _onShouldStartLoadWithRequest) {
253+ [_webView evaluateJavaScript: @" WebViewBridge.__fetch__()" completionHandler: ^(id result, NSError * _Nullable error) {
254+ if (!error) {
255+ NSMutableDictionary <NSString *, id > *onBridgeMessageEvent = [[NSMutableDictionary alloc ] initWithDictionary: @{
256+ @" messages" : [self stringArrayJsonToArray: result]
257+ }];
258+
259+ _onBridgeMessage (onBridgeMessageEvent);
260+ }
261+ }];
262+ }
263+
264+ #pragma mark - WebKit WebView Delegate methods
265+
266+ - (void )webView : (WKWebView *)webView didStartProvisionalNavigation : (WKNavigation *)navigation
267+ {
268+ _shouldTrackLoadingStart = YES ;
269+ }
270+
271+ -(void )webView : (WKWebView *)webView decidePolicyForNavigationAction : (WKNavigationAction *)navigationAction decisionHandler : (void (^)(WKNavigationActionPolicy ))decisionHandler {
272+ if (_onLoadingStart && _shouldTrackLoadingStart) {
273+ _shouldTrackLoadingStart = NO ;
239274 NSMutableDictionary <NSString *, id > *event = [self baseEvent ];
240275 [event addEntriesFromDictionary: @{
241- @" url" : (request.URL ).absoluteString ,
242- @" navigationType" : @(navigationType)
276+ @" url" : (navigationAction. request .URL ).absoluteString ,
277+ @" navigationType" : @(navigationAction. navigationType )
243278 }];
244- if (![self .delegate webView: self
245- shouldStartLoadForRequest: event
246- withCallback: _onShouldStartLoadWithRequest]) {
247- return NO ;
248- }
279+ _onLoadingStart (event);
249280 }
250281
251- if (_onLoadingStart) {
252- // We have this check to filter out iframe requests and whatnot
253- BOOL isTopFrame = [request.URL isEqual: request.mainDocumentURL];
254- if (isTopFrame) {
255- NSMutableDictionary <NSString *, id > *event = [self baseEvent ];
256- [event addEntriesFromDictionary: @{
257- @" url" : (request.URL ).absoluteString ,
258- @" navigationType" : @(navigationType)
259- }];
260- _onLoadingStart (event);
282+ if (_onShouldStartLoadWithRequest) {
283+ NSMutableDictionary <NSString *, id > *event = [self baseEvent ];
284+ [event addEntriesFromDictionary: @{
285+ @" url" : (navigationAction.request .URL ).absoluteString ,
286+ @" navigationType" : @(navigationAction.navigationType )
287+ }];
288+
289+ if (![self .delegate webView: self shouldStartLoadForRequest: event withCallback: _onShouldStartLoadWithRequest]) {
290+ decisionHandler (WKNavigationActionPolicyCancel );
291+ }else {
292+ decisionHandler (WKNavigationActionPolicyAllow );
261293 }
262294 }
263-
264- // JS Navigation handler
265- return !isJSNavigation;
295+ decisionHandler (WKNavigationActionPolicyAllow );
266296}
267297
268- - (void )webView : (__unused UIWebView *)webView didFailLoadWithError : (NSError *)error
269- {
298+ -(void )webView : (WKWebView *)webView didFailNavigation : (WKNavigation *)navigation withError : (NSError *)error {
270299 if (_onLoadingError) {
271300 if ([error.domain isEqualToString: NSURLErrorDomain ] && error.code == NSURLErrorCancelled) {
272301 // NSURLErrorCancelled is reported when a page has a redirect OR if you load
@@ -286,27 +315,25 @@ - (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)er
286315 }
287316}
288317
289- - (void )webViewDidFinishLoad : (UIWebView *)webView
290- {
291- // injecting WebViewBridge Script
318+ -(void )webView : (WKWebView *)webView didFinishNavigation : (WKNavigation *)navigation {
292319 NSString *webViewBridgeScriptContent = [self webViewBridgeScript ];
293- [webView stringByEvaluatingJavaScriptFromString: webViewBridgeScriptContent];
294- // ////////////////////////////////////////////////////////////////////////////
295-
296- if (_injectedJavaScript != nil ) {
297- NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString: _injectedJavaScript];
320+ [webView evaluateJavaScript: webViewBridgeScriptContent completionHandler: ^(id result, NSError * _Nullable error) {
321+ _onLoadingFinish ([self baseEvent ]);
322+ }];
323+ }
298324
299- NSMutableDictionary < NSString *, id > *event = [ self baseEvent ];
300- event[ @" jsEvaluationValue " ] = jsEvaluationValue;
325+ - ( WKWebView *) webView : ( WKWebView *) webView createWebViewWithConfiguration : ( WKWebViewConfiguration *) configuration forNavigationAction : ( WKNavigationAction *) navigationAction windowFeatures : ( WKWindowFeatures *) windowFeatures
326+ {
301327
302- _onLoadingFinish (event);
303- }
304- // we only need the final 'finishLoad' call so only fire the event when we're actually done loading.
305- else if (_onLoadingFinish && !webView.loading && ![webView.request.URL.absoluteString isEqualToString: @" about:blank" ]) {
306- _onLoadingFinish ([self baseEvent ]);
328+ if (!navigationAction.targetFrame .isMainFrame ) {
329+ [webView loadRequest: navigationAction.request];
307330 }
331+
332+ return nil ;
308333}
309334
335+ #pragma mark - WebviewBridge helpers
336+
310337- (NSArray *)stringArrayJsonToArray : (NSString *)message
311338{
312339 return [NSJSONSerialization JSONObjectWithData: [message dataUsingEncoding: NSUTF8StringEncoding]
@@ -326,8 +353,6 @@ - (NSString *)webViewBridgeScript {
326353
327354 return NSStringMultiline(
328355 (function (window) {
329- ' use strict' ;
330-
331356 // Make sure that if WebViewBridge already in scope we don't override it.
332357 if (window.WebViewBridge ) {
333358 return ;
@@ -339,14 +364,42 @@ - (NSString *)webViewBridgeScript {
339364 var doc = window.document ;
340365 var customEvent = doc.createEvent (' Event' );
341366
367+ function wkWebViewBridgeAvailable () {
368+ return (
369+ window.webkit &&
370+ window.webkit .messageHandlers &&
371+ window.webkit .messageHandlers .observe &&
372+ window.webkit .messageHandlers .observe .postMessage
373+ );
374+ }
375+
376+ function wkWebViewSend (event) {
377+ if (!wkWebViewBridgeAvailable ()) {
378+ return ;
379+ }
380+ try {
381+ window.webkit .messageHandlers .observe .postMessage (event);
382+ } catch (e) {
383+ console.error (' wkWebViewSend error' , e.message );
384+ if (window.WebViewBridge .onError ) {
385+ window.WebViewBridge .onError (e);
386+ }
387+ }
388+ }
389+
342390 function callFunc (func, message) {
343391 if (' function' === typeof func) {
344392 func (message);
345393 }
346394 }
347395
348396 function signalNative () {
349- window.location = RNWBSchema + ' ://message' + new Date ().getTime ();
397+ if (wkWebViewBridgeAvailable ()) {
398+ var event = window.WebViewBridge .__fetch__ ();
399+ wkWebViewSend (event);
400+ } else { // iOS UIWebview
401+ window.location = RNWBSchema + ' ://message' + new Date ().getTime ();
402+ }
350403 }
351404
352405 // I made the private function ugly signiture so user doesn't called them accidently.
@@ -396,7 +449,7 @@ function signalNative() {
396449 // dispatch event
397450 customEvent.initEvent (' WebViewBridge' , true , true );
398451 doc.dispatchEvent (customEvent);
399- }(window) );
452+ })(this );
400453 );
401454}
402455
0 commit comments