Bug 1392602 - Remove module boilerplate from DevTools loader. r=jdescottes draft
authorAlexandre Poirot <poirot.alex@gmail.com>
Tue, 29 Aug 2017 10:49:43 +0200
changeset 658603 deba7fd1c755c3d7855f8be531889f19b132b5cb
parent 658509 278f600e725cfc60df6a123ed07a9e85a52af55d
child 658604 919a92469821f45a76043e537956e8b82828c248
push id77824
push userbmo:poirot.alex@gmail.com
push dateMon, 04 Sep 2017 12:19:59 +0000
reviewersjdescottes
bugs1392602
milestone57.0a1
Bug 1392602 - Remove module boilerplate from DevTools loader. r=jdescottes This file is now only loaded as a JSM. Expose symbols directly instead of putting them on `Loader` symbol. No longer exports it as a fake 'toolkit/loader' module and always import it as JSM. MozReview-Commit-ID: 6J3IxHpk9ct
devtools/server/actors/call-watcher.js
devtools/shared/Loader.jsm
devtools/shared/base-loader.js
devtools/shared/builtin-modules.js
--- a/devtools/server/actors/call-watcher.js
+++ b/devtools/server/actors/call-watcher.js
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 /* global XPCNativeWrapper */
 
 const {Ci, Cu} = require("chrome");
 const protocol = require("devtools/shared/protocol");
-const {serializeStack, parseStack} = require("toolkit/loader");
+const {serializeStack, parseStack} = Cu.import("resource://devtools/shared/base-loader.js", {});
 
 const { functionCallSpec, callWatcherSpec } = require("devtools/shared/specs/call-watcher");
 const { CallWatcherFront } = require("devtools/shared/fronts/call-watcher");
 
 /**
  * This actor contains information about a function call, like the function
  * type, name, stack, arguments, returned value etc.
  */
--- a/devtools/shared/Loader.jsm
+++ b/devtools/shared/Loader.jsm
@@ -5,17 +5,18 @@
 "use strict";
 
 /**
  * Manages the base loader (base-loader.js) instance used to load the developer tools.
  */
 
 var { utils: Cu } = Components;
 var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
-var { Loader, descriptor, resolveURI } = Cu.import("resource://devtools/shared/base-loader.js", {});
+var { Loader, Require, descriptor, resolveURI, unload } =
+  Cu.import("resource://devtools/shared/base-loader.js", {});
 var { requireRawId } = Cu.import("resource://devtools/shared/loader-plugin-raw.jsm", {});
 
 this.EXPORTED_SYMBOLS = ["DevToolsLoader", "devtools", "BuiltinProvider",
                          "require", "loader"];
 
 /**
  * Providers are different strategies for loading the devtools.
  */
