Bug 944640 - Add a "Watch" button on the new inspect popup. r=vporof
authorLalit Khattar <luckyk1592@gmail.com>
Fri, 31 Jan 2014 13:01:27 -0500
changeset 182365 fb9b26afff458a0f606546998acca71a0036596f
parent 182364 a4b73c79ac449d4a49404ff9fe4a3d9de8017507
child 182366 29a1843b59863c87f420e2d3f73a9598843a0a30
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvporof
bugs944640
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 944640 - Add a "Watch" button on the new inspect popup. r=vporof
browser/devtools/debugger/debugger-panes.js
browser/devtools/debugger/test/browser.ini
browser/devtools/debugger/test/browser_dbg_variables-view-popup-11.js
browser/devtools/debugger/test/browser_dbg_variables-view-popup-12.js
browser/devtools/debugger/test/doc_watch-expression-button.html
browser/devtools/shared/widgets/Tooltip.js
browser/locales/en-US/chrome/browser/devtools/debugger.properties
browser/themes/shared/devtools/debugger.inc.css
--- a/browser/devtools/debugger/debugger-panes.js
+++ b/browser/devtools/debugger/debugger-panes.js
@@ -1867,17 +1867,24 @@ VariableBubbleView.prototype = {
     // machinery to display it.
     if (VariablesView.isPrimitive({ value: objectActor })) {
       let className = VariablesView.getClass(objectActor);
       let textContent = VariablesView.getString(objectActor);
       this._tooltip.setTextContent({
         messages: [textContent],
         messagesClass: className,
         containerClass: "plain"
-      });
+      }, [{
+        label: L10N.getStr('addWatchExpressionButton'),
+        className: "dbg-expression-button",
+        command: () => {
+          DebuggerView.VariableBubble.hideContents();
+          DebuggerView.WatchExpressions.addExpression(evalPrefix, true);
+        }
+      }]);
     } else {
       this._tooltip.setVariableContent(objectActor, {
         searchPlaceholder: L10N.getStr("emptyPropertiesFilterText"),
         searchEnabled: Prefs.variablesSearchboxVisible,
         eval: (variable, value) => {
           let string = variable.evaluationMacro(variable, value);
           DebuggerController.StackFrames.evaluate(string);
           DebuggerView.VariableBubble.hideContents();
@@ -1889,17 +1896,24 @@ VariableBubbleView.prototype = {
         getterOrSetterEvalMacro: this._getGetterOrSetterEvalMacro(evalPrefix),
         overrideValueEvalMacro: this._getOverrideValueEvalMacro(evalPrefix)
       }, {
         fetched: (aEvent, aType) => {
           if (aType == "properties") {
             window.emit(EVENTS.FETCHED_BUBBLE_PROPERTIES);
           }
         }
-      });
+      }, [{
+        label: L10N.getStr("addWatchExpressionButton"),
+        className: "dbg-expression-button",
+        command: () => {
+          DebuggerView.VariableBubble.hideContents();
+          DebuggerView.WatchExpressions.addExpression(evalPrefix, true);
+        }
+      }]);
     }
 
     this._tooltip.show(this._markedText.anchor);
   },
 
   /**
    * Hides the inspection popup.
    */
@@ -2026,38 +2040,49 @@ WatchExpressionsView.prototype = Heritag
     this.widget.removeEventListener("click", this._onClick, false);
   },
 
   /**
    * Adds a watch expression in this container.
    *
    * @param string aExpression [optional]
    *        An optional initial watch expression text.
+   * @param boolean aSkipUserInput [optional]
+   *        Pass true to avoid waiting for additional user input
+   *        on the watch expression.
    */
