Bug 1691681 - [devtools] Introduce "commands" in order to ease calling global commands throught the Watcher. r=nchevobbe,bomsy,jdescottes

Differential Revision: https://phabricator.services.mozilla.com/D97575
This commit is contained in:
Alexandre Poirot 2021-03-03 17:02:42 +00:00
parent 3c11cc6690
commit ac04c532a5
25 changed files with 208 additions and 47 deletions

View File

@ -50,9 +50,10 @@ const EVENTS = {
* render Accessibility Tree of the current debugger target and the sidebar that
* displays current relevant accessible details.
*/
function AccessibilityPanel(iframeWindow, toolbox) {
function AccessibilityPanel(iframeWindow, toolbox, commands) {
this.panelWin = iframeWindow;
this._toolbox = toolbox;
this._commands = commands;
this.onTabNavigated = this.onTabNavigated.bind(this);
this.onTargetUpdated = this.onTargetUpdated.bind(this);

View File

@ -16,10 +16,13 @@ class ApplicationPanel {
* The frame/window dedicated to this panel.
* @param {Toolbox} toolbox
* The toolbox instance responsible for this panel.
* @param {Object} commands
* The commands object with all interfaces defined from devtools/shared/commands/
*/
constructor(panelWin, toolbox) {
constructor(panelWin, toolbox, commands) {
this.panelWin = panelWin;
this.toolbox = toolbox;
this.commands = commands;
}
async open() {

View File

@ -37,10 +37,11 @@ async function getNodeFront(gripOrFront, toolbox) {
}
class DebuggerPanel {
constructor(iframeWindow, toolbox) {
constructor(iframeWindow, toolbox, commands) {
this.panelWin = iframeWindow;
this.panelWin.L10N = L10N;
this.toolbox = toolbox;
this.commands = commands;
}
async open() {

View File

@ -125,8 +125,8 @@ Tools.options = {
return true;
},
build: function(iframeWindow, toolbox) {
return new OptionsPanel(iframeWindow, toolbox);
build: function(iframeWindow, toolbox, commands) {
return new OptionsPanel(iframeWindow, toolbox, commands);
},
};
@ -162,8 +162,8 @@ Tools.inspector = {
return target.hasActor("inspector");
},
build: function(iframeWindow, toolbox) {
return new InspectorPanel(iframeWindow, toolbox);
build: function(iframeWindow, toolbox, commands) {
return new InspectorPanel(iframeWindow, toolbox, commands);
},
};
Tools.webConsole = {
@ -197,8 +197,8 @@ Tools.webConsole = {
isTargetSupported: function() {
return true;
},
build: function(iframeWindow, toolbox) {
return new WebConsolePanel(iframeWindow, toolbox);
build: function(iframeWindow, toolbox, commands) {
return new WebConsolePanel(iframeWindow, toolbox, commands);
},
};
@ -221,8 +221,8 @@ Tools.jsdebugger = {
isTargetSupported: function() {
return true;
},
build: function(iframeWindow, toolbox) {
return new DebuggerPanel(iframeWindow, toolbox);
build: function(iframeWindow, toolbox, commands) {
return new DebuggerPanel(iframeWindow, toolbox, commands);
},
};
@ -246,8 +246,8 @@ Tools.styleEditor = {
return target.hasActor("styleSheets");
},
build: function(iframeWindow, toolbox) {
return new StyleEditorPanel(iframeWindow, toolbox);
build: function(iframeWindow, toolbox, commands) {
return new StyleEditorPanel(iframeWindow, toolbox, commands);
},
};
@ -274,8 +274,8 @@ function switchPerformancePanel() {
) {
Tools.performance.url =
"chrome://devtools/content/performance-new/index.xhtml";
Tools.performance.build = function(frame, target) {
return new NewPerformancePanel(frame, target);
Tools.performance.build = function(frame, toolbox, commands) {
return new NewPerformancePanel(frame, toolbox, commands);
};
Tools.performance.isTargetSupported = function(target) {
// Only use the new performance panel on local tab toolboxes, as they are guaranteed
@ -287,8 +287,8 @@ function switchPerformancePanel() {
};
} else {
Tools.performance.url = "chrome://devtools/content/performance/index.xhtml";
Tools.performance.build = function(frame, target) {
return new PerformancePanel(frame, target);
Tools.performance.build = function(frame, toolbox, commands) {
return new PerformancePanel(frame, toolbox, commands);
};
Tools.performance.isTargetSupported = function(target) {
return target.hasActor("performance");
@ -327,8 +327,8 @@ Tools.memory = {
return !target.isAddon && !target.isWorkerTarget;
},
build: function(frame, target) {
return new MemoryPanel(frame, target);
build: function(frame, toolbox, commands) {
return new MemoryPanel(frame, toolbox, commands);
},
};
@ -354,8 +354,8 @@ Tools.netMonitor = {
return target.getTrait("networkMonitor") && !target.isWorkerTarget;
},
build: function(iframeWindow, toolbox) {
return new NetMonitorPanel(iframeWindow, toolbox);
build: function(iframeWindow, toolbox, commands) {
return new NetMonitorPanel(iframeWindow, toolbox, commands);
},
};
@ -381,8 +381,8 @@ Tools.storage = {
return target.hasActor("storage");
},
build: function(iframeWindow, toolbox) {
return new StoragePanel(iframeWindow, toolbox);
build: function(iframeWindow, toolbox, commands) {
return new StoragePanel(iframeWindow, toolbox, commands);
},
};
@ -408,8 +408,8 @@ Tools.dom = {
return true;
},
build: function(iframeWindow, toolbox) {
return new DomPanel(iframeWindow, toolbox);
build: function(iframeWindow, toolbox, commands) {
return new DomPanel(iframeWindow, toolbox, commands);
},
};
@ -435,8 +435,8 @@ Tools.accessibility = {
return target.hasActor("accessibility");
},
build(iframeWindow, toolbox) {
return new AccessibilityPanel(iframeWindow, toolbox);
build(iframeWindow, toolbox, commands) {
return new AccessibilityPanel(iframeWindow, toolbox, commands);
},
};
@ -455,8 +455,8 @@ Tools.application = {
return target.hasActor("manifest");
},
build: function(iframeWindow, toolbox) {
return new ApplicationPanel(iframeWindow, toolbox);
build: function(iframeWindow, toolbox, commands) {
return new ApplicationPanel(iframeWindow, toolbox, commands);
},
};

View File

@ -17,9 +17,10 @@ loader.lazyRequireGetter(
* This object represents DOM panel. It's responsibility is to
* render Document Object Model of the current debugger target.
*/
function DomPanel(iframeWindow, toolbox) {
function DomPanel(iframeWindow, toolbox, commands) {
this.panelWin = iframeWindow;
this._toolbox = toolbox;
this._commands = commands;
this.onTabNavigated = this.onTabNavigated.bind(this);
this.onTargetAvailable = this.onTargetAvailable.bind(this);

View File

@ -26,6 +26,15 @@ add_task(async function() {
);
ok(tabDescriptor, "Should have a descriptor actor for the tab");
const firstCommands = await tabDescriptor.getCommands();
ok(firstCommands, "Got commands");
const secondCommands = await tabDescriptor.getCommands();
is(
firstCommands,
secondCommands,
"Multiple calls to getCommands return the same commands object"
);
is(
target.descriptorFront,
tabDescriptor,

View File

@ -237,6 +237,7 @@ function Toolbox(
this.telemetry = new Telemetry();
this.descriptorFront = descriptorFront;
this.targetList = new TargetList(descriptorFront);
this.targetList.on(
"target-thread-wrong-order-on-resume",
@ -778,6 +779,12 @@ Toolbox.prototype = {
);
});
// This attribute is meant to be a public attribute on the Toolbox object
// It exposes commands modules listed in devtools/shared/commands/index.js
// which are an abstraction on top of RDP methods.
// See devtools/shared/commands/README.md
this.commands = await this.descriptorFront.getCommands();
// Optimization: fire up a few other things before waiting on
// the iframe being ready (makes startup faster)
await this.targetList.startListening();
@ -2479,7 +2486,7 @@ Toolbox.prototype = {
// be fired with the panel as an argument. However, in order to keep
// backward compatibility with existing extensions do a check
// for a promise return value.
let built = definition.build(iframe.contentWindow, this);
let built = definition.build(iframe.contentWindow, this, this.commands);
if (!(typeof built.then == "function")) {
const panel = built;

View File

@ -4,6 +4,8 @@
"use strict";
const { createCommandsDictionary } = require("devtools/shared/commands/index");
/**
* A Descriptor represents a debuggable context. It can be a browser tab, a tab on
* a remote device, like a tab on Firefox for Android. But it can also be an add-on,
@ -24,6 +26,13 @@ function DescriptorMixin(parentClass) {
get client() {
return this._client;
}
async getCommands() {
if (!this._commands) {
this._commands = createCommandsDictionary(this);
}
return this._commands;
}
}
return Descriptor;
}

View File

@ -142,10 +142,11 @@ const TELEMETRY_SCALAR_NODE_SELECTION_COUNT =
* Fired when the stylesheet source links have been updated (when switching
* to source-mapped files)
*/
function Inspector(toolbox) {
function Inspector(toolbox, commands) {
EventEmitter.decorate(this);
this._toolbox = toolbox;
this._commands = commands;
this.panelDoc = window.document;
this.panelWin = window;
this.panelWin.inspector = this;
@ -284,6 +285,10 @@ Inspector.prototype = {
return this._toolbox;
},
get commands() {
return this._commands;
},
/**
* Get the list of InspectorFront instances that correspond to all of the inspectable
* targets in remote frames nested within the document inspected here, as well as the

View File

@ -4,8 +4,8 @@
"use strict";
function InspectorPanel(iframeWindow, toolbox) {
this._inspector = new iframeWindow.Inspector(toolbox);
function InspectorPanel(iframeWindow, toolbox, commands) {
this._inspector = new iframeWindow.Inspector(toolbox, commands);
}
InspectorPanel.prototype = {
open() {

View File

@ -8,9 +8,10 @@ const EventEmitter = require("devtools/shared/event-emitter");
const { Cu } = require("chrome");
const HeapAnalysesClient = require("devtools/shared/heapsnapshot/HeapAnalysesClient");
function MemoryPanel(iframeWindow, toolbox) {
function MemoryPanel(iframeWindow, toolbox, commands) {
this.panelWin = iframeWindow;
this._toolbox = toolbox;
this._commands = commands;
const { BrowserLoader } = Cu.import(
"resource://devtools/client/shared/browser-loader.js"

View File

@ -4,9 +4,10 @@
"use strict";
function NetMonitorPanel(iframeWindow, toolbox) {
function NetMonitorPanel(iframeWindow, toolbox, commands) {
this.panelWin = iframeWindow;
this.toolbox = toolbox;
this.commands = commands;
}
NetMonitorPanel.prototype = {

View File

@ -21,10 +21,12 @@ class PerformancePanel {
/**
* @param {PanelWindow} iframeWindow
* @param {Toolbox} toolbox
* @param {Object} commands
*/
constructor(iframeWindow, toolbox) {
constructor(iframeWindow, toolbox, commands) {
this.panelWin = iframeWindow;
this.toolbox = toolbox;
this.commands = commands;
const EventEmitter = require("devtools/shared/event-emitter");
EventEmitter.decorate(this);

View File

@ -5,9 +5,10 @@
loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
function PerformancePanel(iframeWindow, toolbox) {
function PerformancePanel(iframeWindow, toolbox, commands) {
this.panelWin = iframeWindow;
this.toolbox = toolbox;
this.commands = commands;
this._targetAvailablePromise = Promise.resolve();
this._onTargetAvailable = this._onTargetAvailable.bind(this);

View File

@ -9,10 +9,11 @@ const EventEmitter = require("devtools/shared/event-emitter");
loader.lazyRequireGetter(this, "StorageUI", "devtools/client/storage/ui", true);
class StoragePanel {
constructor(panelWin, toolbox) {
constructor(panelWin, toolbox, commands) {
EventEmitter.decorate(this);
this._toolbox = toolbox;
this._commands = commands;
this._panelWin = panelWin;
this.destroy = this.destroy.bind(this);
@ -26,7 +27,7 @@ class StoragePanel {
* open is effectively an asynchronous constructor
*/
async open() {
this.UI = new StorageUI(this._panelWin, this._toolbox);
this.UI = new StorageUI(this._panelWin, this._toolbox, this._commands);
await this.UI.init();

View File

@ -109,13 +109,16 @@ const NON_ORIGINAL_L10N_IDS = new Map([
*
* @param {Window} panelWin
* Window of the toolbox panel to populate UI in.
* @param {Object} commands
* The commands object with all interfaces defined from devtools/shared/commands/
*/
class StorageUI {
constructor(panelWin, toolbox) {
constructor(panelWin, toolbox, commands) {
EventEmitter.decorate(this);
this._window = panelWin;
this._panelDoc = panelWin.document;
this._toolbox = toolbox;
this._commands = commands;
this.sidebarToggledOpen = null;
this.shouldLoadMoreItems = true;

View File

@ -15,10 +15,11 @@ var {
getString,
} = require("resource://devtools/client/styleeditor/StyleEditorUtil.jsm");
var StyleEditorPanel = function StyleEditorPanel(panelWin, toolbox) {
var StyleEditorPanel = function StyleEditorPanel(panelWin, toolbox, commands) {
EventEmitter.decorate(this);
this._toolbox = toolbox;
this._commands = commands;
this._panelWin = panelWin;
this._panelDoc = panelWin.document;

View File

@ -62,7 +62,8 @@ class BrowserConsoleManager {
* A promise object for the opening of the new BrowserConsole instance.
*/
async openBrowserConsole(target, win) {
const hud = new BrowserConsole(target, win, win);
const commands = await target.descriptorFront.getCommands();
const hud = new BrowserConsole(target, commands, win, win);
this._browserConsole = hud;
await hud.init();
return hud;

View File

@ -41,10 +41,11 @@ class BrowserConsole extends WebConsole {
* @param nsIDOMWindow chromeWindow
* The window of the browser console owner.
*/
constructor(target, iframeWindow, chromeWindow) {
super(null, iframeWindow, chromeWindow, true);
constructor(target, commands, iframeWindow, chromeWindow) {
super(null, commands, iframeWindow, chromeWindow, true);
this._browserConsoleTarget = target;
this._descriptorFront = target.descriptorFront;
this._targetList = new TargetList(target.descriptorFront);
this._resourceWatcher = new ResourceWatcher(this._targetList);
this._telemetry = new Telemetry();

View File

@ -16,9 +16,10 @@ loader.lazyGetter(this, "EventEmitter", () =>
/**
* A DevToolPanel that controls the Web Console.
*/
function WebConsolePanel(iframeWindow, toolbox) {
function WebConsolePanel(iframeWindow, toolbox, commands) {
this._frameWindow = iframeWindow;
this._toolbox = toolbox;
this._commands = commands;
EventEmitter.decorate(this);
}
@ -67,6 +68,7 @@ WebConsolePanel.prototype = {
// Open the Web Console.
this.hud = new WebConsole(
this._toolbox,
this._commands,
webConsoleUIWindow,
chromeWindow
);

View File

@ -54,14 +54,23 @@ class WebConsole {
* @constructor
* @param object toolbox
* The toolbox where the web console is displayed.
* @param object commands
* The commands object with all interfaces defined from devtools/shared/commands/
* @param nsIDOMWindow iframeWindow
* The window where the web console UI is already loaded.
* @param nsIDOMWindow chromeWindow
* The window of the web console owner.
* @param bool isBrowserConsole
*/
constructor(toolbox, iframeWindow, chromeWindow, isBrowserConsole = false) {
constructor(
toolbox,
commands,
iframeWindow,
chromeWindow,
isBrowserConsole = false
) {
this.toolbox = toolbox;
this.commands = commands;
this.iframeWindow = iframeWindow;
this.chromeWindow = chromeWindow;
this.hudId = "hud_" + ++gHudId;

View File

@ -0,0 +1,46 @@
# Commands
Commands are singletons, which can be easily used by any frontend code.
They are meant to be exposed widely to the frontend so that any code can easily call any of their methods.
Commands classes expose static methods, which:
* route to the right Front/Actor's method
* handle backward compatibility
* map to many target's actor if needed
These classes are instantiated once per descriptor
and may have inner state, emit events, fire callbacks,...
A transient backward compat need, required by Fission refactorings will be to have some code checking a trait, and either:
* call a single method on a parent process actor (like BreakpointListActor.setBreakpoint)
* otherwise, call a method on each target's scoped actor (like ThreadActor.setBreakpoint, that, for each available target)
Without such layer, we would have to put such code here and there in the frontend code.
This will be harder to remove later, once we get rid of old pre-fission-refactoring codepaths.
This layer already exists in some panels, but we are using slightly different names and practices:
* Debugger uses "client" (devtools/client/debugger/src/client/) and "commands" (devtools/client/debugger/src/client/firefox/commands.js)
Debugger's commands already bundle the code to dispatch an action to many target's actor.
They also contain some backward compat code.
Today, we pass around a `client` object via thunkArgs, which is mapped to commands.js,
instead we could pass a debugger command object.
* Network Monitor uses "connector" (devtools/client/netmonitor/src/connector)
Connectors also bundles backward compat and dispatch to many target's actor.
Today, we pass the `connector` to all middlewares from configureStore,
we could instead pass the netmonitor command object.
* Web Console has:
* WebConsoleConnectionProxy, but this is probably going to disappear and doesn't do much.
* WebConsoleUI, which does dispatch to many target's actor, via getAllProxies (see clearMessagesCache)
* See devtools/client/webconsole/actions/input.js:handleHelperResult(), where we have to put some code, which is a duplicate of Netmonitor Connector,
and could be shared via a netmonitor command class.
* Inspector is probably the panel doing the most dispatch to many target's actor.
Codes using getAllInspectorFronts could all be migrated to an inspector command class:
https://searchfox.org/mozilla-central/search?q=symbol:%23getAllInspectorFronts&redirect=false
and simplify a bit the frontend.
It is also one panel, which still register listener to each target's inspector/walker fronts.
Because inspector isn't using resources.
But this work, registering listeners for each target might be done by such layer and translate the many actor's event into a unified one.
Last, but not least, this layer may allow us to slowly get rid of protocol.js.
Command classes aren't Fronts, nor are they particularly connected to protocol.js.
If we make it so that all the Frontend code using Fronts uses Commands instead, we might more easily get away from protocol.js.

View File

@ -0,0 +1,48 @@
/* 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";
// List of all command modules
// (please try to keep the list alphabetically sorted)
/*eslint sort-keys: "error"*/
const Commands = {};
/**
* For a given descriptor and its related Targets, already initialized,
* return the dictionary with all command instances.
* This dictionary is lazy and commands will be loaded and instanciated on-demand.
*/
async function createCommandsDictionary(descriptorFront) {
// Bug 1675763: Watcher actor is not available in all situations yet.
let watcherFront;
const supportsWatcher = descriptorFront.traits?.watcher;
if (supportsWatcher) {
watcherFront = await descriptorFront.getWatcher();
}
const dictionary = {};
for (const name in Commands) {
loader.lazyGetter(dictionary, name, () => {
const Constructor = require(Commands[name])[name];
return new Constructor({
// Commands can use other commands
commands: dictionary,
// The context to inspect identified by this descriptor
descriptorFront,
// The front for the Watcher Actor, related to the given descriptor
// This is a key actor to watch for targets and resources and pull global actors running in the parent process
watcherFront,
// From here, we could pass DevToolsClient, or any useful protocol classes...
// so that we abstract where and how to fetch all necessary interfaces
// and avoid having to know that you might pull the client via descriptorFront.client
});
});
}
return dictionary;
}
exports.createCommandsDictionary = createCommandsDictionary;

View File

@ -0,0 +1,7 @@
# 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/.
DevToolsModules(
"index.js",
)

View File

@ -9,6 +9,7 @@ include("../templates.mozbuild")
DIRS += [
"acorn",
"css",
"commands",
"compatibility",
"discovery",
"heapsnapshot",