Bug 1392602 - Remove jetpack specifics from DevTools loader. r=jdescottes draft
authorAlexandre Poirot <poirot.alex@gmail.com>
Mon, 28 Aug 2017 11:27:07 +0200
changeset 658604 919a92469821f45a76043e537956e8b82828c248
parent 658603 deba7fd1c755c3d7855f8be531889f19b132b5cb
child 658605 bf44d123cbb341349513c59a7150ea429d456335
push id77824
push userbmo:poirot.alex@gmail.com
push dateMon, 04 Sep 2017 12:19:59 +0000
reviewersjdescottes
bugs1392602
milestone57.0a1
Bug 1392602 - Remove jetpack specifics from DevTools loader. r=jdescottes * Remove notion of "main" module * Remove notion of "isNative" and only use Loader.resolve algorithm MozReview-Commit-ID: 7GOJELAh375
devtools/shared/base-loader.js
--- a/devtools/shared/base-loader.js
+++ b/devtools/shared/base-loader.js
@@ -15,76 +15,28 @@ const { addObserver, notifyObservers } =
                         getService(Ci.nsIObserverService);
 const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
 const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
 const { normalize, dirname } = Cu.import("resource://gre/modules/osfile/ospath_unix.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "resProto",
                                    "@mozilla.org/network/protocol;1?name=resource",
                                    "nsIResProtocolHandler");
-XPCOMUtils.defineLazyServiceGetter(this, "zipCache",
-                                   "@mozilla.org/libjar/zip-reader-cache;1",
-                                   "nsIZipReaderCache");
 
 const { defineLazyGetter } = XPCOMUtils;
 
-defineLazyGetter(this, "XulApp", () => {
-  let xulappURI = module.uri.replace("toolkit/loader.js",
-                                     "sdk/system/xul-app.jsm");
-  return Cu.import(xulappURI, {});
-});
-
 // Define some shortcuts.
 const bind = Function.call.bind(Function.bind);
 const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
 const prototypeOf = Object.getPrototypeOf;
 function* getOwnIdentifiers(x) {
   yield* Object.getOwnPropertyNames(x);
   yield* Object.getOwnPropertySymbols(x);
 }
 
-const NODE_MODULES = new Set([
-  "assert",
-  "buffer_ieee754",
-  "buffer",
-  "child_process",
-  "cluster",
-  "console",
-  "constants",
-  "crypto",
-  "_debugger",
-  "dgram",
-  "dns",
-  "domain",
-  "events",
-  "freelist",
-  "fs",
-  "http",
-  "https",
-  "_linklist",
-  "module",
-  "net",
-  "os",
-  "path",
-  "punycode",
-  "querystring",
-  "readline",
-  "repl",
-  "stream",
-  "string_decoder",
-  "sys",
-  "timers",
-  "tls",
-  "tty",
-  "url",
-  "util",
-  "vm",
-  "zlib",
-]);
-
 const COMPONENT_ERROR = '`Components` is not available in this context.\n' +
   'Functionality provided by Components may be available in an SDK\n' +
   'module: https://developer.mozilla.org/en-US/Add-ons/SDK \n\n' +
   'However, if you still need to import Components, you may use the\n' +
   '`chrome` module\'s properties for shortcuts to Component properties:\n\n' +
   'Shortcuts: \n' +
   '    Cc = Components' + '.classes \n' +
   '    Ci = Components' + '.interfaces \n' +
@@ -184,164 +136,16 @@ function serializeStack(frames) {
     return frame.name + "@" +
            frame.fileName + ":" +
            frame.lineNumber + ":" +
            frame.columnNumber + "\n" +
            stack;
   }, "");
 }
 
