--- a/devtools/client/commandline/test/browser_cmd_paintflashing.js
+++ b/devtools/client/commandline/test/browser_cmd_paintflashing.js
@@ -11,17 +11,17 @@ const TEST_URI = "http://example.com/bro
function test() {
return Task.spawn(testTask).then(finish, helpers.handleError);
}
var tests = {
testInput: function (options) {
let toggleCommand = options.requisition.system.commands.get("paintflashing toggle");
- let _tab = options.tab;
+ let { tab } = options;
let actions = [
{
command: "paintflashing on",
isChecked: true,
label: "checked after on"
},
{
@@ -39,17 +39,17 @@ var tests = {
isChecked: false,
label: "unchecked after toggle"
}
];
return helpers.audit(options, actions.map(spec => ({
setup: spec.command,
exec: {},
- post: () => is(toggleCommand.state.isChecked({_tab}), spec.isChecked, spec.label)
+ post: () => is(toggleCommand.state.isChecked({tab}), spec.isChecked, spec.label)
})));
},
};
function* testTask() {
let options = yield helpers.openTab(TEST_URI);
yield helpers.openToolbar(options);
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -21,16 +21,17 @@ loader.lazyGetter(this, "MemoryPanel", (
loader.lazyGetter(this, "PerformancePanel", () => require("devtools/client/performance/panel").PerformancePanel);
loader.lazyGetter(this, "NetMonitorPanel", () => require("devtools/client/netmonitor/panel").NetMonitorPanel);
loader.lazyGetter(this, "StoragePanel", () => require("devtools/client/storage/panel").StoragePanel);
loader.lazyGetter(this, "ScratchpadPanel", () => require("devtools/client/scratchpad/scratchpad-panel").ScratchpadPanel);
loader.lazyGetter(this, "DomPanel", () => require("devtools/client/dom/dom-panel").DomPanel);
// Other dependencies
loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
+loader.lazyRequireGetter(this, "CommandState", "devtools/shared/gcli/command-state", true);
loader.lazyImporter(this, "ResponsiveUIManager", "resource://devtools/client/responsivedesign/responsivedesign.jsm");
loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
const {LocalizationHelper} = require("devtools/shared/l10n");
const L10N = new LocalizationHelper("devtools/client/locales/startup.properties");
var Tools = {};
exports.Tools = Tools;
@@ -492,17 +493,25 @@ exports.ToolboxButtons = [
}
},
{ id: "command-button-paintflashing",
description: l10n("toolbox.buttons.paintflashing"),
isTargetSupported: target => target.isLocalTab,
onClick(event, toolbox) {
CommandUtils.executeOnTarget(toolbox.target, "paintflashing toggle");
},
- autoToggle: true
+ isChecked(toolbox) {
+ return CommandState.isEnabledForTarget(toolbox.target, "paintflashing");
+ },
+ setup(toolbox, onChange) {
+ CommandState.on("changed", onChange);
+ },
+ teardown(toolbox, onChange) {
+ CommandState.off("changed", onChange);
+ }
},
{ id: "command-button-scratchpad",
description: l10n("toolbox.buttons.scratchpad"),
isTargetSupported: target => target.isLocalTab,
onClick(event, toolbox) {
ScratchpadManager.openScratchpad();
}
},
@@ -546,25 +555,41 @@ exports.ToolboxButtons = [
}
},
{ id: "command-button-rulers",
description: l10n("toolbox.buttons.rulers"),
isTargetSupported: target => target.isLocalTab,
onClick(event, toolbox) {
CommandUtils.executeOnTarget(toolbox.target, "rulers");
},
- autoToggle: true
+ isChecked(toolbox) {
+ return CommandState.isEnabledForTarget(toolbox.target, "rulers");
+ },
+ setup(toolbox, onChange) {
+ CommandState.on("changed", onChange);
+ },
+ teardown(toolbox, onChange) {
+ CommandState.off("changed", onChange);
+ }
},
{ id: "command-button-measure",
description: l10n("toolbox.buttons.measure"),
isTargetSupported: target => target.isLocalTab,
onClick(event, toolbox) {
CommandUtils.executeOnTarget(toolbox.target, "measure");
},
- autoToggle: true
+ isChecked(toolbox) {
+ return CommandState.isEnabledForTarget(toolbox.target, "measure");
+ },
+ setup(toolbox, onChange) {
+ CommandState.on("changed", onChange);
+ },
+ teardown(toolbox, onChange) {
+ CommandState.off("changed", onChange);
+ }
},
];
/**
* Lookup l10n string from a string bundle.
*
* @param {string} name
* The key to lookup.
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -569,35 +569,30 @@ Toolbox.prototype = {
* memory leaks. The same arguments than `setup` function are
* passed to `teardown`.
* @property {Function} isTargetSupported - Function to automatically enable/disable
* the button based on the target. If the target don't support
* the button feature, this method should return false.
* @property {Function} isChecked - Optional function called to known if the button
* is toggled or not. The function should return true when
* the button should be displayed as toggled on.
- * @property {Boolean} autoToggle - If true, the checked state is going to be
- * automatically toggled on click.
*/
_createButtonState: function (options) {
let isCheckedValue = false;
const { id, className, description, onClick, isInStartContainer, setup, teardown,
- isTargetSupported, isChecked, autoToggle } = options;
+ isTargetSupported, isChecked } = options;
const toolbox = this;
const button = {
id,
className,
description,
onClick(event) {
if (typeof onClick == "function") {
onClick(event, toolbox);
}
- if (autoToggle) {
- button.isChecked = !button.isChecked;
- }
},
isTargetSupported,
get isChecked() {
if (typeof isChecked == "function") {
return isChecked(toolbox);
}
return isCheckedValue;
},
--- a/devtools/client/shared/test/browser_telemetry_button_paintflashing.js
+++ b/devtools/client/shared/test/browser_telemetry_button_paintflashing.js
@@ -39,18 +39,25 @@ function* testButton(toolbox, Telemetry)
function* delayedClicks(toolbox, node, clicks) {
for (let i = 0; i < clicks; i++) {
yield new Promise(resolve => {
// See TOOL_DELAY for why we need setTimeout here
setTimeout(() => resolve(), TOOL_DELAY);
});
- let PaintFlashingCmd = require("devtools/shared/gcli/commands/paintflashing");
- let clicked = PaintFlashingCmd.eventEmitter.once("changed");
+ let { CommandState } = require("devtools/shared/gcli/command-state");
+ let clicked = new Promise(resolve => {
+ CommandState.on("changed", function changed(type, { command }) {
+ if (command === "paintflashing") {
+ CommandState.off("changed", changed);
+ resolve();
+ }
+ });
+ });
info("Clicking button " + node.id);
node.click();
yield clicked;
}
}
--- a/devtools/shared/event-emitter.js
+++ b/devtools/shared/event-emitter.js
@@ -79,23 +79,26 @@
}
/**
* Decorate an object with event emitter functionality.
*
* @param Object objectToDecorate
* Bind all public methods of EventEmitter to
* the objectToDecorate object.
+ * @return Object the object given.
*/
EventEmitter.decorate = function (objectToDecorate) {
let emitter = new EventEmitter();
objectToDecorate.on = emitter.on.bind(emitter);
objectToDecorate.off = emitter.off.bind(emitter);
objectToDecorate.once = emitter.once.bind(emitter);
objectToDecorate.emit = emitter.emit.bind(emitter);
+
+ return objectToDecorate;
};
EventEmitter.prototype = {
/**
* Connect a listener.
*
* @param string event
* The event name to which we're connecting.
new file mode 100644
--- /dev/null
+++ b/devtools/shared/gcli/command-state.js
@@ -0,0 +1,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 EventEmitter = require("devtools/shared/event-emitter");
+
+loader.lazyRequireGetter(this, "getBrowserForTab", "sdk/tabs/utils", true);
+
+const getTargetId = ({tab}) => getBrowserForTab(tab).outerWindowID;
+const enabledCommands = new Map();
+
+/**
+ * The `CommandState` is a singleton that provides utility methods to keep the commands'
+ * state in sync between the toolbox, the toolbar and the content.
+ */
+const CommandState = EventEmitter.decorate({
+ /**
+ * Returns if a command is enabled on a given target.
+ *
+ * @param {Object} target
+ * The target object must have a tab's reference.
+ * @param {String} command
+ * The command's name used in gcli.
+ * @ returns {Boolean} returns `false` if the command is not enabled for the target
+ * given, or if the target given hasn't a tab; `true` otherwise.
+ */
+ isEnabledForTarget(target, command) {
+ if (!target.tab || !enabledCommands.has(command)) {
+ return false;
+ }
+
+ return enabledCommands.get(command).has(getTargetId(target));
+ },
+
+ /**
+ * Enables a command on a given target.
+ * Emits a "changed" event to notify potential observers about the new commands state.
+ *
+ * @param {Object} target
+ * The target object must have a tab's reference.
+ * @param {String} command
+ * The command's name used in gcli.
+ */
+ enableForTarget(target, command) {
+ if (!target.tab) {
+ return;
+ }
+
+ if (!enabledCommands.has(command)) {
+ enabledCommands.set(command, new Set());
+ }
+
+ enabledCommands.get(command).add(getTargetId(target));
+
+ CommandState.emit("changed", {target, command});
+ },
+
+ /**
+ * Disabled a command on a given target.
+ * Emits a "changed" event to notify potential observers about the new commands state.
+ *
+ * @param {Object} target
+ * The target object must have a tab's reference.
+ * @param {String} command
+ * The command's name used in gcli.
+ */
+ disableForTarget(target, command) {
+ if (!target.tab || !enabledCommands.has(command)) {
+ return;
+ }
+
+ enabledCommands.get(command).delete(getTargetId(target));
+
+ CommandState.emit("changed", {target, command});
+ },
+});
+exports.CommandState = CommandState;
+
--- a/devtools/shared/gcli/commands/measure.js
+++ b/devtools/shared/gcli/commands/measure.js
@@ -1,94 +1,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/. */
- /* globals getOuterId, getBrowserForTab */
"use strict";
-const EventEmitter = require("devtools/shared/event-emitter");
-const eventEmitter = new EventEmitter();
const events = require("sdk/event/core");
-loader.lazyRequireGetter(this, "getOuterId", "sdk/window/utils", true);
-loader.lazyRequireGetter(this, "getBrowserForTab", "sdk/tabs/utils", true);
+loader.lazyRequireGetter(this, "CommandState",
+ "devtools/shared/gcli/command-state", true);
const l10n = require("gcli/l10n");
require("devtools/server/actors/inspector");
const { MeasuringToolHighlighter, HighlighterEnvironment } =
require("devtools/server/actors/highlighters");
const highlighters = new WeakMap();
-const visibleHighlighters = new Set();
-
-const isCheckedFor = (tab) =>
- tab ? visibleHighlighters.has(getBrowserForTab(tab).outerWindowID) : false;
exports.items = [
// The client measure command is used to maintain the toolbar button state
// only and redirects to the server command to actually toggle the measuring
// tool (see `measure_server` below).
{
name: "measure",
runAt: "client",
description: l10n.lookup("measureDesc"),
manual: l10n.lookup("measureManual"),
buttonId: "command-button-measure",
buttonClass: "command-button command-button-invertable",
tooltipText: l10n.lookup("measureTooltip"),
state: {
- isChecked: ({_tab}) => isCheckedFor(_tab),
- onChange: (target, handler) => eventEmitter.on("changed", handler),
- offChange: (target, handler) => eventEmitter.off("changed", handler)
+ isChecked: (target) => CommandState.isEnabledForTarget(target, "measure"),
+ onChange: (target, handler) => CommandState.on("changed", handler),
+ offChange: (target, handler) => CommandState.off("changed", handler)
},
exec: function* (args, context) {
let { target } = context.environment;
// Pipe the call to the server command.
let response = yield context.updateExec("measure_server");
- let { visible, id } = response.data;
+ let isEnabled = response.data;
- if (visible) {
- visibleHighlighters.add(id);
+ if (isEnabled) {
+ CommandState.enableForTarget(target, "measure");
} else {
- visibleHighlighters.delete(id);
+ CommandState.disableForTarget(target, "measure");
}
- eventEmitter.emit("changed", { target });
-
// Toggle off the button when the page navigates because the measuring
// tool is removed automatically by the MeasuringToolHighlighter on the
// server then.
- let onNavigate = () => {
- visibleHighlighters.delete(id);
- eventEmitter.emit("changed", { target });
- };
- target.off("will-navigate", onNavigate);
- target.once("will-navigate", onNavigate);
+ target.once("will-navigate", () =>
+ CommandState.disableForTarget(target, "measure"));
}
},
// The server measure command is hidden by default, it's just used by the
// client command.
{
name: "measure_server",
runAt: "server",
hidden: true,
returnType: "highlighterVisibility",
exec: function (args, context) {
let env = context.environment;
let { document } = env;
- let id = getOuterId(env.window);
// Calling the command again after the measuring tool has been shown once,
// hides it.
if (highlighters.has(document)) {
let { highlighter } = highlighters.get(document);
highlighter.destroy();
- return {visible: false, id};
+ return false;
}
// Otherwise, display the measuring tool.
let environment = new HighlighterEnvironment();
environment.initFromWindow(env.window);
let highlighter = new MeasuringToolHighlighter(environment);
// Store the instance of the measuring tool highlighter for this document
@@ -101,12 +87,12 @@ exports.items = [
if (highlighters.has(document)) {
let { environment: toDestroy } = highlighters.get(document);
toDestroy.destroy();
highlighters.delete(document);
}
});
highlighter.show();
- return {visible: true, id};
+ return true;
}
}
];
--- a/devtools/shared/gcli/commands/paintflashing.js
+++ b/devtools/shared/gcli/commands/paintflashing.js
@@ -1,59 +1,42 @@
/* 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 { Ci } = require("chrome");
-loader.lazyRequireGetter(this, "getOuterId", "sdk/window/utils", true);
-loader.lazyRequireGetter(this, "getBrowserForTab", "sdk/tabs/utils", true);
+
+loader.lazyRequireGetter(this, "CommandState",
+ "devtools/shared/gcli/command-state", true);
var telemetry;
try {
const Telemetry = require("devtools/client/shared/telemetry");
telemetry = new Telemetry();
} catch (e) {
// DevTools Telemetry module only available in Firefox
}
-const EventEmitter = require("devtools/shared/event-emitter");
-const eventEmitter = new EventEmitter();
-
-// exports the event emitter to help test know when this command is toggled
-exports.eventEmitter = eventEmitter;
-
const gcli = require("gcli/index");
const l10n = require("gcli/l10n");
-const enabledPaintFlashing = new Set();
-
-const isCheckedFor = (tab) =>
- tab ? enabledPaintFlashing.has(getBrowserForTab(tab).outerWindowID) : false;
-
/**
* Fire events and telemetry when paintFlashing happens
*/
-function onPaintFlashingChanged(target, state) {
- const { flashing, id } = state;
-
+function onPaintFlashingChanged(target, flashing) {
if (flashing) {
- enabledPaintFlashing.add(id);
+ CommandState.enableForTarget(target, "paintflashing");
} else {
- enabledPaintFlashing.delete(id);
+ CommandState.disableForTarget(target, "paintflashing");
}
- eventEmitter.emit("changed", { target: target });
- function fireChange() {
- eventEmitter.emit("changed", { target: target });
- }
-
- target.off("navigate", fireChange);
- target.once("navigate", fireChange);
+ target.once("will-navigate", () =>
+ CommandState.disableForTarget(target, "paintflashing"));
if (!telemetry) {
return;
}
if (flashing) {
telemetry.toolOpened("paintflashing");
} else {
telemetry.toolClosed("paintflashing");
@@ -160,19 +143,19 @@ exports.items = [
{
item: "command",
runAt: "client",
name: "paintflashing toggle",
hidden: true,
buttonId: "command-button-paintflashing",
buttonClass: "command-button command-button-invertable",
state: {
- isChecked: ({_tab}) => isCheckedFor(_tab),
- onChange: (_, handler) => eventEmitter.on("changed", handler),
- offChange: (_, handler) => eventEmitter.off("changed", handler),
+ isChecked: (target) => CommandState.isEnabledForTarget(target, "paintflashing"),
+ onChange: (_, handler) => CommandState.on("changed", handler),
+ offChange: (_, handler) => CommandState.off("changed", handler),
},
tooltipText: l10n.lookup("paintflashingTooltip"),
description: l10n.lookup("paintflashingToggleDesc"),
manual: l10n.lookup("paintflashingManual"),
exec: function* (args, context) {
const output = yield context.updateExec("paintflashing_server --state toggle");
onPaintFlashingChanged(context.environment.target, output.data);
@@ -190,15 +173,13 @@ exports.items = [
name: "selection",
data: [ "on", "off", "toggle", "query" ]
}
},
],
returnType: "paintFlashingState",
exec: function (args, context) {
let { window } = context.environment;
- let id = getOuterId(window);
- let flashing = setPaintFlashing(window, args.state);
- return { flashing, id };
+ return setPaintFlashing(window, args.state);
}
}
];
--- a/devtools/shared/gcli/commands/rulers.js
+++ b/devtools/shared/gcli/commands/rulers.js
@@ -1,92 +1,79 @@
/* 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/. */
-/* globals getBrowserForTab */
"use strict";
-const EventEmitter = require("devtools/shared/event-emitter");
-const eventEmitter = new EventEmitter();
const events = require("sdk/event/core");
+
loader.lazyRequireGetter(this, "getOuterId", "sdk/window/utils", true);
-loader.lazyRequireGetter(this, "getBrowserForTab", "sdk/tabs/utils", true);
+loader.lazyRequireGetter(this, "CommandState",
+ "devtools/shared/gcli/command-state", true);
const l10n = require("gcli/l10n");
require("devtools/server/actors/inspector");
const { RulersHighlighter, HighlighterEnvironment } =
require("devtools/server/actors/highlighters");
const highlighters = new WeakMap();
-const visibleHighlighters = new Set();
-
-const isCheckedFor = (tab) =>
- tab ? visibleHighlighters.has(getBrowserForTab(tab).outerWindowID) : false;
exports.items = [
// The client rulers command is used to maintain the toolbar button state only
// and redirects to the server command to actually toggle the rulers (see
// rulers_server below).
{
name: "rulers",
runAt: "client",
description: l10n.lookup("rulersDesc"),
manual: l10n.lookup("rulersManual"),
buttonId: "command-button-rulers",
buttonClass: "command-button command-button-invertable",
tooltipText: l10n.lookup("rulersTooltip"),
state: {
- isChecked: ({_tab}) => isCheckedFor(_tab),
- onChange: (target, handler) => eventEmitter.on("changed", handler),
- offChange: (target, handler) => eventEmitter.off("changed", handler)
+ isChecked: (target) => CommandState.isEnabledForTarget(target, "rulers"),
+ onChange: (target, handler) => CommandState.on("changed", handler),
+ offChange: (target, handler) => CommandState.off("changed", handler)
},
exec: function* (args, context) {
let { target } = context.environment;
// Pipe the call to the server command.
let response = yield context.updateExec("rulers_server");
- let { visible, id } = response.data;
+ let isEnabled = response.data;
- if (visible) {
- visibleHighlighters.add(id);
+ if (isEnabled) {
+ CommandState.enableForTarget(target, "rulers");
} else {
- visibleHighlighters.delete(id);
+ CommandState.disableForTarget(target, "rulers");
}
- eventEmitter.emit("changed", { target });
-
// Toggle off the button when the page navigates because the rulers are
// removed automatically by the RulersHighlighter on the server then.
- let onNavigate = () => {
- visibleHighlighters.delete(id);
- eventEmitter.emit("changed", { target });
- };
- target.off("will-navigate", onNavigate);
- target.once("will-navigate", onNavigate);
+ target.once("will-navigate", () => CommandState.disableForTarget(target, "rulers"));
}
},
// The server rulers command is hidden by default, it's just used by the
// client command.
{
name: "rulers_server",
runAt: "server",
hidden: true,
returnType: "highlighterVisibility",
exec: function (args, context) {
let env = context.environment;
let { document } = env;
- let id = getOuterId(env.window);
// Calling the command again after the rulers have been shown once hides
// them.
if (highlighters.has(document)) {
let { highlighter } = highlighters.get(document);
highlighter.destroy();
- return {visible: false, id};
+ return false;
}
// Otherwise, display the rulers.
let environment = new HighlighterEnvironment();
environment.initFromWindow(env.window);
let highlighter = new RulersHighlighter(environment);
// Store the instance of the rulers highlighter for this document so we
@@ -99,12 +86,12 @@ exports.items = [
if (highlighters.has(document)) {
let { environment: toDestroy } = highlighters.get(document);
toDestroy.destroy();
highlighters.delete(document);
}
});
highlighter.show();
- return {visible: true, id};
+ return true;
}
}
];
--- a/devtools/shared/gcli/moz.build
+++ b/devtools/shared/gcli/moz.build
@@ -14,10 +14,11 @@ DIRS += [
'source/lib/gcli/languages',
'source/lib/gcli/mozui',
'source/lib/gcli/types',
'source/lib/gcli/ui',
'source/lib/gcli/util',
]
DevToolsModules(
+ 'command-state.js',
'templater.js'
)