You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by lo...@apache.org on 2013/05/14 20:32:47 UTC

[05/33] js commit: [BlackBerry10] Implement plugin client script loading via script tag appended to document head.

[BlackBerry10] Implement plugin client script loading via script tag appended to document head.

Reviewed by Rose Tse <rt...@blackberry.com>


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

Branch: refs/heads/master
Commit: d2f526623bdab447af897b9bc7fc3776d1245a53
Parents: 28084c5
Author: Bryan Higgins <bh...@rim.com>
Authored: Tue Mar 5 11:46:53 2013 -0500
Committer: Bryan Higgins <bh...@blackberry.com>
Committed: Fri May 3 09:49:22 2013 -0400

----------------------------------------------------------------------
 lib/blackberry10/plugin/blackberry10/builder.js    |   74 ----
 .../plugin/blackberry10/pluginUtils.js             |   69 ++++
 lib/scripts/bootstrap-blackberry10.js              |  220 ++++--------
 lib/scripts/require.js                             |  289 +++------------
 test/blackberry10/test.builder.js                  |  121 ------
 test/blackberry10/test.pluginUtils.js              |  137 +++++++
 test/runner.js                                     |    2 +-
 7 files changed, 342 insertions(+), 570 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-js/blob/d2f52662/lib/blackberry10/plugin/blackberry10/builder.js
----------------------------------------------------------------------
diff --git a/lib/blackberry10/plugin/blackberry10/builder.js b/lib/blackberry10/plugin/blackberry10/builder.js
deleted file mode 100644
index 9271c58..0000000
--- a/lib/blackberry10/plugin/blackberry10/builder.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- *
- * 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.
- *
-*/
-
-var utils = require("cordova/plugin/blackberry10/utils");
-
-function buildNamespace(currentNamespace, namespaceParts, featureProperties) {
-    var featureId,
-        nextPart;
-
-    if (namespaceParts.length === 1) {
-        //base case, feature properties go here
-        featureId = namespaceParts[0];
-        if (currentNamespace[featureId] === undefined) {
-            currentNamespace[featureId] = {};
-        }
-
-        currentNamespace = utils.mixin(featureProperties, currentNamespace[featureId]);
-        return currentNamespace;
-    }
-    else {
-        nextPart = namespaceParts.shift();
-        if (currentNamespace[nextPart] === undefined) {
-            currentNamespace[nextPart] = {};
-        }
-
-        return buildNamespace(currentNamespace[nextPart], namespaceParts, featureProperties);
-    }
-}
-
-function include(parent, featureIdList) {
-    var featureId,
-        featureProperties,
-        localUrl,
-        i;
-
-    for (i = 0; i < featureIdList.length; i++) {
-        featureId = featureIdList[i];
-
-        localUrl = "local://ext/" + featureId + "/client.js";
-        featureProperties = utils.loadModule(localUrl);
-
-        buildNamespace(parent, featureId.split("."), featureProperties);
-    }
-}
-
-var _self = {
-    build: function (featureIdList) {
-        return {
-            into: function (target) {
-                include(target, featureIdList);
-            }
-        };
-    }
-};
-
-module.exports = _self;