-class DefaultMap extends Map {
-  constructor(createItem, items = undefined) {
-    super(items);
-
-    this.createItem = createItem;
-  }
-
-  get(key) {
-    if (!this.has(key)) {
-      this.set(key, this.createItem(key));
-    }
-
-    return super.get(key);
-  }
-}
-
-const urlCache = {
-  /**
-   * Returns a list of fully-qualified URLs for entries within the zip
-   * file at the given URI which are either directories or files with a
-   * .js or .json extension.
-   *
-   * @param {nsIJARURI} uri
-   * @param {string} baseURL
-   *        The original base URL, prior to resolution.
-   *
-   * @returns {Set<string>}
-   */
-  getZipFileContents(uri, baseURL) {
-    // Make sure the path has a trailing slash, and strip off the leading
-    // slash, so that we can easily check whether it is a path prefix.
-    let basePath = addTrailingSlash(uri.JAREntry).slice(1);
-    let file = uri.JARFile.QueryInterface(Ci.nsIFileURL).file;
-
-    let enumerator = zipCache.getZip(file).findEntries("(*.js|*.json|*/)");
-
-    let results = new Set();
-    for (let entry of XPCOMUtils.IterStringEnumerator(enumerator)) {
-      if (entry.startsWith(basePath)) {
-        let path = entry.slice(basePath.length);
-
-        results.add(baseURL + path);
-      }
-    }
-
-    return results;
-  },
-
-  zipContentsCache: new DefaultMap(baseURL => {
-    let uri = NetUtil.newURI(baseURL);
-
-    if (baseURL.startsWith("resource:")) {
-      uri = NetUtil.newURI(resProto.resolveURI(uri));
-    }
-
-    if (uri instanceof Ci.nsIJARURI) {
-      return urlCache.getZipFileContents(uri, baseURL);
-    }
-
-    return null;
-  }),
-
-  filesCache: new DefaultMap(url => {
-    try {
-      let uri = NetUtil.newURI(url).QueryInterface(Ci.nsIFileURL);
-
-      return uri.file.exists();
-    } catch (e) {
-      return false;
-    }
-  }),
-
-  resolutionCache: new DefaultMap(fullId => {
-    return (resolveAsFile(fullId) ||
-            resolveAsDirectory(fullId));
-  }),
-
-  nodeModulesCache: new Map(),
-
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]),
-
-  observe() {
-    // Clear any module resolution caches when the startup cache is flushed,
-    // since it probably means we're loading new copies of extensions.
-    this.zipContentsCache.clear();
-    this.filesCache.clear();
-    this.resolutionCache.clear();
-    this.nodeModulesCache.clear();
-  },
-
-  getNodeModulePaths(rootURI, start) {
-    let url = join(rootURI, start);
-
-    if (this.nodeModulesCache.has(url))
-      return this.nodeModulesCache.get(url);
-
-    let result = Array.from(getNodeModulePaths(rootURI, start));
-    this.nodeModulesCache.set(url, result);
-    return result;
-  },
-
-  /**
-   * Returns the base URL for the given URL, if one can be determined. For
-   * a resource: URL, this is the root of the resource package. For a jar:
-   * URL, it is the root of the JAR file. Otherwise, null is returned.
-   *
-   * @param {string} url
-   * @returns {string?}
-   */
-  getBaseURL(url) {
-    // By using simple string matching for the common case of resource: URLs
-    // backed by jar: URLs, we can avoid creating any nsIURI objects for the
-    // common case where the JAR contents are already cached.
-    if (url.startsWith("resource://")) {
-      return /^resource:\/\/[^\/]+\//.exec(url)[0];
-    }
-
-    let uri = NetUtil.newURI(url);
-    if (uri instanceof Ci.nsIJARURI) {
-      return `jar:${uri.JARFile.spec}!/`;
-    }
-
-    return null;
-  },
-
-  /**
-   * Returns true if the target of the given URL exists as a local file,
-   * or as an entry in a local zip file.
-   *
-   * @param {string} url
-   * @returns {boolean}
-   */
-  exists(url) {
-    if (!/\.(?:js|json)$/.test(url)) {
-      url = addTrailingSlash(url);
-    }
-
-    let baseURL = this.getBaseURL(url);
-    let scripts = baseURL && this.zipContentsCache.get(baseURL);
-    if (scripts) {
-      return scripts.has(url);
-    }
-
-    return this.filesCache.get(url);
-  },
-}
-addObserver(urlCache, "startupcache-invalidate", true);
-
 function readURI(uri) {
   let nsURI = NetUtil.newURI(uri);
   if (nsURI.scheme == "resource") {
     // Resolve to a real URI, this will catch any obvious bad paths without
     // logging assertions in debug builds, see bug 1135219
     uri = resProto.resolveURI(nsURI);
   }
 
@@ -548,23 +352,16 @@ const load = iced(function load(loader, 
       toString: { value: () => toString, writable: true, configurable: true },
     });
   }
 
   if (loadModuleHook) {
     module = loadModuleHook(module, require);
   }
 