@@ -62,17 +63,17 @@ BuiltinProvider.prototype = {
     // When creating a Loader invisible to the Debugger, we have to ensure
     // using only modules and not depend on any JSM. As everything that is
     // not loaded with Loader isn't going to respect `invisibleToDebugger`.
     // But we have to keep using Promise.jsm for other loader to prevent
     // breaking unhandled promise rejection in tests.
     if (this.invisibleToDebugger) {
       paths.promise = "resource://gre/modules/Promise-backend.js";
     }
-    this.loader = new Loader.Loader({
+    this.loader = new Loader({
       id: "fx-devtools",
       paths,
       invisibleToDebugger: this.invisibleToDebugger,
       sharedGlobal: true,
       sharedGlobalBlocklist: [],
       sandboxName: "DevTools (Module loader)",
       noSandboxAddonId: true,
       requireHook: (id, require) => {
@@ -80,17 +81,17 @@ BuiltinProvider.prototype = {
           return requireRawId(id, require);
         }
         return require(id);
       },
     });
   },
 
   unload: function (reason) {
-    Loader.unload(this.loader, reason);
+    unload(this.loader, reason);
     delete this.loader;
   },
 };
 
 var gNextLoaderID = 0;
 
 /**
  * The main devtools API. The standard instance of this loader is exported as
@@ -163,17 +164,17 @@ DevToolsLoader.prototype = {
       this._provider.unload("newprovider");
     }
     this._provider = provider;
 
     // Pass through internal loader settings specific to this loader instance
     this._provider.invisibleToDebugger = this.invisibleToDebugger;
 
     this._provider.load();
-    this.require = Loader.Require(this._provider.loader, { id: "devtools" });
+    this.require = Require(this._provider.loader, { id: "devtools" });
 
     // Fetch custom pseudo modules and globals
     let { modules, globals } = this.require("devtools/shared/builtin-modules");
 
     // When creating a Loader for the browser toolbox, we have to use
     // Promise-backend.js, as a Loader module. Instead of Promise.jsm which
     // can't be flagged as invisible to debugger.
     if (this.invisibleToDebugger) {
--- a/devtools/shared/base-loader.js
+++ b/devtools/shared/base-loader.js
@@ -1,32 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-;((factory) => { // Module boilerplate :(
-  if (typeof(require) === 'function') { // CommonJS
-    require("chrome").Cu.import(module.uri, exports);
-  }
-  else if (~String(this).indexOf('BackstagePass')) { // JSM
-    let module = { uri: __URI__, id: "toolkit/loader", exports: Object.create(null) }
-    factory(module);
-    Object.assign(this, module.exports);
-    this.EXPORTED_SYMBOLS = Object.getOwnPropertyNames(module.exports);
-  }
-  else {
-    throw Error("Loading environment is not supported");
-  }
-})(module => {
-
 'use strict';
 
-module.metadata = {
-  "stability": "unstable"
-};
+this.EXPORTED_SYMBOLS = ["Loader", "resolveURI", "Module", "Require"]
 
 const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu,
         results: Cr, manager: Cm } = Components;
 const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
 const { loadSubScript } = Cc['@mozilla.org/moz/jssubscript-loader;1'].
                      getService(Ci.mozIJSSubScriptLoader);
 const { addObserver, notifyObservers } = Cc['@mozilla.org/observer-service;1'].
                         getService(Ci.nsIObserverService);
@@ -126,17 +109,16 @@ function freeze(object) {
 
 // Returns map of given `object`-s own property descriptors.
 const descriptor = iced(function descriptor(object) {
   let value = {};
   for (let name of getOwnIdentifiers(object))
     value[name] = getOwnPropertyDescriptor(object, name)
   return value;
 });
-Loader.descriptor = descriptor;
 
 // Freeze important built-ins so they can't be used by untrusted code as a
 // message passing channel.
 freeze(Object);
 freeze(Object.prototype);
 freeze(Function);
 freeze(Function.prototype);
 freeze(Array);
@@ -163,25 +145,22 @@ function iced(f) {
 const override = iced(function override(target, source) {
   let properties = descriptor(target);
 
   for (let name of getOwnIdentifiers(source || {}))
     properties[name] = getOwnPropertyDescriptor(source, name);
 
   return Object.defineProperties({}, properties);
 });
-Loader.override = override;
 
 function sourceURI(uri) { return String(uri).split(" -> ").pop(); }
-Loader.sourceURI = iced(sourceURI);
 
-function isntLoaderFrame(frame) { return frame.fileName !== module.uri }
+function isntLoaderFrame(frame) { return frame.fileName !== __URI__ }
 
 function parseURI(uri) { return String(uri).split(" -> ").pop(); }
-Loader.parseURI = parseURI;
 
 function parseStack(stack) {
   let lines = String(stack).split("\n");
   return lines.reduce(function(frames, line) {
     if (line) {
       let atIndex = line.indexOf("@");
       let columnIndex = line.lastIndexOf(":");
       let lineIndex = line.lastIndexOf(":", columnIndex - 1);
@@ -194,28 +173,26 @@ function parseStack(stack) {
         name: name,
         lineNumber: lineNumber,
         columnNumber: columnNumber
       });
     }
     return frames;
   }, []);
 }
-Loader.parseStack = parseStack;
 
 function serializeStack(frames) {
   return frames.reduce(function(stack, frame) {
     return frame.name + "@" +
            frame.fileName + ":" +
            frame.lineNumber + ":" +
            frame.columnNumber + "\n" +
            stack;
   }, "");
 }
-Loader.serializeStack = serializeStack;
 
 class DefaultMap extends Map {
   constructor(createItem, items = undefined) {
     super(items);
 
     this.createItem = createItem;
   }
 
@@ -388,17 +365,16 @@ function join(base, ...paths) {
   // or we wind up stripping too many slashes and producing invalid URLs.
   let match = /^((?:resource|file|chrome)\:\/\/[^\/]*|jar:[^!]+!)(.*)/.exec(base);
   if (match) {
     return match[1] + normalize([match[2], ...paths].join("/"));
   }
 
   return normalize([base, ...paths].join("/"));
 }
-Loader.join = join;
 
 // Function takes set of options and returns a JS sandbox. Function may be
 // passed set of options:
 //  - `name`: A string value which identifies the sandbox in about:memory. Will
 //    throw exception if omitted.
 // - `principal`: String URI or `nsIPrincipal` for the sandbox. Defaults to
 //    system principal.
 // - `prototype`: Ancestor for the sandbox that will be created. Defaults to
@@ -441,17 +417,16 @@ const Sandbox = iced(function Sandbox(op
   // to avoid shadowing.
   delete sandbox.Iterator;
   delete sandbox.Components;
   delete sandbox.importFunction;
   delete sandbox.debug;
 
   return sandbox;
 });
-Loader.Sandbox = Sandbox;
 
 // Evaluates code from the given `uri` into given `sandbox`. If
 // `options.source` is passed, then that code is evaluated instead.
 // Optionally following options may be given:
 // - `options.encoding`: Source encoding, defaults to 'UTF-8'.
 // - `options.line`: Line number to start count from for stack traces.
 //    Defaults to 1.
 // - `options.version`: Version of JS used, defaults to '1.8'.
@@ -461,17 +436,16 @@ const evaluate = iced(function evaluate(
     line: 1,
     version: '1.8',
     source: null
   }, options);
 
   return source ? Cu.evalInSandbox(source, sandbox, version, uri, line)
                 : loadSubScript(uri, sandbox, encoding);
 });
-Loader.evaluate = evaluate;
 
 // Populates `exports` of the given CommonJS `module` object, in the context
 // of the given `loader` by evaluating code associated with it.
 const load = iced(function load(loader, module) {
   let { sandboxes, globals, loadModuleHook } = loader;
   let require = Require(loader, module);
 
   // We expose set of properties defined by `CommonJS` specification via
@@ -589,17 +563,16 @@ const load = iced(function load(loader, 
   // Only freeze the exports object if we created it ourselves. Modules
   // which completely replace the exports object and still want it
   // frozen need to freeze it themselves.
   if (module.exports === originalExports)
     Object.freeze(module.exports);
 
   return module;
 });
-Loader.load = load;
 
 // Utility function to normalize module `uri`s so they have `.js` extension.
 function normalizeExt(uri) {
   return isJSURI(uri) ? uri :
          isJSONURI(uri) ? uri :
          isJSMURI(uri) ? uri :
          uri + '.js';
 }
@@ -621,17 +594,16 @@ const resolve = iced(function resolve(id
 
   // Joining and normalizing removes the './' from relative files.
   // We need to ensure the resolution still has the root
   if (base.startsWith('./'))
     resolved = './' + resolved;
 
   return resolved;
 });
-Loader.resolve = resolve;
 
 // Attempts to load `path` and then `path.js`
 // Returns `path` with valid file, or `undefined` otherwise
 function resolveAsFile(path) {
   // Append '.js' to path name unless it's another support filetype
   path = normalizeExt(path);
   if (urlCache.exists(path)) {
     return path;
@@ -693,17 +665,17 @@ function* getNodeModulePaths(rootURI, st
 
 // Node-style module lookup
 // Takes an id and path and attempts to load a file using node's resolving
 // algorithm.
 // `id` should already be resolved relatively at this point.
 // http://nodejs.org/api/modules.html#modules_all_together
 const nodeResolve = iced(function nodeResolve(id, requirer, { rootURI }) {
   // Resolve again
-  id = Loader.resolve(id, requirer);
+  id = resolve(id, requirer);
 
   // If this is already an absolute URI then there is no resolution to do
   if (isAbsoluteURI(id)) {
     return null;
   }
 
   // we assume that extensions are correct, i.e., a directory doesnt't have '.js'
   // and a js file isn't named 'file.json.js'
@@ -728,18 +700,16 @@ const nodeResolve = iced(function nodeRe
   }
 
   // We would not find lookup for things like `sdk/tabs`, as that's part of
   // the alias mapping. If during `generateMap`, the runtime lookup resolves
   // with `resolveURI` -- if during runtime, then `resolve` will throw.
   return null;
 });
 
-Loader.nodeResolve = nodeResolve;
-
 function addTrailingSlash(path) {
   return path.replace(/\/*$/, "/");
 }
 
 function compileMapping(paths) {
   // Make mapping array that is sorted from longest path to shortest path.
   let mapping = Object.keys(paths)
                       .sort((a, b) => b.length - a.length)
@@ -786,17 +756,16 @@ function compileMapping(paths) {
 
 const resolveURI = iced(function resolveURI(id, mapping) {
   // Do not resolve if already a resource URI
   if (isAbsoluteURI(id))
     return normalizeExt(id);
 
   return normalizeExt(mapping(id))
 });
-Loader.resolveURI = resolveURI;
 
 /**
  * Defines lazy getters on the given object, which lazily require the
  * given module the first time they are accessed, and then resolve that
  * module's exported properties.
  *
  * @param {object} obj
  *        The target object on which to define the lazy getters.
@@ -853,17 +822,17 @@ const Require = iced(function Require(lo
     manifest, rootURI, isNative, requireHook
   } = loader;
 
   if (isSystemURI(requirer.uri)) {
     // Built-in modules don't require the expensive module resolution
     // algorithm used by SDK add-ons, so give them the more efficient standard
     // resolve instead.
     isNative = false;
-    loaderResolve = Loader.resolve;
+    loaderResolve = resolve;
   }
 
   function require(id) {
     if (!id) // Throw if `id` is not passed.
       throw Error('You must provide a module name when calling require() from '
                   + requirer.id, requirer.uri);
 
     if (requireHook) {
@@ -977,17 +946,17 @@ const Require = iced(function Require(lo
         });
       }
 
       // If not found in the map, not a node module, and wasn't able to be
       // looked up, it's something
       // found in the paths most likely, like `sdk/tabs`, which should
       // be resolved relatively if needed using traditional resolve
       if (!requirement) {
-        requirement = isRelative(id) ? Loader.resolve(id, requirer.id) : id;
+        requirement = isRelative(id) ? resolve(id, requirer.id) : id;
       }
     }
     else if (modules[id]) {
       uri = requirement = id;
     }
     else if (requirer) {
       // Resolve `id` to its requirer if it's relative.
       requirement = loaderResolve(id, requirer.id);
@@ -1028,55 +997,51 @@ const Require = iced(function Require(lo
       return require(prefix + id);
     };
   };
 
   // Make `require.main === module` evaluate to true in main module scope.
   require.main = loader.main === requirer ? requirer : undefined;
   return iced(require);
 });
-Loader.Require = Require;
 
 const main = iced(function main(loader, id) {
   // If no main entry provided, and native loader is used,
   // read the entry in the manifest
   if (!id && loader.isNative)
     id = getManifestMain(loader.manifest);
   let uri = resolveURI(id, loader.mapping);
   let module = loader.main = loader.modules[uri] = Module(id, uri);
   return loader.load(loader, module).exports;
 });
-Loader.main = main;
 
 // Makes module object that is made available to CommonJS modules when they
 // are evaluated, along with `exports` and `require`.
 const Module = iced(function Module(id, uri) {
   return Object.create(null, {
     id: { enumerable: true, value: id },
     exports: { enumerable: true, writable: true, value: Object.create(null),
                configurable: true },
     uri: { value: uri }
   });
 });
-Loader.Module = Module;
 
 // Takes `loader`, and unload `reason` string and notifies all observers that
 // they should cleanup after them-self.
 const unload = iced(function unload(loader, reason) {
   // subject is a unique object created per loader instance.
   // This allows any code to cleanup on loader unload regardless of how
   // it was loaded. To handle unload for specific loader subject may be
   // asserted against loader.destructor or require('@loader/unload')
   // Note: We don not destroy loader's module cache or sandboxes map as
   // some modules may do cleanup in subsequent turns of event loop. Destroying
   // cache may cause module identity problems in such cases.
   let subject = { wrappedJSObject: loader.destructor };
   notifyObservers(subject, 'sdk:loader:destroy', reason);
 });
-Loader.unload = unload;
 
 // Function makes new loader that can be used to load CommonJS modules
 // described by a given `options.manifest`. Loader takes following options:
 // - `globals`: Optional map of globals, that all module scopes will inherit
 //   from. Map is also exposed under `globals` property of the returned loader
 //   so it can be extended further later. Defaults to `{}`.
 // - `modules` Optional map of built-in module exports mapped by module id.
 //   These modules will incorporated into module cache. Each module will be
@@ -1109,18 +1074,18 @@ function Loader(options) {
         });
         Object.defineProperty(this, "console", { value: console });
         return this.console;
       }
     },
     checkCompatibility: false,
     resolve: options.isNative ?
       // Make the returned resolve function have the same signature
-      (id, requirer) => Loader.nodeResolve(id, requirer, { rootURI: normalizeRootURI(rootURI) }) :
-      Loader.resolve,
+      (id, requirer) => nodeResolve(id, requirer, { rootURI: normalizeRootURI(rootURI) }) :
+      resolve,
     sharedGlobalBlocklist: ["sdk/indexed-db"],
     waiveIntereposition: false
   }, options);
 
   // Create overrides defaults, none at the moment
   if (typeof manifest != "object" || !manifest) {
     manifest = {};
   }
@@ -1245,27 +1210,23 @@ function Loader(options) {
   if (isNative) {
     returnObj.isNative = { enumerable: false, value: true };
     returnObj.manifest = { enumerable: false, value: manifest };
     returnObj.rootURI = { enumerable: false, value: normalizeRootURI(rootURI) };
   }
 
   return freeze(Object.create(null, returnObj));
 };
-Loader.Loader = Loader;
 
 var isSystemURI = uri => /^resource:\/\/(gre|devtools|testing-common)\//.test(uri);
 
 var isJSONURI = uri => uri.endsWith('.json');
 var isJSMURI = uri => uri.endsWith('.jsm');
 var isJSURI = uri => uri.endsWith('.js');
 var isAbsoluteURI = uri => /^(resource|chrome|file|jar):/.test(uri);
 var isRelative = id => id.startsWith(".");
 
 // Default `main` entry to './index.js' and ensure is relative,
 // since node allows 'lib/index.js' without relative `./`
 function getManifestMain(manifest) {
   let main = manifest.main || './index.js';
   return isRelative(main) ? main : './' + main;
 }
-
-module.exports = iced(Loader);
-});
--- a/devtools/shared/builtin-modules.js
+++ b/devtools/shared/builtin-modules.js
@@ -9,17 +9,16 @@
  * pseudo modules that aren't separate files but just dynamically set values.
  *
  * As it does so, the module itself doesn't have access to these globals,
  * nor the pseudo modules. Be careful to avoid loading any other js module as
  * they would also miss them.
  */
 
 const { Cu, CC, Cc, Ci } = require("chrome");
-const { Loader } = Cu.import("resource://devtools/shared/base-loader.js", {});
 const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
 const jsmScope = Cu.import("resource://gre/modules/Services.jsm", {});
 const { Services } = jsmScope;
 // Steal various globals only available in JSM scope (and not Sandbox one)
 const { PromiseDebugging, ChromeUtils, ThreadSafeChromeUtils, HeapSnapshot,
         atob, btoa, TextEncoder, TextDecoder } = jsmScope;
 
 // Create a single Sandbox to access global properties needed in this module.
@@ -167,17 +166,16 @@ function lazyRequireGetter(obj, property
     configurable: true,
     enumerable: true
   });
 }
 
 // List of pseudo modules exposed to all devtools modules.
 exports.modules = {
   "Services": Object.create(Services),
-  "toolkit/loader": Loader,
   promise,
   PromiseDebugging,
   ChromeUtils,
   ThreadSafeChromeUtils,
   HeapSnapshot,
   FileReader,
 };