summaryrefslogtreecommitdiff
path: root/StoneIsland/platforms/ios/CordovaLib/Classes/CDVViewController.m
diff options
context:
space:
mode:
Diffstat (limited to 'StoneIsland/platforms/ios/CordovaLib/Classes/CDVViewController.m')
-rw-r--r--StoneIsland/platforms/ios/CordovaLib/Classes/CDVViewController.m1049
1 files changed, 1049 insertions, 0 deletions
diff --git a/StoneIsland/platforms/ios/CordovaLib/Classes/CDVViewController.m b/StoneIsland/platforms/ios/CordovaLib/Classes/CDVViewController.m
new file mode 100644
index 00000000..6d81e8d9
--- /dev/null
+++ b/StoneIsland/platforms/ios/CordovaLib/Classes/CDVViewController.m
@@ -0,0 +1,1049 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+#import <objc/message.h>
+#import "CDV.h"
+#import "CDVCommandDelegateImpl.h"
+#import "CDVConfigParser.h"
+#import "CDVUserAgentUtil.h"
+#import "CDVWebViewDelegate.h"
+#import <AVFoundation/AVFoundation.h>
+#import "CDVHandleOpenURL.h"
+
+#define degreesToRadian(x) (M_PI * (x) / 180.0)
+
+@interface CDVViewController () {
+ NSInteger _userAgentLockToken;
+ CDVWebViewDelegate* _webViewDelegate;
+}
+
+@property (nonatomic, readwrite, strong) NSXMLParser* configParser;
+@property (nonatomic, readwrite, strong) NSMutableDictionary* settings;
+@property (nonatomic, readwrite, strong) CDVWhitelist* whitelist;
+@property (nonatomic, readwrite, strong) NSMutableDictionary* pluginObjects;
+@property (nonatomic, readwrite, strong) NSArray* startupPluginNames;
+@property (nonatomic, readwrite, strong) NSDictionary* pluginsMap;
+@property (nonatomic, readwrite, strong) NSArray* supportedOrientations;
+@property (nonatomic, readwrite, assign) BOOL loadFromString;
+
+@property (readwrite, assign) BOOL initialized;
+
+@property (atomic, strong) NSURL* openURL;
+
+@end
+
+@implementation CDVViewController
+
+@synthesize webView, supportedOrientations;
+@synthesize pluginObjects, pluginsMap, whitelist, startupPluginNames;
+@synthesize configParser, settings, loadFromString;
+@synthesize wwwFolderName, startPage, initialized, openURL, baseUserAgent;
+@synthesize commandDelegate = _commandDelegate;
+@synthesize commandQueue = _commandQueue;
+
+- (void)__init
+{
+ if ((self != nil) && !self.initialized) {
+ _commandQueue = [[CDVCommandQueue alloc] initWithViewController:self];
+ _commandDelegate = [[CDVCommandDelegateImpl alloc] initWithViewController:self];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillTerminate:)
+ name:UIApplicationWillTerminateNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillResignActive:)
+ name:UIApplicationWillResignActiveNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppDidBecomeActive:)
+ name:UIApplicationDidBecomeActiveNotification object:nil];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillEnterForeground:)
+ name:UIApplicationWillEnterForegroundNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppDidEnterBackground:)
+ name:UIApplicationDidEnterBackgroundNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onPageDidLoad:)
+ name:CDVPageDidLoadNotification object:nil];
+
+ // read from UISupportedInterfaceOrientations (or UISupportedInterfaceOrientations~iPad, if its iPad) from -Info.plist
+ self.supportedOrientations = [self parseInterfaceOrientations:
+ [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UISupportedInterfaceOrientations"]];
+
+ [self printVersion];
+ [self printMultitaskingInfo];
+ [self printPlatformVersionWarning];
+ self.initialized = YES;
+
+ // load config.xml settings
+ [self loadSettings];
+ }
+}
+
+- (id)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil
+{
+ self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
+ [self __init];
+ return self;
+}
+
+- (id)initWithCoder:(NSCoder*)aDecoder
+{
+ self = [super initWithCoder:aDecoder];
+ [self __init];
+ return self;
+}
+
+- (id)init
+{
+ self = [super init];
+ [self __init];
+ return self;
+}
+
+- (void)viewWillAppear:(BOOL)animated
+{
+ [super viewWillAppear:animated];
+}
+
+- (void)viewWillDisappear:(BOOL)animated
+{
+ [super viewWillDisappear:animated];
+}
+
+- (void)printVersion
+{
+ NSLog(@"Apache Cordova native platform version %@ is starting.", CDV_VERSION);
+}
+
+- (void)printPlatformVersionWarning
+{
+ if (!IsAtLeastiOSVersion(@"6.0")) {
+ NSLog(@"CRITICAL: For Cordova 3.5.0 and above, you will need to upgrade to at least iOS 6.0 or greater. Your current version of iOS is %@.",
+ [[UIDevice currentDevice] systemVersion]
+ );
+ }
+}
+
+- (void)printMultitaskingInfo
+{
+ UIDevice* device = [UIDevice currentDevice];
+ BOOL backgroundSupported = NO;
+
+ if ([device respondsToSelector:@selector(isMultitaskingSupported)]) {
+ backgroundSupported = device.multitaskingSupported;
+ }
+
+ NSNumber* exitsOnSuspend = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIApplicationExitsOnSuspend"];
+ if (exitsOnSuspend == nil) { // if it's missing, it should be NO (i.e. multi-tasking on by default)
+ exitsOnSuspend = [NSNumber numberWithBool:NO];
+ }
+
+ NSLog(@"Multi-tasking -> Device: %@, App: %@", (backgroundSupported ? @"YES" : @"NO"), (![exitsOnSuspend intValue]) ? @"YES" : @"NO");
+}
+
+- (BOOL)URLisAllowed:(NSURL*)url
+{
+ if (self.whitelist == nil) {
+ return YES;
+ }
+
+ return [self.whitelist URLIsAllowed:url];
+}
+
+- (void)loadSettings
+{
+ CDVConfigParser* delegate = [[CDVConfigParser alloc] init];
+
+ // read from config.xml in the app bundle
+ NSString* path = [[NSBundle mainBundle] pathForResource:@"config" ofType:@"xml"];
+
+ if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
+ NSAssert(NO, @"ERROR: config.xml does not exist. Please run cordova-ios/bin/cordova_plist_to_config_xml path/to/project.");
+ return;
+ }
+
+ NSURL* url = [NSURL fileURLWithPath:path];
+
+ configParser = [[NSXMLParser alloc] initWithContentsOfURL:url];
+ if (configParser == nil) {
+ NSLog(@"Failed to initialize XML parser.");
+ return;
+ }
+ [configParser setDelegate:((id < NSXMLParserDelegate >)delegate)];
+ [configParser parse];
+
+ // Get the plugin dictionary, whitelist and settings from the delegate.
+ self.pluginsMap = delegate.pluginsDict;
+ self.startupPluginNames = delegate.startupPluginNames;
+ self.whitelist = [[CDVWhitelist alloc] initWithArray:delegate.whitelistHosts];
+ self.settings = delegate.settings;
+
+ // And the start folder/page.
+ self.wwwFolderName = @"www";
+ self.startPage = delegate.startPage;
+ if (self.startPage == nil) {
+ self.startPage = @"index.html";
+ }
+
+ // Initialize the plugin objects dict.
+ self.pluginObjects = [[NSMutableDictionary alloc] initWithCapacity:20];
+}
+
+- (NSURL*)appUrl
+{
+ NSURL* appURL = nil;
+
+ if ([self.startPage rangeOfString:@"://"].location != NSNotFound) {
+ appURL = [NSURL URLWithString:self.startPage];
+ } else if ([self.wwwFolderName rangeOfString:@"://"].location != NSNotFound) {
+ appURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@/%@", self.wwwFolderName, self.startPage]];
+ } else {
+ // CB-3005 strip parameters from start page to check if page exists in resources
+ NSURL* startURL = [NSURL URLWithString:self.startPage];
+ NSString* startFilePath = [self.commandDelegate pathForResource:[startURL path]];
+
+ if (startFilePath == nil) {
+ self.loadFromString = YES;
+ appURL = nil;
+ } else {
+ appURL = [NSURL fileURLWithPath:startFilePath];
+ // CB-3005 Add on the query params or fragment.
+ NSString* startPageNoParentDirs = self.startPage;
+ NSRange r = [startPageNoParentDirs rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"?#"] options:0];
+ if (r.location != NSNotFound) {
+ NSString* queryAndOrFragment = [self.startPage substringFromIndex:r.location];
+ appURL = [NSURL URLWithString:queryAndOrFragment relativeToURL:appURL];
+ }
+ }
+ }
+
+ return appURL;
+}
+
+- (NSURL*)errorUrl
+{
+ NSURL* errorURL = nil;
+
+ id setting = [self settingForKey:@"ErrorUrl"];
+
+ if (setting) {
+ NSString* errorUrlString = (NSString*)setting;
+ if ([errorUrlString rangeOfString:@"://"].location != NSNotFound) {
+ errorURL = [NSURL URLWithString:errorUrlString];
+ } else {
+ NSURL* url = [NSURL URLWithString:(NSString*)setting];
+ NSString* errorFilePath = [self.commandDelegate pathForResource:[url path]];
+ if (errorFilePath) {
+ errorURL = [NSURL fileURLWithPath:errorFilePath];
+ }
+ }
+ }
+
+ return errorURL;
+}
+
+// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ // // Fix the iOS 5.1 SECURITY_ERR bug (CB-347), this must be before the webView is instantiated ////
+
+ NSString* backupWebStorageType = @"cloud"; // default value
+
+ id backupWebStorage = [self settingForKey:@"BackupWebStorage"];
+ if ([backupWebStorage isKindOfClass:[NSString class]]) {
+ backupWebStorageType = backupWebStorage;
+ }
+ [self setSetting:backupWebStorageType forKey:@"BackupWebStorage"];
+
+ if (IsAtLeastiOSVersion(@"5.1")) {
+ [CDVLocalStorage __fixupDatabaseLocationsWithBackupType:backupWebStorageType];
+ }
+
+ // // Instantiate the WebView ///////////////
+
+ if (!self.webView) {
+ [self createGapView];
+ }
+
+ // Configure WebView
+ _webViewDelegate = [[CDVWebViewDelegate alloc] initWithDelegate:self];
+ self.webView.delegate = _webViewDelegate;
+
+ // register this viewcontroller with the NSURLProtocol, only after the User-Agent is set
+ [CDVURLProtocol registerViewController:self];
+
+ // /////////////////
+
+ NSString* enableViewportScale = [self settingForKey:@"EnableViewportScale"];
+ NSNumber* allowInlineMediaPlayback = [self settingForKey:@"AllowInlineMediaPlayback"];
+ BOOL mediaPlaybackRequiresUserAction = YES; // default value
+ if ([self settingForKey:@"MediaPlaybackRequiresUserAction"]) {
+ mediaPlaybackRequiresUserAction = [(NSNumber*)[self settingForKey:@"MediaPlaybackRequiresUserAction"] boolValue];
+ }
+
+ self.webView.scalesPageToFit = [enableViewportScale boolValue];
+
+ /*
+ * Fire up CDVLocalStorage to work-around WebKit storage limitations: on all iOS 5.1+ versions for local-only backups, but only needed on iOS 5.1 for cloud backup.
+ */
+ if (IsAtLeastiOSVersion(@"5.1") && (([backupWebStorageType isEqualToString:@"local"]) ||
+ ([backupWebStorageType isEqualToString:@"cloud"] && !IsAtLeastiOSVersion(@"6.0")))) {
+ [self registerPlugin:[[CDVLocalStorage alloc] initWithWebView:self.webView] withClassName:NSStringFromClass([CDVLocalStorage class])];
+ }
+
+ /*
+ * This is for iOS 4.x, where you can allow inline <video> and <audio>, and also autoplay them
+ */
+ if ([allowInlineMediaPlayback boolValue] && [self.webView respondsToSelector:@selector(allowsInlineMediaPlayback)]) {
+ self.webView.allowsInlineMediaPlayback = YES;
+ }
+ if ((mediaPlaybackRequiresUserAction == NO) && [self.webView respondsToSelector:@selector(mediaPlaybackRequiresUserAction)]) {
+ self.webView.mediaPlaybackRequiresUserAction = NO;
+ }
+
+ // By default, overscroll bouncing is allowed.
+ // UIWebViewBounce has been renamed to DisallowOverscroll, but both are checked.
+ BOOL bounceAllowed = YES;
+ NSNumber* disallowOverscroll = [self settingForKey:@"DisallowOverscroll"];
+ if (disallowOverscroll == nil) {
+ NSNumber* bouncePreference = [self settingForKey:@"UIWebViewBounce"];
+ bounceAllowed = (bouncePreference == nil || [bouncePreference boolValue]);
+ } else {
+ bounceAllowed = ![disallowOverscroll boolValue];
+ }
+
+ // prevent webView from bouncing
+ // based on the DisallowOverscroll/UIWebViewBounce key in config.xml
+ if (!bounceAllowed) {
+ if ([self.webView respondsToSelector:@selector(scrollView)]) {
+ ((UIScrollView*)[self.webView scrollView]).bounces = NO;
+ } else {
+ for (id subview in self.webView.subviews) {
+ if ([[subview class] isSubclassOfClass:[UIScrollView class]]) {
+ ((UIScrollView*)subview).bounces = NO;
+ }
+ }
+ }
+ }
+
+ NSString* decelerationSetting = [self settingForKey:@"UIWebViewDecelerationSpeed"];
+ if (![@"fast" isEqualToString:decelerationSetting]) {
+ [self.webView.scrollView setDecelerationRate:UIScrollViewDecelerationRateNormal];
+ }
+
+ /*
+ * iOS 6.0 UIWebView properties
+ */
+ if (IsAtLeastiOSVersion(@"6.0")) {
+ BOOL keyboardDisplayRequiresUserAction = YES; // KeyboardDisplayRequiresUserAction - defaults to YES
+ if ([self settingForKey:@"KeyboardDisplayRequiresUserAction"] != nil) {
+ if ([self settingForKey:@"KeyboardDisplayRequiresUserAction"]) {
+ keyboardDisplayRequiresUserAction = [(NSNumber*)[self settingForKey:@"KeyboardDisplayRequiresUserAction"] boolValue];
+ }
+ }
+
+ // property check for compiling under iOS < 6
+ if ([self.webView respondsToSelector:@selector(setKeyboardDisplayRequiresUserAction:)]) {
+ [self.webView setValue:[NSNumber numberWithBool:keyboardDisplayRequiresUserAction] forKey:@"keyboardDisplayRequiresUserAction"];
+ }
+
+ BOOL suppressesIncrementalRendering = NO; // SuppressesIncrementalRendering - defaults to NO
+ if ([self settingForKey:@"SuppressesIncrementalRendering"] != nil) {
+ if ([self settingForKey:@"SuppressesIncrementalRendering"]) {
+ suppressesIncrementalRendering = [(NSNumber*)[self settingForKey:@"SuppressesIncrementalRendering"] boolValue];
+ }
+ }
+
+ // property check for compiling under iOS < 6
+ if ([self.webView respondsToSelector:@selector(setSuppressesIncrementalRendering:)]) {
+ [self.webView setValue:[NSNumber numberWithBool:suppressesIncrementalRendering] forKey:@"suppressesIncrementalRendering"];
+ }
+ }
+
+ /*
+ * iOS 7.0 UIWebView properties
+ */
+ if (IsAtLeastiOSVersion(@"7.0")) {
+ SEL ios7sel = nil;
+ id prefObj = nil;
+
+ CGFloat gapBetweenPages = 0.0; // default
+ prefObj = [self settingForKey:@"GapBetweenPages"];
+ if (prefObj != nil) {
+ gapBetweenPages = [prefObj floatValue];
+ }
+
+ // property check for compiling under iOS < 7
+ ios7sel = NSSelectorFromString(@"setGapBetweenPages:");
+ if ([self.webView respondsToSelector:ios7sel]) {
+ [self.webView setValue:[NSNumber numberWithFloat:gapBetweenPages] forKey:@"gapBetweenPages"];
+ }
+
+ CGFloat pageLength = 0.0; // default
+ prefObj = [self settingForKey:@"PageLength"];
+ if (prefObj != nil) {
+ pageLength = [[self settingForKey:@"PageLength"] floatValue];
+ }
+
+ // property check for compiling under iOS < 7
+ ios7sel = NSSelectorFromString(@"setPageLength:");
+ if ([self.webView respondsToSelector:ios7sel]) {
+ [self.webView setValue:[NSNumber numberWithBool:pageLength] forKey:@"pageLength"];
+ }
+
+ NSInteger paginationBreakingMode = 0; // default - UIWebPaginationBreakingModePage
+ prefObj = [self settingForKey:@"PaginationBreakingMode"];
+ if (prefObj != nil) {
+ NSArray* validValues = @[@"page", @"column"];
+ NSString* prefValue = [validValues objectAtIndex:0];
+
+ if ([prefObj isKindOfClass:[NSString class]]) {
+ prefValue = prefObj;
+ }
+
+ paginationBreakingMode = [validValues indexOfObject:[prefValue lowercaseString]];
+ if (paginationBreakingMode == NSNotFound) {
+ paginationBreakingMode = 0;
+ }
+ }
+
+ // property check for compiling under iOS < 7
+ ios7sel = NSSelectorFromString(@"setPaginationBreakingMode:");
+ if ([self.webView respondsToSelector:ios7sel]) {
+ [self.webView setValue:[NSNumber numberWithInteger:paginationBreakingMode] forKey:@"paginationBreakingMode"];
+ }
+
+ NSInteger paginationMode = 0; // default - UIWebPaginationModeUnpaginated
+ prefObj = [self settingForKey:@"PaginationMode"];
+ if (prefObj != nil) {
+ NSArray* validValues = @[@"unpaginated", @"lefttoright", @"toptobottom", @"bottomtotop", @"righttoleft"];
+ NSString* prefValue = [validValues objectAtIndex:0];
+
+ if ([prefObj isKindOfClass:[NSString class]]) {
+ prefValue = prefObj;
+ }
+
+ paginationMode = [validValues indexOfObject:[prefValue lowercaseString]];
+ if (paginationMode == NSNotFound) {
+ paginationMode = 0;
+ }
+ }
+
+ // property check for compiling under iOS < 7
+ ios7sel = NSSelectorFromString(@"setPaginationMode:");
+ if ([self.webView respondsToSelector:ios7sel]) {
+ [self.webView setValue:[NSNumber numberWithInteger:paginationMode] forKey:@"paginationMode"];
+ }
+ }
+
+ if ([self.startupPluginNames count] > 0) {
+ [CDVTimer start:@"TotalPluginStartup"];
+
+ for (NSString* pluginName in self.startupPluginNames) {
+ [CDVTimer start:pluginName];
+ [self getCommandInstance:pluginName];
+ [CDVTimer stop:pluginName];
+ }
+
+ [CDVTimer stop:@"TotalPluginStartup"];
+ }
+
+ [self registerPlugin:[[CDVHandleOpenURL alloc] initWithWebView:self.webView] withClassName:NSStringFromClass([CDVHandleOpenURL class])];
+
+ // /////////////////
+ NSURL* appURL = [self appUrl];
+
+ [CDVUserAgentUtil acquireLock:^(NSInteger lockToken) {
+ _userAgentLockToken = lockToken;
+ [CDVUserAgentUtil setUserAgent:self.userAgent lockToken:lockToken];
+ if (appURL) {
+ NSURLRequest* appReq = [NSURLRequest requestWithURL:appURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
+ [self.webView loadRequest:appReq];
+ } else {
+ NSString* loadErr = [NSString stringWithFormat:@"ERROR: Start Page at '%@/%@' was not found.", self.wwwFolderName, self.startPage];
+ NSLog(@"%@", loadErr);
+
+ NSURL* errorUrl = [self errorUrl];
+ if (errorUrl) {
+ errorUrl = [NSURL URLWithString:[NSString stringWithFormat:@"?error=%@", [loadErr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]] relativeToURL:errorUrl];
+ NSLog(@"%@", [errorUrl absoluteString]);
+ [self.webView loadRequest:[NSURLRequest requestWithURL:errorUrl]];
+ } else {
+ NSString* html = [NSString stringWithFormat:@"<html><body> %@ </body></html>", loadErr];
+ [self.webView loadHTMLString:html baseURL:nil];
+ }
+ }
+ }];
+}
+
+- (id)settingForKey:(NSString*)key
+{
+ return [[self settings] objectForKey:[key lowercaseString]];
+}
+
+- (void)setSetting:(id)setting forKey:(NSString*)key
+{
+ [[self settings] setObject:setting forKey:[key lowercaseString]];
+}
+
+- (NSArray*)parseInterfaceOrientations:(NSArray*)orientations
+{
+ NSMutableArray* result = [[NSMutableArray alloc] init];
+
+ if (orientations != nil) {
+ NSEnumerator* enumerator = [orientations objectEnumerator];
+ NSString* orientationString;
+
+ while (orientationString = [enumerator nextObject]) {
+ if ([orientationString isEqualToString:@"UIInterfaceOrientationPortrait"]) {
+ [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationPortrait]];
+ } else if ([orientationString isEqualToString:@"UIInterfaceOrientationPortraitUpsideDown"]) {
+ [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationPortraitUpsideDown]];
+ } else if ([orientationString isEqualToString:@"UIInterfaceOrientationLandscapeLeft"]) {
+ [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationLandscapeLeft]];
+ } else if ([orientationString isEqualToString:@"UIInterfaceOrientationLandscapeRight"]) {
+ [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationLandscapeRight]];
+ }
+ }
+ }
+
+ // default
+ if ([result count] == 0) {
+ [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationPortrait]];
+ }
+
+ return result;
+}
+
+- (NSInteger)mapIosOrientationToJsOrientation:(UIInterfaceOrientation)orientation
+{
+ switch (orientation) {
+ case UIInterfaceOrientationPortraitUpsideDown:
+ return 180;
+
+ case UIInterfaceOrientationLandscapeLeft:
+ return -90;
+
+ case UIInterfaceOrientationLandscapeRight:
+ return 90;
+
+ case UIInterfaceOrientationPortrait:
+ return 0;
+
+ default:
+ return 0;
+ }
+}
+
+- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
+{
+ // First, ask the webview via JS if it supports the new orientation
+ NSString* jsCall = [NSString stringWithFormat:
+ @"window.shouldRotateToOrientation && window.shouldRotateToOrientation(%ld);"
+ , (long)[self mapIosOrientationToJsOrientation:interfaceOrientation]];
+ NSString* res = [webView stringByEvaluatingJavaScriptFromString:jsCall];
+
+ if ([res length] > 0) {
+ return [res boolValue];
+ }
+
+ // if js did not handle the new orientation (no return value), use values from the plist (via supportedOrientations)
+ return [self supportsOrientation:interfaceOrientation];
+}
+
+- (BOOL)shouldAutorotate
+{
+ return YES;
+}
+
+- (NSUInteger)supportedInterfaceOrientations
+{
+ NSUInteger ret = 0;
+
+ if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationPortrait]) {
+ ret = ret | (1 << UIInterfaceOrientationPortrait);
+ }
+ if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationPortraitUpsideDown]) {
+ ret = ret | (1 << UIInterfaceOrientationPortraitUpsideDown);
+ }
+ if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationLandscapeRight]) {
+ ret = ret | (1 << UIInterfaceOrientationLandscapeRight);
+ }
+ if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationLandscapeLeft]) {
+ ret = ret | (1 << UIInterfaceOrientationLandscapeLeft);
+ }
+
+ return ret;
+}
+
+- (BOOL)supportsOrientation:(UIInterfaceOrientation)orientation
+{
+ return [self.supportedOrientations containsObject:[NSNumber numberWithInt:orientation]];
+}
+
+- (UIWebView*)newCordovaViewWithFrame:(CGRect)bounds
+{
+ return [[UIWebView alloc] initWithFrame:bounds];
+}
+
+- (NSString*)userAgent
+{
+ if (_userAgent == nil) {
+ NSString* localBaseUserAgent;
+ if (self.baseUserAgent != nil) {
+ localBaseUserAgent = self.baseUserAgent;
+ } else {
+ localBaseUserAgent = [CDVUserAgentUtil originalUserAgent];
+ }
+ // Use our address as a unique number to append to the User-Agent.
+ _userAgent = [NSString stringWithFormat:@"%@ (%lld)", localBaseUserAgent, (long long)self];
+ }
+ return _userAgent;
+}
+
+- (void)createGapView
+{
+ CGRect webViewBounds = self.view.bounds;
+
+ webViewBounds.origin = self.view.bounds.origin;
+
+ self.webView = [self newCordovaViewWithFrame:webViewBounds];
+ self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
+
+ [self.view addSubview:self.webView];
+ [self.view sendSubviewToBack:self.webView];
+}
+
+- (void)didReceiveMemoryWarning
+{
+ // iterate through all the plugin objects, and call hasPendingOperation
+ // if at least one has a pending operation, we don't call [super didReceiveMemoryWarning]
+
+ NSEnumerator* enumerator = [self.pluginObjects objectEnumerator];
+ CDVPlugin* plugin;
+
+ BOOL doPurge = YES;
+
+ while ((plugin = [enumerator nextObject])) {
+ if (plugin.hasPendingOperation) {
+ NSLog(@"Plugin '%@' has a pending operation, memory purge is delayed for didReceiveMemoryWarning.", NSStringFromClass([plugin class]));
+ doPurge = NO;
+ }
+ }
+
+ if (doPurge) {
+ // Releases the view if it doesn't have a superview.
+ [super didReceiveMemoryWarning];
+ }
+
+ // Release any cached data, images, etc. that aren't in use.
+}
+
+- (void)viewDidUnload
+{
+ // Release any retained subviews of the main view.
+ // e.g. self.myOutlet = nil;
+
+ self.webView.delegate = nil;
+ self.webView = nil;
+ [CDVUserAgentUtil releaseLock:&_userAgentLockToken];
+
+ [super viewDidUnload];
+}
+
+#pragma mark UIWebViewDelegate
+
+/**
+ When web application loads Add stuff to the DOM, mainly the user-defined settings from the Settings.plist file, and
+ the device's data such as device ID, platform version, etc.
+ */
+- (void)webViewDidStartLoad:(UIWebView*)theWebView
+{
+ NSLog(@"Resetting plugins due to page load.");
+ [_commandQueue resetRequestId];
+ [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginResetNotification object:self.webView]];
+}
+
+/**
+ Called when the webview finishes loading. This stops the activity view.
+ */
+- (void)webViewDidFinishLoad:(UIWebView*)theWebView
+{
+ NSLog(@"Finished load of: %@", theWebView.request.URL);
+ // It's safe to release the lock even if this is just a sub-frame that's finished loading.
+ [CDVUserAgentUtil releaseLock:&_userAgentLockToken];
+
+ /*
+ * Hide the Top Activity THROBBER in the Battery Bar
+ */
+ [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
+
+ [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPageDidLoadNotification object:self.webView]];
+}
+
+- (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error
+{
+ [CDVUserAgentUtil releaseLock:&_userAgentLockToken];
+
+ NSString* message = [NSString stringWithFormat:@"Failed to load webpage with error: %@", [error localizedDescription]];
+ NSLog(@"%@", message);
+
+ NSURL* errorUrl = [self errorUrl];
+ if (errorUrl) {
+ errorUrl = [NSURL URLWithString:[NSString stringWithFormat:@"?error=%@", [message stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]] relativeToURL:errorUrl];
+ NSLog(@"%@", [errorUrl absoluteString]);
+ [theWebView loadRequest:[NSURLRequest requestWithURL:errorUrl]];
+ }
+}
+
+- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
+{
+ NSURL* url = [request URL];
+
+ /*
+ * Execute any commands queued with cordova.exec() on the JS side.
+ * The part of the URL after gap:// is irrelevant.
+ */
+ if ([[url scheme] isEqualToString:@"gap"]) {
+ [_commandQueue fetchCommandsFromJs];
+ // The delegate is called asynchronously in this case, so we don't have to use
+ // flushCommandQueueWithDelayedJs (setTimeout(0)) as we do with hash changes.
+ [_commandQueue executePending];
+ return NO;
+ }
+
+ if ([[url fragment] hasPrefix:@"%01"] || [[url fragment] hasPrefix:@"%02"]) {
+ // Delegate is called *immediately* for hash changes. This means that any
+ // calls to stringByEvaluatingJavascriptFromString will occur in the middle
+ // of an existing (paused) call stack. This doesn't cause errors, but may
+ // be unexpected to callers (exec callbacks will be called before exec() even
+ // returns). To avoid this, we do not do any synchronous JS evals by using
+ // flushCommandQueueWithDelayedJs.
+ NSString* inlineCommands = [[url fragment] substringFromIndex:3];
+ if ([inlineCommands length] == 0) {
+ // Reach in right away since the WebCore / Main thread are already synchronized.
+ [_commandQueue fetchCommandsFromJs];
+ } else {
+ inlineCommands = [inlineCommands stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+ [_commandQueue enqueueCommandBatch:inlineCommands];
+ }
+ // Switch these for minor performance improvements, and to really live on the wild side.
+ // Callbacks will occur in the middle of the location.hash = ... statement!
+ [(CDVCommandDelegateImpl*)_commandDelegate flushCommandQueueWithDelayedJs];
+ // [_commandQueue executePending];
+
+ // Although we return NO, the hash change does end up taking effect.
+ return NO;
+ }
+
+ /*
+ * Give plugins the chance to handle the url
+ */
+ for (NSString* pluginName in pluginObjects) {
+ CDVPlugin* plugin = [pluginObjects objectForKey:pluginName];
+ SEL selector = NSSelectorFromString(@"shouldOverrideLoadWithRequest:navigationType:");
+ if ([plugin respondsToSelector:selector]) {
+ if (((BOOL (*)(id, SEL, id, int))objc_msgSend)(plugin, selector, request, navigationType) == YES) {
+ return NO;
+ }
+ }
+ }
+
+ /*
+ * If a URL is being loaded that's a file/http/https URL, just load it internally
+ */
+ if ([url isFileURL]) {
+ return YES;
+ }
+
+ /*
+ * If we loaded the HTML from a string, we let the app handle it
+ */
+ else if (self.loadFromString == YES) {
+ self.loadFromString = NO;
+ return YES;
+ }
+
+ /*
+ * all tel: scheme urls we let the UIWebview handle it using the default behavior
+ */
+ else if ([[url scheme] isEqualToString:@"tel"]) {
+ return YES;
+ }
+
+ /*
+ * all about: scheme urls are not handled
+ */
+ else if ([[url scheme] isEqualToString:@"about"]) {
+ return NO;
+ }
+
+ /*
+ * all data: scheme urls are handled
+ */
+ else if ([[url scheme] isEqualToString:@"data"]) {
+ return YES;
+ }
+
+ /*
+ * Handle all other types of urls (tel:, sms:), and requests to load a url in the main webview.
+ */
+ else {
+ if ([self.whitelist schemeIsAllowed:[url scheme]]) {
+ return [self.whitelist URLIsAllowed:url];
+ } else {
+ if ([[UIApplication sharedApplication] canOpenURL:url]) {
+ [[UIApplication sharedApplication] openURL:url];
+ } else { // handle any custom schemes to plugins
+ [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]];
+ }
+ }
+
+ return NO;
+ }
+
+ return YES;
+}
+
+#pragma mark GapHelpers
+
+- (void)javascriptAlert:(NSString*)text
+{
+ NSString* jsString = [NSString stringWithFormat:@"alert('%@');", text];
+
+ [self.commandDelegate evalJs:jsString];
+}
+
++ (NSString*)applicationDocumentsDirectory
+{
+ NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
+ NSString* basePath = (([paths count] > 0) ? ([paths objectAtIndex : 0]) : nil);
+
+ return basePath;
+}
+
+#pragma mark CordovaCommands
+
+- (void)registerPlugin:(CDVPlugin*)plugin withClassName:(NSString*)className
+{
+ if ([plugin respondsToSelector:@selector(setViewController:)]) {
+ [plugin setViewController:self];
+ }
+
+ if ([plugin respondsToSelector:@selector(setCommandDelegate:)]) {
+ [plugin setCommandDelegate:_commandDelegate];
+ }
+
+ [self.pluginObjects setObject:plugin forKey:className];
+ [plugin pluginInitialize];
+}
+
+- (void)registerPlugin:(CDVPlugin*)plugin withPluginName:(NSString*)pluginName
+{
+ if ([plugin respondsToSelector:@selector(setViewController:)]) {
+ [plugin setViewController:self];
+ }
+
+ if ([plugin respondsToSelector:@selector(setCommandDelegate:)]) {
+ [plugin setCommandDelegate:_commandDelegate];
+ }
+
+ NSString* className = NSStringFromClass([plugin class]);
+ [self.pluginObjects setObject:plugin forKey:className];
+ [self.pluginsMap setValue:className forKey:[pluginName lowercaseString]];
+ [plugin pluginInitialize];
+}
+
+/**
+ Returns an instance of a CordovaCommand object, based on its name. If one exists already, it is returned.
+ */
+- (id)getCommandInstance:(NSString*)pluginName
+{
+ // first, we try to find the pluginName in the pluginsMap
+ // (acts as a whitelist as well) if it does not exist, we return nil
+ // NOTE: plugin names are matched as lowercase to avoid problems - however, a
+ // possible issue is there can be duplicates possible if you had:
+ // "org.apache.cordova.Foo" and "org.apache.cordova.foo" - only the lower-cased entry will match
+ NSString* className = [self.pluginsMap objectForKey:[pluginName lowercaseString]];
+
+ if (className == nil) {
+ return nil;
+ }
+
+ id obj = [self.pluginObjects objectForKey:className];
+ if (!obj) {
+ obj = [[NSClassFromString(className)alloc] initWithWebView:webView];
+
+ if (obj != nil) {
+ [self registerPlugin:obj withClassName:className];
+ } else {
+ NSLog(@"CDVPlugin class %@ (pluginName: %@) does not exist.", className, pluginName);
+ }
+ }
+ return obj;
+}
+
+#pragma mark -
+
+- (NSString*)appURLScheme
+{
+ NSString* URLScheme = nil;
+
+ NSArray* URLTypes = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleURLTypes"];
+
+ if (URLTypes != nil) {
+ NSDictionary* dict = [URLTypes objectAtIndex:0];
+ if (dict != nil) {
+ NSArray* URLSchemes = [dict objectForKey:@"CFBundleURLSchemes"];
+ if (URLSchemes != nil) {
+ URLScheme = [URLSchemes objectAtIndex:0];
+ }
+ }
+ }
+
+ return URLScheme;
+}
+
+/**
+ Returns the contents of the named plist bundle, loaded as a dictionary object
+ */
++ (NSDictionary*)getBundlePlist:(NSString*)plistName
+{
+ NSString* errorDesc = nil;
+ NSPropertyListFormat format;
+ NSString* plistPath = [[NSBundle mainBundle] pathForResource:plistName ofType:@"plist"];
+ NSData* plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
+ NSDictionary* temp = (NSDictionary*)[NSPropertyListSerialization
+ propertyListFromData:plistXML
+ mutabilityOption:NSPropertyListMutableContainersAndLeaves
+ format:&format errorDescription:&errorDesc];
+
+ return temp;
+}
+
+#pragma mark -
+#pragma mark UIApplicationDelegate impl
+
+/*
+ This method lets your application know that it is about to be terminated and purged from memory entirely
+ */
+- (void)onAppWillTerminate:(NSNotification*)notification
+{
+ // empty the tmp directory
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ NSError* __autoreleasing err = nil;
+
+ // clear contents of NSTemporaryDirectory
+ NSString* tempDirectoryPath = NSTemporaryDirectory();
+ NSDirectoryEnumerator* directoryEnumerator = [fileMgr enumeratorAtPath:tempDirectoryPath];
+ NSString* fileName = nil;
+ BOOL result;
+
+ while ((fileName = [directoryEnumerator nextObject])) {
+ NSString* filePath = [tempDirectoryPath stringByAppendingPathComponent:fileName];
+ result = [fileMgr removeItemAtPath:filePath error:&err];
+ if (!result && err) {
+ NSLog(@"Failed to delete: %@ (error: %@)", filePath, err);
+ }
+ }
+}
+
+/*
+ This method is called to let your application know that it is about to move from the active to inactive state.
+ You should use this method to pause ongoing tasks, disable timer, ...
+ */
+- (void)onAppWillResignActive:(NSNotification*)notification
+{
+ // NSLog(@"%@",@"applicationWillResignActive");
+ [self.commandDelegate evalJs:@"cordova.fireDocumentEvent('resign');" scheduledOnRunLoop:NO];
+}
+
+/*
+ In iOS 4.0 and later, this method is called as part of the transition from the background to the inactive state.
+ You can use this method to undo many of the changes you made to your application upon entering the background.
+ invariably followed by applicationDidBecomeActive
+ */
+- (void)onAppWillEnterForeground:(NSNotification*)notification
+{
+ // NSLog(@"%@",@"applicationWillEnterForeground");
+ [self.commandDelegate evalJs:@"cordova.fireDocumentEvent('resume');"];
+}
+
+// This method is called to let your application know that it moved from the inactive to active state.
+- (void)onAppDidBecomeActive:(NSNotification*)notification
+{
+ // NSLog(@"%@",@"applicationDidBecomeActive");
+ [self.commandDelegate evalJs:@"cordova.fireDocumentEvent('active');"];
+}
+
+/*
+ In iOS 4.0 and later, this method is called instead of the applicationWillTerminate: method
+ when the user quits an application that supports background execution.
+ */
+- (void)onAppDidEnterBackground:(NSNotification*)notification
+{
+ // NSLog(@"%@",@"applicationDidEnterBackground");
+ [self.commandDelegate evalJs:@"cordova.fireDocumentEvent('pause', null, true);" scheduledOnRunLoop:NO];
+}
+
+// ///////////////////////
+
+- (void)onPageDidLoad:(NSNotification*)notification
+{
+ if (self.openURL) {
+ [self processOpenUrl:self.openURL pageLoaded:YES];
+ self.openURL = nil;
+ }
+}
+
+- (void)processOpenUrl:(NSURL*)url pageLoaded:(BOOL)pageLoaded
+{
+ if (!pageLoaded) {
+ // query the webview for readystate
+ NSString* readyState = [webView stringByEvaluatingJavaScriptFromString:@"document.readyState"];
+ pageLoaded = [readyState isEqualToString:@"loaded"] || [readyState isEqualToString:@"complete"];
+ }
+
+ if (pageLoaded) {
+ // calls into javascript global function 'handleOpenURL'
+ NSString* jsString = [NSString stringWithFormat:@"if (typeof handleOpenURL === 'function') { handleOpenURL(\"%@\");}", url];
+ [self.webView stringByEvaluatingJavaScriptFromString:jsString];
+ } else {
+ // save for when page has loaded
+ self.openURL = url;
+ }
+}
+
+- (void)processOpenUrl:(NSURL*)url
+{
+ [self processOpenUrl:url pageLoaded:NO];
+}
+
+// ///////////////////////
+
+- (void)dealloc
+{
+ [CDVURLProtocol unregisterViewController:self];
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ self.webView.delegate = nil;
+ self.webView = nil;
+ [CDVUserAgentUtil releaseLock:&_userAgentLockToken];
+ [_commandQueue dispose];
+ [[self.pluginObjects allValues] makeObjectsPerformSelector:@selector(dispose)];
+}
+
+@end