Bug 1397330 - Lazy load inspector spec and front modules. r=jdescottes,jryans draft
authorAlexandre Poirot <poirot.alex@gmail.com>
Wed, 13 Sep 2017 00:55:00 +0200
changeset 664856 c2ce1a0906ffcaff36a3e93be3f86aa8c8ec4032
parent 662089 a5f163da8a9be5d2e86138c57d59be69723b5457
child 664857 8f6efc3554071edbe90bcfd1c6c25821f91dd792
push id79824
push userbmo:poirot.alex@gmail.com
push dateThu, 14 Sep 2017 13:26:05 +0000
reviewersjdescottes, jryans
bugs1397330
milestone57.0a1
Bug 1397330 - Lazy load inspector spec and front modules. r=jdescottes,jryans MozReview-Commit-ID: BbF40tQrzoF
devtools/shared/fronts/inspector.js
devtools/shared/protocol.js
devtools/shared/specs/index.js
devtools/shared/specs/inspector.js
devtools/shared/specs/moz.build
--- a/devtools/shared/fronts/inspector.js
+++ b/devtools/shared/fronts/inspector.js
@@ -1,16 +1,13 @@
 /* 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/. */
 "use strict";
 
-require("devtools/shared/fronts/styles");
-require("devtools/shared/fronts/highlighters");
-require("devtools/shared/fronts/layout");
 const { SimpleStringFront } = require("devtools/shared/fronts/string");
 const {
   Front,
   FrontClassWithSpec,
   custom,
   preEvent,
   types
 } = require("devtools/shared/protocol.js");
--- a/devtools/shared/protocol.js
+++ b/devtools/shared/protocol.js
@@ -5,16 +5,17 @@
 "use strict";
 
 var promise = require("promise");
 var defer = require("devtools/shared/defer");
 const { extend } = require("devtools/shared/extend");
 var EventEmitter = require("devtools/shared/event-emitter");
 var {getStack, callFunctionWithAsyncStack} = require("devtools/shared/platform/stack");
 var {settleAll} = require("devtools/shared/DevToolsUtils");
