Updated Branches: refs/heads/master e7b16f4b0 -> a07ed8dad
Tweaks to iOS XHR bridge: Call [[self client] didReceiveResponse] when simulating a response - This fixes the CFNetwork log message. When XHR bundles payload, do not handle the request. When XHR does *not* bundle payload, use the new "rc" field to only check for messages once instead of multiple times. This fixes https://issues.apache.org/jira/browse/CB-1576 Project: http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/commit/a07ed8da Tree: http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/tree/a07ed8da Diff: http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/diff/a07ed8da Branch: refs/heads/master Commit: a07ed8dad7360b3f995a376f36710ca9396a057f Parents: e7b16f4 Author: Andrew Grieve <agri...@chromium.org> Authored: Wed Oct 3 13:33:33 2012 -0400 Committer: Andrew Grieve <agri...@chromium.org> Committed: Wed Oct 3 13:39:43 2012 -0400 ---------------------------------------------------------------------- CordovaLib/Classes/CDVURLProtocol.h | 6 - CordovaLib/Classes/CDVURLProtocol.m | 74 ++- CordovaLib/Classes/CDVViewController.h | 6 +- CordovaLib/Classes/CDVViewController.m | 12 + bin/templates/project/www/cordova-2.1.0.js | 751 +++++++++++++++++++++-- 5 files changed, 755 insertions(+), 94 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/a07ed8da/CordovaLib/Classes/CDVURLProtocol.h ---------------------------------------------------------------------- diff --git a/CordovaLib/Classes/CDVURLProtocol.h b/CordovaLib/Classes/CDVURLProtocol.h index 055b88c..705667a 100644 --- a/CordovaLib/Classes/CDVURLProtocol.h +++ b/CordovaLib/Classes/CDVURLProtocol.h @@ -40,9 +40,3 @@ + (void)registerViewController:(CDVViewController*)viewController; + (void)unregisterViewController:(CDVViewController*)viewController; @end - -@interface CDVHTTPURLResponse : NSHTTPURLResponse {} - -- (CDVHTTPURLResponse*)initWithUnauthorizedURL:(NSURL*)url; - -@end http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/a07ed8da/CordovaLib/Classes/CDVURLProtocol.m ---------------------------------------------------------------------- diff --git a/CordovaLib/Classes/CDVURLProtocol.m b/CordovaLib/Classes/CDVURLProtocol.m index d14b087..e515a8c 100644 --- a/CordovaLib/Classes/CDVURLProtocol.m +++ b/CordovaLib/Classes/CDVURLProtocol.m @@ -21,7 +21,13 @@ #import "CDVWhitelist.h" #import "CDVViewController.h" -static CDVWhitelist* gWhitelist = nil; +@interface CDVHTTPURLResponse : NSHTTPURLResponse +- (id)initWithUnauthorizedURL:(NSURL*)url; +- (id)initWithBlankResponse:(NSURL*)url; +@property (nonatomic) NSInteger statusCode; +@end + +static CDVWhitelist * gWhitelist = nil; // Contains a set of NSNumbers of addresses of controllers. It doesn't store // the actual pointer to avoid retaining. static NSMutableSet* gRegisteredControllers = nil; @@ -73,22 +79,33 @@ static NSMutableSet* gRegisteredControllers = nil; } long long viewControllerAddress = [viewControllerAddressStr longLongValue]; // Ensure that the CDVViewController has not been dealloc'ed. + CDVViewController* viewController = nil; @synchronized(gRegisteredControllers) { if (![gRegisteredControllers containsObject:[NSNumber numberWithLongLong:viewControllerAddress]]) { return NO; } - CDVViewController* viewController = (__bridge CDVViewController*)(void*)viewControllerAddress; + viewController = (__bridge CDVViewController*)(void*)viewControllerAddress; + } - NSString* queuedCommandsJSON = [theRequest valueForHTTPHeaderField:@"cmds"]; - if ([queuedCommandsJSON length] > 0) { - [viewController performSelectorOnMainThread:@selector(executeCommandsFromJson:) withObject:queuedCommandsJSON waitUntilDone:NO]; - } else { - [viewController performSelectorOnMainThread:@selector(flushCommandQueue) withObject:nil waitUntilDone:NO]; - } + NSString* queuedCommandsJSON = [theRequest valueForHTTPHeaderField:@"cmds"]; + NSString* requestId = [theRequest valueForHTTPHeaderField:@"rc"]; + if (requestId == nil) { + NSLog(@"!cordova request missing rc header"); + return NO; + } + BOOL hasCmds = [queuedCommandsJSON length] > 0; + if (hasCmds) { + SEL sel = @selector(executeCommandsFromJson:); + [viewController performSelectorOnMainThread:sel withObject:queuedCommandsJSON waitUntilDone:NO]; + } else { + SEL sel = @selector(maybeFlushCommandQueue:); + [viewController performSelectorOnMainThread:sel withObject:[NSNumber numberWithInteger:[requestId integerValue]] waitUntilDone:NO]; } // Returning NO here would be 20% faster, but it spams WebInspector's console with failure messages. // If JS->Native bridge speed is really important for an app, they should use the iframe bridge. - return YES; + // Returning YES here causes the request to come through canInitWithRequest two more times. + // For this reason, we return NO when cmds exist. + return !hasCmds; } // we only care about http and https connections @@ -112,6 +129,8 @@ static NSMutableSet* gRegisteredControllers = nil; 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]; return; } @@ -140,28 +159,29 @@ static NSMutableSet* gRegisteredControllers = nil; @end @implementation CDVHTTPURLResponse +@synthesize statusCode; -- (id)initWithUnauthorizedURL:(__unsafe_unretained NSURL*)url +- (id)initWithUnauthorizedURL:(NSURL*)url { - NSInteger statusCode = 401; - NSDictionary* __unsafe_unretained headerFields = [NSDictionary dictionaryWithObject:@"Digest realm = \"Cordova.plist/ExternalHosts\"" forKey:@"WWW-Authenticate"]; - double requestTime = 1; - - SEL selector = NSSelectorFromString(@"initWithURL:statusCode:headerFields:requestTime:"); - NSMethodSignature* signature = [self methodSignatureForSelector:selector]; - - NSInvocation* inv = [NSInvocation invocationWithMethodSignature:signature]; - - [inv setTarget:self]; - [inv setSelector:selector]; - [inv setArgument:&url atIndex:2]; - [inv setArgument:&statusCode atIndex:3]; - [inv setArgument:&headerFields atIndex:4]; - [inv setArgument:&requestTime atIndex:5]; - - [inv invoke]; + self = [super initWithURL:url MIMEType:@"text/plain" expectedContentLength:-1 textEncodingName:@"UTF-8"]; + if (self) { + self.statusCode = 401; + } + return self; +} +- (id)initWithBlankResponse:(NSURL*)url +{ + self = [super initWithURL:url MIMEType:@"text/plain" expectedContentLength:-1 textEncodingName:@"UTF-8"]; + if (self) { + self.statusCode = 200; + } return self; } +- (NSDictionary*)allHeaderFields +{ + return nil; +} + @end http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/a07ed8da/CordovaLib/Classes/CDVViewController.h ---------------------------------------------------------------------- diff --git a/CordovaLib/Classes/CDVViewController.h b/CordovaLib/Classes/CDVViewController.h index c867814..fed48aa 100644 --- a/CordovaLib/Classes/CDVViewController.h +++ b/CordovaLib/Classes/CDVViewController.h @@ -24,7 +24,10 @@ #import "CDVCommandDelegate.h" #import "CDVWhitelist.h" -@interface CDVViewController : UIViewController <UIWebViewDelegate, CDVCommandDelegate>{} +@interface CDVViewController : UIViewController <UIWebViewDelegate, CDVCommandDelegate>{ + @private + NSInteger _lastCommandQueueFlushRequestId; +} @property (nonatomic, strong) IBOutlet CDVCordovaView* webView; @@ -51,6 +54,7 @@ - (void)createGapView; - (CDVCordovaView*)newCordovaViewWithFrame:(CGRect)bounds; +- (void)maybeFlushCommandQueue:(NSNumber*)requestId; - (int)executeCommandsFromJson:(NSString*)queuedCommandsJSON; - (void)flushCommandQueue; http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/a07ed8da/CordovaLib/Classes/CDVViewController.m ---------------------------------------------------------------------- diff --git a/CordovaLib/Classes/CDVViewController.m b/CordovaLib/Classes/CDVViewController.m index a0a29f0..003059b 100644 --- a/CordovaLib/Classes/CDVViewController.m +++ b/CordovaLib/Classes/CDVViewController.m @@ -485,6 +485,7 @@ */ - (void)webViewDidStartLoad:(UIWebView*)theWebView { + _lastCommandQueueFlushRequestId = 0; [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginResetNotification object:nil]]; } @@ -825,6 +826,17 @@ BOOL gSplashScreenShown = NO; return [queuedCommands count]; } +- (void)maybeFlushCommandQueue:(NSNumber*)requestId +{ + // Use the request ID to determine if we've already flushed for this request. + // This is required only because the NSURLProtocol enqueues the same request + // multiple times. + if ([requestId integerValue] > _lastCommandQueueFlushRequestId) { + _lastCommandQueueFlushRequestId = [requestId integerValue]; + [self flushCommandQueue]; + } +} + /** * Repeatedly fetches and executes the command queue until it is empty. */ http://git-wip-us.apache.org/repos/asf/incubator-cordova-ios/blob/a07ed8da/bin/templates/project/www/cordova-2.1.0.js ---------------------------------------------------------------------- diff --git a/bin/templates/project/www/cordova-2.1.0.js b/bin/templates/project/www/cordova-2.1.0.js index cce90a9..8d02a6c 100644 --- a/bin/templates/project/www/cordova-2.1.0.js +++ b/bin/templates/project/www/cordova-2.1.0.js @@ -1,6 +1,6 @@ -// commit d30179b30152b9383a80637e609cf2d785e1aa3e +// commit 27d9aedd5bf520f305e20fcf350da2eef244d53d -// File generated at :: Tue Sep 18 2012 11:34:26 GMT-0400 (EDT) +// File generated at :: Wed Oct 03 2012 13:16:23 GMT-0400 (EDT) /* Licensed to the Apache Software Foundation (ASF) under one @@ -137,7 +137,7 @@ window.addEventListener = function(evt, handler, capture) { document.removeEventListener = function(evt, handler, capture) { var e = evt.toLowerCase(); - // If unsubcribing from an event that is handled by a plugin + // If unsubscribing from an event that is handled by a plugin if (typeof documentEventHandlers[e] != "undefined") { documentEventHandlers[e].unsubscribe(handler); } else { @@ -147,7 +147,7 @@ document.removeEventListener = function(evt, handler, capture) { window.removeEventListener = function(evt, handler, capture) { var e = evt.toLowerCase(); - // If unsubcribing from an event that is handled by a plugin + // If unsubscribing from an event that is handled by a plugin if (typeof windowEventHandlers[e] != "undefined") { windowEventHandlers[e].unsubscribe(handler); } else { @@ -196,7 +196,7 @@ var cordova = { delete documentEventHandlers[event]; }, /** - * Retreive original event handlers that were replaced by Cordova + * Retrieve original event handlers that were replaced by Cordova * * @return object */ @@ -245,7 +245,9 @@ var cordova = { /** * Plugin callback mechanism. */ - callbackId: 0, + // 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, @@ -412,7 +414,7 @@ function recursiveMerge(target, src) { module.exports = { build: function (objects) { return { - intoButDontClobber: function (target) { + intoButDoNotClobber: function (target) { include(target, objects, false, false); }, intoAndClobber: function(target) { @@ -729,6 +731,9 @@ module.exports = { geolocation: { path: 'cordova/plugin/geolocation' }, + globalization: { + path: 'cordova/plugin/globalization' + }, network: { children: { connection: { @@ -844,6 +849,9 @@ module.exports = { Flags: { path: 'cordova/plugin/Flags' }, + GlobalizationError: { + path: 'cordova/plugin/GlobalizationError' + }, LocalFileSystem: { path: 'cordova/plugin/LocalFileSystem' }, @@ -906,7 +914,8 @@ var cordova = require('cordova'), // doesn't exist in 4.X devices anyways. bridgeMode = navigator.userAgent.indexOf(' 4_') == -1 ? jsToNativeModes.XHR_NO_PAYLOAD : jsToNativeModes.IFRAME_NAV, execIframe, - execXhr; + execXhr, + requestCount = 0; function createExecIframe() { var iframe = document.createElement("iframe"); @@ -916,10 +925,10 @@ function createExecIframe() { } function shouldBundleCommandJson() { - if (bridgeMode == 2) { + if (bridgeMode == jsToNativeModes.XHR_WITH_PAYLOAD) { return true; } - if (bridgeMode == 3) { + if (bridgeMode == jsToNativeModes.XHR_OPTIONAL_PAYLOAD) { var payloadLength = 0; for (var i = 0; i < cordova.commandQueue.length; ++i) { payloadLength += cordova.commandQueue[i].length; @@ -975,17 +984,22 @@ function iOSExec() { // the command is executed. cordova.commandQueue.push(JSON.stringify(command)); - // If the queue length is 1, then that means it was empty before we queued - // the given command, so let the native side know that we have some - // commands to execute, unless the queue is currently being flushed, in - // which case the command will be picked up without notification. - if (cordova.commandQueue.length == 1 && !cordova.commandQueueFlushing) { - if (bridgeMode) { + if (!cordova.commandQueueFlushing) { + if (bridgeMode != jsToNativeModes.IFRAME_NAV) { + // Re-using the XHR improves exec() performance by about 10%. + // It is possible for a native stringByEvaluatingJavascriptFromString call + // to cause us to reach this point when a request is already in progress, + // so we check the readyState to guard agains re-using an inprogress XHR. + // Refer to CB-1404. + if (execXhr && execXhr.readyState != 4) { + execXhr = null; + } execXhr = execXhr || new XMLHttpRequest(); - // Changeing this to a GET will make the XHR reach the URIProtocol on 4.2. + // Changing this to a GET will make the XHR reach the URIProtocol on 4.2. // For some reason it still doesn't work though... - execXhr.open('HEAD', "file:///!gap_exec", true); + execXhr.open('HEAD', "/!gap_exec", true); execXhr.setRequestHeader('vc', cordova.iOSVCAddr); + execXhr.setRequestHeader('rc', ++requestCount); if (shouldBundleCommandJson()) { execXhr.setRequestHeader('cmds', nativecomm()); } @@ -1109,6 +1123,7 @@ for (var key in Camera) { * @param {Object} options */ cameraExport.getPicture = function(successCallback, errorCallback, options) { + options = options || {}; // successCallback required if (typeof successCallback != "function") { console.log("Camera Error: successCallback is not a function"); @@ -1122,9 +1137,9 @@ cameraExport.getPicture = function(successCallback, errorCallback, options) { } var quality = 50; - if (options && typeof options.quality == "number") { + if (typeof options.quality == "number") { quality = options.quality; - } else if (options && typeof options.quality == "string") { + } else if (typeof options.quality == "string") { var qlity = parseInt(options.quality, 10); if (isNaN(qlity) === false) { quality = qlity.valueOf(); @@ -1347,7 +1362,7 @@ define("cordova/plugin/CompassError", function(require, exports, module) { /** * CompassError. - * An error code assigned by an implementation when an error has occured + * An error code assigned by an implementation when an error has occurred * @constructor */ var CompassError = function(err) { @@ -1633,7 +1648,7 @@ define("cordova/plugin/ContactError", function(require, exports, module) { /** * ContactError. - * An error code assigned by an implementation when an error has occured + * An error code assigned by an implementation when an error has occurred * @constructor */ var ContactError = function(err) { @@ -1840,7 +1855,7 @@ DirectoryEntry.prototype.createReader = function() { * 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 excluively create the 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 */ @@ -1872,7 +1887,7 @@ DirectoryEntry.prototype.removeRecursively = function(successCallback, errorCall * 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 excluively create the 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 */ @@ -2316,7 +2331,7 @@ var FileReader = function() { // Event handlers this.onloadstart = null; // When the read starts. - this.onprogress = null; // While reading (and decoding) file or fileBlob data, and reporting partial file data (progess.loaded/progress.total) + this.onprogress = null; // While reading (and decoding) file or fileBlob data, and reporting partial file data (progress.loaded/progress.total) this.onload = null; // When the read has successfully completed. this.onerror = null; // When the read has failed (see errors). this.onloadend = null; // When the request has completed (either in success or failure). @@ -2570,13 +2585,27 @@ module.exports = FileSystem; define("cordova/plugin/FileTransfer", function(require, exports, module) { var exec = require('cordova/exec'), - FileTransferError = require('cordova/plugin/FileTransferError'); + 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() {}; +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 @@ -2619,7 +2648,17 @@ FileTransfer.prototype.upload = function(filePath, server, successCallback, erro errorCallback(error); }; - exec(successCallback, fail, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode, headers]); + var self = this; + var win = function(result) { + if (typeof result.lengthComputable != "undefined") { + if (self.onprogress) { + return self.onprogress(newProgressEvent(result)); + } + } else { + return successCallback(result); + } + }; + exec(win, fail, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode, headers, this._id]); }; /** @@ -2628,23 +2667,31 @@ FileTransfer.prototype.upload = function(filePath, server, successCallback, erro * @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) { +FileTransfer.prototype.download = function(source, target, successCallback, errorCallback, trustAllHosts) { // sanity parameter checking if (!source || !target) throw new Error("FileTransfer.download requires source URI and target URI parameters at the minimum."); + var self = this; var win = function(result) { - var entry = null; - if (result.isDirectory) { - entry = new (require('cordova/plugin/DirectoryEntry'))(); - } - else if (result.isFile) { - entry = new (require('cordova/plugin/FileEntry'))(); + if (typeof result.lengthComputable != "undefined") { + if (self.onprogress) { + return self.onprogress(newProgressEvent(result)); + } + } else { + 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); } - entry.isDirectory = result.isDirectory; - entry.isFile = result.isFile; - entry.name = result.name; - entry.fullPath = result.fullPath; - successCallback(entry); }; var fail = function(e) { @@ -2652,9 +2699,18 @@ FileTransfer.prototype.download = function(source, target, successCallback, erro errorCallback(error); }; - exec(win, errorCallback, 'FileTransfer', 'download', [source, target]); + exec(win, errorCallback, '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; }); @@ -2676,6 +2732,7 @@ var FileTransferError = function(code, source, target, status) { FileTransferError.FILE_NOT_FOUND_ERR = 1; FileTransferError.INVALID_URL_ERR = 2; FileTransferError.CONNECTION_ERR = 3; +FileTransferError.ABORT_ERR = 4; module.exports = FileTransferError; @@ -3003,6 +3060,32 @@ 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/LocalFileSystem.js define("cordova/plugin/LocalFileSystem", function(require, exports, module) { @@ -3493,7 +3576,7 @@ function removeListeners(l) { var accelerometer = { /** - * Asynchronously aquires the current acceleration. + * 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) @@ -3524,7 +3607,7 @@ var accelerometer = { }, /** - * Asynchronously aquires the acceleration repeatedly at a given interval. + * 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) @@ -4011,7 +4094,7 @@ console.table = function(data, columns) { //------------------------------------------------------------------------------ // return a new function that calls both functions passed as args //------------------------------------------------------------------------------ -function wrapperedOrigCall(orgFunc, newFunc) { +function wrappedOrigCall(orgFunc, newFunc) { return function() { var args = [].slice.call(arguments); try { orgFunc.apply(WinConsole, args); } catch (e) {} @@ -4026,7 +4109,7 @@ function wrapperedOrigCall(orgFunc, newFunc) { //------------------------------------------------------------------------------ for (var key in console) { if (typeof WinConsole[key] == "function") { - console[key] = wrapperedOrigCall(WinConsole[key], console[key]); + console[key] = wrappedOrigCall(WinConsole[key], console[key]); } } @@ -4171,7 +4254,7 @@ define("cordova/plugin/echo", function(require, exports, module) { var exec = require('cordova/exec'); /** - * Sends the given message through exec() to the Echo plugink, which sends it back to the successCallback. + * 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. @@ -4238,7 +4321,7 @@ function createTimeout(errorCallback, timeout) { var geolocation = { lastPosition:null, // reference to last known (cached) position returned /** - * Asynchronously aquires the current position. + * 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) @@ -4384,6 +4467,545 @@ module.exports = geolocation; }); +// file: lib/common/plugin/globalization.js +define("cordova/plugin/globalization", function(require, exports, module) { + +var 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) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.getPreferredLanguage Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.getPreferredLanguage Error: failureCB is not a function"); + return; + } + + 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) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.getLocaleName Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.getLocaleName Error: failureCB is not a function"); + return; + } + 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) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.dateToString Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.dateToString Error: failureCB is not a function"); + return; + } + + + if (date instanceof Date){ + var dateValue; + dateValue = date.valueOf(); + exec(successCB, failureCB, "Globalization", "dateToString", [{"date": dateValue, "options": options}]); + } + else { + console.log("Globalization.dateToString Error: date is not a Date object"); + } +}, + + +/** +* 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) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.stringToDate Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.stringToDate Error: failureCB is not a function"); + return; + } + if (typeof dateString == "string"){ + exec(successCB, failureCB, "Globalization", "stringToDate", [{"dateString": dateString, "options": options}]); + } + else { + console.log("Globalization.stringToDate Error: dateString is not a string"); + } +}, + + +/** +* 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) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.getDatePattern Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.getDatePattern Error: failureCB is not a function"); + return; + } + + 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) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.getDateNames Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.getDateNames Error: failureCB is not a function"); + return; + } + 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) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.isDayLightSavingsTime Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.isDayLightSavingsTime Error: failureCB is not a function"); + return; + } + + + if (date instanceof Date){ + var dateValue; + dateValue = date.valueOf(); + exec(successCB, failureCB, "Globalization", "isDayLightSavingsTime", [{"date": dateValue}]); + } + else { + console.log("Globalization.isDayLightSavingsTime Error: date is not a Date object"); + } + +}, + +/** +* 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) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.getFirstDayOfWeek Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.getFirstDayOfWeek Error: failureCB is not a function"); + return; + } + + 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) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.numberToString Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.numberToString Error: failureCB is not a function"); + return; + } + + if(typeof number == "number") { + exec(successCB, failureCB, "Globalization", "numberToString", [{"number": number, "options": options}]); + } + else { + console.log("Globalization.numberToString Error: number is not a number"); + } +}, + +/** +* 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) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.stringToNumber Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.stringToNumber Error: failureCB is not a function"); + return; + } + + if(typeof numberString == "string") { + exec(successCB, failureCB, "Globalization", "stringToNumber", [{"numberString": numberString, "options": options}]); + } + else { + console.log("Globalization.stringToNumber Error: numberString is not a string"); + } +}, + +/** +* 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) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.getNumberPattern Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.getNumberPattern Error: failureCB is not a function"); + return; + } + + 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) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.getCurrencyPattern Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.getCurrencyPattern Error: failureCB is not a function"); + return; + } + + if(typeof currencyCode == "string") { + exec(successCB, failureCB, "Globalization", "getCurrencyPattern", [{"currencyCode": currencyCode}]); + } + else { + console.log("Globalization.getCurrencyPattern Error: currencyCode is not a currency code"); + } +} + +}; + +module.exports = globalization; + +}); + // file: lib/ios/plugin/ios/Contact.js define("cordova/plugin/ios/Contact", function(require, exports, module) { @@ -4540,7 +5162,6 @@ var exec = require('cordova/exec'); * @constructor */ var DebugConsole = function() { - this.winConsole = window.console; this.logLevel = DebugConsole.INFO_LEVEL; }; @@ -4555,7 +5176,10 @@ DebugConsole.prototype.setLevel = function(level) { this.logLevel = level; }; -var stringify = function(message) { +/** + * create a nice string for an object + */ +function stringify(message) { try { if (typeof message === "object" && JSON && JSON.stringify) { try { @@ -4573,16 +5197,25 @@ var stringify = function(message) { }; /** + * remember the original console and it's methods + */ +var origConsole = window.console || {} + +var origConsole_log = origConsole.log || function(){} +var origConsole_warn = origConsole.warn || function(){} +var origConsole_error = origConsole.error || function(){} + + +/** * Print a normal log message to the console * @param {Object|String} message Message or object to print to the console */ DebugConsole.prototype.log = function(message) { + origConsole_log.apply(origConsole, arguments) + if (this.logLevel <= DebugConsole.INFO_LEVEL) { exec(null, null, 'Debug Console', 'log', [ stringify(message), { logLevel: 'INFO' } ]); } - else if (this.winConsole && this.winConsole.log) { - this.winConsole.log(message); - } }; /** @@ -4590,12 +5223,11 @@ DebugConsole.prototype.log = function(message) { * @param {Object|String} message Message or object to print to the console */ DebugConsole.prototype.warn = function(message) { + origConsole_warn.apply(origConsole, arguments) + if (this.logLevel <= DebugConsole.WARN_LEVEL) { exec(null, null, 'Debug Console', 'log', [ stringify(message), { logLevel: 'WARN' } ]); } - else if (this.winConsole && this.winConsole.warn) { - this.winConsole.warn(message); - } }; /** @@ -4603,12 +5235,11 @@ DebugConsole.prototype.warn = function(message) { * @param {Object|String} message Message or object to print to the console */ DebugConsole.prototype.error = function(message) { + origConsole_error.apply(origConsole, arguments) + if (this.logLevel <= DebugConsole.ERROR_LEVEL) { exec(null, null, 'Debug Console', 'log', [ stringify(message), { logLevel: 'ERROR' } ]); } - else if (this.winConsole && this.winConsole.error){ - this.winConsole.error(message); - } }; module.exports = new DebugConsole(); @@ -5234,7 +5865,7 @@ utils.clone = function(obj) { }; /** - * Returns a wrappered version of the function + * Returns a wrapped version of the function */ utils.close = function(context, func, params) { if (typeof params == 'undefined') { @@ -5405,7 +6036,7 @@ window.cordova = require('cordova'); platform = require('cordova/platform'); // Drop the common globals into the window object, but be nice and don't overwrite anything. - builder.build(base.objects).intoButDontClobber(window); + builder.build(base.objects).intoButDoNotClobber(window); // Drop the platform-specific globals into the window object // and clobber any existing object.