cordova-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From i..@apache.org
Subject [31/50] [abbrv] ios commit: CB-8690 - Group files into folders in CordovaLib/Classes
Date Thu, 19 Mar 2015 16:56:20 GMT
http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Private/Plugins/CDVLocalStorage/CDVLocalStorage.m
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Private/Plugins/CDVLocalStorage/CDVLocalStorage.m b/CordovaLib/Classes/Private/Plugins/CDVLocalStorage/CDVLocalStorage.m
new file mode 100644
index 0000000..252dfaf
--- /dev/null
+++ b/CordovaLib/Classes/Private/Plugins/CDVLocalStorage/CDVLocalStorage.m
@@ -0,0 +1,487 @@
+/*
+ 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 "CDVLocalStorage.h"
+#import "CDV.h"
+
+@interface CDVLocalStorage ()
+
+@property (nonatomic, readwrite, strong) NSMutableArray* backupInfo;  // array of CDVBackupInfo objects
+@property (nonatomic, readwrite, weak) id <UIWebViewDelegate> webviewDelegate;
+
+@end
+
+@implementation CDVLocalStorage
+
+@synthesize backupInfo, webviewDelegate;
+
+- (void)pluginInitialize
+{
+    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onResignActive)
+                                                 name:UIApplicationWillResignActiveNotification object:nil];
+    BOOL cloudBackup = [@"cloud" isEqualToString : self.commandDelegate.settings[[@"BackupWebStorage" lowercaseString]]];
+
+    self.backupInfo = [[self class] createBackupInfoWithCloudBackup:cloudBackup];
+}
+
+#pragma mark -
+#pragma mark Plugin interface methods
+
++ (NSMutableArray*)createBackupInfoWithTargetDir:(NSString*)targetDir backupDir:(NSString*)backupDir targetDirNests:(BOOL)targetDirNests backupDirNests:(BOOL)backupDirNests rename:(BOOL)rename
+{
+    /*
+     This "helper" does so much work and has so many options it would probably be clearer to refactor the whole thing.
+     Basically, there are three database locations:
+
+     1. "Normal" dir -- LIB/<nested dires WebKit/LocalStorage etc>/<normal filenames>
+     2. "Caches" dir -- LIB/Caches/<normal filenames>
+     3. "Backup" dir -- DOC/Backups/<renamed filenames>
+
+     And between these three, there are various migration paths, most of which only consider 2 of the 3, which is why this helper is based on 2 locations and has a notion of "direction".
+     */
+    NSMutableArray* backupInfo = [NSMutableArray arrayWithCapacity:3];
+
+    NSString* original;
+    NSString* backup;
+    CDVBackupInfo* backupItem;
+
+    // ////////// LOCALSTORAGE
+
+    original = [targetDir stringByAppendingPathComponent:targetDirNests ? @"WebKit/LocalStorage/file__0.localstorage":@"file__0.localstorage"];
+    backup = [backupDir stringByAppendingPathComponent:(backupDirNests ? @"WebKit/LocalStorage" : @"")];
+    backup = [backup stringByAppendingPathComponent:(rename ? @"localstorage.appdata.db" : @"file__0.localstorage")];
+
+    backupItem = [[CDVBackupInfo alloc] init];
+    backupItem.backup = backup;
+    backupItem.original = original;
+    backupItem.label = @"localStorage database";
+
+    [backupInfo addObject:backupItem];
+
+    // ////////// WEBSQL MAIN DB
+
+    original = [targetDir stringByAppendingPathComponent:targetDirNests ? @"WebKit/LocalStorage/Databases.db":@"Databases.db"];
+    backup = [backupDir stringByAppendingPathComponent:(backupDirNests ? @"WebKit/LocalStorage" : @"")];
+    backup = [backup stringByAppendingPathComponent:(rename ? @"websqlmain.appdata.db" : @"Databases.db")];
+
+    backupItem = [[CDVBackupInfo alloc] init];
+    backupItem.backup = backup;
+    backupItem.original = original;
+    backupItem.label = @"websql main database";
+
+    [backupInfo addObject:backupItem];
+
+    // ////////// WEBSQL DATABASES
+
+    original = [targetDir stringByAppendingPathComponent:targetDirNests ? @"WebKit/LocalStorage/file__0":@"file__0"];
+    backup = [backupDir stringByAppendingPathComponent:(backupDirNests ? @"WebKit/LocalStorage" : @"")];
+    backup = [backup stringByAppendingPathComponent:(rename ? @"websqldbs.appdata.db" : @"file__0")];
+
+    backupItem = [[CDVBackupInfo alloc] init];
+    backupItem.backup = backup;
+    backupItem.original = original;
+    backupItem.label = @"websql databases";
+
+    [backupInfo addObject:backupItem];
+
+    return backupInfo;
+}
+
++ (NSMutableArray*)createBackupInfoWithCloudBackup:(BOOL)cloudBackup
+{
+    // create backup info from backup folder to caches folder
+    NSString* appLibraryFolder = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
+    NSString* appDocumentsFolder = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
+    NSString* cacheFolder = [appLibraryFolder stringByAppendingPathComponent:@"Caches"];
+    NSString* backupsFolder = [appDocumentsFolder stringByAppendingPathComponent:@"Backups"];
+
+    // create the backups folder, if needed
+    [[NSFileManager defaultManager] createDirectoryAtPath:backupsFolder withIntermediateDirectories:YES attributes:nil error:nil];
+
+    [self addSkipBackupAttributeToItemAtURL:[NSURL fileURLWithPath:backupsFolder] skip:!cloudBackup];
+
+    return [self createBackupInfoWithTargetDir:cacheFolder backupDir:backupsFolder targetDirNests:NO backupDirNests:NO rename:YES];
+}
+
++ (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL*)URL skip:(BOOL)skip
+{
+    NSError* error = nil;
+    BOOL success = [URL setResourceValue:[NSNumber numberWithBool:skip] forKey:NSURLIsExcludedFromBackupKey error:&error];
+
+    if (!success) {
+        NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
+    }
+    return success;
+}
+
++ (BOOL)copyFrom:(NSString*)src to:(NSString*)dest error:(NSError* __autoreleasing*)error
+{
+    NSFileManager* fileManager = [NSFileManager defaultManager];
+
+    if (![fileManager fileExistsAtPath:src]) {
+        NSString* errorString = [NSString stringWithFormat:@"%@ file does not exist.", src];
+        if (error != NULL) {
+            (*error) = [NSError errorWithDomain:kCDVLocalStorageErrorDomain
+                                           code:kCDVLocalStorageFileOperationError
+                                       userInfo:[NSDictionary dictionaryWithObject:errorString
+                                                                            forKey:NSLocalizedDescriptionKey]];
+        }
+        return NO;
+    }
+
+    // generate unique filepath in temp directory
+    CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
+    CFStringRef uuidString = CFUUIDCreateString(kCFAllocatorDefault, uuidRef);
+    NSString* tempBackup = [[NSTemporaryDirectory() stringByAppendingPathComponent:(__bridge NSString*)uuidString] stringByAppendingPathExtension:@"bak"];
+    CFRelease(uuidString);
+    CFRelease(uuidRef);
+
+    BOOL destExists = [fileManager fileExistsAtPath:dest];
+
+    // backup the dest
+    if (destExists && ![fileManager copyItemAtPath:dest toPath:tempBackup error:error]) {
+        return NO;
+    }
+
+    // remove the dest
+    if (destExists && ![fileManager removeItemAtPath:dest error:error]) {
+        return NO;
+    }
+
+    // create path to dest
+    if (!destExists && ![fileManager createDirectoryAtPath:[dest stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:error]) {
+        return NO;
+    }
+
+    // copy src to dest
+    if ([fileManager copyItemAtPath:src toPath:dest error:error]) {
+        // success - cleanup - delete the backup to the dest
+        if ([fileManager fileExistsAtPath:tempBackup]) {
+            [fileManager removeItemAtPath:tempBackup error:error];
+        }
+        return YES;
+    } else {
+        // failure - we restore the temp backup file to dest
+        [fileManager copyItemAtPath:tempBackup toPath:dest error:error];
+        // cleanup - delete the backup to the dest
+        if ([fileManager fileExistsAtPath:tempBackup]) {
+            [fileManager removeItemAtPath:tempBackup error:error];
+        }
+        return NO;
+    }
+}
+
+- (BOOL)shouldBackup
+{
+    for (CDVBackupInfo* info in self.backupInfo) {
+        if ([info shouldBackup]) {
+            return YES;
+        }
+    }
+
+    return NO;
+}
+
+- (BOOL)shouldRestore
+{
+    for (CDVBackupInfo* info in self.backupInfo) {
+        if ([info shouldRestore]) {
+            return YES;
+        }
+    }
+
+    return NO;
+}
+
+/* copy from webkitDbLocation to persistentDbLocation */
+- (void)backup:(CDVInvokedUrlCommand*)command
+{
+    NSString* callbackId = command.callbackId;
+
+    NSError* __autoreleasing error = nil;
+    CDVPluginResult* result = nil;
+    NSString* message = nil;
+
+    for (CDVBackupInfo* info in self.backupInfo) {
+        if ([info shouldBackup]) {
+            [[self class] copyFrom:info.original to:info.backup error:&error];
+
+            if (callbackId) {
+                if (error == nil) {
+                    message = [NSString stringWithFormat:@"Backed up: %@", info.label];
+                    NSLog(@"%@", message);
+
+                    result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:message];
+                    [self.commandDelegate sendPluginResult:result callbackId:callbackId];
+                } else {
+                    message = [NSString stringWithFormat:@"Error in CDVLocalStorage (%@) backup: %@", info.label, [error localizedDescription]];
+                    NSLog(@"%@", message);
+
+                    result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:message];
+                    [self.commandDelegate sendPluginResult:result callbackId:callbackId];
+                }
+            }
+        }
+    }
+}
+
+/* copy from persistentDbLocation to webkitDbLocation */
+- (void)restore:(CDVInvokedUrlCommand*)command
+{
+    NSError* __autoreleasing error = nil;
+    CDVPluginResult* result = nil;
+    NSString* message = nil;
+
+    for (CDVBackupInfo* info in self.backupInfo) {
+        if ([info shouldRestore]) {
+            [[self class] copyFrom:info.backup to:info.original error:&error];
+
+            if (error == nil) {
+                message = [NSString stringWithFormat:@"Restored: %@", info.label];
+                NSLog(@"%@", message);
+
+                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:message];
+                [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+            } else {
+                message = [NSString stringWithFormat:@"Error in CDVLocalStorage (%@) restore: %@", info.label, [error localizedDescription]];
+                NSLog(@"%@", message);
+
+                result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:message];
+                [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+            }
+        }
+    }
+}
+
++ (void)__fixupDatabaseLocationsWithBackupType:(NSString*)backupType
+{
+    [self __verifyAndFixDatabaseLocations];
+    [self __restoreLegacyDatabaseLocationsWithBackupType:backupType];
+}
+
++ (void)__verifyAndFixDatabaseLocations
+{
+    NSBundle* mainBundle = [NSBundle mainBundle];
+    NSString* bundlePath = [[mainBundle bundlePath] stringByDeletingLastPathComponent];
+    NSString* bundleIdentifier = [[mainBundle infoDictionary] objectForKey:@"CFBundleIdentifier"];
+    NSString* appPlistPath = [bundlePath stringByAppendingPathComponent:[NSString stringWithFormat:@"Library/Preferences/%@.plist", bundleIdentifier]];
+
+    NSMutableDictionary* appPlistDict = [NSMutableDictionary dictionaryWithContentsOfFile:appPlistPath];
+    BOOL modified = [[self class] __verifyAndFixDatabaseLocationsWithAppPlistDict:appPlistDict
+                                                                       bundlePath:bundlePath
+                                                                      fileManager:[NSFileManager defaultManager]];
+
+    if (modified) {
+        BOOL ok = [appPlistDict writeToFile:appPlistPath atomically:YES];
+        [[NSUserDefaults standardUserDefaults] synchronize];
+        NSLog(@"Fix applied for database locations?: %@", ok ? @"YES" : @"NO");
+    }
+}
+
++ (BOOL)__verifyAndFixDatabaseLocationsWithAppPlistDict:(NSMutableDictionary*)appPlistDict
+                                             bundlePath:(NSString*)bundlePath
+                                            fileManager:(NSFileManager*)fileManager
+{
+    NSString* libraryCaches = @"Library/Caches";
+    NSString* libraryWebKit = @"Library/WebKit";
+
+    NSArray* keysToCheck = [NSArray arrayWithObjects:
+        @"WebKitLocalStorageDatabasePathPreferenceKey",
+        @"WebDatabaseDirectory",
+        nil];
+
+    BOOL dirty = NO;
+
+    for (NSString* key in keysToCheck) {
+        NSString* value = [appPlistDict objectForKey:key];
+        // verify key exists, and path is in app bundle, if not - fix
+        if ((value != nil) && ![value hasPrefix:bundlePath]) {
+            // the pathSuffix to use may be wrong - OTA upgrades from < 5.1 to 5.1 do keep the old path Library/WebKit,
+            // while Xcode synced ones do change the storage location to Library/Caches
+            NSString* newBundlePath = [bundlePath stringByAppendingPathComponent:libraryCaches];
+            if (![fileManager fileExistsAtPath:newBundlePath]) {
+                newBundlePath = [bundlePath stringByAppendingPathComponent:libraryWebKit];
+            }
+            [appPlistDict setValue:newBundlePath forKey:key];
+            dirty = YES;
+        }
+    }
+
+    return dirty;
+}
+
++ (void)__restoreLegacyDatabaseLocationsWithBackupType:(NSString*)backupType
+{
+    // on iOS 6, if you toggle between cloud/local backup, you must move database locations.  Default upgrade from iOS5.1 to iOS6 is like a toggle from local to cloud.
+    NSString* appLibraryFolder = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
+    NSString* appDocumentsFolder = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
+
+    NSMutableArray* backupInfo = [NSMutableArray arrayWithCapacity:0];
+
+    if ([backupType isEqualToString:@"cloud"]) {
+#ifdef DEBUG
+            NSLog(@"\n\nStarted backup to iCloud! Please be careful."
+                "\nYour application might be rejected by Apple if you store too much data."
+                "\nFor more information please read \"iOS Data Storage Guidelines\" at:"
+                "\nhttps://developer.apple.com/icloud/documentation/data-storage/"
+                "\nTo disable web storage backup to iCloud, set the BackupWebStorage preference to \"local\" in the Cordova config.xml file\n\n");
+#endif
+        // We would like to restore old backups/caches databases to the new destination (nested in lib folder)
+        [backupInfo addObjectsFromArray:[self createBackupInfoWithTargetDir:appLibraryFolder backupDir:[appDocumentsFolder stringByAppendingPathComponent:@"Backups"] targetDirNests:YES backupDirNests:NO rename:YES]];
+        [backupInfo addObjectsFromArray:[self createBackupInfoWithTargetDir:appLibraryFolder backupDir:[appLibraryFolder stringByAppendingPathComponent:@"Caches"] targetDirNests:YES backupDirNests:NO rename:NO]];
+    } else {
+        // For ios6 local backups we also want to restore from Backups dir -- but we don't need to do that here, since the plugin will do that itself.
+        [backupInfo addObjectsFromArray:[self createBackupInfoWithTargetDir:[appLibraryFolder stringByAppendingPathComponent:@"Caches"] backupDir:appLibraryFolder targetDirNests:NO backupDirNests:YES rename:NO]];
+    }
+
+    NSFileManager* manager = [NSFileManager defaultManager];
+
+    for (CDVBackupInfo* info in backupInfo) {
+        if ([manager fileExistsAtPath:info.backup]) {
+            if ([info shouldRestore]) {
+                NSLog(@"Restoring old webstorage backup. From: '%@' To: '%@'.", info.backup, info.original);
+                [self copyFrom:info.backup to:info.original error:nil];
+            }
+            NSLog(@"Removing old webstorage backup: '%@'.", info.backup);
+            [manager removeItemAtPath:info.backup error:nil];
+        }
+    }
+
+    [[NSUserDefaults standardUserDefaults] setBool:[backupType isEqualToString:@"cloud"] forKey:@"WebKitStoreWebDataForBackup"];
+}
+
+#pragma mark -
+#pragma mark Notification handlers
+
+- (void)onResignActive
+{
+    UIDevice* device = [UIDevice currentDevice];
+    NSNumber* exitsOnSuspend = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIApplicationExitsOnSuspend"];
+
+    BOOL isMultitaskingSupported = [device respondsToSelector:@selector(isMultitaskingSupported)] && [device isMultitaskingSupported];
+
+    if (exitsOnSuspend == nil) { // if it's missing, it should be NO (i.e. multi-tasking on by default)
+        exitsOnSuspend = [NSNumber numberWithBool:NO];
+    }
+
+    if (exitsOnSuspend) {
+        [self backup:nil];
+    } else if (isMultitaskingSupported) {
+        __block UIBackgroundTaskIdentifier backgroundTaskID = UIBackgroundTaskInvalid;
+
+        backgroundTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
+                [[UIApplication sharedApplication] endBackgroundTask:backgroundTaskID];
+                backgroundTaskID = UIBackgroundTaskInvalid;
+                NSLog(@"Background task to backup WebSQL/LocalStorage expired.");
+            }];
+        CDVLocalStorage __weak* weakSelf = self;
+        [self.commandDelegate runInBackground:^{
+            [weakSelf backup:nil];
+
+            [[UIApplication sharedApplication] endBackgroundTask:backgroundTaskID];
+            backgroundTaskID = UIBackgroundTaskInvalid;
+        }];
+    }
+}
+
+- (void)onAppTerminate
+{
+    [self onResignActive];
+}
+
+- (void)onReset
+{
+    [self restore:nil];
+}
+
+@end
+
+#pragma mark -
+#pragma mark CDVBackupInfo implementation
+
+@implementation CDVBackupInfo
+
+@synthesize original, backup, label;
+
+- (BOOL)file:(NSString*)aPath isNewerThanFile:(NSString*)bPath
+{
+    NSFileManager* fileManager = [NSFileManager defaultManager];
+    NSError* __autoreleasing error = nil;
+
+    NSDictionary* aPathAttribs = [fileManager attributesOfItemAtPath:aPath error:&error];
+    NSDictionary* bPathAttribs = [fileManager attributesOfItemAtPath:bPath error:&error];
+
+    NSDate* aPathModDate = [aPathAttribs objectForKey:NSFileModificationDate];
+    NSDate* bPathModDate = [bPathAttribs objectForKey:NSFileModificationDate];
+
+    if ((nil == aPathModDate) && (nil == bPathModDate)) {
+        return NO;
+    }
+
+    return [aPathModDate compare:bPathModDate] == NSOrderedDescending || bPathModDate == nil;
+}
+
+- (BOOL)item:(NSString*)aPath isNewerThanItem:(NSString*)bPath
+{
+    NSFileManager* fileManager = [NSFileManager defaultManager];
+
+    BOOL aPathIsDir = NO, bPathIsDir = NO;
+    BOOL aPathExists = [fileManager fileExistsAtPath:aPath isDirectory:&aPathIsDir];
+
+    [fileManager fileExistsAtPath:bPath isDirectory:&bPathIsDir];
+
+    if (!aPathExists) {
+        return NO;
+    }
+
+    if (!(aPathIsDir && bPathIsDir)) { // just a file
+        return [self file:aPath isNewerThanFile:bPath];
+    }
+
+    // essentially we want rsync here, but have to settle for our poor man's implementation
+    // we get the files in aPath, and see if it is newer than the file in bPath
+    // (it is newer if it doesn't exist in bPath) if we encounter the FIRST file that is newer,
+    // we return YES
+    NSDirectoryEnumerator* directoryEnumerator = [fileManager enumeratorAtPath:aPath];
+    NSString* path;
+
+    while ((path = [directoryEnumerator nextObject])) {
+        NSString* aPathFile = [aPath stringByAppendingPathComponent:path];
+        NSString* bPathFile = [bPath stringByAppendingPathComponent:path];
+
+        BOOL isNewer = [self file:aPathFile isNewerThanFile:bPathFile];
+        if (isNewer) {
+            return YES;
+        }
+    }
+
+    return NO;
+}
+
+- (BOOL)shouldBackup
+{
+    return [self item:self.original isNewerThanItem:self.backup];
+}
+
+- (BOOL)shouldRestore
+{
+    return [self item:self.backup isNewerThanItem:self.original];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Private/Plugins/CDVUIWebViewEngine/CDVUIWebViewDelegate.h
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Private/Plugins/CDVUIWebViewEngine/CDVUIWebViewDelegate.h b/CordovaLib/Classes/Private/Plugins/CDVUIWebViewEngine/CDVUIWebViewDelegate.h
new file mode 100644
index 0000000..d77f191
--- /dev/null
+++ b/CordovaLib/Classes/Private/Plugins/CDVUIWebViewEngine/CDVUIWebViewDelegate.h
@@ -0,0 +1,41 @@
+/*
+ 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 <UIKit/UIKit.h>
+#import "CDVAvailability.h"
+
+/**
+ * Distinguishes top-level navigations from sub-frame navigations.
+ * shouldStartLoadWithRequest is called for every request, but didStartLoad
+ * and didFinishLoad is called only for top-level navigations.
+ * Relevant bug: CB-2389
+ */
+@interface CDVUIWebViewDelegate : NSObject <UIWebViewDelegate>{
+    __weak NSObject <UIWebViewDelegate>* _delegate;
+    NSInteger _loadCount;
+    NSInteger _state;
+    NSInteger _curLoadToken;
+    NSInteger _loadStartPollCount;
+}
+
+- (id)initWithDelegate:(NSObject <UIWebViewDelegate>*)delegate;
+
+- (BOOL)request:(NSURLRequest*)newRequest isEqualToRequestAfterStrippingFragments:(NSURLRequest*)originalRequest;
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Private/Plugins/CDVUIWebViewEngine/CDVUIWebViewDelegate.m
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Private/Plugins/CDVUIWebViewEngine/CDVUIWebViewDelegate.m b/CordovaLib/Classes/Private/Plugins/CDVUIWebViewEngine/CDVUIWebViewDelegate.m
new file mode 100644
index 0000000..01872f6
--- /dev/null
+++ b/CordovaLib/Classes/Private/Plugins/CDVUIWebViewEngine/CDVUIWebViewDelegate.m
@@ -0,0 +1,399 @@
+/*
+ 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.
+ */
+
+//
+// Testing shows:
+//
+// In all cases, webView.request.URL is the previous page's URL (or empty) during the didStartLoad callback.
+// When loading a page with a redirect:
+// 1. shouldStartLoading (requestURL is target page)
+// 2. didStartLoading
+// 3. shouldStartLoading (requestURL is redirect target)
+// 4. didFinishLoad (request.URL is redirect target)
+//
+// Note the lack of a second didStartLoading **
+//
+// When loading a page with iframes:
+// 1. shouldStartLoading (requestURL is main page)
+// 2. didStartLoading
+// 3. shouldStartLoading (requestURL is one of the iframes)
+// 4. didStartLoading
+// 5. didFinishLoad
+// 6. didFinishLoad
+//
+// Note there is no way to distinguish which didFinishLoad maps to which didStartLoad **
+//
+// Loading a page by calling window.history.go(-1):
+// 1. didStartLoading
+// 2. didFinishLoad
+//
+// Note the lack of a shouldStartLoading call **
+// Actually - this is fixed on iOS6. iOS6 has a shouldStart. **
+//
+// Loading a page by calling location.reload()
+// 1. shouldStartLoading
+// 2. didStartLoading
+// 3. didFinishLoad
+//
+// Loading a page with an iframe that fails to load:
+// 1. shouldStart (main page)
+// 2. didStart
+// 3. shouldStart (iframe)
+// 4. didStart
+// 5. didFailWithError
+// 6. didFinish
+//
+// Loading a page with an iframe that fails to load due to an invalid URL:
+// 1. shouldStart (main page)
+// 2. didStart
+// 3. shouldStart (iframe)
+// 5. didFailWithError
+// 6. didFinish
+//
+// This case breaks our logic since there is a missing didStart. To prevent this,
+// we check URLs in shouldStart and return NO if they are invalid.
+//
+// Loading a page with an invalid URL
+// 1. shouldStart (main page)
+// 2. didFailWithError
+//
+// TODO: Record order when page is re-navigated before the first navigation finishes.
+//
+
+#import "CDVUIWebViewDelegate.h"
+
+// #define VerboseLog NSLog
+#define VerboseLog(...) do { \
+} while (0)
+
+typedef enum {
+    STATE_IDLE = 0,
+    STATE_WAITING_FOR_LOAD_START = 1,
+    STATE_WAITING_FOR_LOAD_FINISH = 2,
+    STATE_IOS5_POLLING_FOR_LOAD_START = 3,
+    STATE_IOS5_POLLING_FOR_LOAD_FINISH = 4,
+    STATE_CANCELLED = 5
+} State;
+
+static NSString *stripFragment(NSString* url)
+{
+    NSRange r = [url rangeOfString:@"#"];
+
+    if (r.location == NSNotFound) {
+        return url;
+    }
+    return [url substringToIndex:r.location];
+}
+
+@implementation CDVUIWebViewDelegate
+
+- (id)initWithDelegate:(NSObject <UIWebViewDelegate>*)delegate
+{
+    self = [super init];
+    if (self != nil) {
+        _delegate = delegate;
+        _loadCount = -1;
+        _state = STATE_IDLE;
+    }
+    return self;
+}
+
+- (BOOL)request:(NSURLRequest*)newRequest isEqualToRequestAfterStrippingFragments:(NSURLRequest*)originalRequest
+{
+    if (originalRequest.URL && newRequest.URL) {
+        NSString* originalRequestUrl = [originalRequest.URL absoluteString];
+        NSString* newRequestUrl = [newRequest.URL absoluteString];
+
+        NSString* baseOriginalRequestUrl = stripFragment(originalRequestUrl);
+        NSString* baseNewRequestUrl = stripFragment(newRequestUrl);
+        return [baseOriginalRequestUrl isEqualToString:baseNewRequestUrl];
+    }
+
+    return NO;
+}
+
+- (BOOL)isPageLoaded:(UIWebView*)webView
+{
+    NSString* readyState = [webView stringByEvaluatingJavaScriptFromString:@"document.readyState"];
+
+    return [readyState isEqualToString:@"loaded"] || [readyState isEqualToString:@"complete"];
+}
+
+- (BOOL)isJsLoadTokenSet:(UIWebView*)webView
+{
+    NSString* loadToken = [webView stringByEvaluatingJavaScriptFromString:@"window.__cordovaLoadToken"];
+
+    return [[NSString stringWithFormat:@"%ld", (long)_curLoadToken] isEqualToString:loadToken];
+}
+
+- (void)setLoadToken:(UIWebView*)webView
+{
+    _curLoadToken += 1;
+    [webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"window.__cordovaLoadToken=%ld", (long)_curLoadToken]];
+}
+
+- (NSString*)evalForCurrentURL:(UIWebView*)webView
+{
+    return [webView stringByEvaluatingJavaScriptFromString:@"location.href"];
+}
+
+- (void)pollForPageLoadStart:(UIWebView*)webView
+{
+    if (_state != STATE_IOS5_POLLING_FOR_LOAD_START) {
+        return;
+    }
+    if (![self isJsLoadTokenSet:webView]) {
+        VerboseLog(@"Polled for page load start. result = YES!");
+        _state = STATE_IOS5_POLLING_FOR_LOAD_FINISH;
+        [self setLoadToken:webView];
+        if ([_delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
+            [_delegate webViewDidStartLoad:webView];
+        }
+        [self pollForPageLoadFinish:webView];
+    } else {
+        VerboseLog(@"Polled for page load start. result = NO");
+        // Poll only for 1 second, and then fall back on checking only when delegate methods are called.
+        ++_loadStartPollCount;
+        if (_loadStartPollCount < (1000 * .05)) {
+            [self performSelector:@selector(pollForPageLoadStart:) withObject:webView afterDelay:.05];
+        }
+    }
+}
+
+- (void)pollForPageLoadFinish:(UIWebView*)webView
+{
+    if (_state != STATE_IOS5_POLLING_FOR_LOAD_FINISH) {
+        return;
+    }
+    if ([self isPageLoaded:webView]) {
+        VerboseLog(@"Polled for page load finish. result = YES!");
+        _state = STATE_IDLE;
+        if ([_delegate respondsToSelector:@selector(webViewDidFinishLoad:)]) {
+            [_delegate webViewDidFinishLoad:webView];
+        }
+    } else {
+        VerboseLog(@"Polled for page load finish. result = NO");
+        [self performSelector:@selector(pollForPageLoadFinish:) withObject:webView afterDelay:.05];
+    }
+}
+
+- (BOOL)shouldLoadRequest:(NSURLRequest*)request
+{
+    NSString* scheme = [[request URL] scheme];
+
+    if ([scheme isEqualToString:@"mailto"] || [scheme isEqualToString:@"tel"]) {
+        return YES;
+    }
+
+    return [NSURLConnection canHandleRequest:request];
+}
+
+- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
+{
+    BOOL shouldLoad = YES;
+
+    if ([_delegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
+        shouldLoad = [_delegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
+    }
+
+    VerboseLog(@"webView shouldLoad=%d (before) state=%d loadCount=%d URL=%@", shouldLoad, _state, _loadCount, request.URL);
+
+    if (shouldLoad) {
+        // When devtools refresh occurs, it blindly uses the same request object. If a history.replaceState() has occured, then
+        // mainDocumentURL != URL even though it's a top-level navigation.
+        BOOL isDevToolsRefresh = (request == webView.request);
+        BOOL isTopLevelNavigation = isDevToolsRefresh || [request.URL isEqual:[request mainDocumentURL]];
+        if (isTopLevelNavigation) {
+            // Ignore hash changes that don't navigate to a different page.
+            // webView.request does actually update when history.replaceState() gets called.
+            if ([self request:request isEqualToRequestAfterStrippingFragments:webView.request]) {
+                NSString* prevURL = [self evalForCurrentURL:webView];
+                if ([prevURL isEqualToString:[request.URL absoluteString]]) {
+                    VerboseLog(@"Page reload detected.");
+                } else {
+                    VerboseLog(@"Detected hash change shouldLoad");
+                    return shouldLoad;
+                }
+            }
+
+            switch (_state) {
+                case STATE_WAITING_FOR_LOAD_FINISH:
+                    // Redirect case.
+                    // We expect loadCount == 1.
+                    if (_loadCount != 1) {
+                        NSLog(@"CDVWebViewDelegate: Detected redirect when loadCount=%ld", (long)_loadCount);
+                    }
+                    break;
+
+                case STATE_IDLE:
+                case STATE_IOS5_POLLING_FOR_LOAD_START:
+                case STATE_CANCELLED:
+                    // Page navigation start.
+                    _loadCount = 0;
+                    _state = STATE_WAITING_FOR_LOAD_START;
+                    break;
+
+                default:
+                    {
+                        _loadCount = 0;
+                        _state = STATE_WAITING_FOR_LOAD_START;
+                        NSString* description = [NSString stringWithFormat:@"CDVWebViewDelegate: Navigation started when state=%ld", (long)_state];
+                        NSLog(@"%@", description);
+                        if ([_delegate respondsToSelector:@selector(webView:didFailLoadWithError:)]) {
+                            NSDictionary* errorDictionary = @{NSLocalizedDescriptionKey : description};
+                            NSError* error = [[NSError alloc] initWithDomain:@"CDVUIWebViewDelegate" code:1 userInfo:errorDictionary];
+                            [_delegate webView:webView didFailLoadWithError:error];
+                        }
+                    }
+            }
+        } else {
+            // Deny invalid URLs so that we don't get the case where we go straight from
+            // webViewShouldLoad -> webViewDidFailLoad (messes up _loadCount).
+            shouldLoad = shouldLoad && [self shouldLoadRequest:request];
+        }
+        VerboseLog(@"webView shouldLoad=%d (after) isTopLevelNavigation=%d state=%d loadCount=%d", shouldLoad, isTopLevelNavigation, _state, _loadCount);
+    }
+    return shouldLoad;
+}
+
+- (void)webViewDidStartLoad:(UIWebView*)webView
+{
+    VerboseLog(@"webView didStartLoad (before). state=%d loadCount=%d", _state, _loadCount);
+    BOOL fireCallback = NO;
+    switch (_state) {
+        case STATE_IDLE:
+            break;
+
+        case STATE_CANCELLED:
+            fireCallback = YES;
+            _state = STATE_WAITING_FOR_LOAD_FINISH;
+            _loadCount += 1;
+            break;
+
+        case STATE_WAITING_FOR_LOAD_START:
+            if (_loadCount != 0) {
+                NSLog(@"CDVWebViewDelegate: Unexpected loadCount in didStart. count=%ld", (long)_loadCount);
+            }
+            fireCallback = YES;
+            _state = STATE_WAITING_FOR_LOAD_FINISH;
+            _loadCount = 1;
+            break;
+
+        case STATE_WAITING_FOR_LOAD_FINISH:
+            _loadCount += 1;
+            break;
+
+        case STATE_IOS5_POLLING_FOR_LOAD_START:
+            [self pollForPageLoadStart:webView];
+            break;
+
+        case STATE_IOS5_POLLING_FOR_LOAD_FINISH:
+            [self pollForPageLoadFinish:webView];
+            break;
+
+        default:
+            NSLog(@"CDVWebViewDelegate: Unexpected didStart with state=%ld loadCount=%ld", (long)_state, (long)_loadCount);
+    }
+    VerboseLog(@"webView didStartLoad (after). state=%d loadCount=%d fireCallback=%d", _state, _loadCount, fireCallback);
+    if (fireCallback && [_delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
+        [_delegate webViewDidStartLoad:webView];
+    }
+}
+
+- (void)webViewDidFinishLoad:(UIWebView*)webView
+{
+    VerboseLog(@"webView didFinishLoad (before). state=%d loadCount=%d", _state, _loadCount);
+    BOOL fireCallback = NO;
+    switch (_state) {
+        case STATE_IDLE:
+            break;
+
+        case STATE_WAITING_FOR_LOAD_START:
+            NSLog(@"CDVWebViewDelegate: Unexpected didFinish while waiting for load start.");
+            break;
+
+        case STATE_WAITING_FOR_LOAD_FINISH:
+            if (_loadCount == 1) {
+                fireCallback = YES;
+                _state = STATE_IDLE;
+            }
+            _loadCount -= 1;
+            break;
+
+        case STATE_IOS5_POLLING_FOR_LOAD_START:
+            [self pollForPageLoadStart:webView];
+            break;
+
+        case STATE_IOS5_POLLING_FOR_LOAD_FINISH:
+            [self pollForPageLoadFinish:webView];
+            break;
+    }
+    VerboseLog(@"webView didFinishLoad (after). state=%d loadCount=%d fireCallback=%d", _state, _loadCount, fireCallback);
+    if (fireCallback && [_delegate respondsToSelector:@selector(webViewDidFinishLoad:)]) {
+        [_delegate webViewDidFinishLoad:webView];
+    }
+}
+
+- (void)webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error
+{
+    VerboseLog(@"webView didFailLoad (before). state=%d loadCount=%d", _state, _loadCount);
+    BOOL fireCallback = NO;
+
+    switch (_state) {
+        case STATE_IDLE:
+            break;
+
+        case STATE_WAITING_FOR_LOAD_START:
+            if ([error code] == NSURLErrorCancelled) {
+                _state = STATE_CANCELLED;
+            } else {
+                _state = STATE_IDLE;
+            }
+            fireCallback = YES;
+            break;
+
+        case STATE_WAITING_FOR_LOAD_FINISH:
+            if ([error code] != NSURLErrorCancelled) {
+                if (_loadCount == 1) {
+                    _state = STATE_IDLE;
+                    fireCallback = YES;
+                }
+                _loadCount = -1;
+            } else {
+                fireCallback = YES;
+                _state = STATE_CANCELLED;
+                _loadCount -= 1;
+            }
+            break;
+
+        case STATE_IOS5_POLLING_FOR_LOAD_START:
+            [self pollForPageLoadStart:webView];
+            break;
+
+        case STATE_IOS5_POLLING_FOR_LOAD_FINISH:
+            [self pollForPageLoadFinish:webView];
+            break;
+    }
+    VerboseLog(@"webView didFailLoad (after). state=%d loadCount=%d, fireCallback=%d", _state, _loadCount, fireCallback);
+    if (fireCallback && [_delegate respondsToSelector:@selector(webView:didFailLoadWithError:)]) {
+        [_delegate webView:webView didFailLoadWithError:error];
+    }
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Private/Plugins/CDVUIWebViewEngine/CDVUIWebViewEngine.h
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Private/Plugins/CDVUIWebViewEngine/CDVUIWebViewEngine.h b/CordovaLib/Classes/Private/Plugins/CDVUIWebViewEngine/CDVUIWebViewEngine.h
new file mode 100644
index 0000000..6a9ee77
--- /dev/null
+++ b/CordovaLib/Classes/Private/Plugins/CDVUIWebViewEngine/CDVUIWebViewEngine.h
@@ -0,0 +1,27 @@
+/*
+ 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 "CDVPlugin.h"
+#import "CDVWebViewEngineProtocol.h"
+
+@interface CDVUIWebViewEngine : CDVPlugin <CDVWebViewEngineProtocol>
+
+@property (nonatomic, strong, readonly) id <UIWebViewDelegate> uiWebViewDelegate;
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Private/Plugins/CDVUIWebViewEngine/CDVUIWebViewEngine.m
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Private/Plugins/CDVUIWebViewEngine/CDVUIWebViewEngine.m b/CordovaLib/Classes/Private/Plugins/CDVUIWebViewEngine/CDVUIWebViewEngine.m
new file mode 100644
index 0000000..3e01b08
--- /dev/null
+++ b/CordovaLib/Classes/Private/Plugins/CDVUIWebViewEngine/CDVUIWebViewEngine.m
@@ -0,0 +1,186 @@
+/*
+ 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 "CDVUIWebViewEngine.h"
+#import "CDVUIWebViewDelegate.h"
+#import "NSDictionary+CordovaPreferences.h"
+
+#import <objc/message.h>
+
+@interface CDVUIWebViewEngine ()
+
+@property (nonatomic, strong, readwrite) UIView* engineWebView;
+@property (nonatomic, strong, readwrite) id <UIWebViewDelegate> uiWebViewDelegate;
+
+@end
+
+@implementation CDVUIWebViewEngine
+
+@synthesize engineWebView = _engineWebView;
+
+- (instancetype)initWithFrame:(CGRect)frame
+{
+    self = [super init];
+    if (self) {
+        self.engineWebView = [[UIWebView alloc] initWithFrame:frame];
+        NSLog(@"Using UIWebView");
+    }
+
+    return self;
+}
+
+- (void)pluginInitialize
+{
+    // viewController would be available now. we attempt to set all possible delegates to it, by default
+
+    UIWebView* uiWebView = (UIWebView*)_engineWebView;
+
+    if ([self.viewController conformsToProtocol:@protocol(UIWebViewDelegate)]) {
+        self.uiWebViewDelegate = [[CDVUIWebViewDelegate alloc] initWithDelegate:(id <UIWebViewDelegate>)self.viewController];
+        uiWebView.delegate = self.uiWebViewDelegate;
+    }
+
+    [self updateSettings:self.commandDelegate.settings];
+}
+
+- (void)evaluateJavaScript:(NSString*)javaScriptString completionHandler:(void (^)(id, NSError*))completionHandler
+{
+    NSString* ret = [(UIWebView*)_engineWebView stringByEvaluatingJavaScriptFromString:javaScriptString];
+
+    if (completionHandler) {
+        completionHandler(ret, nil);
+    }
+}
+
+- (id)loadRequest:(NSURLRequest*)request
+{
+    [(UIWebView*)_engineWebView loadRequest:request];
+    return nil;
+}
+
+- (id)loadHTMLString:(NSString*)string baseURL:(NSURL*)baseURL
+{
+    [(UIWebView*)_engineWebView loadHTMLString:string baseURL:baseURL];
+    return nil;
+}
+
+- (NSURL*)URL
+{
+    return [[(UIWebView*)_engineWebView request] URL];
+}
+
+- (void)updateSettings:(NSDictionary*)settings
+{
+    UIWebView* uiWebView = (UIWebView*)_engineWebView;
+
+    uiWebView.scalesPageToFit = [settings cordovaBoolSettingForKey:@"EnableViewportScale" defaultValue:NO];
+    uiWebView.allowsInlineMediaPlayback = [settings cordovaBoolSettingForKey:@"AllowInlineMediaPlayback" defaultValue:NO];
+    uiWebView.mediaPlaybackRequiresUserAction = [settings cordovaBoolSettingForKey:@"MediaPlaybackRequiresUserAction" defaultValue:YES];
+    uiWebView.mediaPlaybackAllowsAirPlay = [settings cordovaBoolSettingForKey:@"MediaPlaybackAllowsAirPlay" defaultValue:YES];
+    uiWebView.keyboardDisplayRequiresUserAction = [settings cordovaBoolSettingForKey:@"KeyboardDisplayRequiresUserAction" defaultValue:YES];
+    uiWebView.suppressesIncrementalRendering = [settings cordovaBoolSettingForKey:@"SuppressesIncrementalRendering" defaultValue:NO];
+    uiWebView.gapBetweenPages = [settings cordovaFloatSettingForKey:@"GapBetweenPages" defaultValue:0.0];
+    uiWebView.pageLength = [settings cordovaFloatSettingForKey:@"PageLength" defaultValue:0.0];
+
+    id prefObj = nil;
+
+    // By default, DisallowOverscroll is false (thus bounce is allowed)
+    BOOL bounceAllowed = !([settings cordovaBoolSettingForKey:@"DisallowOverscroll" defaultValue:NO]);
+
+    // prevent webView from bouncing
+    if (!bounceAllowed) {
+        if ([self.webView respondsToSelector:@selector(scrollView)]) {
+            ((UIScrollView*)[uiWebView scrollView]).bounces = NO;
+        } else {
+            for (id subview in self.webView.subviews) {
+                if ([[subview class] isSubclassOfClass:[UIScrollView class]]) {
+                    ((UIScrollView*)subview).bounces = NO;
+                }
+            }
+        }
+    }
+
+    NSString* decelerationSetting = [settings cordovaSettingForKey:@"UIWebViewDecelerationSpeed"];
+    if (![@"fast" isEqualToString:decelerationSetting]) {
+        [uiWebView.scrollView setDecelerationRate:UIScrollViewDecelerationRateNormal];
+    }
+
+    NSInteger paginationBreakingMode = 0; // default - UIWebPaginationBreakingModePage
+    prefObj = [settings cordovaSettingForKey:@"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;
+        }
+    }
+    uiWebView.paginationBreakingMode = paginationBreakingMode;
+
+    NSInteger paginationMode = 0; // default - UIWebPaginationModeUnpaginated
+    prefObj = [settings cordovaSettingForKey:@"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;
+        }
+    }
+    uiWebView.paginationMode = paginationMode;
+}
+
+- (void)updateWithInfo:(NSDictionary*)info
+{
+    UIWebView* uiWebView = (UIWebView*)_engineWebView;
+
+    id <UIWebViewDelegate> uiWebViewDelegate = [info objectForKey:kCDVWebViewEngineUIWebViewDelegate];
+    NSDictionary* settings = [info objectForKey:kCDVWebViewEngineWebViewPreferences];
+
+    if (uiWebViewDelegate &&
+        [uiWebViewDelegate conformsToProtocol:@protocol(UIWebViewDelegate)]) {
+        self.uiWebViewDelegate = [[CDVUIWebViewDelegate alloc] initWithDelegate:(id <UIWebViewDelegate>)self.viewController];
+        uiWebView.delegate = self.uiWebViewDelegate;
+    }
+
+    if (settings && [settings isKindOfClass:[NSDictionary class]]) {
+        [self updateSettings:settings];
+    }
+}
+
+// This forwards the methods that are in the header that are not implemented here.
+// Both WKWebView and UIWebView implement the below:
+//     loadHTMLString:baseURL:
+//     loadRequest:
+- (id)forwardingTargetForSelector:(SEL)aSelector
+{
+    return _engineWebView;
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/CDV.h
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/CDV.h b/CordovaLib/Classes/Public/CDV.h
new file mode 100644
index 0000000..fbeaea2
--- /dev/null
+++ b/CordovaLib/Classes/Public/CDV.h
@@ -0,0 +1,36 @@
+/*
+ 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 "CDVAvailability.h"
+#import "CDVAvailabilityDeprecated.h"
+#import "CDVAppDelegate.h"
+#import "CDVPlugin.h"
+#import "CDVPluginResult.h"
+#import "CDVViewController.h"
+#import "CDVCommandDelegate.h"
+#import "CDVURLProtocol.h"
+#import "CDVInvokedUrlCommand.h"
+#import "CDVWhitelist.h"
+#import "CDVLocalStorage.h"
+#import "CDVPlugin.h"
+#import "CDVPluginResult.h"
+#import "CDVScreenOrientationDelegate.h"
+#import "CDVTimer.h"
+#import "CDVUserAgentUtil.h"
+#import "NSData+Base64.h"

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/CDVAppDelegate.h
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/CDVAppDelegate.h b/CordovaLib/Classes/Public/CDVAppDelegate.h
new file mode 100644
index 0000000..de5b518
--- /dev/null
+++ b/CordovaLib/Classes/Public/CDVAppDelegate.h
@@ -0,0 +1,28 @@
+/*
+ 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 <Foundation/Foundation.h>
+#import "CDVViewController.h"
+
+@interface CDVAppDelegate : NSObject <UIApplicationDelegate>{}
+
+@property (nonatomic, strong) IBOutlet UIWindow* window;
+@property (nonatomic, strong) IBOutlet CDVViewController* viewController;
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/CDVAppDelegate.m
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/CDVAppDelegate.m b/CordovaLib/Classes/Public/CDVAppDelegate.m
new file mode 100644
index 0000000..52f5837
--- /dev/null
+++ b/CordovaLib/Classes/Public/CDVAppDelegate.m
@@ -0,0 +1,101 @@
+/*
+ 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 "CDVAppDelegate.h"
+
+@implementation CDVAppDelegate
+
+@synthesize window, viewController;
+
+- (id)init
+{
+    /** If you need to do any extra app-specific initialization, you can do it here
+     *  -jm
+     **/
+    NSHTTPCookieStorage* cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+
+    [cookieStorage setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
+
+    int cacheSizeMemory = 8 * 1024 * 1024; // 8MB
+    int cacheSizeDisk = 32 * 1024 * 1024; // 32MB
+    NSURLCache* sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:cacheSizeMemory diskCapacity:cacheSizeDisk diskPath:@"nsurlcache"];
+    [NSURLCache setSharedURLCache:sharedCache];
+
+    self = [super init];
+    return self;
+}
+
+#pragma mark UIApplicationDelegate implementation
+
+/**
+ * This is main kick off after the app inits, the views and Settings are setup here. (preferred - iOS4 and up)
+ */
+- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
+{
+    CGRect screenBounds = [[UIScreen mainScreen] bounds];
+
+    self.window = [[UIWindow alloc] initWithFrame:screenBounds];
+    self.window.autoresizesSubviews = YES;
+
+    // only set if not already set in subclass
+    if (self.viewController == nil) {
+        self.viewController = [[CDVViewController alloc] init];
+    }
+
+    // Set your app's start page by setting the <content src='foo.html' /> tag in config.xml.
+    // If necessary, uncomment the line below to override it.
+    // self.viewController.startPage = @"index.html";
+
+    // NOTE: To customize the view's frame size (which defaults to full screen), override
+    // [self.viewController viewWillAppear:] in your view controller.
+
+    self.window.rootViewController = self.viewController;
+    [self.window makeKeyAndVisible];
+
+    return YES;
+}
+
+// this happens while we are running ( in the background, or from within our own app )
+// only valid if 40x-Info.plist specifies a protocol to handle
+- (BOOL)application:(UIApplication*)application openURL:(NSURL*)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation
+{
+    if (!url) {
+        return NO;
+    }
+
+    // all plugins will get the notification, and their handlers will be called
+    [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]];
+
+    return YES;
+}
+
+- (NSUInteger)application:(UIApplication*)application supportedInterfaceOrientationsForWindow:(UIWindow*)window
+{
+    // iPhone doesn't support upside down by default, while the iPad does.  Override to allow all orientations always, and let the root view controller decide what's allowed (the supported orientations mask gets intersected).
+    NSUInteger supportedInterfaceOrientations = (1 << UIInterfaceOrientationPortrait) | (1 << UIInterfaceOrientationLandscapeLeft) | (1 << UIInterfaceOrientationLandscapeRight) | (1 << UIInterfaceOrientationPortraitUpsideDown);
+
+    return supportedInterfaceOrientations;
+}
+
+- (void)applicationDidReceiveMemoryWarning:(UIApplication*)application
+{
+    [[NSURLCache sharedURLCache] removeAllCachedResponses];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/CDVAvailability.h
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/CDVAvailability.h b/CordovaLib/Classes/Public/CDVAvailability.h
new file mode 100644
index 0000000..40d5489
--- /dev/null
+++ b/CordovaLib/Classes/Public/CDVAvailability.h
@@ -0,0 +1,96 @@
+/*
+ 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 "CDVAvailabilityDeprecated.h"
+
+#define __CORDOVA_IOS__
+
+#define __CORDOVA_0_9_6 906
+#define __CORDOVA_1_0_0 10000
+#define __CORDOVA_1_1_0 10100
+#define __CORDOVA_1_2_0 10200
+#define __CORDOVA_1_3_0 10300
+#define __CORDOVA_1_4_0 10400
+#define __CORDOVA_1_4_1 10401
+#define __CORDOVA_1_5_0 10500
+#define __CORDOVA_1_6_0 10600
+#define __CORDOVA_1_6_1 10601
+#define __CORDOVA_1_7_0 10700
+#define __CORDOVA_1_8_0 10800
+#define __CORDOVA_1_8_1 10801
+#define __CORDOVA_1_9_0 10900
+#define __CORDOVA_2_0_0 20000
+#define __CORDOVA_2_1_0 20100
+#define __CORDOVA_2_2_0 20200
+#define __CORDOVA_2_3_0 20300
+#define __CORDOVA_2_4_0 20400
+#define __CORDOVA_2_5_0 20500
+#define __CORDOVA_2_6_0 20600
+#define __CORDOVA_2_7_0 20700
+#define __CORDOVA_2_8_0 20800
+#define __CORDOVA_2_9_0 20900
+#define __CORDOVA_3_0_0 30000
+#define __CORDOVA_3_1_0 30100
+#define __CORDOVA_3_2_0 30200
+#define __CORDOVA_3_3_0 30300
+#define __CORDOVA_3_4_0 30400
+#define __CORDOVA_3_4_1 30401
+#define __CORDOVA_3_5_0 30500
+#define __CORDOVA_3_6_0 30600
+#define __CORDOVA_3_7_0 30700
+#define __CORDOVA_3_8_0 30800
+#define __CORDOVA_4_0_0 40000
+/* coho:next-version,insert-before */
+#define __CORDOVA_NA 99999      /* not available */
+
+/*
+ #if CORDOVA_VERSION_MIN_REQUIRED >= __CORDOVA_4_0_0
+    // do something when its at least 4.0.0
+ #else
+    // do something else (non 4.0.0)
+ #endif
+ */
+#ifndef CORDOVA_VERSION_MIN_REQUIRED
+    /* coho:next-version-min-required,replace-after */
+    #define CORDOVA_VERSION_MIN_REQUIRED __CORDOVA_4_0_0
+#endif
+
+/*
+ Returns YES if it is at least version specified as NSString(X)
+ Usage:
+     if (IsAtLeastiOSVersion(@"5.1")) {
+         // do something for iOS 5.1 or greater
+     }
+ */
+#define IsAtLeastiOSVersion(X) ([[[UIDevice currentDevice] systemVersion] compare:X options:NSNumericSearch] != NSOrderedAscending)
+
+/* Return the string version of the decimal version */
+#define CDV_VERSION [NSString stringWithFormat:@"%d.%d.%d", \
+    (CORDOVA_VERSION_MIN_REQUIRED / 10000),                 \
+    (CORDOVA_VERSION_MIN_REQUIRED % 10000) / 100,           \
+    (CORDOVA_VERSION_MIN_REQUIRED % 10000) % 100]
+
+// Enable this to log all exec() calls.
+#define CDV_ENABLE_EXEC_LOGGING 0
+#if CDV_ENABLE_EXEC_LOGGING
+    #define CDV_EXEC_LOG NSLog
+#else
+    #define CDV_EXEC_LOG(...) do { \
+} while (NO)
+#endif

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/CDVAvailabilityDeprecated.h
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/CDVAvailabilityDeprecated.h b/CordovaLib/Classes/Public/CDVAvailabilityDeprecated.h
new file mode 100644
index 0000000..abf7a16
--- /dev/null
+++ b/CordovaLib/Classes/Public/CDVAvailabilityDeprecated.h
@@ -0,0 +1,26 @@
+/*
+ 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 <UIKit/UIKit.h>
+
+#ifdef __clang__
+    #define CDV_DEPRECATED(version, msg) __attribute__((deprecated("Deprecated in Cordova " #version ". " msg)))
+#else
+    #define CDV_DEPRECATED(version, msg) __attribute__((deprecated()))
+#endif

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/CDVCommandDelegate.h
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/CDVCommandDelegate.h b/CordovaLib/Classes/Public/CDVCommandDelegate.h
new file mode 100644
index 0000000..926278a
--- /dev/null
+++ b/CordovaLib/Classes/Public/CDVCommandDelegate.h
@@ -0,0 +1,53 @@
+/*
+ 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 "CDVAvailability.h"
+#import "CDVInvokedUrlCommand.h"
+
+@class CDVPlugin;
+@class CDVPluginResult;
+@class CDVWhitelist;
+
+typedef NSURL* (^ UrlTransformerBlock)(NSURL*);
+
+@protocol CDVCommandDelegate <NSObject>
+
+@property (nonatomic, readonly) NSDictionary* settings;
+@property (nonatomic, copy) UrlTransformerBlock urlTransformer;
+
+- (NSString*)pathForResource:(NSString*)resourcepath;
+- (id)getCommandInstance:(NSString*)pluginName;
+
+// Sends a plugin result to the JS. This is thread-safe.
+- (void)sendPluginResult:(CDVPluginResult*)result callbackId:(NSString*)callbackId;
+// Evaluates the given JS. This is thread-safe.
+- (void)evalJs:(NSString*)js;
+// Can be used to evaluate JS right away instead of scheduling it on the run-loop.
+// This is required for dispatch resign and pause events, but should not be used
+// without reason. Without the run-loop delay, alerts used in JS callbacks may result
+// in dead-lock. This method must be called from the UI thread.
+- (void)evalJs:(NSString*)js scheduledOnRunLoop:(BOOL)scheduledOnRunLoop;
+// Runs the given block on a background thread using a shared thread-pool.
+- (void)runInBackground:(void (^)())block;
+// Returns the User-Agent of the associated UIWebView.
+- (NSString*)userAgent;
+// Returns whether the given URL passes the white-list.
+- (BOOL)URLIsWhitelisted:(NSURL*)url;
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/CDVCommandDelegateImpl.h
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/CDVCommandDelegateImpl.h b/CordovaLib/Classes/Public/CDVCommandDelegateImpl.h
new file mode 100644
index 0000000..0531134
--- /dev/null
+++ b/CordovaLib/Classes/Public/CDVCommandDelegateImpl.h
@@ -0,0 +1,36 @@
+/*
+ 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 <UIKit/UIKit.h>
+#import "CDVCommandDelegate.h"
+
+@class CDVViewController;
+@class CDVCommandQueue;
+
+@interface CDVCommandDelegateImpl : NSObject <CDVCommandDelegate>{
+    @private
+    __weak CDVViewController* _viewController;
+    NSRegularExpression* _callbackIdPattern;
+    @protected
+    __weak CDVCommandQueue* _commandQueue;
+    BOOL _delayResponses;
+}
+- (id)initWithViewController:(CDVViewController*)viewController;
+- (void)flushCommandQueueWithDelayedJs;
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/CDVCommandDelegateImpl.m
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/CDVCommandDelegateImpl.m b/CordovaLib/Classes/Public/CDVCommandDelegateImpl.m
new file mode 100644
index 0000000..d4df6c3
--- /dev/null
+++ b/CordovaLib/Classes/Public/CDVCommandDelegateImpl.m
@@ -0,0 +1,187 @@
+/*
+ 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 "CDVCommandDelegateImpl.h"
+#import "CDVJSON_private.h"
+#import "CDVCommandQueue.h"
+#import "CDVPluginResult.h"
+#import "CDVViewController.h"
+
+@implementation CDVCommandDelegateImpl
+
+@synthesize urlTransformer;
+
+- (id)initWithViewController:(CDVViewController*)viewController
+{
+    self = [super init];
+    if (self != nil) {
+        _viewController = viewController;
+        _commandQueue = _viewController.commandQueue;
+
+        NSError* err = nil;
+        _callbackIdPattern = [NSRegularExpression regularExpressionWithPattern:@"[^A-Za-z0-9._-]" options:0 error:&err];
+        if (err != nil) {
+            // Couldn't initialize Regex
+            NSLog(@"Error: Couldn't initialize regex");
+            _callbackIdPattern = nil;
+        }
+    }
+    return self;
+}
+
+- (NSString*)pathForResource:(NSString*)resourcepath
+{
+    NSBundle* mainBundle = [NSBundle mainBundle];
+    NSMutableArray* directoryParts = [NSMutableArray arrayWithArray:[resourcepath componentsSeparatedByString:@"/"]];
+    NSString* filename = [directoryParts lastObject];
+
+    [directoryParts removeLastObject];
+
+    NSString* directoryPartsJoined = [directoryParts componentsJoinedByString:@"/"];
+    NSString* directoryStr = _viewController.wwwFolderName;
+
+    if ([directoryPartsJoined length] > 0) {
+        directoryStr = [NSString stringWithFormat:@"%@/%@", _viewController.wwwFolderName, [directoryParts componentsJoinedByString:@"/"]];
+    }
+
+    return [mainBundle pathForResource:filename ofType:@"" inDirectory:directoryStr];
+}
+
+- (void)flushCommandQueueWithDelayedJs
+{
+    _delayResponses = YES;
+    [_commandQueue executePending];
+    _delayResponses = NO;
+}
+
+- (void)evalJsHelper2:(NSString*)js
+{
+    CDV_EXEC_LOG(@"Exec: evalling: %@", [js substringToIndex:MIN([js length], 160)]);
+    [_viewController.webViewEngine evaluateJavaScript:js completionHandler:^(id obj, NSError* error) {
+        // TODO: obj can be something other than string
+        if ([obj isKindOfClass:[NSString class]]) {
+            NSString* commandsJSON = (NSString*)obj;
+            if ([commandsJSON length] > 0) {
+                CDV_EXEC_LOG(@"Exec: Retrieved new exec messages by chaining.");
+            }
+
+            [_commandQueue enqueueCommandBatch:commandsJSON];
+            [_commandQueue executePending];
+        }
+    }];
+}
+
+- (void)evalJsHelper:(NSString*)js
+{
+    // Cycle the run-loop before executing the JS.
+    // For _delayResponses -
+    //    This ensures that we don't eval JS during the middle of an existing JS
+    //    function (possible since UIWebViewDelegate callbacks can be synchronous).
+    // For !isMainThread -
+    //    It's a hard error to eval on the non-UI thread.
+    // For !_commandQueue.currentlyExecuting -
+    //     This works around a bug where sometimes alerts() within callbacks can cause
+    //     dead-lock.
+    //     If the commandQueue is currently executing, then we know that it is safe to
+    //     execute the callback immediately.
+    // Using    (dispatch_get_main_queue()) does *not* fix deadlocks for some reason,
+    // but performSelectorOnMainThread: does.
+    if (_delayResponses || ![NSThread isMainThread] || !_commandQueue.currentlyExecuting) {
+        [self performSelectorOnMainThread:@selector(evalJsHelper2:) withObject:js waitUntilDone:NO];
+    } else {
+        [self evalJsHelper2:js];
+    }
+}
+
+- (BOOL)isValidCallbackId:(NSString*)callbackId
+{
+    if ((callbackId == nil) || (_callbackIdPattern == nil)) {
+        return NO;
+    }
+
+    // Disallow if too long or if any invalid characters were found.
+    if (([callbackId length] > 100) || [_callbackIdPattern firstMatchInString:callbackId options:0 range:NSMakeRange(0, [callbackId length])]) {
+        return NO;
+    }
+    return YES;
+}
+
+- (void)sendPluginResult:(CDVPluginResult*)result callbackId:(NSString*)callbackId
+{
+    CDV_EXEC_LOG(@"Exec(%@): Sending result. Status=%@", callbackId, result.status);
+    // This occurs when there is are no win/fail callbacks for the call.
+    if ([@"INVALID" isEqualToString:callbackId]) {
+        return;
+    }
+    // This occurs when the callback id is malformed.
+    if (![self isValidCallbackId:callbackId]) {
+        NSLog(@"Invalid callback id received by sendPluginResult");
+        return;
+    }
+    int status = [result.status intValue];
+    BOOL keepCallback = [result.keepCallback boolValue];
+    NSString* argumentsAsJSON = [result argumentsAsJSON];
+
+    NSString* js = [NSString stringWithFormat:@"cordova.require('cordova/exec').nativeCallback('%@',%d,%@,%d)", callbackId, status, argumentsAsJSON, keepCallback];
+
+    [self evalJsHelper:js];
+}
+
+- (void)evalJs:(NSString*)js
+{
+    [self evalJs:js scheduledOnRunLoop:YES];
+}
+
+- (void)evalJs:(NSString*)js scheduledOnRunLoop:(BOOL)scheduledOnRunLoop
+{
+    js = [NSString stringWithFormat:@"cordova.require('cordova/exec').nativeEvalAndFetch(function(){%@})", js];
+    if (scheduledOnRunLoop) {
+        [self evalJsHelper:js];
+    } else {
+        [self evalJsHelper2:js];
+    }
+}
+
+- (id)getCommandInstance:(NSString*)pluginName
+{
+    return [_viewController getCommandInstance:pluginName];
+}
+
+- (void)runInBackground:(void (^)())block
+{
+    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block);
+}
+
+- (NSString*)userAgent
+{
+    return [_viewController userAgent];
+}
+
+- (BOOL)URLIsWhitelisted:(NSURL*)url
+{
+    return ![_viewController.whitelist schemeIsAllowed:[url scheme]] ||
+           [_viewController.whitelist URLIsAllowed:url logFailure:NO];
+}
+
+- (NSDictionary*)settings
+{
+    return _viewController.settings;
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/CDVCommandQueue.h
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/CDVCommandQueue.h b/CordovaLib/Classes/Public/CDVCommandQueue.h
new file mode 100644
index 0000000..3329078
--- /dev/null
+++ b/CordovaLib/Classes/Public/CDVCommandQueue.h
@@ -0,0 +1,40 @@
+/*
+ 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 <Foundation/Foundation.h>
+
+@class CDVInvokedUrlCommand;
+@class CDVViewController;
+
+@interface CDVCommandQueue : NSObject
+
+@property (nonatomic, readonly) BOOL currentlyExecuting;
+
+- (id)initWithViewController:(CDVViewController*)viewController;
+- (void)dispose;
+
+- (void)resetRequestId;
+- (void)enqueueCommandBatch:(NSString*)batchJSON;
+
+- (void)processXhrExecBridgePoke:(NSNumber*)requestId;
+- (void)fetchCommandsFromJs;
+- (void)executePending;
+- (BOOL)execute:(CDVInvokedUrlCommand*)command;
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/CDVCommandQueue.m
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/CDVCommandQueue.m b/CordovaLib/Classes/Public/CDVCommandQueue.m
new file mode 100644
index 0000000..d2f71cb
--- /dev/null
+++ b/CordovaLib/Classes/Public/CDVCommandQueue.m
@@ -0,0 +1,218 @@
+/*
+ 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.
+ */
+
+#include <objc/message.h>
+#import "CDVCommandQueue.h"
+#import "CDVViewController.h"
+#import "CDVCommandDelegateImpl.h"
+#import "CDVJSON_private.h"
+#import "CDVDebug.h"
+
+// Parse JS on the main thread if it's shorter than this.
+static const NSInteger JSON_SIZE_FOR_MAIN_THREAD = 4 * 1024; // Chosen arbitrarily.
+// Execute multiple commands in one go until this many seconds have passed.
+static const double MAX_EXECUTION_TIME = .008; // Half of a 60fps frame.
+
+@interface CDVCommandQueue () {
+    NSInteger _lastCommandQueueFlushRequestId;
+    __weak CDVViewController* _viewController;
+    NSMutableArray* _queue;
+    NSTimeInterval _startExecutionTime;
+}
+@end
+
+@implementation CDVCommandQueue
+
+- (BOOL)currentlyExecuting
+{
+    return _startExecutionTime > 0;
+}
+
+- (id)initWithViewController:(CDVViewController*)viewController
+{
+    self = [super init];
+    if (self != nil) {
+        _viewController = viewController;
+        _queue = [[NSMutableArray alloc] init];
+    }
+    return self;
+}
+
+- (void)dispose
+{
+    // TODO(agrieve): Make this a zeroing weak ref once we drop support for 4.3.
+    _viewController = nil;
+}
+
+- (void)resetRequestId
+{
+    _lastCommandQueueFlushRequestId = 0;
+}
+
+- (void)enqueueCommandBatch:(NSString*)batchJSON
+{
+    if ([batchJSON length] > 0) {
+        NSMutableArray* commandBatchHolder = [[NSMutableArray alloc] init];
+        [_queue addObject:commandBatchHolder];
+        if ([batchJSON length] < JSON_SIZE_FOR_MAIN_THREAD) {
+            [commandBatchHolder addObject:[batchJSON cdv_JSONObject]];
+        } else {
+            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^() {
+                NSMutableArray* result = [batchJSON cdv_JSONObject];
+                @synchronized(commandBatchHolder) {
+                    [commandBatchHolder addObject:result];
+                }
+                [self performSelectorOnMainThread:@selector(executePending) withObject:nil waitUntilDone:NO];
+            });
+        }
+    }
+}
+
+- (void)processXhrExecBridgePoke:(NSNumber*)requestId
+{
+    NSInteger rid = [requestId integerValue];
+
+    // An ID of 1 is a special case because that signifies the first request of
+    // the page. Since resetRequestId is called from webViewDidStartLoad, and the
+    // JS context at the time of webViewDidStartLoad is still that of the previous
+    // page, it's possible for requests from the previous page to come in after this
+    // point. We ignore these by enforcing that ID=1 be the first ID.
+    if ((_lastCommandQueueFlushRequestId == 0) && (rid != 1)) {
+        CDV_EXEC_LOG(@"Exec: Ignoring exec request from previous page.");
+        return;
+    }
+
+    // Use the request ID to determine if we've already flushed for this request.
+    // This is required only because the NSURLProtocol enqueues the same request
+    // multiple times.
+    if (rid > _lastCommandQueueFlushRequestId) {
+        _lastCommandQueueFlushRequestId = [requestId integerValue];
+        [self fetchCommandsFromJs];
+        [self executePending];
+    }
+}
+
+- (void)fetchCommandsFromJs
+{
+    __weak CDVCommandQueue* weakSelf = self;
+    NSString* js = @"cordova.require('cordova/exec').nativeFetchMessages()";
+
+    [_viewController.webViewEngine evaluateJavaScript:js
+                                    completionHandler:^(id obj, NSError* error) {
+        if ((error == nil) && [obj isKindOfClass:[NSString class]]) {
+            NSString* queuedCommandsJSON = (NSString*)obj;
+            CDV_EXEC_LOG(@"Exec: Flushed JS->native queue (hadCommands=%d).", [queuedCommandsJSON length] > 0);
+            [weakSelf enqueueCommandBatch:queuedCommandsJSON];
+            // this has to be called here now, because fetchCommandsFromJs is now async (previously: synchronous)
+            [self executePending];
+        }
+    }];
+}
+
+- (void)executePending
+{
+    // Make us re-entrant-safe.
+    if (_startExecutionTime > 0) {
+        return;
+    }
+    @try {
+        _startExecutionTime = [NSDate timeIntervalSinceReferenceDate];
+
+        while ([_queue count] > 0) {
+            NSMutableArray* commandBatchHolder = _queue[0];
+            NSMutableArray* commandBatch = nil;
+            @synchronized(commandBatchHolder) {
+                // If the next-up command is still being decoded, wait for it.
+                if ([commandBatchHolder count] == 0) {
+                    break;
+                }
+                commandBatch = commandBatchHolder[0];
+            }
+
+            while ([commandBatch count] > 0) {
+                @autoreleasepool {
+                    // Execute the commands one-at-a-time.
+                    NSArray* jsonEntry = [commandBatch cdv_dequeue];
+                    if ([commandBatch count] == 0) {
+                        [_queue removeObjectAtIndex:0];
+                    }
+                    CDVInvokedUrlCommand* command = [CDVInvokedUrlCommand commandFromJson:jsonEntry];
+                    CDV_EXEC_LOG(@"Exec(%@): Calling %@.%@", command.callbackId, command.className, command.methodName);
+
+                    if (![self execute:command]) {
+#ifdef DEBUG
+                            NSString* commandJson = [jsonEntry cdv_JSONString];
+                            static NSUInteger maxLogLength = 1024;
+                            NSString* commandString = ([commandJson length] > maxLogLength) ?
+                                [NSString stringWithFormat : @"%@[...]", [commandJson substringToIndex:maxLogLength]] :
+                                commandJson;
+
+                            DLog(@"FAILED pluginJSON = %@", commandString);
+#endif
+                    }
+                }
+
+                // Yield if we're taking too long.
+                if (([_queue count] > 0) && ([NSDate timeIntervalSinceReferenceDate] - _startExecutionTime > MAX_EXECUTION_TIME)) {
+                    [self performSelector:@selector(executePending) withObject:nil afterDelay:0];
+                    return;
+                }
+            }
+        }
+    } @finally
+    {
+        _startExecutionTime = 0;
+    }
+}
+
+- (BOOL)execute:(CDVInvokedUrlCommand*)command
+{
+    if ((command.className == nil) || (command.methodName == nil)) {
+        NSLog(@"ERROR: Classname and/or methodName not found for command.");
+        return NO;
+    }
+
+    // Fetch an instance of this class
+    CDVPlugin* obj = [_viewController.commandDelegate getCommandInstance:command.className];
+
+    if (!([obj isKindOfClass:[CDVPlugin class]])) {
+        NSLog(@"ERROR: Plugin '%@' not found, or is not a CDVPlugin. Check your plugin mapping in config.xml.", command.className);
+        return NO;
+    }
+    BOOL retVal = YES;
+    double started = [[NSDate date] timeIntervalSince1970] * 1000.0;
+    // Find the proper selector to call.
+    NSString* methodName = [NSString stringWithFormat:@"%@:", command.methodName];
+    SEL normalSelector = NSSelectorFromString(methodName);
+    if ([obj respondsToSelector:normalSelector]) {
+        // [obj performSelector:normalSelector withObject:command];
+        ((void (*)(id, SEL, id))objc_msgSend)(obj, normalSelector, command);
+    } else {
+        // There's no method to call, so throw an error.
+        NSLog(@"ERROR: Method '%@' not defined in Plugin '%@'", methodName, command.className);
+        retVal = NO;
+    }
+    double elapsed = [[NSDate date] timeIntervalSince1970] * 1000.0 - started;
+    if (elapsed > 10) {
+        NSLog(@"THREAD WARNING: ['%@'] took '%f' ms. Plugin should use a background thread.", command.className, elapsed);
+    }
+    return retVal;
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/CDVConfigParser.h
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/CDVConfigParser.h b/CordovaLib/Classes/Public/CDVConfigParser.h
new file mode 100644
index 0000000..2e06c88
--- /dev/null
+++ b/CordovaLib/Classes/Public/CDVConfigParser.h
@@ -0,0 +1,31 @@
+/*
+ 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.
+ */
+
+@interface CDVConfigParser : NSObject <NSXMLParserDelegate>
+{
+    NSString* featureName;
+}
+
+@property (nonatomic, readonly, strong) NSMutableDictionary* pluginsDict;
+@property (nonatomic, readonly, strong) NSMutableDictionary* settings;
+@property (nonatomic, readonly, strong) NSMutableArray* whitelistHosts;
+@property (nonatomic, readonly, strong) NSMutableArray* startupPluginNames;
+@property (nonatomic, readonly, strong) NSString* startPage;
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/CDVConfigParser.m
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/CDVConfigParser.m b/CordovaLib/Classes/Public/CDVConfigParser.m
new file mode 100644
index 0000000..4b73b60
--- /dev/null
+++ b/CordovaLib/Classes/Public/CDVConfigParser.m
@@ -0,0 +1,88 @@
+/*
+ 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 "CDVConfigParser.h"
+
+@interface CDVConfigParser ()
+
+@property (nonatomic, readwrite, strong) NSMutableDictionary* pluginsDict;
+@property (nonatomic, readwrite, strong) NSMutableDictionary* settings;
+@property (nonatomic, readwrite, strong) NSMutableArray* whitelistHosts;
+@property (nonatomic, readwrite, strong) NSMutableArray* startupPluginNames;
+@property (nonatomic, readwrite, strong) NSString* startPage;
+
+@end
+
+@implementation CDVConfigParser
+
+@synthesize pluginsDict, settings, whitelistHosts, startPage, startupPluginNames;
+
+- (id)init
+{
+    self = [super init];
+    if (self != nil) {
+        self.pluginsDict = [[NSMutableDictionary alloc] initWithCapacity:30];
+        self.settings = [[NSMutableDictionary alloc] initWithCapacity:30];
+        self.whitelistHosts = [[NSMutableArray alloc] initWithCapacity:30];
+        [self.whitelistHosts addObject:@"file:///*"];
+        [self.whitelistHosts addObject:@"content:///*"];
+        [self.whitelistHosts addObject:@"data:///*"];
+        self.startupPluginNames = [[NSMutableArray alloc] initWithCapacity:8];
+        featureName = nil;
+    }
+    return self;
+}
+
+- (void)parser:(NSXMLParser*)parser didStartElement:(NSString*)elementName namespaceURI:(NSString*)namespaceURI qualifiedName:(NSString*)qualifiedName attributes:(NSDictionary*)attributeDict
+{
+    if ([elementName isEqualToString:@"preference"]) {
+        settings[[attributeDict[@"name"] lowercaseString]] = attributeDict[@"value"];
+    } else if ([elementName isEqualToString:@"feature"]) { // store feature name to use with correct parameter set
+        featureName = [attributeDict[@"name"] lowercaseString];
+    } else if ((featureName != nil) && [elementName isEqualToString:@"param"]) {
+        NSString* paramName = [attributeDict[@"name"] lowercaseString];
+        id value = attributeDict[@"value"];
+        if ([paramName isEqualToString:@"ios-package"]) {
+            pluginsDict[featureName] = value;
+        }
+        BOOL paramIsOnload = ([paramName isEqualToString:@"onload"] && [@"true" isEqualToString : value]);
+        BOOL attribIsOnload = [@"true" isEqualToString :[attributeDict[@"onload"] lowercaseString]];
+        if (paramIsOnload || attribIsOnload) {
+            [self.startupPluginNames addObject:featureName];
+        }
+    } else if ([elementName isEqualToString:@"access"]) {
+        [whitelistHosts addObject:attributeDict[@"origin"]];
+    } else if ([elementName isEqualToString:@"content"]) {
+        self.startPage = attributeDict[@"src"];
+    }
+}
+
+- (void)parser:(NSXMLParser*)parser didEndElement:(NSString*)elementName namespaceURI:(NSString*)namespaceURI qualifiedName:(NSString*)qualifiedName
+{
+    if ([elementName isEqualToString:@"feature"]) { // no longer handling a feature so release
+        featureName = nil;
+    }
+}
+
+- (void)parser:(NSXMLParser*)parser parseErrorOccurred:(NSError*)parseError
+{
+    NSAssert(NO, @"config.xml parse error line %ld col %ld", (long)[parser lineNumber], (long)[parser columnNumber]);
+}
+
+@end


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


Mime
View raw message