+var {lazyLoadSpec, lazyLoadFront} = require("devtools/shared/specs/index");
 
 /**
  * Types: named marshallers/demarshallers.
  *
  * Types provide a 'write' function that takes a js representation and
  * returns a protocol representation, and a "read" function that
  * takes a protocol representation and returns a js representation.
  *
@@ -60,16 +61,26 @@ types.getType = function (type) {
   }
 
   // If already registered, we're done here.
   let reg = registeredTypes.get(type);
   if (reg) {
     return reg;
   }
 
+  // Try to lazy load the spec, if not already loaded.
+  if (lazyLoadSpec(type)) {
+    // If a spec module was lazy loaded, it will synchronously call
+    // generateActorSpec, and set the type in `registeredTypes`.
+    reg = registeredTypes.get(type);
+    if (reg) {
+      return reg;
+    }
+  }
+
   // New type, see if it's a collection/lifetime type:
   let sep = type.indexOf(":");
   if (sep >= 0) {
     let collection = type.substring(0, sep);
     let subtype = types.getType(type.substring(sep + 1));
 
     if (collection === "array") {
       return types.addArrayType(subtype);
@@ -85,22 +96,16 @@ types.getType = function (type) {
   }
 
   // Not a collection, might be actor detail
   let pieces = type.split("#", 2);
   if (pieces.length > 1) {
     return types.addActorDetail(type, pieces[0], pieces[1]);
   }
 
-  // Might be a lazily-loaded type
-  if (type === "longstring") {
-    require("devtools/shared/specs/string");
-    return registeredTypes.get("longstring");
-  }
-
   throw Error("Unknown type: " + type);
 };
 
 /**
  * Don't allow undefined when writing primitive types to packets.  If
  * you want to allow undefined, use a nullable type.
  */
 function identityWrite(v) {
@@ -255,32 +260,48 @@ types.addDictType = function (name, spec
  * constructed, but the read and write methods won't work until
  * the associated addActorImpl or addActorFront methods have been
  * called during actor/front construction.
  *
  * @param string name
  *    The typestring to register.
  */
 types.addActorType = function (name) {
+  // We call addActorType from:
+  //   FrontClassWithSpec when registering front synchronously,
+  //   generateActorSpec when defining specs,
+  //   specs modules to register actor type early to use them in other types
+  if (registeredTypes.has(name)) {
+    return registeredTypes.get(name);
+  }
   let type = types.addType(name, {
     _actor: true,
     category: "actor",
     read: (v, ctx, detail) => {
       // If we're reading a request on the server side, just
       // find the actor registered with this actorID.
       if (ctx instanceof Actor) {
         return ctx.conn.getActor(v);
       }
 
       // Reading a response on the client side, check for an
       // existing front on the connection, and create the front
       // if it isn't found.
       let actorID = typeof (v) === "string" ? v : v.actor;
       let front = ctx.conn.getActor(actorID);
       if (!front) {
+        // If front isn't instanciated yet, create one.
+
+        // Try lazy loading front if not already loaded.
+        // The front module will synchronously call `FrontClassWithSpec` and
+        // augment `type` with the `frontClass` attribute.
+        if (!type.frontClass) {
+          lazyLoadFront(name);
+        }
+
         front = new type.frontClass(ctx.conn); // eslint-disable-line new-cap
         front.actorID = actorID;
         ctx.marshallPool().manage(front);
       }
 
       v = type.formType(detail).read(v, front, detail);
       front.form(v, detail, ctx);
 
@@ -441,17 +462,20 @@ types.JSON = types.addType("json");
  * @param number index
  *    The argument index to place at this position.
  * @param type type
  *    The argument should be marshalled as this type.
  * @constructor
  */
 var Arg = function (index, type) {
   this.index = index;
-  this.type = types.getType(type);
+  // Prevent force loading all Arg types by accessing it only when needed
+  loader.lazyGetter(this, "type", function () {
+    return types.getType(type);
+  });
 };
 
 Arg.prototype = {
   write: function (arg, ctx) {
     return this.type.write(arg, ctx);
   },
 
   read: function (v, ctx, outArgs) {
@@ -526,17 +550,20 @@ exports.Option = function (index, type) 
 
 /**
  * Placeholder for return values in a response template.
  *
  * @param type type
  *    The return value should be marshalled as this type.
  */
 var RetVal = function (type) {
-  this.type = types.getType(type);
+  // Prevent force loading all RetVal types by accessing it only when needed
+  loader.lazyGetter(this, "type", function () {
+    return types.getType(type);
+  });
 };
 
 RetVal.prototype = {
   write: function (v, ctx) {
     return this.type.write(v, ctx);
   },
 
   read: function (v, ctx) {
new file mode 100644
--- /dev/null
+++ b/devtools/shared/specs/index.js
@@ -0,0 +1,96 @@
+/* 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/. */
+
+"use strict";
+
+// Registry indexing all specs and front modules
+//
+// All spec and front modules should be listed here
+// in order to be referenced by any other spec or front module.
+//
+// TODO: For now we only register dynamically loaded specs and fronts here.
+// See Bug 1399589 for supporting all specs and front modules.
+
+// Declare in which spec module and front module a set of types are defined
+const Types = [
+  {
+    types: ["pagestyle", "domstylerule"],
+    spec: "devtools/shared/specs/styles",
+    front: "devtools/shared/fronts/styles",
+  },
+  {
+    types: ["highlighter", "customhighlighter"],
+    spec: "devtools/shared/specs/highlighters",
+    front: "devtools/shared/fronts/highlighters",
+  },
+  {
+    types: ["grid", "layout"],
+    spec: "devtools/shared/specs/layout",
+    front: "devtools/shared/fronts/layout",
+  },
+  {
+    types: ["longstring"],
+    spec: "devtools/shared/specs/string",
+    front: "devtools/shared/fronts/string",
+  },
+];
+
+const lazySpecs = new Map();
+const lazyFronts = new Map();
+
+// Convert the human readable `Types` list into efficient maps
+Types.forEach(item => {
+  item.types.forEach(type => {
+    lazySpecs.set(type, item.spec);
+    lazyFronts.set(type, item.front);
+  });
+});
+
+/**
+ * Try lazy loading spec module for the given type.
+ *
+ * @param [string] type
+ *    Type name
+ *
+ * @returns true, if it matched a lazy loaded type and tried to load it.
+ */
+function lazyLoadSpec(type) {
+  let modulePath = lazySpecs.get(type);
+  if (modulePath) {
+    try {
+      require(modulePath);
+    } catch (e) {
+      throw new Error(
+        `Unable to load lazy spec module '${modulePath}' for type '${type}'`);
+    }
+    lazySpecs.delete(type);
+    return true;
+  }
+  return false;
+}
+exports.lazyLoadSpec = lazyLoadSpec;
+
+/**
+ * Try lazy loading front module for the given type.
+ *
+ * @param [string] type
+ *    Type name
+ *
+ * @returns true, if it matched a lazy loaded type and tried to load it.
+ */
+function lazyLoadFront(type) {
+  let modulePath = lazyFronts.get(type);
+  if (modulePath) {
+    try {
+      require(modulePath);
+    } catch (e) {
+      throw new Error(
+        `Unable to load lazy front module '${modulePath}' for type '${type}'`);
+    }
+    lazyFronts.delete(type);
+    return true;
+  }
+  return false;
+}
+exports.lazyLoadFront = lazyLoadFront;
--- a/devtools/shared/specs/inspector.js
+++ b/devtools/shared/specs/inspector.js
@@ -6,19 +6,16 @@
 const {
   Arg,
   Option,
   RetVal,
   generateActorSpec,
   types
 } = require("devtools/shared/protocol");
 const { nodeSpec } = require("devtools/shared/specs/node");
-require("devtools/shared/specs/styles");
-require("devtools/shared/specs/highlighters");
-require("devtools/shared/specs/layout");
 
 exports.nodeSpec = nodeSpec;
 
 /**
  * Returned from any call that might return a node that isn't connected to root
  * by nodes the child has seen, such as querySelector.
  */
 types.addDictType("disconnectedNode", {
--- a/devtools/shared/specs/moz.build
+++ b/devtools/shared/specs/moz.build
@@ -18,16 +18,17 @@ DevToolsModules(
     'emulation.js',
     'environment.js',
     'eventlooplag.js',
     'frame.js',
     'framerate.js',
     'gcli.js',
     'heap-snapshot-file.js',
     'highlighters.js',
+    'index.js',
     'inspector.js',
     'layout.js',
     'memory.js',
     'node.js',
     'performance-entries.js',
     'performance-recording.js',
     'performance.js',
     'preference.js',