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));