-  addExpression: function(aExpression = "") {
+  addExpression: function(aExpression = "", aSkipUserInput = false) {
     // Watch expressions are UI elements which benefit from visible panes.
     DebuggerView.showInstrumentsPane();
 
     // Create the element node for the watch expression item.
     let itemView = this._createItemView(aExpression);
 
     // Append a watch expression item to this container.
     let expressionItem = this.push([itemView.container], {
       index: 0, /* specifies on which position should the item be appended */
       attachment: {
         view: itemView,
         initialExpression: aExpression,
         currentExpression: "",
       }
     });
 
-    // Automatically focus the new watch expression input.
-    expressionItem.attachment.view.inputNode.select();
-    expressionItem.attachment.view.inputNode.focus();
-    DebuggerView.Variables.parentNode.scrollTop = 0;
+    // Automatically focus the new watch expression input
+    // if additional user input is desired.
+    if (!aSkipUserInput) {
+      expressionItem.attachment.view.inputNode.select();
+      expressionItem.attachment.view.inputNode.focus();
+      DebuggerView.Variables.parentNode.scrollTop = 0;
+    }
+    // Otherwise, add and evaluate the new watch expression immediately.
+    else {
+      this.toggleContents(false);
+      this._onBlur({ target: expressionItem.attachment.view.inputNode });
+    }
   },
 
   /**
    * Changes the watch expression corresponding to the specified variable item.
    * This function is called whenever a watch expression's code is edited in
    * the variables view container.
    *
    * @param Variable aVar
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -59,16 +59,17 @@ support-files =
   doc_scope-variable.html
   doc_scope-variable-2.html
   doc_scope-variable-3.html
   doc_script-switching-01.html
   doc_script-switching-02.html
   doc_step-out.html
   doc_tracing-01.html
   doc_watch-expressions.html
+  doc_watch-expression-button.html
   doc_with-frame.html
   head.js
   sjs_random-javascript.sjs
   testactors.js
 
 [browser_dbg_aaa_run_first_leaktest.js]
 [browser_dbg_auto-pretty-print-01.js]
 [browser_dbg_auto-pretty-print-02.js]
@@ -239,16 +240,18 @@ support-files =
 [browser_dbg_variables-view-popup-03.js]
 [browser_dbg_variables-view-popup-04.js]
 [browser_dbg_variables-view-popup-05.js]
 [browser_dbg_variables-view-popup-06.js]
 [browser_dbg_variables-view-popup-07.js]
 [browser_dbg_variables-view-popup-08.js]
 [browser_dbg_variables-view-popup-09.js]
 [browser_dbg_variables-view-popup-10.js]
+[browser_dbg_variables-view-popup-11.js]
+[browser_dbg_variables-view-popup-12.js]
 [browser_dbg_variables-view-reexpand-01.js]
 [browser_dbg_variables-view-reexpand-02.js]
 [browser_dbg_variables-view-webidl.js]
 [browser_dbg_watch-expressions-01.js]
 [browser_dbg_watch-expressions-02.js]
 [browser_dbg_chrome-create.js]
 skip-if = os == "linux" # Bug 847558
 [browser_dbg_on-pause-raise.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_variables-view-popup-11.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the watch expression button is added in variable view popup.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_watch-expression-button.html";
+
+function test() {
+  Task.spawn(function() {
+    let [tab, debuggee, panel] = yield initDebugger(TAB_URL);
+    let win = panel.panelWin;
+    let events = win.EVENTS;
+    let watch = win.DebuggerView.WatchExpressions;
+    let bubble = win.DebuggerView.VariableBubble;
+    let tooltip = bubble._tooltip.panel;
+
+    let label = win.L10N.getStr("addWatchExpressionButton");
+    let className = "dbg-expression-button";
+
+    function testExpressionButton(aLabel, aClassName, aExpression) {
+      ok(tooltip.querySelector("button"),
+        "There should be a button available in variable view popup.");
+      is(tooltip.querySelector("button").label, aLabel,
+        "The button available is labeled correctly.");
+      is(tooltip.querySelector("button").className, aClassName,
+        "The button available is styled correctly.");
+
+      tooltip.querySelector("button").click();
+
+      ok(!tooltip.querySelector("button"),
+        "There should be no button available in variable view popup.");
+      ok(watch.getItemAtIndex(0),
+        "The expression at index 0 should be available.");
+      is(watch.getItemAtIndex(0).attachment.initialExpression, aExpression,
+        "The expression at index 0 is correct.");
+    }
+
+    // Allow this generator function to yield first.
+    executeSoon(() => debuggee.start());
+    yield waitForSourceAndCaretAndScopes(panel, ".html", 19);
+
+    // Inspect primitive value variable.
+    yield openVarPopup(panel, { line: 15, ch: 12 });
+    let popupHiding = once(tooltip, "popuphiding");
+    let expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+    testExpressionButton(label, className, "a");
+    yield promise.all([popupHiding, expressionsEvaluated]);
+    ok(true, "The new watch expressions were re-evaluated and the panel got hidden (1).");
+
+    // Inspect non primitive value variable.
+    let expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+    yield openVarPopup(panel, { line: 16, ch: 12 }, true);
+    yield expressionsEvaluated;
+    ok(true, "The watch expressions were re-evaluated when a new panel opened (1).");
+
+    let popupHiding = once(tooltip, "popuphiding");
+    let expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+    testExpressionButton(label, className, "b");
+    yield promise.all([popupHiding, expressionsEvaluated]);
+    ok(true, "The new watch expressions were re-evaluated and the panel got hidden (2).");
+
+    // Inspect property of an object.
+    let expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+    yield openVarPopup(panel, { line: 17, ch: 10 });
+    yield expressionsEvaluated;
+    ok(true, "The watch expressions were re-evaluated when a new panel opened (2).");
+
+    let popupHiding = once(tooltip, "popuphiding");
+    let expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+    testExpressionButton(label, className, "b.a");
+    yield promise.all([popupHiding, expressionsEvaluated]);
+    ok(true, "The new watch expressions were re-evaluated and the panel got hidden (3).");
+
+    yield resumeDebuggerThenCloseAndFinish(panel);
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_variables-view-popup-12.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the clicking "Watch" button twice, for the same expression, only adds it
+ * once.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_watch-expression-button.html";
+
+function test() {
+  Task.spawn(function() {
+    let [tab, debuggee, panel] = yield initDebugger(TAB_URL);
+    let win = panel.panelWin;
+    let events = win.EVENTS;
+    let watch = win.DebuggerView.WatchExpressions;
+    let bubble = win.DebuggerView.VariableBubble;
+    let tooltip = bubble._tooltip.panel;
+
+    function verifyContent(aExpression, aItemCount) {
+
+      ok(watch.getItemAtIndex(0),
+        "The expression at index 0 should be available.");
+      is(watch.getItemAtIndex(0).attachment.initialExpression, aExpression,
+        "The expression at index 0 is correct.");
+      is(watch.itemCount, aItemCount,
+        "The expression count is correct.");
+    }
+
+    // Allow this generator function to yield first.
+    executeSoon(() => debuggee.start());
+    yield waitForSourceAndCaretAndScopes(panel, ".html", 19);
+
+    // Inspect primitive value variable.
+    yield openVarPopup(panel, { line: 15, ch: 12 });
+    let popupHiding = once(tooltip, "popuphiding");
+    let expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+    tooltip.querySelector("button").click();
+    verifyContent("a", 1);
+    yield promise.all([popupHiding, expressionsEvaluated]);
+    ok(true, "The new watch expressions were re-evaluated and the panel got hidden (1).");
+
+    // Inspect property of an object.
+    let expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+    yield openVarPopup(panel, { line: 17, ch: 10 });
+    yield expressionsEvaluated;
+    ok(true, "The watch expressions were re-evaluated when a new panel opened (1).");
+
+    let popupHiding = once(tooltip, "popuphiding");
+    let expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+    tooltip.querySelector("button").click();
+    verifyContent("b.a", 2);
+    yield promise.all([popupHiding, expressionsEvaluated]);
+    ok(true, "The new watch expressions were re-evaluated and the panel got hidden (2).");
+
+    // Re-inspect primitive value variable.
+    let expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+    yield openVarPopup(panel, { line: 15, ch: 12 });
+    yield expressionsEvaluated;
+    ok(true, "The watch expressions were re-evaluated when a new panel opened (2).");
+
+    let popupHiding = once(tooltip, "popuphiding");
+    let expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+    tooltip.querySelector("button").click();
+    verifyContent("b.a", 2);
+    yield promise.all([popupHiding, expressionsEvaluated]);
+    ok(true, "The new watch expressions were re-evaluated and the panel got hidden (3).");
+
+    yield resumeDebuggerThenCloseAndFinish(panel);
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/doc_watch-expression-button.html
@@ -0,0 +1,31 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Debugger test page</title>
+  </head>
+
+  <body>
+    <button onclick="start()">Click me!</button>
+
+    <script type="text/javascript">
+      function test() {
+        var a = 1;
+        var b = { a: a };
+        b.a = 2;
+        debugger;
+      }
+
+      function start() {
+        var e  = eval('test();');
+      }
+
+      var button = document.querySelector("button");
+      var buttonAsProto = Object.create(button);
+    </script>
+  </body>
+
+</html>
--- a/browser/devtools/shared/widgets/Tooltip.js
+++ b/browser/devtools/shared/widgets/Tooltip.js
@@ -409,32 +409,47 @@ Tooltip.prototype = {
    *        A list of text messages.
    * @param {string} messagesClass [optional]
    *        A style class for the text messages.
    * @param {string} containerClass [optional]
    *        A style class for the text messages container.
    * @param {boolean} isAlertTooltip [optional]
    *        Pass true to add an alert image for your tooltip.
    */
-  setTextContent: function({ messages, messagesClass, containerClass, isAlertTooltip }) {
+  setTextContent: function(
+    {
+      messages,
+      messagesClass,
+      containerClass,
+      isAlertTooltip
+    },
+    extraButtons = []) {
     messagesClass = messagesClass || "default-tooltip-simple-text-colors";
     containerClass = containerClass || "default-tooltip-simple-text-colors";
 
     let vbox = this.doc.createElement("vbox");
     vbox.className = "devtools-tooltip-simple-text-container " + containerClass;
     vbox.setAttribute("flex", "1");
 
     for (let text of messages) {
       let description = this.doc.createElement("description");
       description.setAttribute("flex", "1");
       description.className = "devtools-tooltip-simple-text " + messagesClass;
       description.textContent = text;
       vbox.appendChild(description);
     }
 
+    for (let { label, className, command } of extraButtons) {
+      let button = this.doc.createElement("button");
+      button.className = className;
+      button.setAttribute("label", label);
+      button.addEventListener("command", command);
+      vbox.appendChild(button);
+    }
+
     if (isAlertTooltip) {
       let hbox = this.doc.createElement("hbox");
       hbox.setAttribute("align", "start");
 
       let alertImg = this.doc.createElement("image");
       alertImg.className = "devtools-tooltip-alert-icon";
       hbox.appendChild(alertImg);
       hbox.appendChild(vbox);
@@ -462,40 +477,42 @@ Tooltip.prototype = {
    *        Otherwise, if a variable was previously inspected, its widget
    *        will be reused.
    */
   setVariableContent: function(
     objectActor,
     viewOptions = {},
     controllerOptions = {},
     relayEvents = {},
-    reuseCachedWidget = true) {
+    extraButtons = []) {
 
-    if (reuseCachedWidget && this._cachedVariablesView) {
-      var [vbox, widget] = this._cachedVariablesView;
-    } else {
-      var vbox = this.doc.createElement("vbox");
-      vbox.className = "devtools-tooltip-variables-view-box";
-      vbox.setAttribute("flex", "1");
+    let vbox = this.doc.createElement("vbox");
+    vbox.className = "devtools-tooltip-variables-view-box";
+    vbox.setAttribute("flex", "1");
+
+    let innerbox = this.doc.createElement("vbox");
+    innerbox.className = "devtools-tooltip-variables-view-innerbox";
+    innerbox.setAttribute("flex", "1");
+    vbox.appendChild(innerbox);
 
-      let innerbox = this.doc.createElement("vbox");
-      innerbox.className = "devtools-tooltip-variables-view-innerbox";
-      innerbox.setAttribute("flex", "1");
-      vbox.appendChild(innerbox);
-
-      var widget = new VariablesView(innerbox, viewOptions);
+    for (let { label, className, command } of extraButtons) {
+      let button = this.doc.createElement("button");
+      button.className = className;
+      button.setAttribute("label", label);
+      button.addEventListener("command", command);
+      vbox.appendChild(button);
+    }
 
-      // Analyzing state history isn't useful with transient object inspectors.
-      widget.commitHierarchy = () => {};
+    let widget = new VariablesView(innerbox, viewOptions);
 
-      for (let e in relayEvents) widget.on(e, relayEvents[e]);
-      VariablesViewController.attach(widget, controllerOptions);
+    // Analyzing state history isn't useful with transient object inspectors.
+    widget.commitHierarchy = () => {};
 
-      this._cachedVariablesView = [vbox, widget];
-    }
+    for (let e in relayEvents) widget.on(e, relayEvents[e]);
+    VariablesViewController.attach(widget, controllerOptions);
 
     // Some of the view options are allowed to change between uses.
     widget.searchPlaceholder = viewOptions.searchPlaceholder;
     widget.searchEnabled = viewOptions.searchEnabled;
 
     // Use the object actor's grip to display it as a variable in the widget.
     // The controller options are allowed to change between uses.
     widget.controller.setSingleVariable(
--- a/browser/locales/en-US/chrome/browser/devtools/debugger.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/debugger.properties
@@ -209,16 +209,20 @@ loadingText=Loading\u2026
 # LOCALIZATION NOTE (errorLoadingText): The text that is displayed in the debugger
 # viewer when there is an error loading a file
 errorLoadingText=Error loading source:\n
 
 # LOCALIZATION NOTE (addWatchExpressionText): The text that is displayed in the
 # watch expressions list to add a new item.
 addWatchExpressionText=Add watch expression
 
+# LOCALIZATION NOTE (addWatchExpressionButton): The button that is displayed in the
+# variables view popup.
+addWatchExpressionButton=Watch
+
 # LOCALIZATION NOTE (emptyVariablesText): The text that is displayed in the
 # variables pane when there are no variables to display.
 emptyVariablesText=No variables to display
 
 # LOCALIZATION NOTE (scopeLabel): The text that is displayed in the variables
 # pane as a header for each variable scope (e.g. "Global scope, "With scope",
 # etc.).
 scopeLabel=%S scope
--- a/browser/themes/shared/devtools/debugger.inc.css
+++ b/browser/themes/shared/devtools/debugger.inc.css
@@ -285,16 +285,32 @@
   margin: 2px;
   background: -moz-image-rect(url(commandline-icon.png), 0, 32, 16, 16);
 }
 
 .dbg-expression-input {
   color: inherit;
 }
 
+.dbg-expression-button {
+  -moz-appearance: none;
+  border: none;
+  background: none;
+  cursor: pointer;
+  text-decoration: underline;
+}
+
+.theme-dark .dbg-expression-button {
+  color: #46afe3; /* Blue highlight color */
+}
+
+.theme-light .dbg-expression-button {
+  color: #0088cc; /* Blue highlight color */
+}
+
 /* Event listeners view */
 
 .dbg-event-listener-type {
   font-weight: 600;
 }
 
 .theme-dark .dbg-event-listener-location {
   color: #b8c8d9; /* Light content text */