Bug 1397330 - Lazy load inspector spec and front modules. r=jdescottes,jryans
authorAlexandre Poirot <poirot.alex@gmail.com>
Wed, 13 Sep 2017 00:55:00 +0200
changeset 430399 4621001d8ba55088d3c08c7a4575a500e809984b
parent 430398 53a1b44951cc0268e02955743016d3920ed6b542
child 430400 a794ed07b821173cd09561afb4a56c6f449e0e3b
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdescottes, jryans
bugs1397330
milestone57.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 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',