Bug 1262439 - 5 - Move eyedropper btn to inspector and update gcli command; r=bgrins draft
authorPatrick Brosset <pbrosset@mozilla.com>
Thu, 07 Jul 2016 14:59:47 +0200
changeset 385011 686966a8d27f6edfaae4d02355f91bbe6d4b0e12
parent 385010 45b7a7117d12c56563b3445cc7f9326cdc99a81f
child 385012 c7deaa4cbe06bac70cac7c15ae2688b6bd49edbd
push id22388
push userpbrosset@mozilla.com
push dateThu, 07 Jul 2016 13:03:20 +0000
reviewersbgrins
bugs1262439
milestone50.0a1
Bug 1262439 - 5 - Move eyedropper btn to inspector and update gcli command; r=bgrins This moves the eyedropper button from the toolbar into the inspector, therefore the old eyedropper command isn't needed anymore, or at least not as it was. The button in the inspector simply uses the pickColorFromPage inspector actor method. And to preserve a eyedropper gcli command, a new simpler one was added. MozReview-Commit-ID: B1yr1EqLFBD
devtools/client/definitions.js
devtools/client/eyedropper/commands.js
devtools/client/eyedropper/moz.build
devtools/client/framework/toolbox.js
devtools/client/inspector/inspector-commands.js
devtools/client/inspector/inspector-panel.js
devtools/client/inspector/inspector.xul
devtools/client/locales/en-US/inspector.dtd
devtools/client/preferences/devtools.js
devtools/client/shared/test/browser.ini
devtools/client/shared/test/browser_telemetry_button_eyedropper.js
devtools/client/themes/inspector.css
devtools/server/actors/highlighters/eye-dropper.js
devtools/shared/locales/en-US/gclicommands.properties
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -95,18 +95,17 @@ Tools.inspector = {
   panelLabel: l10n("inspector.panelLabel", inspectorStrings),
   get tooltip() {
     return l10n("inspector.tooltip2", inspectorStrings,
     (osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+") + this.key);
   },
   inMenu: true,
   commands: [
     "devtools/client/responsivedesign/resize-commands",
-    "devtools/client/inspector/inspector-commands",
-    "devtools/client/eyedropper/commands.js"
+    "devtools/client/inspector/inspector-commands"
   ],
 
   preventClosingOnKey: true,
   onkey: function (panel, toolbox) {
     toolbox.highlighterUtils.togglePicker();
   },
 
   isTargetSupported: function (target) {
deleted file mode 100644
--- a/devtools/client/eyedropper/commands.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/* 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/. */
-
-const l10n = require("gcli/l10n");
-const EventEmitter = require("devtools/shared/event-emitter");
-const eventEmitter = new EventEmitter();
-
-var { Eyedropper, EyedropperManager } = require("devtools/client/eyedropper/eyedropper");
-
-/**
- * 'eyedropper' command
- */
-exports.items = [{
-  item: "command",
-  runAt: "client",
-  name: "eyedropper",
-  description: l10n.lookup("eyedropperDesc"),
-  manual: l10n.lookup("eyedropperManual"),
-  buttonId: "command-button-eyedropper",
-  buttonClass: "command-button command-button-invertable",
-  tooltipText: l10n.lookup("eyedropperTooltip"),
-  state: {
-    isChecked: function (target) {
-      if (!target.tab) {
-        return false;
-      }
-      let chromeWindow = target.tab.ownerDocument.defaultView;
-      let dropper = EyedropperManager.getInstance(chromeWindow);
-      if (dropper) {
-        return true;
-      }
-      return false;
-    },
-    onChange: function (target, changeHandler) {
-      eventEmitter.on("changed", changeHandler);
-    },
-    offChange: function (target, changeHandler) {
-      eventEmitter.off("changed", changeHandler);
-    },
-  },
-  exec: function (args, context) {
-    let chromeWindow = context.environment.chromeWindow;
-    let target = context.environment.target;
-
-    let dropper = EyedropperManager.createInstance(chromeWindow,
-                                                   { context: "command",
-                                                     copyOnSelect: true });
-    dropper.open();
-
-    eventEmitter.emit("changed", { target: target });
-
-    dropper.once("destroy", () => {
-      eventEmitter.emit("changed", { target: target });
-    });
-  }
-}];
--- a/devtools/client/eyedropper/moz.build
+++ b/devtools/client/eyedropper/moz.build
@@ -1,13 +1,12 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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(
-    'commands.js',
     'eyedropper-child.js',
     'eyedropper.js'
 )
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -90,17 +90,16 @@ const ToolboxButtons = exports.ToolboxBu
       return target.activeTab && target.activeTab.traits.frames;
     }
   },
   { id: "command-button-splitconsole",
     isTargetSupported: target => !target.isAddon },
   { id: "command-button-responsive" },
   { id: "command-button-paintflashing" },
   { id: "command-button-scratchpad" },
-  { id: "command-button-eyedropper" },
   { id: "command-button-screenshot" },
   { id: "command-button-rulers" },
   { id: "command-button-measure" },
   { id: "command-button-noautohide",
     isTargetSupported: target => target.chrome },
 ];
 
 /**
--- a/devtools/client/inspector/inspector-commands.js
+++ b/devtools/client/inspector/inspector-commands.js
@@ -1,17 +1,17 @@
 /* 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 l10n = require("gcli/l10n");
-loader.lazyRequireGetter(this, "gDevTools",
-                         "devtools/client/framework/devtools", true);
+loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
+const {EyeDropper, HighlighterEnvironment} = require("devtools/server/actors/highlighters");
 
 exports.items = [{
   item: "command",
   runAt: "server",
   name: "inspect",
   description: l10n.lookup("inspectDesc"),
   manual: l10n.lookup("inspectManual"),
   params: [
@@ -23,9 +23,27 @@ exports.items = [{
     }
   ],
   exec: function (args, context) {
     let target = context.environment.target;
     return gDevTools.showToolbox(target, "inspector").then(toolbox => {
       toolbox.getCurrentPanel().selection.setNode(args.selector, "gcli");
     });
   }
+}, {
+  item: "command",
+  runAt: "server",
+  name: "eyedropper",
+  description: l10n.lookup("eyedropperDesc"),
+  manual: l10n.lookup("eyedropperManual"),
+  exec: function (args, {environment}) {
+    let env = new HighlighterEnvironment();
+    env.initFromWindow(environment.window);
+    let eyeDropper = new EyeDropper(env);
+
+    eyeDropper.show(environment.document.documentElement, {copyOnSelect: true});
+
+    eyeDropper.once("hidden", () => {
+      eyeDropper.destroy();
+      env.destroy();
+    });
+  }
 }];
--- a/devtools/client/inspector/inspector-panel.js
+++ b/devtools/client/inspector/inspector-panel.js
@@ -94,26 +94,18 @@ function InspectorPanel(iframeWindow, to
 
   this._onBeforeNavigate = this._onBeforeNavigate.bind(this);
   this.onNewRoot = this.onNewRoot.bind(this);
   this._onContextMenu = this._onContextMenu.bind(this);
   this._updateSearchResultsLabel = this._updateSearchResultsLabel.bind(this);
   this.onNewSelection = this.onNewSelection.bind(this);
   this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this);
   this.onDetached = this.onDetached.bind(this);
-  this.onPaneToggleButtonClicked = this.onPaneToggleButtonClicked.bind(this);
   this._onMarkupFrameLoad = this._onMarkupFrameLoad.bind(this);
 
-  let doc = this.panelDoc;
-
-  // Handle 'Add Node' toolbar button.
-  this.addNode = this.addNode.bind(this);
-  this.addNodeButton = doc.getElementById("inspector-element-add-button");
-  this.addNodeButton.addEventListener("click", this.addNode);
-
   this._target.on("will-navigate", this._onBeforeNavigate);
   this._detectingActorFeatures = this._detectActorFeatures();
 
   EventEmitter.decorate(this);
 }
 
 exports.InspectorPanel = InspectorPanel;
 
@@ -238,16 +230,17 @@ InspectorPanel.prototype = {
       this.markup.expandNode(this.selection.nodeFront);
 
       this.emit("ready");
       deferred.resolve(this);
     });
 
     this.setupSearchBox();
     this.setupSidebar();
+    this.setupToolbar();
 
     return deferred.promise;
   },
 
   _onBeforeNavigate: function () {
     this._defaultNode = null;
     this.selection.setNodeFront(null);
     this._destroyMarkup();
@@ -430,36 +423,70 @@ InspectorPanel.prototype = {
 
     if (Services.prefs.getBoolPref("devtools.fontinspector.enabled") &&
         this.canGetUsedFontFaces) {
       this.fontInspector = new FontInspector(this, this.panelWin);
       this.sidebar.toggleTab(true, "fontinspector");
     }
 
     this.sidebar.show(defaultTab);
-
-    this.setupSidebarToggle();
   },
 
-  /**
-   * Add the expand/collapse behavior for the sidebar panel.
-   */
-  setupSidebarToggle: function () {
+  setupToolbar: function () {
+    // Setup the sidebar toggle button.
+    this.onPaneToggleButtonClicked = this.onPaneToggleButtonClicked.bind(this);
+
     let SidebarToggle = this.React.createFactory(this.browserRequire(
       "devtools/client/shared/components/sidebar-toggle"));
 
     let sidebarToggle = SidebarToggle({
       onClick: this.onPaneToggleButtonClicked,
       collapsed: false,
       expandPaneTitle: strings.GetStringFromName("inspector.expandPane"),
       collapsePaneTitle: strings.GetStringFromName("inspector.collapsePane"),
     });
 
     let parentBox = this.panelDoc.getElementById("inspector-sidebar-toggle-box");
     this._sidebarToggle = this.ReactDOM.render(sidebarToggle, parentBox);
+
+    // Setup the add-node button.
+    this.addNode = this.addNode.bind(this);
+    this.addNodeButton = this.panelDoc.getElementById("inspector-element-add-button");
+    this.addNodeButton.addEventListener("click", this.addNode);
+
+    // Setup the eye-dropper icon.
+    this.toolbox.target.actorHasMethod("inspector", "pickColorFromPage").then(value => {
+      if (!value) {
+        return;
+      }
+
+      this.onEyeDropperDone = this.onEyeDropperDone.bind(this);
+      this.onEyeDropperButtonClicked = this.onEyeDropperButtonClicked.bind(this);
+      this.eyeDropperButton = this.panelDoc.getElementById("inspector-eyedropper-toggle");
+      this.eyeDropperButton.style.display = "initial";
+      this.eyeDropperButton.addEventListener("click", this.onEyeDropperButtonClicked);
+    }, e => console.error(e));
+  },
+
+  teardownToolbar: function () {
+    if (this.paneToggleButton) {
+      this.paneToggleButton.removeEventListener("mousedown",
+        this.onPaneToggleButtonClicked);
+      this.paneToggleButton = null;
+    }
+
+    if (this.addNodeButton) {
+      this.addNodeButton.removeEventListener("click", this.addNode);
+      this.addNodeButton = null;
+    }
+
+    if (this.eyeDropperButton) {
+      this.eyeDropperButton.removeEventListener("click", this.onEyeDropperButtonClicked);
+      this.eyeDropperButton = null;
+    }
   },
 
   /**
    * Reset the inspector on new root mutation.
    */
   onNewRoot: function () {
     this._defaultNode = null;
     this.selection.setNodeFront(null);
@@ -701,17 +728,17 @@ InspectorPanel.prototype = {
         front.destroy();
       }
     });
 
     this.sidebar.off("select", this._setDefaultSidebar);
     let sidebarDestroyer = this.sidebar.destroy();
     this.sidebar = null;
 
-    this.addNodeButton.removeEventListener("click", this.addNode);
+    this.teardownToolbar();
     this.breadcrumbs.destroy();
     this.selection.off("new-node-front", this.onNewSelection);
     this.selection.off("before-new-node", this.onBeforeNewSelection);
     this.selection.off("before-new-node-front", this.onBeforeNewSelection);
     this.selection.off("detached-front", this.onDetached);
     let markupDestroyer = this._destroyMarkup();
     this.panelWin.inspector = null;
     this.target = null;
@@ -1181,16 +1208,51 @@ InspectorPanel.prototype = {
 
     if (isVisible) {
       this._sidebarToggle.setState({collapsed: true});
     } else {
       this._sidebarToggle.setState({collapsed: false});
     }
   },
 
+  onEyeDropperButtonClicked: function () {
+    this.eyeDropperButton.hasAttribute("checked")
+      ? this.hideEyeDropper()
+      : this.showEyeDropper();
+  },
+
+  startEyeDropperListeners: function () {
+    this.inspector.once("color-pick-canceled", this.onEyeDropperDone);
+    this.inspector.once("color-picked", this.onEyeDropperDone);
+    this.walker.once("new-root", this.onEyeDropperDone);
+  },
+
+  stopEyeDropperListeners: function () {
+    this.inspector.off("color-pick-canceled", this.onEyeDropperDone);
+    this.inspector.off("color-picked", this.onEyeDropperDone);
+    this.walker.off("new-root", this.onEyeDropperDone);
+  },
+
+  onEyeDropperDone: function () {
+    this.eyeDropperButton.removeAttribute("checked");
+    this.stopEyeDropperListeners();
+  },
+
+  showEyeDropper: function () {
+    this.eyeDropperButton.setAttribute("checked", "true");
+    this.inspector.pickColorFromPage({copyOnSelect: true}).catch(e => console.error(e));
+    this.startEyeDropperListeners();
+  },
+
+  hideEyeDropper: function () {
+    this.eyeDropperButton.removeAttribute("checked");
+    this.inspector.cancelPickColorFromPage().catch(e => console.error(e));
+    this.stopEyeDropperListeners();
+  },
+
   /**
    * Create a new node as the last child of the current selection, expand the
    * parent and select the new node.
    */
   addNode: Task.async(function* () {
     if (!this.canAddHTMLChild()) {
       return;
     }
--- a/devtools/client/inspector/inspector.xul
+++ b/devtools/client/inspector/inspector.xul
@@ -35,18 +35,21 @@
           class="devtools-button" />
         <html:div class="devtools-toolbar-spacer" />
         <html:span id="inspector-searchlabel" />
         <textbox id="inspector-searchbox"
           type="search"
           timeout="50"
           class="devtools-searchinput"
           placeholder="&inspectorSearchHTML.label3;"/>
+        <html:button id="inspector-eyedropper-toggle"
+          title="&inspectorEyeDropper.label;"
+          class="devtools-button command-button-invertable" />
         <div xmlns="http://www.w3.org/1999/xhtml"
-             id="inspector-sidebar-toggle-box" />
+          id="inspector-sidebar-toggle-box" />
       </html:div>
       <vbox flex="1" id="markup-box">
       </vbox>
       <html:div id="inspector-breadcrumbs-toolbar" class="devtools-toolbar">
         <html:div id="inspector-breadcrumbs" class="breadcrumbs-widget-container"/>
       </html:div>
     </vbox>
     <splitter class="devtools-side-splitter"/>
--- a/devtools/client/locales/en-US/inspector.dtd
+++ b/devtools/client/locales/en-US/inspector.dtd
@@ -10,8 +10,13 @@
      shown as the placeholder for the markup view search in the inspector. -->
 <!ENTITY inspectorSearchHTML.label3 "Search HTML">
 
 <!-- LOCALIZATION NOTE (inspectorAddNode.label): This is the label shown in
      the inspector toolbar for the button that lets users add elements to the
      DOM (as children of the currently selected element). -->
 <!ENTITY inspectorAddNode.label       "Create New Node">
 <!ENTITY inspectorAddNode.accesskey   "C">
+
+
+<!-- LOCALIZATION NOTE (inspectorEyeDropper.label): A string displayed as the tooltip of
+     a button in the inspector which toggles the Eyedropper tool -->
+<!ENTITY inspectorEyeDropper.label       "Grab a color from the page">
\ No newline at end of file
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -25,30 +25,29 @@ pref("devtools.toolbar.visible", false);
 pref("devtools.webide.enabled", true);
 
 // Toolbox preferences
 pref("devtools.toolbox.footer.height", 250);
 pref("devtools.toolbox.sidebar.width", 500);
 pref("devtools.toolbox.host", "bottom");
 pref("devtools.toolbox.previousHost", "side");
 pref("devtools.toolbox.selectedTool", "webconsole");
-pref("devtools.toolbox.toolbarSpec", '["splitconsole", "paintflashing toggle","scratchpad","resize toggle","eyedropper","screenshot --fullpage", "rulers", "measure"]');
+pref("devtools.toolbox.toolbarSpec", '["splitconsole", "paintflashing toggle","scratchpad","resize toggle","screenshot --fullpage", "rulers", "measure"]');
 pref("devtools.toolbox.sideEnabled", true);
 pref("devtools.toolbox.zoomValue", "1");
 pref("devtools.toolbox.splitconsoleEnabled", false);
 pref("devtools.toolbox.splitconsoleHeight", 100);
 
 // Toolbox Button preferences
 pref("devtools.command-button-pick.enabled", true);
 pref("devtools.command-button-frames.enabled", true);
 pref("devtools.command-button-splitconsole.enabled", true);
 pref("devtools.command-button-paintflashing.enabled", false);
 pref("devtools.command-button-scratchpad.enabled", false);
 pref("devtools.command-button-responsive.enabled", true);
-pref("devtools.command-button-eyedropper.enabled", false);
 pref("devtools.command-button-screenshot.enabled", false);
 pref("devtools.command-button-rulers.enabled", false);
 pref("devtools.command-button-measure.enabled", false);
 pref("devtools.command-button-noautohide.enabled", false);
 
 // Inspector preferences
 // Enable the Inspector
 pref("devtools.inspector.enabled", true);
--- a/devtools/client/shared/test/browser.ini
+++ b/devtools/client/shared/test/browser.ini
@@ -146,17 +146,16 @@ skip-if = e10s # Test intermittently fai
 [browser_poller.js]
 [browser_prefs-01.js]
 [browser_prefs-02.js]
 [browser_spectrum.js]
 [browser_theme.js]
 [browser_tableWidget_basic.js]
 [browser_tableWidget_keyboard_interaction.js]
 [browser_tableWidget_mouse_interaction.js]
-[browser_telemetry_button_eyedropper.js]
 [browser_telemetry_button_paintflashing.js]
 skip-if = e10s # Bug 937167 - e10s paintflashing
 [browser_telemetry_button_responsive.js]
 skip-if = e10s # Bug 1067145 - e10s responsiveview
 [browser_telemetry_button_scratchpad.js]
 [browser_telemetry_sidebar.js]
 [browser_telemetry_toolbox.js]
 [browser_telemetry_toolboxtabs_canvasdebugger.js]
deleted file mode 100644
--- a/devtools/client/shared/test/browser_telemetry_button_eyedropper.js
+++ /dev/null
@@ -1,61 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-const TEST_URI = "data:text/html;charset=utf-8," +
-  "<p>browser_telemetry_button_eyedropper.js</p><div>test</div>";
-
-var {EyedropperManager} = require("devtools/client/eyedropper/eyedropper");
-
-add_task(function* () {
-  yield addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
-
-  let target = TargetFactory.forTab(gBrowser.selectedTab);
-  let toolbox = yield gDevTools.showToolbox(target, "inspector");
-  info("inspector opened");
-
-  info("testing the eyedropper button");
-  yield testButton(toolbox, Telemetry);
-
-  stopRecordingTelemetryLogs(Telemetry);
-  yield gDevTools.closeToolbox(target);
-  gBrowser.removeCurrentTab();
-});
-
-function* testButton(toolbox, Telemetry) {
-  let button = toolbox.doc.querySelector("#command-button-eyedropper");
-  ok(button, "Captain, we have the eyedropper button");
-
-  let clicked = toolbox._requisition.commandOutputManager.onOutput.once();
-
-  info("clicking the button to open the eyedropper");
-  button.click();
-
-  yield clicked;
-
-  checkResults("_EYEDROPPER_", Telemetry);
-}
-
-function checkResults(histIdFocus, Telemetry) {
-  let result = Telemetry.prototype.telemetryInfo;
-
-  for (let [histId, value] of Iterator(result)) {
-    if (histId.startsWith("DEVTOOLS_INSPECTOR_") ||
-        !histId.includes(histIdFocus)) {
-      // Inspector stats are tested in
-      // browser_telemetry_toolboxtabs_{toolname}.js so we skip them here
-      // because we only open the inspector once for this test.
-      continue;
-    }
-
-    if (histId.endsWith("OPENED_PER_USER_FLAG")) {
-      ok(value.length === 1 && value[0] === true,
-         "Per user value " + histId + " has a single value of true");
-    } else if (histId.endsWith("OPENED_COUNT")) {
-      is(value.length, 1, histId + " has one entry");
-
-      let okay = value.every(element => element === true);
-      ok(okay, "All " + histId + " entries are === true");
-    }
-  }
-}
--- a/devtools/client/themes/inspector.css
+++ b/devtools/client/themes/inspector.css
@@ -73,16 +73,27 @@
 }
 
 #inspector-breadcrumbs .breadcrumbs-widget-item {
   white-space: nowrap;
   flex-shrink: 0;
   font: message-box;
 }
 
+/* Eyedropper toolbar button */
+
+#inspector-eyedropper-toggle {
+  /* hidden by default, until we can check that the required highlighter exists */
+  display: none;
+}
+
+#inspector-eyedropper-toggle::before {
+  background-image: var(--command-eyedropper-image);
+}
+
 /* Add element toolbar button */
 
 #inspector-element-add-button::before {
   background-image: url("chrome://devtools/skin/images/add.svg");
 }
 
 /* "no results" warning message displayed in the ruleview and in the computed view */
 
