forked from mirrors/gecko-dev
onInspectorFrontDestroyed was throwing because when it is called from the destroy callback of watchFronts, it doesn't get a second argument, and the destructuring fails. This patch defaults the second parameter to an empty object in order to avoid this. Differential Revision: https://phabricator.services.mozilla.com/D126235
254 lines
7.8 KiB
JavaScript
254 lines
7.8 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
|
|
|
|
/**
|
|
* Client-side NodePicker module.
|
|
* To be used by inspector front when it needs to select DOM elements.
|
|
*
|
|
* NodePicker is a proxy for the node picker functionality from WalkerFront instances
|
|
* of all available InspectorFronts. It is a single point of entry for the client to:
|
|
* - invoke actions to start and stop picking nodes on all walkers
|
|
* - listen to node picker events from all walkers and relay them to subscribers
|
|
*
|
|
*
|
|
* @param {TargetCommand} targetCommand
|
|
* The TargetCommand component referencing all the targets to be debugged
|
|
* @param {Selection} selection
|
|
* The global Selection object
|
|
*/
|
|
class NodePicker extends EventEmitter {
|
|
constructor(targetCommand, selection) {
|
|
super();
|
|
|
|
this.targetCommand = targetCommand;
|
|
|
|
// Whether or not the node picker is active.
|
|
this.isPicking = false;
|
|
// Whether to focus the top-level frame before picking nodes.
|
|
this.doFocus = false;
|
|
|
|
// The set of inspector fronts corresponding to the targets where picking happens.
|
|
this._currentInspectorFronts = new Set();
|
|
|
|
this._onInspectorFrontAvailable = this._onInspectorFrontAvailable.bind(
|
|
this
|
|
);
|
|
this._onInspectorFrontDestroyed = this._onInspectorFrontDestroyed.bind(
|
|
this
|
|
);
|
|
this._onTargetAvailable = this._onTargetAvailable.bind(this);
|
|
|
|
this.cancel = this.cancel.bind(this);
|
|
this.start = this.start.bind(this);
|
|
this.stop = this.stop.bind(this);
|
|
this.togglePicker = this.togglePicker.bind(this);
|
|
|
|
this._onHovered = this._onHovered.bind(this);
|
|
this._onPicked = this._onPicked.bind(this);
|
|
this._onPreviewed = this._onPreviewed.bind(this);
|
|
this._onCanceled = this._onCanceled.bind(this);
|
|
}
|
|
|
|
/**
|
|
* Start/stop the element picker on the debuggee target.
|
|
*
|
|
* @param {Boolean} doFocus
|
|
* Optionally focus the content area once the picker is activated.
|
|
* @return Promise that resolves when done
|
|
*/
|
|
togglePicker(doFocus) {
|
|
if (this.isPicking) {
|
|
return this.stop();
|
|
}
|
|
return this.start(doFocus);
|
|
}
|
|
|
|
/**
|
|
* Tell the walker front corresponding to the given inspector front to enter node
|
|
* picking mode (listen for mouse movements over its nodes) and set event listeners
|
|
* associated with node picking: hover node, pick node, preview, cancel. See WalkerSpec.
|
|
*
|
|
* @param {InspectorFront} inspectorFront
|
|
* @return {Promise}
|
|
*/
|
|
async _onInspectorFrontAvailable(inspectorFront) {
|
|
this._currentInspectorFronts.add(inspectorFront);
|
|
// watchFront may notify us about inspector fronts that aren't initialized yet,
|
|
// so ensure waiting for initialization in order to have a defined `walker` attribute.
|
|
await inspectorFront.initialize();
|
|
const { walker } = inspectorFront;
|
|
walker.on("picker-node-hovered", this._onHovered);
|
|
walker.on("picker-node-picked", this._onPicked);
|
|
walker.on("picker-node-previewed", this._onPreviewed);
|
|
walker.on("picker-node-canceled", this._onCanceled);
|
|
await walker.pick(this.doFocus);
|
|
|
|
this.emitForTests("inspector-front-ready-for-picker", walker);
|
|
}
|
|
|
|
/**
|
|
* Tell the walker front corresponding to the given inspector front to exit the node
|
|
* picking mode and remove all event listeners associated with node picking.
|
|
*
|
|
* @param {InspectorFront} inspectorFront
|
|
* @param {Boolean} isDestroyCodePath
|
|
* Optional. If true, we assume that's when the toolbox closes
|
|
* and we should avoid doing any RDP request.
|
|
* @return {Promise}
|
|
*/
|
|
async _onInspectorFrontDestroyed(inspectorFront, { isDestroyCodepath } = {}) {
|
|
this._currentInspectorFronts.delete(inspectorFront);
|
|
|
|
const { walker } = inspectorFront;
|
|
if (!walker) {
|
|
return;
|
|
}
|
|
|
|
walker.off("picker-node-hovered", this._onHovered);
|
|
walker.off("picker-node-picked", this._onPicked);
|
|
walker.off("picker-node-previewed", this._onPreviewed);
|
|
walker.off("picker-node-canceled", this._onCanceled);
|
|
// Only do a RDP request if we stop the node picker from a user action.
|
|
// Avoid doing one when we close the toolbox, in this scenario
|
|
// the walker actor on the server side will automatically cancel the node picking.
|
|
if (!isDestroyCodepath) {
|
|
await walker.cancelPick();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* While node picking, we want each target's walker fronts to listen for mouse
|
|
* movements over their nodes and emit events. Walker fronts are obtained from
|
|
* inspector fronts so we watch for the creation and destruction of inspector fronts
|
|
* in order to add or remove the necessary event listeners.
|
|
*
|
|
* @param {TargetFront} targetFront
|
|
* @return {Promise}
|
|
*/
|
|
async _onTargetAvailable({ targetFront }) {
|
|
targetFront.watchFronts(
|
|
"inspector",
|
|
this._onInspectorFrontAvailable,
|
|
this._onInspectorFrontDestroyed
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Start the element picker.
|
|
* This will instruct walker fronts of all available targets (and those of targets
|
|
* created while node picking is active) to listen for mouse movements over their nodes
|
|
* and trigger events when a node is hovered or picked.
|
|
*
|
|
* @param {Boolean} doFocus
|
|
* Optionally focus the content area once the picker is activated.
|
|
*/
|
|
async start(doFocus) {
|
|
if (this.isPicking) {
|
|
return;
|
|
}
|
|
this.isPicking = true;
|
|
this.doFocus = doFocus;
|
|
|
|
this.emit("picker-starting");
|
|
|
|
this.targetCommand.watchTargets(
|
|
this.targetCommand.ALL_TYPES,
|
|
this._onTargetAvailable
|
|
);
|
|
|
|
this.emit("picker-started");
|
|
}
|
|
|
|
/**
|
|
* Stop the element picker. Note that the picker is automatically stopped when
|
|
* an element is picked.
|
|
*
|
|
* @param {Boolean} isDestroyCodePath
|
|
* Optional. If true, we assume that's when the toolbox closes
|
|
* and we should avoid doing any RDP request.
|
|
*/
|
|
async stop({ isDestroyCodepath } = {}) {
|
|
if (!this.isPicking) {
|
|
return;
|
|
}
|
|
this.isPicking = false;
|
|
this.doFocus = false;
|
|
|
|
this.targetCommand.unwatchTargets(
|
|
this.targetCommand.ALL_TYPES,
|
|
this._onTargetAvailable
|
|
);
|
|
|
|
for (const inspectorFront of this._currentInspectorFronts) {
|
|
await this._onInspectorFrontDestroyed(inspectorFront, {
|
|
isDestroyCodepath,
|
|
});
|
|
}
|
|
|
|
this._currentInspectorFronts.clear();
|
|
|
|
this.emit("picker-stopped");
|
|
}
|
|
|
|
destroy() {
|
|
// Do not await for stop as the isDestroy argument will make this method synchronous
|
|
// and we want to avoid having an async destroy
|
|
this.stop({ isDestroyCodepath: true });
|
|
this.targetCommand = null;
|
|
}
|
|
|
|
/**
|
|
* Stop the picker, but also emit an event that the picker was canceled.
|
|
*/
|
|
async cancel() {
|
|
await this.stop();
|
|
this.emit("picker-node-canceled");
|
|
}
|
|
|
|
/**
|
|
* When a node is hovered by the mouse when the highlighter is in picker mode
|
|
*
|
|
* @param {Object} data
|
|
* Information about the node being hovered
|
|
*/
|
|
_onHovered(data) {
|
|
this.emit("picker-node-hovered", data.node);
|
|
}
|
|
|
|
/**
|
|
* When a node has been picked while the highlighter is in picker mode
|
|
*
|
|
* @param {Object} data
|
|
* Information about the picked node
|
|
*/
|
|
_onPicked(data) {
|
|
this.emit("picker-node-picked", data.node);
|
|
return this.stop();
|
|
}
|
|
|
|
/**
|
|
* When a node has been shift-clicked (previewed) while the highlighter is in
|
|
* picker mode
|
|
*
|
|
* @param {Object} data
|
|
* Information about the picked node
|
|
*/
|
|
_onPreviewed(data) {
|
|
this.emit("picker-node-previewed", data.node);
|
|
}
|
|
|
|
/**
|
|
* When the picker is canceled, stop the picker, and make sure the toolbox
|
|
* gets the focus.
|
|
*/
|
|
_onCanceled() {
|
|
return this.cancel();
|
|
}
|
|
}
|
|
|
|
module.exports = NodePicker;
|