UNPKG

53.8 kBPlain TextView Raw
1/*
2 Licensed to the Apache Software Foundation (ASF) under one
3 or more contributor license agreements. See the NOTICE file
4 distributed with this work for additional information
5 regarding copyright ownership. The ASF licenses this file
6 to you under the Apache License, Version 2.0 (the
7 "License"); you may not use this file except in compliance
8 with the License. You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 Unless required by applicable law or agreed to in writing,
13 software distributed under the License is distributed on an
14 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 KIND, either express or implied. See the License for the
16 specific language governing permissions and limitations
17 under the License.
18 */
19
20#import "CDVWKInAppBrowser.h"
21
22#if __has_include("CDVWKProcessPoolFactory.h")
23#import "CDVWKProcessPoolFactory.h"
24#endif
25
26#import <Cordova/CDVPluginResult.h>
27
28#define kInAppBrowserTargetSelf @"_self"
29#define kInAppBrowserTargetSystem @"_system"
30#define kInAppBrowserTargetBlank @"_blank"
31
32#define kInAppBrowserToolbarBarPositionBottom @"bottom"
33#define kInAppBrowserToolbarBarPositionTop @"top"
34
35#define IAB_BRIDGE_NAME @"cordova_iab"
36
37#define TOOLBAR_HEIGHT 44.0
38#define LOCATIONBAR_HEIGHT 21.0
39#define FOOTER_HEIGHT ((TOOLBAR_HEIGHT) + (LOCATIONBAR_HEIGHT))
40
41#pragma mark CDVWKInAppBrowser
42
43@interface CDVWKInAppBrowser () {
44 NSInteger _previousStatusBarStyle;
45}
46@end
47
48@implementation CDVWKInAppBrowser
49
50static CDVWKInAppBrowser* instance = nil;
51
52+ (id) getInstance{
53 return instance;
54}
55
56- (void)pluginInitialize
57{
58 instance = self;
59 _previousStatusBarStyle = -1;
60 _callbackIdPattern = nil;
61 _beforeload = @"";
62 _waitForBeforeload = NO;
63}
64
65- (void)onReset
66{
67 [self close:nil];
68}
69
70- (void)close:(CDVInvokedUrlCommand*)command
71{
72 if (self.inAppBrowserViewController == nil) {
73 NSLog(@"IAB.close() called but it was already closed.");
74 return;
75 }
76
77 // Things are cleaned up in browserExit.
78 [self.inAppBrowserViewController close];
79}
80
81- (BOOL) isSystemUrl:(NSURL*)url
82{
83 if ([[url host] isEqualToString:@"itunes.apple.com"]) {
84 return YES;
85 }
86
87 return NO;
88}
89
90- (void)open:(CDVInvokedUrlCommand*)command
91{
92 CDVPluginResult* pluginResult;
93
94 NSString* url = [command argumentAtIndex:0];
95 NSString* target = [command argumentAtIndex:1 withDefault:kInAppBrowserTargetSelf];
96 NSString* options = [command argumentAtIndex:2 withDefault:@"" andClass:[NSString class]];
97
98 self.callbackId = command.callbackId;
99
100 if (url != nil) {
101 NSURL* baseUrl = [self.webViewEngine URL];
102 NSURL* absoluteUrl = [[NSURL URLWithString:url relativeToURL:baseUrl] absoluteURL];
103
104 if ([self isSystemUrl:absoluteUrl]) {
105 target = kInAppBrowserTargetSystem;
106 }
107
108 if ([target isEqualToString:kInAppBrowserTargetSelf]) {
109 [self openInCordovaWebView:absoluteUrl withOptions:options];
110 } else if ([target isEqualToString:kInAppBrowserTargetSystem]) {
111 [self openInSystem:absoluteUrl];
112 } else { // _blank or anything else
113 [self openInInAppBrowser:absoluteUrl withOptions:options];
114 }
115
116 pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
117 } else {
118 pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"incorrect number of arguments"];
119 }
120
121 [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
122 [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
123}
124
125- (void)openInInAppBrowser:(NSURL*)url withOptions:(NSString*)options
126{
127 CDVInAppBrowserOptions* browserOptions = [CDVInAppBrowserOptions parseOptions:options];
128
129 WKWebsiteDataStore* dataStore = [WKWebsiteDataStore defaultDataStore];
130 if (browserOptions.cleardata) {
131
132 NSDate* dateFrom = [NSDate dateWithTimeIntervalSince1970:0];
133 [dataStore removeDataOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] modifiedSince:dateFrom completionHandler:^{
134 NSLog(@"Removed all WKWebView data");
135 self.inAppBrowserViewController.webView.configuration.processPool = [[WKProcessPool alloc] init]; // create new process pool to flush all data
136 }];
137 }
138
139 if (browserOptions.clearcache) {
140 bool isAtLeastiOS11 = false;
141#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
142 if (@available(iOS 11.0, *)) {
143 isAtLeastiOS11 = true;
144 }
145#endif
146
147 if(isAtLeastiOS11){
148#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
149 // Deletes all cookies
150 WKHTTPCookieStore* cookieStore = dataStore.httpCookieStore;
151 [cookieStore getAllCookies:^(NSArray* cookies) {
152 NSHTTPCookie* cookie;
153 for(cookie in cookies){
154 [cookieStore deleteCookie:cookie completionHandler:nil];
155 }
156 }];
157#endif
158 }else{
159 // https://stackoverflow.com/a/31803708/777265
160 // Only deletes domain cookies (not session cookies)
161 [dataStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes]
162 completionHandler:^(NSArray<WKWebsiteDataRecord *> * __nonnull records) {
163 for (WKWebsiteDataRecord *record in records){
164 NSSet<NSString*>* dataTypes = record.dataTypes;
165 if([dataTypes containsObject:WKWebsiteDataTypeCookies]){
166 [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:record.dataTypes
167 forDataRecords:@[record]
168 completionHandler:^{}];
169 }
170 }
171 }];
172 }
173 }
174
175 if (browserOptions.clearsessioncache) {
176 bool isAtLeastiOS11 = false;
177#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
178 if (@available(iOS 11.0, *)) {
179 isAtLeastiOS11 = true;
180 }
181#endif
182 if (isAtLeastiOS11) {
183#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
184 // Deletes session cookies
185 WKHTTPCookieStore* cookieStore = dataStore.httpCookieStore;
186 [cookieStore getAllCookies:^(NSArray* cookies) {
187 NSHTTPCookie* cookie;
188 for(cookie in cookies){
189 if(cookie.sessionOnly){
190 [cookieStore deleteCookie:cookie completionHandler:nil];
191 }
192 }
193 }];
194#endif
195 }else{
196 NSLog(@"clearsessioncache not available below iOS 11.0");
197 }
198 }
199
200 if (self.inAppBrowserViewController == nil) {
201 self.inAppBrowserViewController = [[CDVWKInAppBrowserViewController alloc] initWithBrowserOptions: browserOptions andSettings:self.commandDelegate.settings];
202 self.inAppBrowserViewController.navigationDelegate = self;
203
204 if ([self.viewController conformsToProtocol:@protocol(CDVScreenOrientationDelegate)]) {
205 self.inAppBrowserViewController.orientationDelegate = (UIViewController <CDVScreenOrientationDelegate>*)self.viewController;
206 }
207 }
208
209 [self.inAppBrowserViewController showLocationBar:browserOptions.location];
210 [self.inAppBrowserViewController showToolBar:browserOptions.toolbar :browserOptions.toolbarposition];
211 if (browserOptions.closebuttoncaption != nil || browserOptions.closebuttoncolor != nil) {
212 int closeButtonIndex = browserOptions.lefttoright ? (browserOptions.hidenavigationbuttons ? 1 : 4) : 0;
213 [self.inAppBrowserViewController setCloseButtonTitle:browserOptions.closebuttoncaption :browserOptions.closebuttoncolor :closeButtonIndex];
214 }
215 // Set Presentation Style
216 UIModalPresentationStyle presentationStyle = UIModalPresentationFullScreen; // default
217 if (browserOptions.presentationstyle != nil) {
218 if ([[browserOptions.presentationstyle lowercaseString] isEqualToString:@"pagesheet"]) {
219 presentationStyle = UIModalPresentationPageSheet;
220 } else if ([[browserOptions.presentationstyle lowercaseString] isEqualToString:@"formsheet"]) {
221 presentationStyle = UIModalPresentationFormSheet;
222 }
223 }
224 self.inAppBrowserViewController.modalPresentationStyle = presentationStyle;
225
226 // Set Transition Style
227 UIModalTransitionStyle transitionStyle = UIModalTransitionStyleCoverVertical; // default
228 if (browserOptions.transitionstyle != nil) {
229 if ([[browserOptions.transitionstyle lowercaseString] isEqualToString:@"fliphorizontal"]) {
230 transitionStyle = UIModalTransitionStyleFlipHorizontal;
231 } else if ([[browserOptions.transitionstyle lowercaseString] isEqualToString:@"crossdissolve"]) {
232 transitionStyle = UIModalTransitionStyleCrossDissolve;
233 }
234 }
235 self.inAppBrowserViewController.modalTransitionStyle = transitionStyle;
236
237 //prevent webView from bouncing
238 if (browserOptions.disallowoverscroll) {
239 if ([self.inAppBrowserViewController.webView respondsToSelector:@selector(scrollView)]) {
240 ((UIScrollView*)[self.inAppBrowserViewController.webView scrollView]).bounces = NO;
241 } else {
242 for (id subview in self.inAppBrowserViewController.webView.subviews) {
243 if ([[subview class] isSubclassOfClass:[UIScrollView class]]) {
244 ((UIScrollView*)subview).bounces = NO;
245 }
246 }
247 }
248 }
249
250 // use of beforeload event
251 if([browserOptions.beforeload isKindOfClass:[NSString class]]){
252 _beforeload = browserOptions.beforeload;
253 }else{
254 _beforeload = @"yes";
255 }
256 _waitForBeforeload = ![_beforeload isEqualToString:@""];
257
258 [self.inAppBrowserViewController navigateTo:url];
259 if (!browserOptions.hidden) {
260 [self show:nil withNoAnimate:browserOptions.hidden];
261 }
262}
263
264- (void)show:(CDVInvokedUrlCommand*)command{
265 [self show:command withNoAnimate:NO];
266}
267
268- (void)show:(CDVInvokedUrlCommand*)command withNoAnimate:(BOOL)noAnimate
269{
270 BOOL initHidden = NO;
271 if(command == nil && noAnimate == YES){
272 initHidden = YES;
273 }
274
275 if (self.inAppBrowserViewController == nil) {
276 NSLog(@"Tried to show IAB after it was closed.");
277 return;
278 }
279 if (_previousStatusBarStyle != -1) {
280 NSLog(@"Tried to show IAB while already shown");
281 return;
282 }
283
284 if(!initHidden){
285 _previousStatusBarStyle = [UIApplication sharedApplication].statusBarStyle;
286 }
287
288 __block CDVInAppBrowserNavigationController* nav = [[CDVInAppBrowserNavigationController alloc]
289 initWithRootViewController:self.inAppBrowserViewController];
290 nav.orientationDelegate = self.inAppBrowserViewController;
291 nav.navigationBarHidden = YES;
292 nav.modalPresentationStyle = self.inAppBrowserViewController.modalPresentationStyle;
293 nav.presentationController.delegate = self.inAppBrowserViewController;
294
295 __weak CDVWKInAppBrowser* weakSelf = self;
296
297 // Run later to avoid the "took a long time" log message.
298 dispatch_async(dispatch_get_main_queue(), ^{
299 if (weakSelf.inAppBrowserViewController != nil) {
300 float osVersion = [[[UIDevice currentDevice] systemVersion] floatValue];
301 __strong __typeof(weakSelf) strongSelf = weakSelf;
302 if (!strongSelf->tmpWindow) {
303 CGRect frame = [[UIScreen mainScreen] bounds];
304 if(initHidden && osVersion < 11){
305 frame.origin.x = -10000;
306 }
307 strongSelf->tmpWindow = [[UIWindow alloc] initWithFrame:frame];
308 }
309 UIViewController *tmpController = [[UIViewController alloc] init];
310 [strongSelf->tmpWindow setRootViewController:tmpController];
311 [strongSelf->tmpWindow setWindowLevel:UIWindowLevelNormal];
312
313 if(!initHidden || osVersion < 11){
314 [self->tmpWindow makeKeyAndVisible];
315 }
316 [tmpController presentViewController:nav animated:!noAnimate completion:nil];
317 }
318 });
319}
320
321- (void)hide:(CDVInvokedUrlCommand*)command
322{
323 // Set tmpWindow to hidden to make main webview responsive to touch again
324 // https://stackoverflow.com/questions/4544489/how-to-remove-a-uiwindow
325 self->tmpWindow.hidden = YES;
326 self->tmpWindow = nil;
327
328 if (self.inAppBrowserViewController == nil) {
329 NSLog(@"Tried to hide IAB after it was closed.");
330 return;
331
332
333 }
334 if (_previousStatusBarStyle == -1) {
335 NSLog(@"Tried to hide IAB while already hidden");
336 return;
337 }
338
339 _previousStatusBarStyle = [UIApplication sharedApplication].statusBarStyle;
340
341 // Run later to avoid the "took a long time" log message.
342 dispatch_async(dispatch_get_main_queue(), ^{
343 if (self.inAppBrowserViewController != nil) {
344 _previousStatusBarStyle = -1;
345 [self.inAppBrowserViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
346 }
347 });
348}
349
350- (void)openInCordovaWebView:(NSURL*)url withOptions:(NSString*)options
351{
352 NSURLRequest* request = [NSURLRequest requestWithURL:url];
353 // the webview engine itself will filter for this according to <allow-navigation> policy
354 // in config.xml for cordova-ios-4.0
355 [self.webViewEngine loadRequest:request];
356}
357
358- (void)openInSystem:(NSURL*)url
359{
360 if ([[UIApplication sharedApplication] openURL:url] == NO) {
361 [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]];
362 [[UIApplication sharedApplication] openURL:url];
363 }
364}
365
366- (void)loadAfterBeforeload:(CDVInvokedUrlCommand*)command
367{
368 NSString* urlStr = [command argumentAtIndex:0];
369
370 if ([_beforeload isEqualToString:@""]) {
371 NSLog(@"unexpected loadAfterBeforeload called without feature beforeload=get|post");
372 }
373 if (self.inAppBrowserViewController == nil) {
374 NSLog(@"Tried to invoke loadAfterBeforeload on IAB after it was closed.");
375 return;
376 }
377 if (urlStr == nil) {
378 NSLog(@"loadAfterBeforeload called with nil argument, ignoring.");
379 return;
380 }
381
382 NSURL* url = [NSURL URLWithString:urlStr];
383 //_beforeload = @"";
384 _waitForBeforeload = NO;
385 [self.inAppBrowserViewController navigateTo:url];
386}
387
388// This is a helper method for the inject{Script|Style}{Code|File} API calls, which
389// provides a consistent method for injecting JavaScript code into the document.
390//
391// If a wrapper string is supplied, then the source string will be JSON-encoded (adding
392// quotes) and wrapped using string formatting. (The wrapper string should have a single
393// '%@' marker).
394//
395// If no wrapper is supplied, then the source string is executed directly.
396
397- (void)injectDeferredObject:(NSString*)source withWrapper:(NSString*)jsWrapper
398{
399 // Ensure a message handler bridge is created to communicate with the CDVWKInAppBrowserViewController
400 [self evaluateJavaScript: [NSString stringWithFormat:@"(function(w){if(!w._cdvMessageHandler) {w._cdvMessageHandler = function(id,d){w.webkit.messageHandlers.%@.postMessage({d:d, id:id});}}})(window)", IAB_BRIDGE_NAME]];
401
402 if (jsWrapper != nil) {
403 NSData* jsonData = [NSJSONSerialization dataWithJSONObject:@[source] options:0 error:nil];
404 NSString* sourceArrayString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
405 if (sourceArrayString) {
406 NSString* sourceString = [sourceArrayString substringWithRange:NSMakeRange(1, [sourceArrayString length] - 2)];
407 NSString* jsToInject = [NSString stringWithFormat:jsWrapper, sourceString];
408 [self evaluateJavaScript:jsToInject];
409 }
410 } else {
411 [self evaluateJavaScript:source];
412 }
413}
414
415
416//Synchronus helper for javascript evaluation
417- (void)evaluateJavaScript:(NSString *)script {
418 __block NSString* _script = script;
419 [self.inAppBrowserViewController.webView evaluateJavaScript:script completionHandler:^(id result, NSError *error) {
420 if (error == nil) {
421 if (result != nil) {
422 NSLog(@"%@", result);
423 }
424 } else {
425 NSLog(@"evaluateJavaScript error : %@ : %@", error.localizedDescription, _script);
426 }
427 }];
428}
429
430- (void)injectScriptCode:(CDVInvokedUrlCommand*)command
431{
432 NSString* jsWrapper = nil;
433
434 if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) {
435 jsWrapper = [NSString stringWithFormat:@"_cdvMessageHandler('%@',JSON.stringify([eval(%%@)]));", command.callbackId];
436 }
437 [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper];
438}
439
440- (void)injectScriptFile:(CDVInvokedUrlCommand*)command
441{
442 NSString* jsWrapper;
443
444 if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) {
445 jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('script'); c.src = %%@; c.onload = function() { _cdvMessageHandler('%@'); }; d.body.appendChild(c); })(document)", command.callbackId];
446 } else {
447 jsWrapper = @"(function(d) { var c = d.createElement('script'); c.src = %@; d.body.appendChild(c); })(document)";
448 }
449 [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper];
450}
451
452- (void)injectStyleCode:(CDVInvokedUrlCommand*)command
453{
454 NSString* jsWrapper;
455
456 if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) {
457 jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('style'); c.innerHTML = %%@; c.onload = function() { _cdvMessageHandler('%@'); }; d.body.appendChild(c); })(document)", command.callbackId];
458 } else {
459 jsWrapper = @"(function(d) { var c = d.createElement('style'); c.innerHTML = %@; d.body.appendChild(c); })(document)";
460 }
461 [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper];
462}
463
464- (void)injectStyleFile:(CDVInvokedUrlCommand*)command
465{
466 NSString* jsWrapper;
467
468 if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) {
469 jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %%@; c.onload = function() { _cdvMessageHandler('%@'); }; d.body.appendChild(c); })(document)", command.callbackId];
470 } else {
471 jsWrapper = @"(function(d) { var c = d.createElement('link'); c.rel='stylesheet', c.type='text/css'; c.href = %@; d.body.appendChild(c); })(document)";
472 }
473 [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper];
474}
475
476- (BOOL)isValidCallbackId:(NSString *)callbackId
477{
478 NSError *err = nil;
479 // Initialize on first use
480 if (self.callbackIdPattern == nil) {
481 self.callbackIdPattern = [NSRegularExpression regularExpressionWithPattern:@"^InAppBrowser[0-9]{1,10}$" options:0 error:&err];
482 if (err != nil) {
483 // Couldn't initialize Regex; No is safer than Yes.
484 return NO;
485 }
486 }
487 if ([self.callbackIdPattern firstMatchInString:callbackId options:0 range:NSMakeRange(0, [callbackId length])]) {
488 return YES;
489 }
490 return NO;
491}
492
493/**
494 * The message handler bridge provided for the InAppBrowser is capable of executing any oustanding callback belonging
495 * to the InAppBrowser plugin. Care has been taken that other callbacks cannot be triggered, and that no
496 * other code execution is possible.
497 */
498- (void)webView:(WKWebView *)theWebView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
499
500 NSURL* url = navigationAction.request.URL;
501 NSURL* mainDocumentURL = navigationAction.request.mainDocumentURL;
502 BOOL isTopLevelNavigation = [url isEqual:mainDocumentURL];
503 BOOL shouldStart = YES;
504 BOOL useBeforeLoad = NO;
505 NSString* httpMethod = navigationAction.request.HTTPMethod;
506 NSString* errorMessage = nil;
507
508 if([_beforeload isEqualToString:@"post"]){
509 //TODO handle POST requests by preserving POST data then remove this condition
510 errorMessage = @"beforeload doesn't yet support POST requests";
511 }
512 else if(isTopLevelNavigation && (
513 [_beforeload isEqualToString:@"yes"]
514 || ([_beforeload isEqualToString:@"get"] && [httpMethod isEqualToString:@"GET"])
515 // TODO comment in when POST requests are handled
516 // || ([_beforeload isEqualToString:@"post"] && [httpMethod isEqualToString:@"POST"])
517 )){
518 useBeforeLoad = YES;
519 }
520
521 // When beforeload, on first URL change, initiate JS callback. Only after the beforeload event, continue.
522 if (_waitForBeforeload && useBeforeLoad) {
523 CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
524 messageAsDictionary:@{@"type":@"beforeload", @"url":[url absoluteString]}];
525 [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
526
527 [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
528 decisionHandler(WKNavigationActionPolicyCancel);
529 return;
530 }
531
532 if(errorMessage != nil){
533 NSLog(errorMessage);
534 CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
535 messageAsDictionary:@{@"type":@"loaderror", @"url":[url absoluteString], @"code": @"-1", @"message": errorMessage}];
536 [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
537 [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
538 }
539
540 //if is an app store link, let the system handle it, otherwise it fails to load it
541 if ([[ url scheme] isEqualToString:@"itms-appss"] || [[ url scheme] isEqualToString:@"itms-apps"]) {
542 [theWebView stopLoading];
543 [self openInSystem:url];
544 shouldStart = NO;
545 }
546 else if ((self.callbackId != nil) && isTopLevelNavigation) {
547 // Send a loadstart event for each top-level navigation (includes redirects).
548 CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
549 messageAsDictionary:@{@"type":@"loadstart", @"url":[url absoluteString]}];
550 [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
551
552 [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
553 }
554
555 if (useBeforeLoad) {
556 _waitForBeforeload = YES;
557 }
558
559 if(shouldStart){
560 // Fix GH-417 & GH-424: Handle non-default target attribute
561 // Based on https://stackoverflow.com/a/25713070/777265
562 if (!navigationAction.targetFrame){
563 [theWebView loadRequest:navigationAction.request];
564 decisionHandler(WKNavigationActionPolicyCancel);
565 }else{
566 decisionHandler(WKNavigationActionPolicyAllow);
567 }
568 }else{
569 decisionHandler(WKNavigationActionPolicyCancel);
570 }
571}
572
573#pragma mark WKScriptMessageHandler delegate
574- (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message {
575
576 CDVPluginResult* pluginResult = nil;
577
578 if([message.body isKindOfClass:[NSDictionary class]]){
579 NSDictionary* messageContent = (NSDictionary*) message.body;
580 NSString* scriptCallbackId = messageContent[@"id"];
581
582 if([messageContent objectForKey:@"d"]){
583 NSString* scriptResult = messageContent[@"d"];
584 NSError* __autoreleasing error = nil;
585 NSData* decodedResult = [NSJSONSerialization JSONObjectWithData:[scriptResult dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error];
586 if ((error == nil) && [decodedResult isKindOfClass:[NSArray class]]) {
587 pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:(NSArray*)decodedResult];
588 } else {
589 pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_JSON_EXCEPTION];
590 }
591 } else {
592 pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:@[]];
593 }
594 [self.commandDelegate sendPluginResult:pluginResult callbackId:scriptCallbackId];
595 }else if(self.callbackId != nil){
596 // Send a message event
597 NSString* messageContent = (NSString*) message.body;
598 NSError* __autoreleasing error = nil;
599 NSData* decodedResult = [NSJSONSerialization JSONObjectWithData:[messageContent dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error];
600 if (error == nil) {
601 NSMutableDictionary* dResult = [NSMutableDictionary new];
602 [dResult setValue:@"message" forKey:@"type"];
603 [dResult setObject:decodedResult forKey:@"data"];
604 CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dResult];
605 [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
606 [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
607 }
608 }
609}
610
611- (void)didStartProvisionalNavigation:(WKWebView*)theWebView
612{
613 NSLog(@"didStartProvisionalNavigation");
614// self.inAppBrowserViewController.currentURL = theWebView.URL;
615}
616
617- (void)didFinishNavigation:(WKWebView*)theWebView
618{
619 if (self.callbackId != nil) {
620 NSString* url = [theWebView.URL absoluteString];
621 if(url == nil){
622 if(self.inAppBrowserViewController.currentURL != nil){
623 url = [self.inAppBrowserViewController.currentURL absoluteString];
624 }else{
625 url = @"";
626 }
627 }
628 CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
629 messageAsDictionary:@{@"type":@"loadstop", @"url":url}];
630 [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
631
632 [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
633 }
634}
635
636- (void)webView:(WKWebView*)theWebView didFailNavigation:(NSError*)error
637{
638 if (self.callbackId != nil) {
639 NSString* url = [theWebView.URL absoluteString];
640 if(url == nil){
641 if(self.inAppBrowserViewController.currentURL != nil){
642 url = [self.inAppBrowserViewController.currentURL absoluteString];
643 }else{
644 url = @"";
645 }
646 }
647 CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
648 messageAsDictionary:@{@"type":@"loaderror", @"url":url, @"code": [NSNumber numberWithInteger:error.code], @"message": error.localizedDescription}];
649 [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
650
651 [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
652 }
653}
654
655- (void)browserExit
656{
657 if (self.callbackId != nil) {
658 CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
659 messageAsDictionary:@{@"type":@"exit"}];
660 [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
661 self.callbackId = nil;
662 }
663
664 [self.inAppBrowserViewController.configuration.userContentController removeScriptMessageHandlerForName:IAB_BRIDGE_NAME];
665 self.inAppBrowserViewController.configuration = nil;
666
667 [self.inAppBrowserViewController.webView stopLoading];
668 [self.inAppBrowserViewController.webView removeFromSuperview];
669 [self.inAppBrowserViewController.webView setUIDelegate:nil];
670 [self.inAppBrowserViewController.webView setNavigationDelegate:nil];
671 self.inAppBrowserViewController.webView = nil;
672
673 // Set navigationDelegate to nil to ensure no callbacks are received from it.
674 self.inAppBrowserViewController.navigationDelegate = nil;
675 self.inAppBrowserViewController = nil;
676
677 // Set tmpWindow to hidden to make main webview responsive to touch again
678 // Based on https://stackoverflow.com/questions/4544489/how-to-remove-a-uiwindow
679 self->tmpWindow.hidden = YES;
680 self->tmpWindow = nil;
681
682 if (IsAtLeastiOSVersion(@"7.0")) {
683 if (_previousStatusBarStyle != -1) {
684 [[UIApplication sharedApplication] setStatusBarStyle:_previousStatusBarStyle];
685
686 }
687 }
688
689 _previousStatusBarStyle = -1; // this value was reset before reapplying it. caused statusbar to stay black on ios7
690}
691
692@end //CDVWKInAppBrowser
693
694#pragma mark CDVWKInAppBrowserViewController
695
696@implementation CDVWKInAppBrowserViewController
697
698@synthesize currentURL;
699
700CGFloat lastReducedStatusBarHeight = 0.0;
701BOOL isExiting = FALSE;
702
703- (id)initWithBrowserOptions: (CDVInAppBrowserOptions*) browserOptions andSettings:(NSDictionary *)settings
704{
705 self = [super init];
706 if (self != nil) {
707 _browserOptions = browserOptions;
708 _settings = settings;
709 self.webViewUIDelegate = [[CDVWKInAppBrowserUIDelegate alloc] initWithTitle:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]];
710 [self.webViewUIDelegate setViewController:self];
711
712 [self createViews];
713 }
714
715 return self;
716}
717
718-(void)dealloc {
719 //NSLog(@"dealloc");
720}
721
722- (void)createViews
723{
724 // We create the views in code for primarily for ease of upgrades and not requiring an external .xib to be included
725
726 CGRect webViewBounds = self.view.bounds;
727 BOOL toolbarIsAtBottom = ![_browserOptions.toolbarposition isEqualToString:kInAppBrowserToolbarBarPositionTop];
728 webViewBounds.size.height -= _browserOptions.location ? FOOTER_HEIGHT : TOOLBAR_HEIGHT;
729 WKUserContentController* userContentController = [[WKUserContentController alloc] init];
730
731 WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init];
732
733 NSString *userAgent = configuration.applicationNameForUserAgent;
734 if (
735 [self settingForKey:@"OverrideUserAgent"] == nil &&
736 [self settingForKey:@"AppendUserAgent"] != nil
737 ) {
738 userAgent = [NSString stringWithFormat:@"%@ %@", userAgent, [self settingForKey:@"AppendUserAgent"]];
739 }
740 configuration.applicationNameForUserAgent = userAgent;
741 configuration.userContentController = userContentController;
742#if __has_include("CDVWKProcessPoolFactory.h")
743 configuration.processPool = [[CDVWKProcessPoolFactory sharedFactory] sharedProcessPool];
744#endif
745 [configuration.userContentController addScriptMessageHandler:self name:IAB_BRIDGE_NAME];
746
747 //WKWebView options
748 configuration.allowsInlineMediaPlayback = _browserOptions.allowinlinemediaplayback;
749 if (IsAtLeastiOSVersion(@"10.0")) {
750 configuration.ignoresViewportScaleLimits = _browserOptions.enableviewportscale;
751 if(_browserOptions.mediaplaybackrequiresuseraction == YES){
752 configuration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeAll;
753 }else{
754 configuration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone;
755 }
756 }else{ // iOS 9
757 configuration.mediaPlaybackRequiresUserAction = _browserOptions.mediaplaybackrequiresuseraction;
758 }
759
760 if (@available(iOS 13.0, *)) {
761 NSString *contentMode = [self settingForKey:@"PreferredContentMode"];
762 if ([contentMode isEqual: @"mobile"]) {
763 configuration.defaultWebpagePreferences.preferredContentMode = WKContentModeMobile;
764 } else if ([contentMode isEqual: @"desktop"]) {
765 configuration.defaultWebpagePreferences.preferredContentMode = WKContentModeDesktop;
766 }
767
768 }
769
770
771 self.webView = [[WKWebView alloc] initWithFrame:webViewBounds configuration:configuration];
772
773 [self.view addSubview:self.webView];
774 [self.view sendSubviewToBack:self.webView];
775
776
777 self.webView.navigationDelegate = self;
778 self.webView.UIDelegate = self.webViewUIDelegate;
779 self.webView.backgroundColor = [UIColor whiteColor];
780 if ([self settingForKey:@"OverrideUserAgent"] != nil) {
781 self.webView.customUserAgent = [self settingForKey:@"OverrideUserAgent"];
782 }
783
784 self.webView.clearsContextBeforeDrawing = YES;
785 self.webView.clipsToBounds = YES;
786 self.webView.contentMode = UIViewContentModeScaleToFill;
787 self.webView.multipleTouchEnabled = YES;
788 self.webView.opaque = YES;
789 self.webView.userInteractionEnabled = YES;
790 self.automaticallyAdjustsScrollViewInsets = YES ;
791 [self.webView setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth];
792 self.webView.allowsLinkPreview = NO;
793 self.webView.allowsBackForwardNavigationGestures = NO;
794
795#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
796 if (@available(iOS 11.0, *)) {
797 [self.webView.scrollView setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
798 }
799#endif
800
801 self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
802 self.spinner.alpha = 1.000;
803 self.spinner.autoresizesSubviews = YES;
804 self.spinner.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin);
805 self.spinner.clearsContextBeforeDrawing = NO;
806 self.spinner.clipsToBounds = NO;
807 self.spinner.contentMode = UIViewContentModeScaleToFill;
808 self.spinner.frame = CGRectMake(CGRectGetMidX(self.webView.frame), CGRectGetMidY(self.webView.frame), 20.0, 20.0);
809 self.spinner.hidden = NO;
810 self.spinner.hidesWhenStopped = YES;
811 self.spinner.multipleTouchEnabled = NO;
812 self.spinner.opaque = NO;
813 self.spinner.userInteractionEnabled = NO;
814 [self.spinner stopAnimating];
815
816 self.closeButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(close)];
817 self.closeButton.enabled = YES;
818
819 UIBarButtonItem* flexibleSpaceButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
820
821 UIBarButtonItem* fixedSpaceButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
822 fixedSpaceButton.width = 20;
823
824 float toolbarY = toolbarIsAtBottom ? self.view.bounds.size.height - TOOLBAR_HEIGHT : 0.0;
825 CGRect toolbarFrame = CGRectMake(0.0, toolbarY, self.view.bounds.size.width, TOOLBAR_HEIGHT);
826
827 self.toolbar = [[UIToolbar alloc] initWithFrame:toolbarFrame];
828 self.toolbar.alpha = 1.000;
829 self.toolbar.autoresizesSubviews = YES;
830 self.toolbar.autoresizingMask = toolbarIsAtBottom ? (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin) : UIViewAutoresizingFlexibleWidth;
831 self.toolbar.barStyle = UIBarStyleBlackOpaque;
832 self.toolbar.clearsContextBeforeDrawing = NO;
833 self.toolbar.clipsToBounds = NO;
834 self.toolbar.contentMode = UIViewContentModeScaleToFill;
835 self.toolbar.hidden = NO;
836 self.toolbar.multipleTouchEnabled = NO;
837 self.toolbar.opaque = NO;
838 self.toolbar.userInteractionEnabled = YES;
839 if (_browserOptions.toolbarcolor != nil) { // Set toolbar color if user sets it in options
840 self.toolbar.barTintColor = [self colorFromHexString:_browserOptions.toolbarcolor];
841 }
842 if (!_browserOptions.toolbartranslucent) { // Set toolbar translucent to no if user sets it in options
843 self.toolbar.translucent = NO;
844 }
845
846 CGFloat labelInset = 5.0;
847 float locationBarY = toolbarIsAtBottom ? self.view.bounds.size.height - FOOTER_HEIGHT : self.view.bounds.size.height - LOCATIONBAR_HEIGHT;
848
849 self.addressLabel = [[UILabel alloc] initWithFrame:CGRectMake(labelInset, locationBarY, self.view.bounds.size.width - labelInset, LOCATIONBAR_HEIGHT)];
850 self.addressLabel.adjustsFontSizeToFitWidth = NO;
851 self.addressLabel.alpha = 1.000;
852 self.addressLabel.autoresizesSubviews = YES;
853 self.addressLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin;
854 self.addressLabel.backgroundColor = [UIColor clearColor];
855 self.addressLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters;
856 self.addressLabel.clearsContextBeforeDrawing = YES;
857 self.addressLabel.clipsToBounds = YES;
858 self.addressLabel.contentMode = UIViewContentModeScaleToFill;
859 self.addressLabel.enabled = YES;
860 self.addressLabel.hidden = NO;
861 self.addressLabel.lineBreakMode = NSLineBreakByTruncatingTail;
862
863 if ([self.addressLabel respondsToSelector:NSSelectorFromString(@"setMinimumScaleFactor:")]) {
864 [self.addressLabel setValue:@(10.0/[UIFont labelFontSize]) forKey:@"minimumScaleFactor"];
865 } else if ([self.addressLabel respondsToSelector:NSSelectorFromString(@"setMinimumFontSize:")]) {
866 [self.addressLabel setValue:@(10.0) forKey:@"minimumFontSize"];
867 }
868
869 self.addressLabel.multipleTouchEnabled = NO;
870 self.addressLabel.numberOfLines = 1;
871 self.addressLabel.opaque = NO;
872 self.addressLabel.shadowOffset = CGSizeMake(0.0, -1.0);
873 self.addressLabel.text = NSLocalizedString(@"Loading...", nil);
874 self.addressLabel.textAlignment = NSTextAlignmentLeft;
875 self.addressLabel.textColor = [UIColor colorWithWhite:1.000 alpha:1.000];
876 self.addressLabel.userInteractionEnabled = NO;
877
878 NSString* frontArrowString = NSLocalizedString(@"►", nil); // create arrow from Unicode char
879 self.forwardButton = [[UIBarButtonItem alloc] initWithTitle:frontArrowString style:UIBarButtonItemStylePlain target:self action:@selector(goForward:)];
880 self.forwardButton.enabled = YES;
881 self.forwardButton.imageInsets = UIEdgeInsetsZero;
882 if (_browserOptions.navigationbuttoncolor != nil) { // Set button color if user sets it in options
883 self.forwardButton.tintColor = [self colorFromHexString:_browserOptions.navigationbuttoncolor];
884 }
885
886 NSString* backArrowString = NSLocalizedString(@"◄", nil); // create arrow from Unicode char
887 self.backButton = [[UIBarButtonItem alloc] initWithTitle:backArrowString style:UIBarButtonItemStylePlain target:self action:@selector(goBack:)];
888 self.backButton.enabled = YES;
889 self.backButton.imageInsets = UIEdgeInsetsZero;
890 if (_browserOptions.navigationbuttoncolor != nil) { // Set button color if user sets it in options
891 self.backButton.tintColor = [self colorFromHexString:_browserOptions.navigationbuttoncolor];
892 }
893
894 // Filter out Navigation Buttons if user requests so
895 if (_browserOptions.hidenavigationbuttons) {
896 if (_browserOptions.lefttoright) {
897 [self.toolbar setItems:@[flexibleSpaceButton, self.closeButton]];
898 } else {
899 [self.toolbar setItems:@[self.closeButton, flexibleSpaceButton]];
900 }
901 } else if (_browserOptions.lefttoright) {
902 [self.toolbar setItems:@[self.backButton, fixedSpaceButton, self.forwardButton, flexibleSpaceButton, self.closeButton]];
903 } else {
904 [self.toolbar setItems:@[self.closeButton, flexibleSpaceButton, self.backButton, fixedSpaceButton, self.forwardButton]];
905 }
906
907 self.view.backgroundColor = [UIColor clearColor];
908 [self.view addSubview:self.toolbar];
909 [self.view addSubview:self.addressLabel];
910 [self.view addSubview:self.spinner];
911}
912
913- (id)settingForKey:(NSString*)key
914{
915 return [_settings objectForKey:[key lowercaseString]];
916}
917
918- (void) setWebViewFrame : (CGRect) frame {
919 NSLog(@"Setting the WebView's frame to %@", NSStringFromCGRect(frame));
920 [self.webView setFrame:frame];
921}
922
923- (void)setCloseButtonTitle:(NSString*)title : (NSString*) colorString : (int) buttonIndex
924{
925 // the advantage of using UIBarButtonSystemItemDone is the system will localize it for you automatically
926 // but, if you want to set this yourself, knock yourself out (we can't set the title for a system Done button, so we have to create a new one)
927 self.closeButton = nil;
928 // Initialize with title if title is set, otherwise the title will be 'Done' localized
929 self.closeButton = title != nil ? [[UIBarButtonItem alloc] initWithTitle:title style:UIBarButtonItemStyleBordered target:self action:@selector(close)] : [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(close)];
930 self.closeButton.enabled = YES;
931 // If color on closebutton is requested then initialize with that that color, otherwise use initialize with default
932 self.closeButton.tintColor = colorString != nil ? [self colorFromHexString:colorString] : [UIColor colorWithRed:60.0 / 255.0 green:136.0 / 255.0 blue:230.0 / 255.0 alpha:1];
933
934 NSMutableArray* items = [self.toolbar.items mutableCopy];
935 [items replaceObjectAtIndex:buttonIndex withObject:self.closeButton];
936 [self.toolbar setItems:items];
937}
938
939- (void)showLocationBar:(BOOL)show
940{
941 CGRect locationbarFrame = self.addressLabel.frame;
942
943 BOOL toolbarVisible = !self.toolbar.hidden;
944
945 // prevent double show/hide
946 if (show == !(self.addressLabel.hidden)) {
947 return;
948 }
949
950 if (show) {
951 self.addressLabel.hidden = NO;
952
953 if (toolbarVisible) {
954 // toolBar at the bottom, leave as is
955 // put locationBar on top of the toolBar
956
957 CGRect webViewBounds = self.view.bounds;
958 webViewBounds.size.height -= FOOTER_HEIGHT;
959 [self setWebViewFrame:webViewBounds];
960
961 locationbarFrame.origin.y = webViewBounds.size.height;
962 self.addressLabel.frame = locationbarFrame;
963 } else {
964 // no toolBar, so put locationBar at the bottom
965
966 CGRect webViewBounds = self.view.bounds;
967 webViewBounds.size.height -= LOCATIONBAR_HEIGHT;
968 [self setWebViewFrame:webViewBounds];
969
970 locationbarFrame.origin.y = webViewBounds.size.height;
971 self.addressLabel.frame = locationbarFrame;
972 }
973 } else {
974 self.addressLabel.hidden = YES;
975
976 if (toolbarVisible) {
977 // locationBar is on top of toolBar, hide locationBar
978
979 // webView take up whole height less toolBar height
980 CGRect webViewBounds = self.view.bounds;
981 webViewBounds.size.height -= TOOLBAR_HEIGHT;
982 [self setWebViewFrame:webViewBounds];
983 } else {
984 // no toolBar, expand webView to screen dimensions
985 [self setWebViewFrame:self.view.bounds];
986 }
987 }
988}
989
990- (void)showToolBar:(BOOL)show : (NSString *) toolbarPosition
991{
992 CGRect toolbarFrame = self.toolbar.frame;
993 CGRect locationbarFrame = self.addressLabel.frame;
994
995 BOOL locationbarVisible = !self.addressLabel.hidden;
996
997 // prevent double show/hide
998 if (show == !(self.toolbar.hidden)) {
999 return;
1000 }
1001
1002 if (show) {
1003 self.toolbar.hidden = NO;
1004 CGRect webViewBounds = self.view.bounds;
1005
1006 if (locationbarVisible) {
1007 // locationBar at the bottom, move locationBar up
1008 // put toolBar at the bottom
1009 webViewBounds.size.height -= FOOTER_HEIGHT;
1010 locationbarFrame.origin.y = webViewBounds.size.height;
1011 self.addressLabel.frame = locationbarFrame;
1012 self.toolbar.frame = toolbarFrame;
1013 } else {
1014 // no locationBar, so put toolBar at the bottom
1015 CGRect webViewBounds = self.view.bounds;
1016 webViewBounds.size.height -= TOOLBAR_HEIGHT;
1017 self.toolbar.frame = toolbarFrame;
1018 }
1019
1020 if ([toolbarPosition isEqualToString:kInAppBrowserToolbarBarPositionTop]) {
1021 toolbarFrame.origin.y = 0;
1022 webViewBounds.origin.y += toolbarFrame.size.height;
1023 [self setWebViewFrame:webViewBounds];
1024 } else {
1025 toolbarFrame.origin.y = (webViewBounds.size.height + LOCATIONBAR_HEIGHT);
1026 }
1027 [self setWebViewFrame:webViewBounds];
1028
1029 } else {
1030 self.toolbar.hidden = YES;
1031
1032 if (locationbarVisible) {
1033 // locationBar is on top of toolBar, hide toolBar
1034 // put locationBar at the bottom
1035
1036 // webView take up whole height less locationBar height
1037 CGRect webViewBounds = self.view.bounds;
1038 webViewBounds.size.height -= LOCATIONBAR_HEIGHT;
1039 [self setWebViewFrame:webViewBounds];
1040
1041 // move locationBar down
1042 locationbarFrame.origin.y = webViewBounds.size.height;
1043 self.addressLabel.frame = locationbarFrame;
1044 } else {
1045 // no locationBar, expand webView to screen dimensions
1046 [self setWebViewFrame:self.view.bounds];
1047 }
1048 }
1049}
1050
1051- (void)viewDidLoad
1052{
1053 [super viewDidLoad];
1054}
1055
1056- (void)viewDidDisappear:(BOOL)animated
1057{
1058 [super viewDidDisappear:animated];
1059 if (isExiting && (self.navigationDelegate != nil) && [self.navigationDelegate respondsToSelector:@selector(browserExit)]) {
1060 [self.navigationDelegate browserExit];
1061 isExiting = FALSE;
1062 }
1063}
1064
1065- (UIStatusBarStyle)preferredStatusBarStyle
1066{
1067 NSString* statusBarStylePreference = [self settingForKey:@"InAppBrowserStatusBarStyle"];
1068 if (statusBarStylePreference && [statusBarStylePreference isEqualToString:@"lightcontent"]) {
1069 return UIStatusBarStyleLightContent;
1070 } else {
1071 return UIStatusBarStyleDefault;
1072 }
1073}
1074
1075- (BOOL)prefersStatusBarHidden {
1076 return NO;
1077}
1078
1079- (void)close
1080{
1081 self.currentURL = nil;
1082
1083 __weak UIViewController* weakSelf = self;
1084
1085 // Run later to avoid the "took a long time" log message.
1086 dispatch_async(dispatch_get_main_queue(), ^{
1087 isExiting = TRUE;
1088 lastReducedStatusBarHeight = 0.0;
1089 if ([weakSelf respondsToSelector:@selector(presentingViewController)]) {
1090 [[weakSelf presentingViewController] dismissViewControllerAnimated:YES completion:nil];
1091 } else {
1092 [[weakSelf parentViewController] dismissViewControllerAnimated:YES completion:nil];
1093 }
1094 });
1095}
1096
1097- (void)navigateTo:(NSURL*)url
1098{
1099 if ([url.scheme isEqualToString:@"file"]) {
1100 [self.webView loadFileURL:url allowingReadAccessToURL:url];
1101 } else {
1102 NSURLRequest* request = [NSURLRequest requestWithURL:url];
1103 [self.webView loadRequest:request];
1104 }
1105}
1106
1107- (void)goBack:(id)sender
1108{
1109 [self.webView goBack];
1110}
1111
1112- (void)goForward:(id)sender
1113{
1114 [self.webView goForward];
1115}
1116
1117- (void)viewWillAppear:(BOOL)animated
1118{
1119 [self rePositionViews];
1120
1121 [super viewWillAppear:animated];
1122}
1123
1124//
1125// On iOS 7 the status bar is part of the view's dimensions, therefore it's height has to be taken into account.
1126// The height of it could be hardcoded as 20 pixels, but that would assume that the upcoming releases of iOS won't
1127// change that value.
1128//
1129- (float) getStatusBarOffset {
1130 return (float) IsAtLeastiOSVersion(@"7.0") ? [[UIApplication sharedApplication] statusBarFrame].size.height : 0.0;
1131}
1132
1133- (void) rePositionViews {
1134 CGRect viewBounds = [self.webView bounds];
1135 CGFloat statusBarHeight = [self getStatusBarOffset];
1136
1137 // orientation portrait or portraitUpsideDown: status bar is on the top and web view is to be aligned to the bottom of the status bar
1138 // orientation landscapeLeft or landscapeRight: status bar height is 0 in but lets account for it in case things ever change in the future
1139 viewBounds.origin.y = statusBarHeight;
1140
1141 // account for web view height portion that may have been reduced by a previous call to this method
1142 viewBounds.size.height = viewBounds.size.height - statusBarHeight + lastReducedStatusBarHeight;
1143 lastReducedStatusBarHeight = statusBarHeight;
1144
1145 if ((_browserOptions.toolbar) && ([_browserOptions.toolbarposition isEqualToString:kInAppBrowserToolbarBarPositionTop])) {
1146 // if we have to display the toolbar on top of the web view, we need to account for its height
1147 viewBounds.origin.y += TOOLBAR_HEIGHT;
1148 self.toolbar.frame = CGRectMake(self.toolbar.frame.origin.x, statusBarHeight, self.toolbar.frame.size.width, self.toolbar.frame.size.height);
1149 }
1150
1151 self.webView.frame = viewBounds;
1152}
1153
1154// Helper function to convert hex color string to UIColor
1155// Assumes input like "#00FF00" (#RRGGBB).
1156// Taken from https://stackoverflow.com/questions/1560081/how-can-i-create-a-uicolor-from-a-hex-string
1157- (UIColor *)colorFromHexString:(NSString *)hexString {
1158 unsigned rgbValue = 0;
1159 NSScanner *scanner = [NSScanner scannerWithString:hexString];
1160 [scanner setScanLocation:1]; // bypass '#' character
1161 [scanner scanHexInt:&rgbValue];
1162 return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16)/255.0 green:((rgbValue & 0xFF00) >> 8)/255.0 blue:(rgbValue & 0xFF)/255.0 alpha:1.0];
1163}
1164
1165#pragma mark WKNavigationDelegate
1166
1167- (void)webView:(WKWebView *)theWebView didStartProvisionalNavigation:(WKNavigation *)navigation{
1168
1169 // loading url, start spinner, update back/forward
1170
1171 self.addressLabel.text = NSLocalizedString(@"Loading...", nil);
1172 self.backButton.enabled = theWebView.canGoBack;
1173 self.forwardButton.enabled = theWebView.canGoForward;
1174
1175 NSLog(_browserOptions.hidespinner ? @"Yes" : @"No");
1176 if(!_browserOptions.hidespinner) {
1177 [self.spinner startAnimating];
1178 }
1179
1180 return [self.navigationDelegate didStartProvisionalNavigation:theWebView];
1181}
1182
1183- (void)webView:(WKWebView *)theWebView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
1184{
1185 NSURL *url = navigationAction.request.URL;
1186 NSURL *mainDocumentURL = navigationAction.request.mainDocumentURL;
1187
1188 BOOL isTopLevelNavigation = [url isEqual:mainDocumentURL];
1189
1190 if (isTopLevelNavigation) {
1191 self.currentURL = url;
1192 }
1193
1194 [self.navigationDelegate webView:theWebView decidePolicyForNavigationAction:navigationAction decisionHandler:decisionHandler];
1195}
1196
1197- (void)webView:(WKWebView *)theWebView didFinishNavigation:(WKNavigation *)navigation
1198{
1199 // update url, stop spinner, update back/forward
1200
1201 self.addressLabel.text = [self.currentURL absoluteString];
1202 self.backButton.enabled = theWebView.canGoBack;
1203 self.forwardButton.enabled = theWebView.canGoForward;
1204 theWebView.scrollView.contentInset = UIEdgeInsetsZero;
1205
1206 [self.spinner stopAnimating];
1207
1208 [self.navigationDelegate didFinishNavigation:theWebView];
1209}
1210
1211- (void)webView:(WKWebView*)theWebView failedNavigation:(NSString*) delegateName withError:(nonnull NSError *)error{
1212 // log fail message, stop spinner, update back/forward
1213 NSLog(@"webView:%@ - %ld: %@", delegateName, (long)error.code, [error localizedDescription]);
1214
1215 self.backButton.enabled = theWebView.canGoBack;
1216 self.forwardButton.enabled = theWebView.canGoForward;
1217 [self.spinner stopAnimating];
1218
1219 self.addressLabel.text = NSLocalizedString(@"Load Error", nil);
1220
1221 [self.navigationDelegate webView:theWebView didFailNavigation:error];
1222}
1223
1224- (void)webView:(WKWebView*)theWebView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(nonnull NSError *)error
1225{
1226 [self webView:theWebView failedNavigation:@"didFailNavigation" withError:error];
1227}
1228
1229- (void)webView:(WKWebView*)theWebView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(nonnull NSError *)error
1230{
1231 [self webView:theWebView failedNavigation:@"didFailProvisionalNavigation" withError:error];
1232}
1233
1234#pragma mark WKScriptMessageHandler delegate
1235- (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message {
1236 if (![message.name isEqualToString:IAB_BRIDGE_NAME]) {
1237 return;
1238 }
1239 //NSLog(@"Received script message %@", message.body);
1240 [self.navigationDelegate userContentController:userContentController didReceiveScriptMessage:message];
1241}
1242
1243#pragma mark CDVScreenOrientationDelegate
1244
1245- (BOOL)shouldAutorotate
1246{
1247 if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(shouldAutorotate)]) {
1248 return [self.orientationDelegate shouldAutorotate];
1249 }
1250 return YES;
1251}
1252
1253- (UIInterfaceOrientationMask)supportedInterfaceOrientations
1254{
1255 if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(supportedInterfaceOrientations)]) {
1256 return [self.orientationDelegate supportedInterfaceOrientations];
1257 }
1258
1259 return 1 << UIInterfaceOrientationPortrait;
1260}
1261
1262- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
1263{
1264 [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context)
1265 {
1266 [self rePositionViews];
1267 } completion:^(id<UIViewControllerTransitionCoordinatorContext> context)
1268 {
1269
1270 }];
1271
1272 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
1273}
1274
1275#pragma mark UIAdaptivePresentationControllerDelegate
1276
1277- (void)presentationControllerWillDismiss:(UIPresentationController *)presentationController {
1278 isExiting = TRUE;
1279}
1280
1281@end //CDVWKInAppBrowserViewController