14
14
15
15
#import < UIKit/UIKit.h>
16
16
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>
24
25
#import < objc/runtime.h>
26
+ #import < WebKit/WebKit.h>
25
27
26
28
// This is a very elegent way of defining multiline string in objective-c.
27
29
// source: http://stackoverflow.com/a/23387659/828487
@@ -41,7 +43,7 @@ -(id)inputAccessoryView
41
43
}
42
44
@end
43
45
44
- @interface RCTWebViewBridge () <UIWebViewDelegate , RCTAutoInsetsProtocol>
46
+ @interface RCTWebViewBridge () <WKUIDelegate , WKNavigationDelegate , WKScriptMessageHandler , RCTAutoInsetsProtocol>
45
47
46
48
@property (nonatomic , copy ) RCTDirectEventBlock onLoadingStart;
47
49
@property (nonatomic , copy ) RCTDirectEventBlock onLoadingFinish;
@@ -53,8 +55,9 @@ @interface RCTWebViewBridge () <UIWebViewDelegate, RCTAutoInsetsProtocol>
53
55
54
56
@implementation RCTWebViewBridge
55
57
{
56
- UIWebView *_webView;
58
+ WKWebView *_webView;
57
59
NSString *_injectedJavaScript;
60
+ bool _shouldTrackLoadingStart;
58
61
}
59
62
60
63
- (instancetype )initWithFrame : (CGRect)frame
@@ -63,8 +66,8 @@ - (instancetype)initWithFrame:(CGRect)frame
63
66
super.backgroundColor = [UIColor clearColor ];
64
67
_automaticallyAdjustContentInsets = YES ;
65
68
_contentInset = UIEdgeInsetsZero;
66
- _webView = [[UIWebView alloc ] initWithFrame: self .bounds] ;
67
- _webView. delegate = self;
69
+ _shouldTrackLoadingStart = NO ;
70
+ [ self setupWebview ] ;
68
71
[self addSubview: _webView];
69
72
}
70
73
return self;
@@ -100,12 +103,16 @@ - (void)sendToBridge:(NSString *)message
100
103
);
101
104
102
105
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
+ }];
104
111
}
105
112
106
113
- (NSURL *)URL
107
114
{
108
- return _webView.request . URL ;
115
+ return _webView.URL ;
109
116
}
110
117
111
118
- (void )setSource : (NSDictionary *)source
@@ -126,7 +133,7 @@ - (void)setSource:(NSDictionary *)source
126
133
// passing the redirect urls back here, so we ignore them if trying to load
127
134
// the same url. We'll expose a call to 'reload' to allow a user to load
128
135
// the existing page.
129
- if ([request.URL isEqual: _webView.request. URL]) {
136
+ if ([request.URL isEqual: _webView.URL]) {
130
137
return ;
131
138
}
132
139
if (!request.URL ) {
@@ -167,9 +174,9 @@ - (UIColor *)backgroundColor
167
174
- (NSMutableDictionary <NSString *, id> *)baseEvent
168
175
{
169
176
NSMutableDictionary <NSString *, id > *event = [[NSMutableDictionary alloc ] initWithDictionary: @{
170
- @" url" : _webView.request . URL .absoluteString ?: @" " ,
177
+ @" url" : _webView.URL .absoluteString ?: @" " ,
171
178
@" loading" : @(_webView.loading ),
172
- @" title" : [ _webView stringByEvaluatingJavaScriptFromString: @" document .title" ] ,
179
+ @" title" : _webView.title ,
173
180
@" canGoBack" : @(_webView.canGoBack ),
174
181
@" canGoForward" : @(_webView.canGoForward ),
175
182
}];
@@ -215,58 +222,80 @@ -(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView
215
222
object_setClass (subview, newClass);
216
223
}
217
224
218
- #pragma mark - UIWebViewDelegate methods
225
+ #pragma mark - WebKit WebView Setup and JS Handler
219
226
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 ;
224
234
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;
227
238
239
+ [[NSHTTPCookieStorage sharedHTTPCookieStorage ] setCookieAcceptPolicy: NSHTTPCookieAcceptPolicyAlways];
240
+ }
241
+
242
+ -(void )userContentController : (WKUserContentController *)userContentController didReceiveScriptMessage : (WKScriptMessage *)message {
243
+ if ([message.body rangeOfString: RCTWebViewBridgeSchema].location == NSNotFound ) {
228
244
NSMutableDictionary <NSString *, id > *onBridgeMessageEvent = [[NSMutableDictionary alloc ] initWithDictionary: @{
229
- @" messages" : [self stringArrayJsonToArray: message]
245
+ @" messages" : [self stringArrayJsonToArray: message.body ]
230
246
}];
231
247
232
248
_onBridgeMessage (onBridgeMessageEvent);
233
249
234
- isJSNavigation = YES ;
250
+ return ;
235
251
}
236
252
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 ;
239
274
NSMutableDictionary <NSString *, id > *event = [self baseEvent ];
240
275
[event addEntriesFromDictionary: @{
241
- @" url" : (request.URL ).absoluteString ,
242
- @" navigationType" : @(navigationType)
276
+ @" url" : (navigationAction. request .URL ).absoluteString ,
277
+ @" navigationType" : @(navigationAction. navigationType )
243
278
}];
244
- if (![self .delegate webView: self
245
- shouldStartLoadForRequest: event
246
- withCallback: _onShouldStartLoadWithRequest]) {
247
- return NO ;
248
- }
279
+ _onLoadingStart (event);
249
280
}
250
281
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 );
261
293
}
262
294
}
263
-
264
- // JS Navigation handler
265
- return !isJSNavigation;
295
+ decisionHandler (WKNavigationActionPolicyAllow );
266
296
}
267
297
268
- - (void )webView : (__unused UIWebView *)webView didFailLoadWithError : (NSError *)error
269
- {
298
+ -(void )webView : (WKWebView *)webView didFailNavigation : (WKNavigation *)navigation withError : (NSError *)error {
270
299
if (_onLoadingError) {
271
300
if ([error.domain isEqualToString: NSURLErrorDomain ] && error.code == NSURLErrorCancelled) {
272
301
// 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
286
315
}
287
316
}
288
317
289
- - (void )webViewDidFinishLoad : (UIWebView *)webView
290
- {
291
- // injecting WebViewBridge Script
318
+ -(void )webView : (WKWebView *)webView didFinishNavigation : (WKNavigation *)navigation {
292
319
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
+ }
298
324
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
+ {
301
327
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];
307
330
}
331
+
332
+ return nil ;
308
333
}
309
334
335
+ #pragma mark - WebviewBridge helpers
336
+
310
337
- (NSArray *)stringArrayJsonToArray : (NSString *)message
311
338
{
312
339
return [NSJSONSerialization JSONObjectWithData: [message dataUsingEncoding: NSUTF8StringEncoding]
@@ -326,8 +353,6 @@ - (NSString *)webViewBridgeScript {
326
353
327
354
return NSStringMultiline(
328
355
(function (window) {
329
- ' use strict' ;
330
-
331
356
// Make sure that if WebViewBridge already in scope we don't override it.
332
357
if (window.WebViewBridge ) {
333
358
return ;
@@ -339,14 +364,42 @@ - (NSString *)webViewBridgeScript {
339
364
var doc = window.document ;
340
365
var customEvent = doc.createEvent (' Event' );
341
366
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
+
342
390
function callFunc (func, message) {
343
391
if (' function' === typeof func) {
344
392
func (message);
345
393
}
346
394
}
347
395
348
396
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
+ }
350
403
}
351
404
352
405
// I made the private function ugly signiture so user doesn't called them accidently.
@@ -396,7 +449,7 @@ function signalNative() {
396
449
// dispatch event
397
450
customEvent.initEvent (' WebViewBridge' , true , true );
398
451
doc.dispatchEvent (customEvent);
399
- }(window) );
452
+ })(this );
400
453
);
401
454
}
402
455
0 commit comments