-  if (loader.checkCompatibility) {
-    let err = XulApp.incompatibility(module);
-    if (err) {
-      throw err;
-    }
-  }
-
   // 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;
 });
@@ -595,121 +392,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;
 });
 
-// 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;
-  }
-
-  return null;
-}
-
-// Attempts to load `path/package.json`'s `main` entry,
-// followed by `path/index.js`, or `undefined` otherwise
-function resolveAsDirectory(path) {
-  try {
-    // If `path/package.json` exists, parse the `main` entry
-    // and attempt to load that
-    let manifestPath = addTrailingSlash(path) + 'package.json';
-
-    let main = (urlCache.exists(manifestPath) &&
-                getManifestMain(JSON.parse(readURI(manifestPath))));
-    if (main) {
-      let found = resolveAsFile(join(path, main));
-      if (found) {
-        return found
-      }
-    }
-  } catch (e) {}
-
-  return resolveAsFile(addTrailingSlash(path) + 'index.js');
-}
-
-function resolveRelative(rootURI, modulesDir, id) {
-  let fullId = join(rootURI, modulesDir, id);
-
-  let resolvedPath = urlCache.resolutionCache.get(fullId);
-  if (resolvedPath) {
-    return './' + resolvedPath.slice(rootURI.length);
-  }
-
-  return null;
-}
-
-// From `resolve` module
-// https://github.com/substack/node-resolve/blob/master/lib/node-modules-paths.js
-function* getNodeModulePaths(rootURI, start) {
-  let moduleDir = 'node_modules';
-
-  let parts = start.split('/');
-  while (parts.length) {
-    let leaf = parts.pop();
-    let path = [...parts, leaf, moduleDir].join("/");
-    if (leaf !== moduleDir && urlCache.exists(join(rootURI, path))) {
-      yield path;
-    }
-  }
-
-  if (urlCache.exists(join(rootURI, moduleDir))) {
-    yield moduleDir;
-  }
-}
-
-// 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 = 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'
-  let resolvedPath;
-
-  if ((resolvedPath = resolveRelative(rootURI, "", id))) {
-    return resolvedPath;
-  }
-
-  // If the requirer is an absolute URI then the node module resolution below
-  // won't work correctly as we prefix everything with rootURI
-  if (isAbsoluteURI(requirer)) {
-    return null;
-  }
-
-  // If manifest has dependencies, attempt to look up node modules
-  // in the `dependencies` list
-  for (let modulesDir of urlCache.getNodeModulePaths(rootURI, dirname(requirer))) {
-    if ((resolvedPath = resolveRelative(rootURI, modulesDir, id))) {
-      return resolvedPath;
-    }
-  }
-
-  // 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;
-});
-
 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)
