Bug 1397330 - Lazy load inspector spec and front modules. r=jdescottes,jryans
MozReview-Commit-ID: BbF40tQrzoF
--- 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',