--- a/devtools/server/actors/highlighters/eye-dropper.js
+++ b/devtools/server/actors/highlighters/eye-dropper.js
@@ -173,18 +173,16 @@ EyeDropper.prototype = {
     pageListenerTarget.removeEventListener("mousemove", this);
     pageListenerTarget.removeEventListener("click", this);
     pageListenerTarget.removeEventListener("keydown", this);
     pageListenerTarget.removeEventListener("DOMMouseScroll", this);
     pageListenerTarget.removeEventListener("FullZoomChange", this);
 
     this.getElement("root").setAttribute("hidden", "true");
     this.getElement("root").removeAttribute("drawn");
-
-    this.emit("hidden");
   },
 
   prepareImageCapture() {
     // Get the page as an image.
     let imageData = getWindowAsImageData(this.win);
     let image = new this.win.Image();
     image.src = imageData;
 
--- a/devtools/shared/locales/en-US/gclicommands.properties
+++ b/devtools/shared/locales/en-US/gclicommands.properties
@@ -305,20 +305,16 @@ inspectNodeManual=A CSS selector for use
 # string is designed to be shown in a menu alongside the command name, which
 # is why it should be as short as possible.
 eyedropperDesc=Grab a color from the page
 
 # LOCALIZATION NOTE (eyedropperManual) A fuller description of the 'eyedropper'
 # command, displayed when the user asks for help on what it does.
 eyedropperManual=Open a panel that magnifies an area of page to inspect pixels and copy color values
 
-# LOCALIZATION NOTE (eyedropperTooltip) A string displayed as the
-# tooltip of button in devtools toolbox which toggles the Eyedropper tool.
-eyedropperTooltip=Grab a color from the page
-
 # LOCALIZATION NOTE (debuggerClosed) Used in the output of several commands
 # to explain that the debugger must be opened first.
 debuggerClosed=The debugger must be opened before using this command
 
 # LOCALIZATION NOTE (debuggerStopped) Used in the output of several commands
 # to explain that the debugger must be opened first before setting breakpoints.
 debuggerStopped=The debugger must be opened before setting breakpoints