Bug 1342478: Support loading modules from relative paths in shell module loader. r=jonco
authorAndré Bargull <andre.bargull@gmail.com>
Thu, 02 Mar 2017 02:35:15 -0800
changeset 345628 2cd83ad751203f5f91b1a1411fb930be9bff18c1
parent 345627 9b990c5890a81b46e4ffd8ae28c230effe9fba40
child 345629 08df0e07f0ba02a27e532b25a0d3415166a6bba1
push id31441
push userkwierso@gmail.com
push dateThu, 02 Mar 2017 22:57:54 +0000
treeherdermozilla-central@b23d6277acca [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs1342478
milestone54.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1342478: Support loading modules from relative paths in shell module loader. r=jonco
js/src/shell/ModuleLoader.js
--- a/js/src/shell/ModuleLoader.js
+++ b/js/src/shell/ModuleLoader.js
@@ -9,29 +9,59 @@
 // Save standard built-ins before scripts can modify them.
 const ArrayPrototypeJoin = Array.prototype.join;
 const MapPrototypeGet = Map.prototype.get;
 const MapPrototypeHas = Map.prototype.has;
 const MapPrototypeSet = Map.prototype.set;
 const ObjectDefineProperty = Object.defineProperty;
 const ReflectApply = Reflect.apply;
 const StringPrototypeIndexOf = String.prototype.indexOf;
+const StringPrototypeLastIndexOf = String.prototype.lastIndexOf;
+const StringPrototypeStartsWith = String.prototype.startsWith;
 const StringPrototypeSubstring = String.prototype.substring;
 
 const ReflectLoader = new class {
     constructor() {
         this.registry = new Map();
+        this.modulePaths = new Map();
         this.loadPath = getModuleLoadPath();
     }
 
-    resolve(name) {
+    resolve(name, module) {
         if (os.path.isAbsolute(name))
             return name;
 
-        return os.path.join(this.loadPath, name);
+        let loadPath = this.loadPath;
+        if (module) {
+            // Treat |name| as a relative path if it starts with either "./"
+            // or "../".
+            let isRelative = ReflectApply(StringPrototypeStartsWith, name, ["./"])
+                          || ReflectApply(StringPrototypeStartsWith, name, ["../"])
+#ifdef XP_WIN
+                          || ReflectApply(StringPrototypeStartsWith, name, [".\\"])
+                          || ReflectApply(StringPrototypeStartsWith, name, ["..\\"])
+#endif
+                             ;
+
+            // If |name| is a relative path and |module|'s path is available,
+            // load |name| relative to the referring module.
+            if (isRelative && ReflectApply(MapPrototypeHas, this.modulePaths, [module])) {
+                let modulePath = ReflectApply(MapPrototypeGet, this.modulePaths, [module]);
+                let sepIndex = ReflectApply(StringPrototypeLastIndexOf, modulePath, ["/"]);
+#ifdef XP_WIN
+                let otherSepIndex = ReflectApply(StringPrototypeLastIndexOf, modulePath, ["\\"]);
+                if (otherSepIndex > sepIndex)
+                    sepIndex = otherSepIndex;
+#endif
+                if (sepIndex >= 0)
+                    loadPath = ReflectApply(StringPrototypeSubstring, modulePath, [0, sepIndex]);
+            }
+        }
+
+        return os.path.join(loadPath, name);
     }
 
     normalize(path) {
 #ifdef XP_WIN
         // Replace all forward slashes with backward slashes.
         // NB: It may be tempting to replace this loop with a call to
         // String.prototype.replace, but user scripts may have modified
         // String.prototype or RegExp.prototype built-in functions, which makes
@@ -119,34 +149,35 @@ const ReflectLoader = new class {
     loadAndParse(path) {
         let normalized = this.normalize(path);
         if (ReflectApply(MapPrototypeHas, this.registry, [normalized]))
             return ReflectApply(MapPrototypeGet, this.registry, [normalized]);
 
         let source = this.fetch(path);
         let module = parseModule(source, path);
         ReflectApply(MapPrototypeSet, this.registry, [normalized, module]);
+        ReflectApply(MapPrototypeSet, this.modulePaths, [module, path]);
         return module;
     }
 
     loadAndExecute(path) {
         let module = this.loadAndParse(path);
         module.declarationInstantiation();
         return module.evaluation();
     }
 
     importRoot(path) {
         return this.loadAndExecute(path);
     }
 
     ["import"](name, referrer) {
-        let path = this.resolve(name);
+        let path = this.resolve(name, null);
         return this.loadAndExecute(path);
     }
 };
 
 setModuleResolveHook((module, requestName) => {
-    let path = ReflectLoader.resolve(requestName);
+    let path = ReflectLoader.resolve(requestName, module);
     return ReflectLoader.loadAndParse(path)
 });
 
 Reflect.Loader = ReflectLoader;
 }