418 lines
14 KiB
JavaScript
418 lines
14 KiB
JavaScript
/* 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";
|
|
|
|
var gRegisteredModules = Object.create(null);
|
|
|
|
const ActorRegistry = {
|
|
// Map of global actor names to actor constructors.
|
|
globalActorFactories: {},
|
|
// Map of target-scoped actor names to actor constructors.
|
|
targetScopedActorFactories: {},
|
|
init(connections) {
|
|
this._connections = connections;
|
|
},
|
|
|
|
/**
|
|
* Register a CommonJS module with the devtools server.
|
|
* @param id string
|
|
* The ID of a CommonJS module.
|
|
* The actor is going to be registered immediately, but loaded only
|
|
* when a client starts sending packets to an actor with the same id.
|
|
*
|
|
* @param options object
|
|
* An object with 3 mandatory attributes:
|
|
* - prefix (string):
|
|
* The prefix of an actor is used to compute:
|
|
* - the `actorID` of each new actor instance (ex: prefix1). (See Pool.manage)
|
|
* - the actor name in the listTabs request. Sending a listTabs
|
|
* request to the root actor returns actor IDs. IDs are in
|
|
* dictionaries, with actor names as keys and actor IDs as values.
|
|
* The actor name is the prefix to which the "Actor" string is
|
|
* appended. So for an actor with the `console` prefix, the actor
|
|
* name will be `consoleActor`.
|
|
* - constructor (string):
|
|
* the name of the exported symbol to be used as the actor
|
|
* constructor.
|
|
* - type (a dictionary of booleans with following attribute names):
|
|
* - "global"
|
|
* registers a global actor instance, if true.
|
|
* A global actor has the root actor as its parent.
|
|
* - "target"
|
|
* registers a target-scoped actor instance, if true.
|
|
* A new actor will be created for each target, such as a tab.
|
|
*/
|
|
registerModule(id, options) {
|
|
if (id in gRegisteredModules) {
|
|
return;
|
|
}
|
|
|
|
if (!options) {
|
|
throw new Error(
|
|
"ActorRegistry.registerModule requires an options argument"
|
|
);
|
|
}
|
|
const { prefix, constructor, type } = options;
|
|
if (typeof prefix !== "string") {
|
|
throw new Error(
|
|
`Lazy actor definition for '${id}' requires a string ` +
|
|
`'prefix' option.`
|
|
);
|
|
}
|
|
if (typeof constructor !== "string") {
|
|
throw new Error(
|
|
`Lazy actor definition for '${id}' requires a string ` +
|
|
`'constructor' option.`
|
|
);
|
|
}
|
|
if (!("global" in type) && !("target" in type)) {
|
|
throw new Error(
|
|
`Lazy actor definition for '${id}' requires a dictionary ` +
|
|
`'type' option whose attributes can be 'global' or 'target'.`
|
|
);
|
|
}
|
|
const name = prefix + "Actor";
|
|
const mod = {
|
|
id,
|
|
prefix,
|
|
constructorName: constructor,
|
|
type,
|
|
globalActor: type.global,
|
|
targetScopedActor: type.target,
|
|
};
|
|
gRegisteredModules[id] = mod;
|
|
if (mod.targetScopedActor) {
|
|
this.addTargetScopedActor(mod, name);
|
|
}
|
|
if (mod.globalActor) {
|
|
this.addGlobalActor(mod, name);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Unregister a previously-loaded CommonJS module from the devtools server.
|
|
*/
|
|
unregisterModule(id) {
|
|
const mod = gRegisteredModules[id];
|
|
if (!mod) {
|
|
throw new Error(
|
|
"Tried to unregister a module that was not previously registered."
|
|
);
|
|
}
|
|
|
|
// Lazy actors
|
|
if (mod.targetScopedActor) {
|
|
this.removeTargetScopedActor(mod);
|
|
}
|
|
if (mod.globalActor) {
|
|
this.removeGlobalActor(mod);
|
|
}
|
|
|
|
delete gRegisteredModules[id];
|
|
},
|
|
|
|
/**
|
|
* Install Firefox-specific actors.
|
|
*
|
|
* /!\ Be careful when adding a new actor, especially global actors.
|
|
* Any new global actor will be exposed and returned by the root actor.
|
|
*/
|
|
addBrowserActors() {
|
|
this.registerModule("devtools/server/actors/preference", {
|
|
prefix: "preference",
|
|
constructor: "PreferenceActor",
|
|
type: { global: true },
|
|
});
|
|
this.registerModule("devtools/server/actors/addon/addons", {
|
|
prefix: "addons",
|
|
constructor: "AddonsActor",
|
|
type: { global: true },
|
|
});
|
|
this.registerModule("devtools/server/actors/device", {
|
|
prefix: "device",
|
|
constructor: "DeviceActor",
|
|
type: { global: true },
|
|
});
|
|
this.registerModule("devtools/server/actors/heap-snapshot-file", {
|
|
prefix: "heapSnapshotFile",
|
|
constructor: "HeapSnapshotFileActor",
|
|
type: { global: true },
|
|
});
|
|
// Always register this as a global module, even while there is a pref turning
|
|
// on and off the other performance actor. This actor shouldn't conflict with
|
|
// the other one. These are also lazily loaded so there shouldn't be a performance
|
|
// impact.
|
|
this.registerModule("devtools/server/actors/perf", {
|
|
prefix: "perf",
|
|
constructor: "PerfActor",
|
|
type: { global: true },
|
|
});
|
|
/**
|
|
* Always register parent accessibility actor as a global module. This
|
|
* actor is responsible for all top level accessibility actor functionality
|
|
* that relies on the parent process.
|
|
*/
|
|
this.registerModule(
|
|
"devtools/server/actors/accessibility/parent-accessibility",
|
|
{
|
|
prefix: "parentAccessibility",
|
|
constructor: "ParentAccessibilityActor",
|
|
type: { global: true },
|
|
}
|
|
);
|
|
|
|
this.registerModule("devtools/server/actors/screenshot", {
|
|
prefix: "screenshot",
|
|
constructor: "ScreenshotActor",
|
|
type: { global: true },
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Install target-scoped actors.
|
|
*/
|
|
addTargetScopedActors() {
|
|
this.registerModule("devtools/server/actors/webconsole", {
|
|
prefix: "console",
|
|
constructor: "WebConsoleActor",
|
|
type: { target: true },
|
|
});
|
|
this.registerModule("devtools/server/actors/inspector/inspector", {
|
|
prefix: "inspector",
|
|
constructor: "InspectorActor",
|
|
type: { target: true },
|
|
});
|
|
this.registerModule("devtools/server/actors/style-sheets", {
|
|
prefix: "styleSheets",
|
|
constructor: "StyleSheetsActor",
|
|
type: { target: true },
|
|
});
|
|
this.registerModule("devtools/server/actors/memory", {
|
|
prefix: "memory",
|
|
constructor: "MemoryActor",
|
|
type: { target: true },
|
|
});
|
|
this.registerModule("devtools/server/actors/reflow", {
|
|
prefix: "reflow",
|
|
constructor: "ReflowActor",
|
|
type: { target: true },
|
|
});
|
|
this.registerModule("devtools/server/actors/css-properties", {
|
|
prefix: "cssProperties",
|
|
constructor: "CssPropertiesActor",
|
|
type: { target: true },
|
|
});
|
|
this.registerModule("devtools/server/actors/animation", {
|
|
prefix: "animations",
|
|
constructor: "AnimationsActor",
|
|
type: { target: true },
|
|
});
|
|
this.registerModule("devtools/server/actors/emulation/responsive", {
|
|
prefix: "responsive",
|
|
constructor: "ResponsiveActor",
|
|
type: { target: true },
|
|
});
|
|
this.registerModule(
|
|
"devtools/server/actors/addon/webextension-inspected-window",
|
|
{
|
|
prefix: "webExtensionInspectedWindow",
|
|
constructor: "WebExtensionInspectedWindowActor",
|
|
type: { target: true },
|
|
}
|
|
);
|
|
this.registerModule("devtools/server/actors/accessibility/accessibility", {
|
|
prefix: "accessibility",
|
|
constructor: "AccessibilityActor",
|
|
type: { target: true },
|
|
});
|
|
this.registerModule("devtools/server/actors/changes", {
|
|
prefix: "changes",
|
|
constructor: "ChangesActor",
|
|
type: { target: true },
|
|
});
|
|
this.registerModule("devtools/server/actors/manifest", {
|
|
prefix: "manifest",
|
|
constructor: "ManifestActor",
|
|
type: { target: true },
|
|
});
|
|
this.registerModule(
|
|
"devtools/server/actors/network-monitor/network-content",
|
|
{
|
|
prefix: "networkContent",
|
|
constructor: "NetworkContentActor",
|
|
type: { target: true },
|
|
}
|
|
);
|
|
this.registerModule("devtools/server/actors/screenshot-content", {
|
|
prefix: "screenshotContent",
|
|
constructor: "ScreenshotContentActor",
|
|
type: { target: true },
|
|
});
|
|
this.registerModule("devtools/server/actors/tracer", {
|
|
prefix: "tracer",
|
|
constructor: "TracerActor",
|
|
type: { target: true },
|
|
});
|
|
this.registerModule("devtools/server/actors/objects-manager", {
|
|
prefix: "objectsManager",
|
|
constructor: "ObjectsManagerActor",
|
|
type: { target: true },
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Registers handlers for new target-scoped request types defined dynamically.
|
|
*
|
|
* Note that the name of the request type is not allowed to clash with existing protocol
|
|
* packet properties, like 'title', 'url' or 'actor', since that would break the protocol.
|
|
*
|
|
* @param options object
|
|
* - constructorName: (required)
|
|
* name of actor constructor, which is also used when removing the actor.
|
|
* One of the following:
|
|
* - id:
|
|
* module ID that contains the actor
|
|
* - constructorFun:
|
|
* a function to construct the actor
|
|
* @param name string
|
|
* The name of the new request type.
|
|
*/
|
|
addTargetScopedActor(options, name) {
|
|
if (!name) {
|
|
throw Error("addTargetScopedActor requires the `name` argument");
|
|
}
|
|
if (["title", "url", "actor"].includes(name)) {
|
|
throw Error(name + " is not allowed");
|
|
}
|
|
if (this.targetScopedActorFactories.hasOwnProperty(name)) {
|
|
throw Error(name + " already exists");
|
|
}
|
|
this.targetScopedActorFactories[name] = { options, name };
|
|
},
|
|
|
|
/**
|
|
* Unregisters the handler for the specified target-scoped request type.
|
|
*
|
|
* When unregistering an existing target-scoped actor, we remove the actor factory as
|
|
* well as all existing instances of the actor.
|
|
*
|
|
* @param actor object, string
|
|
* In case of object:
|
|
* The `actor` object being given to related addTargetScopedActor call.
|
|
* In case of string:
|
|
* The `name` string being given to related addTargetScopedActor call.
|
|
*/
|
|
removeTargetScopedActor(actorOrName) {
|
|
let name;
|
|
if (typeof actorOrName == "string") {
|
|
name = actorOrName;
|
|
} else {
|
|
const actor = actorOrName;
|
|
for (const factoryName in this.targetScopedActorFactories) {
|
|
const handler = this.targetScopedActorFactories[factoryName];
|
|
if (
|
|
handler.options.constructorName == actor.name ||
|
|
handler.options.id == actor.id
|
|
) {
|
|
name = factoryName;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!name) {
|
|
return;
|
|
}
|
|
delete this.targetScopedActorFactories[name];
|
|
for (const connID of Object.getOwnPropertyNames(this._connections)) {
|
|
// DevToolsServerConnection in child process don't have rootActor
|
|
if (this._connections[connID].rootActor) {
|
|
this._connections[connID].rootActor.removeActorByName(name);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Registers handlers for new browser-scoped request types defined dynamically.
|
|
*
|
|
* Note that the name of the request type is not allowed to clash with existing protocol
|
|
* packet properties, like 'from', 'tabs' or 'selected', since that would break the protocol.
|
|
*
|
|
* @param options object
|
|
* - constructorName: (required)
|
|
* name of actor constructor, which is also used when removing the actor.
|
|
* One of the following:
|
|
* - id:
|
|
* module ID that contains the actor
|
|
* - constructorFun:
|
|
* a function to construct the actor
|
|
* @param name string
|
|
* The name of the new request type.
|
|
*/
|
|
addGlobalActor(options, name) {
|
|
if (!name) {
|
|
throw Error("addGlobalActor requires the `name` argument");
|
|
}
|
|
if (["from", "tabs", "selected"].includes(name)) {
|
|
throw Error(name + " is not allowed");
|
|
}
|
|
if (this.globalActorFactories.hasOwnProperty(name)) {
|
|
throw Error(name + " already exists");
|
|
}
|
|
this.globalActorFactories[name] = { options, name };
|
|
},
|
|
|
|
/**
|
|
* Unregisters the handler for the specified browser-scoped request type.
|
|
*
|
|
* When unregistering an existing global actor, we remove the actor factory as well as
|
|
* all existing instances of the actor.
|
|
*
|
|
* @param actor object, string
|
|
* In case of object:
|
|
* The `actor` object being given to related addGlobalActor call.
|
|
* In case of string:
|
|
* The `name` string being given to related addGlobalActor call.
|
|
*/
|
|
removeGlobalActor(actorOrName) {
|
|
let name;
|
|
if (typeof actorOrName == "string") {
|
|
name = actorOrName;
|
|
} else {
|
|
const actor = actorOrName;
|
|
for (const factoryName in this.globalActorFactories) {
|
|
const handler = this.globalActorFactories[factoryName];
|
|
if (
|
|
handler.options.constructorName == actor.name ||
|
|
handler.options.id == actor.id
|
|
) {
|
|
name = factoryName;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!name) {
|
|
return;
|
|
}
|
|
delete this.globalActorFactories[name];
|
|
for (const connID of Object.getOwnPropertyNames(this._connections)) {
|
|
// DevToolsServerConnection in child process don't have rootActor
|
|
if (this._connections[connID].rootActor) {
|
|
this._connections[connID].rootActor.removeActorByName(name);
|
|
}
|
|
}
|
|
},
|
|
|
|
destroy() {
|
|
for (const id of Object.getOwnPropertyNames(gRegisteredModules)) {
|
|
this.unregisterModule(id);
|
|
}
|
|
gRegisteredModules = Object.create(null);
|
|
|
|
this.globalActorFactories = {};
|
|
this.targetScopedActorFactories = {};
|
|
},
|
|
};
|
|
|
|
exports.ActorRegistry = ActorRegistry;
|