Bug 962531 - Page Up and Page Down should always scroll the output area in Console; r=msucan
authorRob Campbell <rcampbell@mozilla.com>
Sat, 01 Feb 2014 08:22:45 -0500
changeset 166453 5becc145339321827983e5612d5aa488b9517be1
parent 166452 41ac71fd705e126e1e1f4c2323db8245bf0be4db
child 166454 9f1c9cf5040f917d2c2f866f3f743ab10edfd5a3
push id26127
push userphilringnalda@gmail.com
push dateSun, 02 Feb 2014 17:11:12 +0000
treeherdermozilla-central@2918a9e625b4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmsucan
bugs962531
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 962531 - Page Up and Page Down should always scroll the output area in Console; r=msucan
browser/devtools/shared/autocomplete-popup.js
browser/devtools/webconsole/test/browser_console_keyboard_accessibility.js
browser/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_keys.js
browser/devtools/webconsole/webconsole.js
--- a/browser/devtools/shared/autocomplete-popup.js
+++ b/browser/devtools/shared/autocomplete-popup.js
@@ -449,16 +449,27 @@ AutocompletePopup.prototype = {
    * Getter for the number of items in the popup.
    * @type number
    */
   get itemCount() {
     return this._list.childNodes.length;
   },
 
   /**
+   * Getter for the height of each item in the list.
+   *
+   * @private
+   *
+   * @type number
+   */
+  get _itemHeight() {
+    return this._list.selectedItem.clientHeight;
+  },
+
+  /**
    * Select the next item in the list.
    *
    * @return object
    *         The newly selected item object.
    */
   selectNextItem: function AP_selectNextItem()
   {
     if (this.selectedIndex < (this.itemCount - 1)) {
@@ -470,31 +481,64 @@ AutocompletePopup.prototype = {
 
     return this.selectedItem;
   },
 
   /**
    * Select the previous item in the list.
    *
    * @return object
-   *         The newly selected item object.
+   *         The newly-selected item object.
    */
   selectPreviousItem: function AP_selectPreviousItem()
   {
     if (this.selectedIndex > 0) {
       this.selectedIndex--;
     }
     else {
       this.selectedIndex = this.itemCount - 1;
     }
 
     return this.selectedItem;
   },
 
   /**
+   * Select the top-most item in the next page of items or
+   * the last item in the list.
+   *
+   * @return object
+   *         The newly-selected item object.
+   */
+  selectNextPageItem: function AP_selectNextPageItem()
+  {
+    let itemsPerPane = Math.floor(this._list.scrollHeight / this._itemHeight);
+    let nextPageIndex = this.selectedIndex + itemsPerPane + 1;
+    this.selectedIndex = nextPageIndex > this.itemCount - 1 ?
+      this.itemCount - 1 : nextPageIndex;
+
+    return this.selectedItem;
+  },
+
+  /**
+   * Select the bottom-most item in the previous page of items,
+   * or the first item in the list.
+   *
+   * @return object
+   *         The newly-selected item object.
+   */
+  selectPreviousPageItem: function AP_selectPreviousPageItem()
+  {
+    let itemsPerPane = Math.floor(this._list.scrollHeight / this._itemHeight);
+    let prevPageIndex = this.selectedIndex - itemsPerPane - 1;
+    this.selectedIndex = prevPageIndex < 0 ? 0 : prevPageIndex;
+
+    return this.selectedItem;
+  },
+
+  /**
    * Focuses the richlistbox.
    */
   focus: function AP_focus()
   {
     this._list.focus();
   },
 
   /**
--- a/browser/devtools/webconsole/test/browser_console_keyboard_accessibility.js
+++ b/browser/devtools/webconsole/test/browser_console_keyboard_accessibility.js
@@ -17,29 +17,39 @@ function test()
     openConsole(null, consoleOpened);
   }, true);
 
   function consoleOpened(aHud)
   {
     hud = aHud;
     ok(hud, "Web Console opened");
 
-    content.console.log("foobarz1");
+    info("dump some spew into the console for scrolling");
+    for (let i = 0; i < 100; i++)
+      content.console.log("foobarz" + i);
     waitForMessages({
       webconsole: hud,
       messages: [{
-        text: "foobarz1",
+        text: "foobarz99",
         category: CATEGORY_WEBDEV,
         severity: SEVERITY_LOG,
       }],
     }).then(onConsoleMessage);
   }
 
   function onConsoleMessage()
   {
+    let currentPosition = hud.outputNode.parentNode.scrollTop;
+    EventUtils.synthesizeKey("VK_PAGE_UP", {});
+    isnot(hud.outputNode.parentNode.scrollTop, currentPosition, "scroll position changed after page up");
+
+    currentPosition = hud.outputNode.parentNode.scrollTop;
+    EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
+    ok(hud.outputNode.parentNode.scrollTop > currentPosition, "scroll position now at bottom");
+
     hud.jsterm.once("messages-cleared", onClear);
     info("try ctrl-l to clear output");
     EventUtils.synthesizeKey("l", { ctrlKey: true });
   }
 
   function onClear()
   {
     is(hud.outputNode.textContent.indexOf("foobarz1"), -1, "output cleared");
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_keys.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_keys.js
@@ -86,16 +86,28 @@ function consoleOpened(aHud) {
 
     EventUtils.synthesizeKey("VK_UP", {});
 
     is(popup.selectedIndex, 0, "index 0 is selected");
     is(popup.selectedItem.label, "watch", "watch is selected");
     is(completeNode.value, prefix + "watch",
         "completeNode.value holds watch");
 
+    let currentSelectionIndex = popup.selectedIndex;
+
+    EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
+
+    ok(popup.selectedIndex > currentSelectionIndex,
+      "Index is greater after PGDN");
+
+    currentSelectionIndex = popup.selectedIndex;
+    EventUtils.synthesizeKey("VK_PAGE_UP", {});
+
+    ok(popup.selectedIndex < currentSelectionIndex, "Index is less after Page UP");
+
     info("press Tab and wait for popup to hide");
     popup._panel.addEventListener("popuphidden", popupHideAfterTab, false);
     EventUtils.synthesizeKey("VK_TAB", {});
   }, false);
 
   info("wait for completion: window.foobarBug585991.");
   jsterm.setInputValue("window.foobarBug585991");
   EventUtils.synthesizeKey(".", {});
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -2656,18 +2656,16 @@ WebConsoleFrame.prototype = {
       if (aEvent.detail != 1 || aEvent.button != 0) {
         return;
       }
 
       aEvent.preventDefault();
 
       // If this event started with a mousedown event and it ends at a different
       // location, we consider this text selection.
-      // Add a fuzz modifier of two pixels in any direction to account for sloppy
-      // clicking.
       if (mousedown &&
           (this._startX != aEvent.clientX) &&
           (this._startY != aEvent.clientY))
       {
         this._startX = this._startY = undefined;
         return;
       }
 
@@ -3058,16 +3056,18 @@ JSTerm.prototype = {
    * Getter for the debugger WebConsoleClient.
    * @type object
    */
   get webConsoleClient() this.hud.webConsoleClient,
 
   COMPLETE_FORWARD: 0,
   COMPLETE_BACKWARD: 1,
   COMPLETE_HINT_ONLY: 2,
+  COMPLETE_PAGEUP: 3,
+  COMPLETE_PAGEDOWN: 4,
 
   /**
    * Initialize the JSTerminal UI.
    */
   init: function JST_init()
   {
     let autocompleteOptions = {
       onSelect: this.onAutocompleteSelect.bind(this),
@@ -3919,16 +3919,50 @@ JSTerm.prototype = {
         else if (this.canCaretGoNext()) {
           inputUpdated = this.historyPeruse(HISTORY_FORWARD);
         }
         if (inputUpdated) {
           aEvent.preventDefault();
         }
         break;
 
+      case Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP:
+        if (this.autocompletePopup.isOpen) {
+          inputUpdated = this.complete(this.COMPLETE_PAGEUP);
+          if (inputUpdated) {
+            this._autocompletePopupNavigated = true;
+          }
+        }
+        else {
+          this.hud.outputNode.parentNode.scrollTop =
+            Math.max(0,
+              this.hud.outputNode.parentNode.scrollTop -
+              this.hud.outputNode.parentNode.clientHeight
+            );
+        }
+        aEvent.preventDefault();
+        break;
+
+      case Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN:
+        if (this.autocompletePopup.isOpen) {
+          inputUpdated = this.complete(this.COMPLETE_PAGEDOWN);
+          if (inputUpdated) {
+            this._autocompletePopupNavigated = true;
+          }
+        }
+        else {
+          this.hud.outputNode.parentNode.scrollTop =
+            Math.min(this.hud.outputNode.parentNode.scrollHeight,
+              this.hud.outputNode.parentNode.scrollTop +
+              this.hud.outputNode.parentNode.clientHeight
+            );
+        }
+        aEvent.preventDefault();
+        break;
+
       case Ci.nsIDOMKeyEvent.DOM_VK_HOME:
       case Ci.nsIDOMKeyEvent.DOM_VK_END:
       case Ci.nsIDOMKeyEvent.DOM_VK_LEFT:
         if (this.autocompletePopup.isOpen || this.lastCompletion.value) {
           this.clearCompletion();
         }
         break;
 
@@ -4090,16 +4124,20 @@ JSTerm.prototype = {
    *          completions is used. If the value changed, then the first possible
    *          completion is used and the selection is set from the current
    *          cursor position to the end of the completed text.
    *          If there is only one possible completion, then this completion
    *          value is used and the cursor is put at the end of the completion.
    *    - this.COMPLETE_BACKWARD: Same as this.COMPLETE_FORWARD but if the
    *          value stayed the same as the last time the function was called,
    *          then the previous completion of all possible completions is used.
+   *    - this.COMPLETE_PAGEUP: Scroll up one page if available or select the first
+   *          item.
+   *    - this.COMPLETE_PAGEDOWN: Scroll down one page if available or select the
+   *          last item.
    *    - this.COMPLETE_HINT_ONLY: If there is more than one possible
    *          completion and the input value stayed the same compared to the
    *          last time this function was called, then the same completion is
    *          used again. If there is only one possible completion, then
    *          the inputNode.value is set to this value and the selection is set
    *          from the current cursor position to the end of the completed text.
    * @param function aCallback
    *        Optional function invoked when the autocomplete properties are
@@ -4143,16 +4181,22 @@ JSTerm.prototype = {
       accepted = true;
     }
     else if (aType == this.COMPLETE_BACKWARD) {
       popup.selectPreviousItem();
     }
     else if (aType == this.COMPLETE_FORWARD) {
       popup.selectNextItem();
     }
+    else if (aType == this.COMPLETE_PAGEUP) {
+      popup.selectPreviousPageItem();
+    }
+    else if (aType == this.COMPLETE_PAGEDOWN) {
+      popup.selectNextPageItem();
+    }
 
     aCallback && aCallback(this);
     this.emit("autocomplete-updated");
     return accepted || popup.itemCount > 0;
   },
 
   /**
    * Update the completion result. This operation is performed asynchronously by