CDVInAppBrowser.m 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144
  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. http://www.apache.org/licenses/LICENSE-2.0
  10. Unless required by applicable law or agreed to in writing,
  11. software distributed under the License is distributed on an
  12. "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  13. KIND, either express or implied. See the License for the
  14. specific language governing permissions and limitations
  15. under the License.
  16. */
  17. #import "CDVInAppBrowser.h"
  18. #import <Cordova/CDVPluginResult.h>
  19. #import <Cordova/CDVUserAgentUtil.h>
  20. #define kInAppBrowserTargetSelf @"_self"
  21. #define kInAppBrowserTargetSystem @"_system"
  22. #define kInAppBrowserTargetBlank @"_blank"
  23. #define kInAppBrowserToolbarBarPositionBottom @"bottom"
  24. #define kInAppBrowserToolbarBarPositionTop @"top"
  25. #define TOOLBAR_HEIGHT 44.0
  26. #define STATUSBAR_HEIGHT 20.0
  27. #define LOCATIONBAR_HEIGHT 21.0
  28. #define FOOTER_HEIGHT ((TOOLBAR_HEIGHT) + (LOCATIONBAR_HEIGHT))
  29. #pragma mark CDVInAppBrowser
  30. @interface CDVInAppBrowser () {
  31. NSInteger _previousStatusBarStyle;
  32. }
  33. @end
  34. @implementation CDVInAppBrowser
  35. - (void)pluginInitialize
  36. {
  37. _previousStatusBarStyle = -1;
  38. _callbackIdPattern = nil;
  39. }
  40. - (id)settingForKey:(NSString*)key
  41. {
  42. return [self.commandDelegate.settings objectForKey:[key lowercaseString]];
  43. }
  44. - (void)onReset
  45. {
  46. [self close:nil];
  47. }
  48. - (void)close:(CDVInvokedUrlCommand*)command
  49. {
  50. if (self.inAppBrowserViewController == nil) {
  51. NSLog(@"IAB.close() called but it was already closed.");
  52. return;
  53. }
  54. // Things are cleaned up in browserExit.
  55. [self.inAppBrowserViewController close];
  56. }
  57. - (BOOL) isSystemUrl:(NSURL*)url
  58. {
  59. if ([[url host] isEqualToString:@"itunes.apple.com"]) {
  60. return YES;
  61. }
  62. return NO;
  63. }
  64. - (void)open:(CDVInvokedUrlCommand*)command
  65. {
  66. CDVPluginResult* pluginResult;
  67. NSString* url = [command argumentAtIndex:0];
  68. NSString* target = [command argumentAtIndex:1 withDefault:kInAppBrowserTargetSelf];
  69. NSString* options = [command argumentAtIndex:2 withDefault:@"" andClass:[NSString class]];
  70. self.callbackId = command.callbackId;
  71. if (url != nil) {
  72. #ifdef __CORDOVA_4_0_0
  73. NSURL* baseUrl = [self.webViewEngine URL];
  74. #else
  75. NSURL* baseUrl = [self.webView.request URL];
  76. #endif
  77. NSURL* absoluteUrl = [[NSURL URLWithString:url relativeToURL:baseUrl] absoluteURL];
  78. if ([self isSystemUrl:absoluteUrl]) {
  79. target = kInAppBrowserTargetSystem;
  80. }
  81. if ([target isEqualToString:kInAppBrowserTargetSelf]) {
  82. [self openInCordovaWebView:absoluteUrl withOptions:options];
  83. } else if ([target isEqualToString:kInAppBrowserTargetSystem]) {
  84. [self openInSystem:absoluteUrl];
  85. } else { // _blank or anything else
  86. [self openInInAppBrowser:absoluteUrl withOptions:options];
  87. }
  88. pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
  89. } else {
  90. pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"incorrect number of arguments"];
  91. }
  92. [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
  93. [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
  94. }
  95. - (void)openInInAppBrowser:(NSURL*)url withOptions:(NSString*)options
  96. {
  97. CDVInAppBrowserOptions* browserOptions = [CDVInAppBrowserOptions parseOptions:options];
  98. if (browserOptions.clearcache) {
  99. NSHTTPCookie *cookie;
  100. NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
  101. for (cookie in [storage cookies])
  102. {
  103. if (![cookie.domain isEqual: @".^filecookies^"]) {
  104. [storage deleteCookie:cookie];
  105. }
  106. }
  107. }
  108. if (browserOptions.clearsessioncache) {
  109. NSHTTPCookie *cookie;
  110. NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
  111. for (cookie in [storage cookies])
  112. {
  113. if (![cookie.domain isEqual: @".^filecookies^"] && cookie.isSessionOnly) {
  114. [storage deleteCookie:cookie];
  115. }
  116. }
  117. }
  118. if (self.inAppBrowserViewController == nil) {
  119. NSString* userAgent = [CDVUserAgentUtil originalUserAgent];
  120. NSString* overrideUserAgent = [self settingForKey:@"OverrideUserAgent"];
  121. NSString* appendUserAgent = [self settingForKey:@"AppendUserAgent"];
  122. if(overrideUserAgent){
  123. userAgent = overrideUserAgent;
  124. }
  125. if(appendUserAgent){
  126. userAgent = [userAgent stringByAppendingString: appendUserAgent];
  127. }
  128. self.inAppBrowserViewController = [[CDVInAppBrowserViewController alloc] initWithUserAgent:userAgent prevUserAgent:[self.commandDelegate userAgent] browserOptions: browserOptions];
  129. self.inAppBrowserViewController.navigationDelegate = self;
  130. if ([self.viewController conformsToProtocol:@protocol(CDVScreenOrientationDelegate)]) {
  131. self.inAppBrowserViewController.orientationDelegate = (UIViewController <CDVScreenOrientationDelegate>*)self.viewController;
  132. }
  133. }
  134. [self.inAppBrowserViewController showLocationBar:browserOptions.location];
  135. [self.inAppBrowserViewController showToolBar:browserOptions.toolbar :browserOptions.toolbarposition];
  136. if (browserOptions.closebuttoncaption != nil || browserOptions.closebuttoncolor != nil) {
  137. [self.inAppBrowserViewController setCloseButtonTitle:browserOptions.closebuttoncaption :browserOptions.closebuttoncolor];
  138. }
  139. // Set Presentation Style
  140. UIModalPresentationStyle presentationStyle = UIModalPresentationFullScreen; // default
  141. if (browserOptions.presentationstyle != nil) {
  142. if ([[browserOptions.presentationstyle lowercaseString] isEqualToString:@"pagesheet"]) {
  143. presentationStyle = UIModalPresentationPageSheet;
  144. } else if ([[browserOptions.presentationstyle lowercaseString] isEqualToString:@"formsheet"]) {
  145. presentationStyle = UIModalPresentationFormSheet;
  146. }
  147. }
  148. self.inAppBrowserViewController.modalPresentationStyle = presentationStyle;
  149. // Set Transition Style
  150. UIModalTransitionStyle transitionStyle = UIModalTransitionStyleCoverVertical; // default
  151. if (browserOptions.transitionstyle != nil) {
  152. if ([[browserOptions.transitionstyle lowercaseString] isEqualToString:@"fliphorizontal"]) {
  153. transitionStyle = UIModalTransitionStyleFlipHorizontal;
  154. } else if ([[browserOptions.transitionstyle lowercaseString] isEqualToString:@"crossdissolve"]) {
  155. transitionStyle = UIModalTransitionStyleCrossDissolve;
  156. }
  157. }
  158. self.inAppBrowserViewController.modalTransitionStyle = transitionStyle;
  159. // prevent webView from bouncing
  160. if (browserOptions.disallowoverscroll) {
  161. if ([self.inAppBrowserViewController.webView respondsToSelector:@selector(scrollView)]) {
  162. ((UIScrollView*)[self.inAppBrowserViewController.webView scrollView]).bounces = NO;
  163. } else {
  164. for (id subview in self.inAppBrowserViewController.webView.subviews) {
  165. if ([[subview class] isSubclassOfClass:[UIScrollView class]]) {
  166. ((UIScrollView*)subview).bounces = NO;
  167. }
  168. }
  169. }
  170. }
  171. // UIWebView options
  172. self.inAppBrowserViewController.webView.scalesPageToFit = browserOptions.enableviewportscale;
  173. self.inAppBrowserViewController.webView.mediaPlaybackRequiresUserAction = browserOptions.mediaplaybackrequiresuseraction;
  174. self.inAppBrowserViewController.webView.allowsInlineMediaPlayback = browserOptions.allowinlinemediaplayback;
  175. if (IsAtLeastiOSVersion(@"6.0")) {
  176. self.inAppBrowserViewController.webView.keyboardDisplayRequiresUserAction = browserOptions.keyboarddisplayrequiresuseraction;
  177. self.inAppBrowserViewController.webView.suppressesIncrementalRendering = browserOptions.suppressesincrementalrendering;
  178. }
  179. [self.inAppBrowserViewController navigateTo:url];
  180. if (!browserOptions.hidden) {
  181. [self show:nil];
  182. }
  183. }
  184. - (void)show:(CDVInvokedUrlCommand*)command
  185. {
  186. if (self.inAppBrowserViewController == nil) {
  187. NSLog(@"Tried to show IAB after it was closed.");
  188. return;
  189. }
  190. if (_previousStatusBarStyle != -1) {
  191. NSLog(@"Tried to show IAB while already shown");
  192. return;
  193. }
  194. _previousStatusBarStyle = [UIApplication sharedApplication].statusBarStyle;
  195. __block CDVInAppBrowserNavigationController* nav = [[CDVInAppBrowserNavigationController alloc]
  196. initWithRootViewController:self.inAppBrowserViewController];
  197. nav.orientationDelegate = self.inAppBrowserViewController;
  198. nav.navigationBarHidden = YES;
  199. nav.modalPresentationStyle = self.inAppBrowserViewController.modalPresentationStyle;
  200. __weak CDVInAppBrowser* weakSelf = self;
  201. // Run later to avoid the "took a long time" log message.
  202. dispatch_async(dispatch_get_main_queue(), ^{
  203. if (weakSelf.inAppBrowserViewController != nil) {
  204. CGRect frame = [[UIScreen mainScreen] bounds];
  205. UIWindow *tmpWindow = [[UIWindow alloc] initWithFrame:frame];
  206. UIViewController *tmpController = [[UIViewController alloc] init];
  207. [tmpWindow setRootViewController:tmpController];
  208. [tmpWindow setWindowLevel:UIWindowLevelNormal];
  209. [tmpWindow makeKeyAndVisible];
  210. [tmpController presentViewController:nav animated:YES completion:nil];
  211. }
  212. });
  213. }
  214. - (void)hide:(CDVInvokedUrlCommand*)command
  215. {
  216. if (self.inAppBrowserViewController == nil) {
  217. NSLog(@"Tried to hide IAB after it was closed.");
  218. return;
  219. }
  220. if (_previousStatusBarStyle == -1) {
  221. NSLog(@"Tried to hide IAB while already hidden");
  222. return;
  223. }
  224. _previousStatusBarStyle = [UIApplication sharedApplication].statusBarStyle;
  225. // Run later to avoid the "took a long time" log message.
  226. dispatch_async(dispatch_get_main_queue(), ^{
  227. if (self.inAppBrowserViewController != nil) {
  228. _previousStatusBarStyle = -1;
  229. [self.inAppBrowserViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
  230. }
  231. });
  232. }
  233. - (void)openInCordovaWebView:(NSURL*)url withOptions:(NSString*)options
  234. {
  235. NSURLRequest* request = [NSURLRequest requestWithURL:url];
  236. #ifdef __CORDOVA_4_0_0
  237. // the webview engine itself will filter for this according to <allow-navigation> policy
  238. // in config.xml for cordova-ios-4.0
  239. [self.webViewEngine loadRequest:request];
  240. #else
  241. if ([self.commandDelegate URLIsWhitelisted:url]) {
  242. [self.webView loadRequest:request];
  243. } else { // this assumes the InAppBrowser can be excepted from the white-list
  244. [self openInInAppBrowser:url withOptions:options];
  245. }
  246. #endif
  247. }
  248. - (void)openInSystem:(NSURL*)url
  249. {
  250. [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]];
  251. [[UIApplication sharedApplication] openURL:url];
  252. }
  253. // This is a helper method for the inject{Script|Style}{Code|File} API calls, which
  254. // provides a consistent method for injecting JavaScript code into the document.
  255. //
  256. // If a wrapper string is supplied, then the source string will be JSON-encoded (adding
  257. // quotes) and wrapped using string formatting. (The wrapper string should have a single
  258. // '%@' marker).
  259. //
  260. // If no wrapper is supplied, then the source string is executed directly.
  261. - (void)injectDeferredObject:(NSString*)source withWrapper:(NSString*)jsWrapper
  262. {
  263. // Ensure an iframe bridge is created to communicate with the CDVInAppBrowserViewController
  264. [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:@"(function(d){_cdvIframeBridge=d.getElementById('_cdvIframeBridge');if(!_cdvIframeBridge) {var e = _cdvIframeBridge = d.createElement('iframe');e.id='_cdvIframeBridge'; e.style.display='none';d.body.appendChild(e);}})(document)"];
  265. if (jsWrapper != nil) {
  266. NSData* jsonData = [NSJSONSerialization dataWithJSONObject:@[source] options:0 error:nil];
  267. NSString* sourceArrayString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
  268. if (sourceArrayString) {
  269. NSString* sourceString = [sourceArrayString substringWithRange:NSMakeRange(1, [sourceArrayString length] - 2)];
  270. NSString* jsToInject = [NSString stringWithFormat:jsWrapper, sourceString];
  271. [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:jsToInject];
  272. }
  273. } else {
  274. [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:source];
  275. }
  276. }
  277. - (void)injectScriptCode:(CDVInvokedUrlCommand*)command
  278. {
  279. NSString* jsWrapper = nil;
  280. if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) {
  281. jsWrapper = [NSString stringWithFormat:@"_cdvIframeBridge.src='gap-iab://%@/'+encodeURIComponent(JSON.stringify([eval(%%@)]));", command.callbackId];
  282. }
  283. [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper];
  284. }
  285. - (void)injectScriptFile:(CDVInvokedUrlCommand*)command
  286. {
  287. NSString* jsWrapper;
  288. if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) {
  289. jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('script'); c.src = %%@; c.onload = function() { _cdvIframeBridge.src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId];
  290. } else {
  291. jsWrapper = @"(function(d) { var c = d.createElement('script'); c.src = %@; d.body.appendChild(c); })(document)";
  292. }
  293. [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper];
  294. }
  295. - (void)injectStyleCode:(CDVInvokedUrlCommand*)command
  296. {
  297. NSString* jsWrapper;
  298. if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) {
  299. jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('style'); c.innerHTML = %%@; c.onload = function() { _cdvIframeBridge.src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId];
  300. } else {
  301. jsWrapper = @"(function(d) { var c = d.createElement('style'); c.innerHTML = %@; d.body.appendChild(c); })(document)";
  302. }
  303. [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper];
  304. }
  305. - (void)injectStyleFile:(CDVInvokedUrlCommand*)command
  306. {
  307. NSString* jsWrapper;
  308. if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) {
  309. jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %%@; c.onload = function() { _cdvIframeBridge.src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId];
  310. } else {
  311. jsWrapper = @"(function(d) { var c = d.createElement('link'); c.rel='stylesheet', c.type='text/css'; c.href = %@; d.body.appendChild(c); })(document)";
  312. }
  313. [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper];
  314. }
  315. - (BOOL)isValidCallbackId:(NSString *)callbackId
  316. {
  317. NSError *err = nil;
  318. // Initialize on first use
  319. if (self.callbackIdPattern == nil) {
  320. self.callbackIdPattern = [NSRegularExpression regularExpressionWithPattern:@"^InAppBrowser[0-9]{1,10}$" options:0 error:&err];
  321. if (err != nil) {
  322. // Couldn't initialize Regex; No is safer than Yes.
  323. return NO;
  324. }
  325. }
  326. if ([self.callbackIdPattern firstMatchInString:callbackId options:0 range:NSMakeRange(0, [callbackId length])]) {
  327. return YES;
  328. }
  329. return NO;
  330. }
  331. /**
  332. * The iframe bridge provided for the InAppBrowser is capable of executing any oustanding callback belonging
  333. * to the InAppBrowser plugin. Care has been taken that other callbacks cannot be triggered, and that no
  334. * other code execution is possible.
  335. *
  336. * To trigger the bridge, the iframe (or any other resource) should attempt to load a url of the form:
  337. *
  338. * gap-iab://<callbackId>/<arguments>
  339. *
  340. * where <callbackId> is the string id of the callback to trigger (something like "InAppBrowser0123456789")
  341. *
  342. * If present, the path component of the special gap-iab:// url is expected to be a URL-escaped JSON-encoded
  343. * value to pass to the callback. [NSURL path] should take care of the URL-unescaping, and a JSON_EXCEPTION
  344. * is returned if the JSON is invalid.
  345. */
  346. - (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
  347. {
  348. NSURL* url = request.URL;
  349. BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]];
  350. // See if the url uses the 'gap-iab' protocol. If so, the host should be the id of a callback to execute,
  351. // and the path, if present, should be a JSON-encoded value to pass to the callback.
  352. if ([[url scheme] isEqualToString:@"gap-iab"]) {
  353. NSString* scriptCallbackId = [url host];
  354. CDVPluginResult* pluginResult = nil;
  355. if ([self isValidCallbackId:scriptCallbackId]) {
  356. NSString* scriptResult = [url path];
  357. NSError* __autoreleasing error = nil;
  358. // The message should be a JSON-encoded array of the result of the script which executed.
  359. if ((scriptResult != nil) && ([scriptResult length] > 1)) {
  360. scriptResult = [scriptResult substringFromIndex:1];
  361. NSData* decodedResult = [NSJSONSerialization JSONObjectWithData:[scriptResult dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error];
  362. if ((error == nil) && [decodedResult isKindOfClass:[NSArray class]]) {
  363. pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:(NSArray*)decodedResult];
  364. } else {
  365. pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_JSON_EXCEPTION];
  366. }
  367. } else {
  368. pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:@[]];
  369. }
  370. [self.commandDelegate sendPluginResult:pluginResult callbackId:scriptCallbackId];
  371. return NO;
  372. }
  373. }
  374. //if is an app store link, let the system handle it, otherwise it fails to load it
  375. else if ([[ url scheme] isEqualToString:@"itms-appss"] || [[ url scheme] isEqualToString:@"itms-apps"]) {
  376. [theWebView stopLoading];
  377. [self openInSystem:url];
  378. return NO;
  379. }
  380. else if ((self.callbackId != nil) && isTopLevelNavigation) {
  381. // Send a loadstart event for each top-level navigation (includes redirects).
  382. CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
  383. messageAsDictionary:@{@"type":@"loadstart", @"url":[url absoluteString]}];
  384. [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
  385. [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
  386. }
  387. return YES;
  388. }
  389. - (void)webViewDidStartLoad:(UIWebView*)theWebView
  390. {
  391. }
  392. - (void)webViewDidFinishLoad:(UIWebView*)theWebView
  393. {
  394. if (self.callbackId != nil) {
  395. // TODO: It would be more useful to return the URL the page is actually on (e.g. if it's been redirected).
  396. NSString* url = [self.inAppBrowserViewController.currentURL absoluteString];
  397. CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
  398. messageAsDictionary:@{@"type":@"loadstop", @"url":url}];
  399. [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
  400. [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
  401. }
  402. }
  403. - (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error
  404. {
  405. if (self.callbackId != nil) {
  406. NSString* url = [self.inAppBrowserViewController.currentURL absoluteString];
  407. CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
  408. messageAsDictionary:@{@"type":@"loaderror", @"url":url, @"code": [NSNumber numberWithInteger:error.code], @"message": error.localizedDescription}];
  409. [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
  410. [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
  411. }
  412. }
  413. - (void)browserExit
  414. {
  415. if (self.callbackId != nil) {
  416. CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
  417. messageAsDictionary:@{@"type":@"exit"}];
  418. [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
  419. self.callbackId = nil;
  420. }
  421. // Set navigationDelegate to nil to ensure no callbacks are received from it.
  422. self.inAppBrowserViewController.navigationDelegate = nil;
  423. // Don't recycle the ViewController since it may be consuming a lot of memory.
  424. // Also - this is required for the PDF/User-Agent bug work-around.
  425. self.inAppBrowserViewController = nil;
  426. if (IsAtLeastiOSVersion(@"7.0")) {
  427. if (_previousStatusBarStyle != -1) {
  428. [[UIApplication sharedApplication] setStatusBarStyle:_previousStatusBarStyle];
  429. }
  430. }
  431. _previousStatusBarStyle = -1; // this value was reset before reapplying it. caused statusbar to stay black on ios7
  432. }
  433. @end
  434. #pragma mark CDVInAppBrowserViewController
  435. @implementation CDVInAppBrowserViewController
  436. @synthesize currentURL;
  437. - (id)initWithUserAgent:(NSString*)userAgent prevUserAgent:(NSString*)prevUserAgent browserOptions: (CDVInAppBrowserOptions*) browserOptions
  438. {
  439. self = [super init];
  440. if (self != nil) {
  441. _userAgent = userAgent;
  442. _prevUserAgent = prevUserAgent;
  443. _browserOptions = browserOptions;
  444. #ifdef __CORDOVA_4_0_0
  445. _webViewDelegate = [[CDVUIWebViewDelegate alloc] initWithDelegate:self];
  446. #else
  447. _webViewDelegate = [[CDVWebViewDelegate alloc] initWithDelegate:self];
  448. #endif
  449. [self createViews];
  450. }
  451. return self;
  452. }
  453. // Prevent crashes on closing windows
  454. -(void)dealloc {
  455. self.webView.delegate = nil;
  456. }
  457. - (void)createViews
  458. {
  459. // We create the views in code for primarily for ease of upgrades and not requiring an external .xib to be included
  460. CGRect webViewBounds = self.view.bounds;
  461. BOOL toolbarIsAtBottom = ![_browserOptions.toolbarposition isEqualToString:kInAppBrowserToolbarBarPositionTop];
  462. webViewBounds.size.height -= _browserOptions.location ? FOOTER_HEIGHT : TOOLBAR_HEIGHT;
  463. self.webView = [[UIWebView alloc] initWithFrame:webViewBounds];
  464. self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
  465. [self.view addSubview:self.webView];
  466. [self.view sendSubviewToBack:self.webView];
  467. self.webView.delegate = _webViewDelegate;
  468. self.webView.backgroundColor = [UIColor whiteColor];
  469. self.webView.clearsContextBeforeDrawing = YES;
  470. self.webView.clipsToBounds = YES;
  471. self.webView.contentMode = UIViewContentModeScaleToFill;
  472. self.webView.multipleTouchEnabled = YES;
  473. self.webView.opaque = YES;
  474. self.webView.scalesPageToFit = NO;
  475. self.webView.userInteractionEnabled = YES;
  476. self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
  477. self.spinner.alpha = 1.000;
  478. self.spinner.autoresizesSubviews = YES;
  479. self.spinner.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin);
  480. self.spinner.clearsContextBeforeDrawing = NO;
  481. self.spinner.clipsToBounds = NO;
  482. self.spinner.contentMode = UIViewContentModeScaleToFill;
  483. self.spinner.frame = CGRectMake(CGRectGetMidX(self.webView.frame), CGRectGetMidY(self.webView.frame), 20.0, 20.0);
  484. self.spinner.hidden = NO;
  485. self.spinner.hidesWhenStopped = YES;
  486. self.spinner.multipleTouchEnabled = NO;
  487. self.spinner.opaque = NO;
  488. self.spinner.userInteractionEnabled = NO;
  489. [self.spinner stopAnimating];
  490. self.closeButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(close)];
  491. self.closeButton.enabled = YES;
  492. UIBarButtonItem* flexibleSpaceButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
  493. UIBarButtonItem* fixedSpaceButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
  494. fixedSpaceButton.width = 20;
  495. float toolbarY = toolbarIsAtBottom ? self.view.bounds.size.height - TOOLBAR_HEIGHT : 0.0;
  496. CGRect toolbarFrame = CGRectMake(0.0, toolbarY, self.view.bounds.size.width, TOOLBAR_HEIGHT);
  497. self.toolbar = [[UIToolbar alloc] initWithFrame:toolbarFrame];
  498. self.toolbar.alpha = 1.000;
  499. self.toolbar.autoresizesSubviews = YES;
  500. self.toolbar.autoresizingMask = toolbarIsAtBottom ? (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin) : UIViewAutoresizingFlexibleWidth;
  501. self.toolbar.barStyle = UIBarStyleBlackOpaque;
  502. self.toolbar.clearsContextBeforeDrawing = NO;
  503. self.toolbar.clipsToBounds = NO;
  504. self.toolbar.contentMode = UIViewContentModeScaleToFill;
  505. self.toolbar.hidden = NO;
  506. self.toolbar.multipleTouchEnabled = NO;
  507. self.toolbar.opaque = NO;
  508. self.toolbar.userInteractionEnabled = YES;
  509. if (_browserOptions.toolbarcolor != nil) { // Set toolbar color if user sets it in options
  510. self.toolbar.barTintColor = [self colorFromHexString:_browserOptions.toolbarcolor];
  511. }
  512. if (!_browserOptions.toolbartranslucent) { // Set toolbar translucent to no if user sets it in options
  513. self.toolbar.translucent = NO;
  514. }
  515. CGFloat labelInset = 5.0;
  516. float locationBarY = toolbarIsAtBottom ? self.view.bounds.size.height - FOOTER_HEIGHT : self.view.bounds.size.height - LOCATIONBAR_HEIGHT;
  517. self.addressLabel = [[UILabel alloc] initWithFrame:CGRectMake(labelInset, locationBarY, self.view.bounds.size.width - labelInset, LOCATIONBAR_HEIGHT)];
  518. self.addressLabel.adjustsFontSizeToFitWidth = NO;
  519. self.addressLabel.alpha = 1.000;
  520. self.addressLabel.autoresizesSubviews = YES;
  521. self.addressLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin;
  522. self.addressLabel.backgroundColor = [UIColor clearColor];
  523. self.addressLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters;
  524. self.addressLabel.clearsContextBeforeDrawing = YES;
  525. self.addressLabel.clipsToBounds = YES;
  526. self.addressLabel.contentMode = UIViewContentModeScaleToFill;
  527. self.addressLabel.enabled = YES;
  528. self.addressLabel.hidden = NO;
  529. self.addressLabel.lineBreakMode = NSLineBreakByTruncatingTail;
  530. if ([self.addressLabel respondsToSelector:NSSelectorFromString(@"setMinimumScaleFactor:")]) {
  531. [self.addressLabel setValue:@(10.0/[UIFont labelFontSize]) forKey:@"minimumScaleFactor"];
  532. } else if ([self.addressLabel respondsToSelector:NSSelectorFromString(@"setMinimumFontSize:")]) {
  533. [self.addressLabel setValue:@(10.0) forKey:@"minimumFontSize"];
  534. }
  535. self.addressLabel.multipleTouchEnabled = NO;
  536. self.addressLabel.numberOfLines = 1;
  537. self.addressLabel.opaque = NO;
  538. self.addressLabel.shadowOffset = CGSizeMake(0.0, -1.0);
  539. self.addressLabel.text = NSLocalizedString(@"Loading...", nil);
  540. self.addressLabel.textAlignment = NSTextAlignmentLeft;
  541. self.addressLabel.textColor = [UIColor colorWithWhite:1.000 alpha:1.000];
  542. self.addressLabel.userInteractionEnabled = NO;
  543. NSString* frontArrowString = NSLocalizedString(@"►", nil); // create arrow from Unicode char
  544. self.forwardButton = [[UIBarButtonItem alloc] initWithTitle:frontArrowString style:UIBarButtonItemStylePlain target:self action:@selector(goForward:)];
  545. self.forwardButton.enabled = YES;
  546. self.forwardButton.imageInsets = UIEdgeInsetsZero;
  547. if (_browserOptions.navigationbuttoncolor != nil) { // Set button color if user sets it in options
  548. self.forwardButton.tintColor = [self colorFromHexString:_browserOptions.navigationbuttoncolor];
  549. }
  550. NSString* backArrowString = NSLocalizedString(@"◄", nil); // create arrow from Unicode char
  551. self.backButton = [[UIBarButtonItem alloc] initWithTitle:backArrowString style:UIBarButtonItemStylePlain target:self action:@selector(goBack:)];
  552. self.backButton.enabled = YES;
  553. self.backButton.imageInsets = UIEdgeInsetsZero;
  554. if (_browserOptions.navigationbuttoncolor != nil) { // Set button color if user sets it in options
  555. self.backButton.tintColor = [self colorFromHexString:_browserOptions.navigationbuttoncolor];
  556. }
  557. // Filter out Navigation Buttons if user requests so
  558. if (_browserOptions.hidenavigationbuttons) {
  559. [self.toolbar setItems:@[self.closeButton, flexibleSpaceButton]];
  560. } else {
  561. [self.toolbar setItems:@[self.closeButton, flexibleSpaceButton, self.backButton, fixedSpaceButton, self.forwardButton]];
  562. }
  563. self.view.backgroundColor = [UIColor grayColor];
  564. [self.view addSubview:self.toolbar];
  565. [self.view addSubview:self.addressLabel];
  566. [self.view addSubview:self.spinner];
  567. }
  568. - (void) setWebViewFrame : (CGRect) frame {
  569. NSLog(@"Setting the WebView's frame to %@", NSStringFromCGRect(frame));
  570. [self.webView setFrame:frame];
  571. }
  572. - (void)setCloseButtonTitle:(NSString*)title : (NSString*) colorString
  573. {
  574. // the advantage of using UIBarButtonSystemItemDone is the system will localize it for you automatically
  575. // 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)
  576. self.closeButton = nil;
  577. // Initialize with title if title is set, otherwise the title will be 'Done' localized
  578. self.closeButton = title != nil ? [[UIBarButtonItem alloc] initWithTitle:title style:UIBarButtonItemStyleBordered target:self action:@selector(close)] : [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(close)];
  579. self.closeButton.enabled = YES;
  580. // If color on closebutton is requested then initialize with that that color, otherwise use initialize with default
  581. 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];
  582. NSMutableArray* items = [self.toolbar.items mutableCopy];
  583. [items replaceObjectAtIndex:0 withObject:self.closeButton];
  584. [self.toolbar setItems:items];
  585. }
  586. - (void)showLocationBar:(BOOL)show
  587. {
  588. CGRect locationbarFrame = self.addressLabel.frame;
  589. BOOL toolbarVisible = !self.toolbar.hidden;
  590. // prevent double show/hide
  591. if (show == !(self.addressLabel.hidden)) {
  592. return;
  593. }
  594. if (show) {
  595. self.addressLabel.hidden = NO;
  596. if (toolbarVisible) {
  597. // toolBar at the bottom, leave as is
  598. // put locationBar on top of the toolBar
  599. CGRect webViewBounds = self.view.bounds;
  600. webViewBounds.size.height -= FOOTER_HEIGHT;
  601. [self setWebViewFrame:webViewBounds];
  602. locationbarFrame.origin.y = webViewBounds.size.height;
  603. self.addressLabel.frame = locationbarFrame;
  604. } else {
  605. // no toolBar, so put locationBar at the bottom
  606. CGRect webViewBounds = self.view.bounds;
  607. webViewBounds.size.height -= LOCATIONBAR_HEIGHT;
  608. [self setWebViewFrame:webViewBounds];
  609. locationbarFrame.origin.y = webViewBounds.size.height;
  610. self.addressLabel.frame = locationbarFrame;
  611. }
  612. } else {
  613. self.addressLabel.hidden = YES;
  614. if (toolbarVisible) {
  615. // locationBar is on top of toolBar, hide locationBar
  616. // webView take up whole height less toolBar height
  617. CGRect webViewBounds = self.view.bounds;
  618. webViewBounds.size.height -= TOOLBAR_HEIGHT;
  619. [self setWebViewFrame:webViewBounds];
  620. } else {
  621. // no toolBar, expand webView to screen dimensions
  622. [self setWebViewFrame:self.view.bounds];
  623. }
  624. }
  625. }
  626. - (void)showToolBar:(BOOL)show : (NSString *) toolbarPosition
  627. {
  628. CGRect toolbarFrame = self.toolbar.frame;
  629. CGRect locationbarFrame = self.addressLabel.frame;
  630. BOOL locationbarVisible = !self.addressLabel.hidden;
  631. // prevent double show/hide
  632. if (show == !(self.toolbar.hidden)) {
  633. return;
  634. }
  635. if (show) {
  636. self.toolbar.hidden = NO;
  637. CGRect webViewBounds = self.view.bounds;
  638. if (locationbarVisible) {
  639. // locationBar at the bottom, move locationBar up
  640. // put toolBar at the bottom
  641. webViewBounds.size.height -= FOOTER_HEIGHT;
  642. locationbarFrame.origin.y = webViewBounds.size.height;
  643. self.addressLabel.frame = locationbarFrame;
  644. self.toolbar.frame = toolbarFrame;
  645. } else {
  646. // no locationBar, so put toolBar at the bottom
  647. CGRect webViewBounds = self.view.bounds;
  648. webViewBounds.size.height -= TOOLBAR_HEIGHT;
  649. self.toolbar.frame = toolbarFrame;
  650. }
  651. if ([toolbarPosition isEqualToString:kInAppBrowserToolbarBarPositionTop]) {
  652. toolbarFrame.origin.y = 0;
  653. webViewBounds.origin.y += toolbarFrame.size.height;
  654. [self setWebViewFrame:webViewBounds];
  655. } else {
  656. toolbarFrame.origin.y = (webViewBounds.size.height + LOCATIONBAR_HEIGHT);
  657. }
  658. [self setWebViewFrame:webViewBounds];
  659. } else {
  660. self.toolbar.hidden = YES;
  661. if (locationbarVisible) {
  662. // locationBar is on top of toolBar, hide toolBar
  663. // put locationBar at the bottom
  664. // webView take up whole height less locationBar height
  665. CGRect webViewBounds = self.view.bounds;
  666. webViewBounds.size.height -= LOCATIONBAR_HEIGHT;
  667. [self setWebViewFrame:webViewBounds];
  668. // move locationBar down
  669. locationbarFrame.origin.y = webViewBounds.size.height;
  670. self.addressLabel.frame = locationbarFrame;
  671. } else {
  672. // no locationBar, expand webView to screen dimensions
  673. [self setWebViewFrame:self.view.bounds];
  674. }
  675. }
  676. }
  677. - (void)viewDidLoad
  678. {
  679. [super viewDidLoad];
  680. }
  681. - (void)viewDidUnload
  682. {
  683. [self.webView loadHTMLString:nil baseURL:nil];
  684. [CDVUserAgentUtil releaseLock:&_userAgentLockToken];
  685. [super viewDidUnload];
  686. }
  687. - (UIStatusBarStyle)preferredStatusBarStyle
  688. {
  689. return UIStatusBarStyleDefault;
  690. }
  691. - (BOOL)prefersStatusBarHidden {
  692. return NO;
  693. }
  694. - (void)close
  695. {
  696. [CDVUserAgentUtil releaseLock:&_userAgentLockToken];
  697. self.currentURL = nil;
  698. if ((self.navigationDelegate != nil) && [self.navigationDelegate respondsToSelector:@selector(browserExit)]) {
  699. [self.navigationDelegate browserExit];
  700. }
  701. __weak UIViewController* weakSelf = self;
  702. // Run later to avoid the "took a long time" log message.
  703. dispatch_async(dispatch_get_main_queue(), ^{
  704. if ([weakSelf respondsToSelector:@selector(presentingViewController)]) {
  705. [[weakSelf presentingViewController] dismissViewControllerAnimated:YES completion:nil];
  706. } else {
  707. [[weakSelf parentViewController] dismissViewControllerAnimated:YES completion:nil];
  708. }
  709. });
  710. }
  711. - (void)navigateTo:(NSURL*)url
  712. {
  713. NSURLRequest* request = [NSURLRequest requestWithURL:url];
  714. if (_userAgentLockToken != 0) {
  715. [self.webView loadRequest:request];
  716. } else {
  717. __weak CDVInAppBrowserViewController* weakSelf = self;
  718. [CDVUserAgentUtil acquireLock:^(NSInteger lockToken) {
  719. _userAgentLockToken = lockToken;
  720. [CDVUserAgentUtil setUserAgent:_userAgent lockToken:lockToken];
  721. [weakSelf.webView loadRequest:request];
  722. }];
  723. }
  724. }
  725. - (void)goBack:(id)sender
  726. {
  727. [self.webView goBack];
  728. }
  729. - (void)goForward:(id)sender
  730. {
  731. [self.webView goForward];
  732. }
  733. - (void)viewWillAppear:(BOOL)animated
  734. {
  735. if (IsAtLeastiOSVersion(@"7.0")) {
  736. [[UIApplication sharedApplication] setStatusBarStyle:[self preferredStatusBarStyle]];
  737. }
  738. [self rePositionViews];
  739. [super viewWillAppear:animated];
  740. }
  741. //
  742. // On iOS 7 the status bar is part of the view's dimensions, therefore it's height has to be taken into account.
  743. // The height of it could be hardcoded as 20 pixels, but that would assume that the upcoming releases of iOS won't
  744. // change that value.
  745. //
  746. - (float) getStatusBarOffset {
  747. CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
  748. float statusBarOffset = IsAtLeastiOSVersion(@"7.0") ? MIN(statusBarFrame.size.width, statusBarFrame.size.height) : 0.0;
  749. return statusBarOffset;
  750. }
  751. - (void) rePositionViews {
  752. if ([_browserOptions.toolbarposition isEqualToString:kInAppBrowserToolbarBarPositionTop]) {
  753. [self.webView setFrame:CGRectMake(self.webView.frame.origin.x, TOOLBAR_HEIGHT, self.webView.frame.size.width, self.webView.frame.size.height)];
  754. [self.toolbar setFrame:CGRectMake(self.toolbar.frame.origin.x, [self getStatusBarOffset], self.toolbar.frame.size.width, self.toolbar.frame.size.height)];
  755. }
  756. }
  757. // Helper function to convert hex color string to UIColor
  758. // Assumes input like "#00FF00" (#RRGGBB).
  759. // Taken from https://stackoverflow.com/questions/1560081/how-can-i-create-a-uicolor-from-a-hex-string
  760. - (UIColor *)colorFromHexString:(NSString *)hexString {
  761. unsigned rgbValue = 0;
  762. NSScanner *scanner = [NSScanner scannerWithString:hexString];
  763. [scanner setScanLocation:1]; // bypass '#' character
  764. [scanner scanHexInt:&rgbValue];
  765. return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16)/255.0 green:((rgbValue & 0xFF00) >> 8)/255.0 blue:(rgbValue & 0xFF)/255.0 alpha:1.0];
  766. }
  767. #pragma mark UIWebViewDelegate
  768. - (void)webViewDidStartLoad:(UIWebView*)theWebView
  769. {
  770. // loading url, start spinner, update back/forward
  771. self.addressLabel.text = NSLocalizedString(@"Loading...", nil);
  772. self.backButton.enabled = theWebView.canGoBack;
  773. self.forwardButton.enabled = theWebView.canGoForward;
  774. NSLog(_browserOptions.hidespinner ? @"Yes" : @"No");
  775. if(!_browserOptions.hidespinner) {
  776. [self.spinner startAnimating];
  777. }
  778. return [self.navigationDelegate webViewDidStartLoad:theWebView];
  779. }
  780. - (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
  781. {
  782. BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]];
  783. if (isTopLevelNavigation) {
  784. self.currentURL = request.URL;
  785. }
  786. return [self.navigationDelegate webView:theWebView shouldStartLoadWithRequest:request navigationType:navigationType];
  787. }
  788. - (void)webViewDidFinishLoad:(UIWebView*)theWebView
  789. {
  790. // update url, stop spinner, update back/forward
  791. self.addressLabel.text = [self.currentURL absoluteString];
  792. self.backButton.enabled = theWebView.canGoBack;
  793. self.forwardButton.enabled = theWebView.canGoForward;
  794. [self.spinner stopAnimating];
  795. // Work around a bug where the first time a PDF is opened, all UIWebViews
  796. // reload their User-Agent from NSUserDefaults.
  797. // This work-around makes the following assumptions:
  798. // 1. The app has only a single Cordova Webview. If not, then the app should
  799. // take it upon themselves to load a PDF in the background as a part of
  800. // their start-up flow.
  801. // 2. That the PDF does not require any additional network requests. We change
  802. // the user-agent here back to that of the CDVViewController, so requests
  803. // from it must pass through its white-list. This *does* break PDFs that
  804. // contain links to other remote PDF/websites.
  805. // More info at https://issues.apache.org/jira/browse/CB-2225
  806. BOOL isPDF = [@"true" isEqualToString :[theWebView stringByEvaluatingJavaScriptFromString:@"document.body==null"]];
  807. if (isPDF) {
  808. [CDVUserAgentUtil setUserAgent:_prevUserAgent lockToken:_userAgentLockToken];
  809. }
  810. [self.navigationDelegate webViewDidFinishLoad:theWebView];
  811. }
  812. - (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error
  813. {
  814. // log fail message, stop spinner, update back/forward
  815. NSLog(@"webView:didFailLoadWithError - %ld: %@", (long)error.code, [error localizedDescription]);
  816. self.backButton.enabled = theWebView.canGoBack;
  817. self.forwardButton.enabled = theWebView.canGoForward;
  818. [self.spinner stopAnimating];
  819. self.addressLabel.text = NSLocalizedString(@"Load Error", nil);
  820. [self.navigationDelegate webView:theWebView didFailLoadWithError:error];
  821. }
  822. #pragma mark CDVScreenOrientationDelegate
  823. - (BOOL)shouldAutorotate
  824. {
  825. if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(shouldAutorotate)]) {
  826. return [self.orientationDelegate shouldAutorotate];
  827. }
  828. return YES;
  829. }
  830. - (NSUInteger)supportedInterfaceOrientations
  831. {
  832. if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(supportedInterfaceOrientations)]) {
  833. return [self.orientationDelegate supportedInterfaceOrientations];
  834. }
  835. return 1 << UIInterfaceOrientationPortrait;
  836. }
  837. - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
  838. {
  839. if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(shouldAutorotateToInterfaceOrientation:)]) {
  840. return [self.orientationDelegate shouldAutorotateToInterfaceOrientation:interfaceOrientation];
  841. }
  842. return YES;
  843. }
  844. @end
  845. @implementation CDVInAppBrowserOptions
  846. - (id)init
  847. {
  848. if (self = [super init]) {
  849. // default values
  850. self.location = YES;
  851. self.toolbar = YES;
  852. self.closebuttoncaption = nil;
  853. self.toolbarposition = kInAppBrowserToolbarBarPositionBottom;
  854. self.clearcache = NO;
  855. self.clearsessioncache = NO;
  856. self.hidespinner = NO;
  857. self.enableviewportscale = NO;
  858. self.mediaplaybackrequiresuseraction = NO;
  859. self.allowinlinemediaplayback = NO;
  860. self.keyboarddisplayrequiresuseraction = YES;
  861. self.suppressesincrementalrendering = NO;
  862. self.hidden = NO;
  863. self.disallowoverscroll = NO;
  864. self.hidenavigationbuttons = NO;
  865. self.closebuttoncolor = nil;
  866. self.toolbarcolor = nil;
  867. self.toolbartranslucent = YES;
  868. }
  869. return self;
  870. }
  871. + (CDVInAppBrowserOptions*)parseOptions:(NSString*)options
  872. {
  873. CDVInAppBrowserOptions* obj = [[CDVInAppBrowserOptions alloc] init];
  874. // NOTE: this parsing does not handle quotes within values
  875. NSArray* pairs = [options componentsSeparatedByString:@","];
  876. // parse keys and values, set the properties
  877. for (NSString* pair in pairs) {
  878. NSArray* keyvalue = [pair componentsSeparatedByString:@"="];
  879. if ([keyvalue count] == 2) {
  880. NSString* key = [[keyvalue objectAtIndex:0] lowercaseString];
  881. NSString* value = [keyvalue objectAtIndex:1];
  882. NSString* value_lc = [value lowercaseString];
  883. BOOL isBoolean = [value_lc isEqualToString:@"yes"] || [value_lc isEqualToString:@"no"];
  884. NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
  885. [numberFormatter setAllowsFloats:YES];
  886. BOOL isNumber = [numberFormatter numberFromString:value_lc] != nil;
  887. // set the property according to the key name
  888. if ([obj respondsToSelector:NSSelectorFromString(key)]) {
  889. if (isNumber) {
  890. [obj setValue:[numberFormatter numberFromString:value_lc] forKey:key];
  891. } else if (isBoolean) {
  892. [obj setValue:[NSNumber numberWithBool:[value_lc isEqualToString:@"yes"]] forKey:key];
  893. } else {
  894. [obj setValue:value forKey:key];
  895. }
  896. }
  897. }
  898. }
  899. return obj;
  900. }
  901. @end
  902. @implementation CDVInAppBrowserNavigationController : UINavigationController
  903. - (void) dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion {
  904. if ( self.presentedViewController) {
  905. [super dismissViewControllerAnimated:flag completion:completion];
  906. }
  907. }
  908. - (void) viewDidLoad {
  909. CGRect statusBarFrame = [self invertFrameIfNeeded:[UIApplication sharedApplication].statusBarFrame];
  910. statusBarFrame.size.height = STATUSBAR_HEIGHT;
  911. // simplified from: http://stackoverflow.com/a/25669695/219684
  912. UIToolbar* bgToolbar = [[UIToolbar alloc] initWithFrame:statusBarFrame];
  913. bgToolbar.barStyle = UIBarStyleDefault;
  914. [bgToolbar setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
  915. [self.view addSubview:bgToolbar];
  916. [super viewDidLoad];
  917. }
  918. - (CGRect) invertFrameIfNeeded:(CGRect)rect {
  919. // We need to invert since on iOS 7 frames are always in Portrait context
  920. if (!IsAtLeastiOSVersion(@"8.0")) {
  921. if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) {
  922. CGFloat temp = rect.size.width;
  923. rect.size.width = rect.size.height;
  924. rect.size.height = temp;
  925. }
  926. rect.origin = CGPointZero;
  927. }
  928. return rect;
  929. }
  930. #pragma mark CDVScreenOrientationDelegate
  931. - (BOOL)shouldAutorotate
  932. {
  933. if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(shouldAutorotate)]) {
  934. return [self.orientationDelegate shouldAutorotate];
  935. }
  936. return YES;
  937. }
  938. - (NSUInteger)supportedInterfaceOrientations
  939. {
  940. if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(supportedInterfaceOrientations)]) {
  941. return [self.orientationDelegate supportedInterfaceOrientations];
  942. }
  943. return 1 << UIInterfaceOrientationPortrait;
  944. }
  945. - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
  946. {
  947. if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(shouldAutorotateToInterfaceOrientation:)]) {
  948. return [self.orientationDelegate shouldAutorotateToInterfaceOrientation:interfaceOrientation];
  949. }
  950. return YES;
  951. }
  952. @end