You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by fi...@apache.org on 2013/02/06 20:06:16 UTC

[4/6] git commit: updating to cordova-ios 2.4.0

updating to cordova-ios 2.4.0


Project: http://git-wip-us.apache.org/repos/asf/cordova-cli/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-cli/commit/5203a279
Tree: http://git-wip-us.apache.org/repos/asf/cordova-cli/tree/5203a279
Diff: http://git-wip-us.apache.org/repos/asf/cordova-cli/diff/5203a279

Branch: refs/heads/master
Commit: 5203a2797bef451b0abf9e8690f5957db911accd
Parents: f6dc3ad
Author: Fil Maj <ma...@gmail.com>
Authored: Wed Feb 6 10:44:17 2013 -0800
Committer: Fil Maj <ma...@gmail.com>
Committed: Wed Feb 6 10:44:17 2013 -0800

----------------------------------------------------------------------
 .../CordovaLib/Classes/CDVCommandDelegateImpl.m    |    4 +
 lib/cordova-ios/CordovaLib/Classes/CDVContact.m    |   30 ++-
 .../CordovaLib/Classes/CDVFileTransfer.m           |    2 +-
 .../CordovaLib/Classes/CDVInAppBrowser.h           |    4 +-
 .../CordovaLib/Classes/CDVInAppBrowser.m           |   65 +++--
 .../CordovaLib/Classes/CDVUserAgentUtil.h          |    4 +-
 .../CordovaLib/Classes/CDVUserAgentUtil.m          |   64 +++-
 .../CordovaLib/Classes/CDVViewController.m         |   35 ++-
 .../CordovaLib.xcodeproj/project.pbxproj           |    6 +-
 lib/cordova-ios/CordovaLib/VERSION                 |    2 +-
 lib/cordova-ios/CordovaLib/cordova.ios.js          |  243 ++++++++++-----
 lib/cordova-ios/CordovaLibTests/CDVUserAgentTest.m |   93 ++++++
 .../CordovaLibTests/CordovaLibApp/config.xml       |    1 -
 .../CordovaTests.xcodeproj/project.pbxproj         |    4 +
 lib/cordova-ios/RELEASENOTES.md                    |   58 ++++-
 .../bin/templates/project/__TESTING__/config.xml   |    1 -
 lib/cordova-ios/bin/update_cordova_subproject      |   52 ++--
 .../guides/Cordova Plugin Upgrade Guide.md         |    8 +
 18 files changed, 507 insertions(+), 169 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/5203a279/lib/cordova-ios/CordovaLib/Classes/CDVCommandDelegateImpl.m
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLib/Classes/CDVCommandDelegateImpl.m b/lib/cordova-ios/CordovaLib/Classes/CDVCommandDelegateImpl.m
index 3447111..8845e40 100644
--- a/lib/cordova-ios/CordovaLib/Classes/CDVCommandDelegateImpl.m
+++ b/lib/cordova-ios/CordovaLib/Classes/CDVCommandDelegateImpl.m
@@ -78,6 +78,10 @@
 
 - (void)sendPluginResult:(CDVPluginResult*)result callbackId:(NSString*)callbackId
 {
+    // This occurs when there is are no win/fail callbacks for the call.
+    if ([@"INVALID" isEqualToString:callbackId]) {
+        return;
+    }
     int status = [result.status intValue];
     BOOL keepCallback = [result.keepCallback boolValue];
     id message = result.message == nil ? [NSNull null] : result.message;

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/5203a279/lib/cordova-ios/CordovaLib/Classes/CDVContact.m
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLib/Classes/CDVContact.m b/lib/cordova-ios/CordovaLib/Classes/CDVContact.m
index 93c4916..9efaf10 100644
--- a/lib/cordova-ios/CordovaLib/Classes/CDVContact.m
+++ b/lib/cordova-ios/CordovaLib/Classes/CDVContact.m
@@ -1174,9 +1174,13 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil;
                 id key = [[CDVContact defaultW3CtoAB] valueForKey:k];
                 if (key && ![k isKindOfClass:[NSNull class]]) {
                     bFound = CFDictionaryGetValueIfPresent(dict, (__bridge const void*)key, (void*)&value);
-                    CFRetain(value);
-                    [newAddress setObject:(bFound && value != NULL) ?  (__bridge id)value:[NSNull null] forKey:k];
-                    CFRelease(value);
+                    if (bFound && (value != NULL)) {
+                        CFRetain(value);
+                        [newAddress setObject:(__bridge id)value forKey:k];
+                        CFRelease(value);
+                    } else {
+                        [newAddress setObject:[NSNull null] forKey:k];
+                    }
                 } else {
                     // was a property that iPhone doesn't support
                     [newAddress setObject:[NSNull null] forKey:k];
@@ -1228,15 +1232,23 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil;
             if ([fields containsObject:kW3ContactFieldValue]) {
                 // value = user name
                 bFound = CFDictionaryGetValueIfPresent(dict, kABPersonInstantMessageUsernameKey, (void*)&value);
-                CFRetain(value);
-                [newDict setObject:(bFound && value != NULL) ?  (__bridge id)value:[NSNull null] forKey:kW3ContactFieldValue];
-                CFRelease(value);
+                if (bFound && (value != NULL)) {
+                    CFRetain(value);
+                    [newDict setObject:(__bridge id)value forKey:kW3ContactFieldValue];
+                    CFRelease(value);
+                } else {
+                    [newDict setObject:[NSNull null] forKey:kW3ContactFieldValue];
+                }
             }
             if ([fields containsObject:kW3ContactFieldType]) {
                 bFound = CFDictionaryGetValueIfPresent(dict, kABPersonInstantMessageServiceKey, (void*)&value);
-                CFRetain(value);
-                [newDict setObject:(bFound && value != NULL) ? (id)[[CDVContact class] convertPropertyLabelToContactType:(__bridge NSString*)value]:[NSNull null] forKey:kW3ContactFieldType];
-                CFRelease(value);
+                if (bFound && (value != NULL)) {
+                    CFRetain(value);
+                    [newDict setObject:(id)[[CDVContact class] convertPropertyLabelToContactType:(__bridge NSString*)value] forKey:kW3ContactFieldType];
+                    CFRelease(value);
+                } else {
+                    [newDict setObject:[NSNull null] forKey:kW3ContactFieldType];
+                }
             }
             // always set ID
             id identifier = [NSNumber numberWithUnsignedInt:ABMultiValueGetIdentifierAtIndex(multi, i)];

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/5203a279/lib/cordova-ios/CordovaLib/Classes/CDVFileTransfer.m
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLib/Classes/CDVFileTransfer.m b/lib/cordova-ios/CordovaLib/Classes/CDVFileTransfer.m
index 128c954..8e05658 100644
--- a/lib/cordova-ios/CordovaLib/Classes/CDVFileTransfer.m
+++ b/lib/cordova-ios/CordovaLib/Classes/CDVFileTransfer.m
@@ -87,7 +87,7 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream)
 {
     [req setValue:@"XMLHttpRequest" forHTTPHeaderField:@"X-Requested-With"];
 
-    NSString* userAgent = [[self.webView request] valueForHTTPHeaderField:@"User-Agent"];
+    NSString* userAgent = [self.commandDelegate userAgent];
     if (userAgent) {
         [req setValue:userAgent forHTTPHeaderField:@"User-Agent"];
     }

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/5203a279/lib/cordova-ios/CordovaLib/Classes/CDVInAppBrowser.h
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLib/Classes/CDVInAppBrowser.h b/lib/cordova-ios/CordovaLib/Classes/CDVInAppBrowser.h
index 0a3ef70..9ff460a 100644
--- a/lib/cordova-ios/CordovaLib/Classes/CDVInAppBrowser.h
+++ b/lib/cordova-ios/CordovaLib/Classes/CDVInAppBrowser.h
@@ -31,7 +31,7 @@
 
 @end
 
-@interface CDVInAppBrowser : CDVPlugin <CDVInAppBrowserNavigationDelegate>{}
+@interface CDVInAppBrowser : CDVPlugin <CDVInAppBrowserNavigationDelegate>
 
 @property (nonatomic, retain) CDVInAppBrowserViewController* inAppBrowserViewController;
 @property (nonatomic, copy) NSString* callbackId;
@@ -46,7 +46,7 @@
     NSURL* _requestedURL;
     NSString* _userAgent;
     NSString* _prevUserAgent;
-    BOOL _isPDF;
+    NSInteger _userAgentLockToken;
 }
 
 @property (nonatomic, strong) IBOutlet UIWebView* webView;

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/5203a279/lib/cordova-ios/CordovaLib/Classes/CDVInAppBrowser.m
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLib/Classes/CDVInAppBrowser.m b/lib/cordova-ios/CordovaLib/Classes/CDVInAppBrowser.m
index 6d7b770..45bf705 100644
--- a/lib/cordova-ios/CordovaLib/Classes/CDVInAppBrowser.m
+++ b/lib/cordova-ios/CordovaLib/Classes/CDVInAppBrowser.m
@@ -130,8 +130,10 @@
     self.inAppBrowserViewController.webView.scalesPageToFit = browserOptions.enableviewportscale;
     self.inAppBrowserViewController.webView.mediaPlaybackRequiresUserAction = browserOptions.mediaplaybackrequiresuseraction;
     self.inAppBrowserViewController.webView.allowsInlineMediaPlayback = browserOptions.allowinlinemediaplayback;
-    self.inAppBrowserViewController.webView.keyboardDisplayRequiresUserAction = browserOptions.keyboarddisplayrequiresuseraction;
-    self.inAppBrowserViewController.webView.suppressesIncrementalRendering = browserOptions.suppressesincrementalrendering;
+    if (IsAtLeastiOSVersion(@"6.0")) {
+        self.inAppBrowserViewController.webView.keyboardDisplayRequiresUserAction = browserOptions.keyboarddisplayrequiresuseraction;
+        self.inAppBrowserViewController.webView.suppressesIncrementalRendering = browserOptions.suppressesincrementalrendering;
+    }
 
     if (self.viewController.modalViewController != self.inAppBrowserViewController) {
         [self.viewController presentModalViewController:self.inAppBrowserViewController animated:YES];
@@ -233,27 +235,23 @@
 
     webViewBounds.size.height -= FOOTER_HEIGHT;
 
-    if (!self.webView) {
-        [CDVUserAgentUtil setUserAgent:_userAgent];
-
-        self.webView = [[UIWebView alloc] initWithFrame:webViewBounds];
-        self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
+    self.webView = [[UIWebView alloc] initWithFrame:webViewBounds];
+    self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
 
-        [self.view addSubview:self.webView];
-        [self.view sendSubviewToBack:self.webView];
+    [self.view addSubview:self.webView];
+    [self.view sendSubviewToBack:self.webView];
 
-        self.webView.delegate = self;
-        self.webView.backgroundColor = [UIColor whiteColor];
+    self.webView.delegate = self;
+    self.webView.backgroundColor = [UIColor whiteColor];
 
-        self.webView.clearsContextBeforeDrawing = YES;
-        self.webView.clipsToBounds = YES;
-        self.webView.contentMode = UIViewContentModeScaleToFill;
-        self.webView.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}");
-        self.webView.multipleTouchEnabled = YES;
-        self.webView.opaque = YES;
-        self.webView.scalesPageToFit = NO;
-        self.webView.userInteractionEnabled = YES;
-    }
+    self.webView.clearsContextBeforeDrawing = YES;
+    self.webView.clipsToBounds = YES;
+    self.webView.contentMode = UIViewContentModeScaleToFill;
+    self.webView.contentStretch = CGRectFromString(@"{{0, 0}, {1, 1}}");
+    self.webView.multipleTouchEnabled = YES;
+    self.webView.opaque = YES;
+    self.webView.scalesPageToFit = NO;
+    self.webView.userInteractionEnabled = YES;
 
     self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
     self.spinner.alpha = 1.000;
@@ -381,6 +379,11 @@
 
 - (void)close
 {
+    if (_userAgentLockToken != 0) {
+        [CDVUserAgentUtil releaseLock:_userAgentLockToken];
+        _userAgentLockToken = 0;
+    }
+
     if ([self respondsToSelector:@selector(presentingViewController)]) {
         [[self presentingViewController] dismissViewControllerAnimated:YES completion:nil];
     } else {
@@ -395,8 +398,17 @@
 - (void)navigateTo:(NSURL*)url
 {
     NSURLRequest* request = [NSURLRequest requestWithURL:url];
+    _requestedURL = url;
 
-    [self.webView loadRequest:request];
+    if (_userAgentLockToken != 0) {
+        [self.webView loadRequest:request];
+    } else {
+        [CDVUserAgentUtil acquireLock:^(NSInteger lockToken) {
+                _userAgentLockToken = lockToken;
+                [CDVUserAgentUtil setUserAgent:_userAgent lockToken:lockToken];
+                [self.webView loadRequest:request];
+            }];
+    }
 }
 
 - (void)goBack:(id)sender
@@ -421,12 +433,8 @@
 
     [self.spinner startAnimating];
 
-    NSURL* url = theWebView.request.URL;
-    // This is probably a bug, but it works on iOS 5 and 6 to know when a PDF
-    // is being loaded.
-    _isPDF = [[url absoluteString] length] == 0;
-
     if ((self.navigationDelegate != nil) && [self.navigationDelegate respondsToSelector:@selector(browserLoadStart:)]) {
+        NSURL* url = theWebView.request.URL;
         if (url == nil) {
             url = _requestedURL;
         }
@@ -455,8 +463,9 @@
     //    from it must pass through its white-list. This *does* break PDFs that
     //    contain links to other remote PDF/websites.
     // More info at https://issues.apache.org/jira/browse/CB-2225
-    if (_isPDF) {
-        [CDVUserAgentUtil setUserAgent:_prevUserAgent];
+    BOOL isPDF = [@"true" isEqualToString:[theWebView stringByEvaluatingJavaScriptFromString:@"document.body==null"]];
+    if (isPDF) {
+        [CDVUserAgentUtil setUserAgent:_prevUserAgent lockToken:_userAgentLockToken];
     }
 
     if ((self.navigationDelegate != nil) && [self.navigationDelegate respondsToSelector:@selector(browserLoadStop:)]) {

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/5203a279/lib/cordova-ios/CordovaLib/Classes/CDVUserAgentUtil.h
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLib/Classes/CDVUserAgentUtil.h b/lib/cordova-ios/CordovaLib/Classes/CDVUserAgentUtil.h
index fdea596..662b674 100644
--- a/lib/cordova-ios/CordovaLib/Classes/CDVUserAgentUtil.h
+++ b/lib/cordova-ios/CordovaLib/Classes/CDVUserAgentUtil.h
@@ -21,5 +21,7 @@
 
 @interface CDVUserAgentUtil : NSObject
 + (NSString*)originalUserAgent;
-+ (void)setUserAgent:(NSString*)newValue;
++ (void)acquireLock:(void (^)(NSInteger lockToken))block;
++ (void)releaseLock:(NSInteger)lockToken;
++ (void)setUserAgent:(NSString*)value lockToken:(NSInteger)lockToken;
 @end

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/5203a279/lib/cordova-ios/CordovaLib/Classes/CDVUserAgentUtil.m
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLib/Classes/CDVUserAgentUtil.m b/lib/cordova-ios/CordovaLib/Classes/CDVUserAgentUtil.m
index ac5c994..e2baef0 100644
--- a/lib/cordova-ios/CordovaLib/Classes/CDVUserAgentUtil.m
+++ b/lib/cordova-ios/CordovaLib/Classes/CDVUserAgentUtil.m
@@ -21,9 +21,16 @@
 
 #import <UIKit/UIKit.h>
 
+// #define VerboseLog NSLog
+#define VerboseLog(...) do {} while (0)
+
+static NSString* const kCdvUserAgentKey = @"Cordova-User-Agent";
+static NSString* const kCdvUserAgentVersionKey = @"Cordova-User-Agent-Version";
+
 static NSString* gOriginalUserAgent = nil;
-static NSString* kCdvUserAgentKey = @"Cordova-User-Agent";
-static NSString* kCdvUserAgentVersionKey = @"Cordova-User-Agent-Version";
+static NSInteger gNextLockToken = 0;
+static NSInteger gCurrentLockToken = 0;
+static NSMutableArray* gPendingSetUserAgentBlocks = nil;
 
 @implementation CDVUserAgentUtil
 
@@ -55,22 +62,55 @@ static NSString* kCdvUserAgentVersionKey = @"Cordova-User-Agent-Version";
     return gOriginalUserAgent;
 }
 
-+ (void)setUserAgent:(NSString*)newValue
++ (void)onAppLocaleDidChange:(NSNotification*)notification
+{
+    // TODO: We should figure out how to update the user-agent of existing UIWebViews when this happens.
+    // Maybe use the PDF bug (noted in setUserAgent:).
+    gOriginalUserAgent = nil;
+}
+
++ (void)acquireLock:(void (^)(NSInteger lockToken))block
+{
+    if (gCurrentLockToken == 0) {
+        gCurrentLockToken = ++gNextLockToken;
+        VerboseLog(@"Gave lock %d", gCurrentLockToken);
+        block(gCurrentLockToken);
+    } else {
+        if (gPendingSetUserAgentBlocks == nil) {
+            gPendingSetUserAgentBlocks = [[NSMutableArray alloc] initWithCapacity:4];
+        }
+        VerboseLog(@"Waiting for lock");
+        [gPendingSetUserAgentBlocks addObject:block];
+    }
+}
+
++ (void)releaseLock:(NSInteger)lockToken
 {
+    NSAssert(gCurrentLockToken == lockToken, @"Got token %d, expected %d", lockToken, gCurrentLockToken);
+
+    VerboseLog(@"Released lock %d", lockToken);
+    if ([gPendingSetUserAgentBlocks count] > 0) {
+        void (^block)() = [gPendingSetUserAgentBlocks objectAtIndex:0];
+        [gPendingSetUserAgentBlocks removeObjectAtIndex:0];
+        gCurrentLockToken = ++gNextLockToken;
+        NSLog (@"Gave lock %d", gCurrentLockToken);
+        block(gCurrentLockToken);
+    } else {
+        gCurrentLockToken = 0;
+    }
+}
+
++ (void)setUserAgent:(NSString*)value lockToken:(NSInteger)lockToken
+{
+    NSAssert(gCurrentLockToken == lockToken, @"Got token %d, expected %d", lockToken, gCurrentLockToken);
+    VerboseLog(@"User-Agent set to: %@", value);
+
     // Setting the UserAgent must occur before a UIWebView is instantiated.
     // It is read per instantiation, so it does not affect previously created views.
     // Except! When a PDF is loaded, all currently active UIWebViews reload their
     // User-Agent from the NSUserDefaults some time after the DidFinishLoad of the PDF bah!
-    NSDictionary* dict = [[NSDictionary alloc] initWithObjectsAndKeys:newValue, @"UserAgent", nil];
-
+    NSDictionary* dict = [[NSDictionary alloc] initWithObjectsAndKeys:value, @"UserAgent", nil];
     [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
 }
 
-+ (void)onAppLocaleDidChange:(NSNotification*)notification
-{
-    // TODO: We should figure out how to update the user-agent of existing UIWebViews when this happens.
-    // Maybe use the PDF bug (noted in setUserAgent:).
-    gOriginalUserAgent = nil;
-}
-
 @end

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/5203a279/lib/cordova-ios/CordovaLib/Classes/CDVViewController.m
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLib/Classes/CDVViewController.m b/lib/cordova-ios/CordovaLib/Classes/CDVViewController.m
index ef91f5f..36ae785 100644
--- a/lib/cordova-ios/CordovaLib/Classes/CDVViewController.m
+++ b/lib/cordova-ios/CordovaLib/Classes/CDVViewController.m
@@ -26,7 +26,9 @@
 
 #define degreesToRadian(x) (M_PI * (x) / 180.0)
 
-@interface CDVViewController ()
+@interface CDVViewController () {
+    NSInteger _userAgentLockToken;
+}
 
 @property (nonatomic, readwrite, strong) NSXMLParser* configParser;
 @property (nonatomic, readwrite, strong) NSDictionary* settings;
@@ -303,14 +305,17 @@
     }
 
     // /////////////////
-
-    if (!loadErr) {
-        NSURLRequest* appReq = [NSURLRequest requestWithURL:appURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
-        [self.webView loadRequest:appReq];
-    } else {
-        NSString* html = [NSString stringWithFormat:@"<html><body> %@ </body></html>", loadErr];
-        [self.webView loadHTMLString:html baseURL:nil];
-    }
+    [CDVUserAgentUtil acquireLock:^(NSInteger lockToken) {
+            _userAgentLockToken = lockToken;
+            [CDVUserAgentUtil setUserAgent:self.userAgent lockToken:lockToken];
+            if (!loadErr) {
+                NSURLRequest* appReq = [NSURLRequest requestWithURL:appURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
+                [self.webView loadRequest:appReq];
+            } else {
+                NSString* html = [NSString stringWithFormat:@"<html><body> %@ </body></html>", loadErr];
+                [self.webView loadHTMLString:html baseURL:nil];
+            }
+        }];
 }
 
 - (NSArray*)parseInterfaceOrientations:(NSArray*)orientations
@@ -430,8 +435,6 @@
     webViewBounds.origin = self.view.bounds.origin;
 
     if (!self.webView) {
-        [CDVUserAgentUtil setUserAgent:self.userAgent];
-
         self.webView = [self newCordovaViewWithFrame:webViewBounds];
         self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
 
@@ -496,6 +499,11 @@
  */
 - (void)webViewDidFinishLoad:(UIWebView*)theWebView
 {
+    if (_userAgentLockToken != 0) {
+        [CDVUserAgentUtil releaseLock:_userAgentLockToken];
+        _userAgentLockToken = 0;
+    }
+
     /*
      * Hide the Top Activity THROBBER in the Battery Bar
      */
@@ -521,6 +529,11 @@
 
 - (void)webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error
 {
+    if (_userAgentLockToken != 0) {
+        [CDVUserAgentUtil releaseLock:_userAgentLockToken];
+        _userAgentLockToken = 0;
+    }
+
     NSLog(@"Failed to load webpage with error: %@", [error localizedDescription]);
 
     /*

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/5203a279/lib/cordova-ios/CordovaLib/CordovaLib.xcodeproj/project.pbxproj
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLib/CordovaLib.xcodeproj/project.pbxproj b/lib/cordova-ios/CordovaLib/CordovaLib.xcodeproj/project.pbxproj
index 74ba2d5..53d2d23 100644
--- a/lib/cordova-ios/CordovaLib/CordovaLib.xcodeproj/project.pbxproj
+++ b/lib/cordova-ios/CordovaLib/CordovaLib.xcodeproj/project.pbxproj
@@ -39,7 +39,7 @@
 		30E33AF313A7E24B00594D64 /* CDVPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 30E33AF113A7E24B00594D64 /* CDVPlugin.m */; };
 		30E563CF13E217EC00C949AA /* NSMutableArray+QueueAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 30E563CD13E217EC00C949AA /* NSMutableArray+QueueAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		30E563D013E217EC00C949AA /* NSMutableArray+QueueAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 30E563CE13E217EC00C949AA /* NSMutableArray+QueueAdditions.m */; };
-		30F3930B169F839700B22307 /* CDVJSON.h in Headers */ = {isa = PBXBuildFile; fileRef = 30F39309169F839700B22307 /* CDVJSON.h */; };
+		30F3930B169F839700B22307 /* CDVJSON.h in Headers */ = {isa = PBXBuildFile; fileRef = 30F39309169F839700B22307 /* CDVJSON.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		30F3930C169F839700B22307 /* CDVJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 30F3930A169F839700B22307 /* CDVJSON.m */; };
 		30F5EBAB14CA26E700987760 /* CDVCommandDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 30F5EBA914CA26E700987760 /* CDVCommandDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		3E76876D156A90EE00EB6FA3 /* CDVLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E76876B156A90EE00EB6FA3 /* CDVLogger.m */; };
@@ -80,11 +80,11 @@
 		EB3B357D161F2A45003DBE7D /* CDVCommandDelegateImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = EB3B357B161F2A45003DBE7D /* CDVCommandDelegateImpl.m */; };
 		EB80C2AC15DEA63D004D9E7B /* CDVEcho.h in Headers */ = {isa = PBXBuildFile; fileRef = EB80C2AA15DEA63D004D9E7B /* CDVEcho.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		EB80C2AD15DEA63D004D9E7B /* CDVEcho.m in Sources */ = {isa = PBXBuildFile; fileRef = EB80C2AB15DEA63D004D9E7B /* CDVEcho.m */; };
-		EB96673B16A8970A00D86CDF /* CDVUserAgentUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = EB96673916A8970900D86CDF /* CDVUserAgentUtil.h */; };
+		EB96673B16A8970A00D86CDF /* CDVUserAgentUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = EB96673916A8970900D86CDF /* CDVUserAgentUtil.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		EB96673C16A8970A00D86CDF /* CDVUserAgentUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = EB96673A16A8970900D86CDF /* CDVUserAgentUtil.m */; };
 		EBA3557315ABD38C00F4DE24 /* NSArray+Comparisons.h in Headers */ = {isa = PBXBuildFile; fileRef = EBA3557115ABD38C00F4DE24 /* NSArray+Comparisons.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		EBA3557515ABD38C00F4DE24 /* NSArray+Comparisons.m in Sources */ = {isa = PBXBuildFile; fileRef = EBA3557215ABD38C00F4DE24 /* NSArray+Comparisons.m */; };
-		F858FBC6166009A8007DA594 /* CDVConfigParser.h in Headers */ = {isa = PBXBuildFile; fileRef = F858FBC4166009A8007DA594 /* CDVConfigParser.h */; };
+		F858FBC6166009A8007DA594 /* CDVConfigParser.h in Headers */ = {isa = PBXBuildFile; fileRef = F858FBC4166009A8007DA594 /* CDVConfigParser.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		F858FBC7166009A8007DA594 /* CDVConfigParser.m in Sources */ = {isa = PBXBuildFile; fileRef = F858FBC5166009A8007DA594 /* CDVConfigParser.m */; };
 /* End PBXBuildFile section */
 

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/5203a279/lib/cordova-ios/CordovaLib/VERSION
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLib/VERSION b/lib/cordova-ios/CordovaLib/VERSION
index edf28ac..197c4d5 100644
--- a/lib/cordova-ios/CordovaLib/VERSION
+++ b/lib/cordova-ios/CordovaLib/VERSION
@@ -1 +1 @@
-2.4.0rc1
\ No newline at end of file
+2.4.0

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/5203a279/lib/cordova-ios/CordovaLib/cordova.ios.js
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLib/cordova.ios.js b/lib/cordova-ios/CordovaLib/cordova.ios.js
index 9d0cacf..bc15819 100644
--- a/lib/cordova-ios/CordovaLib/cordova.ios.js
+++ b/lib/cordova-ios/CordovaLib/cordova.ios.js
@@ -1,6 +1,8 @@
-// commit 7d5552049e33254eca3bda227af8a4892a7ce7c3
+// Platform: ios
 
-// File generated at :: Mon Jan 21 2013 14:46:55 GMT-0800 (PST)
+// commit ac725f6ae0bd655789771e2a40b8d60cb4c8c221
+
+// File generated at :: Tue Feb 05 2013 05:30:42 GMT+0100 (CET)
 
 /*
  Licensed to the Apache Software Foundation (ASF) under one
@@ -78,6 +80,7 @@ var require,
         delete modules[id];
     };
 
+    define.moduleMap = modules;
 })();
 
 //Export for use in node
@@ -409,6 +412,8 @@ function assignOrWrapInDeprecateGetter(obj, key, value, message) {
     if (message) {
         utils.defineGetter(obj, key, function() {
             console.log(message);
+            delete obj[key];
+            clobber(obj, key, value);
             return value;
         });
     } else {
@@ -438,10 +443,6 @@ function include(parent, objects, clobber, merge) {
             // Overwrite if not currently defined.
             if (typeof parent[key] == 'undefined') {
               assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated);
-            } else if (merge && typeof obj.path !== 'undefined') {
-              // If merging, merge parent onto result
-              recursiveMerge(result, parent[key]);
-              parent[key] = result;
             } else {
               // Set result to what already exists, so we can build children into it if they exist.
               result = parent[key];
@@ -467,19 +468,18 @@ function include(parent, objects, clobber, merge) {
 function recursiveMerge(target, src) {
     for (var prop in src) {
         if (src.hasOwnProperty(prop)) {
-            if (typeof target.prototype !== 'undefined' && target.prototype.constructor === target) {
+            if (target.prototype && target.prototype.constructor === target) {
                 // If the target object is a constructor override off prototype.
-                target.prototype[prop] = src[prop];
+                clobber(target.prototype, prop, src[prop]);
             } else {
-                if (typeof src[prop] === 'object') {
-                    target[prop] = recursiveMerge(target[prop], src[prop]);
+                if (typeof src[prop] === 'object' && typeof target[prop] === 'object') {
+                    recursiveMerge(target[prop], src[prop]);
                 } else {
                     clobber(target, prop, src[prop]);
                 }
             }
         }
     }
-    return target;
 }
 
 module.exports = {
@@ -491,7 +491,9 @@ module.exports = {
     },
     buildIntoAndMerge: function(objects, target) {
         include(target, objects, true, true);
-    }
+    },
+    recursiveMerge: recursiveMerge,
+    assignOrWrapInDeprecateGetter: assignOrWrapInDeprecateGetter
 };
 
 });
@@ -904,54 +906,9 @@ module.exports = {
         device: {
             path: 'cordova/plugin/device'
         },
-        DirectoryEntry: {
-            path: 'cordova/plugin/DirectoryEntry'
-        },
-        DirectoryReader: {
-            path: 'cordova/plugin/DirectoryReader'
-        },
-        Entry: {
-            path: 'cordova/plugin/Entry'
-        },
-        File: {
-            path: 'cordova/plugin/File'
-        },
-        FileEntry: {
-            path: 'cordova/plugin/FileEntry'
-        },
-        FileError: {
-            path: 'cordova/plugin/FileError'
-        },
-        FileReader: {
-            path: 'cordova/plugin/FileReader'
-        },
-        FileSystem: {
-            path: 'cordova/plugin/FileSystem'
-        },
-        FileTransfer: {
-            path: 'cordova/plugin/FileTransfer'
-        },
-        FileTransferError: {
-            path: 'cordova/plugin/FileTransferError'
-        },
-        FileUploadOptions: {
-            path: 'cordova/plugin/FileUploadOptions'
-        },
-        FileUploadResult: {
-            path: 'cordova/plugin/FileUploadResult'
-        },
-        FileWriter: {
-            path: 'cordova/plugin/FileWriter'
-        },
-        Flags: {
-            path: 'cordova/plugin/Flags'
-        },
         GlobalizationError: {
             path: 'cordova/plugin/GlobalizationError'
         },
-        LocalFileSystem: {
-            path: 'cordova/plugin/LocalFileSystem'
-        },
         Media: {
             path: 'cordova/plugin/Media'
         },
@@ -964,9 +921,6 @@ module.exports = {
         MediaFileData:{
             path: 'cordova/plugin/MediaFileData'
         },
-        Metadata:{
-            path: 'cordova/plugin/Metadata'
-        },
         Position: {
             path: 'cordova/plugin/Position'
         },
@@ -975,12 +929,6 @@ module.exports = {
         },
         ProgressEvent: {
             path: 'cordova/plugin/ProgressEvent'
-        },
-        requestFileSystem:{
-            path: 'cordova/plugin/requestFileSystem'
-        },
-        resolveLocalFileSystemURI:{
-            path: 'cordova/plugin/resolveLocalFileSystemURI'
         }
     },
     clobbers: {
@@ -1052,7 +1000,7 @@ function massageArgsJsToNative(args) {
         return window.btoa(encodeArrayBufferAs8bitString(ab));
     };
     args.forEach(function(arg, i) {
-        if (Object.prototype.toString.call(arg).slice(8, -1) == 'ArrayBuffer') {
+        if (utils.typeName(arg) == 'ArrayBuffer') {
             args[i] = {
                 'CDVType': 'ArrayBuffer',
                 'data': encodeArrayBufferAsBase64(arg)
@@ -1206,20 +1154,119 @@ module.exports = iOSExec;
 
 });
 
+// file: lib/common/modulemapper.js
+define("cordova/modulemapper", function(require, exports, module) {
+
+var builder = require('cordova/builder'),
+    moduleMap = define.moduleMap,
+    symbolList,
+    deprecationMap;
+
+exports.reset = function() {
+    symbolList = [];
+    deprecationMap = {};
+};
+
+function addEntry(strategy, moduleName, symbolPath, opt_deprecationMessage) {
+    if (!(moduleName in moduleMap)) {
+        throw new Error('Module ' + moduleName + ' does not exist.');
+    }
+    symbolList.push(strategy, moduleName, symbolPath);
+    if (opt_deprecationMessage) {
+        deprecationMap[symbolPath] = opt_deprecationMessage;
+    }
+}
+
+// Note: Android 2.3 does have Function.bind().
+exports.clobbers = function(moduleName, symbolPath, opt_deprecationMessage) {
+    addEntry('c', moduleName, symbolPath, opt_deprecationMessage);
+};
+
+exports.merges = function(moduleName, symbolPath, opt_deprecationMessage) {
+    addEntry('m', moduleName, symbolPath, opt_deprecationMessage);
+};
+
+exports.defaults = function(moduleName, symbolPath, opt_deprecationMessage) {
+    addEntry('d', moduleName, symbolPath, opt_deprecationMessage);
+};
+
+function prepareNamespace(symbolPath, context) {
+    if (!symbolPath) {
+        return context;
+    }
+    var parts = symbolPath.split('.');
+    var cur = context;
+    for (var i = 0, part; part = parts[i]; ++i) {
+        cur[part] = cur[part] || {};
+    }
+    return cur[parts[i-1]];
+}
+
+exports.mapModules = function(context) {
+    var origSymbols = {};
+    context.CDV_origSymbols = origSymbols;
+    for (var i = 0, len = symbolList.length; i < len; i += 3) {
+        var strategy = symbolList[i];
+        var moduleName = symbolList[i + 1];
+        var symbolPath = symbolList[i + 2];
+        var lastDot = symbolPath.lastIndexOf('.');
+        var namespace = symbolPath.substr(0, lastDot);
+        var lastName = symbolPath.substr(lastDot + 1);
+
+        var module = require(moduleName);
+        var deprecationMsg = symbolPath in deprecationMap ? 'Access made to deprecated symbol: ' + symbolPath + '. ' + deprecationMsg : null;
+        var parentObj = prepareNamespace(namespace, context);
+        var target = parentObj[lastName];
+
+        if (strategy == 'm' && target) {
+            builder.recursiveMerge(target, module);
+        } else if ((strategy == 'd' && !target) || (strategy != 'd')) {
+            if (target) {
+                origSymbols[symbolPath] = target;
+            }
+            builder.assignOrWrapInDeprecateGetter(parentObj, lastName, module, deprecationMsg);
+        }
+    }
+};
+
+exports.getOriginalSymbol = function(context, symbolPath) {
+    var origSymbols = context.CDV_origSymbols;
+    if (origSymbols && (symbolPath in origSymbols)) {
+        return origSymbols[symbolPath];
+    }
+    var parts = symbolPath.split('.');
+    var obj = context;
+    for (var i = 0; i < parts.length; ++i) {
+        obj = obj && obj[parts[i]];
+    }
+    return obj;
+};
+
+exports.loadMatchingModules = function(matchingRegExp) {
+    for (var k in moduleMap) {
+        if (matchingRegExp.exec(k)) {
+            require(k);
+        }
+    }
+};
+
+exports.reset();
+
+
+});
+
 // file: lib/ios/platform.js
 define("cordova/platform", function(require, exports, module) {
 
 module.exports = {
     id: "ios",
     initialize:function() {
+        var modulemapper = require('cordova/modulemapper');
+
+        modulemapper.loadMatchingModules(/cordova.*\/symbols$/);
+        modulemapper.mapModules(window);
     },
     clobbers: {
-        File: { // exists natively, override
-            path: "cordova/plugin/File"
-        },
-        FileReader: { // exists natively, override
-            path: "cordova/plugin/FileReader"
-        },
         MediaError: { // exists natively, override
             path: "cordova/plugin/MediaError"
         },
@@ -1234,9 +1281,6 @@ module.exports = {
         Contact:{
             path: "cordova/plugin/ios/Contact"
         },
-        Entry:{
-            path: "cordova/plugin/ios/Entry"
-        },
         navigator:{
             children:{
                 notification:{
@@ -1531,6 +1575,7 @@ module.exports = {
         CELL_2G: "2g",
         CELL_3G: "3g",
         CELL_4G: "4g",
+        CELL:"cellular",
         NONE: "none"
 };
 
@@ -2412,11 +2457,12 @@ module.exports = FileError;
 define("cordova/plugin/FileReader", function(require, exports, module) {
 
 var exec = require('cordova/exec'),
+    modulemapper = require('cordova/modulemapper'),
     utils = require('cordova/utils'),
     File = require('cordova/plugin/File'),
     FileError = require('cordova/plugin/FileError'),
     ProgressEvent = require('cordova/plugin/ProgressEvent'),
-    origFileReader = this.FileReader;
+    origFileReader = modulemapper.getOriginalSymbol(this, 'FileReader');
 
 /**
  * This class reads the mobile device file system.
@@ -4404,6 +4450,44 @@ module.exports = function(successCallback, errorCallback, message, forceAsync) {
 
 });
 
+// file: lib/ios/plugin/file/symbols.js
+define("cordova/plugin/file/symbols", function(require, exports, module) {
+
+
+var modulemapper = require('cordova/modulemapper'),
+    symbolshelper = require('cordova/plugin/file/symbolshelper');
+
+symbolshelper(modulemapper.clobbers);
+modulemapper.merges('cordova/plugin/ios/Entry', 'Entry');
+
+});
+
+// file: lib/common/plugin/file/symbolshelper.js
+define("cordova/plugin/file/symbolshelper", function(require, exports, module) {
+
+module.exports = function(exportFunc) {
+    exportFunc('cordova/plugin/DirectoryEntry', 'DirectoryEntry');
+    exportFunc('cordova/plugin/DirectoryReader', 'DirectoryReader');
+    exportFunc('cordova/plugin/Entry', 'Entry');
+    exportFunc('cordova/plugin/File', 'File');
+    exportFunc('cordova/plugin/FileEntry', 'FileEntry');
+    exportFunc('cordova/plugin/FileError', 'FileError');
+    exportFunc('cordova/plugin/FileReader', 'FileReader');
+    exportFunc('cordova/plugin/FileSystem', 'FileSystem');
+    exportFunc('cordova/plugin/FileTransfer', 'FileTransfer');
+    exportFunc('cordova/plugin/FileTransferError', 'FileTransferError');
+    exportFunc('cordova/plugin/FileUploadOptions', 'FileUploadOptions');
+    exportFunc('cordova/plugin/FileUploadResult', 'FileUploadResult');
+    exportFunc('cordova/plugin/FileWriter', 'FileWriter');
+    exportFunc('cordova/plugin/Flags', 'Flags');
+    exportFunc('cordova/plugin/LocalFileSystem', 'LocalFileSystem');
+    exportFunc('cordova/plugin/Metadata', 'Metadata');
+    exportFunc('cordova/plugin/requestFileSystem', 'requestFileSystem');
+    exportFunc('cordova/plugin/resolveLocalFileSystemURI', 'resolveLocalFileSystemURI');
+};
+
+});
+
 // file: lib/common/plugin/geolocation.js
 define("cordova/plugin/geolocation", function(require, exports, module) {
 
@@ -5642,7 +5726,10 @@ var utils = exports;
  */
 utils.defineGetterSetter = function(obj, key, getFunc, opt_setFunc) {
     if (Object.defineProperty) {
-        var desc = {get:getFunc};
+        var desc = {
+            get: getFunc,
+            configurable: true
+        };
         if (opt_setFunc) {
             desc.set = opt_setFunc;
         }

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/5203a279/lib/cordova-ios/CordovaLibTests/CDVUserAgentTest.m
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLibTests/CDVUserAgentTest.m b/lib/cordova-ios/CordovaLibTests/CDVUserAgentTest.m
new file mode 100644
index 0000000..dcbf30a
--- /dev/null
+++ b/lib/cordova-ios/CordovaLibTests/CDVUserAgentTest.m
@@ -0,0 +1,93 @@
+/*
+ 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 <SenTestingKit/SenTestingKit.h>
+
+#import "CDVWebViewTest.h"
+#import "CDVViewController.h"
+#import "AppDelegate.h"
+
+@interface CDVUserAgentTestViewController : UIViewController
+@property (nonatomic) CDVViewController* vc1;
+@property (nonatomic) CDVViewController* vc2;
+@end
+
+@implementation CDVUserAgentTestViewController
+@synthesize vc1 = _vc1, vc2 = _vc2;
+
+- (void)loadView
+{
+    _vc1 = [[CDVViewController alloc] init];
+    _vc1.wwwFolderName = @"www";
+    _vc1.startPage = @"index.html";
+    [self addChildViewController:_vc1];
+
+    _vc2 = [[CDVViewController alloc] init];
+    _vc2.wwwFolderName = @"www";
+    _vc2.startPage = @"index.html";
+    [self addChildViewController:_vc2];
+
+    CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame];
+    UIView* contentView = [[UIView alloc] initWithFrame:applicationFrame];
+
+    CGRect sub1, sub2;
+    CGRectDivide(applicationFrame, &sub1, &sub2, applicationFrame.size.height / 2, CGRectMinYEdge);
+    [_vc1.view setBounds:sub1];
+    [_vc2.view setBounds:sub2];
+
+    [contentView addSubview:_vc1.view];
+    [contentView addSubview:_vc2.view];
+
+    self.view = contentView;
+}
+
+@end
+
+@interface CDVUserAgentTest : CDVWebViewTest
+@end
+
+@implementation CDVUserAgentTest
+
+- (void)setUp
+{
+    [super setUp];
+}
+
+- (void)tearDown
+{
+    [super tearDown];
+}
+
+- (void)testMultipleViews
+{
+    CDVUserAgentTestViewController* rootVc = [[CDVUserAgentTestViewController alloc] init];
+
+    self.appDelegate.window.rootViewController = rootVc;
+
+    NSString* getUserAgentCode = @"navigator.userAgent";
+    [self waitForConditionName:@"getting user-agents" block:^{
+            return (BOOL)(rootVc.vc1.webView.request != nil && rootVc.vc2.webView.request != nil);
+        }];
+    NSString* ua1 = [rootVc.vc1.webView stringByEvaluatingJavaScriptFromString:getUserAgentCode];
+    NSString* ua2 = [rootVc.vc2.webView stringByEvaluatingJavaScriptFromString:getUserAgentCode];
+
+    STAssertFalse([ua1 isEqual:ua2], @"User-Agents should be different.");
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/5203a279/lib/cordova-ios/CordovaLibTests/CordovaLibApp/config.xml
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLibTests/CordovaLibApp/config.xml b/lib/cordova-ios/CordovaLibTests/CordovaLibApp/config.xml
index 7b786fa..9c36914 100644
--- a/lib/cordova-ios/CordovaLibTests/CordovaLibApp/config.xml
+++ b/lib/cordova-ios/CordovaLibTests/CordovaLibApp/config.xml
@@ -10,7 +10,6 @@
     <preference name="ShowSplashScreenSpinner" value="true" />
     <preference name="MediaPlaybackRequiresUserAction" value="false" />
     <preference name="AllowInlineMediaPlayback" value="false" />
-    <preference name="OpenAllWhitelistURLsInWebView" value="false" />
     <preference name="BackupWebStorage" value="cloud" />
 
     <plugins>

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/5203a279/lib/cordova-ios/CordovaLibTests/CordovaTests.xcodeproj/project.pbxproj
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/CordovaLibTests/CordovaTests.xcodeproj/project.pbxproj b/lib/cordova-ios/CordovaLibTests/CordovaTests.xcodeproj/project.pbxproj
index 02490b5..f29ac11 100644
--- a/lib/cordova-ios/CordovaLibTests/CordovaTests.xcodeproj/project.pbxproj
+++ b/lib/cordova-ios/CordovaLibTests/CordovaTests.xcodeproj/project.pbxproj
@@ -43,6 +43,7 @@
 		68A32D7F141030F3006B237C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 686357AC141002F100DF4CF2 /* Foundation.framework */; };
 		EB3B34E9161B5532003DBE7D /* libCordova.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EB3B34E6161B5454003DBE7D /* libCordova.a */; };
 		EB89634A15FE66EA00E12277 /* CDVInvokedUrlCommandTests.m in Sources */ = {isa = PBXBuildFile; fileRef = EB89634915FE66EA00E12277 /* CDVInvokedUrlCommandTests.m */; };
+		EB96677216ADBCF500D86CDF /* CDVUserAgentTest.m in Sources */ = {isa = PBXBuildFile; fileRef = EB96677116ADBCF500D86CDF /* CDVUserAgentTest.m */; };
 		EBA3554615A731F100F4DE24 /* CDVFakeFileManager.m in Sources */ = {isa = PBXBuildFile; fileRef = EBA3554515A731F100F4DE24 /* CDVFakeFileManager.m */; };
 		EBA3556F15ABD0C900F4DE24 /* CDVFileTransferTests.m in Sources */ = {isa = PBXBuildFile; fileRef = EBA3556E15ABD0C900F4DE24 /* CDVFileTransferTests.m */; };
 		F8EB14D1165FFD3200616F39 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = F8EB14D0165FFD3200616F39 /* config.xml */; };
@@ -105,6 +106,7 @@
 		68A32D7414103017006B237C /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; };
 		EB37018115D18B2D00BEBC43 /* CordovaLib.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CordovaLib.xcodeproj; path = ../CordovaLib/CordovaLib.xcodeproj; sourceTree = "<group>"; };
 		EB89634915FE66EA00E12277 /* CDVInvokedUrlCommandTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVInvokedUrlCommandTests.m; sourceTree = "<group>"; };
+		EB96677116ADBCF500D86CDF /* CDVUserAgentTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVUserAgentTest.m; sourceTree = "<group>"; };
 		EBA3550F15A5F18900F4DE24 /* CDVWebViewTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CDVWebViewTest.h; sourceTree = "<group>"; };
 		EBA3554415A731F100F4DE24 /* CDVFakeFileManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVFakeFileManager.h; sourceTree = "<group>"; };
 		EBA3554515A731F100F4DE24 /* CDVFakeFileManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVFakeFileManager.m; sourceTree = "<group>"; };
@@ -237,6 +239,7 @@
 		EB3B34F4161B585D003DBE7D /* CordovaLibTests */ = {
 			isa = PBXGroup;
 			children = (
+				EB96677116ADBCF500D86CDF /* CDVUserAgentTest.m */,
 				EBA3554415A731F100F4DE24 /* CDVFakeFileManager.h */,
 				EBA3554515A731F100F4DE24 /* CDVFakeFileManager.m */,
 				EBA3550F15A5F18900F4DE24 /* CDVWebViewTest.h */,
@@ -417,6 +420,7 @@
 				EBA3554615A731F100F4DE24 /* CDVFakeFileManager.m in Sources */,
 				EBA3556F15ABD0C900F4DE24 /* CDVFileTransferTests.m in Sources */,
 				EB89634A15FE66EA00E12277 /* CDVInvokedUrlCommandTests.m in Sources */,
+				EB96677216ADBCF500D86CDF /* CDVUserAgentTest.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/5203a279/lib/cordova-ios/RELEASENOTES.md
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/RELEASENOTES.md b/lib/cordova-ios/RELEASENOTES.md
index e6b4334..4ffc39b 100644
--- a/lib/cordova-ios/RELEASENOTES.md
+++ b/lib/cordova-ios/RELEASENOTES.md
@@ -22,7 +22,60 @@
  
  Cordova is a static library that enables developers to include the Cordova API in their iOS application projects easily, and also create new Cordova-based iOS application projects through the command-line.
 
- ### 2.3.0 (201212XX) ###
+ ### 2.4.0 (20130205) ###
+
+* Make cordova_plist_to_config_xml able to handle binary plist files
+* Ran splashscreen images through ImageOptim.
+* [ios] Remove %-escaping version of FileReader.readAsText()
+* Fix trying to mutate an immutable NSArray in CDVInvokedUrlCommand.
+* Fix FileTransfer.download failing for file: URLs.
+* Fix setting of _nativeReady when cordova.js is lazy-loaded.
+* Fix NPE when PDF is opened in InAppBrowser.
+* Refactor User-Agent logic into a helper class.
+* Fix for CB-2225
+* Remove a debugging log statement.
+* Add a code comment that points to the PDF/User-Agent JIRA issue.
+* Disable broken test.
+* Don't send callbacks when there are no listeners.
+* Fix InAppBrowser on iOS 5.
+* Fix CB-2271 - Multiple Cordova Views.
+* Fix usage message of update_cordova_subproject.
+* Delete obsolete instructions in bin/README.md
+* Fixes CB-2209 Contact ARC issues
+* including a manual relpath function
+* Add slice() support to readAsText.
+* Add slice() support to readAsDataURL.
+* Move start page to be specified in <content> tag.
+* Separate the echoArrayBuffer call from normal echo
+* Adding bool plugin result message, tests
+* iOS fix slow contact access due to photos temp file generation
+* [CB-2235] Fixed file transfer whitelisting.
+* [ios]CB-2189: support ArrayBuffer over exec bridge
+* [ios] CB-2215 - Implement ArrayBuffer native->js.
+* [ios] CB-2215 - Implement ArrayBuffer native->js.
+* CordovaLibTests - update project file for iOS 5 support.
+* cordova/run and cordova/emulate refer to old 'debug' script which has been renamed to 'build'
+* [CB-1495] iOS default splash screen images take up several megabytes
+* [CB-1849] Remove iOS 4/5 conditional code block, put in main block
+* [CB-2193] Remove deprecated - iOS - CDVViewController invokeString property
+* Fixed CB-2191 and CB-2192 (removal of deprecated methods)
+* [CB-1832] iOS: CDVCordovaView should not inherit from UIWebView
+* [CB-1946] iOS: Switch JSON serialization to NSJSONSerialization
+* Fixes static analyzer error for using mktemp (substituted with mkstemp)
+* [CB-2159] handleOpenURL not called on iOS
+* [CB-2063] InAppBrowser - support iPad presentation style, iOS transition styles
+* [CB-478] FileTransfer upload - handle "trustAllHosts" parameter
+* Interim js patch for [CB-2094] issue
+* [CB-2071] InAppBrowser: allow UIWebView settings like main CordovaWebView
+* Added interim js for latest changes.
+* Added whitelist unit test to check for query param matches
+* [CB-2290] iOS: 'CDVJSON.h' file not found when adding a plugin
+* Added a native uri option to DestinationType.
+* Added a namespace prefix to a constant.
+
+<br />
+
+ ### 2.3.0 (20130107) ###
 
 * [CB-1550] iOS build, debug, emulate scripts should check xcode version
 * [CB-1669] Issue an error when media.startRecord() is failing.
@@ -82,6 +135,7 @@
 * Add local notification #define, and stubbed method in AppDelegate.m
 * Add appdelegate method didReceiveLocalNotification and repost to NSNotification defaultCenter
 
+<br />
 
  ### 2.2.0 (20121031) ###
 
@@ -158,7 +212,7 @@
 * Fix commandDelegate.evalJs to actually bundle exec() calls.
 * Removed Cordova Settings File guide, added web shortcut to online doc.
 * Changed Cordova.plist BackupWebStorage setting from boolean to string (cloud, local, none)
-* 
+
 <br />
 
 ### 2.1.0 (20120913) ###

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/5203a279/lib/cordova-ios/bin/templates/project/__TESTING__/config.xml
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/bin/templates/project/__TESTING__/config.xml b/lib/cordova-ios/bin/templates/project/__TESTING__/config.xml
index 7d67508..721c734 100644
--- a/lib/cordova-ios/bin/templates/project/__TESTING__/config.xml
+++ b/lib/cordova-ios/bin/templates/project/__TESTING__/config.xml
@@ -30,7 +30,6 @@
     <preference name="ShowSplashScreenSpinner" value="true" />
     <preference name="MediaPlaybackRequiresUserAction" value="false" />
     <preference name="AllowInlineMediaPlayback" value="false" />
-    <preference name="OpenAllWhitelistURLsInWebView" value="false" />
     <preference name="BackupWebStorage" value="cloud" />
 
     <content src="index.html" />

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/5203a279/lib/cordova-ios/bin/update_cordova_subproject
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/bin/update_cordova_subproject b/lib/cordova-ios/bin/update_cordova_subproject
index fca4700..07b257a 100755
--- a/lib/cordova-ios/bin/update_cordova_subproject
+++ b/lib/cordova-ios/bin/update_cordova_subproject
@@ -1,22 +1,20 @@
 #!/usr/bin/python
-"""
-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.
-"""
+# 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.
 """
 Updates the subproject path of the CordovaLib entry to point to this script's version of Cordova.
 
@@ -27,6 +25,7 @@ import fileinput
 import os
 import re
 import sys
+from posixpath import curdir, sep, pardir, join, abspath, commonprefix
 
 def Usage():
   print __doc__
@@ -53,6 +52,21 @@ def AbsProjectPath(relative_path):
     raise Exception('The following is not a valid path to an XCode project: %s' % project_path)
   return project_path
 
+def relpath(path, start=curdir):
+  # it would be nice to use os.path.relpath, 
+  # but that leaves out those with python < v2.6
+  if not path:
+    raise ValueError("no path specified")
+  start_list = abspath(start).split(sep)
+  path_list = abspath(path).split(sep)
+  # Work out how much of the filepath is shared by start and path.
+  i = len(commonprefix([start_list, path_list]))
+  rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
+  if not rel_list:
+    return curdir
+  return join(*rel_list)
+
+
 def main(argv):
   if len(argv) < 2 or len(argv) > 3:
     Usage()
@@ -65,7 +79,7 @@ def main(argv):
     cordova_lib_xcode_path = AbsProjectPath(argv[2])
 
   parent_project_path = AbsParentPath(project_path)
-  subproject_path = os.path.relpath(cordova_lib_xcode_path, parent_project_path)
+  subproject_path = relpath(cordova_lib_xcode_path, parent_project_path)
 
   # /* CordovaLib.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = CordovaLib.xcodeproj; sourceTree = "<group>"; };
   REGEX = re.compile(r'(.+PBXFileReference.+wrapper.pb-project.+)(path = .+?;)(.*)(sourceTree.+;)(.+)')

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/5203a279/lib/cordova-ios/guides/Cordova Plugin Upgrade Guide.md
----------------------------------------------------------------------
diff --git a/lib/cordova-ios/guides/Cordova Plugin Upgrade Guide.md b/lib/cordova-ios/guides/Cordova Plugin Upgrade Guide.md
index a173070..bf6191b 100644
--- a/lib/cordova-ios/guides/Cordova Plugin Upgrade Guide.md	
+++ b/lib/cordova-ios/guides/Cordova Plugin Upgrade Guide.md	
@@ -22,6 +22,14 @@
 
 This document is for developers who need to upgrade their Cordova  plugins to a newer Cordova version. Starting with Cordova 1.5.0, some classes have been renamed, which will require the plugin to be upgraded. Make sure your project itself has been upgraded using the "Cordova Upgrade Guide" document.
 
+## Upgrading older Cordova plugins to 2.4.0 ##
+
+1. **Install** Cordova 2.4.0
+2. Follow the **"Upgrading older Cordova plugins to 2.3.0"** section, if necessary
+
+JSONKit usage has been removed, and replaced by AppKit's NSJSONSerialization. If you are using CordovaLib's JSONKit, either use your own JSONKit or use NSJSONSerialization instead.
+
+
 ## Upgrading older Cordova plugins to 2.3.0 ##
 
 1. **Install** Cordova 2.3.0