http://git-wip-us.apache.org/repos/asf/cordova-js/blob/d2f52662/lib/blackberry10/plugin/blackberry10/pluginUtils.js
----------------------------------------------------------------------
diff --git a/lib/blackberry10/plugin/blackberry10/pluginUtils.js b/lib/blackberry10/plugin/blackberry10/pluginUtils.js
new file mode 100644
index 0000000..9a52a25
--- /dev/null
+++ b/lib/blackberry10/plugin/blackberry10/pluginUtils.js
@@ -0,0 +1,69 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+module.exports = {
+
+    loadClientJs: function (plugins, callback) {
+        var plugin,
+            script,
+            i,
+            count = 0;
+        for (plugin in plugins) {
+            if (plugins.hasOwnProperty(plugin) && plugins[plugin].modules) {
+                for (i = 0; i < plugins[plugin].modules.length; i++) {
+                    script = document.createElement('script');
+                    script.src = 'plugins/' + plugin + '/' + plugins[plugin].modules[i];
+                    script.onload = function () {
+                        if (--count === 0 && typeof callback === 'function') {
+                            callback();
+                        }
+                    };
+                    count++;
+                    document.head.appendChild(script);
+                }
+            }
+        }
+    },
+
+    getPlugins: function (success, error) {
+        var request,
+            response;
+        request = new XMLHttpRequest();
+        request.open('GET', 'plugins/plugins.json', true);
+        request.onreadystatechange = function () {
+            if (request.readyState === 4) {
+                if (request.status === 200) {
+                    try {
+                        response = JSON.parse(decodeURIComponent(request.responseText));
+                        success(response);
+                    }
+                    catch (e) {
+                        error(e);
+                    }
+                }
+                else {
+                    error(request.status);
+                }
+            }
+        };
+        request.send(null);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-js/blob/d2f52662/lib/scripts/bootstrap-blackberry10.js
----------------------------------------------------------------------
diff --git a/lib/scripts/bootstrap-blackberry10.js b/lib/scripts/bootstrap-blackberry10.js
index 1a33845..8c97e46 100644
--- a/lib/scripts/bootstrap-blackberry10.js
+++ b/lib/scripts/bootstrap-blackberry10.js
@@ -20,17 +20,19 @@
 */
 
 (function () {
-    var _d = document.addEventListener,
-        _webworksReady = false,
-        _alreadyFired = false,
-        _listenerRegistered = false;
+    var pluginUtils = require('cordova/plugin/blackberry10/pluginUtils'),
+        docAddEventListener = document.addEventListener,
+        webworksReady = false,
+        alreadyFired = false,
+        listenerRegistered = false;
 
     //Only fire the webworks event when both webworks is ready and a listener is registered
     function fireWebworksReadyEvent() {
-        if (_listenerRegistered && _webworksReady && !_alreadyFired) {
-            _alreadyFired = true;
-            var evt = document.createEvent("Events");
-            evt.initEvent("webworksready", true, true);
+        var evt;
+        if (listenerRegistered && webworksReady && !alreadyFired) {
+            alreadyFired = true;
+            evt = document.createEvent('Events');
+            evt.initEvent('webworksready', true, true);
             document.dispatchEvent(evt);
         }
     }
@@ -38,113 +40,78 @@
     //Trapping when users add listeners to the webworks ready event
     //This way we can make sure not to fire the event before there is a listener
     document.addEventListener = function (event, callback, capture) {
-        _d.call(document, event, callback, capture);
-        if (event.toLowerCase() === "webworksready") {
-            _listenerRegistered = true;
+        docAddEventListener.call(document, event, callback, capture);
+        if (event.toLowerCase() === 'webworksready') {
+            listenerRegistered = true;
             fireWebworksReadyEvent();
         }
     };
 
-    function createWebworksReady() {
-        function RemoteFunctionCall(functionUri) {
-            var params = {};
-
-            function composeUri() {
-                return require("cordova/plugin/blackberry10/utils").getURIPrefix() + functionUri;
-            }
-
-            function createXhrRequest(uri, isAsync) {
-                var request = new XMLHttpRequest();
-
-                request.open("POST", uri, isAsync);
-                request.setRequestHeader("Content-Type", "application/json");
-
-                return request;
-            }
-
-            this.addParam = function (name, value) {
-                params[name] = encodeURIComponent(JSON.stringify(value));
-            };
-
-            this.makeSyncCall = function (success, error) {
-                var requestUri = composeUri(),
-                    request = createXhrRequest(requestUri, false),
-                    response,
-                    errored,
-                    cb,
-                    data;
-
-                request.send(JSON.stringify(params));
-
-                response = JSON.parse(decodeURIComponent(request.responseText) || "null");
-                errored = response.code < 0;
-                cb = errored ? error : success;
-                data = errored ? response.msg : response.data;
-
-                if (cb) {
-                    cb(data, response);
-                }
-                else if (errored) {
-                    throw data;
-                }
-
-                return data;
-            };
-
-            this.makeAsyncCall = function (success, error) {
-                var requestUri = composeUri(),
-                    request = createXhrRequest(requestUri, true);
+    //Fire webworks ready once plugin javascript has been loaded
+    pluginUtils.getPlugins(
+        function (plugins) {
+            pluginUtils.loadClientJs(plugins, function () {
+                webworksReady = true;
+                fireWebworksReadyEvent();
+            });
+        },
+        function (e) {
+            console.log(e);
+        }
+    );
 
-                request.onreadystatechange = function () {
-                    if (request.readyState === 4 && request.status === 200) {
-                        var response = JSON.parse(decodeURIComponent(request.responseText) || "null"),
-                        cb = response.code < 0 ? error : success,
-                        data = response.code < 0 ? response.msg : response.data;
+    /**
+     * webworks.exec
+     *
+     * This will all be moved into lib/blackberry10/exec once cordova.exec can be replaced
+     */
 
-                        return cb && cb(data, response);
-                    }
-                };
+    function RemoteFunctionCall(functionUri) {
+         var params = {};
 
-                request.send(JSON.stringify(params));
-            };
+        function composeUri() {
+            return require("cordova/plugin/blackberry10/utils").getURIPrefix() + functionUri;
         }
 
-        var builder,
-            request,
-            resp,
-            execFunc;
-
-        //For users who wish to have a single source project across BB7 -> PB -> BB10 they will need to use webworks.js
-        //To aid in this, we will fire the webworksready event on these platforms as well
-        //If blackberry object already exists then we are in an older version of webworks
-        if (window.blackberry) {
-            _webworksReady = true;
-            fireWebworksReadyEvent();
-            return;
+        function createXhrRequest(uri, isAsync) {
+            var request = new XMLHttpRequest();
+            request.open("POST", uri, isAsync);
+            request.setRequestHeader("Content-Type", "application/json");
+            return request;
         }
 
-        // Build out the blackberry namespace based on the APIs desired in the config.xml
-        builder = require('cordova/plugin/blackberry10/builder');
+        this.addParam = function (name, value) {
+            params[name] = encodeURIComponent(JSON.stringify(value));
+        };
+
+        this.makeSyncCall = function (success, error) {
+            var requestUri = composeUri(),
+                request = createXhrRequest(requestUri, false),
+                response,
+                errored,
+                cb,
+                data;
 
-        request = new XMLHttpRequest();
-        request.open("GET", "http://localhost:8472/extensions/get", true);
+            request.send(JSON.stringify(params));
 
-        request.onreadystatechange = function () {
-            if (request.readyState === 4) {
-                resp = JSON.parse(decodeURIComponent(request.responseText));
+            response = JSON.parse(decodeURIComponent(request.responseText) || "null");
+            errored = response.code < 0;
+            cb = errored ? error : success;
+            data = errored ? response.msg : response.data;
 
-                if (request.status === 200) {
-                    builder.build(resp.data).into(window);
-                    //At this point all of the APIs should be built into the window object
-                    //Fire the webworks ready event
-                    _webworksReady = true;
-                    fireWebworksReadyEvent();
-                }
+            if (cb) {
+                cb(data, response);
             }
+            else if (errored) {
+                throw data;
+            }
+
+            return data;
         };
-        request.send(null);
+    }
 
-        execFunc = function (success, fail, service, action, args, sync) {
+    window.webworks = {
+        exec: function (success, fail, service, action, args) {
             var uri = service + "/" + action,
                 request = new RemoteFunctionCall(uri),
                 name;
@@ -155,53 +122,16 @@
                 }
             }
 
-            request[sync ? "makeSyncCall" : "makeAsyncCall"](success, fail);
-        };
-
-        window.webworks = {
-            exec: execFunc,
-            execSync: function (service, action, args) {
-                var result;
-
-                execFunc(function (data, response) {
-                    result = data;
-                }, function (data, response) {
-                    throw data;
-                }, service, action, args, true);
-
-                return result;
-            },
-            execAsync: function (service, action, args) {
-                var result;
-
-                execFunc(function (data, response) {
-                    result = data;
-                }, function (data, response) {
-                    throw data;
-                }, service, action, args, false);
-
-                return result;
-            },
-            successCallback: function (id, args) {
-                //HACK: this will live later
-                throw "not implemented";
-            },
-            errorCallback: function (id, args) {
-                //HACK: this will live later
-                throw "not implemented";
-            },
-            defineReadOnlyField: function (obj, field, value) {
-                Object.defineProperty(obj, field, {
-                    "value": value,
-                    "writable": false
-                });
-            },
-            event: require("cordova/plugin/blackberry10/event")
-        };
-    }
-
-    // Let's create the webworks namespace
-    createWebworksReady();
+            return request.makeSyncCall(success, fail);
+        },
+        defineReadOnlyField: function (obj, field, value) {
+            Object.defineProperty(obj, field, {
+                "value": value,
+                "writable": false
+            });
+        },
+        event: require("cordova/plugin/blackberry10/event")
+    };
 }());
 
 document.addEventListener("DOMContentLoaded", function () {

http://git-wip-us.apache.org/repos/asf/cordova-js/blob/d2f52662/lib/scripts/require.js
----------------------------------------------------------------------
diff --git a/lib/scripts/require.js b/lib/scripts/require.js
index 35f160e..5dbc905 100644
--- a/lib/scripts/require.js
+++ b/lib/scripts/require.js
@@ -1,251 +1,82 @@
 /*
- *  Copyright 2012 Research In Motion Limited.
  *
- * 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
+ * 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
+ *   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.
- */
+ * 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.
+ *
+*/
 
-var define,
-    require;
+var require,
+    define;
 
 (function () {
-    var unpreparedModules = {},
-        readyModules = {},
-        ACCEPTABLE_EXTENSIONS = [".js", ".json"],
-        DEFAULT_EXTENSION = ".js";
-
-    function hasValidExtension(moduleName) {
-        return ACCEPTABLE_EXTENSIONS.some(function (element, index, array) {
-            return moduleName.match("\\" + element + "$");
-        });
-    }
-
-
-    function normalizeName(originalName, baseName) {
-        var nameParts,
-            name = originalName.slice(0);
-        //remove ^local:// (if it exists) and .js$
-        //This will not work for local:// without a trailing js
-        name = name.replace(/(?:^local:\/\/)/, "");
-        if (name.charAt(0) === '.' && baseName) {
-            //Split the baseName and remove the final part (the module name)
-            nameParts = baseName.split('/');
-            nameParts.pop();
-            nameParts = nameParts.concat(name.split('/'));
-            
-            name = nameParts.reduce(function (previous, current,  index, array) {
-                var returnValue,
-                    slashIndex;
-
-                //If previous is a dot, ignore it
-                //If previous is ever just .. we're screwed anyway
-                if (previous !== '.') {
-                    returnValue = previous;
-                }
-                
-                //If we have a .. then remove a chunk of previous
-                if (current === "..") {
-                    slashIndex = previous.lastIndexOf('/');
-                    //If there's no slash we're either screwed or we remove the final token
-                    if (slashIndex !== -1) {
-                        returnValue = previous.slice(0, previous.lastIndexOf('/'));
-                    } else {
-                        returnValue = "";
-                    }
-                } else if (current !== '.') {
-                    //Otherwise simply append anything not a .
-                    //Only append a slash if we're not empty
-                    if (returnValue.length) {
-                        returnValue += "/";
-                    }
-                    returnValue += current;
-                }
-
-                return returnValue;
-            });
-
-        }
-        
-        //If there is no acceptable extension tack on a .js
-        if (!hasValidExtension(name)) {
-            name = name + DEFAULT_EXTENSION;
-        }
-
-        return name;
-    }
-
-    function buildModule(name, dependencies, factory) {
-        var module = {exports: {}},
-            localRequire = function (moduleName) {
-                return require(moduleName, name);
-            },
-            args = [];
-        localRequire.toUrl = function (moduleName, baseName) {
-            return require.toUrl(moduleName, baseName || name);
-        };
-        dependencies.forEach(function (dependency) {
-            if (dependency === 'require') {
-                args.push(localRequire);
-            } else if (dependency === 'exports') {
-                args.push(module.exports);
-            } else if (dependency === 'module') {
-                args.push(module);
-            } else {
-                //This is because jshint cannot handle out of order functions
-                /*global loadModule:false */
-                args.push(loadModule(dependency));
-                /*global loadModule:true */
-            }
-        });
-
-        //No need to process dependencies, webworks only has require, exports, module
-        factory.apply(this, args);
-
-        //For full AMD we would need logic to also check the return value
+    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;
-
     }
 
-    function getDefineString(moduleName, body) {
-        var evalString = 'define("' + moduleName + '", function (require, exports, module) {',
-            isJson = /\.json$/.test(moduleName);
-
-        evalString += isJson ? ' module.exports = ' : '';
-        evalString += body.replace(/^\s+|\s+$/g, '');
-        evalString += isJson ? ' ;' : '';
-        evalString += '});';
-
-        return evalString;
-    }
-
-    function loadModule(name, baseName) {
-        var normalizedName = normalizeName(name, baseName),
-            url,
-            xhr,
-            loadResult;
-        //Always check undefined first, this allows the user to redefine modules
-        //(Not used in WebWorks, although it is used in our unit tests)
-        if (unpreparedModules[normalizedName]) {
-            readyModules[normalizedName] = buildModule(normalizedName, unpreparedModules[normalizedName].dependencies, unpreparedModules[normalizedName].factory);
-            delete unpreparedModules[normalizedName];
+    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 the module does not exist, load the module from external source
-        //Webworks currently only loads APIs from across bridge
-        if (!readyModules[normalizedName]) {
-            //If the module to be loaded ends in .js then we will define it
-            //Also if baseName exists than we have a local require situation
-            if (hasValidExtension(name) || baseName) {
-                xhr = new XMLHttpRequest();
-                url = name;
-                //If the module to be loaded starts with local:// go over the bridge
-                //Else If the module to be loaded is a relative load it may not have .js extension which is needed
-                if (/^local:\/\//.test(name)) {
-                    url = "http://localhost:8472/extensions/load/" + normalizedName.replace(/(?:^ext\/)(.+)(?:\/client.js$)/, "$1");
-
-                    xhr.open("GET", url, false);
-                    xhr.send(null);
-                    try {
-                        loadResult = JSON.parse(xhr.responseText);
-
-                        loadResult.dependencies.forEach(function (dep) {
-                            /*jshint evil:true */
-                            eval(getDefineString(dep.moduleName, dep.body));
-                            /*jshint evil:false */
-                        });
-
-                        //Trimming responseText to remove EOF chars
-                        /*jshint evil:true */
-                        eval(getDefineString(normalizedName, loadResult.client));
-                        /*jshint evil:false */
-                    } catch (err1) {
-                        err1.message += ' in ' + url;
-                        throw err1;
-                    }
-                } else {
-                    if (baseName) {
-                        url = normalizedName;
-                    }
-
-                    xhr.open("GET", url, false);
-                    xhr.send(null);
-                    try {
-                        //Trimming responseText to remove EOF chars
-                        /*jshint evil:true */
-                        eval(getDefineString(normalizedName, xhr.responseText));
-                        /*jshint evil:false */
-                    } catch (err) {
-                        err.message += ' in ' + url;
-                        throw err;
-                    }
-                }
-
-                if (unpreparedModules[normalizedName]) {
-                    readyModules[normalizedName] = buildModule(normalizedName, unpreparedModules[normalizedName].dependencies, unpreparedModules[normalizedName].factory);
-                    delete unpreparedModules[normalizedName];
-                }
-            } else {
-                throw "module " + name + " cannot be found";
+        if (modules[id].factory) {
+            try {
+                inProgressModules[id] = requireStack.length;
+                requireStack.push(id);
+                return build(modules[id]);
+            } finally {
+                delete inProgressModules[id];
+                requireStack.pop();
             }
-
-        }
-
-        return readyModules[normalizedName];
-
-    }
-
-    //Use the AMD signature incase we ever want to change.
-    //For now we will only be using (name, baseName)
-    require = function (dependencies, callback) {
-        if (typeof dependencies === "string") {
-            //dependencies is the module name and callback is the relName
-            //relName is not part of the AMDJS spec, but we use it from localRequire
-            return loadModule(dependencies, callback);
-        } else if (Array.isArray(dependencies) && typeof callback === 'function') {
-            //Call it Asynchronously
-            setTimeout(function () {
-                buildModule(undefined, dependencies, callback);
-            }, 0);
         }
-    }; 
-
-    require.toUrl = function (originalName, baseName) {
-        return normalizeName(originalName, baseName);
+        return modules[id].exports;
     };
 
-    //Use the AMD signature incase we ever want to change.
-    //For now webworks will only be using (name, factory) signature.
-    define = function (name, dependencies, factory) {
-        if (typeof name === "string" && typeof dependencies === 'function') {
-            factory = dependencies;
-            dependencies = ['require', 'exports', 'module'];
+    define = function (id, factory) {
+        if (modules[id]) {
+            throw "module " + id + " already defined";
         }
 
-        //According to the AMDJS spec we should parse out the require statments 
-        //from factory.toString and add those to the list of dependencies
-
-        //Normalize the name. Remove local:// and .js
-        name = normalizeName(name);
-        unpreparedModules[name] = {
-            dependencies: dependencies,
+        modules[id] = {
+            id: id,
             factory: factory
-        }; 
+        };
     };
-}());
 
-//Export for use in node for unit tests
-if (typeof module === "object" && typeof require === "function") {
-    module.exports = {
-        require: require,
-        define: define
+    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;
 }

http://git-wip-us.apache.org/repos/asf/cordova-js/blob/d2f52662/test/blackberry10/test.builder.js
----------------------------------------------------------------------
diff --git a/test/blackberry10/test.builder.js b/test/blackberry10/test.builder.js
deleted file mode 100644
index 86e5487..0000000
--- a/test/blackberry10/test.builder.js
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- *
- * 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.
- *
- */
-
-var app,
-    io,
-    filetransfer,
-    system,
-    builder,
-    utils;
-
-describe("blackberry10 builder", function () {
-
-    beforeEach(function () {
-        //Set up mocking, no need to "spyOn" since spies are included in mock
-        window.webworks = {
-            webworks: {
-                execSync: jasmine.createSpy(),
-                defineReadOnlyField: jasmine.createSpy()
-            }
-        };
-
-        app = {
-            "name": "abc",
-            "version": "1.2.3"
-        };
-        io = {
-            "sandbox": false
-        };
-        filetransfer = {
-            "upload": function () {},
-            "download": function () {}
-        };
-        system =  {
-            "getCurrentTimezone": function () {}
-        };
-
-        utils = require("cordova/plugin/blackberry10/utils");
-        spyOn(utils, "loadModule").andCallFake(function (module) {
-            if (module.indexOf("app") !== -1) {
-                return app;
-            } else if (module.indexOf("filetransfer") !== -1) {
-                return filetransfer;
-            } else if (module.indexOf("io") !== -1) {
-                return io;
-            } else if (module.indexOf("system") !== -1) {
-                return system;
-            }
-        });
-
-        builder = require("cordova/plugin/blackberry10/builder");
-    });
-
-    afterEach(function () {
-        delete window.webworks;
-        builder = null;
-    });
-
-    it("can build an object with a single member", function () {
-        var featureIds = ['blackberry.app'],
-            target = {};
-
-        builder.build(featureIds).into(target);
-
-        expect(target.blackberry.app).toEqual(app);
-        expect(Object.hasOwnProperty.call(target.blackberry.app, "name")).toBeTruthy();
-        expect(Object.hasOwnProperty.call(target.blackberry.app, "version")).toBeTruthy();
-    });
-
-    it("can build an object with a nested member", function () {
-        var featureIds = ['blackberry.io', 'blackberry.io.filetransfer'],
-            target = {};
-
-        builder.build(featureIds).into(target);
-        expect(target.blackberry.io.filetransfer).toEqual(filetransfer);
-        expect(target.blackberry.io.sandbox).toEqual(io.sandbox);
-    });
-
-    it("can build with feature IDs provided in any order", function () {
-        var featureIds = ['blackberry.io.filetransfer', 'blackberry.io'],
-            target = {};
-
-        builder.build(featureIds).into(target);
-        expect(target.blackberry.io.filetransfer).toEqual(filetransfer);
-        expect(target.blackberry.io.sandbox).toEqual(io.sandbox);
-    });
-
-    it("can build an object with only the nested member", function () {
-        var featureIds = ['blackberry.io.filetransfer'],
-            target = {};
-
-        builder.build(featureIds).into(target);
-        expect(target.blackberry.io.filetransfer).toEqual(filetransfer);
-    });
-
-    it("can build an object with multiple members", function () {
-        var featureIds = ['blackberry.app', 'blackberry.system'],
-            target = {};
-
-        builder.build(featureIds).into(target);
-        expect(target.blackberry.app).toEqual(app);
-        expect(target.blackberry.system).toEqual(system);
-    });
-});

http://git-wip-us.apache.org/repos/asf/cordova-js/blob/d2f52662/test/blackberry10/test.pluginUtils.js
----------------------------------------------------------------------
diff --git a/test/blackberry10/test.pluginUtils.js b/test/blackberry10/test.pluginUtils.js
new file mode 100644
index 0000000..37f0e9c
--- /dev/null
+++ b/test/blackberry10/test.pluginUtils.js
@@ -0,0 +1,137 @@
+/*
+ *
+ * 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.
+ *
+*/
+
+describe('blackberry10 pluginUtils', function () {
+
+    var pluginUtils = require('cordova/plugin/blackberry10/pluginUtils');
+
+    describe('loadClientJs', function () {
+
+        var callback,
+            script;
+
+        beforeEach(function () {
+            script = {};
+            spyOn(document, "createElement").andCallFake(function () {
+                return script;
+            });
+            spyOn(document.head, "appendChild");
+            callback = jasmine.createSpy();
+        });
+
+        it('does nothing for 0 plugins', function () {
+            var plugins = {};
+            pluginUtils.loadClientJs(plugins, callback);
+            expect(document.createElement).not.toHaveBeenCalled();
+            expect(document.head.appendChild).not.toHaveBeenCalled();
+            expect(callback).not.toHaveBeenCalled();
+        });
+
+        it('adds a script tag for 1 plugin', function () {
+            var plugins = { foo : { client: ['bar.js'] } };
+            pluginUtils.loadClientJs(plugins, callback);
+            expect(document.createElement).toHaveBeenCalled();
+            expect(script.src).toEqual('plugins/foo/bar.js');
+            expect(document.head.appendChild).toHaveBeenCalled();
+            script.onload();
+            expect(callback).toHaveBeenCalled();
+        });
+
+        it('adds multiple script tags for 1 plugin', function () {
+            var plugins = { foo: { client: ['bar.js', '2.js'] } };
+            pluginUtils.loadClientJs(plugins, callback);
+            expect(document.createElement.callCount).toBe(2);
+            expect(document.head.appendChild.callCount).toBe(2);
+            script.onload();
+            script.onload();
+            expect(callback.callCount).toBe(1);
+        });
+
+        it('adds script tags for multiple plugins', function () {
+            var plugins = { foo: { client: ['1.js'] }, bar: { client: ['1.js', '2.js' ] } };
+            pluginUtils.loadClientJs(plugins, callback);
+            expect(document.createElement.callCount).toBe(3);
+            expect(document.head.appendChild.callCount).toBe(3);
+            script.onload();
+            script.onload();
+            script.onload();
+            expect(callback.callCount).toBe(1);
+        });
+
+    });
+
+    describe('getPlugins', function () {
+
+        var success,
+            error,
+            xhr;
+
+        beforeEach(function () {
+            GLOBAL.XMLHttpRequest = function () {
+                this.open = jasmine.createSpy();
+                this.send = jasmine.createSpy();
+                xhr = this;
+            };
+            success = jasmine.createSpy();
+            error = jasmine.createSpy();
+        });
+
+        afterEach(function () {
+            delete GLOBAL.XMLHttpRequest;
+        });
+
+        it('sends XHR for plugins.json', function () {
+            pluginUtils.getPlugins(success, error);
+            expect(xhr.open).toHaveBeenCalledWith('GET', 'plugins/plugins.json', true);
+            expect(xhr.send).toHaveBeenCalled();
+        });
+
+        it('calls success with JSON response', function () {
+            pluginUtils.getPlugins(success, error);
+            xhr.readyState = 4;
+            xhr.status = 200;
+            xhr.responseText = '{ "hello" : "World" }';
+            xhr.onreadystatechange();
+            expect(success).toHaveBeenCalledWith({ hello: "World"});
+            expect(error).not.toHaveBeenCalled();
+        });
+
+        it('calls error with status', function () {
+            pluginUtils.getPlugins(success, error);
+            xhr.readyState = 4;
+            xhr.status = 500;
+            xhr.onreadystatechange();
+            expect(error).toHaveBeenCalledWith(500);
+            expect(success).not.toHaveBeenCalled();
+        });
+
+        it('calls error with parse exception', function () {
+            pluginUtils.getPlugins(success, error);
+            xhr.readyState = 4;
+            xhr.status = 200;
+            xhr.responseText = 'INVALID';
+            xhr.onreadystatechange();
+            expect(error).toHaveBeenCalled();
+            expect(success).not.toHaveBeenCalled();
+        });
+
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-js/blob/d2f52662/test/runner.js
----------------------------------------------------------------------
diff --git a/test/runner.js b/test/runner.js
index e25afbb..c908cf3 100644
--- a/test/runner.js
+++ b/test/runner.js
@@ -49,7 +49,7 @@ module.exports = {
 
         try {
             jsdom = require("jsdom").jsdom;
-            document = jsdom("<html>");
+            document = jsdom("<html><head></head></html>");
             window = document.createWindow();
         } catch (e) {
             //no jsDom (some people don't have compilers)