Bug 1473513 - separate registerModule behavior from DebuggerServer; r=ochameau

MozReview-Commit-ID: 3GsXRxcIKfx

Depends on D6473

Differential Revision: https://phabricator.services.mozilla.com/D6474

--HG--
rename : devtools/server/main.js => devtools/server/actor-registry.js
extra : moz-landing-system : lando
This commit is contained in:
yulia 2018-09-25 08:05:29 +00:00
Родитель 08c887b837
Коммит e857119004
31 изменённых файлов: 493 добавлений и 463 удалений

Просмотреть файл

@ -13,7 +13,7 @@ add_task(async function() {
DebuggerServer.init();
DebuggerServer.registerAllActors();
DebuggerServer.registerModule(ACTORS_URL, {
ActorRegistry.registerModule(ACTORS_URL, {
prefix: "testOne",
constructor: "TestActor1",
type: { global: true },

Просмотреть файл

@ -16,6 +16,7 @@ Services.prefs.setBoolPref("devtools.debugger.log", false);
var { BrowserToolboxProcess } = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
var { DebuggerServer } = require("devtools/server/main");
var { ActorRegistry } = require("devtools/server/actor-registry");
var { DebuggerClient } = require("devtools/shared/client/debugger-client");
var ObjectClient = require("devtools/shared/client/object-client");
var { AddonManager } = ChromeUtils.import("resource://gre/modules/AddonManager.jsm", {});

Просмотреть файл

@ -734,7 +734,7 @@ async function enableWebComponents() {
/*
* Register an actor in the content process of the current tab.
*
* Calling DebuggerServer.registerModule only registers the actor in the current process.
* Calling ActorRegistry.registerModule only registers the actor in the current process.
* As all test scripts are ran in the parent process, it is only registered here.
* This function helps register them in the content process used for the current tab.
*
@ -755,7 +755,7 @@ async function registerActorInContentProcess(url, options) {
return ContentTask.spawn(gBrowser.selectedBrowser, { url, options }, args => {
// eslint-disable-next-line no-shadow
const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
const { DebuggerServer } = require("devtools/server/main");
DebuggerServer.registerModule(args.url, args.options);
const { ActorRegistry } = require("devtools/server/actor-registry");
ActorRegistry.registerModule(args.url, args.options);
});
}

Просмотреть файл

@ -8,12 +8,12 @@ Target-scoped actors target a document, this could be a tab in Firefox or a remo
Global actors however are for the rest, for things not related to any particular document but instead for things global to the whole Firefox/Chrome/Safari instance the toolbox is connected to (e.g. the preference actor).
## The DebuggerServer.registerModule function
## The ActorRegistry.registerModule function
To register a target-scoped actor:
```
DebuggerServer.registerModule("devtools/server/actors/webconsole", {
ActorRegistry.registerModule("devtools/server/actors/webconsole", {
prefix: "console",
constructor: "WebConsoleActor",
type: { target: true }
@ -23,17 +23,17 @@ DebuggerServer.registerModule("devtools/server/actors/webconsole", {
To register a global actor:
```
DebuggerServer.registerModule("devtools/server/actors/addon/addons", {
ActorRegistry.registerModule("devtools/server/actors/addon/addons", {
prefix: "addons",
constructor: "AddonsActor",
type: { global: true }
});
```
If you are adding a new built-in actor, you should be registering it using `DebuggerServer.registerModule` in `_addBrowserActors` or `_addTargetScopedActors` in `/devtools/server/main.js`.
If you are adding a new built-in actor, you should be registering it using `ActorRegistry.registerModule` in `addBrowserActors` or `addTargetScopedActors` in `/devtools/server/actor-registry.js`.
## A note about lazy registration
The `DebuggerServer` loads and creates all of the actors lazily to keep the initial memory usage down (which is extremely important on lower end devices).
The `ActorRegistry` loads and creates all of the actors lazily to keep the initial memory usage down (which is extremely important on lower end devices).
It becomes especially important when debugging pages with e10s when there are more than one process, because that's when we need to spawn a `DebuggerServer` per process (it may not be immediately obvious that the server in the main process is mostly only here for piping messages to the actors in the child process).

Просмотреть файл

@ -51,7 +51,7 @@ The actor implementation would go somewhere like
// You also need to export the actor class in your module for discovery.
exports.HelloActor = HelloActor;
To activate your actor, register it in the `_addBrowserActors` method in `server/main.js`.
To activate your actor, register it in the `addBrowserActors` method in `server/actor-registry.js`.
The registration code would look something like this:
this.registerModule("devtools/server/actors/hello-world", {

Просмотреть файл

@ -0,0 +1,412 @@
/* 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 Services = require("Services");
var { Ci } = require("chrome");
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 debugger 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 ActorPool.addActor)
* - 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 debugger 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/actor-registry", {
prefix: "actorRegistry",
constructor: "ActorRegistryActor",
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 }
});
},
/**
* 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/canvas", {
prefix: "canvas",
constructor: "CanvasActor",
type: { target: true }
});
this.registerModule("devtools/server/actors/webgl", {
prefix: "webgl",
constructor: "WebGLActor",
type: { target: true }
});
this.registerModule("devtools/server/actors/webaudio", {
prefix: "webaudio",
constructor: "WebAudioActor",
type: { target: true }
});
this.registerModule("devtools/server/actors/stylesheets", {
prefix: "styleSheets",
constructor: "StyleSheetsActor",
type: { target: true }
});
this.registerModule("devtools/server/actors/storage", {
prefix: "storage",
constructor: "StorageActor",
type: { target: true }
});
this.registerModule("devtools/server/actors/memory", {
prefix: "memory",
constructor: "MemoryActor",
type: { target: true }
});
this.registerModule("devtools/server/actors/framerate", {
prefix: "framerate",
constructor: "FramerateActor",
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/csscoverage", {
prefix: "cssUsage",
constructor: "CSSUsageActor",
type: { target: true }
});
this.registerModule("devtools/server/actors/timeline", {
prefix: "timeline",
constructor: "TimelineActor",
type: { target: true }
});
if ("nsIProfiler" in Ci &&
!Services.prefs.getBoolPref("devtools.performance.new-panel-enabled", false)) {
this.registerModule("devtools/server/actors/performance", {
prefix: "performance",
constructor: "PerformanceActor",
type: { target: true }
});
}
this.registerModule("devtools/server/actors/animation", {
prefix: "animations",
constructor: "AnimationsActor",
type: { target: true }
});
this.registerModule("devtools/server/actors/promises", {
prefix: "promises",
constructor: "PromisesActor",
type: { target: true }
});
this.registerModule("devtools/server/actors/emulation", {
prefix: "emulation",
constructor: "EmulationActor",
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", {
prefix: "accessibility",
constructor: "AccessibilityActor",
type: { target: true }
});
this.registerModule("devtools/server/actors/screenshot", {
prefix: "screenshot",
constructor: "ScreenshotActor",
type: { target: true }
});
},
/**
* Registers handlers for new target-scoped request types defined dynamically.
*
* Note that the name or actorPrefix 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)) {
// DebuggerServerConnection 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 or actorPrefix 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)) {
// DebuggerServerConnection 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;

Просмотреть файл

@ -42,7 +42,7 @@ loader.lazyRequireGetter(this, "ChromeWindowTargetActor",
*
* - globalActorFactories: an object |A| describing further actors to
* attach to the 'listTabs' reply. This is the type accumulated by
* DebuggerServer.addGlobalActor. For each own property |P| of |A|,
* ActorRegistry.addGlobalActor. For each own property |P| of |A|,
* the root actor adds a property named |P| to the 'listTabs'
* reply whose value is the name of an actor constructed by
* |A[P]|.
@ -576,8 +576,8 @@ RootActor.prototype = {
},
/**
* Remove the extra actor (added by DebuggerServer.addGlobalActor or
* DebuggerServer.addTargetScopedActor) name |name|.
* Remove the extra actor (added by ActorRegistry.addGlobalActor or
* ActorRegistry.addTargetScopedActor) name |name|.
*/
removeActorByName: function(name) {
if (name in this._extraActors) {

Просмотреть файл

@ -23,7 +23,7 @@
var { Ci, Cu, Cr, Cc } = require("chrome");
var Services = require("Services");
const ChromeUtils = require("ChromeUtils");
var { DebuggerServer } = require("devtools/server/main");
var { ActorRegistry } = require("devtools/server/actor-registry");
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
var { assert } = DevToolsUtils;
var { TabSources } = require("devtools/server/actors/utils/TabSources");
@ -103,7 +103,7 @@ const browsingContextTargetPrototype = {
* is a `docShell`.
*
* The main goal of this class is to expose the target-scoped actors being registered
* via `DebuggerServer.registerModule` and manage their lifetimes. In addition, this
* via `ActorRegistry.registerModule` and manage their lifetimes. In addition, this
* class also tracks the lifetime of the targeted browsing context.
*
* ### Main requests:
@ -481,13 +481,13 @@ const browsingContextTargetPrototype = {
// Walk over target-scoped actor factories and make sure they are all
// instantiated and added into the ActorPool.
const addedActors = createExtraActors(
DebuggerServer.targetScopedActorFactories,
const actors = createExtraActors(
ActorRegistry.targetScopedActorFactories,
this._targetScopedActorPool,
this
);
Object.assign(response, addedActors);
Object.assign(response, actors);
return response;
},

Просмотреть файл

@ -7,6 +7,7 @@
const { Cu, CC } = require("chrome");
const { DebuggerServer } = require("devtools/server/main");
const { ActorRegistry } = require("devtools/server/actor-registry");
/**
* Support for actor registration. Main used by ActorRegistryActor
@ -38,15 +39,15 @@ exports.registerActorInCurrentProcess = function(sourceText, fileName, options)
const { prefix, constructor, type } = options;
if (type.global && !DebuggerServer.globalActorFactories.hasOwnProperty(prefix)) {
DebuggerServer.addGlobalActor({
if (type.global && !ActorRegistry.globalActorFactories.hasOwnProperty(prefix)) {
ActorRegistry.addGlobalActor({
constructorName: constructor,
constructorFun: sandbox[constructor]
}, prefix);
}
if (type.target && !DebuggerServer.targetScopedActorFactories.hasOwnProperty(prefix)) {
DebuggerServer.addTargetScopedActor({
if (type.target && !ActorRegistry.targetScopedActorFactories.hasOwnProperty(prefix)) {
ActorRegistry.addTargetScopedActor({
constructorName: constructor,
constructorFun: sandbox[constructor]
}, prefix);
@ -66,10 +67,10 @@ exports.unregisterActor = function(options) {
exports.unregisterActorInCurrentProcess = function(options) {
if (options.target) {
DebuggerServer.removeTargetScopedActor(options);
ActorRegistry.removeTargetScopedActor(options);
}
if (options.global) {
DebuggerServer.removeGlobalActor(options);
ActorRegistry.removeGlobalActor(options);
}
};

Просмотреть файл

@ -9,6 +9,7 @@
var { Ci } = require("chrome");
var Services = require("Services");
var { DebuggerServer } = require("devtools/server/main");
var { ActorRegistry } = require("devtools/server/actor-registry");
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
loader.lazyRequireGetter(this, "RootActor", "devtools/server/actors/root", true);
@ -48,7 +49,7 @@ exports.sendShutdownEvent = sendShutdownEvent;
/**
* Construct a root actor appropriate for use in a server running in a
* browser. The returned root actor:
* - respects the factories registered with DebuggerServer.addGlobalActor,
* - respects the factories registered with ActorRegistry.addGlobalActor,
* - uses a BrowserTabList to supply target actors for tabs,
* - sends all navigator:browser window documents a Debugger:Shutdown event
* when it exits.
@ -64,7 +65,7 @@ exports.createRootActor = function createRootActor(connection) {
serviceWorkerRegistrationList:
new ServiceWorkerRegistrationActorList(connection),
processList: new ProcessActorList(),
globalActorFactories: DebuggerServer.globalActorFactories,
globalActorFactories: ActorRegistry.globalActorFactories,
onShutdown: sendShutdownEvent
});
};

Просмотреть файл

@ -11,6 +11,7 @@
var { Ci, Cc } = require("chrome");
var Services = require("Services");
var { ActorPool } = require("devtools/server/actors/common");
var { ActorRegistry } = require("devtools/server/actor-registry");
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
var { dumpn } = DevToolsUtils;
@ -33,8 +34,6 @@ const CONTENT_PROCESS_SERVER_STARTUP_SCRIPT =
loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
var gRegisteredModules = Object.create(null);
/**
* Public API
*/
@ -82,6 +81,7 @@ var DebuggerServer = {
}
this._connections = {};
ActorRegistry.init(this._connections);
this._nextConnID = 0;
this._initialized = true;
@ -111,14 +111,9 @@ var DebuggerServer = {
this._connections[connID].close();
}
for (const id of Object.getOwnPropertyNames(gRegisteredModules)) {
this.unregisterModule(id);
}
gRegisteredModules = Object.create(null);
ActorRegistry.destroy();
this.closeAllListeners();
this.globalActorFactories = {};
this.targetScopedActorFactories = {};
this._initialized = false;
dumpn("Debugger server is shut down.");
@ -154,7 +149,7 @@ var DebuggerServer = {
*/
registerActors({ root, browser, target }) {
if (browser) {
this._addBrowserActors();
ActorRegistry.addBrowserActors();
}
if (root) {
@ -163,7 +158,7 @@ var DebuggerServer = {
}
if (target) {
this._addTargetScopedActors();
ActorRegistry.addTargetScopedActors();
}
},
@ -174,254 +169,6 @@ var DebuggerServer = {
this.registerActors({ root: true, browser: true, target: true });
},
/**
* Register a CommonJS module with the debugger 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 ActorPool.addActor)
* - 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("DebuggerServer.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);
}
},
/**
* Returns true if a module id has been registered.
*/
isModuleRegistered(id) {
return (id in gRegisteredModules);
},
/**
* Unregister a previously-loaded CommonJS module from the debugger 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/actor-registry", {
prefix: "actorRegistry",
constructor: "ActorRegistryActor",
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 }
});
},
/**
* 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/canvas", {
prefix: "canvas",
constructor: "CanvasActor",
type: { target: true }
});
this.registerModule("devtools/server/actors/webgl", {
prefix: "webgl",
constructor: "WebGLActor",
type: { target: true }
});
this.registerModule("devtools/server/actors/webaudio", {
prefix: "webaudio",
constructor: "WebAudioActor",
type: { target: true }
});
this.registerModule("devtools/server/actors/stylesheets", {
prefix: "styleSheets",
constructor: "StyleSheetsActor",
type: { target: true }
});
this.registerModule("devtools/server/actors/storage", {
prefix: "storage",
constructor: "StorageActor",
type: { target: true }
});
this.registerModule("devtools/server/actors/memory", {
prefix: "memory",
constructor: "MemoryActor",
type: { target: true }
});
this.registerModule("devtools/server/actors/framerate", {
prefix: "framerate",
constructor: "FramerateActor",
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/csscoverage", {
prefix: "cssUsage",
constructor: "CSSUsageActor",
type: { target: true }
});
this.registerModule("devtools/server/actors/timeline", {
prefix: "timeline",
constructor: "TimelineActor",
type: { target: true }
});
if ("nsIProfiler" in Ci &&
!Services.prefs.getBoolPref("devtools.performance.new-panel-enabled", false)) {
this.registerModule("devtools/server/actors/performance", {
prefix: "performance",
constructor: "PerformanceActor",
type: { target: true }
});
}
this.registerModule("devtools/server/actors/animation", {
prefix: "animations",
constructor: "AnimationsActor",
type: { target: true }
});
this.registerModule("devtools/server/actors/promises", {
prefix: "promises",
constructor: "PromisesActor",
type: { target: true }
});
this.registerModule("devtools/server/actors/emulation", {
prefix: "emulation",
constructor: "EmulationActor",
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", {
prefix: "accessibility",
constructor: "AccessibilityActor",
type: { target: true }
});
this.registerModule("devtools/server/actors/screenshot", {
prefix: "screenshot",
constructor: "ScreenshotActor",
type: { target: true }
});
},
/**
* Passes a set of options to the AddonTargetActors for the given ID.
*
@ -1162,146 +909,6 @@ var DebuggerServer = {
this.createRootActor = actorFactory;
},
/**
* Registers handlers for new target-scoped request types defined dynamically.
*
* Note that the name or actorPrefix 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 (DebuggerServer.targetScopedActorFactories.hasOwnProperty(name)) {
throw Error(name + " already exists");
}
DebuggerServer.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 DebuggerServer.targetScopedActorFactories) {
const handler = DebuggerServer.targetScopedActorFactories[factoryName];
if ((handler.options.constructorName == actor.name) ||
(handler.options.id == actor.id)) {
name = factoryName;
break;
}
}
}
if (!name) {
return;
}
delete DebuggerServer.targetScopedActorFactories[name];
for (const connID of Object.getOwnPropertyNames(this._connections)) {
// DebuggerServerConnection 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 or actorPrefix 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 (DebuggerServer.globalActorFactories.hasOwnProperty(name)) {
throw Error(name + " already exists");
}
DebuggerServer.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 DebuggerServer.globalActorFactories) {
const handler = DebuggerServer.globalActorFactories[factoryName];
if ((handler.options.constructorName == actor.name) ||
(handler.options.id == actor.id)) {
name = factoryName;
break;
}
}
}
if (!name) {
return;
}
delete DebuggerServer.globalActorFactories[name];
for (const connID of Object.getOwnPropertyNames(this._connections)) {
// DebuggerServerConnection in child process don't have rootActor
if (this._connections[connID].rootActor) {
this._connections[connID].rootActor.removeActorByName(name);
}
}
},
/**
* Called when DevTools are unloaded to remove the contend process server startup script
* for the list of scripts loaded for each new content process. Will also remove message

Просмотреть файл

@ -20,6 +20,7 @@ MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']
XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
DevToolsModules(
'actor-registry.js',
'main.js',
)

Просмотреть файл

@ -16,7 +16,7 @@ async function test() {
DebuggerServer.init();
DebuggerServer.registerAllActors();
DebuggerServer.registerModule(ACTORS_URL, {
ActorRegistry.registerModule(ACTORS_URL, {
prefix: "error",
constructor: "ErrorActor",
type: { global: true },

Просмотреть файл

@ -12,6 +12,7 @@ Services.scriptloader.loadSubScript(
this);
const {DebuggerClient} = require("devtools/shared/client/debugger-client");
const { ActorRegistry } = require("devtools/server/actor-registry");
const {DebuggerServer} = require("devtools/server/main");
const PATH = "browser/devtools/server/tests/browser/";

Просмотреть файл

@ -47,6 +47,7 @@ function runTests() {
/* eslint-disable no-shadow */
const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
const { DebuggerServer } = require("devtools/server/main");
const { ActorRegistry } = require("devtools/server/actor-registry");
/* eslint-enable no-shadow */
DebuggerServer.init();
@ -67,7 +68,7 @@ function runTests() {
TestActor.prototype.requestTypes = {
"hello": TestActor.prototype.hello
};
DebuggerServer.addTargetScopedActor({
ActorRegistry.addTargetScopedActor({
constructorName: "TestActor",
constructorFun: TestActor,
}, "testActor");

Просмотреть файл

@ -31,6 +31,7 @@ Services.prefs.setBoolPref("devtools.debugger.log", true);
Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const { ActorRegistry } = require("devtools/server/actor-registry");
const { DebuggerServer } = require("devtools/server/main");
const { DebuggerServer: WorkerDebuggerServer } = worker.require("devtools/server/main");
const { DebuggerClient } = require("devtools/shared/client/debugger-client");
@ -76,7 +77,7 @@ function makeMemoryActorTest(testGeneratorFunction) {
return function run_test() {
do_test_pending();
startTestDebuggerServer(TEST_GLOBAL_NAME).then(client => {
DebuggerServer.registerModule("devtools/server/actors/heap-snapshot-file", {
ActorRegistry.registerModule("devtools/server/actors/heap-snapshot-file", {
prefix: "heapSnapshotFile",
constructor: "HeapSnapshotFileActor",
type: { global: true }
@ -114,7 +115,7 @@ function makeFullRuntimeMemoryActorTest(testGeneratorFunction) {
return function run_test() {
do_test_pending();
startTestDebuggerServer("test_MemoryActor").then(client => {
DebuggerServer.registerModule("devtools/server/actors/heap-snapshot-file", {
ActorRegistry.registerModule("devtools/server/actors/heap-snapshot-file", {
prefix: "heapSnapshotFile",
constructor: "HeapSnapshotFileActor",
type: { global: true }

Просмотреть файл

@ -16,12 +16,12 @@ function getActorInstance(connID, actorID) {
* regardless of the object's state.
*/
add_task(async function() {
DebuggerServer.registerModule("resource://test/pre_init_global_actors.js", {
ActorRegistry.registerModule("resource://test/pre_init_global_actors.js", {
prefix: "preInitGlobal",
constructor: "PreInitGlobalActor",
type: { global: true },
});
DebuggerServer.registerModule("resource://test/pre_init_target_scoped_actors.js", {
ActorRegistry.registerModule("resource://test/pre_init_target_scoped_actors.js", {
prefix: "preInitTargetScoped",
constructor: "PreInitTargetScopedActor",
type: { target: true },
@ -29,12 +29,12 @@ add_task(async function() {
const client = await startTestDebuggerServer("example tab");
DebuggerServer.registerModule("resource://test/post_init_global_actors.js", {
ActorRegistry.registerModule("resource://test/post_init_global_actors.js", {
prefix: "postInitGlobal",
constructor: "PostInitGlobalActor",
type: { global: true },
});
DebuggerServer.registerModule("resource://test/post_init_target_scoped_actors.js", {
ActorRegistry.registerModule("resource://test/post_init_target_scoped_actors.js", {
prefix: "postInitTargetScoped",
constructor: "PostInitTargetScopedActor",
type: { target: true },

Просмотреть файл

@ -27,7 +27,7 @@ TestActor.prototype.requestTypes = {
};
function run_test() {
DebuggerServer.addGlobalActor({
ActorRegistry.addGlobalActor({
constructorName: "TestActor",
constructorFun: TestActor,
}, "test");

Просмотреть файл

@ -51,7 +51,7 @@ TestClient.prototype = {
};
function run_test() {
DebuggerServer.addGlobalActor({
ActorRegistry.addGlobalActor({
constructorName: "TestActor",
constructorFun: TestActor,
}, "test");

Просмотреть файл

@ -14,7 +14,7 @@ function run_test() {
run_next_test();
}
// Bug 988237: Test the new lazy actor loading
// Bug 988237: Test the new lazy actor actor-register
function test_lazy_api() {
let isActorLoaded = false;
let isActorInstantiated = false;
@ -26,14 +26,14 @@ function test_lazy_api() {
}
}
Services.obs.addObserver(onActorEvent, "actor");
DebuggerServer.registerModule("xpcshell-test/registertestactors-lazy", {
ActorRegistry.registerModule("xpcshell-test/registertestactors-lazy", {
prefix: "lazy",
constructor: "LazyActor",
type: { global: true, target: true }
});
// The actor is immediatly registered, but not loaded
Assert.ok(DebuggerServer.targetScopedActorFactories.hasOwnProperty("lazyActor"));
Assert.ok(DebuggerServer.globalActorFactories.hasOwnProperty("lazyActor"));
Assert.ok(ActorRegistry.targetScopedActorFactories.hasOwnProperty("lazyActor"));
Assert.ok(ActorRegistry.globalActorFactories.hasOwnProperty("lazyActor"));
Assert.ok(!isActorLoaded);
Assert.ok(!isActorInstantiated);
@ -65,9 +65,9 @@ function test_lazy_api() {
}
function manual_remove() {
Assert.ok(DebuggerServer.globalActorFactories.hasOwnProperty("lazyActor"));
DebuggerServer.removeGlobalActor("lazyActor");
Assert.ok(!DebuggerServer.globalActorFactories.hasOwnProperty("lazyActor"));
Assert.ok(ActorRegistry.globalActorFactories.hasOwnProperty("lazyActor"));
ActorRegistry.removeGlobalActor("lazyActor");
Assert.ok(!ActorRegistry.globalActorFactories.hasOwnProperty("lazyActor"));
run_next_test();
}
@ -76,8 +76,8 @@ function cleanup() {
DebuggerServer.destroy();
// Check that all actors are unregistered on server destruction
Assert.ok(!DebuggerServer.targetScopedActorFactories.hasOwnProperty("lazyActor"));
Assert.ok(!DebuggerServer.globalActorFactories.hasOwnProperty("lazyActor"));
Assert.ok(!ActorRegistry.targetScopedActorFactories.hasOwnProperty("lazyActor"));
Assert.ok(!ActorRegistry.globalActorFactories.hasOwnProperty("lazyActor"));
run_next_test();
}

Просмотреть файл

@ -7,6 +7,7 @@ const { LazyPool, createExtraActors } = require("devtools/shared/protocol/lazy-p
const { RootActor } = require("devtools/server/actors/root");
const { ThreadActor } = require("devtools/server/actors/thread");
const { DebuggerServer } = require("devtools/server/main");
const { ActorRegistry } = require("devtools/server/actor-registry");
const { TabSources } = require("devtools/server/actors/utils/TabSources");
const makeDebugger = require("devtools/server/actors/utils/make-debugger");
@ -63,7 +64,7 @@ TestTabList.prototype = {
exports.createRootActor = function createRootActor(connection) {
const root = new RootActor(connection, {
tabList: new TestTabList(connection),
globalActorFactories: DebuggerServer.globalActorFactories,
globalActorFactories: ActorRegistry.globalActorFactories,
});
root.applicationType = "xpcshell-tests";
@ -112,7 +113,7 @@ TestTargetActor.prototype = {
// Walk over target-scoped actors and add them to a new LazyPool.
const actorPool = new LazyPool(this.conn);
const actors = createExtraActors(
DebuggerServer.targetScopedActorFactories,
ActorRegistry.targetScopedActorFactories,
actorPool,
this
);

Просмотреть файл

@ -51,7 +51,7 @@ exports.LazyPool = LazyPool;
* |pool|. _extraActors is treated as a cache for lazy actors
*
* The target actor uses this to instantiate actors that other
* parts of the browser have specified with DebuggerServer.addTargetScopedActor
* parts of the browser have specified with ActorRegistry.addTargetScopedActor
*
* @param factories
* An object whose own property names are the names of properties to add to

Просмотреть файл

@ -1,12 +1,12 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { LazyPool, createExtraActors } = require("devtools/shared/protocol/lazy-pool");
const { RootActor } = require("devtools/server/actors/root");
const { ThreadActor } = require("devtools/server/actors/thread");
const { DebuggerServer } = require("devtools/server/main");
const { ActorRegistry } = require("devtools/server/actor-registry");
const promise = require("promise");
var gTestGlobals = [];
@ -52,7 +52,7 @@ TestTabList.prototype = {
exports.createRootActor = function createRootActor(connection) {
const root = new RootActor(connection, {
tabList: new TestTabList(connection),
globalActorFactories: DebuggerServer.globalActorFactories
globalActorFactories: ActorRegistry.globalActorFactories
});
root.applicationType = "xpcshell-tests";
return root;
@ -85,7 +85,7 @@ TestTargetActor.prototype = {
// Walk over target-scoped actors and add them to a new LazyPool.
const actorPool = new LazyPool(this.conn);
const actors = createExtraActors(
DebuggerServer.targetScopedActorFactories,
ActorRegistry.targetScopedActorFactories,
actorPool,
this
);

Просмотреть файл

@ -25,6 +25,7 @@ const Services = require("Services");
// Enable remote debugging for the relevant tests.
Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
const { ActorRegistry } = require("devtools/server/actor-registry");
const { DebuggerServer } = require("devtools/server/main");
const { DebuggerClient } = require("devtools/shared/client/debugger-client");
@ -90,7 +91,7 @@ Services.console.registerListener(listener);
* Initialize the testing debugger server.
*/
function initTestDebuggerServer() {
DebuggerServer.registerModule("devtools/server/actors/thread", {
ActorRegistry.registerModule("devtools/server/actors/thread", {
prefix: "script",
constructor: "ScriptActor",
type: { global: true, target: true }

Просмотреть файл

@ -39,7 +39,7 @@ TestBulkActor.prototype.requestTypes = {
};
function add_test_bulk_actor() {
DebuggerServer.addGlobalActor({
ActorRegistry.addGlobalActor({
constructorName: "TestBulkActor",
constructorFun: TestBulkActor,
}, "testBulk");

Просмотреть файл

@ -93,7 +93,7 @@ TestBulkActor.prototype.requestTypes = {
};
function add_test_bulk_actor() {
DebuggerServer.addGlobalActor({
ActorRegistry.addGlobalActor({
constructorName: "TestBulkActor",
constructorFun: TestBulkActor,
}, "testBulk");

Просмотреть файл

@ -3,14 +3,14 @@
"use strict";
const { RootActor } = require("devtools/server/actors/root");
const { DebuggerServer } = require("devtools/server/main");
const { ActorRegistry } = require("devtools/server/actor-registry");
/**
* Root actor that doesn't have the bulk trait.
*/
exports.createRootActor = function createRootActor(connection) {
const root = new RootActor(connection, {
globalActorFactories: DebuggerServer.globalActorFactories
globalActorFactories: ActorRegistry.globalActorFactories
});
root.applicationType = "xpcshell-tests";
root.traits = {

Просмотреть файл

@ -6,6 +6,8 @@ const { LazyPool, createExtraActors } = require("devtools/shared/protocol/lazy-p
const { RootActor } = require("devtools/server/actors/root");
const { ThreadActor } = require("devtools/server/actors/thread");
const { DebuggerServer } = require("devtools/server/main");
const { ActorRegistry } = require("devtools/server/actor-registry");
const promise = require("promise");
var gTestGlobals = [];
DebuggerServer.addTestGlobal = function(global) {
@ -50,7 +52,7 @@ TestTabList.prototype = {
exports.createRootActor = function createRootActor(connection) {
const root = new RootActor(connection, {
tabList: new TestTabList(connection),
globalActorFactories: DebuggerServer.globalActorFactories
globalActorFactories: ActorRegistry.globalActorFactories
});
root.applicationType = "xpcshell-tests";
return root;
@ -83,13 +85,12 @@ TestTargetActor.prototype = {
// Walk over target-scoped actors and add them to a new LazyPool.
const actorPool = new LazyPool(this.conn);
const actors = createExtraActors(
DebuggerServer.targetScopedActorFactories,
ActorRegistry.targetScopedActorFactories,
actorPool,
this
);
if (!actorPool.isEmpty()) {
this._targetActorPool = actorPool;
this.conn.addActorPool(this._targetActorPool);
}
return { ...response, ...actors };

Просмотреть файл

@ -11,14 +11,14 @@
*/
const { RootActor } = require("devtools/server/actors/root");
const { DebuggerServer } = require("devtools/server/main");
const { ActorRegistry } = require("devtools/server/actor-registry");
const { BrowserTabList, BrowserAddonList, sendShutdownEvent } =
require("devtools/server/actors/webbrowser");
/**
* Construct a root actor appropriate for use in a server running in a
* browser on Android. The returned root actor:
* - respects the factories registered with DebuggerServer.addGlobalActor,
* - respects the factories registered with ActorRegistry.addGlobalActor,
* - uses a MobileTabList to supply tab actors,
* - sends all navigator:browser window documents a Debugger:Shutdown event
* when it exits.
@ -30,7 +30,7 @@ exports.createRootActor = function createRootActor(aConnection) {
let parameters = {
tabList: new MobileTabList(aConnection),
addonList: new BrowserAddonList(aConnection),
globalActorFactories: DebuggerServer.globalActorFactories,
globalActorFactories: ActorRegistry.globalActorFactories,
onShutdown: sendShutdownEvent
};
return new RootActor(aConnection, parameters);

Просмотреть файл

@ -34,8 +34,8 @@ module.exports = async function() {
`function () {
const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
const { DebuggerServer } = require("devtools/server/main");
DebuggerServer.registerModule("chrome://damp/content/tests/server/actor.js", {
const { ActorRegistry } = require("devtools/server/actor-registry");
ActorRegistry.registerModule("chrome://damp/content/tests/server/actor.js", {
prefix: "dampTest",
constructor: "DampTestActor",
type: { target: true }

Просмотреть файл

@ -10,6 +10,7 @@ const { DebuggerServer } = require("devtools/server/main");
const { RootActor } = require("devtools/server/actors/root");
const { BrowserTabList } = require("devtools/server/actors/webbrowser");
const Services = require("Services");
const { ActorRegistry } = require("devtools/server/actor-registry");
/**
* xpcshell-test (XPCST) specific actors.
@ -23,7 +24,7 @@ const Services = require("Services");
function createRootActor(connection) {
let parameters = {
tabList: new XPCSTTabList(connection),
globalActorFactories: DebuggerServer.globalActorFactories,
globalActorFactories: ActorRegistry.globalActorFactories,
onShutdown() {
// If the user never switches to the "debugger" tab we might get a
// shutdown before we've attached.