michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: /** michael@0: * Require.jsm is a small module loader that loads JavaScript modules as michael@0: * defined by AMD/RequireJS and CommonJS, or specifically as used by: michael@0: * GCLI, Orion, Firebug, CCDump, NetPanel/HTTPMonitor and others. michael@0: * michael@0: * To date, no attempt has been made to ensure that Require.jsm closely follows michael@0: * either the AMD or CommonJS specs. It is hoped that a more formal JavaScript michael@0: * module standard will arrive before this is necessary. In the mean time it michael@0: * serves the projects it loads. michael@0: */ michael@0: michael@0: this.EXPORTED_SYMBOLS = [ "define", "require" ]; michael@0: michael@0: const console = (function() { michael@0: const tempScope = {}; michael@0: Components.utils.import("resource://gre/modules/devtools/Console.jsm", tempScope); michael@0: return tempScope.console; michael@0: })(); michael@0: michael@0: /** michael@0: * Define a module along with a payload. michael@0: * @param moduleName Name for the payload michael@0: * @param deps Ignored. For compatibility with CommonJS AMD Spec michael@0: * @param payload Function with (require, exports, module) params michael@0: */ michael@0: this.define = function define(moduleName, deps, payload) { michael@0: if (typeof moduleName != "string") { michael@0: throw new Error("Error: Module name is not a string"); michael@0: } michael@0: michael@0: if (arguments.length == 2) { michael@0: payload = deps; michael@0: } michael@0: else { michael@0: payload.deps = deps; michael@0: } michael@0: michael@0: if (define.debugDependencies) { michael@0: console.log("define: " + moduleName + " -> " + payload.toString() michael@0: .slice(0, 40).replace(/\n/, '\\n').replace(/\r/, '\\r') + "..."); michael@0: } michael@0: michael@0: if (moduleName in define.modules) { michael@0: throw new Error("Error: Redefining module: " + moduleName); michael@0: } michael@0: michael@0: // Mark the payload so we know we need to call it to get the real module michael@0: payload.__uncompiled = true; michael@0: define.modules[moduleName] = payload; michael@0: } michael@0: michael@0: /** michael@0: * The global store of un-instantiated modules michael@0: */ michael@0: define.modules = {}; michael@0: michael@0: /** michael@0: * Should we console.log on module definition/instantiation/requirement? michael@0: */ michael@0: define.debugDependencies = false; michael@0: michael@0: michael@0: /** michael@0: * We invoke require() in the context of a Domain so we can have multiple michael@0: * sets of modules running separate from each other. michael@0: * This contrasts with JSMs which are singletons, Domains allows us to michael@0: * optionally load a CommonJS module twice with separate data each time. michael@0: * Perhaps you want 2 command lines with a different set of commands in each, michael@0: * for example. michael@0: */ michael@0: function Domain() { michael@0: this.modules = {}; michael@0: michael@0: if (define.debugDependencies) { michael@0: this.depth = ""; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Lookup module names and resolve them by calling the definition function if michael@0: * needed. michael@0: * There are 2 ways to call this, either with an array of dependencies and a michael@0: * callback to call when the dependencies are found (which can happen michael@0: * asynchronously in an in-page context) or with a single string an no michael@0: * callback where the dependency is resolved synchronously and returned. michael@0: * The API is designed to be compatible with the CommonJS AMD spec and michael@0: * RequireJS. michael@0: * @param deps A name, or array of names for the payload michael@0: * @param callback Function to call when the dependencies are resolved michael@0: * @return The module required or undefined for array/callback method michael@0: */ michael@0: Domain.prototype.require = function(config, deps, callback) { michael@0: if (arguments.length <= 2) { michael@0: callback = deps; michael@0: deps = config; michael@0: config = undefined; michael@0: } michael@0: michael@0: if (Array.isArray(deps)) { michael@0: var params = deps.map(function(dep) { michael@0: return this.lookup(dep); michael@0: }, this); michael@0: if (callback) { michael@0: callback.apply(null, params); michael@0: } michael@0: return undefined; michael@0: } michael@0: else { michael@0: return this.lookup(deps); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Lookup module names and resolve them by calling the definition function if michael@0: * needed. michael@0: * @param moduleName A name for the payload to lookup michael@0: * @return The module specified by aModuleName or null if not found michael@0: */ michael@0: Domain.prototype.lookup = function(moduleName) { michael@0: if (moduleName in this.modules) { michael@0: var module = this.modules[moduleName]; michael@0: if (define.debugDependencies) { michael@0: console.log(this.depth + " Using module: " + moduleName); michael@0: } michael@0: return module; michael@0: } michael@0: michael@0: if (!(moduleName in define.modules)) { michael@0: throw new Error("Missing module: " + moduleName); michael@0: } michael@0: michael@0: var module = define.modules[moduleName]; michael@0: michael@0: if (define.debugDependencies) { michael@0: console.log(this.depth + " Compiling module: " + moduleName); michael@0: } michael@0: michael@0: if (module.__uncompiled) { michael@0: if (define.debugDependencies) { michael@0: this.depth += "."; michael@0: } michael@0: michael@0: var exports = {}; michael@0: try { michael@0: var params = module.deps.map(function(dep) { michael@0: if (dep === "require") { michael@0: return this.require.bind(this); michael@0: } michael@0: if (dep === "exports") { michael@0: return exports; michael@0: } michael@0: if (dep === "module") { michael@0: return { id: moduleName, uri: "" }; michael@0: } michael@0: return this.lookup(dep); michael@0: }.bind(this)); michael@0: michael@0: var reply = module.apply(null, params); michael@0: module = (reply !== undefined) ? reply : exports; michael@0: } michael@0: catch (ex) { michael@0: dump("Error using module '" + moduleName + "' - " + ex + "\n"); michael@0: throw ex; michael@0: } michael@0: michael@0: if (define.debugDependencies) { michael@0: this.depth = this.depth.slice(0, -1); michael@0: } michael@0: } michael@0: michael@0: // cache the resulting module object for next time michael@0: this.modules[moduleName] = module; michael@0: michael@0: return module; michael@0: }; michael@0: michael@0: /** michael@0: * Expose the Domain constructor and a global domain (on the define function michael@0: * to avoid exporting more than we need. This is a common pattern with michael@0: * require systems) michael@0: */ michael@0: define.Domain = Domain; michael@0: define.globalDomain = new Domain(); michael@0: michael@0: /** michael@0: * Expose a default require function which is the require of the global michael@0: * sandbox to make it easy to use. michael@0: */ michael@0: this.require = define.globalDomain.require.bind(define.globalDomain);