123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537 |
- /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
- /* 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);
- });
- /* Root actor for the remote debugging protocol. */
- /**
- * Create a remote debugging protocol root actor.
- *
- * @param aConnection
- * The DebuggerServerConnection whose root actor we are constructing.
- *
- * @param aParameters
- * The properties of |aParameters| provide backing objects for the root
- * actor's requests; if a given property is omitted from |aParameters|, 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 disconnected.
- *
- * 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 disconnected). 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(aConnection, aParameters) {
- this.conn = aConnection;
- this._parameters = aParameters;
- 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;
- }
- 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,
- 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 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 Gecko 40, indicating that the backend isn't stupid about
- // sending resumption packets on tab navigation.
- noNeedToFakeResumptionOnNavigation: 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
- // if allowChromeProcess is true, you can fetch a ChromeActor instance
- // to debug chrome and any non-content ressource via getProcess request
- // if allocChromeProcess is defined, but not true, it means that root actor
- // no longer expose tab actors, but also that getProcess forbids
- // exposing actors 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
- },
- /**
- * 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,
- };
- },
- /**
- * Disconnects the actor from the browser window.
- */
- disconnect: 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 (typeof this._parameters.onShutdown === "function") {
- this._parameters.onShutdown();
- }
- this._extraActors = null;
- this.conn = null;
- this._tabActorPool = null;
- this._globalActorPool = null;
- this._parameters = null;
- this._chromeActor = null;
- },
- /* The 'listTabs' request and the 'tabListChanged' notification. */
- /**
- * Handles the listTabs request. The actors will survive until at least
- * the next listTabs request.
- */
- onListTabs: function () {
- let tabList = this._parameters.tabList;
- if (!tabList) {
- return { from: this.actorID, error: "noTabs",
- message: "This root actor has no browser tabs." };
- }
- /*
- * 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;
- return tabList.getList().then((tabActors) => {
- for (let tabActor of tabActors) {
- if (tabActor.selected) {
- selected = tabActorList.length;
- }
- tabActor.parentID = this.actorID;
- newActorPool.addActor(tabActor);
- tabActorList.push(tabActor);
- }
- /* DebuggerServer.addGlobalActor support: create actors. */
- if (!this._globalActorPool) {
- this._globalActorPool = new ActorPool(this.conn);
- this.conn.addActorPool(this._globalActorPool);
- }
- this._createExtraActors(this._parameters.globalActorFactories, this._globalActorPool);
- /*
- * 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);
- let reply = {
- "from": this.actorID,
- "selected": selected || 0,
- "tabs": tabActorList.map(actor => actor.form())
- };
- /* If a root window is accessible, include its URL. */
- if (this.url) {
- reply.url = this.url;
- }
- /* DebuggerServer.addGlobalActor support: name actors in 'listTabs' reply. */
- this._appendExtraActors(reply);
- /*
- * Now that we're actually going to report the contents of tabList to
- * the client, we're responsible for letting the client know if it
- * changes.
- */
- tabList.onListChanged = this._onTabListChanged;
- return reply;
- });
- },
- onGetTab: 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);
- }
- return tabList.getTab(options)
- .then(tabActor => {
- tabActor.parentID = this.actorID;
- this._tabActorPool.addActor(tabActor);
- return { tab: tabActor.form() };
- }, error => {
- if (error.error) {
- // Pipe expected errors as-is to the client
- return error;
- } else {
- return { error: "noTab",
- message: "Unexpected error while calling getTab(): " + error };
- }
- });
- },
- 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." };
- }
- 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);
- addonList.onListChanged = this._onAddonListChanged;
- 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." };
- }
- 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);
- workerList.onListChanged = this._onWorkerListChanged;
- 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." };
- }
- 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);
- registrationList.onListChanged = this._onServiceWorkerRegistrationListChanged;
- 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 (aRequest) {
- if (!DebuggerServer.allowChromeProcess) {
- return { error: "forbidden",
- message: "You are not allowed to debug chrome." };
- }
- if (("id" in aRequest) && typeof (aRequest.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 aRequest)) || aRequest.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() };
- } else {
- let mm = ppmm.getChildAt(aRequest.id);
- if (!mm) {
- return { error: "noProcess",
- message: "There is no process with id '" + aRequest.id + "'." };
- }
- return DebuggerServer.connectToContent(this.conn, mm)
- .then(form => ({ form }));
- }
- },
- /* This is not in the spec, but it's used by tests. */
- onEcho: function (aRequest) {
- /*
- * Request packets are frozen. Copy aRequest, so that
- * DebuggerServerConnection.onPacket can attach a 'from' property.
- */
- return Cu.cloneInto(aRequest, {});
- },
- 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 |aName|.
- */
- removeActorByName: function (aName) {
- if (aName in this._extraActors) {
- const actor = this._extraActors[aName];
- 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(aName);
- });
- }
- delete this._extraActors[aName];
- }
- }
- };
- RootActor.prototype.requestTypes = {
- "listTabs": RootActor.prototype.onListTabs,
- "getTab": RootActor.prototype.onGetTab,
- "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;
|