Bug 1314861: Pre-compute path mapping function to save on runtime lookups. r?ochameau draft
authorKris Maglione <maglione.k@gmail.com>
Fri, 07 Apr 2017 18:27:07 -0700
changeset 558812 5f1754e1d3d7407451c191cd4e15243dd0be655a
parent 558811 aad8a872168fb97a73cb98358e449a888c07a4d0
child 558813 22e1611c9ba2e557344be3d32c783b192c7251e8
push id52953
push usermaglione.k@gmail.com
push dateSat, 08 Apr 2017 01:32:07 +0000
reviewersochameau
bugs1314861
milestone55.0a1
Bug 1314861: Pre-compute path mapping function to save on runtime lookups. r?ochameau MozReview-Commit-ID: Lc4ju1XOfgR
addon-sdk/source/lib/toolkit/loader.js
--- a/addon-sdk/source/lib/toolkit/loader.js
+++ b/addon-sdk/source/lib/toolkit/loader.js
@@ -704,39 +704,57 @@ const nodeResolve = iced(function nodeRe
 });
 
 Loader.nodeResolve = nodeResolve;
 
 function addTrailingSlash(path) {
   return path.replace(/\/*$/, "/");
 }
 
-const resolveURI = iced(function resolveURI(id, mapping) {
-  // Do not resolve if already a resource URI
-  if (isAbsoluteURI(id))
-    return normalizeExt(id);
+function compileMapping(mapping) {
+  let patterns = [];
+  let paths = {};
+
+  let escapeMeta = str => str.replace(/([.\\?+*(){}[\]^$])/g, '\\$1')
 
   for (let [path, uri] of mapping) {
     // Strip off any trailing slashes to make comparisons simpler
-    let stripped = path.replace(/\/+$/, "");
+    if (path.endsWith("/")) {
+      path = path.slice(0, -1);
+      uri = uri.replace(/\/+$/, "");
+    }
+
+    paths[path] = uri;
 
     // We only want to match path segments explicitly. Examples:
     // * "foo/bar" matches for "foo/bar"
     // * "foo/bar" matches for "foo/bar/baz"
     // * "foo/bar" does not match for "foo/bar-1"
     // * "foo/bar/" does not match for "foo/bar"
     // * "foo/bar/" matches for "foo/bar/baz"
     //
     // Check for an empty path, an exact match, or a substring match
     // with the next character being a forward slash.
-    if(stripped === "" || id === stripped || id.startsWith(stripped + "/")) {
-      return normalizeExt(id.replace(path, uri));
-    }
+    if (path == "")
+      patterns.push("");
+    else
+      patterns.push(`${escapeMeta(path)}(?=$|/)`);
   }
-  return null;
+
+  let pattern = new RegExp(`^(${patterns.join('|')})`);
+
+  return id => id.replace(pattern, (m0, m1) => paths[m1]);
+}
+
+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.
  *
@@ -1079,16 +1097,18 @@ function Loader(options) {
   // observer notifications.
   let destructor = freeze(Object.create(null));
 
   // Make mapping array that is sorted from longest path to shortest path.
   let mapping = Object.keys(paths)
                       .sort((a, b) => b.length - a.length)
                       .map(path => [path, paths[path]]);
 
+  mapping = compileMapping(mapping);
+
   // Define pseudo modules.
   modules = override({
     '@loader/unload': destructor,
     '@loader/options': options,
     'chrome': { Cc: Cc, Ci: Ci, Cu: Cu, Cr: Cr, Cm: Cm,
                 CC: bind(CC, Components), components: Components,
                 // `ChromeWorker` has to be inject in loader global scope.
                 // It is done by bootstrap.js:loadSandbox for the SDK.