mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-27 20:25:44 +00:00
23ff4cc75d
MozReview-Commit-ID: 2RVNt140Zte
622 lines
22 KiB
JavaScript
622 lines
22 KiB
JavaScript
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
|
/* 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";
|
|
|
|
const { Cc, Ci, Cu } = require("chrome");
|
|
const Services = require("Services");
|
|
const { ActorPool, appendExtraActors, createExtraActors } = require("devtools/server/actors/common");
|
|
const { DebuggerServer } = require("devtools/server/main");
|
|
|
|
loader.lazyGetter(this, "ppmm", () => {
|
|
return Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(
|
|
Ci.nsIMessageBroadcaster);
|
|
});
|
|
loader.lazyRequireGetter(this, "WindowActor",
|
|
"devtools/server/actors/window", true);
|
|
|
|
/* Root actor for the remote debugging protocol. */
|
|
|
|
/**
|
|
* Create a remote debugging protocol root actor.
|
|
*
|
|
* @param connection
|
|
* The DebuggerServerConnection whose root actor we are constructing.
|
|
*
|
|
* @param parameters
|
|
* The properties of |parameters| provide backing objects for the root
|
|
* actor's requests; if a given property is omitted from |parameters|, the
|
|
* root actor won't implement the corresponding requests or notifications.
|
|
* Supported properties:
|
|
*
|
|
* - tabList: a live list (see below) of tab actors. If present, the
|
|
* new root actor supports the 'listTabs' request, providing the live
|
|
* list's elements as its tab actors, and sending 'tabListChanged'
|
|
* notifications when the live list's contents change. One actor in
|
|
* this list must have a true '.selected' property.
|
|
*
|
|
* - addonList: a live list (see below) of addon actors. If present, the
|
|
* new root actor supports the 'listAddons' request, providing the live
|
|
* list's elements as its addon actors, and sending 'addonListchanged'
|
|
* notifications when the live list's contents change.
|
|
*
|
|
* - 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|,
|
|
* the root actor adds a property named |P| to the 'listTabs'
|
|
* reply whose value is the name of an actor constructed by
|
|
* |A[P]|.
|
|
*
|
|
* - onShutdown: a function to call when the root actor is destroyed.
|
|
*
|
|
* Instance properties:
|
|
*
|
|
* - applicationType: the string the root actor will include as the
|
|
* "applicationType" property in the greeting packet. By default, this
|
|
* is "browser".
|
|
*
|
|
* Live lists:
|
|
*
|
|
* A "live list", as used for the |tabList|, is an object that presents a
|
|
* list of actors, and also notifies its clients of changes to the list. A
|
|
* live list's interface is two properties:
|
|
*
|
|
* - getList: a method that returns a promise to the contents of the list.
|
|
*
|
|
* - onListChanged: a handler called, with no arguments, when the set of
|
|
* values the iterator would produce has changed since the last
|
|
* time 'iterator' was called. This may only be set to null or a
|
|
* callable value (one for which the typeof operator returns
|
|
* 'function'). (Note that the live list will not call the
|
|
* onListChanged handler until the list has been iterated over
|
|
* once; if nobody's seen the list in the first place, nobody
|
|
* should care if its contents have changed!)
|
|
*
|
|
* When the list changes, the list implementation should ensure that any
|
|
* actors yielded in previous iterations whose referents (tabs) still exist
|
|
* get yielded again in subsequent iterations. If the underlying referent
|
|
* is the same, the same actor should be presented for it.
|
|
*
|
|
* The root actor registers an 'onListChanged' handler on the appropriate
|
|
* list when it may need to send the client 'tabListChanged' notifications,
|
|
* and is careful to remove the handler whenever it does not need to send
|
|
* such notifications (including when it is destroyed). This means that
|
|
* live list implementations can use the state of the handler property (set
|
|
* or null) to install and remove observers and event listeners.
|
|
*
|
|
* Note that, as the only way for the root actor to see the members of the
|
|
* live list is to begin an iteration over the list, the live list need not
|
|
* actually produce any actors until they are reached in the course of
|
|
* iteration: alliterative lazy live lists.
|
|
*/
|
|
function RootActor(connection, parameters) {
|
|
this.conn = connection;
|
|
this._parameters = parameters;
|
|
this._onTabListChanged = this.onTabListChanged.bind(this);
|
|
this._onAddonListChanged = this.onAddonListChanged.bind(this);
|
|
this._onWorkerListChanged = this.onWorkerListChanged.bind(this);
|
|
this._onServiceWorkerRegistrationListChanged =
|
|
this.onServiceWorkerRegistrationListChanged.bind(this);
|
|
this._onProcessListChanged = this.onProcessListChanged.bind(this);
|
|
this._extraActors = {};
|
|
|
|
this._globalActorPool = new ActorPool(this.conn);
|
|
this.conn.addActorPool(this._globalActorPool);
|
|
|
|
this._chromeActor = null;
|
|
this._processActors = new Map();
|
|
}
|
|
|
|
RootActor.prototype = {
|
|
constructor: RootActor,
|
|
applicationType: "browser",
|
|
|
|
traits: {
|
|
sources: true,
|
|
// Whether the inspector actor allows modifying outer HTML.
|
|
editOuterHTML: true,
|
|
// Whether the inspector actor allows modifying innerHTML and inserting
|
|
// adjacent HTML.
|
|
pasteHTML: true,
|
|
// Whether the server-side highlighter actor exists and can be used to
|
|
// remotely highlight nodes (see server/actors/highlighters.js)
|
|
highlightable: true,
|
|
// Which custom highlighter does the server-side highlighter actor supports?
|
|
// (see server/actors/highlighters.js)
|
|
customHighlighters: true,
|
|
// Whether the inspector actor implements the getImageDataFromURL
|
|
// method that returns data-uris for image URLs. This is used for image
|
|
// tooltips for instance
|
|
urlToImageDataResolver: true,
|
|
networkMonitor: true,
|
|
// Whether the storage inspector actor to inspect cookies, etc.
|
|
storageInspector: true,
|
|
// Whether storage inspector is read only
|
|
storageInspectorReadOnly: true,
|
|
// Whether conditional breakpoints are supported
|
|
conditionalBreakpoints: true,
|
|
// Whether the server supports full source actors (breakpoints on
|
|
// eval scripts, etc)
|
|
debuggerSourceActors: true,
|
|
// Whether the server can return wasm binary source
|
|
wasmBinarySource: true,
|
|
bulk: true,
|
|
// Whether the style rule actor implements the modifySelector method
|
|
// that modifies the rule's selector
|
|
selectorEditable: true,
|
|
// Whether the page style actor implements the addNewRule method that
|
|
// adds new rules to the page
|
|
addNewRule: true,
|
|
// Whether the dom node actor implements the getUniqueSelector method
|
|
getUniqueSelector: true,
|
|
// Whether the dom node actor implements the getCssPath method
|
|
getCssPath: true,
|
|
// Whether the dom node actor implements the getXPath method
|
|
getXPath: true,
|
|
// Whether the director scripts are supported
|
|
directorScripts: true,
|
|
// Whether the debugger server supports
|
|
// blackboxing/pretty-printing (not supported in Fever Dream yet)
|
|
noBlackBoxing: false,
|
|
noPrettyPrinting: false,
|
|
// Whether the page style actor implements the getUsedFontFaces method
|
|
// that returns the font faces used on a node
|
|
getUsedFontFaces: true,
|
|
// Trait added in Gecko 38, indicating that all features necessary for
|
|
// grabbing allocations from the MemoryActor are available for the performance tool
|
|
memoryActorAllocations: true,
|
|
// Added in Firefox 40. Indicates that the backend supports registering custom
|
|
// commands through the WebConsoleCommands API.
|
|
webConsoleCommands: true,
|
|
// Whether root actor exposes tab actors and access to any window.
|
|
// If allowChromeProcess is true, you can:
|
|
// * get a ChromeActor instance to debug chrome and any non-content
|
|
// resource via getProcess requests
|
|
// * get a WindowActor instance to debug windows which could be chrome,
|
|
// like browser windows via getWindow requests
|
|
// If allowChromeProcess is defined, but not true, it means that root actor
|
|
// no longer expose tab actors, but also that the above requests are
|
|
// forbidden for security reasons.
|
|
get allowChromeProcess() {
|
|
return DebuggerServer.allowChromeProcess;
|
|
},
|
|
// Whether or not `getProfile()` supports specifying a `startTime`
|
|
// and `endTime` to filter out samples. Fx40+
|
|
profilerDataFilterable: true,
|
|
// Whether or not the MemoryActor's heap snapshot abilities are
|
|
// fully equipped to handle heap snapshots for the memory tool. Fx44+
|
|
heapSnapshots: true,
|
|
// Whether or not the timeline actor can emit DOMContentLoaded and Load
|
|
// markers, currently in use by the network monitor. Fx45+
|
|
documentLoadingMarkers: true,
|
|
// Whether or not the webextension addon actor have to be connected
|
|
// to retrieve the extension child process tab actors.
|
|
webExtensionAddonConnect: true,
|
|
},
|
|
|
|
/**
|
|
* Return a 'hello' packet as specified by the Remote Debugging Protocol.
|
|
*/
|
|
sayHello: function() {
|
|
return {
|
|
from: this.actorID,
|
|
applicationType: this.applicationType,
|
|
/* This is not in the spec, but it's used by tests. */
|
|
testConnectionPrefix: this.conn.prefix,
|
|
traits: this.traits
|
|
};
|
|
},
|
|
|
|
forwardingCancelled: function(prefix) {
|
|
return {
|
|
from: this.actorID,
|
|
type: "forwardingCancelled",
|
|
prefix,
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Destroys the actor from the browser window.
|
|
*/
|
|
destroy: function() {
|
|
/* Tell the live lists we aren't watching any more. */
|
|
if (this._parameters.tabList) {
|
|
this._parameters.tabList.onListChanged = null;
|
|
}
|
|
if (this._parameters.addonList) {
|
|
this._parameters.addonList.onListChanged = null;
|
|
}
|
|
if (this._parameters.workerList) {
|
|
this._parameters.workerList.onListChanged = null;
|
|
}
|
|
if (this._parameters.serviceWorkerRegistrationList) {
|
|
this._parameters.serviceWorkerRegistrationList.onListChanged = null;
|
|
}
|
|
if (this._parameters.processList) {
|
|
this._parameters.processList.onListChanged = null;
|
|
}
|
|
if (typeof this._parameters.onShutdown === "function") {
|
|
this._parameters.onShutdown();
|
|
}
|
|
this._extraActors = null;
|
|
this.conn = null;
|
|
this._tabActorPool = null;
|
|
this._globalActorPool = null;
|
|
this._windowActorPool = null;
|
|
this._parameters = null;
|
|
this._chromeActor = null;
|
|
this._processActors.clear();
|
|
},
|
|
|
|
/**
|
|
* Gets the "root" form, which lists all the global actors that affect the entire
|
|
* browser. This can replace usages of `listTabs` that only wanted the global actors
|
|
* and didn't actually care about tabs.
|
|
*/
|
|
onGetRoot: function() {
|
|
let reply = {
|
|
from: this.actorID,
|
|
};
|
|
|
|
// Create global actors
|
|
if (!this._globalActorPool) {
|
|
this._globalActorPool = new ActorPool(this.conn);
|
|
this.conn.addActorPool(this._globalActorPool);
|
|
}
|
|
this._createExtraActors(this._parameters.globalActorFactories, this._globalActorPool);
|
|
|
|
// List the global actors
|
|
this._appendExtraActors(reply);
|
|
|
|
return reply;
|
|
},
|
|
|
|
/* The 'listTabs' request and the 'tabListChanged' notification. */
|
|
|
|
/**
|
|
* Handles the listTabs request. The actors will survive until at least
|
|
* the next listTabs request.
|
|
*
|
|
* ⚠ WARNING ⚠ This can be a very expensive operation, especially if there are many
|
|
* open tabs. It will cause us to visit every tab, load a frame script, start a
|
|
* debugger server, and read some data. With lazy tab support (bug 906076), this
|
|
* would trigger any lazy tabs to be loaded, greatly increasing resource usage. Avoid
|
|
* this method whenever possible.
|
|
*/
|
|
onListTabs: async function(request) {
|
|
let tabList = this._parameters.tabList;
|
|
if (!tabList) {
|
|
return { from: this.actorID, error: "noTabs",
|
|
message: "This root actor has no browser tabs." };
|
|
}
|
|
|
|
// Now that a client has requested the list of tabs, we reattach the onListChanged
|
|
// listener in order to be notified if the list of tabs changes again in the future.
|
|
tabList.onListChanged = this._onTabListChanged;
|
|
|
|
// Walk the tab list, accumulating the array of tab actors for the reply, and moving
|
|
// all the actors to a new ActorPool. We'll replace the old tab actor pool with the
|
|
// one we build here, thus retiring any actors that didn't get listed again, and
|
|
// preparing any new actors to receive packets.
|
|
let newActorPool = new ActorPool(this.conn);
|
|
let tabActorList = [];
|
|
let selected;
|
|
|
|
let options = request.options || {};
|
|
let tabActors = await tabList.getList(options);
|
|
for (let tabActor of tabActors) {
|
|
if (tabActor.exited) {
|
|
// Tab actor may have exited while we were gathering the list.
|
|
continue;
|
|
}
|
|
if (tabActor.selected) {
|
|
selected = tabActorList.length;
|
|
}
|
|
tabActor.parentID = this.actorID;
|
|
newActorPool.addActor(tabActor);
|
|
tabActorList.push(tabActor);
|
|
}
|
|
|
|
// Start with the root reply, which includes the global actors for the whole browser.
|
|
let reply = this.onGetRoot();
|
|
|
|
// Drop the old actorID -> actor map. Actors that still mattered were added to the
|
|
// new map; others will go away.
|
|
if (this._tabActorPool) {
|
|
this.conn.removeActorPool(this._tabActorPool);
|
|
}
|
|
this._tabActorPool = newActorPool;
|
|
this.conn.addActorPool(this._tabActorPool);
|
|
|
|
// We'll extend the reply here to also mention all the tabs.
|
|
Object.assign(reply, {
|
|
selected: selected || 0,
|
|
tabs: tabActorList.map(actor => actor.form()),
|
|
});
|
|
|
|
return reply;
|
|
},
|
|
|
|
onGetTab: async function(options) {
|
|
let tabList = this._parameters.tabList;
|
|
if (!tabList) {
|
|
return { error: "noTabs",
|
|
message: "This root actor has no browser tabs." };
|
|
}
|
|
if (!this._tabActorPool) {
|
|
this._tabActorPool = new ActorPool(this.conn);
|
|
this.conn.addActorPool(this._tabActorPool);
|
|
}
|
|
|
|
let tabActor;
|
|
try {
|
|
tabActor = await tabList.getTab(options);
|
|
} catch (error) {
|
|
if (error.error) {
|
|
// Pipe expected errors as-is to the client
|
|
return error;
|
|
}
|
|
return {
|
|
error: "noTab",
|
|
message: "Unexpected error while calling getTab(): " + error
|
|
};
|
|
}
|
|
|
|
tabActor.parentID = this.actorID;
|
|
this._tabActorPool.addActor(tabActor);
|
|
|
|
return { tab: tabActor.form() };
|
|
},
|
|
|
|
onGetWindow: function({ outerWindowID }) {
|
|
if (!DebuggerServer.allowChromeProcess) {
|
|
return {
|
|
from: this.actorID,
|
|
error: "forbidden",
|
|
message: "You are not allowed to debug windows."
|
|
};
|
|
}
|
|
let window = Services.wm.getOuterWindowWithId(outerWindowID);
|
|
if (!window) {
|
|
return {
|
|
from: this.actorID,
|
|
error: "notFound",
|
|
message: `No window found with outerWindowID ${outerWindowID}`,
|
|
};
|
|
}
|
|
|
|
if (!this._windowActorPool) {
|
|
this._windowActorPool = new ActorPool(this.conn);
|
|
this.conn.addActorPool(this._windowActorPool);
|
|
}
|
|
|
|
let actor = new WindowActor(this.conn, window);
|
|
actor.parentID = this.actorID;
|
|
this._windowActorPool.addActor(actor);
|
|
|
|
return {
|
|
from: this.actorID,
|
|
window: actor.form(),
|
|
};
|
|
},
|
|
|
|
onTabListChanged: function() {
|
|
this.conn.send({ from: this.actorID, type: "tabListChanged" });
|
|
/* It's a one-shot notification; no need to watch any more. */
|
|
this._parameters.tabList.onListChanged = null;
|
|
},
|
|
|
|
onListAddons: function() {
|
|
let addonList = this._parameters.addonList;
|
|
if (!addonList) {
|
|
return { from: this.actorID, error: "noAddons",
|
|
message: "This root actor has no browser addons." };
|
|
}
|
|
|
|
// Reattach the onListChanged listener now that a client requested the list.
|
|
addonList.onListChanged = this._onAddonListChanged;
|
|
|
|
return addonList.getList().then((addonActors) => {
|
|
let addonActorPool = new ActorPool(this.conn);
|
|
for (let addonActor of addonActors) {
|
|
addonActorPool.addActor(addonActor);
|
|
}
|
|
|
|
if (this._addonActorPool) {
|
|
this.conn.removeActorPool(this._addonActorPool);
|
|
}
|
|
this._addonActorPool = addonActorPool;
|
|
this.conn.addActorPool(this._addonActorPool);
|
|
|
|
return {
|
|
"from": this.actorID,
|
|
"addons": addonActors.map(addonActor => addonActor.form())
|
|
};
|
|
});
|
|
},
|
|
|
|
onAddonListChanged: function() {
|
|
this.conn.send({ from: this.actorID, type: "addonListChanged" });
|
|
this._parameters.addonList.onListChanged = null;
|
|
},
|
|
|
|
onListWorkers: function() {
|
|
let workerList = this._parameters.workerList;
|
|
if (!workerList) {
|
|
return { from: this.actorID, error: "noWorkers",
|
|
message: "This root actor has no workers." };
|
|
}
|
|
|
|
// Reattach the onListChanged listener now that a client requested the list.
|
|
workerList.onListChanged = this._onWorkerListChanged;
|
|
|
|
return workerList.getList().then(actors => {
|
|
let pool = new ActorPool(this.conn);
|
|
for (let actor of actors) {
|
|
pool.addActor(actor);
|
|
}
|
|
|
|
this.conn.removeActorPool(this._workerActorPool);
|
|
this._workerActorPool = pool;
|
|
this.conn.addActorPool(this._workerActorPool);
|
|
|
|
return {
|
|
"from": this.actorID,
|
|
"workers": actors.map(actor => actor.form())
|
|
};
|
|
});
|
|
},
|
|
|
|
onWorkerListChanged: function() {
|
|
this.conn.send({ from: this.actorID, type: "workerListChanged" });
|
|
this._parameters.workerList.onListChanged = null;
|
|
},
|
|
|
|
onListServiceWorkerRegistrations: function() {
|
|
let registrationList = this._parameters.serviceWorkerRegistrationList;
|
|
if (!registrationList) {
|
|
return { from: this.actorID, error: "noServiceWorkerRegistrations",
|
|
message: "This root actor has no service worker registrations." };
|
|
}
|
|
|
|
// Reattach the onListChanged listener now that a client requested the list.
|
|
registrationList.onListChanged = this._onServiceWorkerRegistrationListChanged;
|
|
|
|
return registrationList.getList().then(actors => {
|
|
let pool = new ActorPool(this.conn);
|
|
for (let actor of actors) {
|
|
pool.addActor(actor);
|
|
}
|
|
|
|
this.conn.removeActorPool(this._serviceWorkerRegistrationActorPool);
|
|
this._serviceWorkerRegistrationActorPool = pool;
|
|
this.conn.addActorPool(this._serviceWorkerRegistrationActorPool);
|
|
|
|
return {
|
|
"from": this.actorID,
|
|
"registrations": actors.map(actor => actor.form())
|
|
};
|
|
});
|
|
},
|
|
|
|
onServiceWorkerRegistrationListChanged: function() {
|
|
this.conn.send({ from: this.actorID, type: "serviceWorkerRegistrationListChanged" });
|
|
this._parameters.serviceWorkerRegistrationList.onListChanged = null;
|
|
},
|
|
|
|
onListProcesses: function() {
|
|
let { processList } = this._parameters;
|
|
if (!processList) {
|
|
return { from: this.actorID, error: "noProcesses",
|
|
message: "This root actor has no processes." };
|
|
}
|
|
processList.onListChanged = this._onProcessListChanged;
|
|
return {
|
|
processes: processList.getList()
|
|
};
|
|
},
|
|
|
|
onProcessListChanged: function() {
|
|
this.conn.send({ from: this.actorID, type: "processListChanged" });
|
|
this._parameters.processList.onListChanged = null;
|
|
},
|
|
|
|
onGetProcess: function(request) {
|
|
if (!DebuggerServer.allowChromeProcess) {
|
|
return { error: "forbidden",
|
|
message: "You are not allowed to debug chrome." };
|
|
}
|
|
if (("id" in request) && typeof (request.id) != "number") {
|
|
return { error: "wrongParameter",
|
|
message: "getProcess requires a valid `id` attribute." };
|
|
}
|
|
// If the request doesn't contains id parameter or id is 0
|
|
// (id == 0, based on onListProcesses implementation)
|
|
if ((!("id" in request)) || request.id === 0) {
|
|
if (!this._chromeActor) {
|
|
// Create a ChromeActor for the parent process
|
|
let { ChromeActor } = require("devtools/server/actors/chrome");
|
|
this._chromeActor = new ChromeActor(this.conn);
|
|
this._globalActorPool.addActor(this._chromeActor);
|
|
}
|
|
|
|
return { form: this._chromeActor.form() };
|
|
}
|
|
|
|
let { id } = request;
|
|
let mm = ppmm.getChildAt(id);
|
|
if (!mm) {
|
|
return { error: "noProcess",
|
|
message: "There is no process with id '" + id + "'." };
|
|
}
|
|
let form = this._processActors.get(id);
|
|
if (form) {
|
|
return { form };
|
|
}
|
|
let onDestroy = () => {
|
|
this._processActors.delete(id);
|
|
};
|
|
return DebuggerServer.connectToContent(this.conn, mm, onDestroy).then(formResult => {
|
|
this._processActors.set(id, formResult);
|
|
return { form: formResult };
|
|
});
|
|
},
|
|
|
|
/* This is not in the spec, but it's used by tests. */
|
|
onEcho: function(request) {
|
|
/*
|
|
* Request packets are frozen. Copy request, so that
|
|
* DebuggerServerConnection.onPacket can attach a 'from' property.
|
|
*/
|
|
return Cu.cloneInto(request, {});
|
|
},
|
|
|
|
onProtocolDescription: function() {
|
|
return require("devtools/shared/protocol").dumpProtocolSpec();
|
|
},
|
|
|
|
/* Support for DebuggerServer.addGlobalActor. */
|
|
_createExtraActors: createExtraActors,
|
|
_appendExtraActors: appendExtraActors,
|
|
|
|
/**
|
|
* Remove the extra actor (added by DebuggerServer.addGlobalActor or
|
|
* DebuggerServer.addTabActor) name |name|.
|
|
*/
|
|
removeActorByName: function(name) {
|
|
if (name in this._extraActors) {
|
|
const actor = this._extraActors[name];
|
|
if (this._globalActorPool.has(actor)) {
|
|
this._globalActorPool.removeActor(actor);
|
|
}
|
|
if (this._tabActorPool) {
|
|
// Iterate over TabActor instances to also remove tab actors
|
|
// created during listTabs for each document.
|
|
this._tabActorPool.forEach(tab => {
|
|
tab.removeActorByName(name);
|
|
});
|
|
}
|
|
delete this._extraActors[name];
|
|
}
|
|
}
|
|
};
|
|
|
|
RootActor.prototype.requestTypes = {
|
|
getRoot: RootActor.prototype.onGetRoot,
|
|
listTabs: RootActor.prototype.onListTabs,
|
|
getTab: RootActor.prototype.onGetTab,
|
|
getWindow: RootActor.prototype.onGetWindow,
|
|
listAddons: RootActor.prototype.onListAddons,
|
|
listWorkers: RootActor.prototype.onListWorkers,
|
|
listServiceWorkerRegistrations: RootActor.prototype.onListServiceWorkerRegistrations,
|
|
listProcesses: RootActor.prototype.onListProcesses,
|
|
getProcess: RootActor.prototype.onGetProcess,
|
|
echo: RootActor.prototype.onEcho,
|
|
protocolDescription: RootActor.prototype.onProtocolDescription
|
|
};
|
|
|
|
exports.RootActor = RootActor;
|