diff options
56 files changed, 8062 insertions, 4064 deletions
diff --git a/iPhone/CordovaLib/Classes/CDV.h b/iPhone/CordovaLib/Classes/CDV.h index 5367799..5a0ae6a 100755 --- a/iPhone/CordovaLib/Classes/CDV.h +++ b/iPhone/CordovaLib/Classes/CDV.h @@ -32,7 +32,6 @@ #import "CDVConnection.h" #import "CDVContact.h" #import "CDVContacts.h" -#import "CDVCordovaView.h" #import "CDVDebug.h" #import "CDVDebugConsole.h" #import "CDVDevice.h" @@ -55,4 +54,4 @@ #import "NSMutableArray+QueueAdditions.h" #import "UIDevice+Extensions.h" -#import "JSONKit.h" +#import "CDVJSON.h" diff --git a/iPhone/CordovaLib/Classes/CDVAvailability.h b/iPhone/CordovaLib/Classes/CDVAvailability.h index d2e7f1b..33c6799 100755 --- a/iPhone/CordovaLib/Classes/CDVAvailability.h +++ b/iPhone/CordovaLib/Classes/CDVAvailability.h @@ -35,6 +35,8 @@ #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_NA 99999 /* not available */ /* @@ -45,7 +47,7 @@ #endif */ #ifndef CORDOVA_VERSION_MIN_REQUIRED - #define CORDOVA_VERSION_MIN_REQUIRED __CORDOVA_2_3_0 + #define CORDOVA_VERSION_MIN_REQUIRED __CORDOVA_2_5_0 #endif /* diff --git a/iPhone/CordovaLib/Classes/CDVCamera.h b/iPhone/CordovaLib/Classes/CDVCamera.h index 2905a8a..204d25f 100755 --- a/iPhone/CordovaLib/Classes/CDVCamera.h +++ b/iPhone/CordovaLib/Classes/CDVCamera.h @@ -22,7 +22,8 @@ enum CDVDestinationType { DestinationTypeDataUrl = 0, - DestinationTypeFileUri + DestinationTypeFileUri, + DestinationTypeNativeUri }; typedef NSUInteger CDVDestinationType; @@ -79,6 +80,7 @@ typedef NSUInteger CDVMediaType; - (void)takePicture:(CDVInvokedUrlCommand*)command; - (void)postImage:(UIImage*)anImage withFilename:(NSString*)filename toUrl:(NSURL*)url; - (void)cleanup:(CDVInvokedUrlCommand*)command; +- (void)repositionPopover:(CDVInvokedUrlCommand*)command; - (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info; - (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo; diff --git a/iPhone/CordovaLib/Classes/CDVCamera.m b/iPhone/CordovaLib/Classes/CDVCamera.m index cfbf415..aabe844 100755 --- a/iPhone/CordovaLib/Classes/CDVCamera.m +++ b/iPhone/CordovaLib/Classes/CDVCamera.m @@ -93,6 +93,13 @@ static NSSet* org_apache_cordova_validArrowDirections; targetSize = CGSizeMake([targetWidth floatValue], [targetHeight floatValue]); } + // If a popover is already open, close it; we only want one at a time. + if (([[self pickerController] popoverController] != nil) && [[[self pickerController] popoverController] isPopoverVisible]) { + [[[self pickerController] popoverController] dismissPopoverAnimated:YES]; + [[[self pickerController] popoverController] setDelegate:nil]; + [[self pickerController] setPopoverController:nil]; + } + CDVCameraPicker* cameraPicker = [[CDVCameraPicker alloc] init]; self.pickerController = cameraPicker; @@ -128,28 +135,8 @@ static NSSet* org_apache_cordova_validArrowDirections; if (cameraPicker.popoverController == nil) { cameraPicker.popoverController = [[NSClassFromString (@"UIPopoverController")alloc] initWithContentViewController:cameraPicker]; } - int x = 0; - int y = 32; - int width = 320; - int height = 480; - UIPopoverArrowDirection arrowDirection = UIPopoverArrowDirectionAny; NSDictionary* options = [command.arguments objectAtIndex:10 withDefault:nil]; - if (options) { - x = [options integerValueForKey:@"x" defaultValue:0]; - y = [options integerValueForKey:@"y" defaultValue:32]; - width = [options integerValueForKey:@"width" defaultValue:320]; - height = [options integerValueForKey:@"height" defaultValue:480]; - arrowDirection = [options integerValueForKey:@"arrowDir" defaultValue:UIPopoverArrowDirectionAny]; - if (![org_apache_cordova_validArrowDirections containsObject:[NSNumber numberWithInt:arrowDirection]]) { - arrowDirection = UIPopoverArrowDirectionAny; - } - } - - cameraPicker.popoverController.delegate = self; - [cameraPicker.popoverController presentPopoverFromRect:CGRectMake(x, y, width, height) - inView:[self.webView superview] - permittedArrowDirections:arrowDirection - animated:YES]; + [self displayPopover:options]; } else { if ([self.viewController respondsToSelector:@selector(presentViewController:::)]) { [self.viewController presentViewController:cameraPicker animated:YES completion:nil]; @@ -160,6 +147,39 @@ static NSSet* org_apache_cordova_validArrowDirections; self.hasPendingOperation = YES; } +- (void)repositionPopover:(CDVInvokedUrlCommand*)command +{ + NSDictionary* options = [command.arguments objectAtIndex:0 withDefault:nil]; + + [self displayPopover:options]; +} + +- (void)displayPopover:(NSDictionary*)options +{ + int x = 0; + int y = 32; + int width = 320; + int height = 480; + UIPopoverArrowDirection arrowDirection = UIPopoverArrowDirectionAny; + + if (options) { + x = [options integerValueForKey:@"x" defaultValue:0]; + y = [options integerValueForKey:@"y" defaultValue:32]; + width = [options integerValueForKey:@"width" defaultValue:320]; + height = [options integerValueForKey:@"height" defaultValue:480]; + arrowDirection = [options integerValueForKey:@"arrowDir" defaultValue:UIPopoverArrowDirectionAny]; + if (![org_apache_cordova_validArrowDirections containsObject:[NSNumber numberWithInt:arrowDirection]]) { + arrowDirection = UIPopoverArrowDirectionAny; + } + } + + [[[self pickerController] popoverController] setDelegate:self]; + [[[self pickerController] popoverController] presentPopoverFromRect:CGRectMake(x, y, width, height) + inView:[self.webView superview] + permittedArrowDirections:arrowDirection + animated:YES]; +} + - (void)cleanup:(CDVInvokedUrlCommand*)command { // empty the tmp directory @@ -232,63 +252,68 @@ static NSSet* org_apache_cordova_validArrowDirections; NSString* mediaType = [info objectForKey:UIImagePickerControllerMediaType]; // IMAGE TYPE if ([mediaType isEqualToString:(NSString*)kUTTypeImage]) { - // get the image - UIImage* image = nil; - if (cameraPicker.allowsEditing && [info objectForKey:UIImagePickerControllerEditedImage]) { - image = [info objectForKey:UIImagePickerControllerEditedImage]; + if (cameraPicker.returnType == DestinationTypeNativeUri) { + NSString* nativeUri = [(NSURL*)[info objectForKey:UIImagePickerControllerReferenceURL] absoluteString]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:nativeUri]; } else { - image = [info objectForKey:UIImagePickerControllerOriginalImage]; - } + // get the image + UIImage* image = nil; + if (cameraPicker.allowsEditing && [info objectForKey:UIImagePickerControllerEditedImage]) { + image = [info objectForKey:UIImagePickerControllerEditedImage]; + } else { + image = [info objectForKey:UIImagePickerControllerOriginalImage]; + } - if (cameraPicker.saveToPhotoAlbum) { - UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil); - } + if (cameraPicker.saveToPhotoAlbum) { + UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil); + } - if (cameraPicker.correctOrientation) { - image = [self imageCorrectedForCaptureOrientation:image]; - } + if (cameraPicker.correctOrientation) { + image = [self imageCorrectedForCaptureOrientation:image]; + } - UIImage* scaledImage = nil; + UIImage* scaledImage = nil; - if ((cameraPicker.targetSize.width > 0) && (cameraPicker.targetSize.height > 0)) { - // if cropToSize, resize image and crop to target size, otherwise resize to fit target without cropping - if (cameraPicker.cropToSize) { - scaledImage = [self imageByScalingAndCroppingForSize:image toSize:cameraPicker.targetSize]; - } else { - scaledImage = [self imageByScalingNotCroppingForSize:image toSize:cameraPicker.targetSize]; + if ((cameraPicker.targetSize.width > 0) && (cameraPicker.targetSize.height > 0)) { + // if cropToSize, resize image and crop to target size, otherwise resize to fit target without cropping + if (cameraPicker.cropToSize) { + scaledImage = [self imageByScalingAndCroppingForSize:image toSize:cameraPicker.targetSize]; + } else { + scaledImage = [self imageByScalingNotCroppingForSize:image toSize:cameraPicker.targetSize]; + } } - } - NSData* data = nil; + NSData* data = nil; - if (cameraPicker.encodingType == EncodingTypePNG) { - data = UIImagePNGRepresentation(scaledImage == nil ? image : scaledImage); - } else { - data = UIImageJPEGRepresentation(scaledImage == nil ? image : scaledImage, cameraPicker.quality / 100.0f); - } + if (cameraPicker.encodingType == EncodingTypePNG) { + data = UIImagePNGRepresentation(scaledImage == nil ? image : scaledImage); + } else { + data = UIImageJPEGRepresentation(scaledImage == nil ? image : scaledImage, cameraPicker.quality / 100.0f); + } - if (cameraPicker.returnType == DestinationTypeFileUri) { - // write to temp directory and return URI - // get the temp directory path - NSString* docsPath = [NSTemporaryDirectory ()stringByStandardizingPath]; - NSError* err = nil; - NSFileManager* fileMgr = [[NSFileManager alloc] init]; // recommended by apple (vs [NSFileManager defaultManager]) to be threadsafe - // generate unique file name - NSString* filePath; - - int i = 1; - do { - filePath = [NSString stringWithFormat:@"%@/%@%03d.%@", docsPath, CDV_PHOTO_PREFIX, i++, cameraPicker.encodingType == EncodingTypePNG ? @"png":@"jpg"]; - } while ([fileMgr fileExistsAtPath:filePath]); - - // save file - if (![data writeToFile:filePath options:NSAtomicWrite error:&err]) { - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]]; + if (cameraPicker.returnType == DestinationTypeFileUri) { + // write to temp directory and return URI + // get the temp directory path + NSString* docsPath = [NSTemporaryDirectory ()stringByStandardizingPath]; + NSError* err = nil; + NSFileManager* fileMgr = [[NSFileManager alloc] init]; // recommended by apple (vs [NSFileManager defaultManager]) to be threadsafe + // generate unique file name + NSString* filePath; + + int i = 1; + do { + filePath = [NSString stringWithFormat:@"%@/%@%03d.%@", docsPath, CDV_PHOTO_PREFIX, i++, cameraPicker.encodingType == EncodingTypePNG ? @"png":@"jpg"]; + } while ([fileMgr fileExistsAtPath:filePath]); + + // save file + if (![data writeToFile:filePath options:NSAtomicWrite error:&err]) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]]; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[NSURL fileURLWithPath:filePath] absoluteString]]; + } } else { - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[[NSURL fileURLWithPath:filePath] absoluteString]]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[data base64EncodedString]]; } - } else { - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[data base64EncodedString]]; } } // NOT IMAGE TYPE (MOVIE) diff --git a/iPhone/CordovaLib/Classes/CDVCapture.m b/iPhone/CordovaLib/Classes/CDVCapture.m index 6d52042..ed9f664 100755 --- a/iPhone/CordovaLib/Classes/CDVCapture.m +++ b/iPhone/CordovaLib/Classes/CDVCapture.m @@ -18,9 +18,8 @@ */ #import "CDVCapture.h" -#import "JSONKit.h" +#import "CDVJSON.h" #import "CDVAvailability.h" -#import "CDVViewController.h" #define kW3CMediaFormatHeight @"height" #define kW3CMediaFormatWidth @"width" @@ -329,7 +328,7 @@ movieArray ? (NSObject*) movieArray:[NSNull null], @"video", audioArray ? (NSObject*) audioArray:[NSNull null], @"audio", nil]; - NSString* jsString = [NSString stringWithFormat:@"navigator.device.capture.setSupportedModes(%@);", [modes cdvjk_JSONString]]; + NSString* jsString = [NSString stringWithFormat:@"navigator.device.capture.setSupportedModes(%@);", [modes JSONString]]; [self.commandDelegate evalJs:jsString]; } diff --git a/iPhone/CordovaLib/Classes/CDVCommandDelegate.h b/iPhone/CordovaLib/Classes/CDVCommandDelegate.h index 4a53f98..e177c63 100755 --- a/iPhone/CordovaLib/Classes/CDVCommandDelegate.h +++ b/iPhone/CordovaLib/Classes/CDVCommandDelegate.h @@ -22,9 +22,12 @@ @class CDVPlugin; @class CDVPluginResult; +@class CDVWhitelist; @protocol CDVCommandDelegate <NSObject> +@property (nonatomic, readonly) NSDictionary* settings; + - (NSString*)pathForResource:(NSString*)resourcepath; - (id)getCommandInstance:(NSString*)pluginName; - (void)registerPlugin:(CDVPlugin*)plugin withClassName:(NSString*)className CDV_DEPRECATED(2.2, "Use CDVViewController to register plugins, or use config.xml."); @@ -44,5 +47,9 @@ - (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 diff --git a/iPhone/CordovaLib/Classes/CDVCommandDelegateImpl.m b/iPhone/CordovaLib/Classes/CDVCommandDelegateImpl.m index 76413bd..e399289 100755 --- a/iPhone/CordovaLib/Classes/CDVCommandDelegateImpl.m +++ b/iPhone/CordovaLib/Classes/CDVCommandDelegateImpl.m @@ -18,7 +18,7 @@ */ #import "CDVCommandDelegateImpl.h" - +#import "CDVJSON.h" #import "CDVCommandQueue.h" #import "CDVPluginResult.h" #import "CDVViewController.h" @@ -78,13 +78,17 @@ - (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; // Use an array to encode the message as JSON. message = [NSArray arrayWithObject:message]; - NSString* encodedMessage = [message cdvjk_JSONString]; + NSString* encodedMessage = [message JSONString]; // And then strip off the outer []s. encodedMessage = [encodedMessage substringWithRange:NSMakeRange(1, [encodedMessage length] - 2)]; NSString* js = [NSString stringWithFormat:@"cordova.require('cordova/exec').nativeCallback('%@',%d,%@,%d)", @@ -128,4 +132,20 @@ 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]; +} + +- (NSDictionary*)settings +{ + return _viewController.settings; +} + @end diff --git a/iPhone/CordovaLib/Classes/CDVCommandQueue.m b/iPhone/CordovaLib/Classes/CDVCommandQueue.m index d88a730..a8a58b7 100755 --- a/iPhone/CordovaLib/Classes/CDVCommandQueue.m +++ b/iPhone/CordovaLib/Classes/CDVCommandQueue.m @@ -87,14 +87,14 @@ for (NSUInteger i = 0; i < [_queue count]; ++i) { // Parse the returned JSON array. - NSArray* commandBatch = [[_queue objectAtIndex:i] cdvjk_mutableObjectFromJSONString]; + NSArray* commandBatch = [[_queue objectAtIndex:i] JSONObject]; // Iterate over and execute all of the commands. for (NSArray* jsonEntry in commandBatch) { CDVInvokedUrlCommand* command = [CDVInvokedUrlCommand commandFromJson:jsonEntry]; if (![self execute:command]) { #ifdef DEBUG - NSString* commandJson = [jsonEntry cdvjk_JSONString]; + NSString* commandJson = [jsonEntry JSONString]; static NSUInteger maxLogLength = 1024; NSString* commandString = ([commandJson length] > maxLogLength) ? [NSString stringWithFormat:@"%@[...]", [commandJson substringToIndex:maxLogLength]] : diff --git a/iPhone/CordovaLib/Classes/CDVConfigParser.h b/iPhone/CordovaLib/Classes/CDVConfigParser.h index 23cdd01..7392580 100755 --- a/iPhone/CordovaLib/Classes/CDVConfigParser.h +++ b/iPhone/CordovaLib/Classes/CDVConfigParser.h @@ -17,11 +17,12 @@ under the License. */ -@interface CDVConfigParser : NSObject <NSXMLParserDelegate> { -} +@interface CDVConfigParser : NSObject <NSXMLParserDelegate>{} @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 diff --git a/iPhone/CordovaLib/Classes/CDVConfigParser.m b/iPhone/CordovaLib/Classes/CDVConfigParser.m index b596c9d..6fd5913 100755 --- a/iPhone/CordovaLib/Classes/CDVConfigParser.m +++ b/iPhone/CordovaLib/Classes/CDVConfigParser.m @@ -24,20 +24,23 @@ @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; +@synthesize pluginsDict, settings, whitelistHosts, startPage, startupPluginNames; - (id)init { self = [super init]; if (self != nil) { - self.pluginsDict = [[NSMutableDictionary alloc] initWithCapacity:4]; - self.settings = [[NSMutableDictionary alloc] initWithCapacity:4]; - self.whitelistHosts = [[NSMutableArray alloc] initWithCapacity:1]; + self.pluginsDict = [[NSMutableDictionary alloc] initWithCapacity:30]; + self.settings = [[NSMutableDictionary alloc] initWithCapacity:30]; + self.whitelistHosts = [[NSMutableArray alloc] initWithCapacity:30]; + self.startupPluginNames = [[NSMutableArray alloc] initWithCapacity:8]; } return self; } @@ -45,11 +48,17 @@ - (void)parser:(NSXMLParser*)parser didStartElement:(NSString*)elementName namespaceURI:(NSString*)namespaceURI qualifiedName:(NSString*)qualifiedName attributes:(NSDictionary*)attributeDict { if ([elementName isEqualToString:@"preference"]) { - [settings setObject:[attributeDict objectForKey:@"value"] forKey:[attributeDict objectForKey:@"name"]]; + settings[attributeDict[@"name"]] = attributeDict[@"value"]; } else if ([elementName isEqualToString:@"plugin"]) { - [pluginsDict setObject:[attributeDict objectForKey:@"value"] forKey:[attributeDict objectForKey:@"name"]]; + NSString* name = [attributeDict[@"name"] lowercaseString]; + pluginsDict[name] = attributeDict[@"value"]; + if ([@"true" isEqualToString:attributeDict[@"onload"]]) { + [self.startupPluginNames addObject:name]; + } } else if ([elementName isEqualToString:@"access"]) { - [whitelistHosts addObject:[attributeDict objectForKey:@"origin"]]; + [whitelistHosts addObject:attributeDict[@"origin"]]; + } else if ([elementName isEqualToString:@"content"]) { + self.startPage = attributeDict[@"src"]; } } diff --git a/iPhone/CordovaLib/Classes/CDVContact.m b/iPhone/CordovaLib/Classes/CDVContact.m index 9389207..9efaf10 100755 --- a/iPhone/CordovaLib/Classes/CDVContact.m +++ b/iPhone/CordovaLib/Classes/CDVContact.m @@ -1146,7 +1146,7 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil; if (fields == nil) { // no name fields requested return nil; } - id __weak value; + CFStringRef value; NSObject* addresses; ABMultiValueRef multi = ABRecordCopyValue(self.record, kABPersonAddressProperty); CFIndex count = multi ? ABMultiValueGetCount(multi) : 0; @@ -1174,7 +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); - [newAddress setObject:(bFound && value != NULL) ? (id) value:[NSNull null] forKey:k]; + 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]; @@ -1221,16 +1227,28 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil; NSMutableDictionary* newDict = [NSMutableDictionary dictionaryWithCapacity:3]; // iOS has label property (work, home, other) for each IM but W3C contact API doesn't use CFDictionaryRef dict = (CFDictionaryRef)ABMultiValueCopyValueAtIndex(multi, i); - NSString* __weak value; // all values should be CFStringRefs / NSString* + CFStringRef value; // all values should be CFStringRefs / NSString* bool bFound; if ([fields containsObject:kW3ContactFieldValue]) { // value = user name bFound = CFDictionaryGetValueIfPresent(dict, kABPersonInstantMessageUsernameKey, (void*)&value); - [newDict setObject:(bFound && value != NULL) ? (id) value:[NSNull null] forKey:kW3ContactFieldValue]; + 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); - [newDict setObject:(bFound && value != NULL) ? (id)[[CDVContact class] convertPropertyLabelToContactType:value]:[NSNull null] forKey:kW3ContactFieldType]; + 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)]; @@ -1311,13 +1329,14 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil; // get the temp directory path NSString* docsPath = [NSTemporaryDirectory ()stringByStandardizingPath]; NSError* err = nil; - NSFileManager* fileMgr = [[NSFileManager alloc] init]; - // generate unique file name - NSString* filePath; - int i = 1; - do { - filePath = [NSString stringWithFormat:@"%@/photo_%03d.jpg", docsPath, i++]; - } while ([fileMgr fileExistsAtPath:filePath]); + NSString* filePath = [NSString stringWithFormat:@"%@/photo_XXXXX", docsPath]; + char template[filePath.length + 1]; + strcpy(template, [filePath cStringUsingEncoding:NSASCIIStringEncoding]); + mkstemp(template); + filePath = [[NSFileManager defaultManager] + stringWithFileSystemRepresentation:template + length:strlen(template)]; + // save file if ([data writeToFile:filePath options:NSAtomicWrite error:&err]) { photos = [NSMutableArray arrayWithCapacity:1]; @@ -1694,7 +1713,7 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil; for (NSString* member in fields) { NSString* abKey = [[CDVContact defaultW3CtoAB] valueForKey:member]; // im and address fields are all strings - NSString* __weak abValue = nil; + CFStringRef abValue = nil; if (abKey) { NSString* testString = nil; if ([member isEqualToString:kW3ContactImType]) { @@ -1708,8 +1727,10 @@ static NSDictionary* org_apache_cordova_contacts_defaultFields = nil; if (testString != nil) { BOOL bExists = CFDictionaryGetValueIfPresent(dict, (__bridge const void*)abKey, (void*)&abValue); if (bExists) { + CFRetain(abValue); NSPredicate* containPred = [NSPredicate predicateWithFormat:@"SELF contains[cd] %@", testString]; - bFound = [containPred evaluateWithObject:abValue]; + bFound = [containPred evaluateWithObject:(__bridge id)abValue]; + CFRelease(abValue); } } } diff --git a/iPhone/CordovaLib/Classes/CDVDevice.m b/iPhone/CordovaLib/Classes/CDVDevice.m index 3fa4bb3..cc7ad89 100755 --- a/iPhone/CordovaLib/Classes/CDVDevice.m +++ b/iPhone/CordovaLib/Classes/CDVDevice.m @@ -58,7 +58,8 @@ NSDictionary* temp = [CDVViewController getBundlePlist:@"Settings"]; if ([temp respondsToSelector:@selector(JSONString)]) { - NSString* js = [NSString stringWithFormat:@"window.Settings = %@;", [temp cdvjk_JSONString]]; + NSLog(@"Deprecation warning: window.Setting will be removed Aug 2013. Refer to https://issues.apache.org/jira/browse/CB-2433"); + NSString* js = [NSString stringWithFormat:@"window.Settings = %@;", [temp JSONString]]; [self.commandDelegate evalJs:js]; } diff --git a/iPhone/CordovaLib/Classes/CDVEcho.m b/iPhone/CordovaLib/Classes/CDVEcho.m index 9cda957..916e315 100755 --- a/iPhone/CordovaLib/Classes/CDVEcho.m +++ b/iPhone/CordovaLib/Classes/CDVEcho.m @@ -24,7 +24,8 @@ - (void)echo:(CDVInvokedUrlCommand*)command { - CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[command.arguments objectAtIndex:0]]; + id message = [command.arguments objectAtIndex:0]; + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:message]; [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } @@ -36,9 +37,18 @@ - (void)echoAsync:(CDVInvokedUrlCommand*)command { - CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[command.arguments objectAtIndex:0]]; + id message = [command.arguments objectAtIndex:0]; + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:message]; [self performSelector:@selector(echoAsyncHelper:) withObject:[NSArray arrayWithObjects:pluginResult, command.callbackId, nil] afterDelay:0]; } +- (void)echoArrayBuffer:(CDVInvokedUrlCommand*)command +{ + id message = [command.arguments objectAtIndex:0]; + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArrayBuffer:message]; + + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + @end diff --git a/iPhone/CordovaLib/Classes/CDVFile.h b/iPhone/CordovaLib/Classes/CDVFile.h index 204c9f0..4862921 100755 --- a/iPhone/CordovaLib/Classes/CDVFile.h +++ b/iPhone/CordovaLib/Classes/CDVFile.h @@ -42,6 +42,8 @@ enum CDVFileSystemType { }; typedef int CDVFileSystemType; +extern NSString* const kCDVAssetsLibraryPrefix; + @interface CDVFile : CDVPlugin { NSString* appDocsPath; NSString* appLibraryPath; diff --git a/iPhone/CordovaLib/Classes/CDVFile.m b/iPhone/CordovaLib/Classes/CDVFile.m index 1e5d009..d52405d 100755 --- a/iPhone/CordovaLib/Classes/CDVFile.m +++ b/iPhone/CordovaLib/Classes/CDVFile.m @@ -20,8 +20,11 @@ #import "CDVFile.h" #import "NSArray+Comparisons.h" #import "NSDictionary+Extensions.h" -#import "JSONKit.h" +#import "CDVJSON.h" #import "NSData+Base64.h" +#import <AssetsLibrary/ALAsset.h> +#import <AssetsLibrary/ALAssetRepresentation.h> +#import <AssetsLibrary/ALAssetsLibrary.h> #import <MobileCoreServices/MobileCoreServices.h> #import "CDVAvailability.h" #import "sys/xattr.h" @@ -32,6 +35,8 @@ extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import) NSString* const NSURLIsExcludedFromBackupKey = @"NSURLIsExcludedFromBackupKey"; #endif +NSString* const kCDVAssetsLibraryPrefix = @"assets-library://"; + @implementation CDVFile @synthesize appDocsPath, appLibraryPath, appTempPath, persistentPath, temporaryPath, userHasAllowed; @@ -329,6 +334,13 @@ extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import) NSString* requestedPath = [command.arguments objectAtIndex:1]; NSDictionary* options = [command.arguments objectAtIndex:2 withDefault:nil]; + // return unsupported result for assets-library URLs + if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"getFile not supported for assets-library URLs."]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } + CDVPluginResult* result = nil; BOOL bDirRequest = NO; BOOL create = NO; @@ -425,6 +437,13 @@ extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import) // arguments are URL encoded NSString* fullPath = [command.arguments objectAtIndex:0]; + // we don't (yet?) support getting the parent of an asset + if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_READABLE_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } + CDVPluginResult* result = nil; NSString* newPath = nil; @@ -461,11 +480,40 @@ extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import) { // arguments NSString* argPath = [command.arguments objectAtIndex:0]; + __block CDVPluginResult* result = nil; + + if ([argPath hasPrefix:kCDVAssetsLibraryPrefix]) { + // In this case, we need to use an asynchronous method to retrieve the file. + // Because of this, we can't just assign to `result` and send it at the end of the method. + // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { + if (asset) { + // We have the asset! Retrieve the metadata and send it off. + NSDate* date = [asset valueForProperty:ALAssetPropertyDate]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:[date timeIntervalSince1970] * 1000]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } else { + // We couldn't find the asset. Send the appropriate error. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } + }; + // TODO(maxw): Consider making this a class variable since it's the same every time. + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { + // Retrieving the asset failed for some reason. Send the appropriate error. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }; + + ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init]; + [assetsLibrary assetForURL:[NSURL URLWithString:argPath] resultBlock:resultBlock failureBlock:failureBlock]; + return; + } + NSString* testPath = argPath; // [self getFullPath: argPath]; NSFileManager* fileMgr = [[NSFileManager alloc] init]; NSError* __autoreleasing error = nil; - CDVPluginResult* result = nil; NSDictionary* fileAttribs = [fileMgr attributesOfItemAtPath:testPath error:&error]; @@ -477,7 +525,7 @@ extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import) } else { // didn't get fileAttribs CDVFileError errorCode = ABORT_ERR; - NSLog(@"error getting metadata: %@", [error localizedDescription]); + NSLog (@"error getting metadata: %@", [error localizedDescription]); if ([error code] == NSFileNoSuchFileError) { errorCode = NOT_FOUND_ERR; } @@ -500,27 +548,29 @@ extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import) // arguments NSString* filePath = [command.arguments objectAtIndex:0]; NSDictionary* options = [command.arguments objectAtIndex:1 withDefault:nil]; - CDVPluginResult* result = nil; BOOL ok = NO; - // we only care about this iCloud key for now. - // set to 1/true to skip backup, set to 0/false to back it up (effectively removing the attribute) - NSString* iCloudBackupExtendedAttributeKey = @"com.apple.MobileBackup"; - id iCloudBackupExtendedAttributeValue = [options objectForKey:iCloudBackupExtendedAttributeKey]; - - if ((iCloudBackupExtendedAttributeValue != nil) && [iCloudBackupExtendedAttributeValue isKindOfClass:[NSNumber class]]) { - if (IsAtLeastiOSVersion(@"5.1")) { - NSURL* url = [NSURL fileURLWithPath:filePath]; - NSError* __autoreleasing error = nil; - - ok = [url setResourceValue:[NSNumber numberWithBool:[iCloudBackupExtendedAttributeValue boolValue]] forKey:NSURLIsExcludedFromBackupKey error:&error]; - } else { // below 5.1 (deprecated - only really supported in 5.01) - u_int8_t value = [iCloudBackupExtendedAttributeValue intValue]; - if (value == 0) { // remove the attribute (allow backup, the default) - ok = (removexattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], 0) == 0); - } else { // set the attribute (skip backup) - ok = (setxattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], &value, sizeof(value), 0, 0) == 0); + // setMetadata doesn't make sense for asset library files + if (![filePath hasPrefix:kCDVAssetsLibraryPrefix]) { + // we only care about this iCloud key for now. + // set to 1/true to skip backup, set to 0/false to back it up (effectively removing the attribute) + NSString* iCloudBackupExtendedAttributeKey = @"com.apple.MobileBackup"; + id iCloudBackupExtendedAttributeValue = [options objectForKey:iCloudBackupExtendedAttributeKey]; + + if ((iCloudBackupExtendedAttributeValue != nil) && [iCloudBackupExtendedAttributeValue isKindOfClass:[NSNumber class]]) { + if (IsAtLeastiOSVersion(@"5.1")) { + NSURL* url = [NSURL fileURLWithPath:filePath]; + NSError* __autoreleasing error = nil; + + ok = [url setResourceValue:[NSNumber numberWithBool:[iCloudBackupExtendedAttributeValue boolValue]] forKey:NSURLIsExcludedFromBackupKey error:&error]; + } else { // below 5.1 (deprecated - only really supported in 5.01) + u_int8_t value = [iCloudBackupExtendedAttributeValue intValue]; + if (value == 0) { // remove the attribute (allow backup, the default) + ok = (removexattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], 0) == 0); + } else { // set the attribute (skip backup) + ok = (setxattr([filePath fileSystemRepresentation], [iCloudBackupExtendedAttributeKey cStringUsingEncoding:NSUTF8StringEncoding], &value, sizeof(value), 0, 0) == 0); + } } } } @@ -539,19 +589,21 @@ extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import) * 0 - NSString* fullPath * * returns NO_MODIFICATION_ALLOWED_ERR if is top level directory or no permission to delete dir - * returns INVALID_MODIFICATION_ERR if is dir and is not empty + * returns INVALID_MODIFICATION_ERR if is non-empty dir or asset library file * returns NOT_FOUND_ERR if file or dir is not found */ - (void)remove:(CDVInvokedUrlCommand*)command { // arguments NSString* fullPath = [command.arguments objectAtIndex:0]; - CDVPluginResult* result = nil; CDVFileError errorCode = 0; // !! 0 not currently defined - // error if try to remove top level (documents or tmp) dir - if ([fullPath isEqualToString:self.appDocsPath] || [fullPath isEqualToString:self.appTempPath]) { + // return error for assets-library URLs + if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) { + errorCode = INVALID_MODIFICATION_ERR; + } else if ([fullPath isEqualToString:self.appDocsPath] || [fullPath isEqualToString:self.appTempPath]) { + // error if try to remove top level (documents or tmp) dir errorCode = NO_MODIFICATION_ALLOWED_ERR; } else { NSFileManager* fileMgr = [[NSFileManager alloc] init]; @@ -587,6 +639,13 @@ extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import) // arguments NSString* fullPath = [command.arguments objectAtIndex:0]; + // return unsupported result for assets-library URLs + if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"removeRecursively not supported for assets-library URLs."]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } + CDVPluginResult* result = nil; // error if try to remove top level (documents or tmp) dir @@ -693,7 +752,7 @@ extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import) // optional argument NSString* newName = ([arguments count] > 2) ? [arguments objectAtIndex:2] : [srcFullPath lastPathComponent]; // use last component from appPath if new name not provided - CDVPluginResult* result = nil; + __block CDVPluginResult* result = nil; CDVFileError errCode = 0; // !! Currently 0 is not defined, use this to signal error !! /*NSString* destRootPath = nil; @@ -710,12 +769,59 @@ extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import) errCode = ENCODING_ERR; } else { NSString* newFullPath = [destRootPath stringByAppendingPathComponent:newName]; + NSFileManager* fileMgr = [[NSFileManager alloc] init]; if ([newFullPath isEqualToString:srcFullPath]) { // source and destination can not be the same errCode = INVALID_MODIFICATION_ERR; - } else { - NSFileManager* fileMgr = [[NSFileManager alloc] init]; + } else if ([srcFullPath hasPrefix:kCDVAssetsLibraryPrefix]) { + if (bCopy) { + // Copying (as opposed to moving) an assets library file is okay. + // In this case, we need to use an asynchronous method to retrieve the file. + // Because of this, we can't just assign to `result` and send it at the end of the method. + // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { + if (asset) { + // We have the asset! Get the data and try to copy it over. + if (![fileMgr fileExistsAtPath:destRootPath]) { + // The destination directory doesn't exist. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } else if ([fileMgr fileExistsAtPath:newFullPath]) { + // A file already exists at the destination path. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:PATH_EXISTS_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } + // We're good to go! Write the file to the new destination. + ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; + Byte* buffer = (Byte*)malloc ([assetRepresentation size]); + NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil]; + NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES]; + [data writeToFile:newFullPath atomically:YES]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self getDirectoryEntry:newFullPath isDirectory:NO]]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } else { + // We couldn't find the asset. Send the appropriate error. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } + }; + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { + // Retrieving the asset failed for some reason. Send the appropriate error. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }; + + ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init]; + [assetsLibrary assetForURL:[NSURL URLWithString:srcFullPath] resultBlock:resultBlock failureBlock:failureBlock]; + return; + } else { + // Moving an assets library file is not doable, since we can't remove it. + errCode = INVALID_MODIFICATION_ERR; + } + } else { BOOL bSrcIsDir = NO; BOOL bDestIsDir = NO; BOOL bNewIsDir = NO; @@ -838,30 +944,67 @@ extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import) // arguments NSString* argPath = [command.arguments objectAtIndex:0]; - CDVPluginResult* result = nil; + __block CDVPluginResult* result = nil; NSString* fullPath = argPath; // [self getFullPath: argPath]; if (fullPath) { - NSFileManager* fileMgr = [[NSFileManager alloc] init]; - BOOL bIsDir = NO; - // make sure it exists and is not a directory - BOOL bExists = [fileMgr fileExistsAtPath:fullPath isDirectory:&bIsDir]; - if (!bExists || bIsDir) { - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) { + // In this case, we need to use an asynchronous method to retrieve the file. + // Because of this, we can't just assign to `result` and send it at the end of the method. + // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { + if (asset) { + // We have the asset! Populate the dictionary and send it off. + NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5]; + ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; + [fileInfo setObject:[NSNumber numberWithUnsignedLongLong:[assetRepresentation size]] forKey:@"size"]; + [fileInfo setObject:argPath forKey:@"fullPath"]; + NSString* filename = [assetRepresentation filename]; + [fileInfo setObject:filename forKey:@"name"]; + [fileInfo setObject:[self getMimeTypeFromPath:filename] forKey:@"type"]; + NSDate* creationDate = [asset valueForProperty:ALAssetPropertyDate]; + NSNumber* msDate = [NSNumber numberWithDouble:[creationDate timeIntervalSince1970] * 1000]; + [fileInfo setObject:msDate forKey:@"lastModifiedDate"]; + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } else { + // We couldn't find the asset. Send the appropriate error. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } + }; + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { + // Retrieving the asset failed for some reason. Send the appropriate error. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }; + + ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init]; + [assetsLibrary assetForURL:[NSURL URLWithString:argPath] resultBlock:resultBlock failureBlock:failureBlock]; + return; } else { - // create dictionary of file info - NSError* __autoreleasing error = nil; - NSDictionary* fileAttrs = [fileMgr attributesOfItemAtPath:fullPath error:&error]; - NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5]; - [fileInfo setObject:[NSNumber numberWithUnsignedLongLong:[fileAttrs fileSize]] forKey:@"size"]; - [fileInfo setObject:argPath forKey:@"fullPath"]; - [fileInfo setObject:@"" forKey:@"type"]; // can't easily get the mimetype unless create URL, send request and read response so skipping - [fileInfo setObject:[argPath lastPathComponent] forKey:@"name"]; - NSDate* modDate = [fileAttrs fileModificationDate]; - NSNumber* msDate = [NSNumber numberWithDouble:[modDate timeIntervalSince1970] * 1000]; - [fileInfo setObject:msDate forKey:@"lastModifiedDate"]; - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo]; + NSFileManager* fileMgr = [[NSFileManager alloc] init]; + BOOL bIsDir = NO; + // make sure it exists and is not a directory + BOOL bExists = [fileMgr fileExistsAtPath:fullPath isDirectory:&bIsDir]; + if (!bExists || bIsDir) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + } else { + // create dictionary of file info + NSError* __autoreleasing error = nil; + NSDictionary* fileAttrs = [fileMgr attributesOfItemAtPath:fullPath error:&error]; + NSMutableDictionary* fileInfo = [NSMutableDictionary dictionaryWithCapacity:5]; + [fileInfo setObject:[NSNumber numberWithUnsignedLongLong:[fileAttrs fileSize]] forKey:@"size"]; + [fileInfo setObject:argPath forKey:@"fullPath"]; + [fileInfo setObject:@"" forKey:@"type"]; // can't easily get the mimetype unless create URL, send request and read response so skipping + [fileInfo setObject:[argPath lastPathComponent] forKey:@"name"]; + NSDate* modDate = [fileAttrs fileModificationDate]; + NSNumber* msDate = [NSNumber numberWithDouble:[modDate timeIntervalSince1970] * 1000]; + [fileInfo setObject:msDate forKey:@"lastModifiedDate"]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:fileInfo]; + } } } if (!result) { @@ -875,6 +1018,13 @@ extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import) // arguments NSString* fullPath = [command.arguments objectAtIndex:0]; + // return unsupported result for assets-library URLs + if ([fullPath hasPrefix:kCDVAssetsLibraryPrefix]) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"readEntries not supported for assets-library URLs."]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } + CDVPluginResult* result = nil; NSFileManager* fileMgr = [[NSFileManager alloc] init]; @@ -908,21 +1058,45 @@ extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import) * NSArray* arguments * 0 - NSString* fullPath * 1 - NSString* encoding - NOT USED, iOS reads and writes using UTF8! + * 2 - NSString* start - OPTIONAL, only provided when not == 0. + * 3 - NSString* end - OPTIONAL, only provided when not == length. */ - (void)readAsText:(CDVInvokedUrlCommand*)command { // arguments NSString* argPath = [command.arguments objectAtIndex:0]; + NSInteger start = 0; + NSInteger end = -1; + + if ([command.arguments count] >= 3) { + start = [[command.arguments objectAtIndex:2] integerValue]; + } + if ([command.arguments count] >= 4) { + end = [[command.arguments objectAtIndex:3] integerValue]; + } + // NSString* encoding = [command.arguments objectAtIndex:2]; // not currently used CDVPluginResult* result = nil; NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:argPath]; - if (!file) { + if ([argPath hasPrefix:kCDVAssetsLibraryPrefix]) { + // can't read assets-library URLs as text + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_READABLE_ERR]; + } else if (!file) { // invalid path entry result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; } else { - NSData* readData = [file readDataToEndOfFile]; + if (start > 0) { + [file seekToFileOffset:start]; + } + + NSData* readData; + if (end < 0) { + readData = [file readDataToEndOfFile]; + } else { + readData = [file readDataOfLength:(end - start)]; + } [file closeFile]; NSString* pNStrBuff = nil; @@ -933,7 +1107,7 @@ extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import) pNStrBuff = @""; } - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[pNStrBuff stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:pNStrBuff]; } [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } @@ -950,12 +1124,51 @@ extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import) { // arguments NSString* argPath = [command.arguments objectAtIndex:0]; + NSInteger start = 0; + NSInteger end = -1; + + if ([command.arguments count] >= 2) { + start = [[command.arguments objectAtIndex:1] integerValue]; + } + if ([command.arguments count] >= 3) { + end = [[command.arguments objectAtIndex:2] integerValue]; + } CDVFileError errCode = ABORT_ERR; - CDVPluginResult* result = nil; + __block CDVPluginResult* result = nil; if (!argPath) { errCode = SYNTAX_ERR; + } else if ([argPath hasPrefix:kCDVAssetsLibraryPrefix]) { + // In this case, we need to use an asynchronous method to retrieve the file. + // Because of this, we can't just assign to `result` and send it at the end of the method. + // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { + if (asset) { + // We have the asset! Get the data and send it off. + ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; + Byte* buffer = (Byte*)malloc ([assetRepresentation size]); + NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil]; + NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES]; + NSString* mimeType = [self getMimeTypeFromPath:[assetRepresentation filename]]; + NSString* dataString = [NSString stringWithFormat:@"data:%@;base64,%@", mimeType, [data base64EncodedString]]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:dataString]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } else { + // We couldn't find the asset. Send the appropriate error. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } + }; + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { + // Retrieving the asset failed for some reason. Send the appropriate error. + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }; + + ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init]; + [assetsLibrary assetForURL:[NSURL URLWithString:argPath] resultBlock:resultBlock failureBlock:failureBlock]; + return; } else { NSString* mimeType = [self getMimeTypeFromPath:argPath]; if (!mimeType) { @@ -963,7 +1176,17 @@ extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import) errCode = ENCODING_ERR; } else { NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:argPath]; - NSData* readData = [file readDataToEndOfFile]; + if (start > 0) { + [file seekToFileOffset:start]; + } + + NSData* readData; + if (end < 0) { + readData = [file readDataToEndOfFile]; + } else { + readData = [file readDataOfLength:(end - start)]; + } + [file closeFile]; if (readData) { NSString* output = [NSString stringWithFormat:@"data:%@;base64,%@", mimeType, [readData base64EncodedString]]; @@ -1014,6 +1237,13 @@ extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import) NSString* argPath = [command.arguments objectAtIndex:0]; unsigned long long pos = (unsigned long long)[[command.arguments objectAtIndex:1] longLongValue]; + // assets-library files can't be truncated + if ([argPath hasPrefix:kCDVAssetsLibraryPrefix]) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } + NSString* appFile = argPath; // [self getFullPath:argPath]; unsigned long long newPos = [self truncateFile:appFile atPosition:pos]; @@ -1054,6 +1284,13 @@ extern NSString * const NSURLIsExcludedFromBackupKey __attribute__((weak_import) NSString* argData = [arguments objectAtIndex:1]; unsigned long long pos = (unsigned long long)[[arguments objectAtIndex:2] longLongValue]; + // text can't be written into assets-library files + if ([argPath hasPrefix:kCDVAssetsLibraryPrefix]) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NO_MODIFICATION_ALLOWED_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } + NSString* fullPath = argPath; // [self getFullPath:argPath]; [self truncateFile:fullPath atPosition:pos]; diff --git a/iPhone/CordovaLib/Classes/CDVFileTransfer.h b/iPhone/CordovaLib/Classes/CDVFileTransfer.h index 76c6a95..f96bb7d 100755 --- a/iPhone/CordovaLib/Classes/CDVFileTransfer.h +++ b/iPhone/CordovaLib/Classes/CDVFileTransfer.h @@ -50,7 +50,8 @@ extern NSString* const kOptionsKeyCookie; - (NSMutableDictionary*)createFileTransferError:(int)code AndSource :(NSString*)source AndTarget :(NSString*)target - AndHttpStatus :(int)httpStatus; + AndHttpStatus :(int)httpStatus + AndBody :(NSString*)body; @property (readonly) NSMutableDictionary* activeTransfers; @end @@ -64,8 +65,10 @@ extern NSString* const kOptionsKeyCookie; @property (nonatomic, copy) NSString* objectId; @property (nonatomic, copy) NSString* source; @property (nonatomic, copy) NSString* target; +@property (nonatomic, copy) NSString* mimeType; @property (assign) int responseCode; // atomic @property (nonatomic, assign) NSInteger bytesTransfered; @property (nonatomic, assign) NSInteger bytesExpected; +@property (nonatomic, assign) BOOL trustAllHosts; @end; diff --git a/iPhone/CordovaLib/Classes/CDVFileTransfer.m b/iPhone/CordovaLib/Classes/CDVFileTransfer.m index b25177a..4ccdce6 100755 --- a/iPhone/CordovaLib/Classes/CDVFileTransfer.m +++ b/iPhone/CordovaLib/Classes/CDVFileTransfer.m @@ -19,7 +19,10 @@ #import "CDV.h" -#include <CFNetwork/CFNetwork.h> +#import <AssetsLibrary/ALAsset.h> +#import <AssetsLibrary/ALAssetRepresentation.h> +#import <AssetsLibrary/ALAssetsLibrary.h> +#import <CFNetwork/CFNetwork.h> @interface CDVFileTransfer () // Sets the requests headers for the request. @@ -27,7 +30,7 @@ // Creates a delegate to handle an upload. - (CDVFileTransferDelegate*)delegateForUploadCommand:(CDVInvokedUrlCommand*)command; // Creates an NSData* for the file for the given upload arguments. -- (NSData*)fileDataForUploadCommand:(CDVInvokedUrlCommand*)command; +- (void)fileDataForUploadCommand:(CDVInvokedUrlCommand*)command; @end // Buffer size to use for streaming uploads. @@ -87,7 +90,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"]; } @@ -130,17 +133,10 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) NSString* fileName = [arguments objectAtIndex:3 withDefault:@"no-filename"]; NSString* mimeType = [arguments objectAtIndex:4 withDefault:nil]; NSDictionary* options = [arguments objectAtIndex:5 withDefault:nil]; - // NSString* trustAllHosts = (NSString*)[arguments objectAtIndex:6]; // allow self-signed certs + // BOOL trustAllHosts = [[arguments objectAtIndex:6 withDefault:[NSNumber numberWithBool:YES]] boolValue]; // allow self-signed certs BOOL chunkedMode = [[arguments objectAtIndex:7 withDefault:[NSNumber numberWithBool:YES]] boolValue]; NSDictionary* headers = [arguments objectAtIndex:8 withDefault:nil]; - // CFStreamCreateBoundPair crashes on iOS < 5. - if (!IsAtLeastiOSVersion(@"5")) { - // TODO(agrieve): See if it's okay license-wise to include the work-around code from: - // http://developer.apple.com/library/ios/#samplecode/SimpleURLConnections/Listings/PostController_m.html - chunkedMode = NO; - } - CDVPluginResult* result = nil; CDVFileTransferError errorCode = 0; @@ -245,6 +241,7 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) { NSString* source = [command.arguments objectAtIndex:0]; NSString* server = [command.arguments objectAtIndex:1]; + BOOL trustAllHosts = [[command.arguments objectAtIndex:6 withDefault:[NSNumber numberWithBool:YES]] boolValue]; // allow self-signed certs NSString* objectId = [command.arguments objectAtIndex:9]; CDVFileTransferDelegate* delegate = [[CDVFileTransferDelegate alloc] init]; @@ -255,29 +252,65 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) delegate.objectId = objectId; delegate.source = source; delegate.target = server; + delegate.trustAllHosts = trustAllHosts; + return delegate; } -- (NSData*)fileDataForUploadCommand:(CDVInvokedUrlCommand*)command +- (void)fileDataForUploadCommand:(CDVInvokedUrlCommand*)command { NSString* target = (NSString*)[command.arguments objectAtIndex:0]; NSError* __autoreleasing err = nil; - // Extract the path part out of a file: URL. - NSString* filePath = [target hasPrefix:@"/"] ? [target copy] : [[NSURL URLWithString:target] path]; - // Memory map the file so that it can be read efficiently even if it is large. - NSData* fileData = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:&err]; + // return unsupported result for assets-library URLs + if ([target hasPrefix:kCDVAssetsLibraryPrefix]) { + // Instead, we return after calling the asynchronous method and send `result` in each of the blocks. + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { + if (asset) { + // We have the asset! Get the data and send it off. + ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; + Byte* buffer = (Byte*)malloc ([assetRepresentation size]); + NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil]; + NSData* fileData = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES]; + [self uploadData:fileData command:command]; + } else { + // We couldn't find the asset. Send the appropriate error. + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsInt:NOT_FOUND_ERR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } + }; + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { + // Retrieving the asset failed for some reason. Send the appropriate error. + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[error localizedDescription]]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }; + + ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init]; + [assetsLibrary assetForURL:[NSURL URLWithString:target] resultBlock:resultBlock failureBlock:failureBlock]; + return; + } else { + // Extract the path part out of a file: URL. + NSString* filePath = [target hasPrefix:@"/"] ? [target copy] : [[NSURL URLWithString:target] path]; + + // Memory map the file so that it can be read efficiently even if it is large. + NSData* fileData = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:&err]; - if (err != nil) { - NSLog(@"Error opening file %@: %@", target, err); + if (err != nil) { + NSLog (@"Error opening file %@: %@", target, err); + } + [self uploadData:fileData command:command]; } - return fileData; } - (void)upload:(CDVInvokedUrlCommand*)command { // fileData and req are split into helper functions to ease the unit testing of delegateForUpload. - NSData* fileData = [self fileDataForUploadCommand:command]; + // First, get the file data. This method will call `uploadData:command`. + [self fileDataForUploadCommand:command]; +} + +- (void)uploadData:(NSData*)fileData command:(CDVInvokedUrlCommand*)command +{ NSURLRequest* req = [self requestForUploadCommand:command fileData:fileData]; if (req == nil) { @@ -302,9 +335,9 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) if (delegate != nil) { [delegate.connection cancel]; [activeTransfers removeObjectForKey:objectId]; - - //delete uncomplete file - NSFileManager *fileMgr = [NSFileManager defaultManager]; + + // delete uncomplete file + NSFileManager* fileMgr = [NSFileManager defaultManager]; [fileMgr removeItemAtPath:delegate.target error:nil]; CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self createFileTransferError:CONNECTION_ABORTED AndSource:delegate.source AndTarget:delegate.target]]; @@ -317,8 +350,16 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) DLog(@"File Transfer downloading file..."); NSString* sourceUrl = [command.arguments objectAtIndex:0]; NSString* filePath = [command.arguments objectAtIndex:1]; - // NSString* trustAllHosts = (NSString*)[arguments objectAtIndex:6]; // allow self-signed certs + BOOL trustAllHosts = [[command.arguments objectAtIndex:2 withDefault:[NSNumber numberWithBool:YES]] boolValue]; // allow self-signed certs NSString* objectId = [command.arguments objectAtIndex:3]; + + // return unsupported result for assets-library URLs + if ([filePath hasPrefix:kCDVAssetsLibraryPrefix]) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_MALFORMED_URL_EXCEPTION messageAsString:@"download not supported for assets-library URLs."]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + return; + } + CDVPluginResult* result = nil; CDVFileTransferError errorCode = 0; @@ -356,6 +397,7 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) delegate.objectId = objectId; delegate.source = sourceUrl; delegate.target = filePath; + delegate.trustAllHosts = trustAllHosts; delegate.connection = [NSURLConnection connectionWithRequest:req delegate:delegate]; @@ -382,13 +424,15 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) AndSource:(NSString*)source AndTarget:(NSString*)target AndHttpStatus:(int)httpStatus + AndBody:(NSString*)body { - NSMutableDictionary* result = [NSMutableDictionary dictionaryWithCapacity:4]; + NSMutableDictionary* result = [NSMutableDictionary dictionaryWithCapacity:5]; [result setObject:[NSNumber numberWithInt:code] forKey:@"code"]; [result setObject:source forKey:@"source"]; [result setObject:target forKey:@"target"]; [result setObject:[NSNumber numberWithInt:httpStatus] forKey:@"http_status"]; + [result setObject:body forKey:@"body"]; NSLog(@"FileTransferError %@", result); return result; @@ -412,7 +456,8 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) - (void)connectionDidFinishLoading:(NSURLConnection*)connection { NSString* uploadResponse = nil; - BOOL downloadResponse; + NSString* downloadResponse = nil; + BOOL downloadWriteOK = NO; NSMutableDictionary* uploadResult; CDVPluginResult* result = nil; NSError* __autoreleasing error = nil; @@ -423,9 +468,10 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) NSLog(@"File Transfer Finished with response code %d", self.responseCode); if (self.direction == CDV_TRANSFER_UPLOAD) { + uploadResponse = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding]; + if ((self.responseCode >= 200) && (self.responseCode < 300)) { // create dictionary to return FileUploadResult object - uploadResponse = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding]; uploadResult = [NSMutableDictionary dictionaryWithCapacity:3]; if (uploadResponse != nil) { [uploadResult setObject:uploadResponse forKey:@"response"]; @@ -434,7 +480,7 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) [uploadResult setObject:[NSNumber numberWithInt:self.responseCode] forKey:@"responseCode"]; result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:uploadResult]; } else { - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[command createFileTransferError:CONNECTION_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode]]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[command createFileTransferError:CONNECTION_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode AndBody:uploadResponse]]; } } if (self.direction == CDV_TRANSFER_DOWNLOAD) { @@ -450,11 +496,12 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) [[NSFileManager defaultManager] createDirectoryAtPath:parentPath withIntermediateDirectories:YES attributes:nil error:nil]; } - downloadResponse = [self.responseData writeToFile:self.target options:NSDataWritingFileProtectionNone error:&error]; + downloadWriteOK = [self.responseData writeToFile:self.target options:NSDataWritingFileProtectionNone error:&error]; - if (downloadResponse == NO) { + if (downloadWriteOK == NO) { // send our results back - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[command createFileTransferError:INVALID_URL_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode]]; + downloadResponse = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[command createFileTransferError:INVALID_URL_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode AndBody:downloadResponse]]; } else { DLog(@"File Transfer Download success"); @@ -465,10 +512,13 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) } @catch(id exception) { // jump back to main thread - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsDictionary:[command createFileTransferError:FILE_NOT_FOUND_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode]]; + downloadResponse = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding]; + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsDictionary:[command createFileTransferError:FILE_NOT_FOUND_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode AndBody:downloadResponse]]; } } else { - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[command createFileTransferError:CONNECTION_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode]]; + downloadResponse = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding]; + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[command createFileTransferError:CONNECTION_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode AndBody:downloadResponse]]; } } @@ -480,23 +530,29 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) - (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response { + self.mimeType = [response MIMEType]; + // required for iOS 4.3, for some reason; response is // a plain NSURLResponse, not the HTTP subclass - if (![response isKindOfClass:[NSHTTPURLResponse class]]) { - self.responseCode = 403; + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { + NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response; + + self.responseCode = [httpResponse statusCode]; self.bytesExpected = [response expectedContentLength]; - return; + } else if ([response.URL isFileURL]) { + NSDictionary* attr = [[NSFileManager defaultManager] attributesOfItemAtPath:[response.URL path] error:nil]; + self.responseCode = 200; + self.bytesExpected = [attr[NSFileSize] longLongValue]; + } else { + self.responseCode = 200; + self.bytesExpected = NSURLResponseUnknownLength; } - - NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response; - - self.responseCode = [httpResponse statusCode]; - self.bytesExpected = [response expectedContentLength]; } - (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error { - CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[command createFileTransferError:CONNECTION_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode]]; + NSString* body = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding]; + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[command createFileTransferError:CONNECTION_ERR AndSource:source AndTarget:target AndHttpStatus:self.responseCode AndBody:body]]; NSLog(@"File Transfer Error: %@", [error localizedDescription]); @@ -537,33 +593,20 @@ static CFIndex WriteDataToStream(NSData* data, CFWriteStreamRef stream) self.bytesTransfered = totalBytesWritten; } -/* TESTING ONLY CODE -// use ONLY for testing with self signed certificates -// uncomment and modify server name in connection didReceiveAuthenticationChallenge -- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace +// for self signed certificates +- (void)connection:(NSURLConnection*)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge { - return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]; -} - -- (void)connection:(NSURLConnection *) connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge -{ - if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) - { - //NSLog(@"challenge host: %@", challenge.protectionSpace.host); - // we only trust our own domain - if ([challenge.protectionSpace.host isEqualToString:@"serverName.domain.com"]){ - NSURLCredential* myCredential = [NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust]; - - [challenge.sender useCredential:myCredential forAuthenticationChallenge:challenge]; - + if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { + if (self.trustAllHosts) { + NSURLCredential* credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; + [challenge.sender useCredential:credential forAuthenticationChallenge:challenge]; } + [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge]; + } else { + [challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge]; } - - [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge]; } -// uncomment the above two methods for testing servers with self signed certificates -// END TESTING ONLY CODE - */ + - (id)init { if ((self = [super init])) { diff --git a/iPhone/CordovaLib/Classes/CDVInAppBrowser.h b/iPhone/CordovaLib/Classes/CDVInAppBrowser.h index 51199ed..9ff460a 100755 --- a/iPhone/CordovaLib/Classes/CDVInAppBrowser.h +++ b/iPhone/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; @@ -41,7 +41,13 @@ @end -@interface CDVInAppBrowserViewController : UIViewController <UIWebViewDelegate>{} +@interface CDVInAppBrowserViewController : UIViewController <UIWebViewDelegate>{ + @private + NSURL* _requestedURL; + NSString* _userAgent; + NSString* _prevUserAgent; + NSInteger _userAgentLockToken; +} @property (nonatomic, strong) IBOutlet UIWebView* webView; @property (nonatomic, strong) IBOutlet UIBarButtonItem* closeButton; @@ -53,19 +59,26 @@ @property (nonatomic, weak) id <CDVScreenOrientationDelegate> orientationDelegate; @property (nonatomic, weak) id <CDVInAppBrowserNavigationDelegate> navigationDelegate; -@property (nonatomic, strong) NSString* userAgent; - (void)close; - (void)navigateTo:(NSURL*)url; - (void)showLocationBar:(BOOL)show; -- (id)initWithUserAgent:(NSString*)userAgent; +- (id)initWithUserAgent:(NSString*)userAgent prevUserAgent:(NSString*)prevUserAgent; @end @interface CDVInAppBrowserOptions : NSObject {} @property (nonatomic, assign) BOOL location; +@property (nonatomic, copy) NSString* presentationstyle; +@property (nonatomic, copy) NSString* transitionstyle; + +@property (nonatomic, assign) BOOL enableviewportscale; +@property (nonatomic, assign) BOOL mediaplaybackrequiresuseraction; +@property (nonatomic, assign) BOOL allowinlinemediaplayback; +@property (nonatomic, assign) BOOL keyboarddisplayrequiresuseraction; +@property (nonatomic, assign) BOOL suppressesincrementalrendering; + (CDVInAppBrowserOptions*)parseOptions:(NSString*)options; diff --git a/iPhone/CordovaLib/Classes/CDVInAppBrowser.m b/iPhone/CordovaLib/Classes/CDVInAppBrowser.m index 22fd1fc..14671a5 100755 --- a/iPhone/CordovaLib/Classes/CDVInAppBrowser.m +++ b/iPhone/CordovaLib/Classes/CDVInAppBrowser.m @@ -19,7 +19,7 @@ #import "CDVInAppBrowser.h" #import "CDVPluginResult.h" -#import "CDVViewController.h" +#import "CDVUserAgentUtil.h" #define kInAppBrowserTargetSelf @"_self" #define kInAppBrowserTargetSystem @"_system" @@ -91,8 +91,8 @@ - (void)openInInAppBrowser:(NSURL*)url withOptions:(NSString*)options { if (self.inAppBrowserViewController == nil) { - NSString* originalUA = [CDVViewController originalUserAgent]; - self.inAppBrowserViewController = [[CDVInAppBrowserViewController alloc] initWithUserAgent:originalUA]; + NSString* originalUA = [CDVUserAgentUtil originalUserAgent]; + self.inAppBrowserViewController = [[CDVInAppBrowserViewController alloc] initWithUserAgent:originalUA prevUserAgent:[self.commandDelegate userAgent]]; self.inAppBrowserViewController.navigationDelegate = self; if ([self.viewController conformsToProtocol:@protocol(CDVScreenOrientationDelegate)]) { @@ -103,6 +103,37 @@ CDVInAppBrowserOptions* browserOptions = [CDVInAppBrowserOptions parseOptions:options]; [self.inAppBrowserViewController showLocationBar:browserOptions.location]; + // Set Presentation Style + UIModalPresentationStyle presentationStyle = UIModalPresentationFullScreen; // default + if (browserOptions.presentationstyle != nil) { + if ([browserOptions.presentationstyle isEqualToString:@"pagesheet"]) { + presentationStyle = UIModalPresentationPageSheet; + } else if ([browserOptions.presentationstyle isEqualToString:@"formsheet"]) { + presentationStyle = UIModalPresentationFormSheet; + } + } + self.inAppBrowserViewController.modalPresentationStyle = presentationStyle; + + // Set Transition Style + UIModalTransitionStyle transitionStyle = UIModalTransitionStyleCoverVertical; // default + if (browserOptions.transitionstyle != nil) { + if ([browserOptions.transitionstyle isEqualToString:@"fliphorizontal"]) { + transitionStyle = UIModalTransitionStyleFlipHorizontal; + } else if ([browserOptions.transitionstyle isEqualToString:@"crossdissolve"]) { + transitionStyle = UIModalTransitionStyleCrossDissolve; + } + } + self.inAppBrowserViewController.modalTransitionStyle = transitionStyle; + + // UIWebView options + self.inAppBrowserViewController.webView.scalesPageToFit = browserOptions.enableviewportscale; + self.inAppBrowserViewController.webView.mediaPlaybackRequiresUserAction = browserOptions.mediaplaybackrequiresuseraction; + self.inAppBrowserViewController.webView.allowsInlineMediaPlayback = browserOptions.allowinlinemediaplayback; + 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]; } @@ -111,18 +142,7 @@ - (void)openInCordovaWebView:(NSURL*)url withOptions:(NSString*)options { - BOOL passesWhitelist = YES; - - if ([self.viewController isKindOfClass:[CDVViewController class]]) { - CDVViewController* vc = (CDVViewController*)self.viewController; - if ([vc.whitelist schemeIsAllowed:[url scheme]]) { - passesWhitelist = [vc.whitelist URLIsAllowed:url]; - } - } else { // something went wrong, we can't get the whitelist - passesWhitelist = NO; - } - - if (passesWhitelist) { + if ([self.commandDelegate URLIsWhitelisted:url]) { NSURLRequest* request = [NSURLRequest requestWithURL:url]; [self.webView loadRequest:request]; } else { // this assumes the InAppBrowser can be excepted from the white-list @@ -172,6 +192,9 @@ [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; } + // Don't recycle the ViewController since it may be consuming a lot of memory. + // Also - this is required for the PDF/User-Agent bug work-around. + self.inAppBrowserViewController = nil; } @end @@ -180,11 +203,12 @@ @implementation CDVInAppBrowserViewController -- (id)initWithUserAgent:(NSString*)userAgent +- (id)initWithUserAgent:(NSString*)userAgent prevUserAgent:(NSString*)prevUserAgent { self = [super init]; if (self != nil) { - self.userAgent = userAgent; + _userAgent = userAgent; + _prevUserAgent = prevUserAgent; [self createViews]; } @@ -199,31 +223,23 @@ webViewBounds.size.height -= FOOTER_HEIGHT; - if (!self.webView) { - // setting the UserAgent must occur before the UIWebView is instantiated. - // This is read per instantiation, so it does not affect the main Cordova UIWebView - NSDictionary* dict = [[NSDictionary alloc] initWithObjectsAndKeys:self.userAgent, @"UserAgent", nil]; - [[NSUserDefaults standardUserDefaults] registerDefaults:dict]; - - self.webView = [[UIWebView alloc] initWithFrame:webViewBounds]; - self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); - - [self.view addSubview:self.webView]; - [self.view sendSubviewToBack:self.webView]; - - self.webView.delegate = self; - self.webView.scalesPageToFit = TRUE; - 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 = [[UIWebView alloc] initWithFrame:webViewBounds]; + self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); + + [self.view addSubview:self.webView]; + [self.view sendSubviewToBack:self.webView]; + + 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.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; self.spinner.alpha = 1.000; @@ -346,11 +362,14 @@ - (void)viewDidUnload { [self.webView loadHTMLString:nil baseURL:nil]; + [CDVUserAgentUtil releaseLock:&_userAgentLockToken]; [super viewDidUnload]; } - (void)close { + [CDVUserAgentUtil releaseLock:&_userAgentLockToken]; + if ([self respondsToSelector:@selector(presentingViewController)]) { [[self presentingViewController] dismissViewControllerAnimated:YES completion:nil]; } else { @@ -366,7 +385,17 @@ { NSURLRequest* request = [NSURLRequest requestWithURL:url]; - [self.webView loadRequest:request]; + _requestedURL = url; + + 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 @@ -392,7 +421,11 @@ [self.spinner startAnimating]; if ((self.navigationDelegate != nil) && [self.navigationDelegate respondsToSelector:@selector(browserLoadStart:)]) { - [self.navigationDelegate browserLoadStart:theWebView.request.URL]; + NSURL* url = theWebView.request.URL; + if (url == nil) { + url = _requestedURL; + } + [self.navigationDelegate browserLoadStart:url]; } } @@ -406,8 +439,25 @@ [self.spinner stopAnimating]; + // Work around a bug where the first time a PDF is opened, all UIWebViews + // reload their User-Agent from NSUserDefaults. + // This work-around makes the following assumptions: + // 1. The app has only a single Cordova Webview. If not, then the app should + // take it upon themselves to load a PDF in the background as a part of + // their start-up flow. + // 2. That the PDF does not require any additional network requests. We change + // the user-agent here back to that of the CDVViewController, so requests + // 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 + 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:)]) { - [self.navigationDelegate browserLoadStop:theWebView.request.URL]; + NSURL* url = theWebView.request.URL; + [self.navigationDelegate browserLoadStop:url]; } } @@ -460,6 +510,12 @@ if (self = [super init]) { // default values self.location = YES; + + self.enableviewportscale = NO; + self.mediaplaybackrequiresuseraction = NO; + self.allowinlinemediaplayback = NO; + self.keyboarddisplayrequiresuseraction = YES; + self.suppressesincrementalrendering = NO; } return self; @@ -478,12 +534,22 @@ if ([keyvalue count] == 2) { NSString* key = [[keyvalue objectAtIndex:0] lowercaseString]; - NSString* value = [keyvalue objectAtIndex:1]; - BOOL valueBool = [[value lowercaseString] isEqualToString:@"yes"]; + NSString* value = [[keyvalue objectAtIndex:1] lowercaseString]; + + BOOL isBoolean = [value isEqualToString:@"yes"] || [value isEqualToString:@"no"]; + NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init]; + [numberFormatter setAllowsFloats:YES]; + BOOL isNumber = [numberFormatter numberFromString:value] != nil; // set the property according to the key name if ([obj respondsToSelector:NSSelectorFromString(key)]) { - [obj setValue:[NSNumber numberWithBool:valueBool] forKey:key]; + if (isNumber) { + [obj setValue:[numberFormatter numberFromString:value] forKey:key]; + } else if (isBoolean) { + [obj setValue:[NSNumber numberWithBool:[value isEqualToString:@"yes"]] forKey:key]; + } else { + [obj setValue:value forKey:key]; + } } } } diff --git a/iPhone/CordovaLib/Classes/CDVInvokedUrlCommand.m b/iPhone/CordovaLib/Classes/CDVInvokedUrlCommand.m index 833baad..6c7a856 100755 --- a/iPhone/CordovaLib/Classes/CDVInvokedUrlCommand.m +++ b/iPhone/CordovaLib/Classes/CDVInvokedUrlCommand.m @@ -18,7 +18,8 @@ */ #import "CDVInvokedUrlCommand.h" -#import "JSONKit.h" +#import "CDVJSON.h" +#import "NSData+Base64.h" @implementation CDVInvokedUrlCommand @@ -58,9 +59,36 @@ _className = className; _methodName = methodName; } + [self massageArguments]; return self; } +- (void)massageArguments +{ + NSMutableArray* newArgs = nil; + + for (NSUInteger i = 0, count = [_arguments count]; i < count; ++i) { + id arg = [_arguments objectAtIndex:i]; + if (![arg isKindOfClass:[NSDictionary class]]) { + continue; + } + NSDictionary* dict = arg; + NSString* type = [dict objectForKey:@"CDVType"]; + if (!type || ![type isEqualToString:@"ArrayBuffer"]) { + continue; + } + NSString* data = [dict objectForKey:@"data"]; + if (!data) { + continue; + } + if (newArgs == nil) { + newArgs = [NSMutableArray arrayWithArray:_arguments]; + _arguments = newArgs; + } + [newArgs replaceObjectAtIndex:i withObject:[NSData dataFromBase64String:data]]; + } +} + - (void)legacyArguments:(NSMutableArray**)legacyArguments andDict:(NSMutableDictionary**)legacyDict { NSMutableArray* newArguments = [NSMutableArray arrayWithArray:_arguments]; diff --git a/iPhone/CordovaLib/Classes/CDVCordovaView.h b/iPhone/CordovaLib/Classes/CDVJSON.h index 6318b21..eaa895e 100755 --- a/iPhone/CordovaLib/Classes/CDVCordovaView.h +++ b/iPhone/CordovaLib/Classes/CDVJSON.h @@ -17,7 +17,14 @@ under the License. */ -#import <UIKit/UIKit.h> +@interface NSArray (CDVJSONSerializing) +- (NSString*)JSONString; +@end + +@interface NSDictionary (CDVJSONSerializing) +- (NSString*)JSONString; +@end -@interface CDVCordovaView : UIWebView {} +@interface NSString (CDVJSONSerializing) +- (id)JSONObject; @end diff --git a/iPhone/CordovaLib/Classes/CDVJSON.m b/iPhone/CordovaLib/Classes/CDVJSON.m new file mode 100755 index 0000000..78267e5 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVJSON.m @@ -0,0 +1,77 @@ +/* + 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 "CDVJSON.h" +#import <Foundation/NSJSONSerialization.h> + +@implementation NSArray (CDVJSONSerializing) + +- (NSString*)JSONString +{ + NSError* error = nil; + NSData* jsonData = [NSJSONSerialization dataWithJSONObject:self + options:NSJSONWritingPrettyPrinted + error:&error]; + + if (error != nil) { + NSLog(@"NSArray JSONString error: %@", [error localizedDescription]); + return nil; + } else { + return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + } +} + +@end + +@implementation NSDictionary (CDVJSONSerializing) + +- (NSString*)JSONString +{ + NSError* error = nil; + NSData* jsonData = [NSJSONSerialization dataWithJSONObject:self + options:NSJSONWritingPrettyPrinted + error:&error]; + + if (error != nil) { + NSLog(@"NSDictionary JSONString error: %@", [error localizedDescription]); + return nil; + } else { + return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + } +} + +@end + +@implementation NSString (CDVJSONSerializing) + +- (id)JSONObject +{ + NSError* error = nil; + id object = [NSJSONSerialization JSONObjectWithData:[self dataUsingEncoding:NSUTF8StringEncoding] + options:kNilOptions + error:&error]; + + if (error != nil) { + NSLog(@"NSString JSONObject error: %@", [error localizedDescription]); + } + + return object; +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVLocalStorage.h b/iPhone/CordovaLib/Classes/CDVLocalStorage.h index e5e3112..cc6613f 100755 --- a/iPhone/CordovaLib/Classes/CDVLocalStorage.h +++ b/iPhone/CordovaLib/Classes/CDVLocalStorage.h @@ -22,7 +22,7 @@ #define kCDVLocalStorageErrorDomain @"kCDVLocalStorageErrorDomain" #define kCDVLocalStorageFileOperationError 1 -@interface CDVLocalStorage : CDVPlugin <UIWebViewDelegate> +@interface CDVLocalStorage : CDVPlugin @property (nonatomic, readonly, strong) NSMutableArray* backupInfo; diff --git a/iPhone/CordovaLib/Classes/CDVLocalStorage.m b/iPhone/CordovaLib/Classes/CDVLocalStorage.m index 217f611..68175f1 100755 --- a/iPhone/CordovaLib/Classes/CDVLocalStorage.m +++ b/iPhone/CordovaLib/Classes/CDVLocalStorage.m @@ -31,21 +31,13 @@ @synthesize backupInfo, webviewDelegate; -- (CDVPlugin*)initWithWebView:(UIWebView*)theWebView settings:(NSDictionary*)classSettings +- (void)pluginInitialize { - self = (CDVLocalStorage*)[super initWithWebView:theWebView settings:classSettings]; - if (self) { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onResignActive) - name:UIApplicationWillResignActiveNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onResignActive) + name:UIApplicationWillResignActiveNotification object:nil]; + BOOL cloudBackup = [@"cloud" isEqualToString:self.commandDelegate.settings[@"BackupWebStorage"]]; - self.backupInfo = [[self class] createBackupInfoWithCloudBackup:[(NSString*)[classSettings objectForKey:@"backupType"] isEqualToString:@"cloud"]]; - - // over-ride current webview delegate (for restore reasons) - self.webviewDelegate = theWebView.delegate; - theWebView.delegate = self; - } - - return self; + self.backupInfo = [[self class] createBackupInfoWithCloudBackup:cloudBackup]; } #pragma mark - @@ -411,37 +403,9 @@ [self onResignActive]; } -#pragma mark - -#pragma mark UIWebviewDelegate implementation and forwarding - -- (void)webViewDidStartLoad:(UIWebView*)theWebView +- (void)onReset { [self restore:nil]; - - return [self.webviewDelegate webViewDidStartLoad:theWebView]; -} - -- (void)webViewDidFinishLoad:(UIWebView*)theWebView -{ - return [self.webviewDelegate webViewDidFinishLoad:theWebView]; -} - -- (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error -{ - return [self.webviewDelegate webView:theWebView didFailLoadWithError:error]; -} - -- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType -{ - return [self.webviewDelegate webView:theWebView shouldStartLoadWithRequest:request navigationType:navigationType]; -} - -#pragma mark - -#pragma mark Over-rides - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; // this will remove all notification unless added using addObserverForName:object:queue:usingBlock: } @end diff --git a/iPhone/CordovaLib/Classes/CDVLocation.m b/iPhone/CordovaLib/Classes/CDVLocation.m index 9814f35..07af30e 100755 --- a/iPhone/CordovaLib/Classes/CDVLocation.m +++ b/iPhone/CordovaLib/Classes/CDVLocation.m @@ -18,7 +18,6 @@ */ #import "CDVLocation.h" -#import "CDVViewController.h" #import "NSArray+Comparisons.h" #pragma mark Constants @@ -477,17 +476,8 @@ // helper method to check the orientation and start updating headings - (void)startHeadingWithFilter:(CLLocationDegrees)filter { - if ([self.locationManager respondsToSelector:@selector(headingOrientation)]) { - UIDeviceOrientation currentOrientation = [[UIDevice currentDevice] orientation]; - if (currentOrientation != UIDeviceOrientationUnknown) { - CDVViewController* cdvViewController = (CDVViewController*)self.viewController; - - if ([cdvViewController supportsOrientation:currentOrientation]) { - self.locationManager.headingOrientation = (CLDeviceOrientation)currentOrientation; - // FYI UIDeviceOrientation and CLDeviceOrientation enums are currently the same - } - } - } + // FYI UIDeviceOrientation and CLDeviceOrientation enums are currently the same + self.locationManager.headingOrientation = (CLDeviceOrientation)self.viewController.interfaceOrientation; self.locationManager.headingFilter = filter; [self.locationManager startUpdatingHeading]; self.headingData.headingStatus = HEADINGSTARTING; diff --git a/iPhone/CordovaLib/Classes/CDVPlugin.h b/iPhone/CordovaLib/Classes/CDVPlugin.h index 8c59a2b..f5b50eb 100755 --- a/iPhone/CordovaLib/Classes/CDVPlugin.h +++ b/iPhone/CordovaLib/Classes/CDVPlugin.h @@ -23,6 +23,7 @@ #import "NSMutableArray+QueueAdditions.h" #import "CDVCommandDelegate.h" +#define CDVPageDidLoadNotification @"CDVPageDidLoadNotification" #define CDVPluginHandleOpenURLNotification @"CDVPluginHandleOpenURLNotification" #define CDVPluginResetNotification @"CDVPluginResetNotification" #define CDVLocalNotification @"CDVLocalNotification" @@ -30,14 +31,13 @@ @interface CDVPlugin : NSObject {} @property (nonatomic, weak) UIWebView* webView; -@property (nonatomic, strong) NSDictionary* settings; @property (nonatomic, weak) UIViewController* viewController; @property (nonatomic, weak) id <CDVCommandDelegate> commandDelegate; @property (readonly, assign) BOOL hasPendingOperation; -- (CDVPlugin*)initWithWebView:(UIWebView*)theWebView settings:(NSDictionary*)classSettings; - (CDVPlugin*)initWithWebView:(UIWebView*)theWebView; +- (void)pluginInitialize; - (void)handleOpenURL:(NSNotification*)notification; - (void)onAppTerminate; diff --git a/iPhone/CordovaLib/Classes/CDVPlugin.m b/iPhone/CordovaLib/Classes/CDVPlugin.m index 53023c7..a42d241 100755 --- a/iPhone/CordovaLib/Classes/CDVPlugin.m +++ b/iPhone/CordovaLib/Classes/CDVPlugin.m @@ -26,16 +26,12 @@ @end @implementation CDVPlugin -@synthesize webView, settings, viewController, commandDelegate, hasPendingOperation; +@synthesize webView, viewController, commandDelegate, hasPendingOperation; +// Do not override these methods. Use pluginInitialize instead. - (CDVPlugin*)initWithWebView:(UIWebView*)theWebView settings:(NSDictionary*)classSettings { - self = [self initWithWebView:theWebView]; - if (self) { - self.settings = classSettings; - self.hasPendingOperation = NO; - } - return self; + return [self initWithWebView:theWebView]; } - (CDVPlugin*)initWithWebView:(UIWebView*)theWebView @@ -48,26 +44,29 @@ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onReset) name:CDVPluginResetNotification object:nil]; self.webView = theWebView; - - // You can listen to more app notifications, see: - // http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UIApplication_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40006728-CH3-DontLinkElementID_4 - - /* - // NOTE: if you want to use these, make sure you uncomment the corresponding notification handler - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onPause) name:UIApplicationDidEnterBackgroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onResume) name:UIApplicationWillEnterForegroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onOrientationWillChange) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onOrientationDidChange) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; - - // Added in 2.3.0+ - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveLocalNotification:) name:CDVLocalNotification object:nil]; - - */ } return self; } +- (void)pluginInitialize +{ + // You can listen to more app notifications, see: + // http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UIApplication_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40006728-CH3-DontLinkElementID_4 + + // NOTE: if you want to use these, make sure you uncomment the corresponding notification handler + + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onPause) name:UIApplicationDidEnterBackgroundNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onResume) name:UIApplicationWillEnterForegroundNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onOrientationWillChange) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onOrientationDidChange) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; + + // Added in 2.3.0 + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveLocalNotification:) name:CDVLocalNotification object:nil]; + + // Added in 2.5.0 + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pageDidLoad:) name:CDVPageDidLoadNotification object:nil]; +} + - (void)dispose { viewController = nil; @@ -140,9 +139,9 @@ } // default implementation does nothing, ideally, we are not registered for notification if we aren't going to do anything. -//- (void)didReceiveLocalNotification:(NSNotification *)notification -//{ +// - (void)didReceiveLocalNotification:(NSNotification *)notification +// { // // UILocalNotification* localNotification = [notification object]; // get the payload as a LocalNotification -//} +// } @end diff --git a/iPhone/CordovaLib/Classes/CDVPluginResult.h b/iPhone/CordovaLib/Classes/CDVPluginResult.h index 6dd7d45..8683205 100755 --- a/iPhone/CordovaLib/Classes/CDVPluginResult.h +++ b/iPhone/CordovaLib/Classes/CDVPluginResult.h @@ -44,7 +44,9 @@ typedef enum { + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsArray:(NSArray*)theMessage; + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsInt:(int)theMessage; + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsDouble:(double)theMessage; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsBool:(BOOL)theMessage; + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsDictionary:(NSDictionary*)theMessage; ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsArrayBuffer:(NSData*)theMessage; + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageToErrorObject:(int)errorCode; + (void)setVerbose:(BOOL)verbose; diff --git a/iPhone/CordovaLib/Classes/CDVPluginResult.m b/iPhone/CordovaLib/Classes/CDVPluginResult.m index 5343dce..d9ba08f 100755 --- a/iPhone/CordovaLib/Classes/CDVPluginResult.m +++ b/iPhone/CordovaLib/Classes/CDVPluginResult.m @@ -18,8 +18,9 @@ */ #import "CDVPluginResult.h" -#import "JSONKit.h" +#import "CDVJSON.h" #import "CDVDebug.h" +#import "NSData+Base64.h" @interface CDVPluginResult () @@ -88,11 +89,26 @@ static NSArray* org_apache_cordova_CommandStatusMsgs; return [[self alloc] initWithStatus:statusOrdinal message:[NSNumber numberWithDouble:theMessage]]; } ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsBool:(BOOL)theMessage +{ + return [[self alloc] initWithStatus:statusOrdinal message:[NSNumber numberWithBool:theMessage]]; +} + + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsDictionary:(NSDictionary*)theMessage { return [[self alloc] initWithStatus:statusOrdinal message:theMessage]; } ++ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAsArrayBuffer:(NSData*)theMessage +{ + NSDictionary* arrDict = [NSDictionary dictionaryWithObjectsAndKeys: + @"ArrayBuffer", @"CDVType", + [theMessage base64EncodedString], @"data", + nil]; + + return [[self alloc] initWithStatus:statusOrdinal message:arrDict]; +} + + (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageToErrorObject:(int)errorCode { NSDictionary* errDict = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:errorCode] forKey:@"code"]; @@ -107,11 +123,23 @@ static NSArray* org_apache_cordova_CommandStatusMsgs; - (NSString*)toJSONString { - NSString* resultString = [[NSDictionary dictionaryWithObjectsAndKeys: - self.status, @"status", - self.message ? self. message:[NSNull null], @"message", - self.keepCallback, @"keepCallback", - nil] cdvjk_JSONString]; + NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys: + self.status, @"status", + self.message ? self. message:[NSNull null], @"message", + self.keepCallback, @"keepCallback", + nil]; + + NSError* error = nil; + NSData* jsonData = [NSJSONSerialization dataWithJSONObject:dict + options:NSJSONWritingPrettyPrinted + error:&error]; + NSString* resultString = nil; + + if (error != nil) { + NSLog(@"toJSONString error: %@", [error localizedDescription]); + } else { + resultString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + } if ([[self class] isVerbose]) { NSLog(@"PluginResult:toJSONString - %@", resultString); diff --git a/iPhone/CordovaLib/Classes/CDVSound.h b/iPhone/CordovaLib/Classes/CDVSound.h index 6551621..8dcf98e 100755 --- a/iPhone/CordovaLib/Classes/CDVSound.h +++ b/iPhone/CordovaLib/Classes/CDVSound.h @@ -98,7 +98,13 @@ typedef NSUInteger CDVMediaMsg; - (BOOL)hasAudioSession; // helper methods -- (CDVAudioFile*)audioFileForResource:(NSString*)resourcePath withId:(NSString*)mediaId; +- (NSURL*)urlForRecording:(NSString*)resourcePath; +- (NSURL*)urlForPlaying:(NSString*)resourcePath; +- (NSURL*)urlForResource:(NSString*)resourcePath CDV_DEPRECATED(2.5, "Use specific api for playing or recording"); + +- (CDVAudioFile*)audioFileForResource:(NSString*)resourcePath withId:(NSString*)mediaId CDV_DEPRECATED(2.5, "Use updated audioFileForResource api"); + +- (CDVAudioFile*)audioFileForResource:(NSString*)resourcePath withId:(NSString*)mediaId doValidation:(BOOL)bValidate forRecording:(BOOL)bRecord; - (BOOL)prepareToPlay:(CDVAudioFile*)audioFile withId:(NSString*)mediaId; - (NSString*)createMediaErrorWithCode:(CDVMediaError)code message:(NSString*)message; diff --git a/iPhone/CordovaLib/Classes/CDVSound.m b/iPhone/CordovaLib/Classes/CDVSound.m index f7a3adf..99515d7 100755 --- a/iPhone/CordovaLib/Classes/CDVSound.m +++ b/iPhone/CordovaLib/Classes/CDVSound.m @@ -18,19 +18,18 @@ */ #import "CDVSound.h" -#import "CDVViewController.h" #import "NSArray+Comparisons.h" +#import "CDVJSON.h" #define DOCUMENTS_SCHEME_PREFIX @"documents://" #define HTTP_SCHEME_PREFIX @"http://" #define HTTPS_SCHEME_PREFIX @"https://" +#define RECORDING_WAV @"wav" @implementation CDVSound @synthesize soundCache, avSession; -// Maps a url for a resource path -// "Naked" resource paths are assumed to be from the www folder as its base - (NSURL*)urlForResource:(NSString*)resourcePath { NSURL* resourceURL = nil; @@ -43,7 +42,8 @@ NSLog(@"Will use resource '%@' from the Internet.", resourcePath); resourceURL = [NSURL URLWithString:resourcePath]; } else if ([resourcePath hasPrefix:DOCUMENTS_SCHEME_PREFIX]) { - filePath = [resourcePath stringByReplacingOccurrencesOfString:DOCUMENTS_SCHEME_PREFIX withString:[NSString stringWithFormat:@"%@/", [CDVViewController applicationDocumentsDirectory]]]; + NSString* docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; + filePath = [resourcePath stringByReplacingOccurrencesOfString:DOCUMENTS_SCHEME_PREFIX withString:[NSString stringWithFormat:@"%@/", docsPath]]; NSLog(@"Will use resource '%@' from the documents folder with path = %@", resourcePath, filePath); } else { // attempt to find file path in www directory @@ -70,9 +70,101 @@ return resourceURL; } -// Creates or gets the cached audio file resource object +// Maps a url for a resource path for recording +- (NSURL*)urlForRecording:(NSString*)resourcePath +{ + NSURL* resourceURL = nil; + NSString* filePath = nil; + NSString* docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; + + // first check for correct extension + if ([[resourcePath pathExtension] caseInsensitiveCompare:RECORDING_WAV] != NSOrderedSame) { + resourceURL = nil; + NSLog(@"Resource for recording must have %@ extension", RECORDING_WAV); + } else if ([resourcePath hasPrefix:DOCUMENTS_SCHEME_PREFIX]) { + // try to find Documents:// resources + filePath = [resourcePath stringByReplacingOccurrencesOfString:DOCUMENTS_SCHEME_PREFIX withString:[NSString stringWithFormat:@"%@/", docsPath]]; + NSLog(@"Will use resource '%@' from the documents folder with path = %@", resourcePath, filePath); + } else { + // if resourcePath is not from FileSystem put in tmp dir, else attempt to use provided resource path + NSString* tmpPath = [NSTemporaryDirectory ()stringByStandardizingPath]; + BOOL isTmp = [resourcePath rangeOfString:tmpPath].location != NSNotFound; + BOOL isDoc = [resourcePath rangeOfString:docsPath].location != NSNotFound; + if (!isTmp && !isDoc) { + // put in temp dir + filePath = [NSString stringWithFormat:@"%@/%@", tmpPath, resourcePath]; + } else { + filePath = resourcePath; + } + } + + if (filePath != nil) { + // create resourceURL + resourceURL = [NSURL fileURLWithPath:filePath]; + } + return resourceURL; +} + +// Maps a url for a resource path for playing +// "Naked" resource paths are assumed to be from the www folder as its base +- (NSURL*)urlForPlaying:(NSString*)resourcePath +{ + NSURL* resourceURL = nil; + NSString* filePath = nil; + + // first try to find HTTP:// or Documents:// resources + + if ([resourcePath hasPrefix:HTTP_SCHEME_PREFIX] || [resourcePath hasPrefix:HTTPS_SCHEME_PREFIX]) { + // if it is a http url, use it + NSLog(@"Will use resource '%@' from the Internet.", resourcePath); + resourceURL = [NSURL URLWithString:resourcePath]; + } else if ([resourcePath hasPrefix:DOCUMENTS_SCHEME_PREFIX]) { + NSString* docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; + filePath = [resourcePath stringByReplacingOccurrencesOfString:DOCUMENTS_SCHEME_PREFIX withString:[NSString stringWithFormat:@"%@/", docsPath]]; + NSLog(@"Will use resource '%@' from the documents folder with path = %@", resourcePath, filePath); + } else { + // attempt to find file path in www directory or LocalFileSystem.TEMPORARY directory + filePath = [self.commandDelegate pathForResource:resourcePath]; + if (filePath == nil) { + // see if this exists in the documents/temp directory from a previous recording + NSString* testPath = [NSString stringWithFormat:@"%@/%@", [NSTemporaryDirectory ()stringByStandardizingPath], resourcePath]; + if ([[NSFileManager defaultManager] fileExistsAtPath:testPath]) { + // inefficient as existence will be checked again below but only way to determine if file exists from previous recording + filePath = testPath; + NSLog(@"Will attempt to use file resource from LocalFileSystem.TEMPORARY directory"); + } else { + // attempt to use path provided + filePath = resourcePath; + NSLog(@"Will attempt to use file resource '%@'", filePath); + } + } else { + NSLog(@"Found resource '%@' in the web folder.", filePath); + } + } + // check that file exists for all but HTTP_SHEME_PREFIX + if (filePath != nil) { + // create resourceURL + resourceURL = [NSURL fileURLWithPath:filePath]; + // try to access file + NSFileManager* fMgr = [NSFileManager defaultManager]; + if (![fMgr fileExistsAtPath:filePath]) { + resourceURL = nil; + NSLog(@"Unknown resource '%@'", resourcePath); + } + } + + return resourceURL; +} + - (CDVAudioFile*)audioFileForResource:(NSString*)resourcePath withId:(NSString*)mediaId { + // will maintain backwards compatibility with original implementation + return [self audioFileForResource:resourcePath withId:mediaId doValidation:YES forRecording:NO]; +} + +// Creates or gets the cached audio file resource object +- (CDVAudioFile*)audioFileForResource:(NSString*)resourcePath withId:(NSString*)mediaId doValidation:(BOOL)bValidate forRecording:(BOOL)bRecord +{ BOOL bError = NO; CDVMediaError errcode = MEDIA_ERR_NONE_SUPPORTED; NSString* errMsg = @""; @@ -87,31 +179,37 @@ } if (audioFile == nil) { // validate resourcePath and create - if ((resourcePath == nil) || ![resourcePath isKindOfClass:[NSString class]] || [resourcePath isEqualToString:@""]) { bError = YES; errcode = MEDIA_ERR_ABORTED; errMsg = @"invalid media src argument"; } else { - resourceURL = [self urlForResource:resourcePath]; + audioFile = [[CDVAudioFile alloc] init]; + audioFile.resourcePath = resourcePath; + audioFile.resourceURL = nil; // validate resourceURL when actually play or record + [[self soundCache] setObject:audioFile forKey:mediaId]; + } + } + if (bValidate && (audioFile.resourceURL == nil)) { + if (bRecord) { + resourceURL = [self urlForRecording:resourcePath]; + } else { + resourceURL = [self urlForPlaying:resourcePath]; } - if (resourceURL == nil) { bError = YES; errcode = MEDIA_ERR_ABORTED; errMsg = [NSString stringWithFormat:@"Cannot use audio file from resource '%@'", resourcePath]; - } - if (bError) { - // jsString = [NSString stringWithFormat: @"%@(\"%@\",%d,%d);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_ERROR, errcode]; - jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:errcode message:errMsg]]; - [self.commandDelegate evalJs:jsString]; } else { - audioFile = [[CDVAudioFile alloc] init]; - audioFile.resourcePath = resourcePath; audioFile.resourceURL = resourceURL; - [[self soundCache] setObject:audioFile forKey:mediaId]; } } + + if (bError) { + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:errcode message:errMsg]]; + [self.commandDelegate evalJs:jsString]; + } + return audioFile; } @@ -141,7 +239,7 @@ [errorDict setObject:[NSNumber numberWithUnsignedInt:code] forKey:@"code"]; [errorDict setObject:message ? message:@"" forKey:@"message"]; - return [errorDict cdvjk_JSONString]; + return [errorDict JSONString]; } - (void)create:(CDVInvokedUrlCommand*)command @@ -149,7 +247,7 @@ NSString* mediaId = [command.arguments objectAtIndex:0]; NSString* resourcePath = [command.arguments objectAtIndex:1]; - CDVAudioFile* audioFile = [self audioFileForResource:resourcePath withId:mediaId]; + CDVAudioFile* audioFile = [self audioFileForResource:resourcePath withId:mediaId doValidation:NO forRecording:NO]; if (audioFile == nil) { NSString* errorMessage = [NSString stringWithFormat:@"Failed to initialize Media file with path %@", resourcePath]; @@ -193,9 +291,8 @@ BOOL bError = NO; NSString* jsString = nil; - CDVAudioFile* audioFile = [self audioFileForResource:resourcePath withId:mediaId]; - - if (audioFile != nil) { + CDVAudioFile* audioFile = [self audioFileForResource:resourcePath withId:mediaId doValidation:YES forRecording:NO]; + if ((audioFile != nil) && (audioFile.resourceURL != nil)) { if (audioFile.player == nil) { bError = [self prepareToPlay:audioFile withId:mediaId]; } @@ -274,7 +371,12 @@ if ([resourceURL isFileURL]) { audioFile.player = [[CDVAudioPlayer alloc] initWithContentsOfURL:resourceURL error:&playerError]; } else { - NSURLRequest* request = [NSURLRequest requestWithURL:resourceURL]; + NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:resourceURL]; + NSString* userAgent = [self.commandDelegate userAgent]; + if (userAgent) { + [request setValue:userAgent forHTTPHeaderField:@"User-Agent"]; + } + NSURLResponse* __autoreleasing response = nil; NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&playerError]; if (playerError) { @@ -411,11 +513,11 @@ #pragma unused(callbackId) NSString* mediaId = [command.arguments objectAtIndex:0]; - CDVAudioFile* audioFile = [self audioFileForResource:[command.arguments objectAtIndex:1] withId:mediaId]; + CDVAudioFile* audioFile = [self audioFileForResource:[command.arguments objectAtIndex:1] withId:mediaId doValidation:YES forRecording:YES]; NSString* jsString = nil; NSString* errorMsg = @""; - if (audioFile != nil) { + if ((audioFile != nil) && (audioFile.resourceURL != nil)) { NSError* __autoreleasing error = nil; if (audioFile.recorder != nil) { @@ -462,9 +564,9 @@ jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_ABORTED message:errorMsg]]; } } else { - // file does not exist - NSLog(@"Could not start recording audio, file '%@' does not exist.", audioFile.resourcePath); - jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_ABORTED message:@"File to record to does not exist"]]; + // file did not validate + NSString* errorMsg = [NSString stringWithFormat:@"Could not record audio at '%@'", audioFile.resourcePath]; + jsString = [NSString stringWithFormat:@"%@(\"%@\",%d,%@);", @"cordova.require('cordova/plugin/Media').onStatus", mediaId, MEDIA_ERROR, [self createMediaErrorWithCode:MEDIA_ERR_ABORTED message:errorMsg]]; } if (jsString) { [self.commandDelegate evalJs:jsString]; diff --git a/iPhone/CordovaLib/Classes/CDVSplashScreen.h b/iPhone/CordovaLib/Classes/CDVSplashScreen.h index b0d8615..a0868a0 100755 --- a/iPhone/CordovaLib/Classes/CDVSplashScreen.h +++ b/iPhone/CordovaLib/Classes/CDVSplashScreen.h @@ -20,7 +20,11 @@ #import <Foundation/Foundation.h> #import "CDVPlugin.h" -@interface CDVSplashScreen : CDVPlugin {} +@interface CDVSplashScreen : CDVPlugin { + UIActivityIndicatorView* _activityView; + UIImageView* _imageView; + UIView* _parentView; +} - (void)show:(CDVInvokedUrlCommand*)command; - (void)hide:(CDVInvokedUrlCommand*)command; diff --git a/iPhone/CordovaLib/Classes/CDVSplashScreen.m b/iPhone/CordovaLib/Classes/CDVSplashScreen.m index 2512328..cba1b53 100755 --- a/iPhone/CordovaLib/Classes/CDVSplashScreen.m +++ b/iPhone/CordovaLib/Classes/CDVSplashScreen.m @@ -18,32 +18,157 @@ */ #import "CDVSplashScreen.h" -#import "CDVViewController.h" + +#define kSplashScreenStateShow 0 +#define kSplashScreenStateHide 1 + +#define kSplashScreenDurationDefault 0.25f @implementation CDVSplashScreen -- (void)__show:(BOOL)show +- (void)pluginInitialize { - // Legacy support - once deprecated classes removed, clean this up - id <UIApplicationDelegate> delegate = [[UIApplication sharedApplication] delegate]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pageDidLoad) name:CDVPageDidLoadNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onOrientationWillChange:) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil]; - if ([delegate respondsToSelector:@selector(viewController)]) { - id vc = [delegate performSelector:@selector(viewController)]; - if ([vc isKindOfClass:[CDVViewController class]]) { - ((CDVViewController*)vc).imageView.hidden = !show; - ((CDVViewController*)vc).activityView.hidden = !show; - } - } + [self show:nil]; } - (void)show:(CDVInvokedUrlCommand*)command { - [self __show:YES]; + [self updateSplashScreenWithState:kSplashScreenStateShow]; } - (void)hide:(CDVInvokedUrlCommand*)command { - [self __show:NO]; + [self updateSplashScreenWithState:kSplashScreenStateHide]; +} + +- (void)pageDidLoad +{ + id autoHideSplashScreenValue = [self.commandDelegate.settings objectForKey:@"AutoHideSplashScreen"]; + + // if value is missing, default to yes + if ((autoHideSplashScreenValue == nil) || [autoHideSplashScreenValue boolValue]) { + [self hide:nil]; + } +} + +- (void)onOrientationWillChange:(NSNotification*)notification +{ + if (_imageView != nil) { + UIInterfaceOrientation orientation = [notification.userInfo[UIApplicationStatusBarOrientationUserInfoKey] intValue]; + [self updateSplashImageForOrientation:orientation]; + } +} + +- (void)createViews +{ + /* + * The Activity View is the top spinning throbber in the status/battery bar. We init it with the default Grey Style. + * + * whiteLarge = UIActivityIndicatorViewStyleWhiteLarge + * white = UIActivityIndicatorViewStyleWhite + * gray = UIActivityIndicatorViewStyleGray + * + */ + NSString* topActivityIndicator = [self.commandDelegate.settings objectForKey:@"TopActivityIndicator"]; + UIActivityIndicatorViewStyle topActivityIndicatorStyle = UIActivityIndicatorViewStyleGray; + + if ([topActivityIndicator isEqualToString:@"whiteLarge"]) { + topActivityIndicatorStyle = UIActivityIndicatorViewStyleWhiteLarge; + } else if ([topActivityIndicator isEqualToString:@"white"]) { + topActivityIndicatorStyle = UIActivityIndicatorViewStyleWhite; + } else if ([topActivityIndicator isEqualToString:@"gray"]) { + topActivityIndicatorStyle = UIActivityIndicatorViewStyleGray; + } + + _activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:topActivityIndicatorStyle]; + _activityView.tag = 2; + _activityView.center = self.viewController.view.center; + [_activityView startAnimating]; + + _imageView = [[UIImageView alloc] init]; + [self.viewController.view addSubview:_imageView]; + [self.viewController.view.superview addSubview:_activityView]; + [self.viewController.view.superview layoutSubviews]; +} + +- (void)updateSplashImageForOrientation:(UIInterfaceOrientation)orientation +{ + // IPHONE (default) + NSString* imageName = @"Default"; + + if (CDV_IsIPhone5()) { + imageName = [imageName stringByAppendingString:@"-568h"]; + } else if (CDV_IsIPad()) { + // set default to portrait upside down + imageName = @"Default-Portrait"; // @"Default-PortraitUpsideDown.png"; + + if (orientation == UIInterfaceOrientationLandscapeLeft) { + imageName = @"Default-Landscape.png"; // @"Default-LandscapeLeft.png"; + } else if (orientation == UIInterfaceOrientationLandscapeRight) { + imageName = @"Default-Landscape.png"; // @"Default-LandscapeRight.png"; + } + } + + _imageView.image = [UIImage imageNamed:imageName]; + _imageView.frame = CGRectMake(0, 0, _imageView.image.size.width, _imageView.image.size.height); +} + +- (void)updateSplashScreenWithState:(int)state +{ + float toAlpha = state == kSplashScreenStateShow ? 1.0f : 0.0f; + BOOL hidden = state == kSplashScreenStateShow ? NO : YES; + + id fadeSplashScreenValue = [self.commandDelegate.settings objectForKey:@"FadeSplashScreen"]; + id fadeSplashScreenDuration = [self.commandDelegate.settings objectForKey:@"FadeSplashScreenDuration"]; + + float fadeDuration = fadeSplashScreenDuration == nil ? kSplashScreenDurationDefault : [fadeSplashScreenDuration floatValue]; + + if ((fadeSplashScreenValue == nil) || ![fadeSplashScreenValue boolValue]) { + fadeDuration = 0; + } + if (hidden && (_imageView == nil)) { + return; + } else if (_imageView == nil) { + [self createViews]; + fadeDuration = 0; + } + + if (!hidden) { + [self updateSplashImageForOrientation:self.viewController.interfaceOrientation]; + } + + if (fadeDuration == 0) { + [_imageView setHidden:hidden]; + [_activityView setHidden:hidden]; + } else { + if (state == kSplashScreenStateShow) { + // reset states + [_imageView setHidden:NO]; + [_activityView setHidden:NO]; + [_imageView setAlpha:0.0f]; + [_activityView setAlpha:0.0f]; + } + + [UIView transitionWithView:self.viewController.view + duration:fadeDuration + options:UIViewAnimationOptionTransitionNone + animations:^(void) { + [_imageView setAlpha:toAlpha]; + [_activityView setAlpha:toAlpha]; + } + completion:^(BOOL finished) { + if (state == kSplashScreenStateHide) { + // Clean-up resources. + [_imageView removeFromSuperview]; + [_activityView removeFromSuperview]; + _imageView = nil; + _activityView = nil; + } + }]; + } } @end diff --git a/iPhone/CordovaLib/Classes/CDVURLProtocol.h b/iPhone/CordovaLib/Classes/CDVURLProtocol.h index ce8df38..5444f6d 100755 --- a/iPhone/CordovaLib/Classes/CDVURLProtocol.h +++ b/iPhone/CordovaLib/Classes/CDVURLProtocol.h @@ -24,9 +24,6 @@ @interface CDVURLProtocol : NSURLProtocol {} -+ (void)registerPGHttpURLProtocol CDV_DEPRECATED (2.0, "This is now a no-op and should be removed."); -+ (void)registerURLProtocol CDV_DEPRECATED (2.0, "This is now a no-op and should be removed."); - + (void)registerViewController:(CDVViewController*)viewController; + (void)unregisterViewController:(CDVViewController*)viewController; @end diff --git a/iPhone/CordovaLib/Classes/CDVURLProtocol.m b/iPhone/CordovaLib/Classes/CDVURLProtocol.m index a645288..1959c77 100755 --- a/iPhone/CordovaLib/Classes/CDVURLProtocol.m +++ b/iPhone/CordovaLib/Classes/CDVURLProtocol.m @@ -17,14 +17,17 @@ under the License. */ +#import <AssetsLibrary/ALAsset.h> +#import <AssetsLibrary/ALAssetRepresentation.h> +#import <AssetsLibrary/ALAssetsLibrary.h> +#import <MobileCoreServices/MobileCoreServices.h> #import "CDVURLProtocol.h" #import "CDVCommandQueue.h" #import "CDVWhitelist.h" #import "CDVViewController.h" +#import "CDVFile.h" @interface CDVHTTPURLResponse : NSHTTPURLResponse -- (id)initWithUnauthorizedURL:(NSURL*)url; -- (id)initWithBlankResponse:(NSURL*)url; @property (nonatomic) NSInteger statusCode; @end @@ -106,7 +109,9 @@ static CDVViewController *viewControllerForRequest(NSURLRequest* request) NSURL* theUrl = [theRequest URL]; CDVViewController* viewController = viewControllerForRequest(theRequest); - if (viewController != nil) { + if ([[theUrl absoluteString] hasPrefix:kCDVAssetsLibraryPrefix]) { + return YES; + } else if (viewController != nil) { if ([[theUrl path] isEqualToString:@"/!gap_exec"]) { NSString* queuedCommandsJSON = [theRequest valueForHTTPHeaderField:@"cmds"]; NSString* requestId = [theRequest valueForHTTPHeaderField:@"rc"]; @@ -151,21 +156,35 @@ static CDVViewController *viewControllerForRequest(NSURLRequest* request) NSURL* url = [[self request] URL]; if ([[url path] isEqualToString:@"/!gap_exec"]) { - CDVHTTPURLResponse* response = [[CDVHTTPURLResponse alloc] initWithBlankResponse:url]; - [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; - [[self client] URLProtocolDidFinishLoading:self]; + [self sendResponseWithResponseCode:200 data:nil mimeType:nil]; + return; + } else if ([[url absoluteString] hasPrefix:kCDVAssetsLibraryPrefix]) { + ALAssetsLibraryAssetForURLResultBlock resultBlock = ^(ALAsset * asset) { + if (asset) { + // We have the asset! Get the data and send it along. + ALAssetRepresentation* assetRepresentation = [asset defaultRepresentation]; + NSString* MIMEType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass ((__bridge CFStringRef)[assetRepresentation UTI], kUTTagClassMIMEType); + Byte* buffer = (Byte*)malloc ([assetRepresentation size]); + NSUInteger bufferSize = [assetRepresentation getBytes:buffer fromOffset:0.0 length:[assetRepresentation size] error:nil]; + NSData* data = [NSData dataWithBytesNoCopy:buffer length:bufferSize freeWhenDone:YES]; + [self sendResponseWithResponseCode:200 data:data mimeType:MIMEType]; + } else { + // Retrieving the asset failed for some reason. Send an error. + [self sendResponseWithResponseCode:404 data:nil mimeType:nil]; + } + }; + ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError * error) { + // Retrieving the asset failed for some reason. Send an error. + [self sendResponseWithResponseCode:401 data:nil mimeType:nil]; + }; + + ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init]; + [assetsLibrary assetForURL:url resultBlock:resultBlock failureBlock:failureBlock]; return; } NSString* body = [gWhitelist errorStringForURL:url]; - - CDVHTTPURLResponse* response = [[CDVHTTPURLResponse alloc] initWithUnauthorizedURL:url]; - - [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; - - [[self client] URLProtocol:self didLoadData:[body dataUsingEncoding:NSASCIIStringEncoding]]; - - [[self client] URLProtocolDidFinishLoading:self]; + [self sendResponseWithResponseCode:401 data:[body dataUsingEncoding:NSASCIIStringEncoding] mimeType:nil]; } - (void)stopLoading @@ -178,29 +197,31 @@ static CDVViewController *viewControllerForRequest(NSURLRequest* request) return NO; } -@end - -@implementation CDVHTTPURLResponse -@synthesize statusCode; - -- (id)initWithUnauthorizedURL:(NSURL*)url +- (void)sendResponseWithResponseCode:(NSInteger)statusCode data:(NSData*)data mimeType:(NSString*)mimeType { - self = [super initWithURL:url MIMEType:@"text/plain" expectedContentLength:-1 textEncodingName:@"UTF-8"]; - if (self) { - self.statusCode = 401; + if (mimeType == nil) { + mimeType = @"text/plain"; } - return self; -} + NSString* encodingName = [@"text/plain" isEqualToString:mimeType] ? @"UTF-8" : nil; + CDVHTTPURLResponse* response = + [[CDVHTTPURLResponse alloc] initWithURL:[[self request] URL] + MIMEType:mimeType + expectedContentLength:[data length] + textEncodingName:encodingName]; + response.statusCode = statusCode; -- (id)initWithBlankResponse:(NSURL*)url -{ - self = [super initWithURL:url MIMEType:@"text/plain" expectedContentLength:-1 textEncodingName:@"UTF-8"]; - if (self) { - self.statusCode = 200; + [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; + if (data != nil) { + [[self client] URLProtocol:self didLoadData:data]; } - return self; + [[self client] URLProtocolDidFinishLoading:self]; } +@end + +@implementation CDVHTTPURLResponse +@synthesize statusCode; + - (NSDictionary*)allHeaderFields { return nil; diff --git a/iPhone/CordovaLib/Classes/CDVCordovaView.m b/iPhone/CordovaLib/Classes/CDVUserAgentUtil.h index 3dff80a..4de382f 100755 --- a/iPhone/CordovaLib/Classes/CDVCordovaView.m +++ b/iPhone/CordovaLib/Classes/CDVUserAgentUtil.h @@ -17,21 +17,11 @@ under the License. */ -#import "CDVCordovaView.h" - -@implementation CDVCordovaView - -- (void)loadRequest:(NSURLRequest*)request -{ - [super loadRequest:request]; -} - -/* -// Only override drawRect: if you perform custom drawing. -// An empty implementation adversely affects performance during animation. -- (void)drawRect:(CGRect)rect { - // Drawing code. -} -*/ +#import <Foundation/Foundation.h> +@interface CDVUserAgentUtil : NSObject ++ (NSString*)originalUserAgent; ++ (void)acquireLock:(void (^)(NSInteger lockToken))block; ++ (void)releaseLock:(NSInteger*)lockToken; ++ (void)setUserAgent:(NSString*)value lockToken:(NSInteger)lockToken; @end diff --git a/iPhone/CordovaLib/Classes/CDVUserAgentUtil.m b/iPhone/CordovaLib/Classes/CDVUserAgentUtil.m new file mode 100755 index 0000000..5c43c51 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVUserAgentUtil.m @@ -0,0 +1,120 @@ +/* + 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 "CDVUserAgentUtil.h" + +#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 NSInteger gNextLockToken = 0; +static NSInteger gCurrentLockToken = 0; +static NSMutableArray* gPendingSetUserAgentBlocks = nil; + +@implementation CDVUserAgentUtil + ++ (NSString*)originalUserAgent +{ + if (gOriginalUserAgent == nil) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppLocaleDidChange:) + name:NSCurrentLocaleDidChangeNotification object:nil]; + + NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; + NSString* systemVersion = [[UIDevice currentDevice] systemVersion]; + NSString* localeStr = [[NSLocale currentLocale] localeIdentifier]; + NSString* systemAndLocale = [NSString stringWithFormat:@"%@ %@", systemVersion, localeStr]; + + NSString* cordovaUserAgentVersion = [userDefaults stringForKey:kCdvUserAgentVersionKey]; + gOriginalUserAgent = [userDefaults stringForKey:kCdvUserAgentKey]; + BOOL cachedValueIsOld = ![systemAndLocale isEqualToString:cordovaUserAgentVersion]; + + if ((gOriginalUserAgent == nil) || cachedValueIsOld) { + UIWebView* sampleWebView = [[UIWebView alloc] initWithFrame:CGRectZero]; + gOriginalUserAgent = [sampleWebView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"]; + + [userDefaults setObject:gOriginalUserAgent forKey:kCdvUserAgentKey]; + [userDefaults setObject:systemAndLocale forKey:kCdvUserAgentVersionKey]; + + [userDefaults synchronize]; + } + } + return gOriginalUserAgent; +} + ++ (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 +{ + if (*lockToken == 0) { + return; + } + 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; + } + *lockToken = 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:value, @"UserAgent", nil]; + [[NSUserDefaults standardUserDefaults] registerDefaults:dict]; +} + +@end diff --git a/iPhone/CordovaLib/Classes/CDVViewController.h b/iPhone/CordovaLib/Classes/CDVViewController.h index 8318398..82e22f6 100755 --- a/iPhone/CordovaLib/Classes/CDVViewController.h +++ b/iPhone/CordovaLib/Classes/CDVViewController.h @@ -17,9 +17,8 @@ under the License. */ -#import "CDVCordovaView.h" - -#import "JSONKit.h" +#import <UIKit/UIKit.h> +#import <Foundation/NSJSONSerialization.h> #import "CDVAvailability.h" #import "CDVInvokedUrlCommand.h" #import "CDVCommandDelegate.h" @@ -37,19 +36,15 @@ NSString* _userAgent; } -@property (nonatomic, strong) IBOutlet CDVCordovaView* webView; +@property (nonatomic, strong) IBOutlet UIWebView* webView; @property (nonatomic, readonly, strong) NSMutableDictionary* pluginObjects; @property (nonatomic, readonly, strong) NSDictionary* pluginsMap; -@property (nonatomic, readonly, strong) NSDictionary* settings; +@property (nonatomic, readonly, strong) NSMutableDictionary* settings; @property (nonatomic, readonly, strong) NSXMLParser* configParser; @property (nonatomic, readonly, strong) CDVWhitelist* whitelist; // readonly for public @property (nonatomic, readonly, assign) BOOL loadFromString; -@property (nonatomic, readwrite, copy)NSString * invokeString CDV_DEPRECATED(2.0, "Use window.handleOpenURL(url instead. It is called when the app is launched through a custom scheme url."); - -@property (nonatomic, readwrite, assign) BOOL useSplashScreen; -@property (nonatomic, readonly, strong) IBOutlet UIActivityIndicatorView* activityView; -@property (nonatomic, readonly, strong) UIImageView* imageView; +@property (nonatomic, readwrite, assign) BOOL useSplashScreen CDV_DEPRECATED(2.5, "Add/Remove the SplashScreen plugin instead of setting this property."); @property (nonatomic, readwrite, copy) NSString* wwwFolderName; @property (nonatomic, readwrite, copy) NSString* startPage; @@ -59,11 +54,10 @@ + (NSDictionary*)getBundlePlist:(NSString*)plistName; + (NSString*)applicationDocumentsDirectory; -+ (NSString*)originalUserAgent; - (void)printMultitaskingInfo; - (void)createGapView; -- (CDVCordovaView*)newCordovaViewWithFrame:(CGRect)bounds; +- (UIWebView*)newCordovaViewWithFrame:(CGRect)bounds; - (void)javascriptAlert:(NSString*)text; - (NSString*)appURLScheme; diff --git a/iPhone/CordovaLib/Classes/CDVViewController.m b/iPhone/CordovaLib/Classes/CDVViewController.m index f1b36df..bec716d 100755 --- a/iPhone/CordovaLib/Classes/CDVViewController.m +++ b/iPhone/CordovaLib/Classes/CDVViewController.m @@ -22,36 +22,38 @@ #import "CDVCommandQueue.h" #import "CDVCommandDelegateImpl.h" #import "CDVConfigParser.h" +#import "CDVUserAgentUtil.h" +#import "CDVWebViewDelegate.h" #define degreesToRadian(x) (M_PI * (x) / 180.0) -#define CDV_USER_AGENT_KEY @"Cordova-User-Agent" -#define CDV_USER_AGENT_VERSION_KEY @"Cordova-User-Agent-Version" -static NSString* gOriginalUserAgent = nil; - -@interface CDVViewController () +@interface CDVViewController () { + NSInteger _userAgentLockToken; + CDVWebViewDelegate* _webViewDelegate; +} @property (nonatomic, readwrite, strong) NSXMLParser* configParser; -@property (nonatomic, readwrite, strong) NSDictionary* settings; +@property (nonatomic, readwrite, strong) NSMutableDictionary* settings; @property (nonatomic, readwrite, strong) CDVWhitelist* whitelist; @property (nonatomic, readwrite, strong) NSMutableDictionary* pluginObjects; +@property (nonatomic, readwrite, strong) NSArray* startupPluginNames; @property (nonatomic, readwrite, strong) NSDictionary* pluginsMap; @property (nonatomic, readwrite, strong) NSArray* supportedOrientations; @property (nonatomic, readwrite, assign) BOOL loadFromString; -@property (nonatomic, readwrite, strong) IBOutlet UIActivityIndicatorView* activityView; -@property (nonatomic, readwrite, strong) UIImageView* imageView; @property (readwrite, assign) BOOL initialized; +@property (atomic, strong) NSURL* openURL; + @end @implementation CDVViewController @synthesize webView, supportedOrientations; -@synthesize pluginObjects, pluginsMap, whitelist; +@synthesize pluginObjects, pluginsMap, whitelist, startupPluginNames; @synthesize configParser, settings, loadFromString; -@synthesize imageView, activityView, useSplashScreen; -@synthesize wwwFolderName, startPage, invokeString, initialized; +@synthesize useSplashScreen; +@synthesize wwwFolderName, startPage, initialized, openURL; @synthesize commandDelegate = _commandDelegate; @synthesize commandQueue = _commandQueue; @@ -60,38 +62,30 @@ static NSString* gOriginalUserAgent = nil; if ((self != nil) && !self.initialized) { _commandQueue = [[CDVCommandQueue alloc] initWithViewController:self]; _commandDelegate = [[CDVCommandDelegateImpl alloc] initWithViewController:self]; - [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivedOrientationChange) - name:UIDeviceOrientationDidChangeNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillTerminate:) name:UIApplicationWillTerminateNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppLocaleDidChange:) - name:NSCurrentLocaleDidChangeNotification object:nil]; - - if (IsAtLeastiOSVersion(@"4.0")) { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillEnterForeground:) - name:UIApplicationWillEnterForegroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppDidEnterBackground:) - name:UIApplicationDidEnterBackgroundNotification object:nil]; - } + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillEnterForeground:) + name:UIApplicationWillEnterForegroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppDidEnterBackground:) + name:UIApplicationDidEnterBackgroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleOpenURL:) name:CDVPluginHandleOpenURLNotification object:nil]; // read from UISupportedInterfaceOrientations (or UISupportedInterfaceOrientations~iPad, if its iPad) from -Info.plist self.supportedOrientations = [self parseInterfaceOrientations: [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UISupportedInterfaceOrientations"]]; - self.wwwFolderName = @"www"; - self.startPage = @"index.html"; - [self printMultitaskingInfo]; [self printDeprecationNotice]; self.initialized = YES; // load config.xml settings [self loadSettings]; + useSplashScreen = YES; } } @@ -167,12 +161,20 @@ static NSString* gOriginalUserAgent = nil; [configParser parse]; // Get the plugin dictionary, whitelist and settings from the delegate. - self.pluginsMap = [delegate.pluginsDict dictionaryWithLowercaseKeys]; + self.pluginsMap = delegate.pluginsDict; + self.startupPluginNames = delegate.startupPluginNames; self.whitelist = [[CDVWhitelist alloc] initWithArray:delegate.whitelistHosts]; self.settings = delegate.settings; + // And the start folder/page. + self.wwwFolderName = @"www"; + self.startPage = delegate.startPage; + if (self.startPage == nil) { + self.startPage = @"index.html"; + } + // Initialize the plugin objects dict. - self.pluginObjects = [[NSMutableDictionary alloc] initWithCapacity:4]; + self.pluginObjects = [[NSMutableDictionary alloc] initWithCapacity:20]; } // Implement viewDidLoad to do additional setup after loading the view, typically from a nib. @@ -203,13 +205,14 @@ static NSString* gOriginalUserAgent = nil; NSString* backupWebStorageType = @"cloud"; // default value - id backupWebStorage = [self.settings objectForKey:@"BackupWebStorage"]; + id backupWebStorage = self.settings[@"BackupWebStorage"]; if ([backupWebStorage isKindOfClass:[NSString class]]) { backupWebStorageType = backupWebStorage; } else if ([backupWebStorage isKindOfClass:[NSNumber class]]) { NSLog(@"Deprecated: BackupWebStorage boolean property is a string property now (none, local, cloud). A boolean value of 'true' will be mapped to 'cloud'. Consult the docs: http://docs.cordova.io/en/edge/guide_project-settings_ios_index.md.html#Project%%20Settings%%20for%%20iOS"); backupWebStorageType = [(NSNumber*) backupWebStorage boolValue] ? @"cloud" : @"none"; } + self.settings[@"BackupWebStorage"] = backupWebStorageType; if (IsAtLeastiOSVersion(@"5.1")) { [CDVLocalStorage __fixupDatabaseLocationsWithBackupType:backupWebStorageType]; @@ -244,8 +247,7 @@ static NSString* gOriginalUserAgent = nil; */ if (IsAtLeastiOSVersion(@"5.1") && (([backupWebStorageType isEqualToString:@"local"]) || ([backupWebStorageType isEqualToString:@"cloud"] && !IsAtLeastiOSVersion(@"6.0")))) { - [self registerPlugin:[[CDVLocalStorage alloc] initWithWebView:self.webView settings:[NSDictionary dictionaryWithObjectsAndKeys: - @"backupType", backupWebStorageType, nil]] withClassName:NSStringFromClass([CDVLocalStorage class])]; + [self registerPlugin:[[CDVLocalStorage alloc] initWithWebView:self.webView] withClassName:NSStringFromClass([CDVLocalStorage class])]; } /* @@ -305,15 +307,27 @@ static NSString* gOriginalUserAgent = nil; } } - // ///////////////// + for (NSString* pluginName in self.startupPluginNames) { + [self getCommandInstance:pluginName]; + } - 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]; + // TODO: Remove this explicit instantiation once we move to cordova-CLI. + if (useSplashScreen) { + [self getCommandInstance:@"splashscreen"]; } + + // ///////////////// + [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 @@ -411,55 +425,15 @@ static NSString* gOriginalUserAgent = nil; return [self.supportedOrientations containsObject:[NSNumber numberWithInt:orientation]]; } -/** - Called by UIKit when the device starts to rotate to a new orientation. This fires the \c setOrientation - method on the Orientation object in JavaScript. Look at the JavaScript documentation for more information. - */ -- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation -{ - if (!IsAtLeastiOSVersion(@"5.0")) { - NSString* jsCallback = [NSString stringWithFormat: - @"window.__defineGetter__('orientation',function(){ return %d; }); \ - cordova.fireWindowEvent('orientationchange');" - , [self mapIosOrientationToJsOrientation:fromInterfaceOrientation]]; - [self.commandDelegate evalJs:jsCallback]; - } -} - -- (CDVCordovaView*)newCordovaViewWithFrame:(CGRect)bounds -{ - return [[CDVCordovaView alloc] initWithFrame:bounds]; -} - -+ (NSString*)originalUserAgent +- (UIWebView*)newCordovaViewWithFrame:(CGRect)bounds { - if (gOriginalUserAgent == nil) { - NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; - NSString* systemVersion = [[UIDevice currentDevice] systemVersion]; - NSString* localeStr = [[NSLocale currentLocale] localeIdentifier]; - NSString* systemAndLocale = [NSString stringWithFormat:@"%@ %@", systemVersion, localeStr]; - - NSString* cordovaUserAgentVersion = [userDefaults stringForKey:CDV_USER_AGENT_VERSION_KEY]; - gOriginalUserAgent = [userDefaults stringForKey:CDV_USER_AGENT_KEY]; - BOOL cachedValueIsOld = ![systemAndLocale isEqualToString:cordovaUserAgentVersion]; - - if ((gOriginalUserAgent == nil) || cachedValueIsOld) { - UIWebView* sampleWebView = [[UIWebView alloc] initWithFrame:CGRectZero]; - gOriginalUserAgent = [sampleWebView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"]; - - [userDefaults setObject:gOriginalUserAgent forKey:CDV_USER_AGENT_KEY]; - [userDefaults setObject:systemAndLocale forKey:CDV_USER_AGENT_VERSION_KEY]; - - [userDefaults synchronize]; - } - } - return gOriginalUserAgent; + return [[UIWebView alloc] initWithFrame:bounds]; } - (NSString*)userAgent { if (_userAgent == nil) { - NSString* originalUserAgent = [[self class] originalUserAgent]; + NSString* originalUserAgent = [CDVUserAgentUtil originalUserAgent]; // Use our address as a unique number to append to the User-Agent. _userAgent = [NSString stringWithFormat:@"%@ (%lld)", originalUserAgent, (long long)self]; } @@ -473,18 +447,14 @@ static NSString* gOriginalUserAgent = nil; webViewBounds.origin = self.view.bounds.origin; if (!self.webView) { - // setting the UserAgent must occur before the UIWebView is instantiated. - // This is read per instantiation, so it does not affect the main Cordova UIWebView - NSDictionary* dict = [[NSDictionary alloc] initWithObjectsAndKeys:self.userAgent, @"UserAgent", nil]; - [[NSUserDefaults standardUserDefaults] registerDefaults:dict]; - self.webView = [self newCordovaViewWithFrame:webViewBounds]; self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); [self.view addSubview:self.webView]; [self.view sendSubviewToBack:self.webView]; - self.webView.delegate = self; + _webViewDelegate = [[CDVWebViewDelegate alloc] initWithDelegate:self]; + self.webView.delegate = _webViewDelegate; // register this viewcontroller with the NSURLProtocol, only after the User-Agent is set [CDVURLProtocol registerViewController:self]; @@ -523,6 +493,7 @@ static NSString* gOriginalUserAgent = nil; self.webView.delegate = nil; self.webView = nil; + [CDVUserAgentUtil releaseLock:&_userAgentLockToken]; } #pragma mark UIWebViewDelegate @@ -533,43 +504,41 @@ static NSString* gOriginalUserAgent = nil; */ - (void)webViewDidStartLoad:(UIWebView*)theWebView { + NSLog(@"Resetting plugins due to page load."); [_commandQueue resetRequestId]; - [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginResetNotification object:nil]]; + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginResetNotification object:self.webView]]; } /** - Called when the webview finishes loading. This stops the activity view and closes the imageview + Called when the webview finishes loading. This stops the activity view. */ - (void)webViewDidFinishLoad:(UIWebView*)theWebView { + NSLog(@"Finished load of: %@", theWebView.request.URL); + // It's safe to release the lock even if this is just a sub-frame that's finished loading. + [CDVUserAgentUtil releaseLock:&_userAgentLockToken]; + + // The .onNativeReady().fire() will work when cordova.js is already loaded. + // The _nativeReady = true; is used when this is run before cordova.js is loaded. + NSString* nativeReady = @"try{cordova.require('cordova/channel').onNativeReady.fire();}catch(e){window._nativeReady = true;}"; + // Don't use [commandDelegate evalJs] here since it relies on cordova.js being loaded already. + [self.webView stringByEvaluatingJavaScriptFromString:nativeReady]; + /* * Hide the Top Activity THROBBER in the Battery Bar */ [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; - id autoHideSplashScreenValue = [self.settings objectForKey:@"AutoHideSplashScreen"]; - // if value is missing, default to yes - if ((autoHideSplashScreenValue == nil) || [autoHideSplashScreenValue boolValue]) { - self.imageView.hidden = YES; - self.activityView.hidden = YES; - [self.view.superview bringSubviewToFront:self.webView]; - } - [self didRotateFromInterfaceOrientation:(UIInterfaceOrientation)[[UIDevice currentDevice] orientation]]; + [self processOpenUrl]; - // The .onNativeReady().fire() will work when cordova.js is already loaded. - // The _nativeReady = true; is used when this is run before cordova.js is loaded. - NSString* nativeReady = @"try{cordova.require('cordova/channel').onNativeReady.fire();}catch(e){window._nativeReady = true;}"; - [self.commandDelegate evalJs:nativeReady]; + [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPageDidLoadNotification object:nil]]; } -- (void)webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error +- (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error { - NSLog(@"Failed to load webpage with error: %@", [error localizedDescription]); + [CDVUserAgentUtil releaseLock:&_userAgentLockToken]; - /* - if ([error code] != NSURLErrorCancelled) - alert([error localizedDescription]); - */ + NSLog(@"Failed to load webpage with error: %@", [error localizedDescription]); } - (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType @@ -625,8 +594,6 @@ static NSString* gOriginalUserAgent = nil; * Handle all other types of urls (tel:, sms:), and requests to load a url in the main webview. */ else { - // BOOL isIFrame = ([theWebView.request.mainDocumentURL absoluteString] == nil); - if ([self.whitelist schemeIsAllowed:[url scheme]]) { return [self.whitelist URLIsAllowed:url]; } else { @@ -677,131 +644,6 @@ static NSString* gOriginalUserAgent = nil; return basePath; } -- (void)showSplashScreen -{ - CGRect screenBounds = [[UIScreen mainScreen] bounds]; - NSString* launchImageFile = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UILaunchImageFile"]; - - if (launchImageFile == nil) { // fallback if no launch image was specified - if (CDV_IsIPhone5()) { - // iPhone 5 or iPod Touch 6th-gen - launchImageFile = @"Default-568h"; - } else { - launchImageFile = @"Default"; - } - } - - NSString* orientedLaunchImageFile = nil; - CGAffineTransform startupImageTransform = CGAffineTransformIdentity; - UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation; - CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame; - UIInterfaceOrientation statusBarOrientation = [UIApplication sharedApplication].statusBarOrientation; - UIImage* launchImage = nil; - - // default to center of screen as in the original implementation. This will produce the 20px jump - CGPoint center = CGPointMake((screenBounds.size.width / 2), (screenBounds.size.height / 2)); - - if (CDV_IsIPad()) { - if (!UIDeviceOrientationIsValidInterfaceOrientation(deviceOrientation)) { - deviceOrientation = (UIDeviceOrientation)statusBarOrientation; - } - - switch (deviceOrientation) { - case UIDeviceOrientationLandscapeLeft: // this is where the home button is on the right (yeah, I know, confusing) - { - orientedLaunchImageFile = [NSString stringWithFormat:@"%@-Landscape", launchImageFile]; - startupImageTransform = CGAffineTransformMakeRotation(degreesToRadian(90)); - center.x -= MIN(statusBarFrame.size.width, statusBarFrame.size.height) / 2; - } - break; - - case UIDeviceOrientationLandscapeRight: // this is where the home button is on the left (yeah, I know, confusing) - { - orientedLaunchImageFile = [NSString stringWithFormat:@"%@-Landscape", launchImageFile]; - startupImageTransform = CGAffineTransformMakeRotation(degreesToRadian(-90)); - center.x += MIN(statusBarFrame.size.width, statusBarFrame.size.height) / 2; - } - break; - - case UIDeviceOrientationPortraitUpsideDown: - { - orientedLaunchImageFile = [NSString stringWithFormat:@"%@-Portrait", launchImageFile]; - startupImageTransform = CGAffineTransformMakeRotation(degreesToRadian(180)); - center.y -= MIN(statusBarFrame.size.width, statusBarFrame.size.height) / 2; - } - break; - - case UIDeviceOrientationPortrait: - default: - { - orientedLaunchImageFile = [NSString stringWithFormat:@"%@-Portrait", launchImageFile]; - startupImageTransform = CGAffineTransformIdentity; - center.y += MIN(statusBarFrame.size.width, statusBarFrame.size.height) / 2; - } - break; - } - } else { // not iPad - orientedLaunchImageFile = launchImageFile; - } - - launchImage = [UIImage imageNamed:[[self class] resolveImageResource:orientedLaunchImageFile]]; - if (launchImage == nil) { - NSLog(@"WARNING: Splash-screen image '%@' was not found. Orientation: %d, iPad: %d", orientedLaunchImageFile, deviceOrientation, CDV_IsIPad()); - } - - self.imageView = [[UIImageView alloc] initWithImage:launchImage]; - self.imageView.tag = 1; - self.imageView.center = center; - - self.imageView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin); - [self.imageView setTransform:startupImageTransform]; - [self.view.superview addSubview:self.imageView]; - - /* - * The Activity View is the top spinning throbber in the status/battery bar. We init it with the default Grey Style. - * - * whiteLarge = UIActivityIndicatorViewStyleWhiteLarge - * white = UIActivityIndicatorViewStyleWhite - * gray = UIActivityIndicatorViewStyleGray - * - */ - NSString* topActivityIndicator = [self.settings objectForKey:@"TopActivityIndicator"]; - UIActivityIndicatorViewStyle topActivityIndicatorStyle = UIActivityIndicatorViewStyleGray; - - if ([topActivityIndicator isEqualToString:@"whiteLarge"]) { - topActivityIndicatorStyle = UIActivityIndicatorViewStyleWhiteLarge; - } else if ([topActivityIndicator isEqualToString:@"white"]) { - topActivityIndicatorStyle = UIActivityIndicatorViewStyleWhite; - } else if ([topActivityIndicator isEqualToString:@"gray"]) { - topActivityIndicatorStyle = UIActivityIndicatorViewStyleGray; - } - - self.activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:topActivityIndicatorStyle]; - self.activityView.tag = 2; - - id showSplashScreenSpinnerValue = [self.settings objectForKey:@"ShowSplashScreenSpinner"]; - // backwards compatibility - if key is missing, default to true - if ((showSplashScreenSpinnerValue == nil) || [showSplashScreenSpinnerValue boolValue]) { - [self.view.superview addSubview:self.activityView]; - } - - self.activityView.center = self.view.center; - [self.activityView startAnimating]; - - [self.view.superview layoutSubviews]; -} - -BOOL gSplashScreenShown = NO; -- (void)receivedOrientationChange -{ - if (self.imageView == nil) { - gSplashScreenShown = YES; - if (self.useSplashScreen) { - [self showSplashScreen]; - } - } -} - #pragma mark CordovaCommands - (void)registerPlugin:(CDVPlugin*)plugin withClassName:(NSString*)className @@ -815,6 +657,7 @@ BOOL gSplashScreenShown = NO; } [self.pluginObjects setObject:plugin forKey:className]; + [plugin pluginInitialize]; } /** @@ -835,16 +678,9 @@ BOOL gSplashScreenShown = NO; id obj = [self.pluginObjects objectForKey:className]; if (!obj) { - // attempt to load the settings for this command class - NSDictionary* classSettings = [self.settings objectForKey:className]; - - if (classSettings) { - obj = [[NSClassFromString (className)alloc] initWithWebView:webView settings:classSettings]; - } else { - obj = [[NSClassFromString (className)alloc] initWithWebView:webView]; - } + obj = [[NSClassFromString (className)alloc] initWithWebView:webView]; - if ((obj != nil) && [obj isKindOfClass:[CDVPlugin class]]) { + if (obj != nil) { [self registerPlugin:obj withClassName:className]; } else { NSLog(@"CDVPlugin class %@ (pluginName: %@) does not exist.", className, pluginName); @@ -956,9 +792,21 @@ BOOL gSplashScreenShown = NO; [self.commandDelegate evalJs:@"cordova.fireDocumentEvent('pause', null, true);" scheduledOnRunLoop:NO]; } -- (void)onAppLocaleDidChange:(NSNotification*)notification +// /////////////////////// + +- (void)handleOpenURL:(NSNotification*)notification +{ + self.openURL = notification.object; +} + +- (void)processOpenUrl { - gOriginalUserAgent = nil; + if (self.openURL) { + // calls into javascript global function 'handleOpenURL' + NSString* jsString = [NSString stringWithFormat:@"handleOpenURL(\"%@\");", [self.openURL description]]; + [self.webView stringByEvaluatingJavaScriptFromString:jsString]; + self.openURL = nil; + } } // /////////////////////// @@ -972,9 +820,10 @@ BOOL gSplashScreenShown = NO; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:NSCurrentLocaleDidChangeNotification object:nil]; - + [[NSNotificationCenter defaultCenter] removeObserver:self name:CDVPluginHandleOpenURLNotification object:nil]; self.webView.delegate = nil; self.webView = nil; + [CDVUserAgentUtil releaseLock:&_userAgentLockToken]; [_commandQueue dispose]; [[self.pluginObjects allValues] makeObjectsPerformSelector:@selector(dispose)]; } diff --git a/iPhone/CordovaLib/Classes/CDVWebViewDelegate.h b/iPhone/CordovaLib/Classes/CDVWebViewDelegate.h new file mode 100755 index 0000000..8a89a22 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVWebViewDelegate.h @@ -0,0 +1,37 @@ +/* + 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> + +/** + * 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 CDVWebViewDelegate : NSObject <UIWebViewDelegate>{ + __weak NSObject <UIWebViewDelegate>* _delegate; + NSInteger _loadCount; + NSInteger _state; + NSInteger _curLoadToken; +} + +- (id)initWithDelegate:(NSObject <UIWebViewDelegate>*)delegate; + +@end diff --git a/iPhone/CordovaLib/Classes/CDVWebViewDelegate.m b/iPhone/CordovaLib/Classes/CDVWebViewDelegate.m new file mode 100755 index 0000000..9ee8186 --- /dev/null +++ b/iPhone/CordovaLib/Classes/CDVWebViewDelegate.m @@ -0,0 +1,157 @@ +/* + 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 "CDVWebViewDelegate.h" +#import "CDVAvailability.h" + +typedef enum { + STATE_NORMAL, + STATE_SHOULD_LOAD_MISSING, + STATE_WAITING_FOR_START, + STATE_WAITING_FOR_FINISH +} State; + +@implementation CDVWebViewDelegate + +- (id)initWithDelegate:(NSObject <UIWebViewDelegate>*)delegate +{ + self = [super init]; + if (self != nil) { + _delegate = delegate; + _loadCount = -1; + _state = STATE_NORMAL; + } + return self; +} + +- (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:@"%d", _curLoadToken] isEqualToString:loadToken]; +} + +- (void)setLoadToken:(UIWebView*)webView +{ + _curLoadToken += 1; + [webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"window.__cordovaLoadToken=%d", _curLoadToken]]; +} + +- (void)pollForPageLoadStart:(UIWebView*)webView +{ + if ((_state != STATE_WAITING_FOR_START) && (_state != STATE_SHOULD_LOAD_MISSING)) { + return; + } + if (![self isJsLoadTokenSet:webView]) { + _state = STATE_WAITING_FOR_FINISH; + [self setLoadToken:webView]; + [_delegate webViewDidStartLoad:webView]; + [self pollForPageLoadFinish:webView]; + } +} + +- (void)pollForPageLoadFinish:(UIWebView*)webView +{ + if (_state != STATE_WAITING_FOR_FINISH) { + return; + } + if ([self isPageLoaded:webView]) { + _state = STATE_SHOULD_LOAD_MISSING; + [_delegate webViewDidFinishLoad:webView]; + } else { + [self performSelector:@selector(pollForPageLoaded) withObject:webView afterDelay:50]; + } +} + +- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType +{ + BOOL shouldLoad = [_delegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType]; + + if (shouldLoad) { + BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]]; + if (isTopLevelNavigation) { + _loadCount = 0; + _state = STATE_NORMAL; + } + } + return shouldLoad; +} + +- (void)webViewDidStartLoad:(UIWebView*)webView +{ + if (_state == STATE_NORMAL) { + if (_loadCount == 0) { + [_delegate webViewDidStartLoad:webView]; + _loadCount += 1; + } else if (_loadCount > 0) { + _loadCount += 1; + } else if (!IsAtLeastiOSVersion(@"6.0")) { + // If history.go(-1) is used pre-iOS6, the shouldStartLoadWithRequest function is not called. + // Without shouldLoad, we can't distinguish an iframe from a top-level navigation. + // We could try to distinguish using [UIWebView canGoForward], but that's too much complexity, + // and would work only on the first time it was used. + + // Our work-around is to set a JS variable and poll until it disappears (from a naviagtion). + _state = STATE_WAITING_FOR_START; + [self setLoadToken:webView]; + } + } else { + [self pollForPageLoadStart:webView]; + [self pollForPageLoadFinish:webView]; + } +} + +- (void)webViewDidFinishLoad:(UIWebView*)webView +{ + if (_state == STATE_NORMAL) { + if (_loadCount == 1) { + [_delegate webViewDidFinishLoad:webView]; + _loadCount -= 1; + } else if (_loadCount > 1) { + _loadCount -= 1; + } + } else { + [self pollForPageLoadStart:webView]; + [self pollForPageLoadFinish:webView]; + } +} + +- (void)webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error +{ + if (_state == STATE_NORMAL) { + if (_loadCount == 1) { + [_delegate webView:webView didFailLoadWithError:error]; + _loadCount -= 1; + } else if (_loadCount > 1) { + _loadCount -= 1; + } + } else { + [self pollForPageLoadStart:webView]; + [self pollForPageLoadFinish:webView]; + } +} + +@end diff --git a/iPhone/CordovaLib/Classes/JSON/JSONKit.h b/iPhone/CordovaLib/Classes/JSON/JSONKit.h deleted file mode 100755 index 2b245cd..0000000 --- a/iPhone/CordovaLib/Classes/JSON/JSONKit.h +++ /dev/null @@ -1,251 +0,0 @@ -// -// JSONKit.h -// http://github.com/johnezang/JSONKit -// Dual licensed under either the terms of the BSD License, or alternatively -// under the terms of the Apache License, Version 2.0, as specified below. -// - -/* - Copyright (c) 2011, John Engelhart - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the name of the Zang Industries nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -/* - Copyright 2011 John Engelhart - - Licensed 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 <stddef.h> -#include <stdint.h> -#include <limits.h> -#include <TargetConditionals.h> -#include <AvailabilityMacros.h> - -#ifdef __OBJC__ -#import <Foundation/NSArray.h> -#import <Foundation/NSData.h> -#import <Foundation/NSDictionary.h> -#import <Foundation/NSError.h> -#import <Foundation/NSObjCRuntime.h> -#import <Foundation/NSString.h> -#endif // __OBJC__ - -#ifdef __cplusplus -extern "C" { -#endif - - -// For Mac OS X < 10.5. -#ifndef NSINTEGER_DEFINED -#define NSINTEGER_DEFINED -#if defined(__LP64__) || defined(NS_BUILD_32_LIKE_64) -typedef long NSInteger; -typedef unsigned long NSUInteger; -#define NSIntegerMin LONG_MIN -#define NSIntegerMax LONG_MAX -#define NSUIntegerMax ULONG_MAX -#else // defined(__LP64__) || defined(NS_BUILD_32_LIKE_64) -typedef int NSInteger; -typedef unsigned int NSUInteger; -#define NSIntegerMin INT_MIN -#define NSIntegerMax INT_MAX -#define NSUIntegerMax UINT_MAX -#endif // defined(__LP64__) || defined(NS_BUILD_32_LIKE_64) -#endif // NSINTEGER_DEFINED - - -#ifndef _CDVJSONKIT_H_ -#define _CDVJSONKIT_H_ - -#if defined(__GNUC__) && (__GNUC__ >= 4) && defined(__APPLE_CC__) && (__APPLE_CC__ >= 5465) -#define CDVJK_DEPRECATED_ATTRIBUTE __attribute__((deprecated)) -#else -#define CDVJK_DEPRECATED_ATTRIBUTE -#endif - -#define CDVJSONKIT_VERSION_MAJOR 1 -#define CDVJSONKIT_VERSION_MINOR 4 - -typedef NSUInteger CDVJKFlags; - -/* - CDVJKParseOptionComments : Allow C style // and /_* ... *_/ (without a _, obviously) comments in JSON. - CDVJKParseOptionUnicodeNewlines : Allow Unicode recommended (?:\r\n|[\n\v\f\r\x85\p{Zl}\p{Zp}]) newlines. - CDVJKParseOptionLooseUnicode : Normally the decoder will stop with an error at any malformed Unicode. - This option allows JSON with malformed Unicode to be parsed without reporting an error. - Any malformed Unicode is replaced with \uFFFD, or "REPLACEMENT CHARACTER". - */ - -enum { - CDVJKParseOptionNone = 0, - CDVJKParseOptionStrict = 0, - CDVJKParseOptionComments = (1 << 0), - CDVJKParseOptionUnicodeNewlines = (1 << 1), - CDVJKParseOptionLooseUnicode = (1 << 2), - CDVJKParseOptionPermitTextAfterValidJSON = (1 << 3), - CDVJKParseOptionValidFlags = (CDVJKParseOptionComments | CDVJKParseOptionUnicodeNewlines | CDVJKParseOptionLooseUnicode | CDVJKParseOptionPermitTextAfterValidJSON), -}; -typedef CDVJKFlags CDVJKParseOptionFlags; - -enum { - CDVJKSerializeOptionNone = 0, - CDVJKSerializeOptionPretty = (1 << 0), - CDVJKSerializeOptionEscapeUnicode = (1 << 1), - CDVJKSerializeOptionEscapeForwardSlashes = (1 << 4), - CDVJKSerializeOptionValidFlags = (CDVJKSerializeOptionPretty | CDVJKSerializeOptionEscapeUnicode | CDVJKSerializeOptionEscapeForwardSlashes), -}; -typedef CDVJKFlags CDVJKSerializeOptionFlags; - -#ifdef __OBJC__ - -typedef struct CDVJKParseState CDVJKParseState; // Opaque internal, private type. - -// As a general rule of thumb, if you use a method that doesn't accept a CDVJKParseOptionFlags argument, it defaults to CDVJKParseOptionStrict - -@interface CDVJSONDecoder : NSObject { - CDVJKParseState *parseState; -} -+ (id)decoder; -+ (id)decoderWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags; -- (id)initWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags; -- (void)clearCache; - -// The parse... methods were deprecated in v1.4 in favor of the v1.4 objectWith... methods. -- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length CDVJK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithUTF8String:length: instead. -- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length error:(NSError **)error CDVJK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithUTF8String:length:error: instead. -// The NSData MUST be UTF8 encoded JSON. -- (id)parseJSONData:(NSData *)jsonData CDVJK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithData: instead. -- (id)parseJSONData:(NSData *)jsonData error:(NSError **)error CDVJK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithData:error: instead. - -// Methods that return immutable collection objects. -- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length; -- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error; -// The NSData MUST be UTF8 encoded JSON. -- (id)objectWithData:(NSData *)jsonData; -- (id)objectWithData:(NSData *)jsonData error:(NSError **)error; - -// Methods that return mutable collection objects. -- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length; -- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error; -// The NSData MUST be UTF8 encoded JSON. -- (id)mutableObjectWithData:(NSData *)jsonData; -- (id)mutableObjectWithData:(NSData *)jsonData error:(NSError **)error; - -@end - -//////////// -#pragma mark Deserializing methods -//////////// - -@interface NSString (CDVJSONKitDeserializing) -- (id)cdvjk_objectFromJSONString; -- (id)cdvjk_objectFromJSONStringWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags; -- (id)cdvjk_objectFromJSONStringWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags error:(NSError **)error; -- (id)cdvjk_mutableObjectFromJSONString; -- (id)cdvjk_mutableObjectFromJSONStringWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags; -- (id)cdvjk_mutableObjectFromJSONStringWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags error:(NSError **)error; -@end - -@interface NSData (CDVJSONKitDeserializing) -// The NSData MUST be UTF8 encoded JSON. -- (id)cdvjk_objectFromJSONData; -- (id)cdvjk_objectFromJSONDataWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags; -- (id)cdvjk_objectFromJSONDataWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags error:(NSError **)error; -- (id)cdvjk_mutableObjectFromJSONData; -- (id)cdvjk_mutableObjectFromJSONDataWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags; -- (id)cdvjk_mutableObjectFromJSONDataWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags error:(NSError **)error; -@end - -//////////// -#pragma mark Serializing methods -//////////// - -@interface NSString (CDVJSONKitSerializing) -// Convenience methods for those that need to serialize the receiving NSString (i.e., instead of having to serialize a NSArray with a single NSString, you can "serialize to JSON" just the NSString). -// Normally, a string that is serialized to JSON has quotation marks surrounding it, which you may or may not want when serializing a single string, and can be controlled with includeQuotes: -// includeQuotes:YES `a "test"...` -> `"a \"test\"..."` -// includeQuotes:NO `a "test"...` -> `a \"test\"...` -- (NSData *)cdvjk_JSONData; // Invokes JSONDataWithOptions:CDVJKSerializeOptionNone includeQuotes:YES -- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error; -- (NSString *)cdvjk_JSONString; // Invokes JSONStringWithOptions:CDVJKSerializeOptionNone includeQuotes:YES -- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error; -@end - -@interface NSArray (CDVJSONKitSerializing) -- (NSData *)cdvjk_JSONData; -- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions error:(NSError **)error; -- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error; -- (NSString *)cdvjk_JSONString; -- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions error:(NSError **)error; -- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error; -@end - -@interface NSDictionary (CDVJSONKitSerializing) -- (NSData *)cdvjk_JSONData; -- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions error:(NSError **)error; -- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error; -- (NSString *)cdvjk_JSONString; -- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions error:(NSError **)error; -- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error; -@end - -#ifdef __BLOCKS__ - -@interface NSArray (CDVJSONKitSerializingBlockAdditions) -- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error; -- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error; -@end - -@interface NSDictionary (CDVJSONKitSerializingBlockAdditions) -- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error; -- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error; -@end - -#endif - - -#endif // __OBJC__ - -#endif // _CDVJSONKIT_H_ - -#ifdef __cplusplus -} // extern "C" -#endif diff --git a/iPhone/CordovaLib/Classes/JSON/JSONKit.m b/iPhone/CordovaLib/Classes/JSON/JSONKit.m deleted file mode 100755 index 15a7171..0000000 --- a/iPhone/CordovaLib/Classes/JSON/JSONKit.m +++ /dev/null @@ -1,3061 +0,0 @@ -// -// JSONKit.m -// http://github.com/johnezang/JSONKit -// Dual licensed under either the terms of the BSD License, or alternatively -// under the terms of the Apache License, Version 2.0, as specified below. -// - -/* - Copyright (c) 2011, John Engelhart - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the name of the Zang Industries nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -/* - Copyright 2011 John Engelhart - - Licensed 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. -*/ - - -/* - Acknowledgments: - - The bulk of the UTF8 / UTF32 conversion and verification comes - from ConvertUTF.[hc]. It has been modified from the original sources. - - The original sources were obtained from http://www.unicode.org/. - However, the web site no longer seems to host the files. Instead, - the Unicode FAQ http://www.unicode.org/faq//utf_bom.html#gen4 - points to International Components for Unicode (ICU) - http://site.icu-project.org/ as an example of how to write a UTF - converter. - - The decision to use the ConvertUTF.[ch] code was made to leverage - "proven" code. Hopefully the local modifications are bug free. - - The code in isValidCodePoint() is derived from the ICU code in - utf.h for the macros U_IS_UNICODE_NONCHAR and U_IS_UNICODE_CHAR. - - From the original ConvertUTF.[ch]: - - * Copyright 2001-2004 Unicode, Inc. - * - * Disclaimer - * - * This source code is provided as is by Unicode, Inc. No claims are - * made as to fitness for any particular purpose. No warranties of any - * kind are expressed or implied. The recipient agrees to determine - * applicability of information provided. If this file has been - * purchased on magnetic or optical media from Unicode, Inc., the - * sole remedy for any claim will be exchange of defective media - * within 90 days of receipt. - * - * Limitations on Rights to Redistribute This Code - * - * Unicode, Inc. hereby grants the right to freely use the information - * supplied in this file in the creation of products supporting the - * Unicode Standard, and to make copies of this file in any form - * for internal or external distribution as long as this notice - * remains attached. - -*/ - -#include <stdio.h> -#include <stdlib.h> -#include <stdint.h> -#include <string.h> -#include <assert.h> -#include <sys/errno.h> -#include <math.h> -#include <limits.h> -#include <objc/runtime.h> - -#import "JSONKit.h" - -//#include <CoreFoundation/CoreFoundation.h> -#include <CoreFoundation/CFString.h> -#include <CoreFoundation/CFArray.h> -#include <CoreFoundation/CFDictionary.h> -#include <CoreFoundation/CFNumber.h> - -//#import <Foundation/Foundation.h> -#import <Foundation/NSArray.h> -#import <Foundation/NSAutoreleasePool.h> -#import <Foundation/NSData.h> -#import <Foundation/NSDictionary.h> -#import <Foundation/NSException.h> -#import <Foundation/NSNull.h> -#import <Foundation/NSObjCRuntime.h> - -#ifndef __cdv_has_feature -#define __cdv_has_feature(x) 0 -#endif - -#ifdef CDVJK_ENABLE_CF_TRANSFER_OWNERSHIP_CALLBACKS -#warning As of CDVJSONKit v1.4, CDVJK_ENABLE_CF_TRANSFER_OWNERSHIP_CALLBACKS is no longer required. It is no longer a valid option. -#endif - -#ifdef __OBJC_GC__ -#error CDVJSONKit does not support Objective-C Garbage Collection -#endif - -#if __has_feature(objc_arc) -#error CDVJSONKit does not support Objective-C Automatic Reference Counting (ARC) -#endif - -// The following checks are really nothing more than sanity checks. -// CDVJSONKit technically has a few problems from a "strictly C99 conforming" standpoint, though they are of the pedantic nitpicking variety. -// In practice, though, for the compilers and architectures we can reasonably expect this code to be compiled for, these pedantic nitpicks aren't really a problem. -// Since we're limited as to what we can do with pre-processor #if checks, these checks are not nearly as through as they should be. - -#if (UINT_MAX != 0xffffffffU) || (INT_MIN != (-0x7fffffff-1)) || (ULLONG_MAX != 0xffffffffffffffffULL) || (LLONG_MIN != (-0x7fffffffffffffffLL-1LL)) -#error CDVJSONKit requires the C 'int' and 'long long' types to be 32 and 64 bits respectively. -#endif - -#if !defined(__LP64__) && ((UINT_MAX != ULONG_MAX) || (INT_MAX != LONG_MAX) || (INT_MIN != LONG_MIN) || (WORD_BIT != LONG_BIT)) -#error CDVJSONKit requires the C 'int' and 'long' types to be the same on 32-bit architectures. -#endif - -// Cocoa / Foundation uses NS*Integer as the type for a lot of arguments. We make sure that NS*Integer is something we are expecting and is reasonably compatible with size_t / ssize_t - -#if (NSUIntegerMax != ULONG_MAX) || (NSIntegerMax != LONG_MAX) || (NSIntegerMin != LONG_MIN) -#error CDVJSONKit requires NSInteger and NSUInteger to be the same size as the C 'long' type. -#endif - -#if (NSUIntegerMax != SIZE_MAX) || (NSIntegerMax != SSIZE_MAX) -#error CDVJSONKit requires NSInteger and NSUInteger to be the same size as the C 'size_t' type. -#endif - - -// For DJB hash. -#define CDVJK_HASH_INIT (1402737925UL) - -// Use __builtin_clz() instead of trailingBytesForUTF8[] table lookup. -#define CDVJK_FAST_TRAILING_BYTES - -// CDVJK_CACHE_SLOTS must be a power of 2. Default size is 1024 slots. -#define CDVJK_CACHE_SLOTS_BITS (10) -#define CDVJK_CACHE_SLOTS (1UL << CDVJK_CACHE_SLOTS_BITS) -// CDVJK_CACHE_PROBES is the number of probe attempts. -#define CDVJK_CACHE_PROBES (4UL) -// CDVJK_INIT_CACHE_AGE must be (1 << AGE) - 1 -#define CDVJK_INIT_CACHE_AGE (0) - -// CDVJK_TOKENBUFFER_SIZE is the default stack size for the temporary buffer used to hold "non-simple" strings (i.e., contains \ escapes) -#define CDVJK_TOKENBUFFER_SIZE (1024UL * 2UL) - -// CDVJK_STACK_OBJS is the default number of spaces reserved on the stack for temporarily storing pointers to Obj-C objects before they can be transferred to a NSArray / NSDictionary. -#define CDVJK_STACK_OBJS (1024UL * 1UL) - -#define CDVJK_JSONBUFFER_SIZE (1024UL * 4UL) -#define CDVJK_UTF8BUFFER_SIZE (1024UL * 16UL) - -#define CDVJK_ENCODE_CACHE_SLOTS (1024UL) - - -#if defined (__GNUC__) && (__GNUC__ >= 4) -#define CDVJK_ATTRIBUTES(attr, ...) __attribute__((attr, ##__VA_ARGS__)) -#define CDVJK_EXPECTED(cond, expect) __builtin_expect((long)(cond), (expect)) -#define CDVJK_EXPECT_T(cond) CDVJK_EXPECTED(cond, 1U) -#define CDVJK_EXPECT_F(cond) CDVJK_EXPECTED(cond, 0U) -#define CDVJK_PREFETCH(ptr) __builtin_prefetch(ptr) -#else // defined (__GNUC__) && (__GNUC__ >= 4) -#define CDVJK_ATTRIBUTES(attr, ...) -#define CDVJK_EXPECTED(cond, expect) (cond) -#define CDVJK_EXPECT_T(cond) (cond) -#define CDVJK_EXPECT_F(cond) (cond) -#define CDVJK_PREFETCH(ptr) -#endif // defined (__GNUC__) && (__GNUC__ >= 4) - -#define CDVJK_STATIC_INLINE static __inline__ CDVJK_ATTRIBUTES(always_inline) -#define CDVJK_ALIGNED(arg) CDVJK_ATTRIBUTES(aligned(arg)) -#define CDVJK_UNUSED_ARG CDVJK_ATTRIBUTES(unused) -#define CDVJK_WARN_UNUSED CDVJK_ATTRIBUTES(warn_unused_result) -#define CDVJK_WARN_UNUSED_CONST CDVJK_ATTRIBUTES(warn_unused_result, const) -#define CDVJK_WARN_UNUSED_PURE CDVJK_ATTRIBUTES(warn_unused_result, pure) -#define CDVJK_WARN_UNUSED_SENTINEL CDVJK_ATTRIBUTES(warn_unused_result, sentinel) -#define CDVJK_NONNULL_ARGS(arg, ...) CDVJK_ATTRIBUTES(nonnull(arg, ##__VA_ARGS__)) -#define CDVJK_WARN_UNUSED_NONNULL_ARGS(arg, ...) CDVJK_ATTRIBUTES(warn_unused_result, nonnull(arg, ##__VA_ARGS__)) -#define CDVJK_WARN_UNUSED_CONST_NONNULL_ARGS(arg, ...) CDVJK_ATTRIBUTES(warn_unused_result, const, nonnull(arg, ##__VA_ARGS__)) -#define CDVJK_WARN_UNUSED_PURE_NONNULL_ARGS(arg, ...) CDVJK_ATTRIBUTES(warn_unused_result, pure, nonnull(arg, ##__VA_ARGS__)) - -#if defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3) -#define CDVJK_ALLOC_SIZE_NON_NULL_ARGS_WARN_UNUSED(as, nn, ...) CDVJK_ATTRIBUTES(warn_unused_result, nonnull(nn, ##__VA_ARGS__), alloc_size(as)) -#else // defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3) -#define CDVJK_ALLOC_SIZE_NON_NULL_ARGS_WARN_UNUSED(as, nn, ...) CDVJK_ATTRIBUTES(warn_unused_result, nonnull(nn, ##__VA_ARGS__)) -#endif // defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3) - - -@class CDVJKArray, CDVJKDictionaryEnumerator, CDVJKDictionary; - -enum { - CDVJSONNumberStateStart = 0, - CDVJSONNumberStateFinished = 1, - CDVJSONNumberStateError = 2, - CDVJSONNumberStateWholeNumberStart = 3, - CDVJSONNumberStateWholeNumberMinus = 4, - CDVJSONNumberStateWholeNumberZero = 5, - CDVJSONNumberStateWholeNumber = 6, - CDVJSONNumberStatePeriod = 7, - CDVJSONNumberStateFractionalNumberStart = 8, - CDVJSONNumberStateFractionalNumber = 9, - CDVJSONNumberStateExponentStart = 10, - CDVJSONNumberStateExponentPlusMinus = 11, - CDVJSONNumberStateExponent = 12, -}; - -enum { - CDVJSONStringStateStart = 0, - CDVJSONStringStateParsing = 1, - CDVJSONStringStateFinished = 2, - CDVJSONStringStateError = 3, - CDVJSONStringStateEscape = 4, - CDVJSONStringStateEscapedUnicode1 = 5, - CDVJSONStringStateEscapedUnicode2 = 6, - CDVJSONStringStateEscapedUnicode3 = 7, - CDVJSONStringStateEscapedUnicode4 = 8, - CDVJSONStringStateEscapedUnicodeSurrogate1 = 9, - CDVJSONStringStateEscapedUnicodeSurrogate2 = 10, - CDVJSONStringStateEscapedUnicodeSurrogate3 = 11, - CDVJSONStringStateEscapedUnicodeSurrogate4 = 12, - CDVJSONStringStateEscapedNeedEscapeForSurrogate = 13, - CDVJSONStringStateEscapedNeedEscapedUForSurrogate = 14, -}; - -enum { - CDVJKParseAcceptValue = (1 << 0), - CDVJKParseAcceptComma = (1 << 1), - CDVJKParseAcceptEnd = (1 << 2), - CDVJKParseAcceptValueOrEnd = (CDVJKParseAcceptValue | CDVJKParseAcceptEnd), - CDVJKParseAcceptCommaOrEnd = (CDVJKParseAcceptComma | CDVJKParseAcceptEnd), -}; - -enum { - CDVJKClassUnknown = 0, - CDVJKClassString = 1, - CDVJKClassNumber = 2, - CDVJKClassArray = 3, - CDVJKClassDictionary = 4, - CDVJKClassNull = 5, -}; - -enum { - CDVJKManagedBufferOnStack = 1, - CDVJKManagedBufferOnHeap = 2, - CDVJKManagedBufferLocationMask = (0x3), - CDVJKManagedBufferLocationShift = (0), - - CDVJKManagedBufferMustFree = (1 << 2), -}; -typedef CDVJKFlags CDVJKManagedBufferFlags; - -enum { - CDVJKObjectStackOnStack = 1, - CDVJKObjectStackOnHeap = 2, - CDVJKObjectStackLocationMask = (0x3), - CDVJKObjectStackLocationShift = (0), - - CDVJKObjectStackMustFree = (1 << 2), -}; -typedef CDVJKFlags CDVJKObjectStackFlags; - -enum { - CDVJKTokenTypeInvalid = 0, - CDVJKTokenTypeNumber = 1, - CDVJKTokenTypeString = 2, - CDVJKTokenTypeObjectBegin = 3, - CDVJKTokenTypeObjectEnd = 4, - CDVJKTokenTypeArrayBegin = 5, - CDVJKTokenTypeArrayEnd = 6, - CDVJKTokenTypeSeparator = 7, - CDVJKTokenTypeComma = 8, - CDVJKTokenTypeTrue = 9, - CDVJKTokenTypeFalse = 10, - CDVJKTokenTypeNull = 11, - CDVJKTokenTypeWhiteSpace = 12, -}; -typedef NSUInteger CDVJKTokenType; - -// These are prime numbers to assist with hash slot probing. -enum { - CDVJKValueTypeNone = 0, - CDVJKValueTypeString = 5, - CDVJKValueTypeLongLong = 7, - CDVJKValueTypeUnsignedLongLong = 11, - CDVJKValueTypeDouble = 13, -}; -typedef NSUInteger CDVJKValueType; - -enum { - CDVJKEncodeOptionAsData = 1, - CDVJKEncodeOptionAsString = 2, - CDVJKEncodeOptionAsTypeMask = 0x7, - CDVJKEncodeOptionCollectionObj = (1 << 3), - CDVJKEncodeOptionStringObj = (1 << 4), - CDVJKEncodeOptionStringObjTrimQuotes = (1 << 5), - -}; -typedef NSUInteger CDVJKEncodeOptionType; - -typedef NSUInteger CDVJKHash; - -typedef struct CDVJKTokenCacheItem CDVJKTokenCacheItem; -typedef struct CDVJKTokenCache CDVJKTokenCache; -typedef struct CDVJKTokenValue CDVJKTokenValue; -typedef struct CDVJKParseToken CDVJKParseToken; -typedef struct CDVJKPtrRange CDVJKPtrRange; -typedef struct CDVJKObjectStack CDVJKObjectStack; -typedef struct CDVJKBuffer CDVJKBuffer; -typedef struct CDVJKConstBuffer CDVJKConstBuffer; -typedef struct CDVJKConstPtrRange CDVJKConstPtrRange; -typedef struct CDVJKRange CDVJKRange; -typedef struct CDVJKManagedBuffer CDVJKManagedBuffer; -typedef struct CDVJKFastClassLookup CDVJKFastClassLookup; -typedef struct CDVJKEncodeCache CDVJKEncodeCache; -typedef struct CDVJKEncodeState CDVJKEncodeState; -typedef struct CDVJKObjCImpCache CDVJKObjCImpCache; -typedef struct CDVJKHashTableEntry CDVJKHashTableEntry; - -typedef id (*NSNumberAllocImp)(id receiver, SEL selector); -typedef id (*NSNumberInitWithUnsignedLongLongImp)(id receiver, SEL selector, unsigned long long value); -typedef id (*CDVJKClassFormatterIMP)(id receiver, SEL selector, id object); -#ifdef __BLOCKS__ -typedef id (^CDVJKClassFormatterBlock)(id formatObject); -#endif - - -struct CDVJKPtrRange { - unsigned char *ptr; - size_t length; -}; - -struct CDVJKConstPtrRange { - const unsigned char *ptr; - size_t length; -}; - -struct CDVJKRange { - size_t location, length; -}; - -struct CDVJKManagedBuffer { - CDVJKPtrRange bytes; - CDVJKManagedBufferFlags flags; - size_t roundSizeUpToMultipleOf; -}; - -struct CDVJKObjectStack { - void **objects, **keys; - CFHashCode *cfHashes; - size_t count, index, roundSizeUpToMultipleOf; - CDVJKObjectStackFlags flags; -}; - -struct CDVJKBuffer { - CDVJKPtrRange bytes; -}; - -struct CDVJKConstBuffer { - CDVJKConstPtrRange bytes; -}; - -struct CDVJKTokenValue { - CDVJKConstPtrRange ptrRange; - CDVJKValueType type; - CDVJKHash hash; - union { - long long longLongValue; - unsigned long long unsignedLongLongValue; - double doubleValue; - } number; - CDVJKTokenCacheItem *cacheItem; -}; - -struct CDVJKParseToken { - CDVJKConstPtrRange tokenPtrRange; - CDVJKTokenType type; - CDVJKTokenValue value; - CDVJKManagedBuffer tokenBuffer; -}; - -struct CDVJKTokenCacheItem { - void *object; - CDVJKHash hash; - CFHashCode cfHash; - size_t size; - unsigned char *bytes; - CDVJKValueType type; -}; - -struct CDVJKTokenCache { - CDVJKTokenCacheItem *items; - size_t count; - unsigned int prng_lfsr; - unsigned char age[CDVJK_CACHE_SLOTS]; -}; - -struct CDVJKObjCImpCache { - Class NSNumberClass; - NSNumberAllocImp NSNumberAlloc; - NSNumberInitWithUnsignedLongLongImp NSNumberInitWithUnsignedLongLong; -}; - -struct CDVJKParseState { - CDVJKParseOptionFlags parseOptionFlags; - CDVJKConstBuffer stringBuffer; - size_t atIndex, lineNumber, lineStartIndex; - size_t prev_atIndex, prev_lineNumber, prev_lineStartIndex; - CDVJKParseToken token; - CDVJKObjectStack objectStack; - CDVJKTokenCache cache; - CDVJKObjCImpCache objCImpCache; - NSError *error; - int errorIsPrev; - BOOL mutableCollections; -}; - -struct CDVJKFastClassLookup { - void *stringClass; - void *numberClass; - void *arrayClass; - void *dictionaryClass; - void *nullClass; -}; - -struct CDVJKEncodeCache { - id object; - size_t offset; - size_t length; -}; - -struct CDVJKEncodeState { - CDVJKManagedBuffer utf8ConversionBuffer; - CDVJKManagedBuffer stringBuffer; - size_t atIndex; - CDVJKFastClassLookup fastClassLookup; - CDVJKEncodeCache cache[CDVJK_ENCODE_CACHE_SLOTS]; - CDVJKSerializeOptionFlags serializeOptionFlags; - CDVJKEncodeOptionType encodeOption; - size_t depth; - NSError *error; - id classFormatterDelegate; - SEL classFormatterSelector; - CDVJKClassFormatterIMP classFormatterIMP; -#ifdef __BLOCKS__ - CDVJKClassFormatterBlock classFormatterBlock; -#endif -}; - -// This is a CDVJSONKit private class. -@interface CDVJKSerializer : NSObject { - CDVJKEncodeState *encodeState; -} - -#ifdef __BLOCKS__ -#define CDVJKSERIALIZER_BLOCKS_PROTO id(^)(id object) -#else -#define CDVJKSERIALIZER_BLOCKS_PROTO id -#endif - -+ (id)serializeObject:(id)object options:(CDVJKSerializeOptionFlags)optionFlags encodeOption:(CDVJKEncodeOptionType)encodeOption block:(CDVJKSERIALIZER_BLOCKS_PROTO)block delegate:(id)delegate selector:(SEL)selector error:(NSError **)error; -- (id)serializeObject:(id)object options:(CDVJKSerializeOptionFlags)optionFlags encodeOption:(CDVJKEncodeOptionType)encodeOption block:(CDVJKSERIALIZER_BLOCKS_PROTO)block delegate:(id)delegate selector:(SEL)selector error:(NSError **)error; -- (void)releaseState; - -@end - -struct CDVJKHashTableEntry { - NSUInteger keyHash; - id key, object; -}; - - -typedef uint32_t UTF32; /* at least 32 bits */ -typedef uint16_t UTF16; /* at least 16 bits */ -typedef uint8_t UTF8; /* typically 8 bits */ - -typedef enum { - conversionOK, /* conversion successful */ - sourceExhausted, /* partial character in source, but hit end */ - targetExhausted, /* insuff. room in target for conversion */ - sourceIllegal /* source sequence is illegal/malformed */ -} CDV_ConversionResult; - -#define CDV_UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD -#define CDV_UNI_MAX_BMP (UTF32)0x0000FFFF -#define CDV_UNI_MAX_UTF16 (UTF32)0x0010FFFF -#define CDV_UNI_MAX_UTF32 (UTF32)0x7FFFFFFF -#define CDV_UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF -#define CDV_UNI_SUR_HIGH_START (UTF32)0xD800 -#define CDV_UNI_SUR_HIGH_END (UTF32)0xDBFF -#define CDV_UNI_SUR_LOW_START (UTF32)0xDC00 -#define CDV_UNI_SUR_LOW_END (UTF32)0xDFFF - - -#if !defined(CDVJK_FAST_TRAILING_BYTES) -static const char trailingBytesForUTF8[256] = { - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 -}; -#endif - -static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; -static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; - -#define CDVJK_AT_STRING_PTR(x) (&((x)->stringBuffer.bytes.ptr[(x)->atIndex])) -#define CDVJK_END_STRING_PTR(x) (&((x)->stringBuffer.bytes.ptr[(x)->stringBuffer.bytes.length])) - - -static CDVJKArray *_CDVJKArrayCreate(id *objects, NSUInteger count, BOOL mutableCollection); -static void _CDVJKArrayInsertObjectAtIndex(CDVJKArray *array, id newObject, NSUInteger objectIndex); -static void _CDVJKArrayReplaceObjectAtIndexWithObject(CDVJKArray *array, NSUInteger objectIndex, id newObject); -static void _CDVJKArrayRemoveObjectAtIndex(CDVJKArray *array, NSUInteger objectIndex); - - -static NSUInteger _CDVJKDictionaryCapacityForCount(NSUInteger count); -static CDVJKDictionary *_CDVJKDictionaryCreate(id *keys, NSUInteger *keyHashes, id *objects, NSUInteger count, BOOL mutableCollection); -static CDVJKHashTableEntry *_CDVJKDictionaryHashEntry(CDVJKDictionary *dictionary); -static NSUInteger _CDVJKDictionaryCapacity(CDVJKDictionary *dictionary); -static void _CDVJKDictionaryResizeIfNeccessary(CDVJKDictionary *dictionary); -static void _CDVJKDictionaryRemoveObjectWithEntry(CDVJKDictionary *dictionary, CDVJKHashTableEntry *entry); -static void _CDVJKDictionaryAddObject(CDVJKDictionary *dictionary, NSUInteger keyHash, id key, id object); -static CDVJKHashTableEntry *_CDVJKDictionaryHashTableEntryForKey(CDVJKDictionary *dictionary, id aKey); - - -static void _CDVJSONDecoderCleanup(CDVJSONDecoder *decoder); - -static id _CDVNSStringObjectFromJSONString(NSString *jsonString, CDVJKParseOptionFlags parseOptionFlags, NSError **error, BOOL mutableCollection); - - -static void cdvjk_managedBuffer_release(CDVJKManagedBuffer *managedBuffer); -static void cdvjk_managedBuffer_setToStackBuffer(CDVJKManagedBuffer *managedBuffer, unsigned char *ptr, size_t length); -static unsigned char *cdvjk_managedBuffer_resize(CDVJKManagedBuffer *managedBuffer, size_t newSize); -static void cdvjk_objectStack_release(CDVJKObjectStack *objectStack); -static void cdvjk_objectStack_setToStackBuffer(CDVJKObjectStack *objectStack, void **objects, void **keys, CFHashCode *cfHashes, size_t count); -static int cdvjk_objectStack_resize(CDVJKObjectStack *objectStack, size_t newCount); - -static void cdvjk_error(CDVJKParseState *parseState, NSString *format, ...); -static int cdvjk_parse_string(CDVJKParseState *parseState); -static int cdvjk_parse_number(CDVJKParseState *parseState); -static size_t cdvjk_parse_is_newline(CDVJKParseState *parseState, const unsigned char *atCharacterPtr); -CDVJK_STATIC_INLINE int cdvjk_parse_skip_newline(CDVJKParseState *parseState); -CDVJK_STATIC_INLINE void cdvjk_parse_skip_whitespace(CDVJKParseState *parseState); -static int cdvjk_parse_next_token(CDVJKParseState *parseState); -static void cdvjk_error_parse_accept_or3(CDVJKParseState *parseState, int state, NSString *or1String, NSString *or2String, NSString *or3String); -static void *cdvjk_create_dictionary(CDVJKParseState *parseState, size_t startingObjectIndex); -static void *cdvjk_parse_dictionary(CDVJKParseState *parseState); -static void *cdvjk_parse_array(CDVJKParseState *parseState); -static void *cdvjk_object_for_token(CDVJKParseState *parseState); -static void *cdvjk_cachedObjects(CDVJKParseState *parseState); -CDVJK_STATIC_INLINE void cdvjk_cache_age(CDVJKParseState *parseState); -CDVJK_STATIC_INLINE void cdvjk_set_parsed_token(CDVJKParseState *parseState, const unsigned char *ptr, size_t length, CDVJKTokenType type, size_t advanceBy); - - -static void cdvjk_encode_error(CDVJKEncodeState *encodeState, NSString *format, ...); -static int cdvjk_encode_printf(CDVJKEncodeState *encodeState, CDVJKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format, ...); -static int cdvjk_encode_write(CDVJKEncodeState *encodeState, CDVJKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format); -static int cdvjk_encode_writePrettyPrintWhiteSpace(CDVJKEncodeState *encodeState); -static int cdvjk_encode_write1slow(CDVJKEncodeState *encodeState, ssize_t depthChange, const char *format); -static int cdvjk_encode_write1fast(CDVJKEncodeState *encodeState, ssize_t depthChange CDVJK_UNUSED_ARG, const char *format); -static int cdvjk_encode_writen(CDVJKEncodeState *encodeState, CDVJKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format, size_t length); -CDVJK_STATIC_INLINE CDVJKHash cdvjk_encode_object_hash(void *objectPtr); -CDVJK_STATIC_INLINE void cdvjk_encode_updateCache(CDVJKEncodeState *encodeState, CDVJKEncodeCache *cacheSlot, size_t startingAtIndex, id object); -static int cdvjk_encode_add_atom_to_buffer(CDVJKEncodeState *encodeState, void *objectPtr); - -#define cdvjk_encode_write1(es, dc, f) (CDVJK_EXPECT_F(_jk_encode_prettyPrint) ? cdvjk_encode_write1slow(es, dc, f) : cdvjk_encode_write1fast(es, dc, f)) - - -CDVJK_STATIC_INLINE size_t cdvjk_min(size_t a, size_t b); -CDVJK_STATIC_INLINE size_t cdvjk_max(size_t a, size_t b); -CDVJK_STATIC_INLINE CDVJKHash cdvcalculateHash(CDVJKHash currentHash, unsigned char c); - -// CDVJSONKit v1.4 used both a CDVJKArray : NSArray and CDVJKMutableArray : NSMutableArray, and the same for the dictionary collection type. -// However, Louis Gerbarg (via cocoa-dev) pointed out that Cocoa / Core Foundation actually implements only a single class that inherits from the -// mutable version, and keeps an ivar bit for whether or not that instance is mutable. This means that the immutable versions of the collection -// classes receive the mutating methods, but this is handled by having those methods throw an exception when the ivar bit is set to immutable. -// We adopt the same strategy here. It's both cleaner and gets rid of the method swizzling hackery used in CDVJSONKit v1.4. - - -// This is a workaround for issue #23 https://github.com/johnezang/JSONKit/pull/23 -// Basically, there seem to be a problem with using +load in static libraries on iOS. However, __attribute__ ((constructor)) does work correctly. -// Since we do not require anything "special" that +load provides, and we can accomplish the same thing using __attribute__ ((constructor)), the +load logic was moved here. - -static Class _CDVJKArrayClass = NULL; -static size_t _CDVJKArrayInstanceSize = 0UL; -static Class _CDVJKDictionaryClass = NULL; -static size_t _CDVJKDictionaryInstanceSize = 0UL; - -// For CDVJSONDecoder... -static Class _CDVjk_NSNumberClass = NULL; -static NSNumberAllocImp _CDVjk_NSNumberAllocImp = NULL; -static NSNumberInitWithUnsignedLongLongImp _CDVjk_NSNumberInitWithUnsignedLongLongImp = NULL; - -extern void cdvjk_collectionClassLoadTimeInitialization(void) __attribute__ ((constructor)); - -void cdvjk_collectionClassLoadTimeInitialization(void) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Though technically not required, the run time environment at load time initialization may be less than ideal. - - _CDVJKArrayClass = objc_getClass("CDVJKArray"); - _CDVJKArrayInstanceSize = cdvjk_max(16UL, class_getInstanceSize(_CDVJKArrayClass)); - - _CDVJKDictionaryClass = objc_getClass("CDVJKDictionary"); - _CDVJKDictionaryInstanceSize = cdvjk_max(16UL, class_getInstanceSize(_CDVJKDictionaryClass)); - - // For CDVJSONDecoder... - _CDVjk_NSNumberClass = [NSNumber class]; - _CDVjk_NSNumberAllocImp = (NSNumberAllocImp)[NSNumber methodForSelector:@selector(alloc)]; - - // Hacktacular. Need to do it this way due to the nature of class clusters. - id temp_NSNumber = [NSNumber alloc]; - _CDVjk_NSNumberInitWithUnsignedLongLongImp = (NSNumberInitWithUnsignedLongLongImp)[temp_NSNumber methodForSelector:@selector(initWithUnsignedLongLong:)]; - [[temp_NSNumber init] release]; - temp_NSNumber = NULL; - - [pool release]; pool = NULL; -} - - -#pragma mark - -@interface CDVJKArray : NSMutableArray <NSCopying, NSMutableCopying, NSFastEnumeration> { - id *objects; - NSUInteger count, capacity, mutations; -} -@end - -@implementation CDVJKArray - -+ (id)allocWithZone:(NSZone *)zone -{ -#pragma unused(zone) - [NSException raise:NSInvalidArgumentException format:@"*** - [%@ %@]: The %@ class is private to CDVJSONKit and should not be used in this fashion.", NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSStringFromClass([self class])]; - return(NULL); -} - -static CDVJKArray *_CDVJKArrayCreate(id *objects, NSUInteger count, BOOL mutableCollection) { - NSCParameterAssert((objects != NULL) && (_CDVJKArrayClass != NULL) && (_CDVJKArrayInstanceSize > 0UL)); - CDVJKArray *array = NULL; - if(CDVJK_EXPECT_T((array = (CDVJKArray *)calloc(1UL, _CDVJKArrayInstanceSize)) != NULL)) { // Directly allocate the CDVJKArray instance via calloc. - array->isa = _CDVJKArrayClass; - if((array = [array init]) == NULL) { return(NULL); } - array->capacity = count; - array->count = count; - if(CDVJK_EXPECT_F((array->objects = (id *)malloc(sizeof(id) * array->capacity)) == NULL)) { [array autorelease]; return(NULL); } - memcpy(array->objects, objects, array->capacity * sizeof(id)); - array->mutations = (mutableCollection == NO) ? 0UL : 1UL; - } - return(array); -} - -// Note: The caller is responsible for -retaining the object that is to be added. -static void _CDVJKArrayInsertObjectAtIndex(CDVJKArray *array, id newObject, NSUInteger objectIndex) { - NSCParameterAssert((array != NULL) && (array->objects != NULL) && (array->count <= array->capacity) && (objectIndex <= array->count) && (newObject != NULL)); - if(!((array != NULL) && (array->objects != NULL) && (objectIndex <= array->count) && (newObject != NULL))) { [newObject autorelease]; return; } - if((array->count + 1UL) >= array->capacity) { - id *newObjects = NULL; - if((newObjects = (id *)realloc(array->objects, sizeof(id) * (array->capacity + 16UL))) == NULL) { [NSException raise:NSMallocException format:@"Unable to resize objects array."]; } - array->objects = newObjects; - array->capacity += 16UL; - memset(&array->objects[array->count], 0, sizeof(id) * (array->capacity - array->count)); - } - array->count++; - if((objectIndex + 1UL) < array->count) { memmove(&array->objects[objectIndex + 1UL], &array->objects[objectIndex], sizeof(id) * ((array->count - 1UL) - objectIndex)); array->objects[objectIndex] = NULL; } - array->objects[objectIndex] = newObject; -} - -// Note: The caller is responsible for -retaining the object that is to be added. -static void _CDVJKArrayReplaceObjectAtIndexWithObject(CDVJKArray *array, NSUInteger objectIndex, id newObject) { - NSCParameterAssert((array != NULL) && (array->objects != NULL) && (array->count <= array->capacity) && (objectIndex < array->count) && (array->objects[objectIndex] != NULL) && (newObject != NULL)); - if(!((array != NULL) && (array->objects != NULL) && (objectIndex < array->count) && (array->objects[objectIndex] != NULL) && (newObject != NULL))) { [newObject autorelease]; return; } - CFRelease(array->objects[objectIndex]); - array->objects[objectIndex] = NULL; - array->objects[objectIndex] = newObject; -} - -static void _CDVJKArrayRemoveObjectAtIndex(CDVJKArray *array, NSUInteger objectIndex) { - NSCParameterAssert((array != NULL) && (array->objects != NULL) && (array->count > 0UL) && (array->count <= array->capacity) && (objectIndex < array->count) && (array->objects[objectIndex] != NULL)); - if(!((array != NULL) && (array->objects != NULL) && (array->count > 0UL) && (array->count <= array->capacity) && (objectIndex < array->count) && (array->objects[objectIndex] != NULL))) { return; } - CFRelease(array->objects[objectIndex]); - array->objects[objectIndex] = NULL; - if((objectIndex + 1UL) < array->count) { memmove(&array->objects[objectIndex], &array->objects[objectIndex + 1UL], sizeof(id) * ((array->count - 1UL) - objectIndex)); array->objects[array->count - 1UL] = NULL; } - array->count--; -} - -- (void)dealloc -{ - if(CDVJK_EXPECT_T(objects != NULL)) { - NSUInteger atObject = 0UL; - for(atObject = 0UL; atObject < count; atObject++) { if(CDVJK_EXPECT_T(objects[atObject] != NULL)) { CFRelease(objects[atObject]); objects[atObject] = NULL; } } - free(objects); objects = NULL; - } - - [super dealloc]; -} - -- (NSUInteger)count -{ - NSParameterAssert((objects != NULL) && (count <= capacity)); - return(count); -} - -- (void)getObjects:(id *)objectsPtr range:(NSRange)range -{ - NSParameterAssert((objects != NULL) && (count <= capacity)); - if((objectsPtr == NULL) && (NSMaxRange(range) > 0UL)) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: pointer to objects array is NULL but range length is %u", NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSMaxRange(range)]; } - if((range.location > count) || (NSMaxRange(range) > count)) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%u) beyond bounds (%u)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSMaxRange(range), count]; } - if (objectsPtr != NULL) { - memcpy(objectsPtr, objects + range.location, range.length * sizeof(id)); - } -} - -- (id)objectAtIndex:(NSUInteger)objectIndex -{ - if(objectIndex >= count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%u) beyond bounds (%u)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), objectIndex, count]; } - NSParameterAssert((objects != NULL) && (count <= capacity) && (objects[objectIndex] != NULL)); - return(objects[objectIndex]); -} - -- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len -{ - NSParameterAssert((state != NULL) && (stackbuf != NULL) && (len > 0UL) && (objects != NULL) && (count <= capacity)); - if(CDVJK_EXPECT_F(state->state == 0UL)) { state->mutationsPtr = (unsigned long *)&mutations; state->itemsPtr = stackbuf; } - if(CDVJK_EXPECT_F(state->state >= count)) { return(0UL); } - - NSUInteger enumeratedCount = 0UL; - while(CDVJK_EXPECT_T(enumeratedCount < len) && CDVJK_EXPECT_T(state->state < count)) { NSParameterAssert(objects[state->state] != NULL); stackbuf[enumeratedCount++] = objects[state->state++]; } - - return(enumeratedCount); -} - -- (void)insertObject:(id)anObject atIndex:(NSUInteger)objectIndex -{ - if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } - if(anObject == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to insert nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } - if(objectIndex > count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%u) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), objectIndex, count + 1UL]; } -#ifdef __clang_analyzer__ - [anObject retain]; // Stupid clang analyzer... Issue #19. -#else - anObject = [anObject retain]; -#endif - _CDVJKArrayInsertObjectAtIndex(self, anObject, objectIndex); - mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL; -} - -- (void)removeObjectAtIndex:(NSUInteger)objectIndex -{ - if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } - if(objectIndex >= count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%u) beyond bounds (%u)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), objectIndex, count]; } - _CDVJKArrayRemoveObjectAtIndex(self, objectIndex); - mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL; -} - -- (void)replaceObjectAtIndex:(NSUInteger)objectIndex withObject:(id)anObject -{ - if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } - if(anObject == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to insert nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } - if(objectIndex >= count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%u) beyond bounds (%u)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), objectIndex, count]; } -#ifdef __clang_analyzer__ - [anObject retain]; // Stupid clang analyzer... Issue #19. -#else - anObject = [anObject retain]; -#endif - _CDVJKArrayReplaceObjectAtIndexWithObject(self, objectIndex, anObject); - mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL; -} - -- (id)copyWithZone:(NSZone *)zone -{ - NSParameterAssert((objects != NULL) && (count <= capacity)); - return((mutations == 0UL) ? [self retain] : [(NSArray *)[NSArray allocWithZone:zone] initWithObjects:objects count:count]); -} - -- (id)mutableCopyWithZone:(NSZone *)zone -{ - NSParameterAssert((objects != NULL) && (count <= capacity)); - return([(NSMutableArray *)[NSMutableArray allocWithZone:zone] initWithObjects:objects count:count]); -} - -@end - - -#pragma mark - -@interface CDVJKDictionaryEnumerator : NSEnumerator { - id collection; - NSUInteger nextObject; -} - -- (id)initWithJKDictionary:(CDVJKDictionary *)initDictionary; -- (NSArray *)allObjects; -- (id)nextObject; - -@end - -@implementation CDVJKDictionaryEnumerator - -- (id)initWithJKDictionary:(CDVJKDictionary *)initDictionary -{ - NSParameterAssert(initDictionary != NULL); - if((self = [super init]) == NULL) { return(NULL); } - if((collection = (id)CFRetain(initDictionary)) == NULL) { [self autorelease]; return(NULL); } - return(self); -} - -- (void)dealloc -{ - if(collection != NULL) { CFRelease(collection); collection = NULL; } - [super dealloc]; -} - -- (NSArray *)allObjects -{ - NSParameterAssert(collection != NULL); - NSUInteger count = [collection count], atObject = 0UL; - id objects[count]; - - while((objects[atObject] = [self nextObject]) != NULL) { NSParameterAssert(atObject < count); atObject++; } - - return([NSArray arrayWithObjects:objects count:atObject]); -} - -- (id)nextObject -{ - NSParameterAssert((collection != NULL) && (_CDVJKDictionaryHashEntry(collection) != NULL)); - CDVJKHashTableEntry *entry = _CDVJKDictionaryHashEntry(collection); - NSUInteger capacity = _CDVJKDictionaryCapacity(collection); - id returnObject = NULL; - - if(entry != NULL) { while((nextObject < capacity) && ((returnObject = entry[nextObject++].key) == NULL)) { /* ... */ } } - - return(returnObject); -} - -@end - -#pragma mark - -@interface CDVJKDictionary : NSMutableDictionary <NSCopying, NSMutableCopying, NSFastEnumeration> { - NSUInteger count, capacity, mutations; - CDVJKHashTableEntry *entry; -} -@end - -@implementation CDVJKDictionary - -+ (id)allocWithZone:(NSZone *)zone -{ -#pragma unused(zone) - [NSException raise:NSInvalidArgumentException format:@"*** - [%@ %@]: The %@ class is private to CDVJSONKit and should not be used in this fashion.", NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSStringFromClass([self class])]; - return(NULL); -} - -// These values are taken from Core Foundation CF-550 CFBasicHash.m. As a bonus, they align very well with our CDVJKHashTableEntry struct too. -static const NSUInteger cdvjk_dictionaryCapacities[] = { - 0UL, 3UL, 7UL, 13UL, 23UL, 41UL, 71UL, 127UL, 191UL, 251UL, 383UL, 631UL, 1087UL, 1723UL, - 2803UL, 4523UL, 7351UL, 11959UL, 19447UL, 31231UL, 50683UL, 81919UL, 132607UL, - 214519UL, 346607UL, 561109UL, 907759UL, 1468927UL, 2376191UL, 3845119UL, - 6221311UL, 10066421UL, 16287743UL, 26354171UL, 42641881UL, 68996069UL, - 111638519UL, 180634607UL, 292272623UL, 472907251UL -}; - -static NSUInteger _CDVJKDictionaryCapacityForCount(NSUInteger count) { - NSUInteger bottom = 0UL, top = sizeof(cdvjk_dictionaryCapacities) / sizeof(NSUInteger), mid = 0UL, tableSize = lround(floor((count) * 1.33)); - while(top > bottom) { mid = (top + bottom) / 2UL; if(cdvjk_dictionaryCapacities[mid] < tableSize) { bottom = mid + 1UL; } else { top = mid; } } - return(cdvjk_dictionaryCapacities[bottom]); -} - -static void _CDVJKDictionaryResizeIfNeccessary(CDVJKDictionary *dictionary) { - NSCParameterAssert((dictionary != NULL) && (dictionary->entry != NULL) && (dictionary->count <= dictionary->capacity)); - - NSUInteger capacityForCount = 0UL; - if(dictionary->capacity < (capacityForCount = _CDVJKDictionaryCapacityForCount(dictionary->count + 1UL))) { // resize - NSUInteger oldCapacity = dictionary->capacity; -#ifndef NS_BLOCK_ASSERTIONS - NSUInteger oldCount = dictionary->count; -#endif - CDVJKHashTableEntry *oldEntry = dictionary->entry; - if(CDVJK_EXPECT_F((dictionary->entry = (CDVJKHashTableEntry *)calloc(1UL, sizeof(CDVJKHashTableEntry) * capacityForCount)) == NULL)) { [NSException raise:NSMallocException format:@"Unable to allocate memory for hash table."]; } - dictionary->capacity = capacityForCount; - dictionary->count = 0UL; - - NSUInteger idx = 0UL; - for(idx = 0UL; idx < oldCapacity; idx++) { if(oldEntry[idx].key != NULL) { _CDVJKDictionaryAddObject(dictionary, oldEntry[idx].keyHash, oldEntry[idx].key, oldEntry[idx].object); oldEntry[idx].keyHash = 0UL; oldEntry[idx].key = NULL; oldEntry[idx].object = NULL; } } - NSCParameterAssert((oldCount == dictionary->count)); - free(oldEntry); oldEntry = NULL; - } -} - -static CDVJKDictionary *_CDVJKDictionaryCreate(id *keys, NSUInteger *keyHashes, id *objects, NSUInteger count, BOOL mutableCollection) { - NSCParameterAssert((keys != NULL) && (keyHashes != NULL) && (objects != NULL) && (_CDVJKDictionaryClass != NULL) && (_CDVJKDictionaryInstanceSize > 0UL)); - CDVJKDictionary *dictionary = NULL; - if(CDVJK_EXPECT_T((dictionary = (CDVJKDictionary *)calloc(1UL, _CDVJKDictionaryInstanceSize)) != NULL)) { // Directly allocate the CDVJKDictionary instance via calloc. - dictionary->isa = _CDVJKDictionaryClass; - if((dictionary = [dictionary init]) == NULL) { return(NULL); } - dictionary->capacity = _CDVJKDictionaryCapacityForCount(count); - dictionary->count = 0UL; - - if(CDVJK_EXPECT_F((dictionary->entry = (CDVJKHashTableEntry *)calloc(1UL, sizeof(CDVJKHashTableEntry) * dictionary->capacity)) == NULL)) { [dictionary autorelease]; return(NULL); } - - NSUInteger idx = 0UL; - for(idx = 0UL; idx < count; idx++) { _CDVJKDictionaryAddObject(dictionary, keyHashes[idx], keys[idx], objects[idx]); } - - dictionary->mutations = (mutableCollection == NO) ? 0UL : 1UL; - } - return(dictionary); -} - -- (void)dealloc -{ - if(CDVJK_EXPECT_T(entry != NULL)) { - NSUInteger atEntry = 0UL; - for(atEntry = 0UL; atEntry < capacity; atEntry++) { - if(CDVJK_EXPECT_T(entry[atEntry].key != NULL)) { CFRelease(entry[atEntry].key); entry[atEntry].key = NULL; } - if(CDVJK_EXPECT_T(entry[atEntry].object != NULL)) { CFRelease(entry[atEntry].object); entry[atEntry].object = NULL; } - } - - free(entry); entry = NULL; - } - - [super dealloc]; -} - -static CDVJKHashTableEntry *_CDVJKDictionaryHashEntry(CDVJKDictionary *dictionary) { - NSCParameterAssert(dictionary != NULL); - return(dictionary->entry); -} - -static NSUInteger _CDVJKDictionaryCapacity(CDVJKDictionary *dictionary) { - NSCParameterAssert(dictionary != NULL); - return(dictionary->capacity); -} - -static void _CDVJKDictionaryRemoveObjectWithEntry(CDVJKDictionary *dictionary, CDVJKHashTableEntry *entry) { - NSCParameterAssert((dictionary != NULL) && (entry != NULL) && (entry->key != NULL) && (entry->object != NULL) && (dictionary->count > 0UL) && (dictionary->count <= dictionary->capacity)); - CFRelease(entry->key); entry->key = NULL; - CFRelease(entry->object); entry->object = NULL; - entry->keyHash = 0UL; - dictionary->count--; - // In order for certain invariants that are used to speed up the search for a particular key, we need to "re-add" all the entries in the hash table following this entry until we hit a NULL entry. - NSUInteger removeIdx = entry - dictionary->entry, idx = 0UL; - NSCParameterAssert((removeIdx < dictionary->capacity)); - for(idx = 0UL; idx < dictionary->capacity; idx++) { - NSUInteger entryIdx = (removeIdx + idx + 1UL) % dictionary->capacity; - CDVJKHashTableEntry *atEntry = &dictionary->entry[entryIdx]; - if(atEntry->key == NULL) { break; } - NSUInteger keyHash = atEntry->keyHash; - id key = atEntry->key, object = atEntry->object; - NSCParameterAssert(object != NULL); - atEntry->keyHash = 0UL; - atEntry->key = NULL; - atEntry->object = NULL; - NSUInteger addKeyEntry = keyHash % dictionary->capacity, addIdx = 0UL; - for(addIdx = 0UL; addIdx < dictionary->capacity; addIdx++) { - CDVJKHashTableEntry *atAddEntry = &dictionary->entry[((addKeyEntry + addIdx) % dictionary->capacity)]; - if(CDVJK_EXPECT_T(atAddEntry->key == NULL)) { NSCParameterAssert((atAddEntry->keyHash == 0UL) && (atAddEntry->object == NULL)); atAddEntry->key = key; atAddEntry->object = object; atAddEntry->keyHash = keyHash; break; } - } - } -} - -static void _CDVJKDictionaryAddObject(CDVJKDictionary *dictionary, NSUInteger keyHash, id key, id object) { - NSCParameterAssert((dictionary != NULL) && (key != NULL) && (object != NULL) && (dictionary->count < dictionary->capacity) && (dictionary->entry != NULL)); - NSUInteger keyEntry = keyHash % dictionary->capacity, idx = 0UL; - for(idx = 0UL; idx < dictionary->capacity; idx++) { - NSUInteger entryIdx = (keyEntry + idx) % dictionary->capacity; - CDVJKHashTableEntry *atEntry = &dictionary->entry[entryIdx]; - if(CDVJK_EXPECT_F(atEntry->keyHash == keyHash) && CDVJK_EXPECT_T(atEntry->key != NULL) && (CDVJK_EXPECT_F(key == atEntry->key) || CDVJK_EXPECT_F(CFEqual(atEntry->key, key)))) { _CDVJKDictionaryRemoveObjectWithEntry(dictionary, atEntry); } - if(CDVJK_EXPECT_T(atEntry->key == NULL)) { NSCParameterAssert((atEntry->keyHash == 0UL) && (atEntry->object == NULL)); atEntry->key = key; atEntry->object = object; atEntry->keyHash = keyHash; dictionary->count++; return; } - } - - // We should never get here. If we do, we -release the key / object because it's our responsibility. - CFRelease(key); - CFRelease(object); -} - -- (NSUInteger)count -{ - return(count); -} - -static CDVJKHashTableEntry *_CDVJKDictionaryHashTableEntryForKey(CDVJKDictionary *dictionary, id aKey) { - NSCParameterAssert((dictionary != NULL) && (dictionary->entry != NULL) && (dictionary->count <= dictionary->capacity)); - if((aKey == NULL) || (dictionary->capacity == 0UL)) { return(NULL); } - NSUInteger keyHash = CFHash(aKey), keyEntry = (keyHash % dictionary->capacity), idx = 0UL; - CDVJKHashTableEntry *atEntry = NULL; - for(idx = 0UL; idx < dictionary->capacity; idx++) { - atEntry = &dictionary->entry[(keyEntry + idx) % dictionary->capacity]; - if(CDVJK_EXPECT_T(atEntry->keyHash == keyHash) && CDVJK_EXPECT_T(atEntry->key != NULL) && ((atEntry->key == aKey) || CFEqual(atEntry->key, aKey))) { NSCParameterAssert(atEntry->object != NULL); return(atEntry); break; } - if(CDVJK_EXPECT_F(atEntry->key == NULL)) { NSCParameterAssert(atEntry->object == NULL); return(NULL); break; } // If the key was in the table, we would have found it by now. - } - return(NULL); -} - -- (id)objectForKey:(id)aKey -{ - NSParameterAssert((entry != NULL) && (count <= capacity)); - CDVJKHashTableEntry *entryForKey = _CDVJKDictionaryHashTableEntryForKey(self, aKey); - return((entryForKey != NULL) ? entryForKey->object : NULL); -} - -- (void)getObjects:(id *)objects andKeys:(id *)keys -{ - NSParameterAssert((entry != NULL) && (count <= capacity)); - NSUInteger atEntry = 0UL; NSUInteger arrayIdx = 0UL; - for(atEntry = 0UL; atEntry < capacity; atEntry++) { - if(CDVJK_EXPECT_T(entry[atEntry].key != NULL)) { - NSCParameterAssert((entry[atEntry].object != NULL) && (arrayIdx < count)); - if(CDVJK_EXPECT_T(keys != NULL)) { keys[arrayIdx] = entry[atEntry].key; } - if(CDVJK_EXPECT_T(objects != NULL)) { objects[arrayIdx] = entry[atEntry].object; } - arrayIdx++; - } - } -} - -- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len -{ - NSParameterAssert((state != NULL) && (stackbuf != NULL) && (len > 0UL) && (entry != NULL) && (count <= capacity)); - if(CDVJK_EXPECT_F(state->state == 0UL)) { state->mutationsPtr = (unsigned long *)&mutations; state->itemsPtr = stackbuf; } - if(CDVJK_EXPECT_F(state->state >= capacity)) { return(0UL); } - - NSUInteger enumeratedCount = 0UL; - while(CDVJK_EXPECT_T(enumeratedCount < len) && CDVJK_EXPECT_T(state->state < capacity)) { if(CDVJK_EXPECT_T(entry[state->state].key != NULL)) { stackbuf[enumeratedCount++] = entry[state->state].key; } state->state++; } - - return(enumeratedCount); -} - -- (NSEnumerator *)keyEnumerator -{ - return([[[CDVJKDictionaryEnumerator alloc] initWithJKDictionary:self] autorelease]); -} - -- (void)setObject:(id)anObject forKey:(id)aKey -{ - if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } - if(aKey == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to insert nil key", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } - if(anObject == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to insert nil value (key: %@)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), aKey]; } - - _CDVJKDictionaryResizeIfNeccessary(self); -#ifndef __clang_analyzer__ - aKey = [aKey copy]; // Why on earth would clang complain that this -copy "might leak", - anObject = [anObject retain]; // but this -retain doesn't!? -#endif // __clang_analyzer__ - _CDVJKDictionaryAddObject(self, CFHash(aKey), aKey, anObject); - mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL; -} - -- (void)removeObjectForKey:(id)aKey -{ - if(mutations == 0UL) { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } - if(aKey == NULL) { [NSException raise:NSInvalidArgumentException format:@"*** -[%@ %@]: attempt to remove nil key", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } - CDVJKHashTableEntry *entryForKey = _CDVJKDictionaryHashTableEntryForKey(self, aKey); - if(entryForKey != NULL) { - _CDVJKDictionaryRemoveObjectWithEntry(self, entryForKey); - mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL; - } -} - -- (id)copyWithZone:(NSZone *)zone -{ - NSParameterAssert((entry != NULL) && (count <= capacity)); - return((mutations == 0UL) ? [self retain] : [[NSDictionary allocWithZone:zone] initWithDictionary:self]); -} - -- (id)mutableCopyWithZone:(NSZone *)zone -{ - NSParameterAssert((entry != NULL) && (count <= capacity)); - return([[NSMutableDictionary allocWithZone:zone] initWithDictionary:self]); -} - -@end - - - -#pragma mark - - -CDVJK_STATIC_INLINE size_t cdvjk_min(size_t a, size_t b) { return((a < b) ? a : b); } -CDVJK_STATIC_INLINE size_t cdvjk_max(size_t a, size_t b) { return((a > b) ? a : b); } - -CDVJK_STATIC_INLINE CDVJKHash cdvcalculateHash(CDVJKHash currentHash, unsigned char c) { return(((currentHash << 5) + currentHash) + c); } - -static void cdvjk_error(CDVJKParseState *parseState, NSString *format, ...) { - NSCParameterAssert((parseState != NULL) && (format != NULL)); - - va_list varArgsList; - va_start(varArgsList, format); - NSString *formatString = [[[NSString alloc] initWithFormat:format arguments:varArgsList] autorelease]; - va_end(varArgsList); - -#if 0 - const unsigned char *lineStart = parseState->stringBuffer.bytes.ptr + parseState->lineStartIndex; - const unsigned char *lineEnd = lineStart; - const unsigned char *atCharacterPtr = NULL; - - for(atCharacterPtr = lineStart; atCharacterPtr < CDVJK_END_STRING_PTR(parseState); atCharacterPtr++) { lineEnd = atCharacterPtr; if(cdvjk_parse_is_newline(parseState, atCharacterPtr)) { break; } } - - NSString *lineString = @"", *carretString = @""; - if(lineStart < CDVJK_END_STRING_PTR(parseState)) { - lineString = [[[NSString alloc] initWithBytes:lineStart length:(lineEnd - lineStart) encoding:NSUTF8StringEncoding] autorelease]; - carretString = [NSString stringWithFormat:@"%*.*s^", (int)(parseState->atIndex - parseState->lineStartIndex), (int)(parseState->atIndex - parseState->lineStartIndex), " "]; - } -#endif - - if(parseState->error == NULL) { - parseState->error = [NSError errorWithDomain:@"CDVJKErrorDomain" code:-1L userInfo: - [NSDictionary dictionaryWithObjectsAndKeys: - formatString, NSLocalizedDescriptionKey, - [NSNumber numberWithUnsignedLong:parseState->atIndex], @"CDVJKAtIndexKey", - [NSNumber numberWithUnsignedLong:parseState->lineNumber], @"CDVJKLineNumberKey", - //lineString, @"CDVJKErrorLine0Key", - //carretString, @"CDVJKErrorLine1Key", - NULL]]; - } -} - -#pragma mark - -#pragma mark Buffer and Object Stack management functions - -static void cdvjk_managedBuffer_release(CDVJKManagedBuffer *managedBuffer) { - if((managedBuffer->flags & CDVJKManagedBufferMustFree)) { - if(managedBuffer->bytes.ptr != NULL) { free(managedBuffer->bytes.ptr); managedBuffer->bytes.ptr = NULL; } - managedBuffer->flags &= ~CDVJKManagedBufferMustFree; - } - - managedBuffer->bytes.ptr = NULL; - managedBuffer->bytes.length = 0UL; - managedBuffer->flags &= ~CDVJKManagedBufferLocationMask; -} - -static void cdvjk_managedBuffer_setToStackBuffer(CDVJKManagedBuffer *managedBuffer, unsigned char *ptr, size_t length) { - cdvjk_managedBuffer_release(managedBuffer); - managedBuffer->bytes.ptr = ptr; - managedBuffer->bytes.length = length; - managedBuffer->flags = (managedBuffer->flags & ~CDVJKManagedBufferLocationMask) | CDVJKManagedBufferOnStack; -} - -static unsigned char *cdvjk_managedBuffer_resize(CDVJKManagedBuffer *managedBuffer, size_t newSize) { - size_t roundedUpNewSize = newSize; - - if(managedBuffer->roundSizeUpToMultipleOf > 0UL) { roundedUpNewSize = newSize + ((managedBuffer->roundSizeUpToMultipleOf - (newSize % managedBuffer->roundSizeUpToMultipleOf)) % managedBuffer->roundSizeUpToMultipleOf); } - - if((roundedUpNewSize != managedBuffer->bytes.length) && (roundedUpNewSize > managedBuffer->bytes.length)) { - if((managedBuffer->flags & CDVJKManagedBufferLocationMask) == CDVJKManagedBufferOnStack) { - NSCParameterAssert((managedBuffer->flags & CDVJKManagedBufferMustFree) == 0); - unsigned char *newBuffer = NULL, *oldBuffer = managedBuffer->bytes.ptr; - - if((newBuffer = (unsigned char *)malloc(roundedUpNewSize)) == NULL) { return(NULL); } - memcpy(newBuffer, oldBuffer, cdvjk_min(managedBuffer->bytes.length, roundedUpNewSize)); - managedBuffer->flags = (managedBuffer->flags & ~CDVJKManagedBufferLocationMask) | (CDVJKManagedBufferOnHeap | CDVJKManagedBufferMustFree); - managedBuffer->bytes.ptr = newBuffer; - managedBuffer->bytes.length = roundedUpNewSize; - } else { - NSCParameterAssert(((managedBuffer->flags & CDVJKManagedBufferMustFree) != 0) && ((managedBuffer->flags & CDVJKManagedBufferLocationMask) == CDVJKManagedBufferOnHeap)); - if((managedBuffer->bytes.ptr = (unsigned char *)reallocf(managedBuffer->bytes.ptr, roundedUpNewSize)) == NULL) { return(NULL); } - managedBuffer->bytes.length = roundedUpNewSize; - } - } - - return(managedBuffer->bytes.ptr); -} - - - -static void cdvjk_objectStack_release(CDVJKObjectStack *objectStack) { - NSCParameterAssert(objectStack != NULL); - - NSCParameterAssert(objectStack->index <= objectStack->count); - size_t atIndex = 0UL; - for(atIndex = 0UL; atIndex < objectStack->index; atIndex++) { - if(objectStack->objects[atIndex] != NULL) { CFRelease(objectStack->objects[atIndex]); objectStack->objects[atIndex] = NULL; } - if(objectStack->keys[atIndex] != NULL) { CFRelease(objectStack->keys[atIndex]); objectStack->keys[atIndex] = NULL; } - } - objectStack->index = 0UL; - - if(objectStack->flags & CDVJKObjectStackMustFree) { - NSCParameterAssert((objectStack->flags & CDVJKObjectStackLocationMask) == CDVJKObjectStackOnHeap); - if(objectStack->objects != NULL) { free(objectStack->objects); objectStack->objects = NULL; } - if(objectStack->keys != NULL) { free(objectStack->keys); objectStack->keys = NULL; } - if(objectStack->cfHashes != NULL) { free(objectStack->cfHashes); objectStack->cfHashes = NULL; } - objectStack->flags &= ~CDVJKObjectStackMustFree; - } - - objectStack->objects = NULL; - objectStack->keys = NULL; - objectStack->cfHashes = NULL; - - objectStack->count = 0UL; - objectStack->flags &= ~CDVJKObjectStackLocationMask; -} - -static void cdvjk_objectStack_setToStackBuffer(CDVJKObjectStack *objectStack, void **objects, void **keys, CFHashCode *cfHashes, size_t count) { - NSCParameterAssert((objectStack != NULL) && (objects != NULL) && (keys != NULL) && (cfHashes != NULL) && (count > 0UL)); - cdvjk_objectStack_release(objectStack); - objectStack->objects = objects; - objectStack->keys = keys; - objectStack->cfHashes = cfHashes; - objectStack->count = count; - objectStack->flags = (objectStack->flags & ~CDVJKObjectStackLocationMask) | CDVJKObjectStackOnStack; -#ifndef NS_BLOCK_ASSERTIONS - size_t idx; - for(idx = 0UL; idx < objectStack->count; idx++) { objectStack->objects[idx] = NULL; objectStack->keys[idx] = NULL; objectStack->cfHashes[idx] = 0UL; } -#endif -} - -static int cdvjk_objectStack_resize(CDVJKObjectStack *objectStack, size_t newCount) { - size_t roundedUpNewCount = newCount; - int returnCode = 0; - - void **newObjects = NULL, **newKeys = NULL; - CFHashCode *newCFHashes = NULL; - - if(objectStack->roundSizeUpToMultipleOf > 0UL) { roundedUpNewCount = newCount + ((objectStack->roundSizeUpToMultipleOf - (newCount % objectStack->roundSizeUpToMultipleOf)) % objectStack->roundSizeUpToMultipleOf); } - - if((roundedUpNewCount != objectStack->count) && (roundedUpNewCount > objectStack->count)) { - if((objectStack->flags & CDVJKObjectStackLocationMask) == CDVJKObjectStackOnStack) { - NSCParameterAssert((objectStack->flags & CDVJKObjectStackMustFree) == 0); - - if((newObjects = (void ** )calloc(1UL, roundedUpNewCount * sizeof(void * ))) == NULL) { returnCode = 1; goto errorExit; } - memcpy(newObjects, objectStack->objects, cdvjk_min(objectStack->count, roundedUpNewCount) * sizeof(void *)); - if((newKeys = (void ** )calloc(1UL, roundedUpNewCount * sizeof(void * ))) == NULL) { returnCode = 1; goto errorExit; } - memcpy(newKeys, objectStack->keys, cdvjk_min(objectStack->count, roundedUpNewCount) * sizeof(void *)); - - if((newCFHashes = (CFHashCode *)calloc(1UL, roundedUpNewCount * sizeof(CFHashCode))) == NULL) { returnCode = 1; goto errorExit; } - memcpy(newCFHashes, objectStack->cfHashes, cdvjk_min(objectStack->count, roundedUpNewCount) * sizeof(CFHashCode)); - - objectStack->flags = (objectStack->flags & ~CDVJKObjectStackLocationMask) | (CDVJKObjectStackOnHeap | CDVJKObjectStackMustFree); - objectStack->objects = newObjects; newObjects = NULL; - objectStack->keys = newKeys; newKeys = NULL; - objectStack->cfHashes = newCFHashes; newCFHashes = NULL; - objectStack->count = roundedUpNewCount; - } else { - NSCParameterAssert(((objectStack->flags & CDVJKObjectStackMustFree) != 0) && ((objectStack->flags & CDVJKObjectStackLocationMask) == CDVJKObjectStackOnHeap)); - if((newObjects = (void ** )realloc(objectStack->objects, roundedUpNewCount * sizeof(void * ))) != NULL) { objectStack->objects = newObjects; newObjects = NULL; } else { returnCode = 1; goto errorExit; } - if((newKeys = (void ** )realloc(objectStack->keys, roundedUpNewCount * sizeof(void * ))) != NULL) { objectStack->keys = newKeys; newKeys = NULL; } else { returnCode = 1; goto errorExit; } - if((newCFHashes = (CFHashCode *)realloc(objectStack->cfHashes, roundedUpNewCount * sizeof(CFHashCode))) != NULL) { objectStack->cfHashes = newCFHashes; newCFHashes = NULL; } else { returnCode = 1; goto errorExit; } - -#ifndef NS_BLOCK_ASSERTIONS - size_t idx; - for(idx = objectStack->count; idx < roundedUpNewCount; idx++) { objectStack->objects[idx] = NULL; objectStack->keys[idx] = NULL; objectStack->cfHashes[idx] = 0UL; } -#endif - objectStack->count = roundedUpNewCount; - } - } - - errorExit: - if(newObjects != NULL) { free(newObjects); newObjects = NULL; } - if(newKeys != NULL) { free(newKeys); newKeys = NULL; } - if(newCFHashes != NULL) { free(newCFHashes); newCFHashes = NULL; } - - return(returnCode); -} - -//////////// -#pragma mark - -#pragma mark Unicode related functions - -CDVJK_STATIC_INLINE CDV_ConversionResult cdvisValidCodePoint(UTF32 *u32CodePoint) { - CDV_ConversionResult result = conversionOK; - UTF32 ch = *u32CodePoint; - - if(CDVJK_EXPECT_F(ch >= CDV_UNI_SUR_HIGH_START) && (CDVJK_EXPECT_T(ch <= CDV_UNI_SUR_LOW_END))) { result = sourceIllegal; ch = CDV_UNI_REPLACEMENT_CHAR; goto finished; } - if(CDVJK_EXPECT_F(ch >= 0xFDD0U) && (CDVJK_EXPECT_F(ch <= 0xFDEFU) || CDVJK_EXPECT_F((ch & 0xFFFEU) == 0xFFFEU)) && CDVJK_EXPECT_T(ch <= 0x10FFFFU)) { result = sourceIllegal; ch = CDV_UNI_REPLACEMENT_CHAR; goto finished; } - if(CDVJK_EXPECT_F(ch == 0U)) { result = sourceIllegal; ch = CDV_UNI_REPLACEMENT_CHAR; goto finished; } - - finished: - *u32CodePoint = ch; - return(result); -} - - -static int cdvisLegalUTF8(const UTF8 *source, size_t length) { - const UTF8 *srcptr = source + length; - UTF8 a; - - switch(length) { - default: return(0); // Everything else falls through when "true"... - case 4: if(CDVJK_EXPECT_F(((a = (*--srcptr)) < 0x80) || (a > 0xBF))) { return(0); } - case 3: if(CDVJK_EXPECT_F(((a = (*--srcptr)) < 0x80) || (a > 0xBF))) { return(0); } - case 2: if(CDVJK_EXPECT_F( (a = (*--srcptr)) > 0xBF )) { return(0); } - - switch(*source) { // no fall-through in this inner switch - case 0xE0: if(CDVJK_EXPECT_F(a < 0xA0)) { return(0); } break; - case 0xED: if(CDVJK_EXPECT_F(a > 0x9F)) { return(0); } break; - case 0xF0: if(CDVJK_EXPECT_F(a < 0x90)) { return(0); } break; - case 0xF4: if(CDVJK_EXPECT_F(a > 0x8F)) { return(0); } break; - default: if(CDVJK_EXPECT_F(a < 0x80)) { return(0); } - } - - case 1: if(CDVJK_EXPECT_F((CDVJK_EXPECT_T(*source < 0xC2)) && CDVJK_EXPECT_F(*source >= 0x80))) { return(0); } - } - - if(CDVJK_EXPECT_F(*source > 0xF4)) { return(0); } - - return(1); -} - -static CDV_ConversionResult cdvConvertSingleCodePointInUTF8(const UTF8 *sourceStart, const UTF8 *sourceEnd, UTF8 const **nextUTF8, UTF32 *convertedUTF32) { - CDV_ConversionResult result = conversionOK; - const UTF8 *source = sourceStart; - UTF32 ch = 0UL; - -#if !defined(CDVJK_FAST_TRAILING_BYTES) - unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; -#else - unsigned short extraBytesToRead = __builtin_clz(((*source)^0xff) << 25); -#endif - - if(CDVJK_EXPECT_F((source + extraBytesToRead + 1) > sourceEnd) || CDVJK_EXPECT_F(!cdvisLegalUTF8(source, extraBytesToRead + 1))) { - source++; - while((source < sourceEnd) && (((*source) & 0xc0) == 0x80) && ((source - sourceStart) < (extraBytesToRead + 1))) { source++; } - NSCParameterAssert(source <= sourceEnd); - result = ((source < sourceEnd) && (((*source) & 0xc0) != 0x80)) ? sourceIllegal : ((sourceStart + extraBytesToRead + 1) > sourceEnd) ? sourceExhausted : sourceIllegal; - ch = CDV_UNI_REPLACEMENT_CHAR; - goto finished; - } - - switch(extraBytesToRead) { // The cases all fall through. - case 5: ch += *source++; ch <<= 6; - case 4: ch += *source++; ch <<= 6; - case 3: ch += *source++; ch <<= 6; - case 2: ch += *source++; ch <<= 6; - case 1: ch += *source++; ch <<= 6; - case 0: ch += *source++; - } - ch -= offsetsFromUTF8[extraBytesToRead]; - - result = cdvisValidCodePoint(&ch); - - finished: - *nextUTF8 = source; - *convertedUTF32 = ch; - - return(result); -} - - -static CDV_ConversionResult cdvConvertUTF32toUTF8 (UTF32 u32CodePoint, UTF8 **targetStart, UTF8 *targetEnd) { - const UTF32 byteMask = 0xBF, byteMark = 0x80; - CDV_ConversionResult result = conversionOK; - UTF8 *target = *targetStart; - UTF32 ch = u32CodePoint; - unsigned short bytesToWrite = 0; - - result = cdvisValidCodePoint(&ch); - - // Figure out how many bytes the result will require. Turn any illegally large UTF32 things (> Plane 17) into replacement chars. - if(ch < (UTF32)0x80) { bytesToWrite = 1; } - else if(ch < (UTF32)0x800) { bytesToWrite = 2; } - else if(ch < (UTF32)0x10000) { bytesToWrite = 3; } - else if(ch <= CDV_UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; } - else { bytesToWrite = 3; ch = CDV_UNI_REPLACEMENT_CHAR; result = sourceIllegal; } - - target += bytesToWrite; - if (target > targetEnd) { target -= bytesToWrite; result = targetExhausted; goto finished; } - - switch (bytesToWrite) { // note: everything falls through. - case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 1: *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); - } - - target += bytesToWrite; - - finished: - *targetStart = target; - return(result); -} - -CDVJK_STATIC_INLINE int cdvjk_string_add_unicodeCodePoint(CDVJKParseState *parseState, uint32_t unicodeCodePoint, size_t *tokenBufferIdx, CDVJKHash *stringHash) { - UTF8 *u8s = &parseState->token.tokenBuffer.bytes.ptr[*tokenBufferIdx]; - CDV_ConversionResult result; - - if((result = cdvConvertUTF32toUTF8(unicodeCodePoint, &u8s, (parseState->token.tokenBuffer.bytes.ptr + parseState->token.tokenBuffer.bytes.length))) != conversionOK) { if(result == targetExhausted) { return(1); } } - size_t utf8len = u8s - &parseState->token.tokenBuffer.bytes.ptr[*tokenBufferIdx], nextIdx = (*tokenBufferIdx) + utf8len; - - while(*tokenBufferIdx < nextIdx) { *stringHash = cdvcalculateHash(*stringHash, parseState->token.tokenBuffer.bytes.ptr[(*tokenBufferIdx)++]); } - - return(0); -} - -//////////// -#pragma mark - -#pragma mark Decoding / parsing / deserializing functions - -static int cdvjk_parse_string(CDVJKParseState *parseState) { - NSCParameterAssert((parseState != NULL) && (CDVJK_AT_STRING_PTR(parseState) <= CDVJK_END_STRING_PTR(parseState))); - const unsigned char *stringStart = CDVJK_AT_STRING_PTR(parseState) + 1; - const unsigned char *endOfBuffer = CDVJK_END_STRING_PTR(parseState); - const unsigned char *atStringCharacter = stringStart; - unsigned char *tokenBuffer = parseState->token.tokenBuffer.bytes.ptr; - size_t tokenStartIndex = parseState->atIndex; - size_t tokenBufferIdx = 0UL; - - int onlySimpleString = 1, stringState = CDVJSONStringStateStart; - uint16_t escapedUnicode1 = 0U, escapedUnicode2 = 0U; - uint32_t escapedUnicodeCodePoint = 0U; - CDVJKHash stringHash = CDVJK_HASH_INIT; - - while(1) { - unsigned long currentChar; - - if(CDVJK_EXPECT_F(atStringCharacter == endOfBuffer)) { /* XXX Add error message */ stringState = CDVJSONStringStateError; goto finishedParsing; } - - if(CDVJK_EXPECT_F((currentChar = *atStringCharacter++) >= 0x80UL)) { - const unsigned char *nextValidCharacter = NULL; - UTF32 u32ch = 0U; - CDV_ConversionResult result; - - if(CDVJK_EXPECT_F((result = cdvConvertSingleCodePointInUTF8(atStringCharacter - 1, endOfBuffer, (UTF8 const **)&nextValidCharacter, &u32ch)) != conversionOK)) { goto switchToSlowPath; } - stringHash = cdvcalculateHash(stringHash, currentChar); - while(atStringCharacter < nextValidCharacter) { NSCParameterAssert(CDVJK_AT_STRING_PTR(parseState) <= CDVJK_END_STRING_PTR(parseState)); stringHash = cdvcalculateHash(stringHash, *atStringCharacter++); } - continue; - } else { - if(CDVJK_EXPECT_F(currentChar == (unsigned long)'"')) { stringState = CDVJSONStringStateFinished; goto finishedParsing; } - - if(CDVJK_EXPECT_F(currentChar == (unsigned long)'\\')) { - switchToSlowPath: - onlySimpleString = 0; - stringState = CDVJSONStringStateParsing; - tokenBufferIdx = (atStringCharacter - stringStart) - 1L; - if(CDVJK_EXPECT_F((tokenBufferIdx + 16UL) > parseState->token.tokenBuffer.bytes.length)) { if((tokenBuffer = cdvjk_managedBuffer_resize(&parseState->token.tokenBuffer, tokenBufferIdx + 1024UL)) == NULL) { cdvjk_error(parseState, @"Internal error: Unable to resize temporary buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = CDVJSONStringStateError; goto finishedParsing; } } - memcpy(tokenBuffer, stringStart, tokenBufferIdx); - goto slowMatch; - } - - if(CDVJK_EXPECT_F(currentChar < 0x20UL)) { cdvjk_error(parseState, @"Invalid character < 0x20 found in string: 0x%2.2x.", currentChar); stringState = CDVJSONStringStateError; goto finishedParsing; } - - stringHash = cdvcalculateHash(stringHash, currentChar); - } - } - - slowMatch: - - for(atStringCharacter = (stringStart + ((atStringCharacter - stringStart) - 1L)); (atStringCharacter < endOfBuffer) && (tokenBufferIdx < parseState->token.tokenBuffer.bytes.length); atStringCharacter++) { - if((tokenBufferIdx + 16UL) > parseState->token.tokenBuffer.bytes.length) { if((tokenBuffer = cdvjk_managedBuffer_resize(&parseState->token.tokenBuffer, tokenBufferIdx + 1024UL)) == NULL) { cdvjk_error(parseState, @"Internal error: Unable to resize temporary buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = CDVJSONStringStateError; goto finishedParsing; } } - - NSCParameterAssert(tokenBufferIdx < parseState->token.tokenBuffer.bytes.length); - - unsigned long currentChar = (*atStringCharacter), escapedChar; - - if(CDVJK_EXPECT_T(stringState == CDVJSONStringStateParsing)) { - if(CDVJK_EXPECT_T(currentChar >= 0x20UL)) { - if(CDVJK_EXPECT_T(currentChar < (unsigned long)0x80)) { // Not a UTF8 sequence - if(CDVJK_EXPECT_F(currentChar == (unsigned long)'"')) { stringState = CDVJSONStringStateFinished; atStringCharacter++; goto finishedParsing; } - if(CDVJK_EXPECT_F(currentChar == (unsigned long)'\\')) { stringState = CDVJSONStringStateEscape; continue; } - stringHash = cdvcalculateHash(stringHash, currentChar); - tokenBuffer[tokenBufferIdx++] = currentChar; - continue; - } else { // UTF8 sequence - const unsigned char *nextValidCharacter = NULL; - UTF32 u32ch = 0U; - CDV_ConversionResult result; - - if(CDVJK_EXPECT_F((result = cdvConvertSingleCodePointInUTF8(atStringCharacter, endOfBuffer, (UTF8 const **)&nextValidCharacter, &u32ch)) != conversionOK)) { - if((result == sourceIllegal) && ((parseState->parseOptionFlags & CDVJKParseOptionLooseUnicode) == 0)) { cdvjk_error(parseState, @"Illegal UTF8 sequence found in \"\" string."); stringState = CDVJSONStringStateError; goto finishedParsing; } - if(result == sourceExhausted) { cdvjk_error(parseState, @"End of buffer reached while parsing UTF8 in \"\" string."); stringState = CDVJSONStringStateError; goto finishedParsing; } - if(cdvjk_string_add_unicodeCodePoint(parseState, u32ch, &tokenBufferIdx, &stringHash)) { cdvjk_error(parseState, @"Internal error: Unable to add UTF8 sequence to internal string buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = CDVJSONStringStateError; goto finishedParsing; } - atStringCharacter = nextValidCharacter - 1; - continue; - } else { - while(atStringCharacter < nextValidCharacter) { tokenBuffer[tokenBufferIdx++] = *atStringCharacter; stringHash = cdvcalculateHash(stringHash, *atStringCharacter++); } - atStringCharacter--; - continue; - } - } - } else { // currentChar < 0x20 - cdvjk_error(parseState, @"Invalid character < 0x20 found in string: 0x%2.2x.", currentChar); stringState = CDVJSONStringStateError; goto finishedParsing; - } - - } else { // stringState != CDVJSONStringStateParsing - int isSurrogate = 1; - - switch(stringState) { - case CDVJSONStringStateEscape: - switch(currentChar) { - case 'u': escapedUnicode1 = 0U; escapedUnicode2 = 0U; escapedUnicodeCodePoint = 0U; stringState = CDVJSONStringStateEscapedUnicode1; break; - - case 'b': escapedChar = '\b'; goto parsedEscapedChar; - case 'f': escapedChar = '\f'; goto parsedEscapedChar; - case 'n': escapedChar = '\n'; goto parsedEscapedChar; - case 'r': escapedChar = '\r'; goto parsedEscapedChar; - case 't': escapedChar = '\t'; goto parsedEscapedChar; - case '\\': escapedChar = '\\'; goto parsedEscapedChar; - case '/': escapedChar = '/'; goto parsedEscapedChar; - case '"': escapedChar = '"'; goto parsedEscapedChar; - - parsedEscapedChar: - stringState = CDVJSONStringStateParsing; - stringHash = cdvcalculateHash(stringHash, escapedChar); - tokenBuffer[tokenBufferIdx++] = escapedChar; - break; - - default: cdvjk_error(parseState, @"Invalid escape sequence found in \"\" string."); stringState = CDVJSONStringStateError; goto finishedParsing; break; - } - break; - - case CDVJSONStringStateEscapedUnicode1: - case CDVJSONStringStateEscapedUnicode2: - case CDVJSONStringStateEscapedUnicode3: - case CDVJSONStringStateEscapedUnicode4: isSurrogate = 0; - case CDVJSONStringStateEscapedUnicodeSurrogate1: - case CDVJSONStringStateEscapedUnicodeSurrogate2: - case CDVJSONStringStateEscapedUnicodeSurrogate3: - case CDVJSONStringStateEscapedUnicodeSurrogate4: - { - uint16_t hexValue = 0U; - - switch(currentChar) { - case '0' ... '9': hexValue = currentChar - '0'; goto parsedHex; - case 'a' ... 'f': hexValue = (currentChar - 'a') + 10U; goto parsedHex; - case 'A' ... 'F': hexValue = (currentChar - 'A') + 10U; goto parsedHex; - - parsedHex: - if(!isSurrogate) { escapedUnicode1 = (escapedUnicode1 << 4) | hexValue; } else { escapedUnicode2 = (escapedUnicode2 << 4) | hexValue; } - - if(stringState == CDVJSONStringStateEscapedUnicode4) { - if(((escapedUnicode1 >= 0xD800U) && (escapedUnicode1 < 0xE000U))) { - if((escapedUnicode1 >= 0xD800U) && (escapedUnicode1 < 0xDC00U)) { stringState = CDVJSONStringStateEscapedNeedEscapeForSurrogate; } - else if((escapedUnicode1 >= 0xDC00U) && (escapedUnicode1 < 0xE000U)) { - if((parseState->parseOptionFlags & CDVJKParseOptionLooseUnicode)) { escapedUnicodeCodePoint = CDV_UNI_REPLACEMENT_CHAR; } - else { cdvjk_error(parseState, @"Illegal \\u Unicode escape sequence."); stringState = CDVJSONStringStateError; goto finishedParsing; } - } - } - else { escapedUnicodeCodePoint = escapedUnicode1; } - } - - if(stringState == CDVJSONStringStateEscapedUnicodeSurrogate4) { - if((escapedUnicode2 < 0xdc00) || (escapedUnicode2 > 0xdfff)) { - if((parseState->parseOptionFlags & CDVJKParseOptionLooseUnicode)) { escapedUnicodeCodePoint = CDV_UNI_REPLACEMENT_CHAR; } - else { cdvjk_error(parseState, @"Illegal \\u Unicode escape sequence."); stringState = CDVJSONStringStateError; goto finishedParsing; } - } - else { escapedUnicodeCodePoint = ((escapedUnicode1 - 0xd800) * 0x400) + (escapedUnicode2 - 0xdc00) + 0x10000; } - } - - if((stringState == CDVJSONStringStateEscapedUnicode4) || (stringState == CDVJSONStringStateEscapedUnicodeSurrogate4)) { - if((cdvisValidCodePoint(&escapedUnicodeCodePoint) == sourceIllegal) && ((parseState->parseOptionFlags & CDVJKParseOptionLooseUnicode) == 0)) { cdvjk_error(parseState, @"Illegal \\u Unicode escape sequence."); stringState = CDVJSONStringStateError; goto finishedParsing; } - stringState = CDVJSONStringStateParsing; - if(cdvjk_string_add_unicodeCodePoint(parseState, escapedUnicodeCodePoint, &tokenBufferIdx, &stringHash)) { cdvjk_error(parseState, @"Internal error: Unable to add UTF8 sequence to internal string buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = CDVJSONStringStateError; goto finishedParsing; } - } - else if((stringState >= CDVJSONStringStateEscapedUnicode1) && (stringState <= CDVJSONStringStateEscapedUnicodeSurrogate4)) { stringState++; } - break; - - default: cdvjk_error(parseState, @"Unexpected character found in \\u Unicode escape sequence. Found '%c', expected [0-9a-fA-F].", currentChar); stringState = CDVJSONStringStateError; goto finishedParsing; break; - } - } - break; - - case CDVJSONStringStateEscapedNeedEscapeForSurrogate: - if(currentChar == '\\') { stringState = CDVJSONStringStateEscapedNeedEscapedUForSurrogate; } - else { - if((parseState->parseOptionFlags & CDVJKParseOptionLooseUnicode) == 0) { cdvjk_error(parseState, @"Required a second \\u Unicode escape sequence following a surrogate \\u Unicode escape sequence."); stringState = CDVJSONStringStateError; goto finishedParsing; } - else { stringState = CDVJSONStringStateParsing; atStringCharacter--; if(cdvjk_string_add_unicodeCodePoint(parseState, CDV_UNI_REPLACEMENT_CHAR, &tokenBufferIdx, &stringHash)) { cdvjk_error(parseState, @"Internal error: Unable to add UTF8 sequence to internal string buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = CDVJSONStringStateError; goto finishedParsing; } } - } - break; - - case CDVJSONStringStateEscapedNeedEscapedUForSurrogate: - if(currentChar == 'u') { stringState = CDVJSONStringStateEscapedUnicodeSurrogate1; } - else { - if((parseState->parseOptionFlags & CDVJKParseOptionLooseUnicode) == 0) { cdvjk_error(parseState, @"Required a second \\u Unicode escape sequence following a surrogate \\u Unicode escape sequence."); stringState = CDVJSONStringStateError; goto finishedParsing; } - else { stringState = CDVJSONStringStateParsing; atStringCharacter -= 2; if(cdvjk_string_add_unicodeCodePoint(parseState, CDV_UNI_REPLACEMENT_CHAR, &tokenBufferIdx, &stringHash)) { cdvjk_error(parseState, @"Internal error: Unable to add UTF8 sequence to internal string buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = CDVJSONStringStateError; goto finishedParsing; } } - } - break; - - default: cdvjk_error(parseState, @"Internal error: Unknown stringState. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = CDVJSONStringStateError; goto finishedParsing; break; - } - } - } - -finishedParsing: - - if(CDVJK_EXPECT_T(stringState == CDVJSONStringStateFinished)) { - NSCParameterAssert((parseState->stringBuffer.bytes.ptr + tokenStartIndex) < atStringCharacter); - - parseState->token.tokenPtrRange.ptr = parseState->stringBuffer.bytes.ptr + tokenStartIndex; - parseState->token.tokenPtrRange.length = (atStringCharacter - parseState->token.tokenPtrRange.ptr); - - if(CDVJK_EXPECT_T(onlySimpleString)) { - NSCParameterAssert(((parseState->token.tokenPtrRange.ptr + 1) < endOfBuffer) && (parseState->token.tokenPtrRange.length >= 2UL) && (((parseState->token.tokenPtrRange.ptr + 1) + (parseState->token.tokenPtrRange.length - 2)) < endOfBuffer)); - parseState->token.value.ptrRange.ptr = parseState->token.tokenPtrRange.ptr + 1; - parseState->token.value.ptrRange.length = parseState->token.tokenPtrRange.length - 2UL; - } else { - parseState->token.value.ptrRange.ptr = parseState->token.tokenBuffer.bytes.ptr; - parseState->token.value.ptrRange.length = tokenBufferIdx; - } - - parseState->token.value.hash = stringHash; - parseState->token.value.type = CDVJKValueTypeString; - parseState->atIndex = (atStringCharacter - parseState->stringBuffer.bytes.ptr); - } - - if(CDVJK_EXPECT_F(stringState != CDVJSONStringStateFinished)) { cdvjk_error(parseState, @"Invalid string."); } - return(CDVJK_EXPECT_T(stringState == CDVJSONStringStateFinished) ? 0 : 1); -} - -static int cdvjk_parse_number(CDVJKParseState *parseState) { - NSCParameterAssert((parseState != NULL) && (CDVJK_AT_STRING_PTR(parseState) <= CDVJK_END_STRING_PTR(parseState))); - const unsigned char *numberStart = CDVJK_AT_STRING_PTR(parseState); - const unsigned char *endOfBuffer = CDVJK_END_STRING_PTR(parseState); - const unsigned char *atNumberCharacter = NULL; - int numberState = CDVJSONNumberStateWholeNumberStart, isFloatingPoint = 0, isNegative = 0, backup = 0; - size_t startingIndex = parseState->atIndex; - - for(atNumberCharacter = numberStart; (CDVJK_EXPECT_T(atNumberCharacter < endOfBuffer)) && (CDVJK_EXPECT_T(!(CDVJK_EXPECT_F(numberState == CDVJSONNumberStateFinished) || CDVJK_EXPECT_F(numberState == CDVJSONNumberStateError)))); atNumberCharacter++) { - unsigned long currentChar = (unsigned long)(*atNumberCharacter), lowerCaseCC = currentChar | 0x20UL; - - switch(numberState) { - case CDVJSONNumberStateWholeNumberStart: if (currentChar == '-') { numberState = CDVJSONNumberStateWholeNumberMinus; isNegative = 1; break; } - case CDVJSONNumberStateWholeNumberMinus: if (currentChar == '0') { numberState = CDVJSONNumberStateWholeNumberZero; break; } - else if( (currentChar >= '1') && (currentChar <= '9')) { numberState = CDVJSONNumberStateWholeNumber; break; } - else { /* XXX Add error message */ numberState = CDVJSONNumberStateError; break; } - case CDVJSONNumberStateExponentStart: if( (currentChar == '+') || (currentChar == '-')) { numberState = CDVJSONNumberStateExponentPlusMinus; break; } - case CDVJSONNumberStateFractionalNumberStart: - case CDVJSONNumberStateExponentPlusMinus:if(!((currentChar >= '0') && (currentChar <= '9'))) { /* XXX Add error message */ numberState = CDVJSONNumberStateError; break; } - else { if(numberState == CDVJSONNumberStateFractionalNumberStart) { numberState = CDVJSONNumberStateFractionalNumber; } - else { numberState = CDVJSONNumberStateExponent; } break; } - case CDVJSONNumberStateWholeNumberZero: - case CDVJSONNumberStateWholeNumber: if (currentChar == '.') { numberState = CDVJSONNumberStateFractionalNumberStart; isFloatingPoint = 1; break; } - case CDVJSONNumberStateFractionalNumber: if (lowerCaseCC == 'e') { numberState = CDVJSONNumberStateExponentStart; isFloatingPoint = 1; break; } - case CDVJSONNumberStateExponent: if(!((currentChar >= '0') && (currentChar <= '9')) || (numberState == CDVJSONNumberStateWholeNumberZero)) { numberState = CDVJSONNumberStateFinished; backup = 1; break; } - break; - default: /* XXX Add error message */ numberState = CDVJSONNumberStateError; break; - } - } - - parseState->token.tokenPtrRange.ptr = parseState->stringBuffer.bytes.ptr + startingIndex; - parseState->token.tokenPtrRange.length = (atNumberCharacter - parseState->token.tokenPtrRange.ptr) - backup; - parseState->atIndex = (parseState->token.tokenPtrRange.ptr + parseState->token.tokenPtrRange.length) - parseState->stringBuffer.bytes.ptr; - - if(CDVJK_EXPECT_T(numberState == CDVJSONNumberStateFinished)) { - unsigned char numberTempBuf[parseState->token.tokenPtrRange.length + 4UL]; - unsigned char *endOfNumber = NULL; - - memcpy(numberTempBuf, parseState->token.tokenPtrRange.ptr, parseState->token.tokenPtrRange.length); - numberTempBuf[parseState->token.tokenPtrRange.length] = 0; - - errno = 0; - - // Treat "-0" as a floating point number, which is capable of representing negative zeros. - if(CDVJK_EXPECT_F(parseState->token.tokenPtrRange.length == 2UL) && CDVJK_EXPECT_F(numberTempBuf[1] == '0') && CDVJK_EXPECT_F(isNegative)) { isFloatingPoint = 1; } - - if(isFloatingPoint) { - parseState->token.value.number.doubleValue = strtod((const char *)numberTempBuf, (char **)&endOfNumber); // strtod is documented to return U+2261 (identical to) 0.0 on an underflow error (along with setting errno to ERANGE). - parseState->token.value.type = CDVJKValueTypeDouble; - parseState->token.value.ptrRange.ptr = (const unsigned char *)&parseState->token.value.number.doubleValue; - parseState->token.value.ptrRange.length = sizeof(double); - parseState->token.value.hash = (CDVJK_HASH_INIT + parseState->token.value.type); - } else { - if(isNegative) { - parseState->token.value.number.longLongValue = strtoll((const char *)numberTempBuf, (char **)&endOfNumber, 10); - parseState->token.value.type = CDVJKValueTypeLongLong; - parseState->token.value.ptrRange.ptr = (const unsigned char *)&parseState->token.value.number.longLongValue; - parseState->token.value.ptrRange.length = sizeof(long long); - parseState->token.value.hash = (CDVJK_HASH_INIT + parseState->token.value.type) + (CDVJKHash)parseState->token.value.number.longLongValue; - } else { - parseState->token.value.number.unsignedLongLongValue = strtoull((const char *)numberTempBuf, (char **)&endOfNumber, 10); - parseState->token.value.type = CDVJKValueTypeUnsignedLongLong; - parseState->token.value.ptrRange.ptr = (const unsigned char *)&parseState->token.value.number.unsignedLongLongValue; - parseState->token.value.ptrRange.length = sizeof(unsigned long long); - parseState->token.value.hash = (CDVJK_HASH_INIT + parseState->token.value.type) + (CDVJKHash)parseState->token.value.number.unsignedLongLongValue; - } - } - - if(CDVJK_EXPECT_F(errno != 0)) { - numberState = CDVJSONNumberStateError; - if(errno == ERANGE) { - switch(parseState->token.value.type) { - case CDVJKValueTypeDouble: cdvjk_error(parseState, @"The value '%s' could not be represented as a 'double' due to %s.", numberTempBuf, (parseState->token.value.number.doubleValue == 0.0) ? "underflow" : "overflow"); break; // see above for == 0.0. - case CDVJKValueTypeLongLong: cdvjk_error(parseState, @"The value '%s' exceeded the minimum value that could be represented: %lld.", numberTempBuf, parseState->token.value.number.longLongValue); break; - case CDVJKValueTypeUnsignedLongLong: cdvjk_error(parseState, @"The value '%s' exceeded the maximum value that could be represented: %llu.", numberTempBuf, parseState->token.value.number.unsignedLongLongValue); break; - default: cdvjk_error(parseState, @"Internal error: Unknown token value type. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break; - } - } - } - if(CDVJK_EXPECT_F(endOfNumber != &numberTempBuf[parseState->token.tokenPtrRange.length]) && CDVJK_EXPECT_F(numberState != CDVJSONNumberStateError)) { numberState = CDVJSONNumberStateError; cdvjk_error(parseState, @"The conversion function did not consume all of the number tokens characters."); } - - size_t hashIndex = 0UL; - for(hashIndex = 0UL; hashIndex < parseState->token.value.ptrRange.length; hashIndex++) { parseState->token.value.hash = cdvcalculateHash(parseState->token.value.hash, parseState->token.value.ptrRange.ptr[hashIndex]); } - } - - if(CDVJK_EXPECT_F(numberState != CDVJSONNumberStateFinished)) { cdvjk_error(parseState, @"Invalid number."); } - return(CDVJK_EXPECT_T((numberState == CDVJSONNumberStateFinished)) ? 0 : 1); -} - -CDVJK_STATIC_INLINE void cdvjk_set_parsed_token(CDVJKParseState *parseState, const unsigned char *ptr, size_t length, CDVJKTokenType type, size_t advanceBy) { - parseState->token.tokenPtrRange.ptr = ptr; - parseState->token.tokenPtrRange.length = length; - parseState->token.type = type; - parseState->atIndex += advanceBy; -} - -static size_t cdvjk_parse_is_newline(CDVJKParseState *parseState, const unsigned char *atCharacterPtr) { - NSCParameterAssert((parseState != NULL) && (atCharacterPtr != NULL) && (atCharacterPtr >= parseState->stringBuffer.bytes.ptr) && (atCharacterPtr < CDVJK_END_STRING_PTR(parseState))); - const unsigned char *endOfStringPtr = CDVJK_END_STRING_PTR(parseState); - - if(CDVJK_EXPECT_F(atCharacterPtr >= endOfStringPtr)) { return(0UL); } - - if(CDVJK_EXPECT_F((*(atCharacterPtr + 0)) == '\n')) { return(1UL); } - if(CDVJK_EXPECT_F((*(atCharacterPtr + 0)) == '\r')) { if((CDVJK_EXPECT_T((atCharacterPtr + 1) < endOfStringPtr)) && ((*(atCharacterPtr + 1)) == '\n')) { return(2UL); } return(1UL); } - if(parseState->parseOptionFlags & CDVJKParseOptionUnicodeNewlines) { - if((CDVJK_EXPECT_F((*(atCharacterPtr + 0)) == 0xc2)) && (((atCharacterPtr + 1) < endOfStringPtr) && ((*(atCharacterPtr + 1)) == 0x85))) { return(2UL); } - if((CDVJK_EXPECT_F((*(atCharacterPtr + 0)) == 0xe2)) && (((atCharacterPtr + 2) < endOfStringPtr) && ((*(atCharacterPtr + 1)) == 0x80) && (((*(atCharacterPtr + 2)) == 0xa8) || ((*(atCharacterPtr + 2)) == 0xa9)))) { return(3UL); } - } - - return(0UL); -} - -CDVJK_STATIC_INLINE int cdvjk_parse_skip_newline(CDVJKParseState *parseState) { - size_t newlineAdvanceAtIndex = 0UL; - if(CDVJK_EXPECT_F((newlineAdvanceAtIndex = cdvjk_parse_is_newline(parseState, CDVJK_AT_STRING_PTR(parseState))) > 0UL)) { parseState->lineNumber++; parseState->atIndex += (newlineAdvanceAtIndex - 1UL); parseState->lineStartIndex = parseState->atIndex + 1UL; return(1); } - return(0); -} - -CDVJK_STATIC_INLINE void cdvjk_parse_skip_whitespace(CDVJKParseState *parseState) { -#ifndef __clang_analyzer__ - NSCParameterAssert((parseState != NULL) && (CDVJK_AT_STRING_PTR(parseState) <= CDVJK_END_STRING_PTR(parseState))); - const unsigned char *atCharacterPtr = NULL; - const unsigned char *endOfStringPtr = CDVJK_END_STRING_PTR(parseState); - - for(atCharacterPtr = CDVJK_AT_STRING_PTR(parseState); (CDVJK_EXPECT_T((atCharacterPtr = CDVJK_AT_STRING_PTR(parseState)) < endOfStringPtr)); parseState->atIndex++) { - if(((*(atCharacterPtr + 0)) == ' ') || ((*(atCharacterPtr + 0)) == '\t')) { continue; } - if(cdvjk_parse_skip_newline(parseState)) { continue; } - if(parseState->parseOptionFlags & CDVJKParseOptionComments) { - if((CDVJK_EXPECT_F((*(atCharacterPtr + 0)) == '/')) && (CDVJK_EXPECT_T((atCharacterPtr + 1) < endOfStringPtr))) { - if((*(atCharacterPtr + 1)) == '/') { - parseState->atIndex++; - for(atCharacterPtr = CDVJK_AT_STRING_PTR(parseState); (CDVJK_EXPECT_T((atCharacterPtr = CDVJK_AT_STRING_PTR(parseState)) < endOfStringPtr)); parseState->atIndex++) { if(cdvjk_parse_skip_newline(parseState)) { break; } } - continue; - } - if((*(atCharacterPtr + 1)) == '*') { - parseState->atIndex++; - for(atCharacterPtr = CDVJK_AT_STRING_PTR(parseState); (CDVJK_EXPECT_T((atCharacterPtr = CDVJK_AT_STRING_PTR(parseState)) < endOfStringPtr)); parseState->atIndex++) { - if(cdvjk_parse_skip_newline(parseState)) { continue; } - if(((*(atCharacterPtr + 0)) == '*') && (((atCharacterPtr + 1) < endOfStringPtr) && ((*(atCharacterPtr + 1)) == '/'))) { parseState->atIndex++; break; } - } - continue; - } - } - } - break; - } -#endif -} - -static int cdvjk_parse_next_token(CDVJKParseState *parseState) { - NSCParameterAssert((parseState != NULL) && (CDVJK_AT_STRING_PTR(parseState) <= CDVJK_END_STRING_PTR(parseState))); - const unsigned char *atCharacterPtr = NULL; - const unsigned char *endOfStringPtr = CDVJK_END_STRING_PTR(parseState); - unsigned char currentCharacter = 0U; - int stopParsing = 0; - - parseState->prev_atIndex = parseState->atIndex; - parseState->prev_lineNumber = parseState->lineNumber; - parseState->prev_lineStartIndex = parseState->lineStartIndex; - - cdvjk_parse_skip_whitespace(parseState); - - if((CDVJK_AT_STRING_PTR(parseState) == endOfStringPtr)) { stopParsing = 1; } - - if((CDVJK_EXPECT_T(stopParsing == 0)) && (CDVJK_EXPECT_T((atCharacterPtr = CDVJK_AT_STRING_PTR(parseState)) < endOfStringPtr))) { - currentCharacter = *atCharacterPtr; - - if(CDVJK_EXPECT_T(currentCharacter == '"')) { if(CDVJK_EXPECT_T((stopParsing = cdvjk_parse_string(parseState)) == 0)) { cdvjk_set_parsed_token(parseState, parseState->token.tokenPtrRange.ptr, parseState->token.tokenPtrRange.length, CDVJKTokenTypeString, 0UL); } } - else if(CDVJK_EXPECT_T(currentCharacter == ':')) { cdvjk_set_parsed_token(parseState, atCharacterPtr, 1UL, CDVJKTokenTypeSeparator, 1UL); } - else if(CDVJK_EXPECT_T(currentCharacter == ',')) { cdvjk_set_parsed_token(parseState, atCharacterPtr, 1UL, CDVJKTokenTypeComma, 1UL); } - else if((CDVJK_EXPECT_T(currentCharacter >= '0') && CDVJK_EXPECT_T(currentCharacter <= '9')) || CDVJK_EXPECT_T(currentCharacter == '-')) { if(CDVJK_EXPECT_T((stopParsing = cdvjk_parse_number(parseState)) == 0)) { cdvjk_set_parsed_token(parseState, parseState->token.tokenPtrRange.ptr, parseState->token.tokenPtrRange.length, CDVJKTokenTypeNumber, 0UL); } } - else if(CDVJK_EXPECT_T(currentCharacter == '{')) { cdvjk_set_parsed_token(parseState, atCharacterPtr, 1UL, CDVJKTokenTypeObjectBegin, 1UL); } - else if(CDVJK_EXPECT_T(currentCharacter == '}')) { cdvjk_set_parsed_token(parseState, atCharacterPtr, 1UL, CDVJKTokenTypeObjectEnd, 1UL); } - else if(CDVJK_EXPECT_T(currentCharacter == '[')) { cdvjk_set_parsed_token(parseState, atCharacterPtr, 1UL, CDVJKTokenTypeArrayBegin, 1UL); } - else if(CDVJK_EXPECT_T(currentCharacter == ']')) { cdvjk_set_parsed_token(parseState, atCharacterPtr, 1UL, CDVJKTokenTypeArrayEnd, 1UL); } - - else if(CDVJK_EXPECT_T(currentCharacter == 't')) { if(!((CDVJK_EXPECT_T((atCharacterPtr + 4UL) < endOfStringPtr)) && (CDVJK_EXPECT_T(atCharacterPtr[1] == 'r')) && (CDVJK_EXPECT_T(atCharacterPtr[2] == 'u')) && (CDVJK_EXPECT_T(atCharacterPtr[3] == 'e')))) { stopParsing = 1; /* XXX Add error message */ } else { cdvjk_set_parsed_token(parseState, atCharacterPtr, 4UL, CDVJKTokenTypeTrue, 4UL); } } - else if(CDVJK_EXPECT_T(currentCharacter == 'f')) { if(!((CDVJK_EXPECT_T((atCharacterPtr + 5UL) < endOfStringPtr)) && (CDVJK_EXPECT_T(atCharacterPtr[1] == 'a')) && (CDVJK_EXPECT_T(atCharacterPtr[2] == 'l')) && (CDVJK_EXPECT_T(atCharacterPtr[3] == 's')) && (CDVJK_EXPECT_T(atCharacterPtr[4] == 'e')))) { stopParsing = 1; /* XXX Add error message */ } else { cdvjk_set_parsed_token(parseState, atCharacterPtr, 5UL, CDVJKTokenTypeFalse, 5UL); } } - else if(CDVJK_EXPECT_T(currentCharacter == 'n')) { if(!((CDVJK_EXPECT_T((atCharacterPtr + 4UL) < endOfStringPtr)) && (CDVJK_EXPECT_T(atCharacterPtr[1] == 'u')) && (CDVJK_EXPECT_T(atCharacterPtr[2] == 'l')) && (CDVJK_EXPECT_T(atCharacterPtr[3] == 'l')))) { stopParsing = 1; /* XXX Add error message */ } else { cdvjk_set_parsed_token(parseState, atCharacterPtr, 4UL, CDVJKTokenTypeNull, 4UL); } } - else { stopParsing = 1; /* XXX Add error message */ } - } - - if(CDVJK_EXPECT_F(stopParsing)) { cdvjk_error(parseState, @"Unexpected token, wanted '{', '}', '[', ']', ',', ':', 'true', 'false', 'null', '\"STRING\"', 'NUMBER'."); } - return(stopParsing); -} - -static void cdvjk_error_parse_accept_or3(CDVJKParseState *parseState, int state, NSString *or1String, NSString *or2String, NSString *or3String) { - NSString *acceptStrings[16]; - int acceptIdx = 0; - if(state & CDVJKParseAcceptValue) { acceptStrings[acceptIdx++] = or1String; } - if(state & CDVJKParseAcceptComma) { acceptStrings[acceptIdx++] = or2String; } - if(state & CDVJKParseAcceptEnd) { acceptStrings[acceptIdx++] = or3String; } - if(acceptIdx == 1) { cdvjk_error(parseState, @"Expected %@, not '%*.*s'", acceptStrings[0], (int)parseState->token.tokenPtrRange.length, (int)parseState->token.tokenPtrRange.length, parseState->token.tokenPtrRange.ptr); } - else if(acceptIdx == 2) { cdvjk_error(parseState, @"Expected %@ or %@, not '%*.*s'", acceptStrings[0], acceptStrings[1], (int)parseState->token.tokenPtrRange.length, (int)parseState->token.tokenPtrRange.length, parseState->token.tokenPtrRange.ptr); } - else if(acceptIdx == 3) { cdvjk_error(parseState, @"Expected %@, %@, or %@, not '%*.*s", acceptStrings[0], acceptStrings[1], acceptStrings[2], (int)parseState->token.tokenPtrRange.length, (int)parseState->token.tokenPtrRange.length, parseState->token.tokenPtrRange.ptr); } -} - -static void *cdvjk_parse_array(CDVJKParseState *parseState) { - size_t startingObjectIndex = parseState->objectStack.index; - int arrayState = CDVJKParseAcceptValueOrEnd, stopParsing = 0; - void *parsedArray = NULL; - - while(CDVJK_EXPECT_T((CDVJK_EXPECT_T(stopParsing == 0)) && (CDVJK_EXPECT_T(parseState->atIndex < parseState->stringBuffer.bytes.length)))) { - if(CDVJK_EXPECT_F(parseState->objectStack.index > (parseState->objectStack.count - 4UL))) { if(cdvjk_objectStack_resize(&parseState->objectStack, parseState->objectStack.count + 128UL)) { cdvjk_error(parseState, @"Internal error: [array] objectsIndex > %zu, resize failed? %@ line %#ld", (parseState->objectStack.count - 4UL), [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break; } } - - if(CDVJK_EXPECT_T((stopParsing = cdvjk_parse_next_token(parseState)) == 0)) { - void *object = NULL; -#ifndef NS_BLOCK_ASSERTIONS - parseState->objectStack.objects[parseState->objectStack.index] = NULL; - parseState->objectStack.keys [parseState->objectStack.index] = NULL; -#endif - switch(parseState->token.type) { - case CDVJKTokenTypeNumber: - case CDVJKTokenTypeString: - case CDVJKTokenTypeTrue: - case CDVJKTokenTypeFalse: - case CDVJKTokenTypeNull: - case CDVJKTokenTypeArrayBegin: - case CDVJKTokenTypeObjectBegin: - if(CDVJK_EXPECT_F((arrayState & CDVJKParseAcceptValue) == 0)) { parseState->errorIsPrev = 1; cdvjk_error(parseState, @"Unexpected value."); stopParsing = 1; break; } - if(CDVJK_EXPECT_F((object = cdvjk_object_for_token(parseState)) == NULL)) { cdvjk_error(parseState, @"Internal error: Object == NULL"); stopParsing = 1; break; } else { parseState->objectStack.objects[parseState->objectStack.index++] = object; arrayState = CDVJKParseAcceptCommaOrEnd; } - break; - case CDVJKTokenTypeArrayEnd: if(CDVJK_EXPECT_T(arrayState & CDVJKParseAcceptEnd)) { NSCParameterAssert(parseState->objectStack.index >= startingObjectIndex); parsedArray = (void *)_CDVJKArrayCreate((id *)&parseState->objectStack.objects[startingObjectIndex], (parseState->objectStack.index - startingObjectIndex), parseState->mutableCollections); } else { parseState->errorIsPrev = 1; cdvjk_error(parseState, @"Unexpected ']'."); } stopParsing = 1; break; - case CDVJKTokenTypeComma: if(CDVJK_EXPECT_T(arrayState & CDVJKParseAcceptComma)) { arrayState = CDVJKParseAcceptValue; } else { parseState->errorIsPrev = 1; cdvjk_error(parseState, @"Unexpected ','."); stopParsing = 1; } break; - default: parseState->errorIsPrev = 1; cdvjk_error_parse_accept_or3(parseState, arrayState, @"a value", @"a comma", @"a ']'"); stopParsing = 1; break; - } - } - } - - if(CDVJK_EXPECT_F(parsedArray == NULL)) { size_t idx = 0UL; for(idx = startingObjectIndex; idx < parseState->objectStack.index; idx++) { if(parseState->objectStack.objects[idx] != NULL) { CFRelease(parseState->objectStack.objects[idx]); parseState->objectStack.objects[idx] = NULL; } } } -#if !defined(NS_BLOCK_ASSERTIONS) - else { size_t idx = 0UL; for(idx = startingObjectIndex; idx < parseState->objectStack.index; idx++) { parseState->objectStack.objects[idx] = NULL; parseState->objectStack.keys[idx] = NULL; } } -#endif - - parseState->objectStack.index = startingObjectIndex; - return(parsedArray); -} - -static void *cdvjk_create_dictionary(CDVJKParseState *parseState, size_t startingObjectIndex) { - void *parsedDictionary = NULL; - - parseState->objectStack.index--; - - parsedDictionary = _CDVJKDictionaryCreate((id *)&parseState->objectStack.keys[startingObjectIndex], (NSUInteger *)&parseState->objectStack.cfHashes[startingObjectIndex], (id *)&parseState->objectStack.objects[startingObjectIndex], (parseState->objectStack.index - startingObjectIndex), parseState->mutableCollections); - - return(parsedDictionary); -} - -static void *cdvjk_parse_dictionary(CDVJKParseState *parseState) { - size_t startingObjectIndex = parseState->objectStack.index; - int dictState = CDVJKParseAcceptValueOrEnd, stopParsing = 0; - void *parsedDictionary = NULL; - - while(CDVJK_EXPECT_T((CDVJK_EXPECT_T(stopParsing == 0)) && (CDVJK_EXPECT_T(parseState->atIndex < parseState->stringBuffer.bytes.length)))) { - if(CDVJK_EXPECT_F(parseState->objectStack.index > (parseState->objectStack.count - 4UL))) { if(cdvjk_objectStack_resize(&parseState->objectStack, parseState->objectStack.count + 128UL)) { cdvjk_error(parseState, @"Internal error: [dictionary] objectsIndex > %zu, resize failed? %@ line #%ld", (parseState->objectStack.count - 4UL), [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break; } } - - size_t objectStackIndex = parseState->objectStack.index++; - parseState->objectStack.keys[objectStackIndex] = NULL; - parseState->objectStack.objects[objectStackIndex] = NULL; - void *key = NULL, *object = NULL; - - if(CDVJK_EXPECT_T((CDVJK_EXPECT_T(stopParsing == 0)) && (CDVJK_EXPECT_T((stopParsing = cdvjk_parse_next_token(parseState)) == 0)))) { - switch(parseState->token.type) { - case CDVJKTokenTypeString: - if(CDVJK_EXPECT_F((dictState & CDVJKParseAcceptValue) == 0)) { parseState->errorIsPrev = 1; cdvjk_error(parseState, @"Unexpected string."); stopParsing = 1; break; } - if(CDVJK_EXPECT_F((key = cdvjk_object_for_token(parseState)) == NULL)) { cdvjk_error(parseState, @"Internal error: Key == NULL."); stopParsing = 1; break; } - else { - parseState->objectStack.keys[objectStackIndex] = key; - if(CDVJK_EXPECT_T(parseState->token.value.cacheItem != NULL)) { if(CDVJK_EXPECT_F(parseState->token.value.cacheItem->cfHash == 0UL)) { parseState->token.value.cacheItem->cfHash = CFHash(key); } parseState->objectStack.cfHashes[objectStackIndex] = parseState->token.value.cacheItem->cfHash; } - else { parseState->objectStack.cfHashes[objectStackIndex] = CFHash(key); } - } - break; - - case CDVJKTokenTypeObjectEnd: if((CDVJK_EXPECT_T(dictState & CDVJKParseAcceptEnd))) { NSCParameterAssert(parseState->objectStack.index >= startingObjectIndex); parsedDictionary = cdvjk_create_dictionary(parseState, startingObjectIndex); } else { parseState->errorIsPrev = 1; cdvjk_error(parseState, @"Unexpected '}'."); } stopParsing = 1; break; - case CDVJKTokenTypeComma: if((CDVJK_EXPECT_T(dictState & CDVJKParseAcceptComma))) { dictState = CDVJKParseAcceptValue; parseState->objectStack.index--; continue; } else { parseState->errorIsPrev = 1; cdvjk_error(parseState, @"Unexpected ','."); stopParsing = 1; } break; - - default: parseState->errorIsPrev = 1; cdvjk_error_parse_accept_or3(parseState, dictState, @"a \"STRING\"", @"a comma", @"a '}'"); stopParsing = 1; break; - } - } - - if(CDVJK_EXPECT_T(stopParsing == 0)) { - if(CDVJK_EXPECT_T((stopParsing = cdvjk_parse_next_token(parseState)) == 0)) { if(CDVJK_EXPECT_F(parseState->token.type != CDVJKTokenTypeSeparator)) { parseState->errorIsPrev = 1; cdvjk_error(parseState, @"Expected ':'."); stopParsing = 1; } } - } - - if((CDVJK_EXPECT_T(stopParsing == 0)) && (CDVJK_EXPECT_T((stopParsing = cdvjk_parse_next_token(parseState)) == 0))) { - switch(parseState->token.type) { - case CDVJKTokenTypeNumber: - case CDVJKTokenTypeString: - case CDVJKTokenTypeTrue: - case CDVJKTokenTypeFalse: - case CDVJKTokenTypeNull: - case CDVJKTokenTypeArrayBegin: - case CDVJKTokenTypeObjectBegin: - if(CDVJK_EXPECT_F((dictState & CDVJKParseAcceptValue) == 0)) { parseState->errorIsPrev = 1; cdvjk_error(parseState, @"Unexpected value."); stopParsing = 1; break; } - if(CDVJK_EXPECT_F((object = cdvjk_object_for_token(parseState)) == NULL)) { cdvjk_error(parseState, @"Internal error: Object == NULL."); stopParsing = 1; break; } else { parseState->objectStack.objects[objectStackIndex] = object; dictState = CDVJKParseAcceptCommaOrEnd; } - break; - default: parseState->errorIsPrev = 1; cdvjk_error_parse_accept_or3(parseState, dictState, @"a value", @"a comma", @"a '}'"); stopParsing = 1; break; - } - } - } - - if(CDVJK_EXPECT_F(parsedDictionary == NULL)) { size_t idx = 0UL; for(idx = startingObjectIndex; idx < parseState->objectStack.index; idx++) { if(parseState->objectStack.keys[idx] != NULL) { CFRelease(parseState->objectStack.keys[idx]); parseState->objectStack.keys[idx] = NULL; } if(parseState->objectStack.objects[idx] != NULL) { CFRelease(parseState->objectStack.objects[idx]); parseState->objectStack.objects[idx] = NULL; } } } -#if !defined(NS_BLOCK_ASSERTIONS) - else { size_t idx = 0UL; for(idx = startingObjectIndex; idx < parseState->objectStack.index; idx++) { parseState->objectStack.objects[idx] = NULL; parseState->objectStack.keys[idx] = NULL; } } -#endif - - parseState->objectStack.index = startingObjectIndex; - return(parsedDictionary); -} - -static id json_parse_it(CDVJKParseState *parseState) { - id parsedObject = NULL; - int stopParsing = 0; - - while((CDVJK_EXPECT_T(stopParsing == 0)) && (CDVJK_EXPECT_T(parseState->atIndex < parseState->stringBuffer.bytes.length))) { - if((CDVJK_EXPECT_T(stopParsing == 0)) && (CDVJK_EXPECT_T((stopParsing = cdvjk_parse_next_token(parseState)) == 0))) { - switch(parseState->token.type) { - case CDVJKTokenTypeArrayBegin: - case CDVJKTokenTypeObjectBegin: parsedObject = [(id)cdvjk_object_for_token(parseState) autorelease]; stopParsing = 1; break; - default: cdvjk_error(parseState, @"Expected either '[' or '{'."); stopParsing = 1; break; - } - } - } - - NSCParameterAssert((parseState->objectStack.index == 0) && (CDVJK_AT_STRING_PTR(parseState) <= CDVJK_END_STRING_PTR(parseState))); - - if((parsedObject == NULL) && (CDVJK_AT_STRING_PTR(parseState) == CDVJK_END_STRING_PTR(parseState))) { cdvjk_error(parseState, @"Reached the end of the buffer."); } - if(parsedObject == NULL) { cdvjk_error(parseState, @"Unable to parse CDVJSON."); } - - if((parsedObject != NULL) && (CDVJK_AT_STRING_PTR(parseState) < CDVJK_END_STRING_PTR(parseState))) { - cdvjk_parse_skip_whitespace(parseState); - if((parsedObject != NULL) && ((parseState->parseOptionFlags & CDVJKParseOptionPermitTextAfterValidJSON) == 0) && (CDVJK_AT_STRING_PTR(parseState) < CDVJK_END_STRING_PTR(parseState))) { - cdvjk_error(parseState, @"A valid CDVJSON object was parsed but there were additional non-white-space characters remaining."); - parsedObject = NULL; - } - } - - return(parsedObject); -} - -//////////// -#pragma mark - -#pragma mark Object cache - -// This uses a Galois Linear Feedback Shift Register (LFSR) PRNG to pick which item in the cache to age. It has a period of (2^32)-1. -// NOTE: A LFSR *MUST* be initialized to a non-zero value and must always have a non-zero value. -CDVJK_STATIC_INLINE void cdvjk_cache_age(CDVJKParseState *parseState) { - NSCParameterAssert((parseState != NULL) && (parseState->cache.prng_lfsr != 0U)); - parseState->cache.prng_lfsr = (parseState->cache.prng_lfsr >> 1) ^ ((0U - (parseState->cache.prng_lfsr & 1U)) & 0x80200003U); - parseState->cache.age[parseState->cache.prng_lfsr & (parseState->cache.count - 1UL)] >>= 1; -} - -// The object cache is nothing more than a hash table with open addressing collision resolution that is bounded by CDVJK_CACHE_PROBES attempts. -// -// The hash table is a linear C array of CDVJKTokenCacheItem. The terms "item" and "bucket" are synonymous with the index in to the cache array, i.e. cache.items[bucket]. -// -// Items in the cache have an age associated with them. The age is the number of rightmost 1 bits, i.e. 0000 = 0, 0001 = 1, 0011 = 2, 0111 = 3, 1111 = 4. -// This allows us to use left and right shifts to add or subtract from an items age. Add = (age << 1) | 1. Subtract = age >> 0. Subtract is synonymous with "age" (i.e., age an item). -// The reason for this is it allows us to perform saturated adds and subtractions and is branchless. -// The primitive C type MUST be unsigned. It is currently a "char", which allows (at a minimum and in practice) 8 bits. -// -// A "useable bucket" is a bucket that is not in use (never populated), or has an age == 0. -// -// When an item is found in the cache, it's age is incremented. -// If a useable bucket hasn't been found, the current item (bucket) is aged along with two random items. -// -// If a value is not found in the cache, and no useable bucket has been found, that value is not added to the cache. - -static void *cdvjk_cachedObjects(CDVJKParseState *parseState) { - unsigned long bucket = parseState->token.value.hash & (parseState->cache.count - 1UL), setBucket = 0UL, useableBucket = 0UL, x = 0UL; - void *parsedAtom = NULL; - - if(CDVJK_EXPECT_F(parseState->token.value.ptrRange.length == 0UL) && CDVJK_EXPECT_T(parseState->token.value.type == CDVJKValueTypeString)) { return(@""); } - - for(x = 0UL; x < CDVJK_CACHE_PROBES; x++) { - if(CDVJK_EXPECT_F(parseState->cache.items[bucket].object == NULL)) { setBucket = 1UL; useableBucket = bucket; break; } - - if((CDVJK_EXPECT_T(parseState->cache.items[bucket].hash == parseState->token.value.hash)) && (CDVJK_EXPECT_T(parseState->cache.items[bucket].size == parseState->token.value.ptrRange.length)) && (CDVJK_EXPECT_T(parseState->cache.items[bucket].type == parseState->token.value.type)) && (CDVJK_EXPECT_T(parseState->cache.items[bucket].bytes != NULL)) && (CDVJK_EXPECT_T(memcmp(parseState->cache.items[bucket].bytes, parseState->token.value.ptrRange.ptr, parseState->token.value.ptrRange.length) == 0U))) { - parseState->cache.age[bucket] = (parseState->cache.age[bucket] << 1) | 1U; - parseState->token.value.cacheItem = &parseState->cache.items[bucket]; - NSCParameterAssert(parseState->cache.items[bucket].object != NULL); - return((void *)CFRetain(parseState->cache.items[bucket].object)); - } else { - if(CDVJK_EXPECT_F(setBucket == 0UL) && CDVJK_EXPECT_F(parseState->cache.age[bucket] == 0U)) { setBucket = 1UL; useableBucket = bucket; } - if(CDVJK_EXPECT_F(setBucket == 0UL)) { parseState->cache.age[bucket] >>= 1; cdvjk_cache_age(parseState); cdvjk_cache_age(parseState); } - // This is the open addressing function. The values length and type are used as a form of "double hashing" to distribute values with the same effective value hash across different object cache buckets. - // The values type is a prime number that is relatively coprime to the other primes in the set of value types and the number of hash table buckets. - bucket = (parseState->token.value.hash + (parseState->token.value.ptrRange.length * (x + 1UL)) + (parseState->token.value.type * (x + 1UL)) + (3UL * (x + 1UL))) & (parseState->cache.count - 1UL); - } - } - - switch(parseState->token.value.type) { - case CDVJKValueTypeString: parsedAtom = (void *)CFStringCreateWithBytes(NULL, parseState->token.value.ptrRange.ptr, parseState->token.value.ptrRange.length, kCFStringEncodingUTF8, 0); break; - case CDVJKValueTypeLongLong: parsedAtom = (void *)CFNumberCreate(NULL, kCFNumberLongLongType, &parseState->token.value.number.longLongValue); break; - case CDVJKValueTypeUnsignedLongLong: - if(parseState->token.value.number.unsignedLongLongValue <= LLONG_MAX) { parsedAtom = (void *)CFNumberCreate(NULL, kCFNumberLongLongType, &parseState->token.value.number.unsignedLongLongValue); } - else { parsedAtom = (void *)parseState->objCImpCache.NSNumberInitWithUnsignedLongLong(parseState->objCImpCache.NSNumberAlloc(parseState->objCImpCache.NSNumberClass, @selector(alloc)), @selector(initWithUnsignedLongLong:), parseState->token.value.number.unsignedLongLongValue); } - break; - case CDVJKValueTypeDouble: parsedAtom = (void *)CFNumberCreate(NULL, kCFNumberDoubleType, &parseState->token.value.number.doubleValue); break; - default: cdvjk_error(parseState, @"Internal error: Unknown token value type. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break; - } - - if(CDVJK_EXPECT_T(setBucket) && (CDVJK_EXPECT_T(parsedAtom != NULL))) { - bucket = useableBucket; - if(CDVJK_EXPECT_T((parseState->cache.items[bucket].object != NULL))) { CFRelease(parseState->cache.items[bucket].object); parseState->cache.items[bucket].object = NULL; } - - if(CDVJK_EXPECT_T((parseState->cache.items[bucket].bytes = (unsigned char *)reallocf(parseState->cache.items[bucket].bytes, parseState->token.value.ptrRange.length)) != NULL)) { - memcpy(parseState->cache.items[bucket].bytes, parseState->token.value.ptrRange.ptr, parseState->token.value.ptrRange.length); - parseState->cache.items[bucket].object = (void *)CFRetain(parsedAtom); - parseState->cache.items[bucket].hash = parseState->token.value.hash; - parseState->cache.items[bucket].cfHash = 0UL; - parseState->cache.items[bucket].size = parseState->token.value.ptrRange.length; - parseState->cache.items[bucket].type = parseState->token.value.type; - parseState->token.value.cacheItem = &parseState->cache.items[bucket]; - parseState->cache.age[bucket] = CDVJK_INIT_CACHE_AGE; - } else { // The realloc failed, so clear the appropriate fields. - parseState->cache.items[bucket].hash = 0UL; - parseState->cache.items[bucket].cfHash = 0UL; - parseState->cache.items[bucket].size = 0UL; - parseState->cache.items[bucket].type = 0UL; - } - } - - return(parsedAtom); -} - - -static void *cdvjk_object_for_token(CDVJKParseState *parseState) { - void *parsedAtom = NULL; - - parseState->token.value.cacheItem = NULL; - switch(parseState->token.type) { - case CDVJKTokenTypeString: parsedAtom = cdvjk_cachedObjects(parseState); break; - case CDVJKTokenTypeNumber: parsedAtom = cdvjk_cachedObjects(parseState); break; - case CDVJKTokenTypeObjectBegin: parsedAtom = cdvjk_parse_dictionary(parseState); break; - case CDVJKTokenTypeArrayBegin: parsedAtom = cdvjk_parse_array(parseState); break; - case CDVJKTokenTypeTrue: parsedAtom = (void *)kCFBooleanTrue; break; - case CDVJKTokenTypeFalse: parsedAtom = (void *)kCFBooleanFalse; break; - case CDVJKTokenTypeNull: parsedAtom = (void *)kCFNull; break; - default: cdvjk_error(parseState, @"Internal error: Unknown token type. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break; - } - - return(parsedAtom); -} - -#pragma mark - -@implementation CDVJSONDecoder - -+ (id)decoder -{ - return([self decoderWithParseOptions:CDVJKParseOptionStrict]); -} - -+ (id)decoderWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags -{ - return([[[self alloc] initWithParseOptions:parseOptionFlags] autorelease]); -} - -- (id)init -{ - return([self initWithParseOptions:CDVJKParseOptionStrict]); -} - -- (id)initWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags -{ - if((self = [super init]) == NULL) { return(NULL); } - - if(parseOptionFlags & ~CDVJKParseOptionValidFlags) { [self autorelease]; [NSException raise:NSInvalidArgumentException format:@"Invalid parse options."]; } - - if((parseState = (CDVJKParseState *)calloc(1UL, sizeof(CDVJKParseState))) == NULL) { goto errorExit; } - - parseState->parseOptionFlags = parseOptionFlags; - - parseState->token.tokenBuffer.roundSizeUpToMultipleOf = 4096UL; - parseState->objectStack.roundSizeUpToMultipleOf = 2048UL; - - parseState->objCImpCache.NSNumberClass = _CDVjk_NSNumberClass; - parseState->objCImpCache.NSNumberAlloc = _CDVjk_NSNumberAllocImp; - parseState->objCImpCache.NSNumberInitWithUnsignedLongLong = _CDVjk_NSNumberInitWithUnsignedLongLongImp; - - parseState->cache.prng_lfsr = 1U; - parseState->cache.count = CDVJK_CACHE_SLOTS; - if((parseState->cache.items = (CDVJKTokenCacheItem *)calloc(1UL, sizeof(CDVJKTokenCacheItem) * parseState->cache.count)) == NULL) { goto errorExit; } - - return(self); - - errorExit: - if(self) { [self autorelease]; self = NULL; } - return(NULL); -} - -// This is here primarily to support the NSString and NSData convenience functions so the autoreleased CDVJSONDecoder can release most of its resources before the pool pops. -static void _JSONDecoderCleanup(CDVJSONDecoder *decoder) { - if((decoder != NULL) && (decoder->parseState != NULL)) { - cdvjk_managedBuffer_release(&decoder->parseState->token.tokenBuffer); - cdvjk_objectStack_release(&decoder->parseState->objectStack); - - [decoder clearCache]; - if(decoder->parseState->cache.items != NULL) { free(decoder->parseState->cache.items); decoder->parseState->cache.items = NULL; } - - free(decoder->parseState); decoder->parseState = NULL; - } -} - -- (void)dealloc -{ - _JSONDecoderCleanup(self); - [super dealloc]; -} - -- (void)clearCache -{ - if(CDVJK_EXPECT_T(parseState != NULL)) { - if(CDVJK_EXPECT_T(parseState->cache.items != NULL)) { - size_t idx = 0UL; - for(idx = 0UL; idx < parseState->cache.count; idx++) { - if(CDVJK_EXPECT_T(parseState->cache.items[idx].object != NULL)) { CFRelease(parseState->cache.items[idx].object); parseState->cache.items[idx].object = NULL; } - if(CDVJK_EXPECT_T(parseState->cache.items[idx].bytes != NULL)) { free(parseState->cache.items[idx].bytes); parseState->cache.items[idx].bytes = NULL; } - memset(&parseState->cache.items[idx], 0, sizeof(CDVJKTokenCacheItem)); - parseState->cache.age[idx] = 0U; - } - } - } -} - -// This needs to be completely rewritten. -static id _CDVJKParseUTF8String(CDVJKParseState *parseState, BOOL mutableCollections, const unsigned char *string, size_t length, NSError **error) { - NSCParameterAssert((parseState != NULL) && (string != NULL) && (parseState->cache.prng_lfsr != 0U)); - parseState->stringBuffer.bytes.ptr = string; - parseState->stringBuffer.bytes.length = length; - parseState->atIndex = 0UL; - parseState->lineNumber = 1UL; - parseState->lineStartIndex = 0UL; - parseState->prev_atIndex = 0UL; - parseState->prev_lineNumber = 1UL; - parseState->prev_lineStartIndex = 0UL; - parseState->error = NULL; - parseState->errorIsPrev = 0; - parseState->mutableCollections = (mutableCollections == NO) ? NO : YES; - - unsigned char stackTokenBuffer[CDVJK_TOKENBUFFER_SIZE] CDVJK_ALIGNED(64); - cdvjk_managedBuffer_setToStackBuffer(&parseState->token.tokenBuffer, stackTokenBuffer, sizeof(stackTokenBuffer)); - - void *stackObjects [CDVJK_STACK_OBJS] CDVJK_ALIGNED(64); - void *stackKeys [CDVJK_STACK_OBJS] CDVJK_ALIGNED(64); - CFHashCode stackCFHashes[CDVJK_STACK_OBJS] CDVJK_ALIGNED(64); - cdvjk_objectStack_setToStackBuffer(&parseState->objectStack, stackObjects, stackKeys, stackCFHashes, CDVJK_STACK_OBJS); - - id parsedJSON = json_parse_it(parseState); - - if((error != NULL) && (parseState->error != NULL)) { *error = parseState->error; } - - cdvjk_managedBuffer_release(&parseState->token.tokenBuffer); - cdvjk_objectStack_release(&parseState->objectStack); - - parseState->stringBuffer.bytes.ptr = NULL; - parseState->stringBuffer.bytes.length = 0UL; - parseState->atIndex = 0UL; - parseState->lineNumber = 1UL; - parseState->lineStartIndex = 0UL; - parseState->prev_atIndex = 0UL; - parseState->prev_lineNumber = 1UL; - parseState->prev_lineStartIndex = 0UL; - parseState->error = NULL; - parseState->errorIsPrev = 0; - parseState->mutableCollections = NO; - - return(parsedJSON); -} - -//////////// -#pragma mark Deprecated as of v1.4 -//////////// - -// Deprecated in CDVJSONKit v1.4. Use objectWithUTF8String:length: instead. -- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length -{ - return([self objectWithUTF8String:string length:length error:NULL]); -} - -// Deprecated in CDVJSONKit v1.4. Use objectWithUTF8String:length:error: instead. -- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length error:(NSError **)error -{ - return([self objectWithUTF8String:string length:length error:error]); -} - -// Deprecated in CDVJSONKit v1.4. Use objectWithData: instead. -- (id)parseJSONData:(NSData *)jsonData -{ - return([self objectWithData:jsonData error:NULL]); -} - -// Deprecated in CDVJSONKit v1.4. Use objectWithData:error: instead. -- (id)parseJSONData:(NSData *)jsonData error:(NSError **)error -{ - return([self objectWithData:jsonData error:error]); -} - -//////////// -#pragma mark Methods that return immutable collection objects -//////////// - -- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length -{ - return([self objectWithUTF8String:string length:length error:NULL]); -} - -- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error -{ - if(parseState == NULL) { [NSException raise:NSInternalInconsistencyException format:@"parseState is NULL."]; } - if(string == NULL) { [NSException raise:NSInvalidArgumentException format:@"The string argument is NULL."]; } - - return(_CDVJKParseUTF8String(parseState, NO, string, (size_t)length, error)); -} - -- (id)objectWithData:(NSData *)jsonData -{ - return([self objectWithData:jsonData error:NULL]); -} - -- (id)objectWithData:(NSData *)jsonData error:(NSError **)error -{ - if(jsonData == NULL) { [NSException raise:NSInvalidArgumentException format:@"The jsonData argument is NULL."]; } - return([self objectWithUTF8String:(const unsigned char *)[jsonData bytes] length:[jsonData length] error:error]); -} - -//////////// -#pragma mark Methods that return mutable collection objects -//////////// - -- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length -{ - return([self mutableObjectWithUTF8String:string length:length error:NULL]); -} - -- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error -{ - if(parseState == NULL) { [NSException raise:NSInternalInconsistencyException format:@"parseState is NULL."]; } - if(string == NULL) { [NSException raise:NSInvalidArgumentException format:@"The string argument is NULL."]; } - - return(_CDVJKParseUTF8String(parseState, YES, string, (size_t)length, error)); -} - -- (id)mutableObjectWithData:(NSData *)jsonData -{ - return([self mutableObjectWithData:jsonData error:NULL]); -} - -- (id)mutableObjectWithData:(NSData *)jsonData error:(NSError **)error -{ - if(jsonData == NULL) { [NSException raise:NSInvalidArgumentException format:@"The jsonData argument is NULL."]; } - return([self mutableObjectWithUTF8String:(const unsigned char *)[jsonData bytes] length:[jsonData length] error:error]); -} - -@end - -/* - The NSString and NSData convenience methods need a little bit of explanation. - - Prior to CDVJSONKit v1.4, the NSString -objectFromJSONStringWithParseOptions:error: method looked like - - const unsigned char *utf8String = (const unsigned char *)[self UTF8String]; - if(utf8String == NULL) { return(NULL); } - size_t utf8Length = strlen((const char *)utf8String); - return([[JSONDecoder decoderWithParseOptions:parseOptionFlags] parseUTF8String:utf8String length:utf8Length error:error]); - - This changed with v1.4 to a more complicated method. The reason for this is to keep the amount of memory that is - allocated, but not yet freed because it is dependent on the autorelease pool to pop before it can be reclaimed. - - In the simpler v1.3 code, this included all the bytes used to store the -UTF8String along with the CDVJSONDecoder and all its overhead. - - Now we use an autoreleased CFMutableData that is sized to the UTF8 length of the NSString in question and is used to hold the UTF8 - conversion of said string. - - Once parsed, the CFMutableData has its length set to 0. This should, hopefully, allow the CFMutableData to realloc and/or free - the buffer. - - Another change made was a slight modification to CDVJSONDecoder so that most of the cleanup work that was done in -dealloc was moved - to a private, internal function. These convenience routines keep the pointer to the autoreleased CDVJSONDecoder and calls - _JSONDecoderCleanup() to early release the decoders resources since we already know that particular decoder is not going to be used - again. - - If everything goes smoothly, this will most likely result in perhaps a few hundred bytes that are allocated but waiting for the - autorelease pool to pop. This is compared to the thousands and easily hundreds of thousands of bytes that would have been in - autorelease limbo. It's more complicated for us, but a win for the user. - - Autorelease objects are used in case things don't go smoothly. By having them autoreleased, we effectively guarantee that our - requirement to -release the object is always met, not matter what goes wrong. The downside is having a an object or two in - autorelease limbo, but we've done our best to minimize that impact, so it all balances out. - */ - -@implementation NSString (CDVJSONKitDeserializing) - -static id _NSStringObjectFromJSONString(NSString *jsonString, CDVJKParseOptionFlags parseOptionFlags, NSError **error, BOOL mutableCollection) { - id returnObject = NULL; - CFMutableDataRef mutableData = NULL; - CDVJSONDecoder *decoder = NULL; - - CFIndex stringLength = CFStringGetLength((CFStringRef)jsonString); - NSUInteger stringUTF8Length = [jsonString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; - - if((mutableData = (CFMutableDataRef)[(id)CFDataCreateMutable(NULL, (NSUInteger)stringUTF8Length) autorelease]) != NULL) { - UInt8 *utf8String = CFDataGetMutableBytePtr(mutableData); - CFIndex usedBytes = 0L, convertedCount = 0L; - - convertedCount = CFStringGetBytes((CFStringRef)jsonString, CFRangeMake(0L, stringLength), kCFStringEncodingUTF8, '?', NO, utf8String, (NSUInteger)stringUTF8Length, &usedBytes); - if(CDVJK_EXPECT_F(convertedCount != stringLength) || CDVJK_EXPECT_F(usedBytes < 0L)) { if(error != NULL) { *error = [NSError errorWithDomain:@"CDVJKErrorDomain" code:-1L userInfo:[NSDictionary dictionaryWithObject:@"An error occurred converting the contents of a NSString to UTF8." forKey:NSLocalizedDescriptionKey]]; } goto exitNow; } - - if(mutableCollection == NO) { returnObject = [(decoder = [CDVJSONDecoder decoderWithParseOptions:parseOptionFlags]) objectWithUTF8String:(const unsigned char *)utf8String length:(size_t)usedBytes error:error]; } - else { returnObject = [(decoder = [CDVJSONDecoder decoderWithParseOptions:parseOptionFlags]) mutableObjectWithUTF8String:(const unsigned char *)utf8String length:(size_t)usedBytes error:error]; } - } - -exitNow: - if(mutableData != NULL) { CFDataSetLength(mutableData, 0L); } - if(decoder != NULL) { _JSONDecoderCleanup(decoder); } - return(returnObject); -} - -- (id)cdvjk_objectFromJSONString -{ - return([self cdvjk_objectFromJSONStringWithParseOptions:CDVJKParseOptionStrict error:NULL]); -} - -- (id)cdvjk_objectFromJSONStringWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags -{ - return([self cdvjk_objectFromJSONStringWithParseOptions:parseOptionFlags error:NULL]); -} - -- (id)cdvjk_objectFromJSONStringWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags error:(NSError **)error -{ - return(_NSStringObjectFromJSONString(self, parseOptionFlags, error, NO)); -} - - -- (id)cdvjk_mutableObjectFromJSONString -{ - return([self cdvjk_mutableObjectFromJSONStringWithParseOptions:CDVJKParseOptionStrict error:NULL]); -} - -- (id)cdvjk_mutableObjectFromJSONStringWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags -{ - return([self cdvjk_mutableObjectFromJSONStringWithParseOptions:parseOptionFlags error:NULL]); -} - -- (id)cdvjk_mutableObjectFromJSONStringWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags error:(NSError **)error -{ - return(_NSStringObjectFromJSONString(self, parseOptionFlags, error, YES)); -} - -@end - -@implementation NSData (CDVJSONKitDeserializing) - -- (id)cdvjk_objectFromJSONData -{ - return([self cdvjk_objectFromJSONDataWithParseOptions:CDVJKParseOptionStrict error:NULL]); -} - -- (id)cdvjk_objectFromJSONDataWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags -{ - return([self cdvjk_objectFromJSONDataWithParseOptions:parseOptionFlags error:NULL]); -} - -- (id)cdvjk_objectFromJSONDataWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags error:(NSError **)error -{ - CDVJSONDecoder *decoder = NULL; - id returnObject = [(decoder = [CDVJSONDecoder decoderWithParseOptions:parseOptionFlags]) objectWithData:self error:error]; - if(decoder != NULL) { _JSONDecoderCleanup(decoder); } - return(returnObject); -} - -- (id)cdvjk_mutableObjectFromJSONData -{ - return([self cdvjk_mutableObjectFromJSONDataWithParseOptions:CDVJKParseOptionStrict error:NULL]); -} - -- (id)cdvjk_mutableObjectFromJSONDataWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags -{ - return([self cdvjk_mutableObjectFromJSONDataWithParseOptions:parseOptionFlags error:NULL]); -} - -- (id)cdvjk_mutableObjectFromJSONDataWithParseOptions:(CDVJKParseOptionFlags)parseOptionFlags error:(NSError **)error -{ - CDVJSONDecoder *decoder = NULL; - id returnObject = [(decoder = [CDVJSONDecoder decoderWithParseOptions:parseOptionFlags]) mutableObjectWithData:self error:error]; - if(decoder != NULL) { _JSONDecoderCleanup(decoder); } - return(returnObject); -} - - -@end - -//////////// -#pragma mark - -#pragma mark Encoding / deserializing functions - -static void cdvjk_encode_error(CDVJKEncodeState *encodeState, NSString *format, ...) { - NSCParameterAssert((encodeState != NULL) && (format != NULL)); - - va_list varArgsList; - va_start(varArgsList, format); - NSString *formatString = [[[NSString alloc] initWithFormat:format arguments:varArgsList] autorelease]; - va_end(varArgsList); - - if(encodeState->error == NULL) { - encodeState->error = [NSError errorWithDomain:@"CDVJKErrorDomain" code:-1L userInfo: - [NSDictionary dictionaryWithObjectsAndKeys: - formatString, NSLocalizedDescriptionKey, - NULL]]; - } -} - -CDVJK_STATIC_INLINE void cdvjk_encode_updateCache(CDVJKEncodeState *encodeState, CDVJKEncodeCache *cacheSlot, size_t startingAtIndex, id object) { - NSCParameterAssert(encodeState != NULL); - if(CDVJK_EXPECT_T(cacheSlot != NULL)) { - NSCParameterAssert((object != NULL) && (startingAtIndex <= encodeState->atIndex)); - cacheSlot->object = object; - cacheSlot->offset = startingAtIndex; - cacheSlot->length = (size_t)(encodeState->atIndex - startingAtIndex); - } -} - -static int cdvjk_encode_printf(CDVJKEncodeState *encodeState, CDVJKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format, ...) { - va_list varArgsList, varArgsListCopy; - va_start(varArgsList, format); - va_copy(varArgsListCopy, varArgsList); - - NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (startingAtIndex <= encodeState->atIndex) && (format != NULL)); - - ssize_t formattedStringLength = 0L; - int returnValue = 0; - - if(CDVJK_EXPECT_T((formattedStringLength = vsnprintf((char *)&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex], (encodeState->stringBuffer.bytes.length - encodeState->atIndex), format, varArgsList)) >= (ssize_t)(encodeState->stringBuffer.bytes.length - encodeState->atIndex))) { - NSCParameterAssert(((encodeState->atIndex + (formattedStringLength * 2UL) + 256UL) > encodeState->stringBuffer.bytes.length)); - if(CDVJK_EXPECT_F(((encodeState->atIndex + (formattedStringLength * 2UL) + 256UL) > encodeState->stringBuffer.bytes.length)) && CDVJK_EXPECT_F((cdvjk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + (formattedStringLength * 2UL)+ 4096UL) == NULL))) { cdvjk_encode_error(encodeState, @"Unable to resize temporary buffer."); returnValue = 1; goto exitNow; } - if(CDVJK_EXPECT_F((formattedStringLength = vsnprintf((char *)&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex], (encodeState->stringBuffer.bytes.length - encodeState->atIndex), format, varArgsListCopy)) >= (ssize_t)(encodeState->stringBuffer.bytes.length - encodeState->atIndex))) { cdvjk_encode_error(encodeState, @"vsnprintf failed unexpectedly."); returnValue = 1; goto exitNow; } - } - -exitNow: - va_end(varArgsList); - va_end(varArgsListCopy); - if(CDVJK_EXPECT_T(returnValue == 0)) { encodeState->atIndex += formattedStringLength; cdvjk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, object); } - return(returnValue); -} - -static int cdvjk_encode_write(CDVJKEncodeState *encodeState, CDVJKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format) { - NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (startingAtIndex <= encodeState->atIndex) && (format != NULL)); - if(CDVJK_EXPECT_F(((encodeState->atIndex + strlen(format) + 256UL) > encodeState->stringBuffer.bytes.length)) && CDVJK_EXPECT_F((cdvjk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + strlen(format) + 1024UL) == NULL))) { cdvjk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } - - size_t formatIdx = 0UL; - for(formatIdx = 0UL; format[formatIdx] != 0; formatIdx++) { NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length); encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = format[formatIdx]; } - cdvjk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, object); - return(0); -} - -static int cdvjk_encode_writePrettyPrintWhiteSpace(CDVJKEncodeState *encodeState) { - NSCParameterAssert((encodeState != NULL) && ((encodeState->serializeOptionFlags & CDVJKSerializeOptionPretty) != 0UL)); - if(CDVJK_EXPECT_F((encodeState->atIndex + ((encodeState->depth + 1UL) * 2UL) + 16UL) > encodeState->stringBuffer.bytes.length) && CDVJK_EXPECT_T(cdvjk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + ((encodeState->depth + 1UL) * 2UL) + 4096UL) == NULL)) { cdvjk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } - encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\n'; - size_t depthWhiteSpace = 0UL; - for(depthWhiteSpace = 0UL; depthWhiteSpace < (encodeState->depth * 2UL); depthWhiteSpace++) { NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length); encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = ' '; } - return(0); -} - -static int cdvjk_encode_write1slow(CDVJKEncodeState *encodeState, ssize_t depthChange, const char *format) { - NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (format != NULL) && ((depthChange >= -1L) && (depthChange <= 1L)) && ((encodeState->depth == 0UL) ? (depthChange >= 0L) : 1) && ((encodeState->serializeOptionFlags & CDVJKSerializeOptionPretty) != 0UL)); - if(CDVJK_EXPECT_F((encodeState->atIndex + ((encodeState->depth + 1UL) * 2UL) + 16UL) > encodeState->stringBuffer.bytes.length) && CDVJK_EXPECT_F(cdvjk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + ((encodeState->depth + 1UL) * 2UL) + 4096UL) == NULL)) { cdvjk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } - encodeState->depth += depthChange; - if(CDVJK_EXPECT_T(format[0] == ':')) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = format[0]; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = ' '; } - else { - if(CDVJK_EXPECT_F(depthChange == -1L)) { if(CDVJK_EXPECT_F(cdvjk_encode_writePrettyPrintWhiteSpace(encodeState))) { return(1); } } - encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = format[0]; - if(CDVJK_EXPECT_T(depthChange != -1L)) { if(CDVJK_EXPECT_F(cdvjk_encode_writePrettyPrintWhiteSpace(encodeState))) { return(1); } } - } - NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length); - return(0); -} - -static int cdvjk_encode_write1fast(CDVJKEncodeState *encodeState, ssize_t depthChange CDVJK_UNUSED_ARG, const char *format) { - NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && ((encodeState->serializeOptionFlags & CDVJKSerializeOptionPretty) == 0UL)); - if(CDVJK_EXPECT_T((encodeState->atIndex + 4UL) < encodeState->stringBuffer.bytes.length)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = format[0]; } - else { return(cdvjk_encode_write(encodeState, NULL, 0UL, NULL, format)); } - return(0); -} - -static int cdvjk_encode_writen(CDVJKEncodeState *encodeState, CDVJKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format, size_t length) { - NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (startingAtIndex <= encodeState->atIndex)); - if(CDVJK_EXPECT_F((encodeState->stringBuffer.bytes.length - encodeState->atIndex) < (length + 4UL))) { if(cdvjk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + 4096UL + length) == NULL) { cdvjk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } } - memcpy(encodeState->stringBuffer.bytes.ptr + encodeState->atIndex, format, length); - encodeState->atIndex += length; - cdvjk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, object); - return(0); -} - -CDVJK_STATIC_INLINE CDVJKHash cdvjk_encode_object_hash(void *objectPtr) { - return( ( (((CDVJKHash)objectPtr) >> 21) ^ (((CDVJKHash)objectPtr) >> 9) ) + (((CDVJKHash)objectPtr) >> 4) ); -} - -static int cdvjk_encode_add_atom_to_buffer(CDVJKEncodeState *encodeState, void *objectPtr) { - NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (objectPtr != NULL)); - - id object = (id)objectPtr, encodeCacheObject = object; - int isClass = CDVJKClassUnknown; - size_t startingAtIndex = encodeState->atIndex; - - CDVJKHash objectHash = cdvjk_encode_object_hash(objectPtr); - CDVJKEncodeCache *cacheSlot = &encodeState->cache[objectHash % CDVJK_ENCODE_CACHE_SLOTS]; - - if(CDVJK_EXPECT_T(cacheSlot->object == object)) { - NSCParameterAssert((cacheSlot->object != NULL) && - (cacheSlot->offset < encodeState->atIndex) && ((cacheSlot->offset + cacheSlot->length) < encodeState->atIndex) && - (cacheSlot->offset < encodeState->stringBuffer.bytes.length) && ((cacheSlot->offset + cacheSlot->length) < encodeState->stringBuffer.bytes.length) && - ((encodeState->stringBuffer.bytes.ptr + encodeState->atIndex) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) && - ((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) && - ((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset + cacheSlot->length) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length))); - if(CDVJK_EXPECT_F(((encodeState->atIndex + cacheSlot->length + 256UL) > encodeState->stringBuffer.bytes.length)) && CDVJK_EXPECT_F((cdvjk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + cacheSlot->length + 1024UL) == NULL))) { cdvjk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } - NSCParameterAssert(((encodeState->atIndex + cacheSlot->length) < encodeState->stringBuffer.bytes.length) && - ((encodeState->stringBuffer.bytes.ptr + encodeState->atIndex) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) && - ((encodeState->stringBuffer.bytes.ptr + encodeState->atIndex + cacheSlot->length) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) && - ((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) && - ((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset + cacheSlot->length) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) && - ((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset + cacheSlot->length) < (encodeState->stringBuffer.bytes.ptr + encodeState->atIndex))); - memcpy(encodeState->stringBuffer.bytes.ptr + encodeState->atIndex, encodeState->stringBuffer.bytes.ptr + cacheSlot->offset, cacheSlot->length); - encodeState->atIndex += cacheSlot->length; - return(0); - } - - // When we encounter a class that we do not handle, and we have either a delegate or block that the user supplied to format unsupported classes, - // we "re-run" the object check. However, we re-run the object check exactly ONCE. If the user supplies an object that isn't one of the - // supported classes, we fail the second time (i.e., double fault error). - BOOL rerunningAfterClassFormatter = NO; - rerunAfterClassFormatter:; - - // XXX XXX XXX XXX - // - // We need to work around a bug in 10.7, which breaks ABI compatibility with Objective-C going back not just to 10.0, but OpenStep and even NextStep. - // - // It has long been documented that "the very first thing that a pointer to an Objective-C object "points to" is a pointer to that objects class". - // - // This is euphemistically called "tagged pointers". There are a number of highly technical problems with this, most involving long passages from - // the C standard(s). In short, one can make a strong case, couched from the perspective of the C standard(s), that that 10.7 "tagged pointers" are - // fundamentally Wrong and Broken, and should have never been implemented. Assuming those points are glossed over, because the change is very clearly - // breaking ABI compatibility, this should have resulted in a minimum of a "minimum version required" bump in various shared libraries to prevent - // causes code that used to work just fine to suddenly break without warning. - // - // In fact, the C standard says that the hack below is "undefined behavior"- there is no requirement that the 10.7 tagged pointer hack of setting the - // "lower, unused bits" must be preserved when casting the result to an integer type, but this "works" because for most architectures - // `sizeof(long) == sizeof(void *)` and the compiler uses the same representation for both. (note: this is informal, not meant to be - // normative or pedantically correct). - // - // In other words, while this "works" for now, technically the compiler is not obligated to do "what we want", and a later version of the compiler - // is not required in any way to produce the same results or behavior that earlier versions of the compiler did for the statement below. - // - // Fan-fucking-tastic. - // - // Why not just use `object_getClass()`? Because `object->isa` reduces to (typically) a *single* instruction. Calling `object_getClass()` requires - // that the compiler potentially spill registers, establish a function call frame / environment, and finally execute a "jump subroutine" instruction. - // Then, the called subroutine must spend half a dozen instructions in its prolog, however many instructions doing whatever it does, then half a dozen - // instructions in its prolog. One instruction compared to dozens, maybe a hundred instructions. - // - // Yes, that's one to two orders of magnitude difference. Which is compelling in its own right. When going for performance, you're often happy with - // gains in the two to three percent range. - // - // XXX XXX XXX XXX - - BOOL workAroundMacOSXABIBreakingBug = NO; - if(CDVJK_EXPECT_F(((NSUInteger)object) & 0x1)) { workAroundMacOSXABIBreakingBug = YES; goto slowClassLookup; } - - if(CDVJK_EXPECT_T(object_getClass(object) == encodeState->fastClassLookup.stringClass)) { isClass = CDVJKClassString; } - else if(CDVJK_EXPECT_T(object_getClass(object) == encodeState->fastClassLookup.numberClass)) { isClass = CDVJKClassNumber; } - else if(CDVJK_EXPECT_T(object_getClass(object) == encodeState->fastClassLookup.dictionaryClass)) { isClass = CDVJKClassDictionary; } - else if(CDVJK_EXPECT_T(object_getClass(object) == encodeState->fastClassLookup.arrayClass)) { isClass = CDVJKClassArray; } - else if(CDVJK_EXPECT_T(object_getClass(object) == encodeState->fastClassLookup.nullClass)) { isClass = CDVJKClassNull; } - else { - slowClassLookup: - if(CDVJK_EXPECT_T([object isKindOfClass:[NSString class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.stringClass = object_getClass(object); } isClass = CDVJKClassString; } - else if(CDVJK_EXPECT_T([object isKindOfClass:[NSNumber class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.numberClass = object_getClass(object); } isClass = CDVJKClassNumber; } - else if(CDVJK_EXPECT_T([object isKindOfClass:[NSDictionary class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.dictionaryClass = object_getClass(object); } isClass = CDVJKClassDictionary; } - else if(CDVJK_EXPECT_T([object isKindOfClass:[NSArray class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.arrayClass = object_getClass(object); } isClass = CDVJKClassArray; } - else if(CDVJK_EXPECT_T([object isKindOfClass:[NSNull class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.nullClass = object_getClass(object); } isClass = CDVJKClassNull; } - else { - if((rerunningAfterClassFormatter == NO) && ( -#ifdef __BLOCKS__ - ((encodeState->classFormatterBlock) && ((object = encodeState->classFormatterBlock(object)) != NULL)) || -#endif - ((encodeState->classFormatterIMP) && ((object = encodeState->classFormatterIMP(encodeState->classFormatterDelegate, encodeState->classFormatterSelector, object)) != NULL)) )) { rerunningAfterClassFormatter = YES; goto rerunAfterClassFormatter; } - - if(rerunningAfterClassFormatter == NO) { cdvjk_encode_error(encodeState, @"Unable to serialize object class %@.", NSStringFromClass([encodeCacheObject class])); return(1); } - else { cdvjk_encode_error(encodeState, @"Unable to serialize object class %@ that was returned by the unsupported class formatter. Original object class was %@.", (object == NULL) ? @"NULL" : NSStringFromClass([object class]), NSStringFromClass([encodeCacheObject class])); return(1); } - } - } - - // This is here for the benefit of the optimizer. It allows the optimizer to do loop invariant code motion for the CDVJKClassArray - // and CDVJKClassDictionary cases when printing simple, single characters via cdvjk_encode_write(), which is actually a macro: - // #define cdvjk_encode_write1(es, dc, f) (_jk_encode_prettyPrint ? cdvjk_encode_write1slow(es, dc, f) : cdvjk_encode_write1fast(es, dc, f)) - int _jk_encode_prettyPrint = CDVJK_EXPECT_T((encodeState->serializeOptionFlags & CDVJKSerializeOptionPretty) == 0) ? 0 : 1; - - switch(isClass) { - case CDVJKClassString: - { - { - const unsigned char *cStringPtr = (const unsigned char *)CFStringGetCStringPtr((CFStringRef)object, kCFStringEncodingMacRoman); - if(cStringPtr != NULL) { - const unsigned char *utf8String = cStringPtr; - size_t utf8Idx = 0UL; - - CFIndex stringLength = CFStringGetLength((CFStringRef)object); - if(CDVJK_EXPECT_F(((encodeState->atIndex + (stringLength * 2UL) + 256UL) > encodeState->stringBuffer.bytes.length)) && CDVJK_EXPECT_F((cdvjk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + (stringLength * 2UL) + 1024UL) == NULL))) { cdvjk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } - - if(CDVJK_EXPECT_T((encodeState->encodeOption & CDVJKEncodeOptionStringObjTrimQuotes) == 0UL)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\"'; } - for(utf8Idx = 0UL; utf8String[utf8Idx] != 0U; utf8Idx++) { - NSCParameterAssert(((&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex]) - encodeState->stringBuffer.bytes.ptr) < (ssize_t)encodeState->stringBuffer.bytes.length); - NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length); - if(CDVJK_EXPECT_F(utf8String[utf8Idx] >= 0x80U)) { encodeState->atIndex = startingAtIndex; goto slowUTF8Path; } - if(CDVJK_EXPECT_F(utf8String[utf8Idx] < 0x20U)) { - switch(utf8String[utf8Idx]) { - case '\b': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'b'; break; - case '\f': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'f'; break; - case '\n': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'n'; break; - case '\r': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'r'; break; - case '\t': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 't'; break; - default: if(CDVJK_EXPECT_F(cdvjk_encode_printf(encodeState, NULL, 0UL, NULL, "\\u%4.4x", utf8String[utf8Idx]))) { return(1); } break; - } - } else { - if(CDVJK_EXPECT_F(utf8String[utf8Idx] == '\"') || CDVJK_EXPECT_F(utf8String[utf8Idx] == '\\') || (CDVJK_EXPECT_F(encodeState->serializeOptionFlags & CDVJKSerializeOptionEscapeForwardSlashes) && CDVJK_EXPECT_F(utf8String[utf8Idx] == '/'))) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; } - encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = utf8String[utf8Idx]; - } - } - NSCParameterAssert((encodeState->atIndex + 1UL) < encodeState->stringBuffer.bytes.length); - if(CDVJK_EXPECT_T((encodeState->encodeOption & CDVJKEncodeOptionStringObjTrimQuotes) == 0UL)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\"'; } - cdvjk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, encodeCacheObject); - return(0); - } - } - - slowUTF8Path: - { - CFIndex stringLength = CFStringGetLength((CFStringRef)object); - CFIndex maxStringUTF8Length = CFStringGetMaximumSizeForEncoding(stringLength, kCFStringEncodingUTF8) + 32L; - - if(CDVJK_EXPECT_F((size_t)maxStringUTF8Length > encodeState->utf8ConversionBuffer.bytes.length) && CDVJK_EXPECT_F(cdvjk_managedBuffer_resize(&encodeState->utf8ConversionBuffer, maxStringUTF8Length + 1024UL) == NULL)) { cdvjk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } - - CFIndex usedBytes = 0L, convertedCount = 0L; - convertedCount = CFStringGetBytes((CFStringRef)object, CFRangeMake(0L, stringLength), kCFStringEncodingUTF8, '?', NO, encodeState->utf8ConversionBuffer.bytes.ptr, encodeState->utf8ConversionBuffer.bytes.length - 16L, &usedBytes); - if(CDVJK_EXPECT_F(convertedCount != stringLength) || CDVJK_EXPECT_F(usedBytes < 0L)) { cdvjk_encode_error(encodeState, @"An error occurred converting the contents of a NSString to UTF8."); return(1); } - - if(CDVJK_EXPECT_F((encodeState->atIndex + (maxStringUTF8Length * 2UL) + 256UL) > encodeState->stringBuffer.bytes.length) && CDVJK_EXPECT_F(cdvjk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + (maxStringUTF8Length * 2UL) + 1024UL) == NULL)) { cdvjk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } - - const unsigned char *utf8String = encodeState->utf8ConversionBuffer.bytes.ptr; - - size_t utf8Idx = 0UL; - if(CDVJK_EXPECT_T((encodeState->encodeOption & CDVJKEncodeOptionStringObjTrimQuotes) == 0UL)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\"'; } - for(utf8Idx = 0UL; utf8Idx < (size_t)usedBytes; utf8Idx++) { - NSCParameterAssert(((&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex]) - encodeState->stringBuffer.bytes.ptr) < (ssize_t)encodeState->stringBuffer.bytes.length); - NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length); - NSCParameterAssert((CFIndex)utf8Idx < usedBytes); - if(CDVJK_EXPECT_F(utf8String[utf8Idx] < 0x20U)) { - switch(utf8String[utf8Idx]) { - case '\b': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'b'; break; - case '\f': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'f'; break; - case '\n': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'n'; break; - case '\r': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'r'; break; - case '\t': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 't'; break; - default: if(CDVJK_EXPECT_F(cdvjk_encode_printf(encodeState, NULL, 0UL, NULL, "\\u%4.4x", utf8String[utf8Idx]))) { return(1); } break; - } - } else { - if(CDVJK_EXPECT_F(utf8String[utf8Idx] >= 0x80U) && (encodeState->serializeOptionFlags & CDVJKSerializeOptionEscapeUnicode)) { - const unsigned char *nextValidCharacter = NULL; - UTF32 u32ch = 0U; - CDV_ConversionResult result; - - if(CDVJK_EXPECT_F((result = cdvConvertSingleCodePointInUTF8(&utf8String[utf8Idx], &utf8String[usedBytes], (UTF8 const **)&nextValidCharacter, &u32ch)) != conversionOK)) { cdvjk_encode_error(encodeState, @"Error converting UTF8."); return(1); } - else { - utf8Idx = (nextValidCharacter - utf8String) - 1UL; - if(CDVJK_EXPECT_T(u32ch <= 0xffffU)) { if(CDVJK_EXPECT_F(cdvjk_encode_printf(encodeState, NULL, 0UL, NULL, "\\u%4.4x", u32ch))) { return(1); } } - else { if(CDVJK_EXPECT_F(cdvjk_encode_printf(encodeState, NULL, 0UL, NULL, "\\u%4.4x\\u%4.4x", (0xd7c0U + (u32ch >> 10)), (0xdc00U + (u32ch & 0x3ffU))))) { return(1); } } - } - } else { - if(CDVJK_EXPECT_F(utf8String[utf8Idx] == '\"') || CDVJK_EXPECT_F(utf8String[utf8Idx] == '\\') || (CDVJK_EXPECT_F(encodeState->serializeOptionFlags & CDVJKSerializeOptionEscapeForwardSlashes) && CDVJK_EXPECT_F(utf8String[utf8Idx] == '/'))) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; } - encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = utf8String[utf8Idx]; - } - } - } - NSCParameterAssert((encodeState->atIndex + 1UL) < encodeState->stringBuffer.bytes.length); - if(CDVJK_EXPECT_T((encodeState->encodeOption & CDVJKEncodeOptionStringObjTrimQuotes) == 0UL)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\"'; } - cdvjk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, encodeCacheObject); - return(0); - } - } - break; - - case CDVJKClassNumber: - { - if(object == (id)kCFBooleanTrue) { return(cdvjk_encode_writen(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, "true", 4UL)); } - else if(object == (id)kCFBooleanFalse) { return(cdvjk_encode_writen(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, "false", 5UL)); } - - const char *objCType = [object objCType]; - char anum[256], *aptr = &anum[255]; - int isNegative = 0; - unsigned long long ullv; - long long llv; - - if(CDVJK_EXPECT_F(objCType == NULL) || CDVJK_EXPECT_F(objCType[0] == 0) || CDVJK_EXPECT_F(objCType[1] != 0)) { cdvjk_encode_error(encodeState, @"NSNumber conversion error, unknown type. Type: '%s'", (objCType == NULL) ? "<NULL>" : objCType); return(1); } - - switch(objCType[0]) { - case 'c': case 'i': case 's': case 'l': case 'q': - if(CDVJK_EXPECT_T(CFNumberGetValue((CFNumberRef)object, kCFNumberLongLongType, &llv))) { - if(llv < 0LL) { ullv = -llv; isNegative = 1; } else { ullv = llv; isNegative = 0; } - goto convertNumber; - } else { cdvjk_encode_error(encodeState, @"Unable to get scalar value from number object."); return(1); } - break; - case 'C': case 'I': case 'S': case 'L': case 'Q': case 'B': - if(CDVJK_EXPECT_T(CFNumberGetValue((CFNumberRef)object, kCFNumberLongLongType, &ullv))) { - convertNumber: - if(CDVJK_EXPECT_F(ullv < 10ULL)) { *--aptr = ullv + '0'; } else { while(CDVJK_EXPECT_T(ullv > 0ULL)) { *--aptr = (ullv % 10ULL) + '0'; ullv /= 10ULL; NSCParameterAssert(aptr > anum); } } - if(isNegative) { *--aptr = '-'; } - NSCParameterAssert(aptr > anum); - return(cdvjk_encode_writen(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, aptr, &anum[255] - aptr)); - } else { cdvjk_encode_error(encodeState, @"Unable to get scalar value from number object."); return(1); } - break; - case 'f': case 'd': - { - double dv; - if(CDVJK_EXPECT_T(CFNumberGetValue((CFNumberRef)object, kCFNumberDoubleType, &dv))) { - if(CDVJK_EXPECT_F(!isfinite(dv))) { cdvjk_encode_error(encodeState, @"Floating point values must be finite. CDVJSON does not support NaN or Infinity."); return(1); } - return(cdvjk_encode_printf(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, "%.17g", dv)); - } else { cdvjk_encode_error(encodeState, @"Unable to get floating point value from number object."); return(1); } - } - break; - default: cdvjk_encode_error(encodeState, @"NSNumber conversion error, unknown type. Type: '%c' / 0x%2.2x", objCType[0], objCType[0]); return(1); break; - } - } - break; - - case CDVJKClassArray: - { - int printComma = 0; - CFIndex arrayCount = CFArrayGetCount((CFArrayRef)object), idx = 0L; - if(CDVJK_EXPECT_F(cdvjk_encode_write1(encodeState, 1L, "["))) { return(1); } - if(CDVJK_EXPECT_F(arrayCount > 1020L)) { - for(id arrayObject in object) { if(CDVJK_EXPECT_T(printComma)) { if(CDVJK_EXPECT_F(cdvjk_encode_write1(encodeState, 0L, ","))) { return(1); } } printComma = 1; if(CDVJK_EXPECT_F(cdvjk_encode_add_atom_to_buffer(encodeState, arrayObject))) { return(1); } } - } else { - void *objects[1024]; - CFArrayGetValues((CFArrayRef)object, CFRangeMake(0L, arrayCount), (const void **)objects); - for(idx = 0L; idx < arrayCount; idx++) { if(CDVJK_EXPECT_T(printComma)) { if(CDVJK_EXPECT_F(cdvjk_encode_write1(encodeState, 0L, ","))) { return(1); } } printComma = 1; if(CDVJK_EXPECT_F(cdvjk_encode_add_atom_to_buffer(encodeState, objects[idx]))) { return(1); } } - } - return(cdvjk_encode_write1(encodeState, -1L, "]")); - } - break; - - case CDVJKClassDictionary: - { - int printComma = 0; - CFIndex dictionaryCount = CFDictionaryGetCount((CFDictionaryRef)object), idx = 0L; - id enumerateObject = CDVJK_EXPECT_F(_jk_encode_prettyPrint) ? [[object allKeys] sortedArrayUsingSelector:@selector(compare:)] : object; - - if(CDVJK_EXPECT_F(cdvjk_encode_write1(encodeState, 1L, "{"))) { return(1); } - if(CDVJK_EXPECT_F(_jk_encode_prettyPrint) || CDVJK_EXPECT_F(dictionaryCount > 1020L)) { - for(id keyObject in enumerateObject) { - if(CDVJK_EXPECT_T(printComma)) { if(CDVJK_EXPECT_F(cdvjk_encode_write1(encodeState, 0L, ","))) { return(1); } } - printComma = 1; - if(CDVJK_EXPECT_F((object_getClass(keyObject) != encodeState->fastClassLookup.stringClass)) && CDVJK_EXPECT_F(([keyObject isKindOfClass:[NSString class]] == NO))) { cdvjk_encode_error(encodeState, @"Key must be a string object."); return(1); } - if(CDVJK_EXPECT_F(cdvjk_encode_add_atom_to_buffer(encodeState, keyObject))) { return(1); } - if(CDVJK_EXPECT_F(cdvjk_encode_write1(encodeState, 0L, ":"))) { return(1); } - if(CDVJK_EXPECT_F(cdvjk_encode_add_atom_to_buffer(encodeState, (void *)CFDictionaryGetValue((CFDictionaryRef)object, keyObject)))) { return(1); } - } - } else { - void *keys[1024], *objects[1024]; - CFDictionaryGetKeysAndValues((CFDictionaryRef)object, (const void **)keys, (const void **)objects); - for(idx = 0L; idx < dictionaryCount; idx++) { - if(CDVJK_EXPECT_T(printComma)) { if(CDVJK_EXPECT_F(cdvjk_encode_write1(encodeState, 0L, ","))) { return(1); } } - printComma = 1; - if(CDVJK_EXPECT_F(object_getClass(((id)keys[idx])) != encodeState->fastClassLookup.stringClass) && CDVJK_EXPECT_F([(id)keys[idx] isKindOfClass:[NSString class]] == NO)) { cdvjk_encode_error(encodeState, @"Key must be a string object."); return(1); } - if(CDVJK_EXPECT_F(cdvjk_encode_add_atom_to_buffer(encodeState, keys[idx]))) { return(1); } - if(CDVJK_EXPECT_F(cdvjk_encode_write1(encodeState, 0L, ":"))) { return(1); } - if(CDVJK_EXPECT_F(cdvjk_encode_add_atom_to_buffer(encodeState, objects[idx]))) { return(1); } - } - } - return(cdvjk_encode_write1(encodeState, -1L, "}")); - } - break; - - case CDVJKClassNull: return(cdvjk_encode_writen(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, "null", 4UL)); break; - - default: cdvjk_encode_error(encodeState, @"Unable to serialize object class %@.", NSStringFromClass([object class])); return(1); break; - } - - return(0); -} - - -@implementation CDVJKSerializer - -+ (id)serializeObject:(id)object options:(CDVJKSerializeOptionFlags)optionFlags encodeOption:(CDVJKEncodeOptionType)encodeOption block:(CDVJKSERIALIZER_BLOCKS_PROTO)block delegate:(id)delegate selector:(SEL)selector error:(NSError **)error -{ - return([[[[self alloc] init] autorelease] serializeObject:object options:optionFlags encodeOption:encodeOption block:block delegate:delegate selector:selector error:error]); -} - -- (id)serializeObject:(id)object options:(CDVJKSerializeOptionFlags)optionFlags encodeOption:(CDVJKEncodeOptionType)encodeOption block:(CDVJKSERIALIZER_BLOCKS_PROTO)block delegate:(id)delegate selector:(SEL)selector error:(NSError **)error -{ -#ifndef __BLOCKS__ -#pragma unused(block) -#endif - NSParameterAssert((object != NULL) && (encodeState == NULL) && ((delegate != NULL) ? (block == NULL) : 1) && ((block != NULL) ? (delegate == NULL) : 1) && - (((encodeOption & CDVJKEncodeOptionCollectionObj) != 0UL) ? (((encodeOption & CDVJKEncodeOptionStringObj) == 0UL) && ((encodeOption & CDVJKEncodeOptionStringObjTrimQuotes) == 0UL)) : 1) && - (((encodeOption & CDVJKEncodeOptionStringObj) != 0UL) ? ((encodeOption & CDVJKEncodeOptionCollectionObj) == 0UL) : 1)); - - id returnObject = NULL; - - if(encodeState != NULL) { [self releaseState]; } - if((encodeState = (struct CDVJKEncodeState *)calloc(1UL, sizeof(CDVJKEncodeState))) == NULL) { [NSException raise:NSMallocException format:@"Unable to allocate state structure."]; return(NULL); } - - if((error != NULL) && (*error != NULL)) { *error = NULL; } - - if(delegate != NULL) { - if(selector == NULL) { [NSException raise:NSInvalidArgumentException format:@"The delegate argument is not NULL, but the selector argument is NULL."]; } - if([delegate respondsToSelector:selector] == NO) { [NSException raise:NSInvalidArgumentException format:@"The serializeUnsupportedClassesUsingDelegate: delegate does not respond to the selector argument."]; } - encodeState->classFormatterDelegate = delegate; - encodeState->classFormatterSelector = selector; - encodeState->classFormatterIMP = (CDVJKClassFormatterIMP)[delegate methodForSelector:selector]; - NSCParameterAssert(encodeState->classFormatterIMP != NULL); - } - -#ifdef __BLOCKS__ - encodeState->classFormatterBlock = block; -#endif - encodeState->serializeOptionFlags = optionFlags; - encodeState->encodeOption = encodeOption; - encodeState->stringBuffer.roundSizeUpToMultipleOf = (1024UL * 32UL); - encodeState->utf8ConversionBuffer.roundSizeUpToMultipleOf = 4096UL; - - unsigned char stackJSONBuffer[CDVJK_JSONBUFFER_SIZE] CDVJK_ALIGNED(64); - cdvjk_managedBuffer_setToStackBuffer(&encodeState->stringBuffer, stackJSONBuffer, sizeof(stackJSONBuffer)); - - unsigned char stackUTF8Buffer[CDVJK_UTF8BUFFER_SIZE] CDVJK_ALIGNED(64); - cdvjk_managedBuffer_setToStackBuffer(&encodeState->utf8ConversionBuffer, stackUTF8Buffer, sizeof(stackUTF8Buffer)); - - if(((encodeOption & CDVJKEncodeOptionCollectionObj) != 0UL) && (([object isKindOfClass:[NSArray class]] == NO) && ([object isKindOfClass:[NSDictionary class]] == NO))) { cdvjk_encode_error(encodeState, @"Unable to serialize object class %@, expected a NSArray or NSDictionary.", NSStringFromClass([object class])); goto errorExit; } - if(((encodeOption & CDVJKEncodeOptionStringObj) != 0UL) && ([object isKindOfClass:[NSString class]] == NO)) { cdvjk_encode_error(encodeState, @"Unable to serialize object class %@, expected a NSString.", NSStringFromClass([object class])); goto errorExit; } - - if(cdvjk_encode_add_atom_to_buffer(encodeState, object) == 0) { - BOOL stackBuffer = ((encodeState->stringBuffer.flags & CDVJKManagedBufferMustFree) == 0UL) ? YES : NO; - - if((encodeState->atIndex < 2UL)) - if((stackBuffer == NO) && ((encodeState->stringBuffer.bytes.ptr = (unsigned char *)reallocf(encodeState->stringBuffer.bytes.ptr, encodeState->atIndex + 16UL)) == NULL)) { cdvjk_encode_error(encodeState, @"Unable to realloc buffer"); goto errorExit; } - - switch((encodeOption & CDVJKEncodeOptionAsTypeMask)) { - case CDVJKEncodeOptionAsData: - if(stackBuffer == YES) { if((returnObject = [(id)CFDataCreate( NULL, encodeState->stringBuffer.bytes.ptr, (CFIndex)encodeState->atIndex) autorelease]) == NULL) { cdvjk_encode_error(encodeState, @"Unable to create NSData object"); } } - else { if((returnObject = [(id)CFDataCreateWithBytesNoCopy( NULL, encodeState->stringBuffer.bytes.ptr, (CFIndex)encodeState->atIndex, NULL) autorelease]) == NULL) { cdvjk_encode_error(encodeState, @"Unable to create NSData object"); } } - break; - - case CDVJKEncodeOptionAsString: - if(stackBuffer == YES) { if((returnObject = [(id)CFStringCreateWithBytes( NULL, (const UInt8 *)encodeState->stringBuffer.bytes.ptr, (CFIndex)encodeState->atIndex, kCFStringEncodingUTF8, NO) autorelease]) == NULL) { cdvjk_encode_error(encodeState, @"Unable to create NSString object"); } } - else { if((returnObject = [(id)CFStringCreateWithBytesNoCopy(NULL, (const UInt8 *)encodeState->stringBuffer.bytes.ptr, (CFIndex)encodeState->atIndex, kCFStringEncodingUTF8, NO, NULL) autorelease]) == NULL) { cdvjk_encode_error(encodeState, @"Unable to create NSString object"); } } - break; - - default: cdvjk_encode_error(encodeState, @"Unknown encode as type."); break; - } - - if((returnObject != NULL) && (stackBuffer == NO)) { encodeState->stringBuffer.flags &= ~CDVJKManagedBufferMustFree; encodeState->stringBuffer.bytes.ptr = NULL; encodeState->stringBuffer.bytes.length = 0UL; } - } - -errorExit: - if((encodeState != NULL) && (error != NULL) && (encodeState->error != NULL)) { *error = encodeState->error; encodeState->error = NULL; } - [self releaseState]; - - return(returnObject); -} - -- (void)releaseState -{ - if(encodeState != NULL) { - cdvjk_managedBuffer_release(&encodeState->stringBuffer); - cdvjk_managedBuffer_release(&encodeState->utf8ConversionBuffer); - free(encodeState); encodeState = NULL; - } -} - -- (void)dealloc -{ - [self releaseState]; - [super dealloc]; -} - -@end - -@implementation NSString (CDVJSONKitSerializing) - -//////////// -#pragma mark Methods for serializing a single NSString. -//////////// - -// Useful for those who need to serialize just a NSString. Otherwise you would have to do something like [NSArray arrayWithObject:stringToBeJSONSerialized], serializing the array, and then chopping of the extra ^\[.*\]$ square brackets. - -// NSData returning methods... - -- (NSData *)cdvjk_JSONData -{ - return([self cdvjk_JSONDataWithOptions:CDVJKSerializeOptionNone includeQuotes:YES error:NULL]); -} - -- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error -{ - return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsData | ((includeQuotes == NO) ? CDVJKEncodeOptionStringObjTrimQuotes : 0UL) | CDVJKEncodeOptionStringObj) block:NULL delegate:NULL selector:NULL error:error]); -} - -// NSString returning methods... - -- (NSString *)cdvjk_JSONString -{ - return([self cdvjk_JSONStringWithOptions:CDVJKSerializeOptionNone includeQuotes:YES error:NULL]); -} - -- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error -{ - return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsString | ((includeQuotes == NO) ? CDVJKEncodeOptionStringObjTrimQuotes : 0UL) | CDVJKEncodeOptionStringObj) block:NULL delegate:NULL selector:NULL error:error]); -} - -@end - -@implementation NSArray (CDVJSONKitSerializing) - -// NSData returning methods... - -- (NSData *)cdvjk_JSONData -{ - return([CDVJKSerializer serializeObject:self options:CDVJKSerializeOptionNone encodeOption:(CDVJKEncodeOptionAsData | CDVJKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:NULL]); -} - -- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions error:(NSError **)error -{ - return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsData | CDVJKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:error]); -} - -- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error -{ - return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsData | CDVJKEncodeOptionCollectionObj) block:NULL delegate:delegate selector:selector error:error]); -} - -// NSString returning methods... - -- (NSString *)cdvjk_JSONString -{ - return([CDVJKSerializer serializeObject:self options:CDVJKSerializeOptionNone encodeOption:(CDVJKEncodeOptionAsString | CDVJKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:NULL]); -} - -- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions error:(NSError **)error -{ - return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsString | CDVJKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:error]); -} - -- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error -{ - return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsString | CDVJKEncodeOptionCollectionObj) block:NULL delegate:delegate selector:selector error:error]); -} - -@end - -@implementation NSDictionary (CDVJSONKitSerializing) - -// NSData returning methods... - -- (NSData *)cdvjk_JSONData -{ - return([CDVJKSerializer serializeObject:self options:CDVJKSerializeOptionNone encodeOption:(CDVJKEncodeOptionAsData | CDVJKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:NULL]); -} - -- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions error:(NSError **)error -{ - return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsData | CDVJKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:error]); -} - -- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error -{ - return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsData | CDVJKEncodeOptionCollectionObj) block:NULL delegate:delegate selector:selector error:error]); -} - -// NSString returning methods... - -- (NSString *)cdvjk_JSONString -{ - return([CDVJKSerializer serializeObject:self options:CDVJKSerializeOptionNone encodeOption:(CDVJKEncodeOptionAsString | CDVJKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:NULL]); -} - -- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions error:(NSError **)error -{ - return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsString | CDVJKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:error]); -} - -- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error -{ - return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsString | CDVJKEncodeOptionCollectionObj) block:NULL delegate:delegate selector:selector error:error]); -} - -@end - - -#ifdef __BLOCKS__ - -@implementation NSArray (CDVJSONKitSerializingBlockAdditions) - -- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error -{ - return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsData | CDVJKEncodeOptionCollectionObj) block:block delegate:NULL selector:NULL error:error]); -} - -- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error -{ - return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsString | CDVJKEncodeOptionCollectionObj) block:block delegate:NULL selector:NULL error:error]); -} - -@end - -@implementation NSDictionary (CDVJSONKitSerializingBlockAdditions) - -- (NSData *)cdvjk_JSONDataWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error -{ - return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsData | CDVJKEncodeOptionCollectionObj) block:block delegate:NULL selector:NULL error:error]); -} - -- (NSString *)cdvjk_JSONStringWithOptions:(CDVJKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error -{ - return([CDVJKSerializer serializeObject:self options:serializeOptions encodeOption:(CDVJKEncodeOptionAsString | CDVJKEncodeOptionCollectionObj) block:block delegate:NULL selector:NULL error:error]); -} - -@end - -#endif // __BLOCKS__ - diff --git a/iPhone/CordovaLib/CordovaLib.xcodeproj/project.pbxproj b/iPhone/CordovaLib/CordovaLib.xcodeproj/project.pbxproj index 1bb7fa7..4868020 100755 --- a/iPhone/CordovaLib/CordovaLib.xcodeproj/project.pbxproj +++ b/iPhone/CordovaLib/CordovaLib.xcodeproj/project.pbxproj @@ -27,8 +27,6 @@ 3073E9ED1656D51200957977 /* CDVScreenOrientationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 3073E9EC1656D51200957977 /* CDVScreenOrientationDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 307A8F9E1385A2EC00E43782 /* CDVConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = 307A8F9C1385A2EC00E43782 /* CDVConnection.h */; settings = {ATTRIBUTES = (Public, ); }; }; 307A8F9F1385A2EC00E43782 /* CDVConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 307A8F9D1385A2EC00E43782 /* CDVConnection.m */; }; - 30A90B9114588697006178D3 /* JSONKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 30A90B8F14588697006178D3 /* JSONKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 30A90B9314588697006178D3 /* JSONKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 30A90B9014588697006178D3 /* JSONKit.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 30B39EBE13D0268B0009682A /* CDVSplashScreen.h in Headers */ = {isa = PBXBuildFile; fileRef = 30B39EBC13D0268B0009682A /* CDVSplashScreen.h */; settings = {ATTRIBUTES = (Public, ); }; }; 30B39EBF13D0268B0009682A /* CDVSplashScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 30B39EBD13D0268B0009682A /* CDVSplashScreen.m */; }; 30C5F1DF15AF9E950052A00D /* CDVDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = 30C5F1DD15AF9E950052A00D /* CDVDevice.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -41,13 +39,13 @@ 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 */; 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 */; }; 3E76876F156A90EE00EB6FA3 /* CDVLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 3E76876C156A90EE00EB6FA3 /* CDVLogger.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8852C43A14B65FD800F0E735 /* CDVViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 8852C43614B65FD800F0E735 /* CDVViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8852C43C14B65FD800F0E735 /* CDVViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8852C43714B65FD800F0E735 /* CDVViewController.m */; }; - 8852C43F14B65FD800F0E735 /* CDVCordovaView.h in Headers */ = {isa = PBXBuildFile; fileRef = 8852C43814B65FD800F0E735 /* CDVCordovaView.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 8852C44114B65FD800F0E735 /* CDVCordovaView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8852C43914B65FD800F0E735 /* CDVCordovaView.m */; }; 8887FD661090FBE7009987E8 /* CDVCamera.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD261090FBE7009987E8 /* CDVCamera.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8887FD671090FBE7009987E8 /* CDVCamera.m in Sources */ = {isa = PBXBuildFile; fileRef = 8887FD271090FBE7009987E8 /* CDVCamera.m */; }; 8887FD681090FBE7009987E8 /* NSDictionary+Extensions.h in Headers */ = {isa = PBXBuildFile; fileRef = 8887FD281090FBE7009987E8 /* NSDictionary+Extensions.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -82,9 +80,13 @@ 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 */; 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 */; }; + EBFF4DBC16D3FE2E008F452B /* CDVWebViewDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = EBFF4DBA16D3FE2E008F452B /* CDVWebViewDelegate.m */; }; + EBFF4DBD16D3FE2E008F452B /* CDVWebViewDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = EBFF4DBB16D3FE2E008F452B /* CDVWebViewDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 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 */ @@ -110,8 +112,6 @@ 3073E9EC1656D51200957977 /* CDVScreenOrientationDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVScreenOrientationDelegate.h; path = Classes/CDVScreenOrientationDelegate.h; sourceTree = "<group>"; }; 307A8F9C1385A2EC00E43782 /* CDVConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVConnection.h; path = Classes/CDVConnection.h; sourceTree = "<group>"; }; 307A8F9D1385A2EC00E43782 /* CDVConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVConnection.m; path = Classes/CDVConnection.m; sourceTree = "<group>"; }; - 30A90B8F14588697006178D3 /* JSONKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONKit.h; sourceTree = "<group>"; }; - 30A90B9014588697006178D3 /* JSONKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONKit.m; sourceTree = "<group>"; }; 30B39EBC13D0268B0009682A /* CDVSplashScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVSplashScreen.h; path = Classes/CDVSplashScreen.h; sourceTree = "<group>"; }; 30B39EBD13D0268B0009682A /* CDVSplashScreen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVSplashScreen.m; path = Classes/CDVSplashScreen.m; sourceTree = "<group>"; }; 30C5F1DD15AF9E950052A00D /* CDVDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVDevice.h; path = Classes/CDVDevice.h; sourceTree = "<group>"; }; @@ -124,6 +124,8 @@ 30E33AF113A7E24B00594D64 /* CDVPlugin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVPlugin.m; path = Classes/CDVPlugin.m; sourceTree = "<group>"; }; 30E563CD13E217EC00C949AA /* NSMutableArray+QueueAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSMutableArray+QueueAdditions.h"; path = "Classes/NSMutableArray+QueueAdditions.h"; sourceTree = "<group>"; }; 30E563CE13E217EC00C949AA /* NSMutableArray+QueueAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSMutableArray+QueueAdditions.m"; path = "Classes/NSMutableArray+QueueAdditions.m"; sourceTree = "<group>"; }; + 30F39309169F839700B22307 /* CDVJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVJSON.h; path = Classes/CDVJSON.h; sourceTree = "<group>"; }; + 30F3930A169F839700B22307 /* CDVJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVJSON.m; path = Classes/CDVJSON.m; sourceTree = "<group>"; }; 30F5EBA914CA26E700987760 /* CDVCommandDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVCommandDelegate.h; path = Classes/CDVCommandDelegate.h; sourceTree = "<group>"; }; 3E76876B156A90EE00EB6FA3 /* CDVLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVLogger.m; path = Classes/CDVLogger.m; sourceTree = "<group>"; }; 3E76876C156A90EE00EB6FA3 /* CDVLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVLogger.h; path = Classes/CDVLogger.h; sourceTree = "<group>"; }; @@ -141,8 +143,6 @@ 68A32D7414103017006B237C /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; 8852C43614B65FD800F0E735 /* CDVViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVViewController.h; path = Classes/CDVViewController.h; sourceTree = "<group>"; }; 8852C43714B65FD800F0E735 /* CDVViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVViewController.m; path = Classes/CDVViewController.m; sourceTree = "<group>"; }; - 8852C43814B65FD800F0E735 /* CDVCordovaView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVCordovaView.h; path = Classes/CDVCordovaView.h; sourceTree = "<group>"; }; - 8852C43914B65FD800F0E735 /* CDVCordovaView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVCordovaView.m; path = Classes/CDVCordovaView.m; sourceTree = "<group>"; }; 8887FD261090FBE7009987E8 /* CDVCamera.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVCamera.h; path = Classes/CDVCamera.h; sourceTree = "<group>"; }; 8887FD271090FBE7009987E8 /* CDVCamera.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVCamera.m; path = Classes/CDVCamera.m; sourceTree = "<group>"; }; 8887FD281090FBE7009987E8 /* NSDictionary+Extensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSDictionary+Extensions.h"; path = "Classes/NSDictionary+Extensions.h"; sourceTree = "<group>"; }; @@ -178,8 +178,12 @@ EB3B357B161F2A45003DBE7D /* CDVCommandDelegateImpl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVCommandDelegateImpl.m; path = Classes/CDVCommandDelegateImpl.m; sourceTree = "<group>"; }; EB80C2AA15DEA63D004D9E7B /* CDVEcho.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVEcho.h; path = Classes/CDVEcho.h; sourceTree = "<group>"; }; EB80C2AB15DEA63D004D9E7B /* CDVEcho.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVEcho.m; path = Classes/CDVEcho.m; sourceTree = "<group>"; }; + EB96673916A8970900D86CDF /* CDVUserAgentUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVUserAgentUtil.h; path = Classes/CDVUserAgentUtil.h; sourceTree = "<group>"; }; + EB96673A16A8970900D86CDF /* CDVUserAgentUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVUserAgentUtil.m; path = Classes/CDVUserAgentUtil.m; sourceTree = "<group>"; }; EBA3557115ABD38C00F4DE24 /* NSArray+Comparisons.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSArray+Comparisons.h"; path = "Classes/NSArray+Comparisons.h"; sourceTree = "<group>"; }; EBA3557215ABD38C00F4DE24 /* NSArray+Comparisons.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSArray+Comparisons.m"; path = "Classes/NSArray+Comparisons.m"; sourceTree = "<group>"; }; + EBFF4DBA16D3FE2E008F452B /* CDVWebViewDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVWebViewDelegate.m; path = Classes/CDVWebViewDelegate.m; sourceTree = "<group>"; }; + EBFF4DBB16D3FE2E008F452B /* CDVWebViewDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVWebViewDelegate.h; path = Classes/CDVWebViewDelegate.h; sourceTree = "<group>"; }; F858FBC4166009A8007DA594 /* CDVConfigParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CDVConfigParser.h; path = Classes/CDVConfigParser.h; sourceTree = "<group>"; }; F858FBC5166009A8007DA594 /* CDVConfigParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CDVConfigParser.m; path = Classes/CDVConfigParser.m; sourceTree = "<group>"; }; /* End PBXFileReference section */ @@ -240,8 +244,6 @@ F858FBC5166009A8007DA594 /* CDVConfigParser.m */, 8852C43614B65FD800F0E735 /* CDVViewController.h */, 8852C43714B65FD800F0E735 /* CDVViewController.m */, - 8852C43814B65FD800F0E735 /* CDVCordovaView.h */, - 8852C43914B65FD800F0E735 /* CDVCordovaView.m */, EB3B3545161CB44D003DBE7D /* CDVCommandQueue.h */, EB3B3546161CB44D003DBE7D /* CDVCommandQueue.m */, ); @@ -259,6 +261,8 @@ 888700D710922F56009987E8 /* Commands */ = { isa = PBXGroup; children = ( + EBFF4DBA16D3FE2E008F452B /* CDVWebViewDelegate.m */, + EBFF4DBB16D3FE2E008F452B /* CDVWebViewDelegate.h */, 30C5F1DD15AF9E950052A00D /* CDVDevice.h */, 30C5F1DE15AF9E950052A00D /* CDVDevice.m */, 301F2F2914F3C9CA003FE9FC /* CDV.h */, @@ -317,6 +321,10 @@ 3073E9E71656D37700957977 /* CDVInAppBrowser.h */, 3073E9E81656D37700957977 /* CDVInAppBrowser.m */, 3073E9EC1656D51200957977 /* CDVScreenOrientationDelegate.h */, + 30F39309169F839700B22307 /* CDVJSON.h */, + 30F3930A169F839700B22307 /* CDVJSON.m */, + EB96673916A8970900D86CDF /* CDVUserAgentUtil.h */, + EB96673A16A8970900D86CDF /* CDVUserAgentUtil.m */, ); name = Commands; sourceTree = "<group>"; @@ -344,22 +352,11 @@ children = ( 3054098714B77FF3009841CA /* Cleaver */, 888700D710922F56009987E8 /* Commands */, - 8887FD361090FBE7009987E8 /* JSON */, 888700D910923009009987E8 /* Util */, ); name = Classes; sourceTree = "<group>"; }; - 8887FD361090FBE7009987E8 /* JSON */ = { - isa = PBXGroup; - children = ( - 30A90B8F14588697006178D3 /* JSONKit.h */, - 30A90B9014588697006178D3 /* JSONKit.m */, - ); - name = JSON; - path = Classes/JSON; - sourceTree = "<group>"; - }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -391,9 +388,7 @@ 1F2BECC013F9785B00A93BF6 /* CDVBattery.h in Headers */, 30C684801406CB38004C1A8E /* CDVWhitelist.h in Headers */, 30C684941407044B004C1A8E /* CDVURLProtocol.h in Headers */, - 30A90B9114588697006178D3 /* JSONKit.h in Headers */, 8852C43A14B65FD800F0E735 /* CDVViewController.h in Headers */, - 8852C43F14B65FD800F0E735 /* CDVCordovaView.h in Headers */, 30F5EBAB14CA26E700987760 /* CDVCommandDelegate.h in Headers */, 301F2F2A14F3C9CA003FE9FC /* CDV.h in Headers */, 30392E4E14F4FCAB00B9E0B8 /* CDVAvailability.h in Headers */, @@ -409,6 +404,9 @@ 3073E9E91656D37700957977 /* CDVInAppBrowser.h in Headers */, 3073E9ED1656D51200957977 /* CDVScreenOrientationDelegate.h in Headers */, F858FBC6166009A8007DA594 /* CDVConfigParser.h in Headers */, + 30F3930B169F839700B22307 /* CDVJSON.h in Headers */, + EBFF4DBD16D3FE2E008F452B /* CDVWebViewDelegate.h in Headers */, + EB96673B16A8970A00D86CDF /* CDVUserAgentUtil.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -438,7 +436,7 @@ 0867D690FE84028FC02AAC07 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0430; + LastUpgradeCheck = 0460; }; buildConfigurationList = 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "CordovaLib" */; compatibilityVersion = "Xcode 3.2"; @@ -489,9 +487,7 @@ 1F2BECC113F9785B00A93BF6 /* CDVBattery.m in Sources */, 30C684821406CB38004C1A8E /* CDVWhitelist.m in Sources */, 30C684961407044B004C1A8E /* CDVURLProtocol.m in Sources */, - 30A90B9314588697006178D3 /* JSONKit.m in Sources */, 8852C43C14B65FD800F0E735 /* CDVViewController.m in Sources */, - 8852C44114B65FD800F0E735 /* CDVCordovaView.m in Sources */, 3034979E1513D56A0090E688 /* CDVLocalStorage.m in Sources */, 3062D122151D0EDB000D9128 /* UIDevice+Extensions.m in Sources */, 3E76876D156A90EE00EB6FA3 /* CDVLogger.m in Sources */, @@ -503,6 +499,9 @@ 9D76CF3D1625A4C50008A0F6 /* CDVGlobalization.m in Sources */, 3073E9EA1656D37700957977 /* CDVInAppBrowser.m in Sources */, F858FBC7166009A8007DA594 /* CDVConfigParser.m in Sources */, + 30F3930C169F839700B22307 /* CDVJSON.m in Sources */, + EB96673C16A8970A00D86CDF /* CDVUserAgentUtil.m in Sources */, + EBFF4DBC16D3FE2E008F452B /* CDVWebViewDelegate.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -573,12 +572,17 @@ armv7s, ); "ARCHS[sdk=iphonesimulator*]" = i386; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; GCC_C_LANGUAGE_STANDARD = c99; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ""; GCC_THUMB_SUPPORT = NO; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 4.3; ONLY_ACTIVE_ARCH = NO; @@ -601,11 +605,16 @@ armv7s, ); "ARCHS[sdk=iphonesimulator*]" = i386; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; GCC_C_LANGUAGE_STANDARD = c99; GCC_PREPROCESSOR_DEFINITIONS = ""; GCC_THUMB_SUPPORT = NO; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 4.3; ONLY_ACTIVE_ARCH = NO; diff --git a/iPhone/CordovaLib/CordovaLib.xcodeproj/xcuserdata/struan.xcuserdatad/xcschemes/xcschememanagement.plist b/iPhone/CordovaLib/CordovaLib.xcodeproj/xcuserdata/struan.xcuserdatad/xcschemes/xcschememanagement.plist index 283503b..8672103 100644 --- a/iPhone/CordovaLib/CordovaLib.xcodeproj/xcuserdata/struan.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/iPhone/CordovaLib/CordovaLib.xcodeproj/xcuserdata/struan.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ <key>CordovaLib.xcscheme</key> <dict> <key>orderHint</key> - <integer>1</integer> + <integer>2</integer> </dict> </dict> <key>SuppressBuildableAutocreation</key> diff --git a/iPhone/CordovaLib/VERSION b/iPhone/CordovaLib/VERSION index cc6612c..437459c 100755 --- a/iPhone/CordovaLib/VERSION +++ b/iPhone/CordovaLib/VERSION @@ -1 +1 @@ -2.3.0
\ No newline at end of file +2.5.0 diff --git a/iPhone/FixMyStreet.xcodeproj/project.pbxproj b/iPhone/FixMyStreet.xcodeproj/project.pbxproj index 9c798f1..f42384a 100644 --- a/iPhone/FixMyStreet.xcodeproj/project.pbxproj +++ b/iPhone/FixMyStreet.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 1F766FE113BBADB100FB74C0 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1F766FDC13BBADB100FB74C0 /* Localizable.strings */; }; 1F766FE213BBADB100FB74C0 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1F766FDF13BBADB100FB74C0 /* Localizable.strings */; }; + 246C8F801700A4010052666F /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 246C8F7F1700A4010052666F /* AssetsLibrary.framework */; }; 288765FD0DF74451002DB57D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 288765FC0DF74451002DB57D /* CoreGraphics.framework */; }; 301BF552109A68D80062928A /* libCordova.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 301BF535109A57CC0062928A /* libCordova.a */; }; 301BF570109A69640062928A /* www in Resources */ = {isa = PBXBuildFile; fileRef = 301BF56E109A69640062928A /* www */; }; @@ -63,6 +64,7 @@ 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 1F766FDD13BBADB100FB74C0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Localizable.strings; sourceTree = "<group>"; }; 1F766FE013BBADB100FB74C0 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = Localizable.strings; sourceTree = "<group>"; }; + 246C8F7F1700A4010052666F /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; 288765FC0DF74451002DB57D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; }; 301BF52D109A57CC0062928A /* CordovaLib.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CordovaLib.xcodeproj; path = CordovaLib/CordovaLib.xcodeproj; sourceTree = "<group>"; }; @@ -104,6 +106,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 246C8F801700A4010052666F /* AssetsLibrary.framework in Frameworks */, 301BF552109A68D80062928A /* libCordova.a in Frameworks */, 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */, 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */, @@ -165,6 +168,7 @@ 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { isa = PBXGroup; children = ( + 246C8F7F1700A4010052666F /* AssetsLibrary.framework */, F840E1F0165FE0F500CFE078 /* config.xml */, 301BF56E109A69640062928A /* www */, 301BF52D109A57CC0062928A /* CordovaLib.xcodeproj */, @@ -309,7 +313,7 @@ 29B97313FDCFA39411CA2CEA /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0430; + LastUpgradeCheck = 0460; }; buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "FixMyStreet" */; compatibilityVersion = "Xcode 3.2"; @@ -450,7 +454,6 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ENABLE_OBJC_ARC = NO; - CLANG_WARN_OBJCPP_ARC_ABI = YES; COPY_PHASE_STRIP = NO; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -470,7 +473,6 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ENABLE_OBJC_ARC = NO; - CLANG_WARN_OBJCPP_ARC_ABI = YES; COPY_PHASE_STRIP = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "FixMyStreet/FixMyStreet-Prefix.pch"; @@ -487,11 +489,16 @@ isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; GCC_C_LANGUAGE_STANDARD = c99; GCC_THUMB_SUPPORT = NO; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ( "\"$(TARGET_BUILD_DIR)/usr/local/lib/include\"", @@ -522,11 +529,16 @@ isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; GCC_C_LANGUAGE_STANDARD = c99; GCC_THUMB_SUPPORT = NO; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ( "\"$(TARGET_BUILD_DIR)/usr/local/lib/include\"", diff --git a/iPhone/FixMyStreet/Classes/AppDelegate.m b/iPhone/FixMyStreet/Classes/AppDelegate.m index 7d6bcd8..77e991d 100644 --- a/iPhone/FixMyStreet/Classes/AppDelegate.m +++ b/iPhone/FixMyStreet/Classes/AppDelegate.m @@ -43,6 +43,11 @@ [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; } @@ -109,4 +114,9 @@ return supportedInterfaceOrientations; } +- (void)applicationDidReceiveMemoryWarning:(UIApplication*)application +{ + [[NSURLCache sharedURLCache] removeAllCachedResponses]; +} + @end diff --git a/iPhone/FixMyStreet/config.xml b/iPhone/FixMyStreet/config.xml index 7ea9bb5..9798f07 100644 --- a/iPhone/FixMyStreet/config.xml +++ b/iPhone/FixMyStreet/config.xml @@ -1,12 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> -<cordova> +<widget> <preference name="AllowInlineMediaPlayback" value="false"/> <preference name="AutoHideSplashScreen" value="true"/> <preference name="EnableLocation" value="false"/> <preference name="EnableViewportScale" value="false"/> <preference name="MediaPlaybackRequiresUserAction" value="false"/> - <preference name="OpenAllWhitelistURLsInWebView" value="false"/> <preference name="ShowSplashScreenSpinner" value="true"/> + <preference name="FadeSplashScreen" value="true" /> + <preference name="FadeSplashScreenDuration" value="2" /> <preference name="TopActivityIndicator" value="gray"/> <preference name="UIWebViewBounce" value="true"/> <content src="index.html" /> @@ -32,4 +33,4 @@ <access origin="*.virtualearth.net"/> <access origin="mapit.mysociety.org"/> <access origin="struan.fixmystreet.dev.mysociety.org"/> -</cordova> +</widget> diff --git a/iPhone/cordova/build b/iPhone/cordova/build new file mode 100755 index 0000000..1574c5e --- /dev/null +++ b/iPhone/cordova/build @@ -0,0 +1,51 @@ +#!/bin/bash + +# +# 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. +# + +# +# compile and launch a Cordova/iOS project to the simulator +# + +set -e + +XCODE_VER=$(xcodebuild -version | head -n 1 | sed -e 's/Xcode //') +XCODE_MIN_VERSION="4.5" + +if [[ "$XCODE_VER" < "$XCODE_MIN_VERSION" ]]; then + echo "Cordova can only run in Xcode version $XCODE_MIN_VERSION or greater." + exit 1 +fi + +CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd -P) +PROJECT_PATH="$(dirname "$CORDOVA_PATH")" +XCODEPROJ=$( ls "$PROJECT_PATH" | grep .xcodeproj ) +PROJECT_NAME=$(basename "$XCODEPROJ" .xcodeproj) + +cd "$PROJECT_PATH" + +APP=build/$PROJECT_NAME.app +SDK=`xcodebuild -showsdks | grep Sim | tail -1 | awk '{print $6}'` + +xcodebuild -project $PROJECT_NAME.xcodeproj -arch i386 -target $PROJECT_NAME -configuration Debug -sdk $SDK clean build VALID_ARCHS="i386" CONFIGURATION_BUILD_DIR="$PROJECT_PATH/build" + + + + + diff --git a/iPhone/cordova/emulate b/iPhone/cordova/emulate new file mode 100755 index 0000000..f26cb3a --- /dev/null +++ b/iPhone/cordova/emulate @@ -0,0 +1,55 @@ +#! /bin/bash +# +# 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. +# + +set -e + +XCODE_VER=$(xcodebuild -version | head -n 1 | sed -e 's/Xcode //') +XCODE_MIN_VERSION="4.5" + +if [[ "$XCODE_VER" < "$XCODE_MIN_VERSION" ]]; then + echo "Cordova can only run in Xcode version $XCODE_MIN_VERSION or greater." + exit 1 +fi + +CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd -P) +PROJECT_PATH="$(dirname "$CORDOVA_PATH")" +XCODEPROJ=$( ls "$PROJECT_PATH" | grep .xcodeproj ) +PROJECT_NAME=$(basename "$XCODEPROJ" .xcodeproj) + +APP_PATH=${1:-$PROJECT_PATH/build/$PROJECT_NAME.app} +DEVICE_FAMILY=${2:-${DEVICE_FAMILY:-iphone}} + +if [ ! -d "$APP_PATH" ]; then + echo "Project '$APP_PATH' is not built. Building." + $CORDOVA_PATH/build || exit $? +fi + +if [ ! -d "$APP_PATH" ]; then + echo "$APP_PATH not found to emulate." + exit 1 +fi + +# launch using ios-sim +if which ios-sim >/dev/null; then + ios-sim launch "$APP_PATH" --family "$DEVICE_FAMILY" --stderr "$CORDOVA_PATH/console.log" --stdout "$CORDOVA_PATH/console.log" & +else + echo -e '\033[31mError: ios-sim was not found. Please download, build and install version 1.4 or greater from https://github.com/phonegap/ios-sim into your path. Or "brew install ios-sim" using homebrew: http://mxcl.github.com/homebrew/\033[m'; exit 1; +fi + diff --git a/iPhone/cordova/log b/iPhone/cordova/log new file mode 100755 index 0000000..b235b09 --- /dev/null +++ b/iPhone/cordova/log @@ -0,0 +1,23 @@ +#! /bin/sh +# +# 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. +# + +CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd -P) + +tail -f "$CORDOVA_PATH/console.log" diff --git a/iPhone/cordova/release b/iPhone/cordova/release new file mode 100755 index 0000000..7263934 --- /dev/null +++ b/iPhone/cordova/release @@ -0,0 +1,51 @@ +#!/bin/bash + +# +# 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. +# + +# +# compile and launch a Cordova/iOS project to the simulator +# + +set -e + +XCODE_VER=$(xcodebuild -version | head -n 1 | sed -e 's/Xcode //') +XCODE_MIN_VERSION="4.5" + +if [[ "$XCODE_VER" < "$XCODE_MIN_VERSION" ]]; then + echo "Cordova can only run in Xcode version $XCODE_MIN_VERSION or greater." + exit 1 +fi + +CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd -P) +PROJECT_PATH="$(dirname "$CORDOVA_PATH")" +XCODEPROJ=$( ls "$PROJECT_PATH" | grep .xcodeproj ) +PROJECT_NAME=$(basename "$XCODEPROJ" .xcodeproj) + +cd "$PROJECT_PATH" + +APP=build/$PROJECT_NAME.app +SDK=`xcodebuild -showsdks | grep Sim | tail -1 | awk '{print $6}'` + +xcodebuild -project $PROJECT_NAME.xcodeproj -arch i386 -target $PROJECT_NAME -configuration Release -sdk $SDK clean build VALID_ARCHS="i386" CONFIGURATION_BUILD_DIR="$PROJECT_PATH/build" + + + + + diff --git a/iPhone/cordova/run b/iPhone/cordova/run new file mode 100755 index 0000000..ef7198e --- /dev/null +++ b/iPhone/cordova/run @@ -0,0 +1,58 @@ +#! /bin/sh +# +# 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. +# + +set -e + +XCODE_VER=$(xcodebuild -version | head -n 1 | sed -e 's/Xcode //') +XCODE_MIN_VERSION="4.5" + +if [[ "$XCODE_VER" < "$XCODE_MIN_VERSION" ]]; then + echo "Cordova can only run in Xcode version $XCODE_MIN_VERSION or greater." + exit 1 +fi + +CORDOVA_PATH=$( cd "$( dirname "$0" )" && pwd -P) +PROJECT_PATH="$(dirname "$CORDOVA_PATH")" +XCODEPROJ=$( ls "$PROJECT_PATH" | grep .xcodeproj ) +PROJECT_NAME=$(basename "$XCODEPROJ" .xcodeproj) + +APP_PATH=$1 + +if [ $# -lt 1 ]; then + APP_PATH="$PROJECT_PATH/build/$PROJECT_NAME.app" +fi + +if [ ! -d "$APP_PATH" ]; then + echo "Project '$APP_PATH' is not built. Building." + $CORDOVA_PATH/build || exit $? +fi + +if [ ! -d "$APP_PATH" ]; then + echo "$APP_PATH not found to emulate." + exit 1 +fi + +# launch using ios-sim +if which ios-sim >/dev/null; then + ios-sim launch "$APP_PATH" --stderr "$CORDOVA_PATH/console.log" --stdout "$CORDOVA_PATH/console.log" & +else + echo -e '\033[31mError: ios-sim was not found. Please download, build and install version 1.4 or greater from https://github.com/phonegap/ios-sim into your path. Or "brew install ios-sim" using homebrew: http://mxcl.github.com/homebrew/\033[m'; exit 1; +fi + diff --git a/www/cordova-independent.js b/www/cordova-independent.js index c6dde34..6590173 100644 --- a/www/cordova-independent.js +++ b/www/cordova-independent.js @@ -2,7 +2,7 @@ var scriptElement = document.createElement("script"); scriptElement.type = "text/javascript"; if (navigator.userAgent.match(/(iPhone|iPod|iPad)/)) { - scriptElement.src = 'cordova-ios-2.4.0.js'; + scriptElement.src = 'cordova-ios-2.5.0.js'; } else if (navigator.userAgent.match(/Android/)) { scriptElement.src = 'cordova-android-2.2.0.js'; } else { diff --git a/www/cordova-ios-2.5.0.js b/www/cordova-ios-2.5.0.js new file mode 100755 index 0000000..3d83df3 --- /dev/null +++ b/www/cordova-ios-2.5.0.js @@ -0,0 +1,6083 @@ +// Platform: ios + +// commit f50d20a87431c79a54572263729461883f611a53 + +// File generated at :: Tue Feb 26 2013 14:26:19 GMT-0800 (PST) + +/* + 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. +*/ + +;(function() { + +// file: lib/scripts/require.js + +var require, + define; + +(function () { + var modules = {}; + // Stack of moduleIds currently being built. + var requireStack = []; + // Map of module ID -> index into requireStack of modules currently being built. + var inProgressModules = {}; + + function build(module) { + var factory = module.factory; + module.exports = {}; + delete module.factory; + factory(require, module.exports, module); + return module.exports; + } + + require = function (id) { + if (!modules[id]) { + throw "module " + id + " not found"; + } else if (id in inProgressModules) { + var cycle = requireStack.slice(inProgressModules[id]).join('->') + '->' + id; + throw "Cycle in require graph: " + cycle; + } + if (modules[id].factory) { + try { + inProgressModules[id] = requireStack.length; + requireStack.push(id); + return build(modules[id]); + } finally { + delete inProgressModules[id]; + requireStack.pop(); + } + } + return modules[id].exports; + }; + + define = function (id, factory) { + if (modules[id]) { + throw "module " + id + " already defined"; + } + + modules[id] = { + id: id, + factory: factory + }; + }; + + define.remove = function (id) { + delete modules[id]; + }; + + define.moduleMap = modules; +})(); + +//Export for use in node +if (typeof module === "object" && typeof require === "function") { + module.exports.require = require; + module.exports.define = define; +} + +// file: lib/cordova.js +define("cordova", function(require, exports, module) { + + +var channel = require('cordova/channel'); + +/** + * Listen for DOMContentLoaded and notify our channel subscribers. + */ +document.addEventListener('DOMContentLoaded', function() { + channel.onDOMContentLoaded.fire(); +}, false); +if (document.readyState == 'complete' || document.readyState == 'interactive') { + channel.onDOMContentLoaded.fire(); +} + +/** + * Intercept calls to addEventListener + removeEventListener and handle deviceready, + * resume, and pause events. + */ +var m_document_addEventListener = document.addEventListener; +var m_document_removeEventListener = document.removeEventListener; +var m_window_addEventListener = window.addEventListener; +var m_window_removeEventListener = window.removeEventListener; + +/** + * Houses custom event handlers to intercept on document + window event listeners. + */ +var documentEventHandlers = {}, + windowEventHandlers = {}; + +document.addEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + if (typeof documentEventHandlers[e] != 'undefined') { + documentEventHandlers[e].subscribe(handler); + } else { + m_document_addEventListener.call(document, evt, handler, capture); + } +}; + +window.addEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + if (typeof windowEventHandlers[e] != 'undefined') { + windowEventHandlers[e].subscribe(handler); + } else { + m_window_addEventListener.call(window, evt, handler, capture); + } +}; + +document.removeEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + // If unsubscribing from an event that is handled by a plugin + if (typeof documentEventHandlers[e] != "undefined") { + documentEventHandlers[e].unsubscribe(handler); + } else { + m_document_removeEventListener.call(document, evt, handler, capture); + } +}; + +window.removeEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + // If unsubscribing from an event that is handled by a plugin + if (typeof windowEventHandlers[e] != "undefined") { + windowEventHandlers[e].unsubscribe(handler); + } else { + m_window_removeEventListener.call(window, evt, handler, capture); + } +}; + +function createEvent(type, data) { + var event = document.createEvent('Events'); + event.initEvent(type, false, false); + if (data) { + for (var i in data) { + if (data.hasOwnProperty(i)) { + event[i] = data[i]; + } + } + } + return event; +} + +if(typeof window.console === "undefined") { + window.console = { + log:function(){} + }; +} + +var cordova = { + define:define, + require:require, + /** + * Methods to add/remove your own addEventListener hijacking on document + window. + */ + addWindowEventHandler:function(event) { + return (windowEventHandlers[event] = channel.create(event)); + }, + addStickyDocumentEventHandler:function(event) { + return (documentEventHandlers[event] = channel.createSticky(event)); + }, + addDocumentEventHandler:function(event) { + return (documentEventHandlers[event] = channel.create(event)); + }, + removeWindowEventHandler:function(event) { + delete windowEventHandlers[event]; + }, + removeDocumentEventHandler:function(event) { + delete documentEventHandlers[event]; + }, + /** + * Retrieve original event handlers that were replaced by Cordova + * + * @return object + */ + getOriginalHandlers: function() { + return {'document': {'addEventListener': m_document_addEventListener, 'removeEventListener': m_document_removeEventListener}, + 'window': {'addEventListener': m_window_addEventListener, 'removeEventListener': m_window_removeEventListener}}; + }, + /** + * Method to fire event from native code + * bNoDetach is required for events which cause an exception which needs to be caught in native code + */ + fireDocumentEvent: function(type, data, bNoDetach) { + var evt = createEvent(type, data); + if (typeof documentEventHandlers[type] != 'undefined') { + if( bNoDetach ) { + documentEventHandlers[type].fire(evt); + } + else { + setTimeout(function() { + documentEventHandlers[type].fire(evt); + }, 0); + } + } else { + document.dispatchEvent(evt); + } + }, + fireWindowEvent: function(type, data) { + var evt = createEvent(type,data); + if (typeof windowEventHandlers[type] != 'undefined') { + setTimeout(function() { + windowEventHandlers[type].fire(evt); + }, 0); + } else { + window.dispatchEvent(evt); + } + }, + + /** + * Plugin callback mechanism. + */ + // Randomize the starting callbackId to avoid collisions after refreshing or navigating. + // This way, it's very unlikely that any new callback would get the same callbackId as an old callback. + callbackId: Math.floor(Math.random() * 2000000000), + callbacks: {}, + callbackStatus: { + NO_RESULT: 0, + OK: 1, + CLASS_NOT_FOUND_EXCEPTION: 2, + ILLEGAL_ACCESS_EXCEPTION: 3, + INSTANTIATION_EXCEPTION: 4, + MALFORMED_URL_EXCEPTION: 5, + IO_EXCEPTION: 6, + INVALID_ACTION: 7, + JSON_EXCEPTION: 8, + ERROR: 9 + }, + + /** + * Called by native code when returning successful result from an action. + */ + callbackSuccess: function(callbackId, args) { + try { + cordova.callbackFromNative(callbackId, true, args.status, args.message, args.keepCallback); + } catch (e) { + console.log("Error in error callback: " + callbackId + " = "+e); + } + }, + + /** + * Called by native code when returning error result from an action. + */ + callbackError: function(callbackId, args) { + // TODO: Deprecate callbackSuccess and callbackError in favour of callbackFromNative. + // Derive success from status. + try { + cordova.callbackFromNative(callbackId, false, args.status, args.message, args.keepCallback); + } catch (e) { + console.log("Error in error callback: " + callbackId + " = "+e); + } + }, + + /** + * Called by native code when returning the result from an action. + */ + callbackFromNative: function(callbackId, success, status, message, keepCallback) { + var callback = cordova.callbacks[callbackId]; + if (callback) { + if (success && status == cordova.callbackStatus.OK) { + callback.success && callback.success(message); + } else if (!success) { + callback.fail && callback.fail(message); + } + + // Clear callback if not expecting any more results + if (!keepCallback) { + delete cordova.callbacks[callbackId]; + } + } + }, + addConstructor: function(func) { + channel.onCordovaReady.subscribe(function() { + try { + func(); + } catch(e) { + console.log("Failed to run constructor: " + e); + } + }); + } +}; + +// Register pause, resume and deviceready channels as events on document. +channel.onPause = cordova.addDocumentEventHandler('pause'); +channel.onResume = cordova.addDocumentEventHandler('resume'); +channel.onDeviceReady = cordova.addStickyDocumentEventHandler('deviceready'); + +module.exports = cordova; + +}); + +// file: lib/common/argscheck.js +define("cordova/argscheck", function(require, exports, module) { + +var exec = require('cordova/exec'); +var utils = require('cordova/utils'); + +var moduleExports = module.exports; + +var typeMap = { + 'A': 'Array', + 'D': 'Date', + 'N': 'Number', + 'S': 'String', + 'F': 'Function', + 'O': 'Object' +}; + +function extractParamName(callee, argIndex) { + return (/.*?\((.*?)\)/).exec(callee)[1].split(', ')[argIndex]; +} + +function checkArgs(spec, functionName, args, opt_callee) { + if (!moduleExports.enableChecks) { + return; + } + var errMsg = null; + var typeName; + for (var i = 0; i < spec.length; ++i) { + var c = spec.charAt(i), + cUpper = c.toUpperCase(), + arg = args[i]; + // Asterix means allow anything. + if (c == '*') { + continue; + } + typeName = utils.typeName(arg); + if ((arg === null || arg === undefined) && c == cUpper) { + continue; + } + if (typeName != typeMap[cUpper]) { + errMsg = 'Expected ' + typeMap[cUpper]; + break; + } + } + if (errMsg) { + errMsg += ', but got ' + typeName + '.'; + errMsg = 'Wrong type for parameter "' + extractParamName(opt_callee || args.callee, i) + '" of ' + functionName + ': ' + errMsg; + // Don't log when running jake test. + if (typeof jasmine == 'undefined') { + console.error(errMsg); + } + throw TypeError(errMsg); + } +} + +function getValue(value, defaultValue) { + return value === undefined ? defaultValue : value; +} + +moduleExports.checkArgs = checkArgs; +moduleExports.getValue = getValue; +moduleExports.enableChecks = true; + + +}); + +// file: lib/common/builder.js +define("cordova/builder", function(require, exports, module) { + +var utils = require('cordova/utils'); + +function each(objects, func, context) { + for (var prop in objects) { + if (objects.hasOwnProperty(prop)) { + func.apply(context, [objects[prop], prop]); + } + } +} + +function clobber(obj, key, value) { + exports.replaceHookForTesting(obj, key); + obj[key] = value; + // Getters can only be overridden by getters. + if (obj[key] !== value) { + utils.defineGetter(obj, key, function() { + return value; + }); + } +} + +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 { + clobber(obj, key, value); + } +} + +function include(parent, objects, clobber, merge) { + each(objects, function (obj, key) { + try { + var result = obj.path ? require(obj.path) : {}; + + if (clobber) { + // Clobber if it doesn't exist. + if (typeof parent[key] === 'undefined') { + assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated); + } else if (typeof obj.path !== 'undefined') { + // If merging, merge properties onto parent, otherwise, clobber. + if (merge) { + recursiveMerge(parent[key], result); + } else { + assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated); + } + } + result = parent[key]; + } else { + // Overwrite if not currently defined. + if (typeof parent[key] == 'undefined') { + assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated); + } else { + // Set result to what already exists, so we can build children into it if they exist. + result = parent[key]; + } + } + + if (obj.children) { + include(result, obj.children, clobber, merge); + } + } catch(e) { + utils.alert('Exception building cordova JS globals: ' + e + ' for key "' + key + '"'); + } + }); +} + +/** + * Merge properties from one object onto another recursively. Properties from + * the src object will overwrite existing target property. + * + * @param target Object to merge properties into. + * @param src Object to merge properties from. + */ +function recursiveMerge(target, src) { + for (var prop in src) { + if (src.hasOwnProperty(prop)) { + if (target.prototype && target.prototype.constructor === target) { + // If the target object is a constructor override off prototype. + clobber(target.prototype, prop, src[prop]); + } else { + if (typeof src[prop] === 'object' && typeof target[prop] === 'object') { + recursiveMerge(target[prop], src[prop]); + } else { + clobber(target, prop, src[prop]); + } + } + } + } +} + +exports.buildIntoButDoNotClobber = function(objects, target) { + include(target, objects, false, false); +}; +exports.buildIntoAndClobber = function(objects, target) { + include(target, objects, true, false); +}; +exports.buildIntoAndMerge = function(objects, target) { + include(target, objects, true, true); +}; +exports.recursiveMerge = recursiveMerge; +exports.assignOrWrapInDeprecateGetter = assignOrWrapInDeprecateGetter; +exports.replaceHookForTesting = function() {}; + +}); + +// file: lib/common/channel.js +define("cordova/channel", function(require, exports, module) { + +var utils = require('cordova/utils'), + nextGuid = 1; + +/** + * Custom pub-sub "channel" that can have functions subscribed to it + * This object is used to define and control firing of events for + * cordova initialization, as well as for custom events thereafter. + * + * The order of events during page load and Cordova startup is as follows: + * + * onDOMContentLoaded* Internal event that is received when the web page is loaded and parsed. + * onNativeReady* Internal event that indicates the Cordova native side is ready. + * onCordovaReady* Internal event fired when all Cordova JavaScript objects have been created. + * onCordovaInfoReady* Internal event fired when device properties are available. + * onCordovaConnectionReady* Internal event fired when the connection property has been set. + * onDeviceReady* User event fired to indicate that Cordova is ready + * onResume User event fired to indicate a start/resume lifecycle event + * onPause User event fired to indicate a pause lifecycle event + * onDestroy* Internal event fired when app is being destroyed (User should use window.onunload event, not this one). + * + * The events marked with an * are sticky. Once they have fired, they will stay in the fired state. + * All listeners that subscribe after the event is fired will be executed right away. + * + * The only Cordova events that user code should register for are: + * deviceready Cordova native code is initialized and Cordova APIs can be called from JavaScript + * pause App has moved to background + * resume App has returned to foreground + * + * Listeners can be registered as: + * document.addEventListener("deviceready", myDeviceReadyListener, false); + * document.addEventListener("resume", myResumeListener, false); + * document.addEventListener("pause", myPauseListener, false); + * + * The DOM lifecycle events should be used for saving and restoring state + * window.onload + * window.onunload + * + */ + +/** + * Channel + * @constructor + * @param type String the channel name + */ +var Channel = function(type, sticky) { + this.type = type; + // Map of guid -> function. + this.handlers = {}; + // 0 = Non-sticky, 1 = Sticky non-fired, 2 = Sticky fired. + this.state = sticky ? 1 : 0; + // Used in sticky mode to remember args passed to fire(). + this.fireArgs = null; + // Used by onHasSubscribersChange to know if there are any listeners. + this.numHandlers = 0; + // Function that is called when the first listener is subscribed, or when + // the last listener is unsubscribed. + this.onHasSubscribersChange = null; +}, + channel = { + /** + * Calls the provided function only after all of the channels specified + * have been fired. All channels must be sticky channels. + */ + join: function(h, c) { + var len = c.length, + i = len, + f = function() { + if (!(--i)) h(); + }; + for (var j=0; j<len; j++) { + if (c[j].state === 0) { + throw Error('Can only use join with sticky channels.'); + } + c[j].subscribe(f); + } + if (!len) h(); + }, + create: function(type) { + return channel[type] = new Channel(type, false); + }, + createSticky: function(type) { + return channel[type] = new Channel(type, true); + }, + + /** + * cordova Channels that must fire before "deviceready" is fired. + */ + deviceReadyChannelsArray: [], + deviceReadyChannelsMap: {}, + + /** + * Indicate that a feature needs to be initialized before it is ready to be used. + * This holds up Cordova's "deviceready" event until the feature has been initialized + * and Cordova.initComplete(feature) is called. + * + * @param feature {String} The unique feature name + */ + waitForInitialization: function(feature) { + if (feature) { + var c = channel[feature] || this.createSticky(feature); + this.deviceReadyChannelsMap[feature] = c; + this.deviceReadyChannelsArray.push(c); + } + }, + + /** + * Indicate that initialization code has completed and the feature is ready to be used. + * + * @param feature {String} The unique feature name + */ + initializationComplete: function(feature) { + var c = this.deviceReadyChannelsMap[feature]; + if (c) { + c.fire(); + } + } + }; + +function forceFunction(f) { + if (typeof f != 'function') throw "Function required as first argument!"; +} + +/** + * Subscribes the given function to the channel. Any time that + * Channel.fire is called so too will the function. + * Optionally specify an execution context for the function + * and a guid that can be used to stop subscribing to the channel. + * Returns the guid. + */ +Channel.prototype.subscribe = function(f, c) { + // need a function to call + forceFunction(f); + if (this.state == 2) { + f.apply(c || this, this.fireArgs); + return; + } + + var func = f, + guid = f.observer_guid; + if (typeof c == "object") { func = utils.close(c, f); } + + if (!guid) { + // first time any channel has seen this subscriber + guid = '' + nextGuid++; + } + func.observer_guid = guid; + f.observer_guid = guid; + + // Don't add the same handler more than once. + if (!this.handlers[guid]) { + this.handlers[guid] = func; + this.numHandlers++; + if (this.numHandlers == 1) { + this.onHasSubscribersChange && this.onHasSubscribersChange(); + } + } +}; + +/** + * Unsubscribes the function with the given guid from the channel. + */ +Channel.prototype.unsubscribe = function(f) { + // need a function to unsubscribe + forceFunction(f); + + var guid = f.observer_guid, + handler = this.handlers[guid]; + if (handler) { + delete this.handlers[guid]; + this.numHandlers--; + if (this.numHandlers === 0) { + this.onHasSubscribersChange && this.onHasSubscribersChange(); + } + } +}; + +/** + * Calls all functions subscribed to this channel. + */ +Channel.prototype.fire = function(e) { + var fail = false, + fireArgs = Array.prototype.slice.call(arguments); + // Apply stickiness. + if (this.state == 1) { + this.state = 2; + this.fireArgs = fireArgs; + } + if (this.numHandlers) { + // Copy the values first so that it is safe to modify it from within + // callbacks. + var toCall = []; + for (var item in this.handlers) { + toCall.push(this.handlers[item]); + } + for (var i = 0; i < toCall.length; ++i) { + toCall[i].apply(this, fireArgs); + } + if (this.state == 2 && this.numHandlers) { + this.numHandlers = 0; + this.handlers = {}; + this.onHasSubscribersChange && this.onHasSubscribersChange(); + } + } +}; + + +// defining them here so they are ready super fast! +// DOM event that is received when the web page is loaded and parsed. +channel.createSticky('onDOMContentLoaded'); + +// Event to indicate the Cordova native side is ready. +channel.createSticky('onNativeReady'); + +// Event to indicate that all Cordova JavaScript objects have been created +// and it's time to run plugin constructors. +channel.createSticky('onCordovaReady'); + +// Event to indicate that device properties are available +channel.createSticky('onCordovaInfoReady'); + +// Event to indicate that the connection property has been set. +channel.createSticky('onCordovaConnectionReady'); + +// Event to indicate that Cordova is ready +channel.createSticky('onDeviceReady'); + +// Event to indicate a resume lifecycle event +channel.create('onResume'); + +// Event to indicate a pause lifecycle event +channel.create('onPause'); + +// Event to indicate a destroy lifecycle event +channel.createSticky('onDestroy'); + +// Channels that must fire before "deviceready" is fired. +channel.waitForInitialization('onCordovaReady'); +channel.waitForInitialization('onCordovaConnectionReady'); + +module.exports = channel; + +}); + +// file: lib/common/commandProxy.js +define("cordova/commandProxy", function(require, exports, module) { + + +// internal map of proxy function +var CommandProxyMap = {}; + +module.exports = { + + // example: cordova.commandProxy.add("Accelerometer",{getCurrentAcceleration: function(successCallback, errorCallback, options) {...},...); + add:function(id,proxyObj) { + console.log("adding proxy for " + id); + CommandProxyMap[id] = proxyObj; + return proxyObj; + }, + + // cordova.commandProxy.remove("Accelerometer"); + remove:function(id) { + var proxy = CommandProxyMap[id]; + delete CommandProxyMap[id]; + CommandProxyMap[id] = null; + return proxy; + }, + + get:function(service,action) { + return ( CommandProxyMap[service] ? CommandProxyMap[service][action] : null ); + } +}; +}); + +// file: lib/ios/exec.js +define("cordova/exec", function(require, exports, module) { + +/** + * Creates a gap bridge iframe used to notify the native code about queued + * commands. + * + * @private + */ +var cordova = require('cordova'), + channel = require('cordova/channel'), + utils = require('cordova/utils'), + jsToNativeModes = { + IFRAME_NAV: 0, + XHR_NO_PAYLOAD: 1, + XHR_WITH_PAYLOAD: 2, + XHR_OPTIONAL_PAYLOAD: 3 + }, + bridgeMode, + execIframe, + execXhr, + requestCount = 0, + vcHeaderValue = null, + commandQueue = [], // Contains pending JS->Native messages. + isInContextOfEvalJs = 0; + +function createExecIframe() { + var iframe = document.createElement("iframe"); + iframe.style.display = 'none'; + document.body.appendChild(iframe); + return iframe; +} + +function shouldBundleCommandJson() { + if (bridgeMode == jsToNativeModes.XHR_WITH_PAYLOAD) { + return true; + } + if (bridgeMode == jsToNativeModes.XHR_OPTIONAL_PAYLOAD) { + var payloadLength = 0; + for (var i = 0; i < commandQueue.length; ++i) { + payloadLength += commandQueue[i].length; + } + // The value here was determined using the benchmark within CordovaLibApp on an iPad 3. + return payloadLength < 4500; + } + return false; +} + +function massageArgsJsToNative(args) { + if (!args || utils.typeName(args) != 'Array') { + return args; + } + var encodeArrayBufferAs8bitString = function(ab) { + return String.fromCharCode.apply(null, new Uint8Array(ab)); + }; + var encodeArrayBufferAsBase64 = function(ab) { + return window.btoa(encodeArrayBufferAs8bitString(ab)); + }; + args.forEach(function(arg, i) { + if (utils.typeName(arg) == 'ArrayBuffer') { + args[i] = { + 'CDVType': 'ArrayBuffer', + 'data': encodeArrayBufferAsBase64(arg) + }; + } + }); + return args; +} + +function massagePayloadNativeToJs(payload) { + if (payload && payload.hasOwnProperty('CDVType') && payload.CDVType == 'ArrayBuffer') { + var stringToArrayBuffer = function(str) { + var ret = new Uint8Array(str.length); + for (var i = 0; i < str.length; i++) { + ret[i] = str.charCodeAt(i); + } + return ret.buffer; + }; + var base64ToArrayBuffer = function(b64) { + return stringToArrayBuffer(atob(b64)); + }; + payload = base64ToArrayBuffer(payload.data); + } + return payload; +} + +function iOSExec() { + // XHR mode does not work on iOS 4.2, so default to IFRAME_NAV for such devices. + // XHR mode's main advantage is working around a bug in -webkit-scroll, which + // doesn't exist in 4.X devices anyways. + if (bridgeMode === undefined) { + bridgeMode = navigator.userAgent.indexOf(' 4_') == -1 ? jsToNativeModes.XHR_NO_PAYLOAD : jsToNativeModes.IFRAME_NAV; + } + + var successCallback, failCallback, service, action, actionArgs, splitCommand; + var callbackId = null; + if (typeof arguments[0] !== "string") { + // FORMAT ONE + successCallback = arguments[0]; + failCallback = arguments[1]; + service = arguments[2]; + action = arguments[3]; + actionArgs = arguments[4]; + + // Since we need to maintain backwards compatibility, we have to pass + // an invalid callbackId even if no callback was provided since plugins + // will be expecting it. The Cordova.exec() implementation allocates + // an invalid callbackId and passes it even if no callbacks were given. + callbackId = 'INVALID'; + } else { + // FORMAT TWO + splitCommand = arguments[0].split("."); + action = splitCommand.pop(); + service = splitCommand.join("."); + actionArgs = Array.prototype.splice.call(arguments, 1); + } + + // Register the callbacks and add the callbackId to the positional + // arguments if given. + if (successCallback || failCallback) { + callbackId = service + cordova.callbackId++; + cordova.callbacks[callbackId] = + {success:successCallback, fail:failCallback}; + } + + actionArgs = massageArgsJsToNative(actionArgs); + + var command = [callbackId, service, action, actionArgs]; + + // Stringify and queue the command. We stringify to command now to + // effectively clone the command arguments in case they are mutated before + // the command is executed. + commandQueue.push(JSON.stringify(command)); + + // If we're in the context of a stringByEvaluatingJavaScriptFromString call, + // then the queue will be flushed when it returns; no need for a poke. + // Also, if there is already a command in the queue, then we've already + // poked the native side, so there is no reason to do so again. + if (!isInContextOfEvalJs && commandQueue.length == 1) { + if (bridgeMode != jsToNativeModes.IFRAME_NAV) { + // This prevents sending an XHR when there is already one being sent. + // This should happen only in rare circumstances (refer to unit tests). + if (execXhr && execXhr.readyState != 4) { + execXhr = null; + } + // Re-using the XHR improves exec() performance by about 10%. + execXhr = execXhr || new XMLHttpRequest(); + // Changing this to a GET will make the XHR reach the URIProtocol on 4.2. + // For some reason it still doesn't work though... + // Add a timestamp to the query param to prevent caching. + execXhr.open('HEAD', "/!gap_exec?" + (+new Date()), true); + if (!vcHeaderValue) { + vcHeaderValue = /.*\((.*)\)/.exec(navigator.userAgent)[1]; + } + execXhr.setRequestHeader('vc', vcHeaderValue); + execXhr.setRequestHeader('rc', ++requestCount); + if (shouldBundleCommandJson()) { + execXhr.setRequestHeader('cmds', iOSExec.nativeFetchMessages()); + } + execXhr.send(null); + } else { + execIframe = execIframe || createExecIframe(); + execIframe.src = "gap://ready"; + } + } +} + +iOSExec.jsToNativeModes = jsToNativeModes; + +iOSExec.setJsToNativeBridgeMode = function(mode) { + // Remove the iFrame since it may be no longer required, and its existence + // can trigger browser bugs. + // https://issues.apache.org/jira/browse/CB-593 + if (execIframe) { + execIframe.parentNode.removeChild(execIframe); + execIframe = null; + } + bridgeMode = mode; +}; + +iOSExec.nativeFetchMessages = function() { + // Each entry in commandQueue is a JSON string already. + if (!commandQueue.length) { + return ''; + } + var json = '[' + commandQueue.join(',') + ']'; + commandQueue.length = 0; + return json; +}; + +iOSExec.nativeCallback = function(callbackId, status, payload, keepCallback) { + return iOSExec.nativeEvalAndFetch(function() { + var success = status === 0 || status === 1; + payload = massagePayloadNativeToJs(payload); + cordova.callbackFromNative(callbackId, success, status, payload, keepCallback); + }); +}; + +iOSExec.nativeEvalAndFetch = function(func) { + // This shouldn't be nested, but better to be safe. + isInContextOfEvalJs++; + try { + func(); + return iOSExec.nativeFetchMessages(); + } finally { + isInContextOfEvalJs--; + } +}; + +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 = cur[part] = cur[part] || {}; + } + return cur; +} + +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 (!(symbolPath in origSymbols)) { + 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.*\/plugininit$/); + + modulemapper.loadMatchingModules(/cordova.*\/symbols$/); + modulemapper.mapModules(window); + } +}; + + +}); + +// file: lib/common/plugin/Acceleration.js +define("cordova/plugin/Acceleration", function(require, exports, module) { + +var Acceleration = function(x, y, z, timestamp) { + this.x = x; + this.y = y; + this.z = z; + this.timestamp = timestamp || (new Date()).getTime(); +}; + +module.exports = Acceleration; + +}); + +// file: lib/common/plugin/Camera.js +define("cordova/plugin/Camera", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + exec = require('cordova/exec'), + Camera = require('cordova/plugin/CameraConstants'), + CameraPopoverHandle = require('cordova/plugin/CameraPopoverHandle'); + +var cameraExport = {}; + +// Tack on the Camera Constants to the base camera plugin. +for (var key in Camera) { + cameraExport[key] = Camera[key]; +} + +/** + * Gets a picture from source defined by "options.sourceType", and returns the + * image as defined by the "options.destinationType" option. + + * The defaults are sourceType=CAMERA and destinationType=FILE_URI. + * + * @param {Function} successCallback + * @param {Function} errorCallback + * @param {Object} options + */ +cameraExport.getPicture = function(successCallback, errorCallback, options) { + argscheck.checkArgs('fFO', 'Camera.getPicture', arguments); + options = options || {}; + var getValue = argscheck.getValue; + + var quality = getValue(options.quality, 50); + var destinationType = getValue(options.destinationType, Camera.DestinationType.FILE_URI); + var sourceType = getValue(options.sourceType, Camera.PictureSourceType.CAMERA); + var targetWidth = getValue(options.targetWidth, -1); + var targetHeight = getValue(options.targetHeight, -1); + var encodingType = getValue(options.encodingType, Camera.EncodingType.JPEG); + var mediaType = getValue(options.mediaType, Camera.MediaType.PICTURE); + var allowEdit = !!options.allowEdit; + var correctOrientation = !!options.correctOrientation; + var saveToPhotoAlbum = !!options.saveToPhotoAlbum; + var popoverOptions = getValue(options.popoverOptions, null); + + var args = [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType, + mediaType, allowEdit, correctOrientation, saveToPhotoAlbum, popoverOptions]; + + exec(successCallback, errorCallback, "Camera", "takePicture", args); + return new CameraPopoverHandle(); +}; + +cameraExport.cleanup = function(successCallback, errorCallback) { + exec(successCallback, errorCallback, "Camera", "cleanup", []); +}; + +module.exports = cameraExport; + +}); + +// file: lib/common/plugin/CameraConstants.js +define("cordova/plugin/CameraConstants", function(require, exports, module) { + +module.exports = { + DestinationType:{ + DATA_URL: 0, // Return base64 encoded string + FILE_URI: 1, // Return file uri (content://media/external/images/media/2 for Android) + NATIVE_URI: 2 // Return native uri (eg. asset-library://... for iOS) + }, + EncodingType:{ + JPEG: 0, // Return JPEG encoded image + PNG: 1 // Return PNG encoded image + }, + MediaType:{ + PICTURE: 0, // allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType + VIDEO: 1, // allow selection of video only, ONLY RETURNS URL + ALLMEDIA : 2 // allow selection from all media types + }, + PictureSourceType:{ + PHOTOLIBRARY : 0, // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) + CAMERA : 1, // Take picture from camera + SAVEDPHOTOALBUM : 2 // Choose image from picture library (same as PHOTOLIBRARY for Android) + }, + PopoverArrowDirection:{ + ARROW_UP : 1, // matches iOS UIPopoverArrowDirection constants to specify arrow location on popover + ARROW_DOWN : 2, + ARROW_LEFT : 4, + ARROW_RIGHT : 8, + ARROW_ANY : 15 + } +}; + +}); + +// file: lib/ios/plugin/CameraPopoverHandle.js +define("cordova/plugin/CameraPopoverHandle", function(require, exports, module) { + +var exec = require('cordova/exec'); + +/** + * A handle to an image picker popover. + */ +var CameraPopoverHandle = function() { + this.setPosition = function(popoverOptions) { + var args = [ popoverOptions ]; + exec(null, null, "Camera", "repositionPopover", args); + }; +}; + +module.exports = CameraPopoverHandle; + +}); + +// file: lib/common/plugin/CameraPopoverOptions.js +define("cordova/plugin/CameraPopoverOptions", function(require, exports, module) { + +var Camera = require('cordova/plugin/CameraConstants'); + +/** + * Encapsulates options for iOS Popover image picker + */ +var CameraPopoverOptions = function(x,y,width,height,arrowDir){ + // information of rectangle that popover should be anchored to + this.x = x || 0; + this.y = y || 32; + this.width = width || 320; + this.height = height || 480; + // The direction of the popover arrow + this.arrowDir = arrowDir || Camera.PopoverArrowDirection.ARROW_ANY; +}; + +module.exports = CameraPopoverOptions; + +}); + +// file: lib/common/plugin/CaptureAudioOptions.js +define("cordova/plugin/CaptureAudioOptions", function(require, exports, module) { + +/** + * Encapsulates all audio capture operation configuration options. + */ +var CaptureAudioOptions = function(){ + // Upper limit of sound clips user can record. Value must be equal or greater than 1. + this.limit = 1; + // Maximum duration of a single sound clip in seconds. + this.duration = 0; + // The selected audio mode. Must match with one of the elements in supportedAudioModes array. + this.mode = null; +}; + +module.exports = CaptureAudioOptions; + +}); + +// file: lib/common/plugin/CaptureError.js +define("cordova/plugin/CaptureError", function(require, exports, module) { + +/** + * The CaptureError interface encapsulates all errors in the Capture API. + */ +var CaptureError = function(c) { + this.code = c || null; +}; + +// Camera or microphone failed to capture image or sound. +CaptureError.CAPTURE_INTERNAL_ERR = 0; +// Camera application or audio capture application is currently serving other capture request. +CaptureError.CAPTURE_APPLICATION_BUSY = 1; +// Invalid use of the API (e.g. limit parameter has value less than one). +CaptureError.CAPTURE_INVALID_ARGUMENT = 2; +// User exited camera application or audio capture application before capturing anything. +CaptureError.CAPTURE_NO_MEDIA_FILES = 3; +// The requested capture operation is not supported. +CaptureError.CAPTURE_NOT_SUPPORTED = 20; + +module.exports = CaptureError; + +}); + +// file: lib/common/plugin/CaptureImageOptions.js +define("cordova/plugin/CaptureImageOptions", function(require, exports, module) { + +/** + * Encapsulates all image capture operation configuration options. + */ +var CaptureImageOptions = function(){ + // Upper limit of images user can take. Value must be equal or greater than 1. + this.limit = 1; + // The selected image mode. Must match with one of the elements in supportedImageModes array. + this.mode = null; +}; + +module.exports = CaptureImageOptions; + +}); + +// file: lib/common/plugin/CaptureVideoOptions.js +define("cordova/plugin/CaptureVideoOptions", function(require, exports, module) { + +/** + * Encapsulates all video capture operation configuration options. + */ +var CaptureVideoOptions = function(){ + // Upper limit of videos user can record. Value must be equal or greater than 1. + this.limit = 1; + // Maximum duration of a single video clip in seconds. + this.duration = 0; + // The selected video mode. Must match with one of the elements in supportedVideoModes array. + this.mode = null; +}; + +module.exports = CaptureVideoOptions; + +}); + +// file: lib/common/plugin/CompassError.js +define("cordova/plugin/CompassError", function(require, exports, module) { + +/** + * CompassError. + * An error code assigned by an implementation when an error has occurred + * @constructor + */ +var CompassError = function(err) { + this.code = (err !== undefined ? err : null); +}; + +CompassError.COMPASS_INTERNAL_ERR = 0; +CompassError.COMPASS_NOT_SUPPORTED = 20; + +module.exports = CompassError; + +}); + +// file: lib/common/plugin/CompassHeading.js +define("cordova/plugin/CompassHeading", function(require, exports, module) { + +var CompassHeading = function(magneticHeading, trueHeading, headingAccuracy, timestamp) { + this.magneticHeading = magneticHeading; + this.trueHeading = trueHeading; + this.headingAccuracy = headingAccuracy; + this.timestamp = timestamp || new Date().getTime(); +}; + +module.exports = CompassHeading; + +}); + +// file: lib/common/plugin/ConfigurationData.js +define("cordova/plugin/ConfigurationData", function(require, exports, module) { + +/** + * Encapsulates a set of parameters that the capture device supports. + */ +function ConfigurationData() { + // The ASCII-encoded string in lower case representing the media type. + this.type = null; + // The height attribute represents height of the image or video in pixels. + // In the case of a sound clip this attribute has value 0. + this.height = 0; + // The width attribute represents width of the image or video in pixels. + // In the case of a sound clip this attribute has value 0 + this.width = 0; +} + +module.exports = ConfigurationData; + +}); + +// file: lib/common/plugin/Connection.js +define("cordova/plugin/Connection", function(require, exports, module) { + +/** + * Network status + */ +module.exports = { + UNKNOWN: "unknown", + ETHERNET: "ethernet", + WIFI: "wifi", + CELL_2G: "2g", + CELL_3G: "3g", + CELL_4G: "4g", + CELL:"cellular", + NONE: "none" +}; + +}); + +// file: lib/common/plugin/Contact.js +define("cordova/plugin/Contact", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + exec = require('cordova/exec'), + ContactError = require('cordova/plugin/ContactError'), + utils = require('cordova/utils'); + +/** +* Converts primitives into Complex Object +* Currently only used for Date fields +*/ +function convertIn(contact) { + var value = contact.birthday; + try { + contact.birthday = new Date(parseFloat(value)); + } catch (exception){ + console.log("Cordova Contact convertIn error: exception creating date."); + } + return contact; +} + +/** +* Converts Complex objects into primitives +* Only conversion at present is for Dates. +**/ + +function convertOut(contact) { + var value = contact.birthday; + if (value !== null) { + // try to make it a Date object if it is not already + if (!utils.isDate(value)){ + try { + value = new Date(value); + } catch(exception){ + value = null; + } + } + if (utils.isDate(value)){ + value = value.valueOf(); // convert to milliseconds + } + contact.birthday = value; + } + return contact; +} + +/** +* Contains information about a single contact. +* @constructor +* @param {DOMString} id unique identifier +* @param {DOMString} displayName +* @param {ContactName} name +* @param {DOMString} nickname +* @param {Array.<ContactField>} phoneNumbers array of phone numbers +* @param {Array.<ContactField>} emails array of email addresses +* @param {Array.<ContactAddress>} addresses array of addresses +* @param {Array.<ContactField>} ims instant messaging user ids +* @param {Array.<ContactOrganization>} organizations +* @param {DOMString} birthday contact's birthday +* @param {DOMString} note user notes about contact +* @param {Array.<ContactField>} photos +* @param {Array.<ContactField>} categories +* @param {Array.<ContactField>} urls contact's web sites +*/ +var Contact = function (id, displayName, name, nickname, phoneNumbers, emails, addresses, + ims, organizations, birthday, note, photos, categories, urls) { + this.id = id || null; + this.rawId = null; + this.displayName = displayName || null; + this.name = name || null; // ContactName + this.nickname = nickname || null; + this.phoneNumbers = phoneNumbers || null; // ContactField[] + this.emails = emails || null; // ContactField[] + this.addresses = addresses || null; // ContactAddress[] + this.ims = ims || null; // ContactField[] + this.organizations = organizations || null; // ContactOrganization[] + this.birthday = birthday || null; + this.note = note || null; + this.photos = photos || null; // ContactField[] + this.categories = categories || null; // ContactField[] + this.urls = urls || null; // ContactField[] +}; + +/** +* Removes contact from device storage. +* @param successCB success callback +* @param errorCB error callback +*/ +Contact.prototype.remove = function(successCB, errorCB) { + argscheck.checkArgs('FF', 'Contact.remove', arguments); + var fail = errorCB && function(code) { + errorCB(new ContactError(code)); + }; + if (this.id === null) { + fail(ContactError.UNKNOWN_ERROR); + } + else { + exec(successCB, fail, "Contacts", "remove", [this.id]); + } +}; + +/** +* Creates a deep copy of this Contact. +* With the contact ID set to null. +* @return copy of this Contact +*/ +Contact.prototype.clone = function() { + var clonedContact = utils.clone(this); + clonedContact.id = null; + clonedContact.rawId = null; + + function nullIds(arr) { + if (arr) { + for (var i = 0; i < arr.length; ++i) { + arr[i].id = null; + } + } + } + + // Loop through and clear out any id's in phones, emails, etc. + nullIds(clonedContact.phoneNumbers); + nullIds(clonedContact.emails); + nullIds(clonedContact.addresses); + nullIds(clonedContact.ims); + nullIds(clonedContact.organizations); + nullIds(clonedContact.categories); + nullIds(clonedContact.photos); + nullIds(clonedContact.urls); + return clonedContact; +}; + +/** +* Persists contact to device storage. +* @param successCB success callback +* @param errorCB error callback +*/ +Contact.prototype.save = function(successCB, errorCB) { + argscheck.checkArgs('FFO', 'Contact.save', arguments); + var fail = errorCB && function(code) { + errorCB(new ContactError(code)); + }; + var success = function(result) { + if (result) { + if (successCB) { + var fullContact = require('cordova/plugin/contacts').create(result); + successCB(convertIn(fullContact)); + } + } + else { + // no Entry object returned + fail(ContactError.UNKNOWN_ERROR); + } + }; + var dupContact = convertOut(utils.clone(this)); + exec(success, fail, "Contacts", "save", [dupContact]); +}; + + +module.exports = Contact; + +}); + +// file: lib/common/plugin/ContactAddress.js +define("cordova/plugin/ContactAddress", function(require, exports, module) { + +/** +* Contact address. +* @constructor +* @param {DOMString} id unique identifier, should only be set by native code +* @param formatted // NOTE: not a W3C standard +* @param streetAddress +* @param locality +* @param region +* @param postalCode +* @param country +*/ + +var ContactAddress = function(pref, type, formatted, streetAddress, locality, region, postalCode, country) { + this.id = null; + this.pref = (typeof pref != 'undefined' ? pref : false); + this.type = type || null; + this.formatted = formatted || null; + this.streetAddress = streetAddress || null; + this.locality = locality || null; + this.region = region || null; + this.postalCode = postalCode || null; + this.country = country || null; +}; + +module.exports = ContactAddress; + +}); + +// file: lib/common/plugin/ContactError.js +define("cordova/plugin/ContactError", function(require, exports, module) { + +/** + * ContactError. + * An error code assigned by an implementation when an error has occurred + * @constructor + */ +var ContactError = function(err) { + this.code = (typeof err != 'undefined' ? err : null); +}; + +/** + * Error codes + */ +ContactError.UNKNOWN_ERROR = 0; +ContactError.INVALID_ARGUMENT_ERROR = 1; +ContactError.TIMEOUT_ERROR = 2; +ContactError.PENDING_OPERATION_ERROR = 3; +ContactError.IO_ERROR = 4; +ContactError.NOT_SUPPORTED_ERROR = 5; +ContactError.PERMISSION_DENIED_ERROR = 20; + +module.exports = ContactError; + +}); + +// file: lib/common/plugin/ContactField.js +define("cordova/plugin/ContactField", function(require, exports, module) { + +/** +* Generic contact field. +* @constructor +* @param {DOMString} id unique identifier, should only be set by native code // NOTE: not a W3C standard +* @param type +* @param value +* @param pref +*/ +var ContactField = function(type, value, pref) { + this.id = null; + this.type = (type && type.toString()) || null; + this.value = (value && value.toString()) || null; + this.pref = (typeof pref != 'undefined' ? pref : false); +}; + +module.exports = ContactField; + +}); + +// file: lib/common/plugin/ContactFindOptions.js +define("cordova/plugin/ContactFindOptions", function(require, exports, module) { + +/** + * ContactFindOptions. + * @constructor + * @param filter used to match contacts against + * @param multiple boolean used to determine if more than one contact should be returned + */ + +var ContactFindOptions = function(filter, multiple) { + this.filter = filter || ''; + this.multiple = (typeof multiple != 'undefined' ? multiple : false); +}; + +module.exports = ContactFindOptions; + +}); + +// file: lib/common/plugin/ContactName.js +define("cordova/plugin/ContactName", function(require, exports, module) { + +/** +* Contact name. +* @constructor +* @param formatted // NOTE: not part of W3C standard +* @param familyName +* @param givenName +* @param middle +* @param prefix +* @param suffix +*/ +var ContactName = function(formatted, familyName, givenName, middle, prefix, suffix) { + this.formatted = formatted || null; + this.familyName = familyName || null; + this.givenName = givenName || null; + this.middleName = middle || null; + this.honorificPrefix = prefix || null; + this.honorificSuffix = suffix || null; +}; + +module.exports = ContactName; + +}); + +// file: lib/common/plugin/ContactOrganization.js +define("cordova/plugin/ContactOrganization", function(require, exports, module) { + +/** +* Contact organization. +* @constructor +* @param {DOMString} id unique identifier, should only be set by native code // NOTE: not a W3C standard +* @param name +* @param dept +* @param title +* @param startDate +* @param endDate +* @param location +* @param desc +*/ + +var ContactOrganization = function(pref, type, name, dept, title) { + this.id = null; + this.pref = (typeof pref != 'undefined' ? pref : false); + this.type = type || null; + this.name = name || null; + this.department = dept || null; + this.title = title || null; +}; + +module.exports = ContactOrganization; + +}); + +// file: lib/common/plugin/Coordinates.js +define("cordova/plugin/Coordinates", function(require, exports, module) { + +/** + * This class contains position information. + * @param {Object} lat + * @param {Object} lng + * @param {Object} alt + * @param {Object} acc + * @param {Object} head + * @param {Object} vel + * @param {Object} altacc + * @constructor + */ +var Coordinates = function(lat, lng, alt, acc, head, vel, altacc) { + /** + * The latitude of the position. + */ + this.latitude = lat; + /** + * The longitude of the position, + */ + this.longitude = lng; + /** + * The accuracy of the position. + */ + this.accuracy = acc; + /** + * The altitude of the position. + */ + this.altitude = (alt !== undefined ? alt : null); + /** + * The direction the device is moving at the position. + */ + this.heading = (head !== undefined ? head : null); + /** + * The velocity with which the device is moving at the position. + */ + this.speed = (vel !== undefined ? vel : null); + + if (this.speed === 0 || this.speed === null) { + this.heading = NaN; + } + + /** + * The altitude accuracy of the position. + */ + this.altitudeAccuracy = (altacc !== undefined) ? altacc : null; +}; + +module.exports = Coordinates; + +}); + +// file: lib/common/plugin/DirectoryEntry.js +define("cordova/plugin/DirectoryEntry", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + utils = require('cordova/utils'), + exec = require('cordova/exec'), + Entry = require('cordova/plugin/Entry'), + FileError = require('cordova/plugin/FileError'), + DirectoryReader = require('cordova/plugin/DirectoryReader'); + +/** + * An interface representing a directory on the file system. + * + * {boolean} isFile always false (readonly) + * {boolean} isDirectory always true (readonly) + * {DOMString} name of the directory, excluding the path leading to it (readonly) + * {DOMString} fullPath the absolute full path to the directory (readonly) + * TODO: implement this!!! {FileSystem} filesystem on which the directory resides (readonly) + */ +var DirectoryEntry = function(name, fullPath) { + DirectoryEntry.__super__.constructor.call(this, false, true, name, fullPath); +}; + +utils.extend(DirectoryEntry, Entry); + +/** + * Creates a new DirectoryReader to read entries from this directory + */ +DirectoryEntry.prototype.createReader = function() { + return new DirectoryReader(this.fullPath); +}; + +/** + * Creates or looks up a directory + * + * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory + * @param {Flags} options to create or exclusively create the directory + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.getDirectory = function(path, options, successCallback, errorCallback) { + argscheck.checkArgs('sOFF', 'DirectoryEntry.getDirectory', arguments); + var win = successCallback && function(result) { + var entry = new DirectoryEntry(result.name, result.fullPath); + successCallback(entry); + }; + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "getDirectory", [this.fullPath, path, options]); +}; + +/** + * Deletes a directory and all of it's contents + * + * @param {Function} successCallback is called with no parameters + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.removeRecursively = function(successCallback, errorCallback) { + argscheck.checkArgs('FF', 'DirectoryEntry.removeRecursively', arguments); + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + exec(successCallback, fail, "File", "removeRecursively", [this.fullPath]); +}; + +/** + * Creates or looks up a file + * + * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file + * @param {Flags} options to create or exclusively create the file + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.getFile = function(path, options, successCallback, errorCallback) { + argscheck.checkArgs('sOFF', 'DirectoryEntry.getFile', arguments); + var win = successCallback && function(result) { + var FileEntry = require('cordova/plugin/FileEntry'); + var entry = new FileEntry(result.name, result.fullPath); + successCallback(entry); + }; + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "getFile", [this.fullPath, path, options]); +}; + +module.exports = DirectoryEntry; + +}); + +// file: lib/common/plugin/DirectoryReader.js +define("cordova/plugin/DirectoryReader", function(require, exports, module) { + +var exec = require('cordova/exec'), + FileError = require('cordova/plugin/FileError') ; + +/** + * An interface that lists the files and directories in a directory. + */ +function DirectoryReader(path) { + this.path = path || null; +} + +/** + * Returns a list of entries from a directory. + * + * @param {Function} successCallback is called with a list of entries + * @param {Function} errorCallback is called with a FileError + */ +DirectoryReader.prototype.readEntries = function(successCallback, errorCallback) { + var win = typeof successCallback !== 'function' ? null : function(result) { + var retVal = []; + for (var i=0; i<result.length; i++) { + var entry = null; + if (result[i].isDirectory) { + entry = new (require('cordova/plugin/DirectoryEntry'))(); + } + else if (result[i].isFile) { + entry = new (require('cordova/plugin/FileEntry'))(); + } + entry.isDirectory = result[i].isDirectory; + entry.isFile = result[i].isFile; + entry.name = result[i].name; + entry.fullPath = result[i].fullPath; + retVal.push(entry); + } + successCallback(retVal); + }; + var fail = typeof errorCallback !== 'function' ? null : function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "readEntries", [this.path]); +}; + +module.exports = DirectoryReader; + +}); + +// file: lib/common/plugin/Entry.js +define("cordova/plugin/Entry", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + exec = require('cordova/exec'), + FileError = require('cordova/plugin/FileError'), + Metadata = require('cordova/plugin/Metadata'); + +/** + * Represents a file or directory on the local file system. + * + * @param isFile + * {boolean} true if Entry is a file (readonly) + * @param isDirectory + * {boolean} true if Entry is a directory (readonly) + * @param name + * {DOMString} name of the file or directory, excluding the path + * leading to it (readonly) + * @param fullPath + * {DOMString} the absolute full path to the file or directory + * (readonly) + */ +function Entry(isFile, isDirectory, name, fullPath, fileSystem) { + this.isFile = !!isFile; + this.isDirectory = !!isDirectory; + this.name = name || ''; + this.fullPath = fullPath || ''; + this.filesystem = fileSystem || null; +} + +/** + * Look up the metadata of the entry. + * + * @param successCallback + * {Function} is called with a Metadata object + * @param errorCallback + * {Function} is called with a FileError + */ +Entry.prototype.getMetadata = function(successCallback, errorCallback) { + argscheck.checkArgs('FF', 'Entry.getMetadata', arguments); + var success = successCallback && function(lastModified) { + var metadata = new Metadata(lastModified); + successCallback(metadata); + }; + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + + exec(success, fail, "File", "getMetadata", [this.fullPath]); +}; + +/** + * Set the metadata of the entry. + * + * @param successCallback + * {Function} is called with a Metadata object + * @param errorCallback + * {Function} is called with a FileError + * @param metadataObject + * {Object} keys and values to set + */ +Entry.prototype.setMetadata = function(successCallback, errorCallback, metadataObject) { + argscheck.checkArgs('FFO', 'Entry.setMetadata', arguments); + exec(successCallback, errorCallback, "File", "setMetadata", [this.fullPath, metadataObject]); +}; + +/** + * Move a file or directory to a new location. + * + * @param parent + * {DirectoryEntry} the directory to which to move this entry + * @param newName + * {DOMString} new name of the entry, defaults to the current name + * @param successCallback + * {Function} called with the new DirectoryEntry object + * @param errorCallback + * {Function} called with a FileError + */ +Entry.prototype.moveTo = function(parent, newName, successCallback, errorCallback) { + argscheck.checkArgs('oSFF', 'Entry.moveTo', arguments); + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + // source path + var srcPath = this.fullPath, + // entry name + name = newName || this.name, + success = function(entry) { + if (entry) { + if (successCallback) { + // create appropriate Entry object + var result = (entry.isDirectory) ? new (require('cordova/plugin/DirectoryEntry'))(entry.name, entry.fullPath) : new (require('cordova/plugin/FileEntry'))(entry.name, entry.fullPath); + successCallback(result); + } + } + else { + // no Entry object returned + fail && fail(FileError.NOT_FOUND_ERR); + } + }; + + // copy + exec(success, fail, "File", "moveTo", [srcPath, parent.fullPath, name]); +}; + +/** + * Copy a directory to a different location. + * + * @param parent + * {DirectoryEntry} the directory to which to copy the entry + * @param newName + * {DOMString} new name of the entry, defaults to the current name + * @param successCallback + * {Function} called with the new Entry object + * @param errorCallback + * {Function} called with a FileError + */ +Entry.prototype.copyTo = function(parent, newName, successCallback, errorCallback) { + argscheck.checkArgs('oSFF', 'Entry.copyTo', arguments); + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + + // source path + var srcPath = this.fullPath, + // entry name + name = newName || this.name, + // success callback + success = function(entry) { + if (entry) { + if (successCallback) { + // create appropriate Entry object + var result = (entry.isDirectory) ? new (require('cordova/plugin/DirectoryEntry'))(entry.name, entry.fullPath) : new (require('cordova/plugin/FileEntry'))(entry.name, entry.fullPath); + successCallback(result); + } + } + else { + // no Entry object returned + fail && fail(FileError.NOT_FOUND_ERR); + } + }; + + // copy + exec(success, fail, "File", "copyTo", [srcPath, parent.fullPath, name]); +}; + +/** + * Return a URL that can be used to identify this entry. + */ +Entry.prototype.toURL = function() { + // fullPath attribute contains the full URL + return this.fullPath; +}; + +/** + * Returns a URI that can be used to identify this entry. + * + * @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI. + * @return uri + */ +Entry.prototype.toURI = function(mimeType) { + console.log("DEPRECATED: Update your code to use 'toURL'"); + // fullPath attribute contains the full URI + return this.toURL(); +}; + +/** + * Remove a file or directory. It is an error to attempt to delete a + * directory that is not empty. It is an error to attempt to delete a + * root directory of a file system. + * + * @param successCallback {Function} called with no parameters + * @param errorCallback {Function} called with a FileError + */ +Entry.prototype.remove = function(successCallback, errorCallback) { + argscheck.checkArgs('FF', 'Entry.remove', arguments); + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + exec(successCallback, fail, "File", "remove", [this.fullPath]); +}; + +/** + * Look up the parent DirectoryEntry of this entry. + * + * @param successCallback {Function} called with the parent DirectoryEntry object + * @param errorCallback {Function} called with a FileError + */ +Entry.prototype.getParent = function(successCallback, errorCallback) { + argscheck.checkArgs('FF', 'Entry.getParent', arguments); + var win = successCallback && function(result) { + var DirectoryEntry = require('cordova/plugin/DirectoryEntry'); + var entry = new DirectoryEntry(result.name, result.fullPath); + successCallback(entry); + }; + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "getParent", [this.fullPath]); +}; + +module.exports = Entry; + +}); + +// file: lib/common/plugin/File.js +define("cordova/plugin/File", function(require, exports, module) { + +/** + * Constructor. + * name {DOMString} name of the file, without path information + * fullPath {DOMString} the full path of the file, including the name + * type {DOMString} mime type + * lastModifiedDate {Date} last modified date + * size {Number} size of the file in bytes + */ + +var File = function(name, fullPath, type, lastModifiedDate, size){ + this.name = name || ''; + this.fullPath = fullPath || null; + this.type = type || null; + this.lastModifiedDate = lastModifiedDate || null; + this.size = size || 0; + + // These store the absolute start and end for slicing the file. + this.start = 0; + this.end = this.size; +}; + +/** + * Returns a "slice" of the file. Since Cordova Files don't contain the actual + * content, this really returns a File with adjusted start and end. + * Slices of slices are supported. + * start {Number} The index at which to start the slice (inclusive). + * end {Number} The index at which to end the slice (exclusive). + */ +File.prototype.slice = function(start, end) { + var size = this.end - this.start; + var newStart = 0; + var newEnd = size; + if (arguments.length) { + if (start < 0) { + newStart = Math.max(size + start, 0); + } else { + newStart = Math.min(size, start); + } + } + + if (arguments.length >= 2) { + if (end < 0) { + newEnd = Math.max(size + end, 0); + } else { + newEnd = Math.min(end, size); + } + } + + var newFile = new File(this.name, this.fullPath, this.type, this.lastModifiedData, this.size); + newFile.start = this.start + newStart; + newFile.end = this.start + newEnd; + return newFile; +}; + + +module.exports = File; + +}); + +// file: lib/common/plugin/FileEntry.js +define("cordova/plugin/FileEntry", function(require, exports, module) { + +var utils = require('cordova/utils'), + exec = require('cordova/exec'), + Entry = require('cordova/plugin/Entry'), + FileWriter = require('cordova/plugin/FileWriter'), + File = require('cordova/plugin/File'), + FileError = require('cordova/plugin/FileError'); + +/** + * An interface representing a file on the file system. + * + * {boolean} isFile always true (readonly) + * {boolean} isDirectory always false (readonly) + * {DOMString} name of the file, excluding the path leading to it (readonly) + * {DOMString} fullPath the absolute full path to the file (readonly) + * {FileSystem} filesystem on which the file resides (readonly) + */ +var FileEntry = function(name, fullPath) { + FileEntry.__super__.constructor.apply(this, [true, false, name, fullPath]); +}; + +utils.extend(FileEntry, Entry); + +/** + * Creates a new FileWriter associated with the file that this FileEntry represents. + * + * @param {Function} successCallback is called with the new FileWriter + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.createWriter = function(successCallback, errorCallback) { + this.file(function(filePointer) { + var writer = new FileWriter(filePointer); + + if (writer.fileName === null || writer.fileName === "") { + errorCallback && errorCallback(new FileError(FileError.INVALID_STATE_ERR)); + } else { + successCallback && successCallback(writer); + } + }, errorCallback); +}; + +/** + * Returns a File that represents the current state of the file that this FileEntry represents. + * + * @param {Function} successCallback is called with the new File object + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.file = function(successCallback, errorCallback) { + var win = successCallback && function(f) { + var file = new File(f.name, f.fullPath, f.type, f.lastModifiedDate, f.size); + successCallback(file); + }; + var fail = errorCallback && function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "getFileMetadata", [this.fullPath]); +}; + + +module.exports = FileEntry; + +}); + +// file: lib/common/plugin/FileError.js +define("cordova/plugin/FileError", function(require, exports, module) { + +/** + * FileError + */ +function FileError(error) { + this.code = error || null; +} + +// File error codes +// Found in DOMException +FileError.NOT_FOUND_ERR = 1; +FileError.SECURITY_ERR = 2; +FileError.ABORT_ERR = 3; + +// Added by File API specification +FileError.NOT_READABLE_ERR = 4; +FileError.ENCODING_ERR = 5; +FileError.NO_MODIFICATION_ALLOWED_ERR = 6; +FileError.INVALID_STATE_ERR = 7; +FileError.SYNTAX_ERR = 8; +FileError.INVALID_MODIFICATION_ERR = 9; +FileError.QUOTA_EXCEEDED_ERR = 10; +FileError.TYPE_MISMATCH_ERR = 11; +FileError.PATH_EXISTS_ERR = 12; + +module.exports = FileError; + +}); + +// file: lib/common/plugin/FileReader.js +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 = modulemapper.getOriginalSymbol(this, 'FileReader'); + +/** + * This class reads the mobile device file system. + * + * For Android: + * The root directory is the root of the file system. + * To read from the SD card, the file name is "sdcard/my_file.txt" + * @constructor + */ +var FileReader = function() { + this._readyState = 0; + this._error = null; + this._result = null; + this._fileName = ''; + this._realReader = origFileReader ? new origFileReader() : {}; +}; + +// States +FileReader.EMPTY = 0; +FileReader.LOADING = 1; +FileReader.DONE = 2; + +utils.defineGetter(FileReader.prototype, 'readyState', function() { + return this._fileName ? this._readyState : this._realReader.readyState; +}); + +utils.defineGetter(FileReader.prototype, 'error', function() { + return this._fileName ? this._error: this._realReader.error; +}); + +utils.defineGetter(FileReader.prototype, 'result', function() { + return this._fileName ? this._result: this._realReader.result; +}); + +function defineEvent(eventName) { + utils.defineGetterSetter(FileReader.prototype, eventName, function() { + return this._realReader[eventName] || null; + }, function(value) { + this._realReader[eventName] = value; + }); +} +defineEvent('onloadstart'); // When the read starts. +defineEvent('onprogress'); // While reading (and decoding) file or fileBlob data, and reporting partial file data (progress.loaded/progress.total) +defineEvent('onload'); // When the read has successfully completed. +defineEvent('onerror'); // When the read has failed (see errors). +defineEvent('onloadend'); // When the request has completed (either in success or failure). +defineEvent('onabort'); // When the read has been aborted. For instance, by invoking the abort() method. + +function initRead(reader, file) { + // Already loading something + if (reader.readyState == FileReader.LOADING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + reader._result = null; + reader._error = null; + reader._readyState = FileReader.LOADING; + + if (typeof file == 'string') { + // Deprecated in Cordova 2.4. + console.warning('Using a string argument with FileReader.readAs functions is deprecated.'); + reader._fileName = file; + } else if (typeof file.fullPath == 'string') { + reader._fileName = file.fullPath; + } else { + reader._fileName = ''; + return true; + } + + reader.onloadstart && reader.onloadstart(new ProgressEvent("loadstart", {target:reader})); +} + +/** + * Abort reading file. + */ +FileReader.prototype.abort = function() { + if (origFileReader && !this._fileName) { + return this._realReader.abort(); + } + this._result = null; + + if (this._readyState == FileReader.DONE || this._readyState == FileReader.EMPTY) { + return; + } + + this._readyState = FileReader.DONE; + + // If abort callback + if (typeof this.onabort === 'function') { + this.onabort(new ProgressEvent('abort', {target:this})); + } + // If load end callback + if (typeof this.onloadend === 'function') { + this.onloadend(new ProgressEvent('loadend', {target:this})); + } +}; + +/** + * Read text file. + * + * @param file {File} File object containing file properties + * @param encoding [Optional] (see http://www.iana.org/assignments/character-sets) + */ +FileReader.prototype.readAsText = function(file, encoding) { + if (initRead(this, file)) { + return this._realReader.readAsText(file, encoding); + } + + // Default encoding is UTF-8 + var enc = encoding ? encoding : "UTF-8"; + var me = this; + var execArgs = [this._fileName, enc]; + + // Maybe add slice parameters. + if (file.end < file.size) { + execArgs.push(file.start, file.end); + } else if (file.start > 0) { + execArgs.push(file.start); + } + + // Read file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // Save result + me._result = r; + + // If onload callback + if (typeof me.onload === "function") { + me.onload(new ProgressEvent("load", {target:me})); + } + + // DONE state + me._readyState = FileReader.DONE; + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + // null result + me._result = null; + + // Save error + me._error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, "File", "readAsText", execArgs); +}; + + +/** + * Read file and return data as a base64 encoded data url. + * A data url is of the form: + * data:[<mediatype>][;base64],<data> + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsDataURL = function(file) { + if (initRead(this, file)) { + return this._realReader.readAsDataURL(file); + } + + var me = this; + var execArgs = [this._fileName]; + + // Maybe add slice parameters. + if (file.end < file.size) { + execArgs.push(file.start, file.end); + } else if (file.start > 0) { + execArgs.push(file.start); + } + + // Read file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + // Save result + me._result = r; + + // If onload callback + if (typeof me.onload === "function") { + me.onload(new ProgressEvent("load", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me._readyState === FileReader.DONE) { + return; + } + + // DONE state + me._readyState = FileReader.DONE; + + me._result = null; + + // Save error + me._error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, "File", "readAsDataURL", execArgs); +}; + +/** + * Read file and return data as a binary data. + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsBinaryString = function(file) { + if (initRead(this, file)) { + return this._realReader.readAsBinaryString(file); + } + // TODO - Can't return binary data to browser. + console.log('method "readAsBinaryString" is not supported at this time.'); + this.abort(); +}; + +/** + * Read file and return data as a binary data. + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsArrayBuffer = function(file) { + if (initRead(this, file)) { + return this._realReader.readAsArrayBuffer(file); + } + // TODO - Can't return binary data to browser. + console.log('This method is not supported at this time.'); + this.abort(); +}; + +module.exports = FileReader; + +}); + +// file: lib/common/plugin/FileSystem.js +define("cordova/plugin/FileSystem", function(require, exports, module) { + +var DirectoryEntry = require('cordova/plugin/DirectoryEntry'); + +/** + * An interface representing a file system + * + * @constructor + * {DOMString} name the unique name of the file system (readonly) + * {DirectoryEntry} root directory of the file system (readonly) + */ +var FileSystem = function(name, root) { + this.name = name || null; + if (root) { + this.root = new DirectoryEntry(root.name, root.fullPath); + } +}; + +module.exports = FileSystem; + +}); + +// file: lib/common/plugin/FileTransfer.js +define("cordova/plugin/FileTransfer", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + exec = require('cordova/exec'), + FileTransferError = require('cordova/plugin/FileTransferError'), + ProgressEvent = require('cordova/plugin/ProgressEvent'); + +function newProgressEvent(result) { + var pe = new ProgressEvent(); + pe.lengthComputable = result.lengthComputable; + pe.loaded = result.loaded; + pe.total = result.total; + return pe; +} + +var idCounter = 0; + +/** + * FileTransfer uploads a file to a remote server. + * @constructor + */ +var FileTransfer = function() { + this._id = ++idCounter; + this.onprogress = null; // optional callback +}; + +/** +* Given an absolute file path, uploads a file on the device to a remote server +* using a multipart HTTP request. +* @param filePath {String} Full path of the file on the device +* @param server {String} URL of the server to receive the file +* @param successCallback (Function} Callback to be invoked when upload has completed +* @param errorCallback {Function} Callback to be invoked upon error +* @param options {FileUploadOptions} Optional parameters such as file name and mimetype +* @param trustAllHosts {Boolean} Optional trust all hosts (e.g. for self-signed certs), defaults to false +*/ +FileTransfer.prototype.upload = function(filePath, server, successCallback, errorCallback, options, trustAllHosts) { + argscheck.checkArgs('ssFFO*', 'FileTransfer.upload', arguments); + // check for options + var fileKey = null; + var fileName = null; + var mimeType = null; + var params = null; + var chunkedMode = true; + var headers = null; + if (options) { + fileKey = options.fileKey; + fileName = options.fileName; + mimeType = options.mimeType; + headers = options.headers; + if (options.chunkedMode !== null || typeof options.chunkedMode != "undefined") { + chunkedMode = options.chunkedMode; + } + if (options.params) { + params = options.params; + } + else { + params = {}; + } + } + + var fail = errorCallback && function(e) { + var error = new FileTransferError(e.code, e.source, e.target, e.http_status); + errorCallback(error); + }; + + var self = this; + var win = function(result) { + if (typeof result.lengthComputable != "undefined") { + if (self.onprogress) { + self.onprogress(newProgressEvent(result)); + } + } else { + successCallback && successCallback(result); + } + }; + exec(win, fail, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode, headers, this._id]); +}; + +/** + * Downloads a file form a given URL and saves it to the specified directory. + * @param source {String} URL of the server to receive the file + * @param target {String} Full path of the file on the device + * @param successCallback (Function} Callback to be invoked when upload has completed + * @param errorCallback {Function} Callback to be invoked upon error + * @param trustAllHosts {Boolean} Optional trust all hosts (e.g. for self-signed certs), defaults to false + */ +FileTransfer.prototype.download = function(source, target, successCallback, errorCallback, trustAllHosts) { + argscheck.checkArgs('ssFF*', 'FileTransfer.download', arguments); + var self = this; + var win = function(result) { + if (typeof result.lengthComputable != "undefined") { + if (self.onprogress) { + return self.onprogress(newProgressEvent(result)); + } + } else if (successCallback) { + var entry = null; + if (result.isDirectory) { + entry = new (require('cordova/plugin/DirectoryEntry'))(); + } + else if (result.isFile) { + entry = new (require('cordova/plugin/FileEntry'))(); + } + entry.isDirectory = result.isDirectory; + entry.isFile = result.isFile; + entry.name = result.name; + entry.fullPath = result.fullPath; + successCallback(entry); + } + }; + + var fail = errorCallback && function(e) { + var error = new FileTransferError(e.code, e.source, e.target, e.http_status); + errorCallback(error); + }; + + exec(win, fail, 'FileTransfer', 'download', [source, target, trustAllHosts, this._id]); +}; + +/** + * Aborts the ongoing file transfer on this object + * @param successCallback {Function} Callback to be invoked upon success + * @param errorCallback {Function} Callback to be invoked upon error + */ +FileTransfer.prototype.abort = function(successCallback, errorCallback) { + exec(successCallback, errorCallback, 'FileTransfer', 'abort', [this._id]); +}; + +module.exports = FileTransfer; + +}); + +// file: lib/common/plugin/FileTransferError.js +define("cordova/plugin/FileTransferError", function(require, exports, module) { + +/** + * FileTransferError + * @constructor + */ +var FileTransferError = function(code, source, target, status, body) { + this.code = code || null; + this.source = source || null; + this.target = target || null; + this.http_status = status || null; + this.body = body || null; +}; + +FileTransferError.FILE_NOT_FOUND_ERR = 1; +FileTransferError.INVALID_URL_ERR = 2; +FileTransferError.CONNECTION_ERR = 3; +FileTransferError.ABORT_ERR = 4; + +module.exports = FileTransferError; + +}); + +// file: lib/common/plugin/FileUploadOptions.js +define("cordova/plugin/FileUploadOptions", function(require, exports, module) { + +/** + * Options to customize the HTTP request used to upload files. + * @constructor + * @param fileKey {String} Name of file request parameter. + * @param fileName {String} Filename to be used by the server. Defaults to image.jpg. + * @param mimeType {String} Mimetype of the uploaded file. Defaults to image/jpeg. + * @param params {Object} Object with key: value params to send to the server. + * @param headers {Object} Keys are header names, values are header values. Multiple + * headers of the same name are not supported. + */ +var FileUploadOptions = function(fileKey, fileName, mimeType, params, headers) { + this.fileKey = fileKey || null; + this.fileName = fileName || null; + this.mimeType = mimeType || null; + this.params = params || null; + this.headers = headers || null; +}; + +module.exports = FileUploadOptions; + +}); + +// file: lib/common/plugin/FileUploadResult.js +define("cordova/plugin/FileUploadResult", function(require, exports, module) { + +/** + * FileUploadResult + * @constructor + */ +var FileUploadResult = function() { + this.bytesSent = 0; + this.responseCode = null; + this.response = null; +}; + +module.exports = FileUploadResult; + +}); + +// file: lib/common/plugin/FileWriter.js +define("cordova/plugin/FileWriter", function(require, exports, module) { + +var exec = require('cordova/exec'), + FileError = require('cordova/plugin/FileError'), + ProgressEvent = require('cordova/plugin/ProgressEvent'); + +/** + * This class writes to the mobile device file system. + * + * For Android: + * The root directory is the root of the file system. + * To write to the SD card, the file name is "sdcard/my_file.txt" + * + * @constructor + * @param file {File} File object containing file properties + * @param append if true write to the end of the file, otherwise overwrite the file + */ +var FileWriter = function(file) { + this.fileName = ""; + this.length = 0; + if (file) { + this.fileName = file.fullPath || file; + this.length = file.size || 0; + } + // default is to write at the beginning of the file + this.position = 0; + + this.readyState = 0; // EMPTY + + this.result = null; + + // Error + this.error = null; + + // Event handlers + this.onwritestart = null; // When writing starts + this.onprogress = null; // While writing the file, and reporting partial file data + this.onwrite = null; // When the write has successfully completed. + this.onwriteend = null; // When the request has completed (either in success or failure). + this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method. + this.onerror = null; // When the write has failed (see errors). +}; + +// States +FileWriter.INIT = 0; +FileWriter.WRITING = 1; +FileWriter.DONE = 2; + +/** + * Abort writing file. + */ +FileWriter.prototype.abort = function() { + // check for invalid state + if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + // set error + this.error = new FileError(FileError.ABORT_ERR); + + this.readyState = FileWriter.DONE; + + // If abort callback + if (typeof this.onabort === "function") { + this.onabort(new ProgressEvent("abort", {"target":this})); + } + + // If write end callback + if (typeof this.onwriteend === "function") { + this.onwriteend(new ProgressEvent("writeend", {"target":this})); + } +}; + +/** + * Writes data to the file + * + * @param text to be written + */ +FileWriter.prototype.write = function(text) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + // WRITING state + this.readyState = FileWriter.WRITING; + + var me = this; + + // If onwritestart callback + if (typeof me.onwritestart === "function") { + me.onwritestart(new ProgressEvent("writestart", {"target":me})); + } + + // Write file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // position always increases by bytes written because file would be extended + me.position += r; + // The length of the file is now where we are done writing. + + me.length = me.position; + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwrite callback + if (typeof me.onwrite === "function") { + me.onwrite(new ProgressEvent("write", {"target":me})); + } + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend(new ProgressEvent("writeend", {"target":me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // DONE state + me.readyState = FileWriter.DONE; + + // Save error + me.error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {"target":me})); + } + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend(new ProgressEvent("writeend", {"target":me})); + } + }, "File", "write", [this.fileName, text, this.position]); +}; + +/** + * Moves the file pointer to the location specified. + * + * If the offset is a negative number the position of the file + * pointer is rewound. If the offset is greater than the file + * size the position is set to the end of the file. + * + * @param offset is the location to move the file pointer to. + */ +FileWriter.prototype.seek = function(offset) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + if (!offset && offset !== 0) { + return; + } + + // See back from end of file. + if (offset < 0) { + this.position = Math.max(offset + this.length, 0); + } + // Offset is bigger than file size so set position + // to the end of the file. + else if (offset > this.length) { + this.position = this.length; + } + // Offset is between 0 and file size so set the position + // to start writing. + else { + this.position = offset; + } +}; + +/** + * Truncates the file to the size specified. + * + * @param size to chop the file at. + */ +FileWriter.prototype.truncate = function(size) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + // WRITING state + this.readyState = FileWriter.WRITING; + + var me = this; + + // If onwritestart callback + if (typeof me.onwritestart === "function") { + me.onwritestart(new ProgressEvent("writestart", {"target":this})); + } + + // Write file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // DONE state + me.readyState = FileWriter.DONE; + + // Update the length of the file + me.length = r; + me.position = Math.min(me.position, r); + + // If onwrite callback + if (typeof me.onwrite === "function") { + me.onwrite(new ProgressEvent("write", {"target":me})); + } + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend(new ProgressEvent("writeend", {"target":me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // DONE state + me.readyState = FileWriter.DONE; + + // Save error + me.error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {"target":me})); + } + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend(new ProgressEvent("writeend", {"target":me})); + } + }, "File", "truncate", [this.fileName, size]); +}; + +module.exports = FileWriter; + +}); + +// file: lib/common/plugin/Flags.js +define("cordova/plugin/Flags", function(require, exports, module) { + +/** + * Supplies arguments to methods that lookup or create files and directories. + * + * @param create + * {boolean} file or directory if it doesn't exist + * @param exclusive + * {boolean} used with create; if true the command will fail if + * target path exists + */ +function Flags(create, exclusive) { + this.create = create || false; + this.exclusive = exclusive || false; +} + +module.exports = Flags; + +}); + +// file: lib/common/plugin/GlobalizationError.js +define("cordova/plugin/GlobalizationError", function(require, exports, module) { + + +/** + * Globalization error object + * + * @constructor + * @param code + * @param message + */ +var GlobalizationError = function(code, message) { + this.code = code || null; + this.message = message || ''; +}; + +// Globalization error codes +GlobalizationError.UNKNOWN_ERROR = 0; +GlobalizationError.FORMATTING_ERROR = 1; +GlobalizationError.PARSING_ERROR = 2; +GlobalizationError.PATTERN_ERROR = 3; + +module.exports = GlobalizationError; + +}); + +// file: lib/common/plugin/InAppBrowser.js +define("cordova/plugin/InAppBrowser", function(require, exports, module) { + +var exec = require('cordova/exec'); +var channel = require('cordova/channel'); + +function InAppBrowser() { + this.channels = { + 'loadstart': channel.create('loadstart'), + 'loadstop' : channel.create('loadstop'), + 'exit' : channel.create('exit') + }; +} + +InAppBrowser.prototype = { + _eventHandler: function (event) { + if (event.type in this.channels) { + this.channels[event.type].fire(event); + } + }, + close: function (eventname) { + exec(null, null, "InAppBrowser", "close", []); + }, + addEventListener: function (eventname,f) { + if (eventname in this.channels) { + this.channels[eventname].subscribe(f); + } + }, + removeEventListener: function(eventname, f) { + if (eventname in this.channels) { + this.channels[eventname].unsubscribe(f); + } + } +}; + +module.exports = function(strUrl, strWindowName, strWindowFeatures) { + var iab = new InAppBrowser(); + var cb = function(eventname) { + iab._eventHandler(eventname); + }; + exec(cb, null, "InAppBrowser", "open", [strUrl, strWindowName, strWindowFeatures]); + return iab; +}; + + +}); + +// file: lib/common/plugin/LocalFileSystem.js +define("cordova/plugin/LocalFileSystem", function(require, exports, module) { + +var exec = require('cordova/exec'); + +/** + * Represents a local file system. + */ +var LocalFileSystem = function() { + +}; + +LocalFileSystem.TEMPORARY = 0; //temporary, with no guarantee of persistence +LocalFileSystem.PERSISTENT = 1; //persistent + +module.exports = LocalFileSystem; + +}); + +// file: lib/common/plugin/Media.js +define("cordova/plugin/Media", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + utils = require('cordova/utils'), + exec = require('cordova/exec'); + +var mediaObjects = {}; + +/** + * This class provides access to the device media, interfaces to both sound and video + * + * @constructor + * @param src The file name or url to play + * @param successCallback The callback to be called when the file is done playing or recording. + * successCallback() + * @param errorCallback The callback to be called if there is an error. + * errorCallback(int errorCode) - OPTIONAL + * @param statusCallback The callback to be called when media status has changed. + * statusCallback(int statusCode) - OPTIONAL + */ +var Media = function(src, successCallback, errorCallback, statusCallback) { + argscheck.checkArgs('SFFF', 'Media', arguments); + this.id = utils.createUUID(); + mediaObjects[this.id] = this; + this.src = src; + this.successCallback = successCallback; + this.errorCallback = errorCallback; + this.statusCallback = statusCallback; + this._duration = -1; + this._position = -1; + exec(null, this.errorCallback, "Media", "create", [this.id, this.src]); +}; + +// Media messages +Media.MEDIA_STATE = 1; +Media.MEDIA_DURATION = 2; +Media.MEDIA_POSITION = 3; +Media.MEDIA_ERROR = 9; + +// Media states +Media.MEDIA_NONE = 0; +Media.MEDIA_STARTING = 1; +Media.MEDIA_RUNNING = 2; +Media.MEDIA_PAUSED = 3; +Media.MEDIA_STOPPED = 4; +Media.MEDIA_MSG = ["None", "Starting", "Running", "Paused", "Stopped"]; + +// "static" function to return existing objs. +Media.get = function(id) { + return mediaObjects[id]; +}; + +/** + * Start or resume playing audio file. + */ +Media.prototype.play = function(options) { + exec(null, null, "Media", "startPlayingAudio", [this.id, this.src, options]); +}; + +/** + * Stop playing audio file. + */ +Media.prototype.stop = function() { + var me = this; + exec(function() { + me._position = 0; + }, this.errorCallback, "Media", "stopPlayingAudio", [this.id]); +}; + +/** + * Seek or jump to a new time in the track.. + */ +Media.prototype.seekTo = function(milliseconds) { + var me = this; + exec(function(p) { + me._position = p; + }, this.errorCallback, "Media", "seekToAudio", [this.id, milliseconds]); +}; + +/** + * Pause playing audio file. + */ +Media.prototype.pause = function() { + exec(null, this.errorCallback, "Media", "pausePlayingAudio", [this.id]); +}; + +/** + * Get duration of an audio file. + * The duration is only set for audio that is playing, paused or stopped. + * + * @return duration or -1 if not known. + */ +Media.prototype.getDuration = function() { + return this._duration; +}; + +/** + * Get position of audio. + */ +Media.prototype.getCurrentPosition = function(success, fail) { + var me = this; + exec(function(p) { + me._position = p; + success(p); + }, fail, "Media", "getCurrentPositionAudio", [this.id]); +}; + +/** + * Start recording audio file. + */ +Media.prototype.startRecord = function() { + exec(null, this.errorCallback, "Media", "startRecordingAudio", [this.id, this.src]); +}; + +/** + * Stop recording audio file. + */ +Media.prototype.stopRecord = function() { + exec(null, this.errorCallback, "Media", "stopRecordingAudio", [this.id]); +}; + +/** + * Release the resources. + */ +Media.prototype.release = function() { + exec(null, this.errorCallback, "Media", "release", [this.id]); +}; + +/** + * Adjust the volume. + */ +Media.prototype.setVolume = function(volume) { + exec(null, null, "Media", "setVolume", [this.id, volume]); +}; + +/** + * Audio has status update. + * PRIVATE + * + * @param id The media object id (string) + * @param msgType The 'type' of update this is + * @param value Use of value is determined by the msgType + */ +Media.onStatus = function(id, msgType, value) { + + var media = mediaObjects[id]; + + if(media) { + switch(msgType) { + case Media.MEDIA_STATE : + media.statusCallback && media.statusCallback(value); + if(value == Media.MEDIA_STOPPED) { + media.successCallback && media.successCallback(); + } + break; + case Media.MEDIA_DURATION : + media._duration = value; + break; + case Media.MEDIA_ERROR : + media.errorCallback && media.errorCallback(value); + break; + case Media.MEDIA_POSITION : + media._position = Number(value); + break; + default : + console.error && console.error("Unhandled Media.onStatus :: " + msgType); + break; + } + } + else { + console.error && console.error("Received Media.onStatus callback for unknown media :: " + id); + } + +}; + +module.exports = Media; + +}); + +// file: lib/common/plugin/MediaError.js +define("cordova/plugin/MediaError", function(require, exports, module) { + +/** + * This class contains information about any Media errors. +*/ +/* + According to :: http://dev.w3.org/html5/spec-author-view/video.html#mediaerror + We should never be creating these objects, we should just implement the interface + which has 1 property for an instance, 'code' + + instead of doing : + errorCallbackFunction( new MediaError(3,'msg') ); +we should simply use a literal : + errorCallbackFunction( {'code':3} ); + */ + + var _MediaError = window.MediaError; + + +if(!_MediaError) { + window.MediaError = _MediaError = function(code, msg) { + this.code = (typeof code != 'undefined') ? code : null; + this.message = msg || ""; // message is NON-standard! do not use! + }; +} + +_MediaError.MEDIA_ERR_NONE_ACTIVE = _MediaError.MEDIA_ERR_NONE_ACTIVE || 0; +_MediaError.MEDIA_ERR_ABORTED = _MediaError.MEDIA_ERR_ABORTED || 1; +_MediaError.MEDIA_ERR_NETWORK = _MediaError.MEDIA_ERR_NETWORK || 2; +_MediaError.MEDIA_ERR_DECODE = _MediaError.MEDIA_ERR_DECODE || 3; +_MediaError.MEDIA_ERR_NONE_SUPPORTED = _MediaError.MEDIA_ERR_NONE_SUPPORTED || 4; +// TODO: MediaError.MEDIA_ERR_NONE_SUPPORTED is legacy, the W3 spec now defines it as below. +// as defined by http://dev.w3.org/html5/spec-author-view/video.html#error-codes +_MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = _MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED || 4; + +module.exports = _MediaError; + +}); + +// file: lib/common/plugin/MediaFile.js +define("cordova/plugin/MediaFile", function(require, exports, module) { + +var utils = require('cordova/utils'), + exec = require('cordova/exec'), + File = require('cordova/plugin/File'), + CaptureError = require('cordova/plugin/CaptureError'); +/** + * Represents a single file. + * + * name {DOMString} name of the file, without path information + * fullPath {DOMString} the full path of the file, including the name + * type {DOMString} mime type + * lastModifiedDate {Date} last modified date + * size {Number} size of the file in bytes + */ +var MediaFile = function(name, fullPath, type, lastModifiedDate, size){ + MediaFile.__super__.constructor.apply(this, arguments); +}; + +utils.extend(MediaFile, File); + +/** + * Request capture format data for a specific file and type + * + * @param {Function} successCB + * @param {Function} errorCB + */ +MediaFile.prototype.getFormatData = function(successCallback, errorCallback) { + if (typeof this.fullPath === "undefined" || this.fullPath === null) { + errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT)); + } else { + exec(successCallback, errorCallback, "Capture", "getFormatData", [this.fullPath, this.type]); + } +}; + +module.exports = MediaFile; + +}); + +// file: lib/common/plugin/MediaFileData.js +define("cordova/plugin/MediaFileData", function(require, exports, module) { + +/** + * MediaFileData encapsulates format information of a media file. + * + * @param {DOMString} codecs + * @param {long} bitrate + * @param {long} height + * @param {long} width + * @param {float} duration + */ +var MediaFileData = function(codecs, bitrate, height, width, duration){ + this.codecs = codecs || null; + this.bitrate = bitrate || 0; + this.height = height || 0; + this.width = width || 0; + this.duration = duration || 0; +}; + +module.exports = MediaFileData; + +}); + +// file: lib/common/plugin/Metadata.js +define("cordova/plugin/Metadata", function(require, exports, module) { + +/** + * Information about the state of the file or directory + * + * {Date} modificationTime (readonly) + */ +var Metadata = function(time) { + this.modificationTime = (typeof time != 'undefined'?new Date(time):null); +}; + +module.exports = Metadata; + +}); + +// file: lib/common/plugin/Position.js +define("cordova/plugin/Position", function(require, exports, module) { + +var Coordinates = require('cordova/plugin/Coordinates'); + +var Position = function(coords, timestamp) { + if (coords) { + this.coords = new Coordinates(coords.latitude, coords.longitude, coords.altitude, coords.accuracy, coords.heading, coords.velocity, coords.altitudeAccuracy); + } else { + this.coords = new Coordinates(); + } + this.timestamp = (timestamp !== undefined) ? timestamp : new Date(); +}; + +module.exports = Position; + +}); + +// file: lib/common/plugin/PositionError.js +define("cordova/plugin/PositionError", function(require, exports, module) { + +/** + * Position error object + * + * @constructor + * @param code + * @param message + */ +var PositionError = function(code, message) { + this.code = code || null; + this.message = message || ''; +}; + +PositionError.PERMISSION_DENIED = 1; +PositionError.POSITION_UNAVAILABLE = 2; +PositionError.TIMEOUT = 3; + +module.exports = PositionError; + +}); + +// file: lib/common/plugin/ProgressEvent.js +define("cordova/plugin/ProgressEvent", function(require, exports, module) { + +// If ProgressEvent exists in global context, use it already, otherwise use our own polyfill +// Feature test: See if we can instantiate a native ProgressEvent; +// if so, use that approach, +// otherwise fill-in with our own implementation. +// +// NOTE: right now we always fill in with our own. Down the road would be nice if we can use whatever is native in the webview. +var ProgressEvent = (function() { + /* + var createEvent = function(data) { + var event = document.createEvent('Events'); + event.initEvent('ProgressEvent', false, false); + if (data) { + for (var i in data) { + if (data.hasOwnProperty(i)) { + event[i] = data[i]; + } + } + if (data.target) { + // TODO: cannot call <some_custom_object>.dispatchEvent + // need to first figure out how to implement EventTarget + } + } + return event; + }; + try { + var ev = createEvent({type:"abort",target:document}); + return function ProgressEvent(type, data) { + data.type = type; + return createEvent(data); + }; + } catch(e){ + */ + return function ProgressEvent(type, dict) { + this.type = type; + this.bubbles = false; + this.cancelBubble = false; + this.cancelable = false; + this.lengthComputable = false; + this.loaded = dict && dict.loaded ? dict.loaded : 0; + this.total = dict && dict.total ? dict.total : 0; + this.target = dict && dict.target ? dict.target : null; + }; + //} +})(); + +module.exports = ProgressEvent; + +}); + +// file: lib/common/plugin/accelerometer.js +define("cordova/plugin/accelerometer", function(require, exports, module) { + +/** + * This class provides access to device accelerometer data. + * @constructor + */ +var argscheck = require('cordova/argscheck'), + utils = require("cordova/utils"), + exec = require("cordova/exec"), + Acceleration = require('cordova/plugin/Acceleration'); + +// Is the accel sensor running? +var running = false; + +// Keeps reference to watchAcceleration calls. +var timers = {}; + +// Array of listeners; used to keep track of when we should call start and stop. +var listeners = []; + +// Last returned acceleration object from native +var accel = null; + +// Tells native to start. +function start() { + exec(function(a) { + var tempListeners = listeners.slice(0); + accel = new Acceleration(a.x, a.y, a.z, a.timestamp); + for (var i = 0, l = tempListeners.length; i < l; i++) { + tempListeners[i].win(accel); + } + }, function(e) { + var tempListeners = listeners.slice(0); + for (var i = 0, l = tempListeners.length; i < l; i++) { + tempListeners[i].fail(e); + } + }, "Accelerometer", "start", []); + running = true; +} + +// Tells native to stop. +function stop() { + exec(null, null, "Accelerometer", "stop", []); + running = false; +} + +// Adds a callback pair to the listeners array +function createCallbackPair(win, fail) { + return {win:win, fail:fail}; +} + +// Removes a win/fail listener pair from the listeners array +function removeListeners(l) { + var idx = listeners.indexOf(l); + if (idx > -1) { + listeners.splice(idx, 1); + if (listeners.length === 0) { + stop(); + } + } +} + +var accelerometer = { + /** + * Asynchronously acquires the current acceleration. + * + * @param {Function} successCallback The function to call when the acceleration data is available + * @param {Function} errorCallback The function to call when there is an error getting the acceleration data. (OPTIONAL) + * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. (OPTIONAL) + */ + getCurrentAcceleration: function(successCallback, errorCallback, options) { + argscheck.checkArgs('fFO', 'accelerometer.getCurrentAcceleration', arguments); + + var p; + var win = function(a) { + removeListeners(p); + successCallback(a); + }; + var fail = function(e) { + removeListeners(p); + errorCallback && errorCallback(e); + }; + + p = createCallbackPair(win, fail); + listeners.push(p); + + if (!running) { + start(); + } + }, + + /** + * Asynchronously acquires the acceleration repeatedly at a given interval. + * + * @param {Function} successCallback The function to call each time the acceleration data is available + * @param {Function} errorCallback The function to call when there is an error getting the acceleration data. (OPTIONAL) + * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. (OPTIONAL) + * @return String The watch id that must be passed to #clearWatch to stop watching. + */ + watchAcceleration: function(successCallback, errorCallback, options) { + argscheck.checkArgs('fFO', 'accelerometer.watchAcceleration', arguments); + // Default interval (10 sec) + var frequency = (options && options.frequency && typeof options.frequency == 'number') ? options.frequency : 10000; + + // Keep reference to watch id, and report accel readings as often as defined in frequency + var id = utils.createUUID(); + + var p = createCallbackPair(function(){}, function(e) { + removeListeners(p); + errorCallback && errorCallback(e); + }); + listeners.push(p); + + timers[id] = { + timer:window.setInterval(function() { + if (accel) { + successCallback(accel); + } + }, frequency), + listeners:p + }; + + if (running) { + // If we're already running then immediately invoke the success callback + // but only if we have retrieved a value, sample code does not check for null ... + if (accel) { + successCallback(accel); + } + } else { + start(); + } + + return id; + }, + + /** + * Clears the specified accelerometer watch. + * + * @param {String} id The id of the watch returned from #watchAcceleration. + */ + clearWatch: function(id) { + // Stop javascript timer & remove from timer list + if (id && timers[id]) { + window.clearInterval(timers[id].timer); + removeListeners(timers[id].listeners); + delete timers[id]; + } + } +}; + +module.exports = accelerometer; + +}); + +// file: lib/common/plugin/accelerometer/symbols.js +define("cordova/plugin/accelerometer/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.defaults('cordova/plugin/Acceleration', 'Acceleration'); +modulemapper.defaults('cordova/plugin/accelerometer', 'navigator.accelerometer'); + +}); + +// file: lib/common/plugin/battery.js +define("cordova/plugin/battery", function(require, exports, module) { + +/** + * This class contains information about the current battery status. + * @constructor + */ +var cordova = require('cordova'), + exec = require('cordova/exec'); + +function handlers() { + return battery.channels.batterystatus.numHandlers + + battery.channels.batterylow.numHandlers + + battery.channels.batterycritical.numHandlers; +} + +var Battery = function() { + this._level = null; + this._isPlugged = null; + // Create new event handlers on the window (returns a channel instance) + this.channels = { + batterystatus:cordova.addWindowEventHandler("batterystatus"), + batterylow:cordova.addWindowEventHandler("batterylow"), + batterycritical:cordova.addWindowEventHandler("batterycritical") + }; + for (var key in this.channels) { + this.channels[key].onHasSubscribersChange = Battery.onHasSubscribersChange; + } +}; +/** + * Event handlers for when callbacks get registered for the battery. + * Keep track of how many handlers we have so we can start and stop the native battery listener + * appropriately (and hopefully save on battery life!). + */ +Battery.onHasSubscribersChange = function() { + // If we just registered the first handler, make sure native listener is started. + if (this.numHandlers === 1 && handlers() === 1) { + exec(battery._status, battery._error, "Battery", "start", []); + } else if (handlers() === 0) { + exec(null, null, "Battery", "stop", []); + } +}; + +/** + * Callback for battery status + * + * @param {Object} info keys: level, isPlugged + */ +Battery.prototype._status = function(info) { + if (info) { + var me = battery; + var level = info.level; + if (me._level !== level || me._isPlugged !== info.isPlugged) { + // Fire batterystatus event + cordova.fireWindowEvent("batterystatus", info); + + // Fire low battery event + if (level === 20 || level === 5) { + if (level === 20) { + cordova.fireWindowEvent("batterylow", info); + } + else { + cordova.fireWindowEvent("batterycritical", info); + } + } + } + me._level = level; + me._isPlugged = info.isPlugged; + } +}; + +/** + * Error callback for battery start + */ +Battery.prototype._error = function(e) { + console.log("Error initializing Battery: " + e); +}; + +var battery = new Battery(); + +module.exports = battery; + +}); + +// file: lib/common/plugin/battery/symbols.js +define("cordova/plugin/battery/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.defaults('cordova/plugin/battery', 'navigator.battery'); + +}); + +// file: lib/common/plugin/camera/symbols.js +define("cordova/plugin/camera/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.defaults('cordova/plugin/Camera', 'navigator.camera'); +modulemapper.defaults('cordova/plugin/CameraConstants', 'Camera'); +modulemapper.defaults('cordova/plugin/CameraPopoverOptions', 'CameraPopoverOptions'); + +}); + +// file: lib/common/plugin/capture.js +define("cordova/plugin/capture", function(require, exports, module) { + +var exec = require('cordova/exec'), + MediaFile = require('cordova/plugin/MediaFile'); + +/** + * Launches a capture of different types. + * + * @param (DOMString} type + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureVideoOptions} options + */ +function _capture(type, successCallback, errorCallback, options) { + var win = function(pluginResult) { + var mediaFiles = []; + var i; + for (i = 0; i < pluginResult.length; i++) { + var mediaFile = new MediaFile(); + mediaFile.name = pluginResult[i].name; + mediaFile.fullPath = pluginResult[i].fullPath; + mediaFile.type = pluginResult[i].type; + mediaFile.lastModifiedDate = pluginResult[i].lastModifiedDate; + mediaFile.size = pluginResult[i].size; + mediaFiles.push(mediaFile); + } + successCallback(mediaFiles); + }; + exec(win, errorCallback, "Capture", type, [options]); +} +/** + * The Capture interface exposes an interface to the camera and microphone of the hosting device. + */ +function Capture() { + this.supportedAudioModes = []; + this.supportedImageModes = []; + this.supportedVideoModes = []; +} + +/** + * Launch audio recorder application for recording audio clip(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureAudioOptions} options + */ +Capture.prototype.captureAudio = function(successCallback, errorCallback, options){ + _capture("captureAudio", successCallback, errorCallback, options); +}; + +/** + * Launch camera application for taking image(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureImageOptions} options + */ +Capture.prototype.captureImage = function(successCallback, errorCallback, options){ + _capture("captureImage", successCallback, errorCallback, options); +}; + +/** + * Launch device camera application for recording video(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureVideoOptions} options + */ +Capture.prototype.captureVideo = function(successCallback, errorCallback, options){ + _capture("captureVideo", successCallback, errorCallback, options); +}; + + +module.exports = new Capture(); + +}); + +// file: lib/common/plugin/capture/symbols.js +define("cordova/plugin/capture/symbols", function(require, exports, module) { + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/CaptureError', 'CaptureError'); +modulemapper.clobbers('cordova/plugin/CaptureAudioOptions', 'CaptureAudioOptions'); +modulemapper.clobbers('cordova/plugin/CaptureImageOptions', 'CaptureImageOptions'); +modulemapper.clobbers('cordova/plugin/CaptureVideoOptions', 'CaptureVideoOptions'); +modulemapper.clobbers('cordova/plugin/ConfigurationData', 'ConfigurationData'); +modulemapper.clobbers('cordova/plugin/MediaFile', 'MediaFile'); +modulemapper.clobbers('cordova/plugin/MediaFileData', 'MediaFileData'); +modulemapper.clobbers('cordova/plugin/capture', 'navigator.device.capture'); + +}); + +// file: lib/common/plugin/compass.js +define("cordova/plugin/compass", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + exec = require('cordova/exec'), + utils = require('cordova/utils'), + CompassHeading = require('cordova/plugin/CompassHeading'), + CompassError = require('cordova/plugin/CompassError'), + timers = {}, + compass = { + /** + * Asynchronously acquires the current heading. + * @param {Function} successCallback The function to call when the heading + * data is available + * @param {Function} errorCallback The function to call when there is an error + * getting the heading data. + * @param {CompassOptions} options The options for getting the heading data (not used). + */ + getCurrentHeading:function(successCallback, errorCallback, options) { + argscheck.checkArgs('fFO', 'compass.getCurrentHeading', arguments); + + var win = function(result) { + var ch = new CompassHeading(result.magneticHeading, result.trueHeading, result.headingAccuracy, result.timestamp); + successCallback(ch); + }; + var fail = errorCallback && function(code) { + var ce = new CompassError(code); + errorCallback(ce); + }; + + // Get heading + exec(win, fail, "Compass", "getHeading", [options]); + }, + + /** + * Asynchronously acquires the heading repeatedly at a given interval. + * @param {Function} successCallback The function to call each time the heading + * data is available + * @param {Function} errorCallback The function to call when there is an error + * getting the heading data. + * @param {HeadingOptions} options The options for getting the heading data + * such as timeout and the frequency of the watch. For iOS, filter parameter + * specifies to watch via a distance filter rather than time. + */ + watchHeading:function(successCallback, errorCallback, options) { + argscheck.checkArgs('fFO', 'compass.watchHeading', arguments); + // Default interval (100 msec) + var frequency = (options !== undefined && options.frequency !== undefined) ? options.frequency : 100; + var filter = (options !== undefined && options.filter !== undefined) ? options.filter : 0; + + var id = utils.createUUID(); + if (filter > 0) { + // is an iOS request for watch by filter, no timer needed + timers[id] = "iOS"; + compass.getCurrentHeading(successCallback, errorCallback, options); + } else { + // Start watch timer to get headings + timers[id] = window.setInterval(function() { + compass.getCurrentHeading(successCallback, errorCallback); + }, frequency); + } + + return id; + }, + + /** + * Clears the specified heading watch. + * @param {String} watchId The ID of the watch returned from #watchHeading. + */ + clearWatch:function(id) { + // Stop javascript timer & remove from timer list + if (id && timers[id]) { + if (timers[id] != "iOS") { + clearInterval(timers[id]); + } else { + // is iOS watch by filter so call into device to stop + exec(null, null, "Compass", "stopHeading", []); + } + delete timers[id]; + } + } + }; + +module.exports = compass; + +}); + +// file: lib/common/plugin/compass/symbols.js +define("cordova/plugin/compass/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/CompassHeading', 'CompassHeading'); +modulemapper.clobbers('cordova/plugin/CompassError', 'CompassError'); +modulemapper.clobbers('cordova/plugin/compass', 'navigator.compass'); + +}); + +// file: lib/common/plugin/console-via-logger.js +define("cordova/plugin/console-via-logger", function(require, exports, module) { + +//------------------------------------------------------------------------------ + +var logger = require("cordova/plugin/logger"); +var utils = require("cordova/utils"); + +//------------------------------------------------------------------------------ +// object that we're exporting +//------------------------------------------------------------------------------ +var console = module.exports; + +//------------------------------------------------------------------------------ +// copy of the original console object +//------------------------------------------------------------------------------ +var WinConsole = window.console; + +//------------------------------------------------------------------------------ +// whether to use the logger +//------------------------------------------------------------------------------ +var UseLogger = false; + +//------------------------------------------------------------------------------ +// Timers +//------------------------------------------------------------------------------ +var Timers = {}; + +//------------------------------------------------------------------------------ +// used for unimplemented methods +//------------------------------------------------------------------------------ +function noop() {} + +//------------------------------------------------------------------------------ +// used for unimplemented methods +//------------------------------------------------------------------------------ +console.useLogger = function (value) { + if (arguments.length) UseLogger = !!value; + + if (UseLogger) { + if (logger.useConsole()) { + throw new Error("console and logger are too intertwingly"); + } + } + + return UseLogger; +}; + +//------------------------------------------------------------------------------ +console.log = function() { + if (logger.useConsole()) return; + logger.log.apply(logger, [].slice.call(arguments)); +}; + +//------------------------------------------------------------------------------ +console.error = function() { + if (logger.useConsole()) return; + logger.error.apply(logger, [].slice.call(arguments)); +}; + +//------------------------------------------------------------------------------ +console.warn = function() { + if (logger.useConsole()) return; + logger.warn.apply(logger, [].slice.call(arguments)); +}; + +//------------------------------------------------------------------------------ +console.info = function() { + if (logger.useConsole()) return; + logger.info.apply(logger, [].slice.call(arguments)); +}; + +//------------------------------------------------------------------------------ +console.debug = function() { + if (logger.useConsole()) return; + logger.debug.apply(logger, [].slice.call(arguments)); +}; + +//------------------------------------------------------------------------------ +console.assert = function(expression) { + if (expression) return; + + var message = utils.vformat(arguments[1], [].slice.call(arguments, 2)); + console.log("ASSERT: " + message); +}; + +//------------------------------------------------------------------------------ +console.clear = function() {}; + +//------------------------------------------------------------------------------ +console.dir = function(object) { + console.log("%o", object); +}; + +//------------------------------------------------------------------------------ +console.dirxml = function(node) { + console.log(node.innerHTML); +}; + +//------------------------------------------------------------------------------ +console.trace = noop; + +//------------------------------------------------------------------------------ +console.group = console.log; + +//------------------------------------------------------------------------------ +console.groupCollapsed = console.log; + +//------------------------------------------------------------------------------ +console.groupEnd = noop; + +//------------------------------------------------------------------------------ +console.time = function(name) { + Timers[name] = new Date().valueOf(); +}; + +//------------------------------------------------------------------------------ +console.timeEnd = function(name) { + var timeStart = Timers[name]; + if (!timeStart) { + console.warn("unknown timer: " + name); + return; + } + + var timeElapsed = new Date().valueOf() - timeStart; + console.log(name + ": " + timeElapsed + "ms"); +}; + +//------------------------------------------------------------------------------ +console.timeStamp = noop; + +//------------------------------------------------------------------------------ +console.profile = noop; + +//------------------------------------------------------------------------------ +console.profileEnd = noop; + +//------------------------------------------------------------------------------ +console.count = noop; + +//------------------------------------------------------------------------------ +console.exception = console.log; + +//------------------------------------------------------------------------------ +console.table = function(data, columns) { + console.log("%o", data); +}; + +//------------------------------------------------------------------------------ +// return a new function that calls both functions passed as args +//------------------------------------------------------------------------------ +function wrappedOrigCall(orgFunc, newFunc) { + return function() { + var args = [].slice.call(arguments); + try { orgFunc.apply(WinConsole, args); } catch (e) {} + try { newFunc.apply(console, args); } catch (e) {} + }; +} + +//------------------------------------------------------------------------------ +// For every function that exists in the original console object, that +// also exists in the new console object, wrap the new console method +// with one that calls both +//------------------------------------------------------------------------------ +for (var key in console) { + if (typeof WinConsole[key] == "function") { + console[key] = wrappedOrigCall(WinConsole[key], console[key]); + } +} + +}); + +// file: lib/common/plugin/contacts.js +define("cordova/plugin/contacts", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + exec = require('cordova/exec'), + ContactError = require('cordova/plugin/ContactError'), + utils = require('cordova/utils'), + Contact = require('cordova/plugin/Contact'); + +/** +* Represents a group of Contacts. +* @constructor +*/ +var contacts = { + /** + * Returns an array of Contacts matching the search criteria. + * @param fields that should be searched + * @param successCB success callback + * @param errorCB error callback + * @param {ContactFindOptions} options that can be applied to contact searching + * @return array of Contacts matching search criteria + */ + find:function(fields, successCB, errorCB, options) { + argscheck.checkArgs('afFO', 'contacts.find', arguments); + if (!fields.length) { + errorCB && errorCB(new ContactError(ContactError.INVALID_ARGUMENT_ERROR)); + } else { + var win = function(result) { + var cs = []; + for (var i = 0, l = result.length; i < l; i++) { + cs.push(contacts.create(result[i])); + } + successCB(cs); + }; + exec(win, errorCB, "Contacts", "search", [fields, options]); + } + }, + + /** + * This function creates a new contact, but it does not persist the contact + * to device storage. To persist the contact to device storage, invoke + * contact.save(). + * @param properties an object whose properties will be examined to create a new Contact + * @returns new Contact object + */ + create:function(properties) { + argscheck.checkArgs('O', 'contacts.create', arguments); + var contact = new Contact(); + for (var i in properties) { + if (typeof contact[i] !== 'undefined' && properties.hasOwnProperty(i)) { + contact[i] = properties[i]; + } + } + return contact; + } +}; + +module.exports = contacts; + +}); + +// file: lib/common/plugin/contacts/symbols.js +define("cordova/plugin/contacts/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/contacts', 'navigator.contacts'); +modulemapper.clobbers('cordova/plugin/Contact', 'Contact'); +modulemapper.clobbers('cordova/plugin/ContactAddress', 'ContactAddress'); +modulemapper.clobbers('cordova/plugin/ContactError', 'ContactError'); +modulemapper.clobbers('cordova/plugin/ContactField', 'ContactField'); +modulemapper.clobbers('cordova/plugin/ContactFindOptions', 'ContactFindOptions'); +modulemapper.clobbers('cordova/plugin/ContactName', 'ContactName'); +modulemapper.clobbers('cordova/plugin/ContactOrganization', 'ContactOrganization'); + +}); + +// file: lib/common/plugin/device.js +define("cordova/plugin/device", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + channel = require('cordova/channel'), + utils = require('cordova/utils'), + exec = require('cordova/exec'); + +// Tell cordova channel to wait on the CordovaInfoReady event +channel.waitForInitialization('onCordovaInfoReady'); + +/** + * This represents the mobile device, and provides properties for inspecting the model, version, UUID of the + * phone, etc. + * @constructor + */ +function Device() { + this.available = false; + this.platform = null; + this.version = null; + this.name = null; + this.uuid = null; + this.cordova = null; + this.model = null; + + var me = this; + + channel.onCordovaReady.subscribe(function() { + me.getInfo(function(info) { + me.available = true; + me.platform = info.platform; + me.version = info.version; + me.name = info.name; + me.uuid = info.uuid; + me.cordova = info.cordova; + me.model = info.model; + channel.onCordovaInfoReady.fire(); + },function(e) { + me.available = false; + utils.alert("[ERROR] Error initializing Cordova: " + e); + }); + }); +} + +/** + * Get device info + * + * @param {Function} successCallback The function to call when the heading data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) + */ +Device.prototype.getInfo = function(successCallback, errorCallback) { + argscheck.checkArgs('fF', 'Device.getInfo', arguments); + exec(successCallback, errorCallback, "Device", "getDeviceInfo", []); +}; + +module.exports = new Device(); + +}); + +// file: lib/common/plugin/device/symbols.js +define("cordova/plugin/device/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/device', 'device'); + +}); + +// file: lib/common/plugin/echo.js +define("cordova/plugin/echo", function(require, exports, module) { + +var exec = require('cordova/exec'); + +/** + * Sends the given message through exec() to the Echo plugin, which sends it back to the successCallback. + * @param successCallback invoked with a FileSystem object + * @param errorCallback invoked if error occurs retrieving file system + * @param message The string to be echoed. + * @param forceAsync Whether to force an async return value (for testing native->js bridge). + */ +module.exports = function(successCallback, errorCallback, message, forceAsync) { + var action = forceAsync ? 'echoAsync' : 'echo'; + if (!forceAsync && message.constructor == ArrayBuffer) { + action = 'echoArrayBuffer'; + } + exec(successCallback, errorCallback, "Echo", action, [message]); +}; + + +}); + +// 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/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/ProgressEvent', 'ProgressEvent'); + exportFunc('cordova/plugin/requestFileSystem', 'requestFileSystem'); + exportFunc('cordova/plugin/resolveLocalFileSystemURI', 'resolveLocalFileSystemURI'); +}; + +}); + +// file: lib/common/plugin/filetransfer/symbols.js +define("cordova/plugin/filetransfer/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/FileTransfer', 'FileTransfer'); +modulemapper.clobbers('cordova/plugin/FileTransferError', 'FileTransferError'); + +}); + +// file: lib/common/plugin/geolocation.js +define("cordova/plugin/geolocation", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + utils = require('cordova/utils'), + exec = require('cordova/exec'), + PositionError = require('cordova/plugin/PositionError'), + Position = require('cordova/plugin/Position'); + +var timers = {}; // list of timers in use + +// Returns default params, overrides if provided with values +function parseParameters(options) { + var opt = { + maximumAge: 0, + enableHighAccuracy: false, + timeout: Infinity + }; + + if (options) { + if (options.maximumAge !== undefined && !isNaN(options.maximumAge) && options.maximumAge > 0) { + opt.maximumAge = options.maximumAge; + } + if (options.enableHighAccuracy !== undefined) { + opt.enableHighAccuracy = options.enableHighAccuracy; + } + if (options.timeout !== undefined && !isNaN(options.timeout)) { + if (options.timeout < 0) { + opt.timeout = 0; + } else { + opt.timeout = options.timeout; + } + } + } + + return opt; +} + +// Returns a timeout failure, closed over a specified timeout value and error callback. +function createTimeout(errorCallback, timeout) { + var t = setTimeout(function() { + clearTimeout(t); + t = null; + errorCallback({ + code:PositionError.TIMEOUT, + message:"Position retrieval timed out." + }); + }, timeout); + return t; +} + +var geolocation = { + lastPosition:null, // reference to last known (cached) position returned + /** + * Asynchronously acquires the current position. + * + * @param {Function} successCallback The function to call when the position data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading position. (OPTIONAL) + * @param {PositionOptions} options The options for getting the position data. (OPTIONAL) + */ + getCurrentPosition:function(successCallback, errorCallback, options) { + argscheck.checkArgs('fFO', 'geolocation.getCurrentPosition', arguments); + options = parseParameters(options); + + // Timer var that will fire an error callback if no position is retrieved from native + // before the "timeout" param provided expires + var timeoutTimer = {timer:null}; + + var win = function(p) { + clearTimeout(timeoutTimer.timer); + if (!(timeoutTimer.timer)) { + // Timeout already happened, or native fired error callback for + // this geo request. + // Don't continue with success callback. + return; + } + var pos = new Position( + { + latitude:p.latitude, + longitude:p.longitude, + altitude:p.altitude, + accuracy:p.accuracy, + heading:p.heading, + velocity:p.velocity, + altitudeAccuracy:p.altitudeAccuracy + }, + (p.timestamp === undefined ? new Date() : ((p.timestamp instanceof Date) ? p.timestamp : new Date(p.timestamp))) + ); + geolocation.lastPosition = pos; + successCallback(pos); + }; + var fail = function(e) { + clearTimeout(timeoutTimer.timer); + timeoutTimer.timer = null; + var err = new PositionError(e.code, e.message); + if (errorCallback) { + errorCallback(err); + } + }; + + // Check our cached position, if its timestamp difference with current time is less than the maximumAge, then just + // fire the success callback with the cached position. + if (geolocation.lastPosition && options.maximumAge && (((new Date()).getTime() - geolocation.lastPosition.timestamp.getTime()) <= options.maximumAge)) { + successCallback(geolocation.lastPosition); + // If the cached position check failed and the timeout was set to 0, error out with a TIMEOUT error object. + } else if (options.timeout === 0) { + fail({ + code:PositionError.TIMEOUT, + message:"timeout value in PositionOptions set to 0 and no cached Position object available, or cached Position object's age exceeds provided PositionOptions' maximumAge parameter." + }); + // Otherwise we have to call into native to retrieve a position. + } else { + if (options.timeout !== Infinity) { + // If the timeout value was not set to Infinity (default), then + // set up a timeout function that will fire the error callback + // if no successful position was retrieved before timeout expired. + timeoutTimer.timer = createTimeout(fail, options.timeout); + } else { + // This is here so the check in the win function doesn't mess stuff up + // may seem weird but this guarantees timeoutTimer is + // always truthy before we call into native + timeoutTimer.timer = true; + } + exec(win, fail, "Geolocation", "getLocation", [options.enableHighAccuracy, options.maximumAge]); + } + return timeoutTimer; + }, + /** + * Asynchronously watches the geolocation for changes to geolocation. When a change occurs, + * the successCallback is called with the new location. + * + * @param {Function} successCallback The function to call each time the location data is available + * @param {Function} errorCallback The function to call when there is an error getting the location data. (OPTIONAL) + * @param {PositionOptions} options The options for getting the location data such as frequency. (OPTIONAL) + * @return String The watch id that must be passed to #clearWatch to stop watching. + */ + watchPosition:function(successCallback, errorCallback, options) { + argscheck.checkArgs('fFO', 'geolocation.getCurrentPosition', arguments); + options = parseParameters(options); + + var id = utils.createUUID(); + + // Tell device to get a position ASAP, and also retrieve a reference to the timeout timer generated in getCurrentPosition + timers[id] = geolocation.getCurrentPosition(successCallback, errorCallback, options); + + var fail = function(e) { + clearTimeout(timers[id].timer); + var err = new PositionError(e.code, e.message); + if (errorCallback) { + errorCallback(err); + } + }; + + var win = function(p) { + clearTimeout(timers[id].timer); + if (options.timeout !== Infinity) { + timers[id].timer = createTimeout(fail, options.timeout); + } + var pos = new Position( + { + latitude:p.latitude, + longitude:p.longitude, + altitude:p.altitude, + accuracy:p.accuracy, + heading:p.heading, + velocity:p.velocity, + altitudeAccuracy:p.altitudeAccuracy + }, + (p.timestamp === undefined ? new Date() : ((p.timestamp instanceof Date) ? p.timestamp : new Date(p.timestamp))) + ); + geolocation.lastPosition = pos; + successCallback(pos); + }; + + exec(win, fail, "Geolocation", "addWatch", [id, options.enableHighAccuracy]); + + return id; + }, + /** + * Clears the specified heading watch. + * + * @param {String} id The ID of the watch returned from #watchPosition + */ + clearWatch:function(id) { + if (id && timers[id] !== undefined) { + clearTimeout(timers[id].timer); + timers[id].timer = false; + exec(null, null, "Geolocation", "clearWatch", [id]); + } + } +}; + +module.exports = geolocation; + +}); + +// file: lib/common/plugin/geolocation/symbols.js +define("cordova/plugin/geolocation/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.defaults('cordova/plugin/geolocation', 'navigator.geolocation'); +modulemapper.clobbers('cordova/plugin/PositionError', 'PositionError'); +modulemapper.clobbers('cordova/plugin/Position', 'Position'); +modulemapper.clobbers('cordova/plugin/Coordinates', 'Coordinates'); + +}); + +// file: lib/common/plugin/globalization.js +define("cordova/plugin/globalization", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + exec = require('cordova/exec'), + GlobalizationError = require('cordova/plugin/GlobalizationError'); + +var globalization = { + +/** +* Returns the string identifier for the client's current language. +* It returns the language identifier string to the successCB callback with a +* properties object as a parameter. If there is an error getting the language, +* then the errorCB callback is invoked. +* +* @param {Function} successCB +* @param {Function} errorCB +* +* @return Object.value {String}: The language identifier +* +* @error GlobalizationError.UNKNOWN_ERROR +* +* Example +* globalization.getPreferredLanguage(function (language) {alert('language:' + language.value + '\n');}, +* function () {}); +*/ +getPreferredLanguage:function(successCB, failureCB) { + argscheck.checkArgs('fF', 'Globalization.getPreferredLanguage', arguments); + exec(successCB, failureCB, "Globalization","getPreferredLanguage", []); +}, + +/** +* Returns the string identifier for the client's current locale setting. +* It returns the locale identifier string to the successCB callback with a +* properties object as a parameter. If there is an error getting the locale, +* then the errorCB callback is invoked. +* +* @param {Function} successCB +* @param {Function} errorCB +* +* @return Object.value {String}: The locale identifier +* +* @error GlobalizationError.UNKNOWN_ERROR +* +* Example +* globalization.getLocaleName(function (locale) {alert('locale:' + locale.value + '\n');}, +* function () {}); +*/ +getLocaleName:function(successCB, failureCB) { + argscheck.checkArgs('fF', 'Globalization.getLocaleName', arguments); + exec(successCB, failureCB, "Globalization","getLocaleName", []); +}, + + +/** +* Returns a date formatted as a string according to the client's user preferences and +* calendar using the time zone of the client. It returns the formatted date string to the +* successCB callback with a properties object as a parameter. If there is an error +* formatting the date, then the errorCB callback is invoked. +* +* The defaults are: formatLenght="short" and selector="date and time" +* +* @param {Date} date +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* formatLength {String}: 'short', 'medium', 'long', or 'full' +* selector {String}: 'date', 'time', or 'date and time' +* +* @return Object.value {String}: The localized date string +* +* @error GlobalizationError.FORMATTING_ERROR +* +* Example +* globalization.dateToString(new Date(), +* function (date) {alert('date:' + date.value + '\n');}, +* function (errorCode) {alert(errorCode);}, +* {formatLength:'short'}); +*/ +dateToString:function(date, successCB, failureCB, options) { + argscheck.checkArgs('dfFO', 'Globalization.dateToString', arguments); + var dateValue = date.valueOf(); + exec(successCB, failureCB, "Globalization", "dateToString", [{"date": dateValue, "options": options}]); +}, + + +/** +* Parses a date formatted as a string according to the client's user +* preferences and calendar using the time zone of the client and returns +* the corresponding date object. It returns the date to the successCB +* callback with a properties object as a parameter. If there is an error +* parsing the date string, then the errorCB callback is invoked. +* +* The defaults are: formatLength="short" and selector="date and time" +* +* @param {String} dateString +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* formatLength {String}: 'short', 'medium', 'long', or 'full' +* selector {String}: 'date', 'time', or 'date and time' +* +* @return Object.year {Number}: The four digit year +* Object.month {Number}: The month from (0 - 11) +* Object.day {Number}: The day from (1 - 31) +* Object.hour {Number}: The hour from (0 - 23) +* Object.minute {Number}: The minute from (0 - 59) +* Object.second {Number}: The second from (0 - 59) +* Object.millisecond {Number}: The milliseconds (from 0 - 999), +* not available on all platforms +* +* @error GlobalizationError.PARSING_ERROR +* +* Example +* globalization.stringToDate('4/11/2011', +* function (date) { alert('Month:' + date.month + '\n' + +* 'Day:' + date.day + '\n' + +* 'Year:' + date.year + '\n');}, +* function (errorCode) {alert(errorCode);}, +* {selector:'date'}); +*/ +stringToDate:function(dateString, successCB, failureCB, options) { + argscheck.checkArgs('sfFO', 'Globalization.stringToDate', arguments); + exec(successCB, failureCB, "Globalization", "stringToDate", [{"dateString": dateString, "options": options}]); +}, + + +/** +* Returns a pattern string for formatting and parsing dates according to the client's +* user preferences. It returns the pattern to the successCB callback with a +* properties object as a parameter. If there is an error obtaining the pattern, +* then the errorCB callback is invoked. +* +* The defaults are: formatLength="short" and selector="date and time" +* +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* formatLength {String}: 'short', 'medium', 'long', or 'full' +* selector {String}: 'date', 'time', or 'date and time' +* +* @return Object.pattern {String}: The date and time pattern for formatting and parsing dates. +* The patterns follow Unicode Technical Standard #35 +* http://unicode.org/reports/tr35/tr35-4.html +* Object.timezone {String}: The abbreviated name of the time zone on the client +* Object.utc_offset {Number}: The current difference in seconds between the client's +* time zone and coordinated universal time. +* Object.dst_offset {Number}: The current daylight saving time offset in seconds +* between the client's non-daylight saving's time zone +* and the client's daylight saving's time zone. +* +* @error GlobalizationError.PATTERN_ERROR +* +* Example +* globalization.getDatePattern( +* function (date) {alert('pattern:' + date.pattern + '\n');}, +* function () {}, +* {formatLength:'short'}); +*/ +getDatePattern:function(successCB, failureCB, options) { + argscheck.checkArgs('fFO', 'Globalization.getDatePattern', arguments); + exec(successCB, failureCB, "Globalization", "getDatePattern", [{"options": options}]); +}, + + +/** +* Returns an array of either the names of the months or days of the week +* according to the client's user preferences and calendar. It returns the array of names to the +* successCB callback with a properties object as a parameter. If there is an error obtaining the +* names, then the errorCB callback is invoked. +* +* The defaults are: type="wide" and item="months" +* +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* type {String}: 'narrow' or 'wide' +* item {String}: 'months', or 'days' +* +* @return Object.value {Array{String}}: The array of names starting from either +* the first month in the year or the +* first day of the week. +* @error GlobalizationError.UNKNOWN_ERROR +* +* Example +* globalization.getDateNames(function (names) { +* for(var i = 0; i < names.value.length; i++) { +* alert('Month:' + names.value[i] + '\n');}}, +* function () {}); +*/ +getDateNames:function(successCB, failureCB, options) { + argscheck.checkArgs('fFO', 'Globalization.getDateNames', arguments); + exec(successCB, failureCB, "Globalization", "getDateNames", [{"options": options}]); +}, + +/** +* Returns whether daylight savings time is in effect for a given date using the client's +* time zone and calendar. It returns whether or not daylight savings time is in effect +* to the successCB callback with a properties object as a parameter. If there is an error +* reading the date, then the errorCB callback is invoked. +* +* @param {Date} date +* @param {Function} successCB +* @param {Function} errorCB +* +* @return Object.dst {Boolean}: The value "true" indicates that daylight savings time is +* in effect for the given date and "false" indicate that it is not. +* +* @error GlobalizationError.UNKNOWN_ERROR +* +* Example +* globalization.isDayLightSavingsTime(new Date(), +* function (date) {alert('dst:' + date.dst + '\n');} +* function () {}); +*/ +isDayLightSavingsTime:function(date, successCB, failureCB) { + argscheck.checkArgs('dfF', 'Globalization.isDayLightSavingsTime', arguments); + var dateValue = date.valueOf(); + exec(successCB, failureCB, "Globalization", "isDayLightSavingsTime", [{"date": dateValue}]); +}, + +/** +* Returns the first day of the week according to the client's user preferences and calendar. +* The days of the week are numbered starting from 1 where 1 is considered to be Sunday. +* It returns the day to the successCB callback with a properties object as a parameter. +* If there is an error obtaining the pattern, then the errorCB callback is invoked. +* +* @param {Function} successCB +* @param {Function} errorCB +* +* @return Object.value {Number}: The number of the first day of the week. +* +* @error GlobalizationError.UNKNOWN_ERROR +* +* Example +* globalization.getFirstDayOfWeek(function (day) +* { alert('Day:' + day.value + '\n');}, +* function () {}); +*/ +getFirstDayOfWeek:function(successCB, failureCB) { + argscheck.checkArgs('fF', 'Globalization.getFirstDayOfWeek', arguments); + exec(successCB, failureCB, "Globalization", "getFirstDayOfWeek", []); +}, + + +/** +* Returns a number formatted as a string according to the client's user preferences. +* It returns the formatted number string to the successCB callback with a properties object as a +* parameter. If there is an error formatting the number, then the errorCB callback is invoked. +* +* The defaults are: type="decimal" +* +* @param {Number} number +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* type {String}: 'decimal', "percent", or 'currency' +* +* @return Object.value {String}: The formatted number string. +* +* @error GlobalizationError.FORMATTING_ERROR +* +* Example +* globalization.numberToString(3.25, +* function (number) {alert('number:' + number.value + '\n');}, +* function () {}, +* {type:'decimal'}); +*/ +numberToString:function(number, successCB, failureCB, options) { + argscheck.checkArgs('nfFO', 'Globalization.numberToString', arguments); + exec(successCB, failureCB, "Globalization", "numberToString", [{"number": number, "options": options}]); +}, + +/** +* Parses a number formatted as a string according to the client's user preferences and +* returns the corresponding number. It returns the number to the successCB callback with a +* properties object as a parameter. If there is an error parsing the number string, then +* the errorCB callback is invoked. +* +* The defaults are: type="decimal" +* +* @param {String} numberString +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* type {String}: 'decimal', "percent", or 'currency' +* +* @return Object.value {Number}: The parsed number. +* +* @error GlobalizationError.PARSING_ERROR +* +* Example +* globalization.stringToNumber('1234.56', +* function (number) {alert('Number:' + number.value + '\n');}, +* function () { alert('Error parsing number');}); +*/ +stringToNumber:function(numberString, successCB, failureCB, options) { + argscheck.checkArgs('sfFO', 'Globalization.stringToNumber', arguments); + exec(successCB, failureCB, "Globalization", "stringToNumber", [{"numberString": numberString, "options": options}]); +}, + +/** +* Returns a pattern string for formatting and parsing numbers according to the client's user +* preferences. It returns the pattern to the successCB callback with a properties object as a +* parameter. If there is an error obtaining the pattern, then the errorCB callback is invoked. +* +* The defaults are: type="decimal" +* +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* type {String}: 'decimal', "percent", or 'currency' +* +* @return Object.pattern {String}: The number pattern for formatting and parsing numbers. +* The patterns follow Unicode Technical Standard #35. +* http://unicode.org/reports/tr35/tr35-4.html +* Object.symbol {String}: The symbol to be used when formatting and parsing +* e.g., percent or currency symbol. +* Object.fraction {Number}: The number of fractional digits to use when parsing and +* formatting numbers. +* Object.rounding {Number}: The rounding increment to use when parsing and formatting. +* Object.positive {String}: The symbol to use for positive numbers when parsing and formatting. +* Object.negative: {String}: The symbol to use for negative numbers when parsing and formatting. +* Object.decimal: {String}: The decimal symbol to use for parsing and formatting. +* Object.grouping: {String}: The grouping symbol to use for parsing and formatting. +* +* @error GlobalizationError.PATTERN_ERROR +* +* Example +* globalization.getNumberPattern( +* function (pattern) {alert('Pattern:' + pattern.pattern + '\n');}, +* function () {}); +*/ +getNumberPattern:function(successCB, failureCB, options) { + argscheck.checkArgs('fFO', 'Globalization.getNumberPattern', arguments); + exec(successCB, failureCB, "Globalization", "getNumberPattern", [{"options": options}]); +}, + +/** +* Returns a pattern string for formatting and parsing currency values according to the client's +* user preferences and ISO 4217 currency code. It returns the pattern to the successCB callback with a +* properties object as a parameter. If there is an error obtaining the pattern, then the errorCB +* callback is invoked. +* +* @param {String} currencyCode +* @param {Function} successCB +* @param {Function} errorCB +* +* @return Object.pattern {String}: The currency pattern for formatting and parsing currency values. +* The patterns follow Unicode Technical Standard #35 +* http://unicode.org/reports/tr35/tr35-4.html +* Object.code {String}: The ISO 4217 currency code for the pattern. +* Object.fraction {Number}: The number of fractional digits to use when parsing and +* formatting currency. +* Object.rounding {Number}: The rounding increment to use when parsing and formatting. +* Object.decimal: {String}: The decimal symbol to use for parsing and formatting. +* Object.grouping: {String}: The grouping symbol to use for parsing and formatting. +* +* @error GlobalizationError.FORMATTING_ERROR +* +* Example +* globalization.getCurrencyPattern('EUR', +* function (currency) {alert('Pattern:' + currency.pattern + '\n');} +* function () {}); +*/ +getCurrencyPattern:function(currencyCode, successCB, failureCB) { + argscheck.checkArgs('sfF', 'Globalization.getCurrencyPattern', arguments); + exec(successCB, failureCB, "Globalization", "getCurrencyPattern", [{"currencyCode": currencyCode}]); +} + +}; + +module.exports = globalization; + +}); + +// file: lib/common/plugin/globalization/symbols.js +define("cordova/plugin/globalization/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/globalization', 'navigator.globalization'); +modulemapper.clobbers('cordova/plugin/GlobalizationError', 'GlobalizationError'); + +}); + +// file: lib/ios/plugin/inappbrowser/symbols.js +define("cordova/plugin/inappbrowser/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/InAppBrowser', 'open'); + +}); + +// file: lib/ios/plugin/ios/Contact.js +define("cordova/plugin/ios/Contact", function(require, exports, module) { + +var exec = require('cordova/exec'), + ContactError = require('cordova/plugin/ContactError'); + +/** + * Provides iOS Contact.display API. + */ +module.exports = { + display : function(errorCB, options) { + /* + * Display a contact using the iOS Contact Picker UI + * NOT part of W3C spec so no official documentation + * + * @param errorCB error callback + * @param options object + * allowsEditing: boolean AS STRING + * "true" to allow editing the contact + * "false" (default) display contact + */ + + if (this.id === null) { + if (typeof errorCB === "function") { + var errorObj = new ContactError(ContactError.UNKNOWN_ERROR); + errorCB(errorObj); + } + } + else { + exec(null, errorCB, "Contacts","displayContact", [this.id, options]); + } + } +}; + +}); + +// file: lib/ios/plugin/ios/Entry.js +define("cordova/plugin/ios/Entry", function(require, exports, module) { + +module.exports = { + toURL:function() { + // TODO: refactor path in a cross-platform way so we can eliminate + // these kinds of platform-specific hacks. + return "file://localhost" + this.fullPath; + }, + toURI: function() { + console.log("DEPRECATED: Update your code to use 'toURL'"); + return "file://localhost" + this.fullPath; + } +}; + +}); + +// file: lib/ios/plugin/ios/console.js +define("cordova/plugin/ios/console", function(require, exports, module) { + +var exec = require('cordova/exec'); + +/** + * create a nice string for an object + */ +function stringify(message) { + try { + if (typeof message === "object" && JSON && JSON.stringify) { + try { + return JSON.stringify(message); + } + catch (e) { + return "error JSON.stringify()ing argument: " + e; + } + } else { + return message.toString(); + } + } catch (e) { + return e.toString(); + } +} + +/** + * Wrapper one of the console logging methods, so that + * the Cordova logging native is called, then the original. + */ +function wrappedMethod(console, method) { + var origMethod = console[method]; + + return function(message) { + exec(null, null, + 'Debug Console', 'log', + [ stringify(message), { logLevel: method.toUpperCase() } ] + ); + + if (!origMethod) return; + + origMethod.apply(console, arguments); + }; +} + +var console = window.console || {}; + +// 2012-10-06 pmuellr - marking setLevel() method and logLevel property +// on console as deprecated; +// it didn't do anything useful, since the level constants weren't accessible +// to anyone + +console.setLevel = function() {}; +console.logLevel = 0; + +// wrapper the logging messages + +var methods = ["log", "debug", "info", "warn", "error"]; + +for (var i=0; i<methods.length; i++) { + var method = methods[i]; + + console[method] = wrappedMethod(console, method); +} + +module.exports = console; + +}); + +// file: lib/ios/plugin/ios/console/symbols.js +define("cordova/plugin/ios/console/symbols", function(require, exports, module) { + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/ios/console', 'console'); + +}); + +// file: lib/ios/plugin/ios/contacts.js +define("cordova/plugin/ios/contacts", function(require, exports, module) { + +var exec = require('cordova/exec'); + +/** + * Provides iOS enhanced contacts API. + */ +module.exports = { + newContactUI : function(successCallback) { + /* + * Create a contact using the iOS Contact Picker UI + * NOT part of W3C spec so no official documentation + * + * returns: the id of the created contact as param to successCallback + */ + exec(successCallback, null, "Contacts","newContact", []); + }, + chooseContact : function(successCallback, options) { + /* + * Select a contact using the iOS Contact Picker UI + * NOT part of W3C spec so no official documentation + * + * @param errorCB error callback + * @param options object + * allowsEditing: boolean AS STRING + * "true" to allow editing the contact + * "false" (default) display contact + * fields: array of fields to return in contact object (see ContactOptions.fields) + * + * @returns + * id of contact selected + * ContactObject + * if no fields provided contact contains just id information + * if fields provided contact object contains information for the specified fields + * + */ + var win = function(result) { + var fullContact = require('cordova/plugin/contacts').create(result); + successCallback(fullContact.id, fullContact); + }; + exec(win, null, "Contacts","chooseContact", [options]); + } +}; + +}); + +// file: lib/ios/plugin/ios/contacts/symbols.js +define("cordova/plugin/ios/contacts/symbols", function(require, exports, module) { + +require('cordova/plugin/contacts/symbols'); + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.merges('cordova/plugin/ios/contacts', 'navigator.contacts'); +modulemapper.merges('cordova/plugin/ios/Contact', 'Contact'); + +}); + +// file: lib/ios/plugin/ios/geolocation/symbols.js +define("cordova/plugin/ios/geolocation/symbols", function(require, exports, module) { + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.merges('cordova/plugin/geolocation', 'navigator.geolocation'); + +}); + +// file: lib/ios/plugin/ios/logger/plugininit.js +define("cordova/plugin/ios/logger/plugininit", function(require, exports, module) { + +// use the native logger +var logger = require("cordova/plugin/logger"); +logger.useConsole(false); + +}); + +// file: lib/ios/plugin/ios/notification.js +define("cordova/plugin/ios/notification", function(require, exports, module) { + +var Media = require('cordova/plugin/Media'); + +module.exports = { + beep:function(count) { + (new Media('beep.wav')).play(); + } +}; + +}); + +// file: lib/common/plugin/logger.js +define("cordova/plugin/logger", function(require, exports, module) { + +//------------------------------------------------------------------------------ +// The logger module exports the following properties/functions: +// +// LOG - constant for the level LOG +// ERROR - constant for the level ERROR +// WARN - constant for the level WARN +// INFO - constant for the level INFO +// DEBUG - constant for the level DEBUG +// logLevel() - returns current log level +// logLevel(value) - sets and returns a new log level +// useConsole() - returns whether logger is using console +// useConsole(value) - sets and returns whether logger is using console +// log(message,...) - logs a message at level LOG +// error(message,...) - logs a message at level ERROR +// warn(message,...) - logs a message at level WARN +// info(message,...) - logs a message at level INFO +// debug(message,...) - logs a message at level DEBUG +// logLevel(level,message,...) - logs a message specified level +// +//------------------------------------------------------------------------------ + +var logger = exports; + +var exec = require('cordova/exec'); +var utils = require('cordova/utils'); + +var UseConsole = true; +var Queued = []; +var DeviceReady = false; +var CurrentLevel; + +/** + * Logging levels + */ + +var Levels = [ + "LOG", + "ERROR", + "WARN", + "INFO", + "DEBUG" +]; + +/* + * add the logging levels to the logger object and + * to a separate levelsMap object for testing + */ + +var LevelsMap = {}; +for (var i=0; i<Levels.length; i++) { + var level = Levels[i]; + LevelsMap[level] = i; + logger[level] = level; +} + +CurrentLevel = LevelsMap.WARN; + +/** + * Getter/Setter for the logging level + * + * Returns the current logging level. + * + * When a value is passed, sets the logging level to that value. + * The values should be one of the following constants: + * logger.LOG + * logger.ERROR + * logger.WARN + * logger.INFO + * logger.DEBUG + * + * The value used determines which messages get printed. The logging + * values above are in order, and only messages logged at the logging + * level or above will actually be displayed to the user. E.g., the + * default level is WARN, so only messages logged with LOG, ERROR, or + * WARN will be displayed; INFO and DEBUG messages will be ignored. + */ +logger.level = function (value) { + if (arguments.length) { + if (LevelsMap[value] === null) { + throw new Error("invalid logging level: " + value); + } + CurrentLevel = LevelsMap[value]; + } + + return Levels[CurrentLevel]; +}; + +/** + * Getter/Setter for the useConsole functionality + * + * When useConsole is true, the logger will log via the + * browser 'console' object. Otherwise, it will use the + * native Logger plugin. + */ +logger.useConsole = function (value) { + if (arguments.length) UseConsole = !!value; + + if (UseConsole) { + if (typeof console == "undefined") { + throw new Error("global console object is not defined"); + } + + if (typeof console.log != "function") { + throw new Error("global console object does not have a log function"); + } + + if (typeof console.useLogger == "function") { + if (console.useLogger()) { + throw new Error("console and logger are too intertwingly"); + } + } + } + + return UseConsole; +}; + +/** + * Logs a message at the LOG level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.log = function(message) { logWithArgs("LOG", arguments); }; + +/** + * Logs a message at the ERROR level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.error = function(message) { logWithArgs("ERROR", arguments); }; + +/** + * Logs a message at the WARN level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.warn = function(message) { logWithArgs("WARN", arguments); }; + +/** + * Logs a message at the INFO level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.info = function(message) { logWithArgs("INFO", arguments); }; + +/** + * Logs a message at the DEBUG level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.debug = function(message) { logWithArgs("DEBUG", arguments); }; + +// log at the specified level with args +function logWithArgs(level, args) { + args = [level].concat([].slice.call(args)); + logger.logLevel.apply(logger, args); +} + +/** + * Logs a message at the specified level. + * + * Parameters passed after message are used applied to + * the message with utils.format() + */ +logger.logLevel = function(level, message /* , ... */) { + // format the message with the parameters + var formatArgs = [].slice.call(arguments, 2); + message = utils.vformat(message, formatArgs); + + if (LevelsMap[level] === null) { + throw new Error("invalid logging level: " + level); + } + + if (LevelsMap[level] > CurrentLevel) return; + + // queue the message if not yet at deviceready + if (!DeviceReady && !UseConsole) { + Queued.push([level, message]); + return; + } + + // if not using the console, use the native logger + if (!UseConsole) { + exec(null, null, "Logger", "logLevel", [level, message]); + return; + } + + // make sure console is not using logger + if (console.__usingCordovaLogger) { + throw new Error("console and logger are too intertwingly"); + } + + // log to the console + switch (level) { + case logger.LOG: console.log(message); break; + case logger.ERROR: console.log("ERROR: " + message); break; + case logger.WARN: console.log("WARN: " + message); break; + case logger.INFO: console.log("INFO: " + message); break; + case logger.DEBUG: console.log("DEBUG: " + message); break; + } +}; + +// when deviceready fires, log queued messages +logger.__onDeviceReady = function() { + if (DeviceReady) return; + + DeviceReady = true; + + for (var i=0; i<Queued.length; i++) { + var messageArgs = Queued[i]; + logger.logLevel(messageArgs[0], messageArgs[1]); + } + + Queued = null; +}; + +// add a deviceready event to log queued messages +document.addEventListener("deviceready", logger.__onDeviceReady, false); + +}); + +// file: lib/common/plugin/logger/symbols.js +define("cordova/plugin/logger/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/logger', 'cordova.logger'); + +}); + +// file: lib/ios/plugin/media/symbols.js +define("cordova/plugin/media/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.defaults('cordova/plugin/Media', 'Media'); +modulemapper.clobbers('cordova/plugin/MediaError', 'MediaError'); + +}); + +// file: lib/common/plugin/network.js +define("cordova/plugin/network", function(require, exports, module) { + +var exec = require('cordova/exec'), + cordova = require('cordova'), + channel = require('cordova/channel'), + utils = require('cordova/utils'); + +// Link the onLine property with the Cordova-supplied network info. +// This works because we clobber the naviagtor object with our own +// object in bootstrap.js. +if (typeof navigator != 'undefined') { + utils.defineGetter(navigator, 'onLine', function() { + return this.connection.type != 'none'; + }); +} + +function NetworkConnection() { + this.type = 'unknown'; +} + +/** + * Get connection info + * + * @param {Function} successCallback The function to call when the Connection data is available + * @param {Function} errorCallback The function to call when there is an error getting the Connection data. (OPTIONAL) + */ +NetworkConnection.prototype.getInfo = function(successCallback, errorCallback) { + exec(successCallback, errorCallback, "NetworkStatus", "getConnectionInfo", []); +}; + +var me = new NetworkConnection(); +var timerId = null; +var timeout = 500; + +channel.onCordovaReady.subscribe(function() { + me.getInfo(function(info) { + me.type = info; + if (info === "none") { + // set a timer if still offline at the end of timer send the offline event + timerId = setTimeout(function(){ + cordova.fireDocumentEvent("offline"); + timerId = null; + }, timeout); + } else { + // If there is a current offline event pending clear it + if (timerId !== null) { + clearTimeout(timerId); + timerId = null; + } + cordova.fireDocumentEvent("online"); + } + + // should only fire this once + if (channel.onCordovaConnectionReady.state !== 2) { + channel.onCordovaConnectionReady.fire(); + } + }, + function (e) { + // If we can't get the network info we should still tell Cordova + // to fire the deviceready event. + if (channel.onCordovaConnectionReady.state !== 2) { + channel.onCordovaConnectionReady.fire(); + } + console.log("Error initializing Network Connection: " + e); + }); +}); + +module.exports = me; + +}); + +// file: lib/common/plugin/networkstatus/symbols.js +define("cordova/plugin/networkstatus/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/network', 'navigator.network.connection', 'navigator.network.connection is deprecated. Use navigator.connection instead.'); +modulemapper.clobbers('cordova/plugin/network', 'navigator.connection'); +modulemapper.defaults('cordova/plugin/Connection', 'Connection'); + +}); + +// file: lib/common/plugin/notification.js +define("cordova/plugin/notification", function(require, exports, module) { + +var exec = require('cordova/exec'); + +/** + * Provides access to notifications on the device. + */ + +module.exports = { + + /** + * Open a native alert dialog, with a customizable title and button text. + * + * @param {String} message Message to print in the body of the alert + * @param {Function} completeCallback The callback that is called when user clicks on a button. + * @param {String} title Title of the alert dialog (default: Alert) + * @param {String} buttonLabel Label of the close button (default: OK) + */ + alert: function(message, completeCallback, title, buttonLabel) { + var _title = (title || "Alert"); + var _buttonLabel = (buttonLabel || "OK"); + exec(completeCallback, null, "Notification", "alert", [message, _title, _buttonLabel]); + }, + + /** + * Open a native confirm dialog, with a customizable title and button text. + * The result that the user selects is returned to the result callback. + * + * @param {String} message Message to print in the body of the alert + * @param {Function} resultCallback The callback that is called when user clicks on a button. + * @param {String} title Title of the alert dialog (default: Confirm) + * @param {String} buttonLabels Comma separated list of the labels of the buttons (default: 'OK,Cancel') + */ + confirm: function(message, resultCallback, title, buttonLabels) { + var _title = (title || "Confirm"); + var _buttonLabels = (buttonLabels || "OK,Cancel"); + exec(resultCallback, null, "Notification", "confirm", [message, _title, _buttonLabels]); + }, + + /** + * Causes the device to vibrate. + * + * @param {Integer} mills The number of milliseconds to vibrate for. + */ + vibrate: function(mills) { + exec(null, null, "Notification", "vibrate", [mills]); + }, + + /** + * Causes the device to beep. + * On Android, the default notification ringtone is played "count" times. + * + * @param {Integer} count The number of beeps. + */ + beep: function(count) { + exec(null, null, "Notification", "beep", [count]); + } +}; + +}); + +// file: lib/ios/plugin/notification/symbols.js +define("cordova/plugin/notification/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/notification', 'navigator.notification'); +modulemapper.merges('cordova/plugin/ios/notification', 'navigator.notification'); + +}); + +// file: lib/common/plugin/requestFileSystem.js +define("cordova/plugin/requestFileSystem", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + FileError = require('cordova/plugin/FileError'), + FileSystem = require('cordova/plugin/FileSystem'), + exec = require('cordova/exec'); + +/** + * Request a file system in which to store application data. + * @param type local file system type + * @param size indicates how much storage space, in bytes, the application expects to need + * @param successCallback invoked with a FileSystem object + * @param errorCallback invoked if error occurs retrieving file system + */ +var requestFileSystem = function(type, size, successCallback, errorCallback) { + argscheck.checkArgs('nnFF', 'requestFileSystem', arguments); + var fail = function(code) { + errorCallback && errorCallback(new FileError(code)); + }; + + if (type < 0 || type > 3) { + fail(FileError.SYNTAX_ERR); + } else { + // if successful, return a FileSystem object + var success = function(file_system) { + if (file_system) { + if (successCallback) { + // grab the name and root from the file system object + var result = new FileSystem(file_system.name, file_system.root); + successCallback(result); + } + } + else { + // no FileSystem object returned + fail(FileError.NOT_FOUND_ERR); + } + }; + exec(success, fail, "File", "requestFileSystem", [type, size]); + } +}; + +module.exports = requestFileSystem; + +}); + +// file: lib/common/plugin/resolveLocalFileSystemURI.js +define("cordova/plugin/resolveLocalFileSystemURI", function(require, exports, module) { + +var argscheck = require('cordova/argscheck'), + DirectoryEntry = require('cordova/plugin/DirectoryEntry'), + FileEntry = require('cordova/plugin/FileEntry'), + FileError = require('cordova/plugin/FileError'), + exec = require('cordova/exec'); + +/** + * Look up file system Entry referred to by local URI. + * @param {DOMString} uri URI referring to a local file or directory + * @param successCallback invoked with Entry object corresponding to URI + * @param errorCallback invoked if error occurs retrieving file system entry + */ +module.exports = function(uri, successCallback, errorCallback) { + argscheck.checkArgs('sFF', 'resolveLocalFileSystemURI', arguments); + // error callback + var fail = function(error) { + errorCallback && errorCallback(new FileError(error)); + }; + // sanity check for 'not:valid:filename' + if(!uri || uri.split(":").length > 2) { + setTimeout( function() { + fail(FileError.ENCODING_ERR); + },0); + return; + } + // if successful, return either a file or directory entry + var success = function(entry) { + var result; + if (entry) { + if (successCallback) { + // create appropriate Entry object + result = (entry.isDirectory) ? new DirectoryEntry(entry.name, entry.fullPath) : new FileEntry(entry.name, entry.fullPath); + successCallback(result); + } + } + else { + // no Entry object returned + fail(FileError.NOT_FOUND_ERR); + } + }; + + exec(success, fail, "File", "resolveLocalFileSystemURI", [uri]); +}; + +}); + +// file: lib/common/plugin/splashscreen.js +define("cordova/plugin/splashscreen", function(require, exports, module) { + +var exec = require('cordova/exec'); + +var splashscreen = { + show:function() { + exec(null, null, "SplashScreen", "show", []); + }, + hide:function() { + exec(null, null, "SplashScreen", "hide", []); + } +}; + +module.exports = splashscreen; + +}); + +// file: lib/common/plugin/splashscreen/symbols.js +define("cordova/plugin/splashscreen/symbols", function(require, exports, module) { + + +var modulemapper = require('cordova/modulemapper'); + +modulemapper.clobbers('cordova/plugin/splashscreen', 'navigator.splashscreen'); + +}); + +// file: lib/common/symbols.js +define("cordova/symbols", function(require, exports, module) { + +var modulemapper = require('cordova/modulemapper'); + +// Use merges here in case others symbols files depend on this running first, +// but fail to declare the dependency with a require(). +modulemapper.merges('cordova', 'cordova'); +modulemapper.clobbers('cordova/exec', 'cordova.exec'); +modulemapper.clobbers('cordova/exec', 'Cordova.exec'); + +}); + +// file: lib/common/utils.js +define("cordova/utils", function(require, exports, module) { + +var utils = exports; + +/** + * Defines a property getter / setter for obj[key]. + */ +utils.defineGetterSetter = function(obj, key, getFunc, opt_setFunc) { + if (Object.defineProperty) { + var desc = { + get: getFunc, + configurable: true + }; + if (opt_setFunc) { + desc.set = opt_setFunc; + } + Object.defineProperty(obj, key, desc); + } else { + obj.__defineGetter__(key, getFunc); + if (opt_setFunc) { + obj.__defineSetter__(key, opt_setFunc); + } + } +}; + +/** + * Defines a property getter for obj[key]. + */ +utils.defineGetter = utils.defineGetterSetter; + +utils.arrayIndexOf = function(a, item) { + if (a.indexOf) { + return a.indexOf(item); + } + var len = a.length; + for (var i = 0; i < len; ++i) { + if (a[i] == item) { + return i; + } + } + return -1; +}; + +/** + * Returns whether the item was found in the array. + */ +utils.arrayRemove = function(a, item) { + var index = utils.arrayIndexOf(a, item); + if (index != -1) { + a.splice(index, 1); + } + return index != -1; +}; + +utils.typeName = function(val) { + return Object.prototype.toString.call(val).slice(8, -1); +}; + +/** + * Returns an indication of whether the argument is an array or not + */ +utils.isArray = function(a) { + return utils.typeName(a) == 'Array'; +}; + +/** + * Returns an indication of whether the argument is a Date or not + */ +utils.isDate = function(d) { + return utils.typeName(d) == 'Date'; +}; + +/** + * Does a deep clone of the object. + */ +utils.clone = function(obj) { + if(!obj || typeof obj == 'function' || utils.isDate(obj) || typeof obj != 'object') { + return obj; + } + + var retVal, i; + + if(utils.isArray(obj)){ + retVal = []; + for(i = 0; i < obj.length; ++i){ + retVal.push(utils.clone(obj[i])); + } + return retVal; + } + + retVal = {}; + for(i in obj){ + if(!(i in retVal) || retVal[i] != obj[i]) { + retVal[i] = utils.clone(obj[i]); + } + } + return retVal; +}; + +/** + * Returns a wrapped version of the function + */ +utils.close = function(context, func, params) { + if (typeof params == 'undefined') { + return function() { + return func.apply(context, arguments); + }; + } else { + return function() { + return func.apply(context, params); + }; + } +}; + +/** + * Create a UUID + */ +utils.createUUID = function() { + return UUIDcreatePart(4) + '-' + + UUIDcreatePart(2) + '-' + + UUIDcreatePart(2) + '-' + + UUIDcreatePart(2) + '-' + + UUIDcreatePart(6); +}; + +/** + * Extends a child object from a parent object using classical inheritance + * pattern. + */ +utils.extend = (function() { + // proxy used to establish prototype chain + var F = function() {}; + // extend Child from Parent + return function(Child, Parent) { + F.prototype = Parent.prototype; + Child.prototype = new F(); + Child.__super__ = Parent.prototype; + Child.prototype.constructor = Child; + }; +}()); + +/** + * Alerts a message in any available way: alert or console.log. + */ +utils.alert = function(msg) { + if (window.alert) { + window.alert(msg); + } else if (console && console.log) { + console.log(msg); + } +}; + +/** + * Formats a string and arguments following it ala sprintf() + * + * see utils.vformat() for more information + */ +utils.format = function(formatString /* ,... */) { + var args = [].slice.call(arguments, 1); + return utils.vformat(formatString, args); +}; + +/** + * Formats a string and arguments following it ala vsprintf() + * + * format chars: + * %j - format arg as JSON + * %o - format arg as JSON + * %c - format arg as '' + * %% - replace with '%' + * any other char following % will format it's + * arg via toString(). + * + * for rationale, see FireBug's Console API: + * http://getfirebug.com/wiki/index.php/Console_API + */ +utils.vformat = function(formatString, args) { + if (formatString === null || formatString === undefined) return ""; + if (arguments.length == 1) return formatString.toString(); + if (typeof formatString != "string") return formatString.toString(); + + var pattern = /(.*?)%(.)(.*)/; + var rest = formatString; + var result = []; + + while (args.length) { + var arg = args.shift(); + var match = pattern.exec(rest); + + if (!match) break; + + rest = match[3]; + + result.push(match[1]); + + if (match[2] == '%') { + result.push('%'); + args.unshift(arg); + continue; + } + + result.push(formatted(arg, match[2])); + } + + result.push(rest); + + return result.join(''); +}; + +//------------------------------------------------------------------------------ +function UUIDcreatePart(length) { + var uuidpart = ""; + for (var i=0; i<length; i++) { + var uuidchar = parseInt((Math.random() * 256), 10).toString(16); + if (uuidchar.length == 1) { + uuidchar = "0" + uuidchar; + } + uuidpart += uuidchar; + } + return uuidpart; +} + +//------------------------------------------------------------------------------ +function formatted(object, formatChar) { + + try { + switch(formatChar) { + case 'j': + case 'o': return JSON.stringify(object); + case 'c': return ''; + } + } + catch (e) { + return "error JSON.stringify()ing argument: " + e; + } + + if ((object === null) || (object === undefined)) { + return Object.prototype.toString.call(object); + } + + return object.toString(); +} + +}); + + +window.cordova = require('cordova'); + +// file: lib/scripts/bootstrap.js + +(function (context) { + // Replace navigator before any modules are required(), to ensure it happens as soon as possible. + // We replace it so that properties that can't be clobbered can instead be overridden. + if (context.navigator) { + var CordovaNavigator = function() {}; + CordovaNavigator.prototype = context.navigator; + context.navigator = new CordovaNavigator(); + } + + var channel = require("cordova/channel"), + _self = { + boot: function () { + /** + * Create all cordova objects once page has fully loaded and native side is ready. + */ + channel.join(function() { + var builder = require('cordova/builder'), + platform = require('cordova/platform'); + + builder.buildIntoButDoNotClobber(platform.defaults, context); + builder.buildIntoAndClobber(platform.clobbers, context); + builder.buildIntoAndMerge(platform.merges, context); + + // Call the platform-specific initialization + platform.initialize(); + + // Fire event to notify that all objects are created + channel.onCordovaReady.fire(); + + // Fire onDeviceReady event once all constructors have run and + // cordova info has been received from native side. + channel.join(function() { + require('cordova').fireDocumentEvent('deviceready'); + }, channel.deviceReadyChannelsArray); + + }, [ channel.onDOMContentLoaded, channel.onNativeReady ]); + } + }; + + // boot up once native side is ready + channel.onNativeReady.subscribe(_self.boot); + + // _nativeReady is global variable that the native side can set + // to signify that the native code is ready. It is a global since + // it may be called before any cordova JS is ready. + if (window._nativeReady) { + channel.onNativeReady.fire(); + } + +}(window)); + + +})();
\ No newline at end of file |