You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ag...@apache.org on 2014/04/17 18:15:08 UTC
js commit: Add async version of base64 encode/decode for android exec
bridge.
Repository: cordova-js
Updated Branches:
refs/heads/async_base64_android [created] 99e4f88e5
Add async version of base64 encode/decode for android exec bridge.
Project: http://git-wip-us.apache.org/repos/asf/cordova-js/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-js/commit/99e4f88e
Tree: http://git-wip-us.apache.org/repos/asf/cordova-js/tree/99e4f88e
Diff: http://git-wip-us.apache.org/repos/asf/cordova-js/diff/99e4f88e
Branch: refs/heads/async_base64_android
Commit: 99e4f88e5d75798957b900085bf2568a71cea433
Parents: b0a18f8
Author: Andrew Grieve <ag...@chromium.org>
Authored: Thu Apr 17 11:38:58 2014 -0400
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Thu Apr 17 12:14:53 2014 -0400
----------------------------------------------------------------------
NOTICE | 25 ++++
src/android/exec.js | 78 ++++++----
src/common/base64.js | 52 ++++++-
src/common/init.js | 2 +
src/common/third_party/setImmediate.js | 218 ++++++++++++++++++++++++++++
5 files changed, 342 insertions(+), 33 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cordova-js/blob/99e4f88e/NOTICE
----------------------------------------------------------------------
diff --git a/NOTICE b/NOTICE
index 8ec56a5..5f0d95b 100644
--- a/NOTICE
+++ b/NOTICE
@@ -3,3 +3,28 @@ Copyright 2012 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).
+
+
+setImmediate polyfill: https://github.com/YuzuJS/setImmediate
+setImmediate license (MIT):
+
+Copyright (c) 2012 Barnesandnoble.com, llc, Donavon West, and Domenic Denicola
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
http://git-wip-us.apache.org/repos/asf/cordova-js/blob/99e4f88e/src/android/exec.js
----------------------------------------------------------------------
diff --git a/src/android/exec.js b/src/android/exec.js
index 7eca8c6..1dfa4f8 100644
--- a/src/android/exec.js
+++ b/src/android/exec.js
@@ -65,6 +65,19 @@ var cordova = require('cordova'),
pollEnabled = false,
messagesFromNative = [];
+function encodeParamAsync(arr, i, cb) {
+ if (utils.typeName(arr[i]) == 'ArrayBuffer') {
+ if (cordova.ASYNC_AB_ENCODE) {
+ base64.fromArrayBufferAsync(arr[i], function(a) {arr[i]=a;cb();});
+ } else {
+ arr[i] = base64.fromArrayBuffer(arr[i]);
+ cb();
+ }
+ } else {
+ cb();
+ }
+}
+
function androidExec(success, fail, service, action, args) {
// Set default bridge modes if they have not already been set.
// By default, we use the failsafe, since addJavascriptInterface breaks too often
@@ -72,33 +85,38 @@ function androidExec(success, fail, service, action, args) {
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
}
+ var cnt = 0;
// Process any ArrayBuffers in the args into a string.
for (var i = 0; i < args.length; i++) {
- if (utils.typeName(args[i]) == 'ArrayBuffer') {
- args[i] = base64.fromArrayBuffer(args[i]);
- }
+ encodeParamAsync(args, i, function() {
+ if (++cnt == args.length) afterArgs();
+ });
}
-
- var callbackId = service + cordova.callbackId++,
- argsJson = JSON.stringify(args);
-
- if (success || fail) {
- cordova.callbacks[callbackId] = {success:success, fail:fail};
+ if (args.length === 0) {
+ afterArgs();
}
+ function afterArgs() {
+ var callbackId = service + cordova.callbackId++,
+ argsJson = JSON.stringify(args);
- if (jsToNativeBridgeMode == jsToNativeModes.LOCATION_CHANGE) {
- window.location = 'http://cdv_exec/' + service + '#' + action + '#' + callbackId + '#' + argsJson;
- } else {
- var messages = nativeApiProvider.get().exec(service, action, callbackId, argsJson);
- // If argsJson was received by Java as null, try again with the PROMPT bridge mode.
- // This happens in rare circumstances, such as when certain Unicode characters are passed over the bridge on a Galaxy S2. See CB-2666.
- if (jsToNativeBridgeMode == jsToNativeModes.JS_OBJECT && messages === "@Null arguments.") {
- androidExec.setJsToNativeBridgeMode(jsToNativeModes.PROMPT);
- androidExec(success, fail, service, action, args);
- androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
- return;
+ if (success || fail) {
+ cordova.callbacks[callbackId] = {success:success, fail:fail};
+ }
+
+ if (jsToNativeBridgeMode == jsToNativeModes.LOCATION_CHANGE) {
+ window.location = 'http://cdv_exec/' + service + '#' + action + '#' + callbackId + '#' + argsJson;
} else {
- androidExec.processMessages(messages, true);
+ var messages = nativeApiProvider.get().exec(service, action, callbackId, argsJson);
+ // If argsJson was received by Java as null, try again with the PROMPT bridge mode.
+ // This happens in rare circumstances, such as when certain Unicode characters are passed over the bridge on a Galaxy S2. See CB-2666.
+ if (jsToNativeBridgeMode == jsToNativeModes.JS_OBJECT && messages === "@Null arguments.") {
+ androidExec.setJsToNativeBridgeMode(jsToNativeModes.PROMPT);
+ androidExec(success, fail, service, action, args);
+ androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
+ return;
+ } else {
+ androidExec.processMessages(messages, true);
+ }
}
}
}
@@ -193,18 +211,17 @@ function processMessage(message) {
payload = +message.slice(nextSpaceIdx + 2);
} else if (payloadKind == 'A') {
var data = message.slice(nextSpaceIdx + 2);
- var bytes = window.atob(data);
- var arraybuffer = new Uint8Array(bytes.length);
- for (var i = 0; i < bytes.length; i++) {
- arraybuffer[i] = bytes.charCodeAt(i);
+ if (cordova.ASYNC_AB_DECODE) {
+ base64.toArrayBufferAsync(data, afterDecode);
+ return;
}
- payload = arraybuffer.buffer;
+ payload = base64.toArrayBuffer(data);
} else if (payloadKind == 'S') {
payload = window.atob(message.slice(nextSpaceIdx + 2));
} else {
payload = JSON.parse(message.slice(nextSpaceIdx + 1));
}
- cordova.callbackFromNative(callbackId, success, status, [payload], keepCallback);
+ afterDecode(payload);
} else {
console.log("processMessage failed: invalid message: " + JSON.stringify(message));
}
@@ -213,6 +230,9 @@ function processMessage(message) {
console.log("processMessage failed: Stack: " + e.stack);
console.log("processMessage failed: Message: " + message);
}
+ function afterDecode(arg) {
+ cordova.callbackFromNative(callbackId, success, status, [arg], keepCallback);
+ }
}
var isProcessing = false;
@@ -227,7 +247,7 @@ androidExec.processMessages = function(messages, opt_useTimeout) {
return;
}
if (opt_useTimeout) {
- window.setTimeout(androidExec.processMessages, 0);
+ setImmediate(androidExec.processMessages);
return;
}
isProcessing = true;
@@ -238,7 +258,7 @@ androidExec.processMessages = function(messages, opt_useTimeout) {
// The Java side can send a * message to indicate that it
// still has messages waiting to be retrieved.
if (msg == '*' && messagesFromNative.length === 0) {
- setTimeout(pollOnce, 0);
+ setImmediate(pollOnce);
return;
}
processMessage(msg);
http://git-wip-us.apache.org/repos/asf/cordova-js/blob/99e4f88e/src/common/base64.js
----------------------------------------------------------------------
diff --git a/src/common/base64.js b/src/common/base64.js
index 5181390..06d6974 100644
--- a/src/common/base64.js
+++ b/src/common/base64.js
@@ -21,19 +21,63 @@
var base64 = exports;
+function blobFromArrayBuffer(arrayBuffer) {
+ try {
+ return new Blob([arrayBuffer]);
+ } catch (e) {
+ if (window.WebKitBlobBuilder) {
+ var bb = new window.WebKitBlobBuilder();
+ bb.append(arrayBuffer);
+ return bb.getBlob();
+ }
+ }
+ return null;
+}
+
base64.fromArrayBuffer = function(arrayBuffer) {
var array = new Uint8Array(arrayBuffer);
return uint8ToBase64(array);
};
+base64.fromArrayBufferAsync = function(arrayBuffer, cb) {
+ var blob = blobFromArrayBuffer(arrayBuffer);
+ if (!blob) {
+ setImmediate(function() {
+ cb(base64.fromArrayBuffer(arrayBuffer));
+ });
+ } else {
+ var fr = new FileReader();
+ fr.onloadend = function(e) {
+ cb(fr.result.slice(fr.result.indexOf(',') + 1));
+ };
+ fr.readAsDataURL(blob);
+ }
+};
+
base64.toArrayBuffer = function(str) {
var decodedStr = typeof atob != 'undefined' ? atob(str) : new Buffer(str,'base64').toString('binary');
- var arrayBuffer = new ArrayBuffer(decodedStr.length);
- var array = new Uint8Array(arrayBuffer);
- for (var i=0, len=decodedStr.length; i < len; i++) {
+ var len = decodedStr.length;
+ var array = new Uint8Array(len);
+ for (var i=0; i < len; ++i) {
array[i] = decodedStr.charCodeAt(i);
}
- return arrayBuffer;
+ return array.buffer;
+};
+
+base64.toArrayBufferAsync = function(str, cb) {
+ var xhr = new XMLHttpRequest();
+ xhr.responseType = 'arraybuffer';
+ if (xhr.responseType != 'arraybuffer') {
+ setImmediate(function() {
+ cb(base64.toArrayBuffer(str));
+ });
+ } else {
+ xhr.open('GET', 'data:;base64,' + str, true);
+ xhr.onload = function() {
+ cb(xhr.response);
+ };
+ xhr.send();
+ }
};
//------------------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cordova-js/blob/99e4f88e/src/common/init.js
----------------------------------------------------------------------
diff --git a/src/common/init.js b/src/common/init.js
index 4f4882c..a63dc1a 100644
--- a/src/common/init.js
+++ b/src/common/init.js
@@ -19,6 +19,8 @@
*
*/
+require('cordova/third_party/setImmediate');
+
var channel = require('cordova/channel');
var cordova = require('cordova');
var modulemapper = require('cordova/modulemapper');
http://git-wip-us.apache.org/repos/asf/cordova-js/blob/99e4f88e/src/common/third_party/setImmediate.js
----------------------------------------------------------------------
diff --git a/src/common/third_party/setImmediate.js b/src/common/third_party/setImmediate.js
new file mode 100644
index 0000000..f034bb4
--- /dev/null
+++ b/src/common/third_party/setImmediate.js
@@ -0,0 +1,218 @@
+(function (global, undefined) {
+ "use strict";
+
+ var tasks = (function () {
+ function Task(handler, args) {
+ this.handler = handler;
+ this.args = args;
+ }
+ Task.prototype.run = function () {
+ // See steps in section 5 of the spec.
+ if (typeof this.handler === "function") {
+ // Choice of `thisArg` is not in the setImmediate spec; `undefined` is in the setTimeout spec though:
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html
+ this.handler.apply(undefined, this.args);
+ } else {
+ var scriptSource = "" + this.handler;
+ /*jshint evil: true */
+ eval(scriptSource);
+ }
+ };
+
+ var nextHandle = 1; // Spec says greater than zero
+ var tasksByHandle = {};
+ var currentlyRunningATask = false;
+
+ return {
+ addFromSetImmediateArguments: function (args) {
+ var handler = args[0];
+ var argsToHandle = Array.prototype.slice.call(args, 1);
+ var task = new Task(handler, argsToHandle);
+
+ var thisHandle = nextHandle++;
+ tasksByHandle[thisHandle] = task;
+ return thisHandle;
+ },
+ runIfPresent: function (handle) {
+ // From the spec: "Wait until any invocations of this algorithm started before this one have completed."
+ // So if we're currently running a task, we'll need to delay this invocation.
+ if (!currentlyRunningATask) {
+ var task = tasksByHandle[handle];
+ if (task) {
+ currentlyRunningATask = true;
+ try {
+ task.run();
+ } finally {
+ delete tasksByHandle[handle];
+ currentlyRunningATask = false;
+ }
+ }
+ } else {
+ // Delay by doing a setTimeout. setImmediate was tried instead, but in Firefox 7 it generated a
+ // "too much recursion" error.
+ global.setTimeout(function () {
+ tasks.runIfPresent(handle);
+ }, 0);
+ }
+ },
+ remove: function (handle) {
+ delete tasksByHandle[handle];
+ }
+ };
+ }());
+
+ function canUseNextTick() {
+ // Don't get fooled by e.g. browserify environments.
+ return typeof process === "object" &&
+ Object.prototype.toString.call(process) === "[object process]";
+ }
+
+ function canUseMessageChannel() {
+ return !!global.MessageChannel;
+ }
+
+ function canUsePostMessage() {
+ // The test against `importScripts` prevents this implementation from being installed inside a web worker,
+ // where `global.postMessage` means something completely different and can't be used for this purpose.
+
+ if (!global.postMessage || global.importScripts) {
+ return false;
+ }
+
+ var postMessageIsAsynchronous = true;
+ var oldOnMessage = global.onmessage;
+ global.onmessage = function () {
+ postMessageIsAsynchronous = false;
+ };
+ global.postMessage("", "*");
+ global.onmessage = oldOnMessage;
+
+ return postMessageIsAsynchronous;
+ }
+
+ function canUseReadyStateChange() {
+ return "document" in global && "onreadystatechange" in global.document.createElement("script");
+ }
+
+ function installNextTickImplementation(attachTo) {
+ attachTo.setImmediate = function () {
+ var handle = tasks.addFromSetImmediateArguments(arguments);
+
+ process.nextTick(function () {
+ tasks.runIfPresent(handle);
+ });
+
+ return handle;
+ };
+ }
+
+ function installMessageChannelImplementation(attachTo) {
+ var channel = new global.MessageChannel();
+ channel.port1.onmessage = function (event) {
+ var handle = event.data;
+ tasks.runIfPresent(handle);
+ };
+ attachTo.setImmediate = function () {
+ var handle = tasks.addFromSetImmediateArguments(arguments);
+
+ channel.port2.postMessage(handle);
+
+ return handle;
+ };
+ }
+
+ function installPostMessageImplementation(attachTo) {
+ // Installs an event handler on `global` for the `message` event: see
+ // * https://developer.mozilla.org/en/DOM/window.postMessage
+ // * http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#crossDocumentMessages
+
+ var MESSAGE_PREFIX = "com.bn.NobleJS.setImmediate" + Math.random();
+
+ function isStringAndStartsWith(string, putativeStart) {
+ return typeof string === "string" && string.substring(0, putativeStart.length) === putativeStart;
+ }
+
+ function onGlobalMessage(event) {
+ // This will catch all incoming messages (even from other windows!), so we need to try reasonably hard to
+ // avoid letting anyone else trick us into firing off. We test the origin is still this window, and that a
+ // (randomly generated) unpredictable identifying prefix is present.
+ if (event.source === global && isStringAndStartsWith(event.data, MESSAGE_PREFIX)) {
+ var handle = event.data.substring(MESSAGE_PREFIX.length);
+ tasks.runIfPresent(handle);
+ }
+ }
+ if (global.addEventListener) {
+ global.addEventListener("message", onGlobalMessage, false);
+ } else {
+ global.attachEvent("onmessage", onGlobalMessage);
+ }
+
+ attachTo.setImmediate = function () {
+ var handle = tasks.addFromSetImmediateArguments(arguments);
+
+ // Make `global` post a message to itself with the handle and identifying prefix, thus asynchronously
+ // invoking our onGlobalMessage listener above.
+ global.postMessage(MESSAGE_PREFIX + handle, "*");
+
+ return handle;
+ };
+ }
+
+ function installReadyStateChangeImplementation(attachTo) {
+ attachTo.setImmediate = function () {
+ var handle = tasks.addFromSetImmediateArguments(arguments);
+
+ // Create a <script> element; its readystatechange event will be fired asynchronously once it is inserted
+ // into the document. Do so, thus queuing up the task. Remember to clean up once it's been called.
+ var scriptEl = global.document.createElement("script");
+ scriptEl.onreadystatechange = function () {
+ tasks.runIfPresent(handle);
+
+ scriptEl.onreadystatechange = null;
+ scriptEl.parentNode.removeChild(scriptEl);
+ scriptEl = null;
+ };
+ global.document.documentElement.appendChild(scriptEl);
+
+ return handle;
+ };
+ }
+
+ function installSetTimeoutImplementation(attachTo) {
+ attachTo.setImmediate = function () {
+ var handle = tasks.addFromSetImmediateArguments(arguments);
+
+ global.setTimeout(function () {
+ tasks.runIfPresent(handle);
+ }, 0);
+
+ return handle;
+ };
+ }
+
+ if (!global.setImmediate) {
+ // If supported, we should attach to the prototype of global, since that is where setTimeout et al. live.
+ var attachTo = typeof Object.getPrototypeOf === "function" && "setTimeout" in Object.getPrototypeOf(global) ?
+ Object.getPrototypeOf(global)
+ : global;
+
+ if (canUseNextTick()) {
+ // For Node.js before 0.9
+ installNextTickImplementation(attachTo);
+ } else if (canUsePostMessage()) {
+ // For non-IE10 modern browsers
+ installPostMessageImplementation(attachTo);
+ } else if (canUseMessageChannel()) {
+ // For web workers, where supported
+ installMessageChannelImplementation(attachTo);
+ } else if (canUseReadyStateChange()) {
+ // For IE 6–8
+ installReadyStateChangeImplementation(attachTo);
+ } else {
+ // For older browsers
+ installSetTimeoutImplementation(attachTo);
+ }
+
+ attachTo.clearImmediate = tasks.remove;
+ }
+}(typeof global === "object" && global ? global : this));