@@ -817,24 +509,16 @@ function lazyRequireModule(obj, moduleId
 // of `require` that is allowed to load only a modules that are associated
 // with it during link time.
 const Require = iced(function Require(loader, requirer) {
   let {
     modules, mapping, mappingCache, resolve: loaderResolve, load,
     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 = 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) {
       return requireHook(id, _require);
     }
@@ -905,61 +589,17 @@ const Require = iced(function Require(lo
   // Used by both `require` and `require.resolve`.
   function getRequirements(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);
 
     let requirement, uri;
 
-    // TODO should get native Firefox modules before doing node-style lookups
-    // to save on loading time
-    if (isNative) {
-      let { overrides } = manifest.jetpack;
-      for (let key in overrides) {
-        // ignore any overrides using relative keys
-        if (/^[.\/]/.test(key)) {
-          continue;
-        }
-
-        // If the override is for x -> y,
-        // then using require("x/lib/z") to get reqire("y/lib/z")
-        // should also work
-        if (id == key || id.startsWith(key + "/")) {
-          id = overrides[key] + id.substr(key.length);
-          id = id.replace(/^[.\/]+/, "");
-        }
-      }
-
-      // For native modules, we want to check if it's a module specified
-      // in 'modules', like `chrome`, or `@loader` -- if it exists,
-      // just set the uri to skip resolution
-      if (!requirement && modules[id])
-        uri = requirement = id;
-
-      if (!requirement && !NODE_MODULES.has(id)) {
-        // If `isNative` defined, this is using the new, native-style
-        // loader, not cuddlefish, so lets resolve using node's algorithm
-        // and get back a path that needs to be resolved via paths mapping
-        // in `resolveURI`
-        requirement = loaderResolve(id, requirer.id, {
-          manifest: manifest,
-          rootURI: rootURI
-        });
-      }
-
-      // 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) ? resolve(id, requirer.id) : id;
-      }
-    }
-    else if (modules[id]) {
+    if (modules[id]) {
       uri = requirement = id;
     }
     else if (requirer) {
       // Resolve `id` to its requirer if it's relative.
       requirement = loaderResolve(id, requirer.id);
     }
     else {
       requirement = id;
@@ -993,31 +633,19 @@ const Require = iced(function Require(lo
   // This is like webpack's require.context.  It returns a new require
   // function that prepends the prefix to any requests.
   require.context = prefix => {
     return id => {
       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);
 });
 
-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;
-});
-
 // 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 }
@@ -1119,20 +747,16 @@ function Loader(options) {
     }
   }, modules);
 
   const builtinModuleExports = modules;
   modules = {};
   for (let id of Object.keys(builtinModuleExports)) {
     // We resolve `uri` from `id` since modules are cached by `uri`.
     let uri = resolveURI(id, mapping);
-    // In native loader, the mapping will not contain values for
-    // pseudomodules -- store them as their ID rather than the URI
-    if (isNative && !uri)
-      uri = id;
     let module = Module(id, uri);
 
     // Lazily expose built-in modules in order to
     // allow them to be loaded lazily.
     Object.defineProperty(module, "exports", {
       enumerable: true,
       get: function() {
         return builtinModuleExports[id];
@@ -1172,61 +796,35 @@ function Loader(options) {
   // as they are pure implementation detail that no one should rely upon.
   let returnObj = {
     destructor: { enumerable: false, value: destructor },
     globals: { enumerable: false, value: globals },
     mapping: { enumerable: false, value: mapping },
     mappingCache: { enumerable: false, value: new Map() },
     // Map of module objects indexed by module URIs.
     modules: { enumerable: false, value: modules },
-    metadata: { enumerable: false, value: metadata },
     useSharedGlobalSandbox: { enumerable: false, value: !!sharedGlobal },
     sharedGlobalSandbox: { enumerable: false, value: sharedGlobalSandbox },
     sharedGlobalBlocklist: { enumerable: false, value: sharedGlobalBlocklist },
     sharedGlobalBlacklist: { enumerable: false, value: sharedGlobalBlocklist },
     // Map of module sandboxes indexed by module URIs.
     sandboxes: { enumerable: false, value: {} },
     resolve: { enumerable: false, value: resolve },
     // ID of the addon, if provided.
     id: { enumerable: false, value: options.id },
     // Whether the modules loaded should be ignored by the debugger
     invisibleToDebugger: { enumerable: false,
                            value: options.invisibleToDebugger || false },
     load: { enumerable: false, value: options.load || load },
-    checkCompatibility: { enumerable: false, value: checkCompatibility },
     requireHook: { enumerable: false, value: options.requireHook },
     loadModuleHook: { enumerable: false, value: options.loadModuleHook },
-    // Main (entry point) module, it can be set only once, since loader
-    // instance can have only one main module.
-    main: new function() {
-      let main;
-      return {
-        enumerable: false,
-        get: function() { return main; },
-        // Only set main if it has not being set yet!
-        set: function(module) { main = main || module; }
-      }
-    }
   };
 
-  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));
 };
 
 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;
-}