--- 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();
}
},
@@ -547,25 +556,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/responsive.html/browser/swap.js
+++ b/devtools/client/responsive.html/browser/swap.js
@@ -3,16 +3,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Ci } = require("chrome");
const promise = require("promise");
const { Task } = require("devtools/shared/task");
const { tunnelToInnerBrowser } = require("./tunnel");
+const { CommandState } = require("devtools/shared/gcli/command-state");
/**
* Swap page content from an existing tab into a new browser within a container
* page. Page state is preserved by using `swapFrameLoaders`, just like when
* you move a tab to a new window. This provides a seamless transition for the
* user since the page is not reloaded.
*
* See /devtools/docs/responsive-design-mode.md for a high level overview of how
@@ -135,16 +136,21 @@ function swapToInnerBrowser({ tab, conta
}
}
// Force the browser UI to match the new state of the tab and browser.
thawNavigationState(tab);
gBrowser.setTabTitle(tab);
gBrowser.updateCurrentBrowser(true);
+ // Since bug 1341756 when we're entering in RDM we're clearing any highlighters;
+ // so we have to update the command's state too.
+ CommandState.disableForTarget({tab}, "measure");
+ CommandState.disableForTarget({tab}, "rulers");
+
// Show the browser content again now that the move is done.
tab.linkedBrowser.style.visibility = "";
}),
stop() {
// Hide the browser content temporarily while things move around to avoid displaying
// strange intermediate states.
tab.linkedBrowser.style.visibility = "hidden";
@@ -204,16 +210,21 @@ function swapToInnerBrowser({ tab, conta
// The focus manager seems to get a little dizzy after all this swapping. If a
// content element had been focused inside the viewport before stopping, it will
// have lost focus. Activate the frame to restore expected focus.
tab.linkedBrowser.frameLoader.activateRemoteFrame();
delete tab.isResponsiveDesignMode;
+ // Since bug 1341756 when we're exiting from RDM we're clearing any highlighters;
+ // so we have to update the command's state too.
+ CommandState.disableForTarget({tab}, "measure");
+ CommandState.disableForTarget({tab}, "rulers");
+
// Show the browser content again now that the move is done.
tab.linkedBrowser.style.visibility = "";
},
};
}
/**
--- 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,41 @@
/* 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 +142,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 +172,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'
)