Bug 1068852 - Highlight search suggestions on hover/mouseover on about:home/about:newtab. r=MattN, a=sylvestre
authorDrew Willcoxon <adw@mozilla.com>
Fri, 19 Sep 2014 09:35:06 -0700
changeset 216798 a34329afda87
parent 216793 7cd3ae0255ec
child 216799 fa3e1469d0f1
push id3917
push userdwillcoxon@mozilla.com
push date2014-09-19 16:35 +0000
treeherdermozilla-beta@a34329afda87 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN, sylvestre
bugs1068852
milestone33.0
Bug 1068852 - Highlight search suggestions on hover/mouseover on about:home/about:newtab. r=MattN, a=sylvestre
browser/base/content/searchSuggestionUI.js
browser/base/content/test/general/browser_searchSuggestionUI.js
browser/base/content/test/general/searchSuggestionUI.js
--- a/browser/base/content/searchSuggestionUI.js
+++ b/browser/base/content/searchSuggestionUI.js
@@ -91,46 +91,46 @@ SearchSuggestionUIController.prototype =
     // Update the table's rows, and the input when there is a selection.
     this._table.removeAttribute("aria-activedescendant");
     for (let i = 0; i < this._table.children.length; i++) {
       let row = this._table.children[i];
       if (i == idx) {
         row.classList.add("selected");
         row.firstChild.setAttribute("aria-selected", "true");
         this._table.setAttribute("aria-activedescendant", row.firstChild.id);
-        this.input.value = this.suggestionAtIndex(i);
       }
       else {
         row.classList.remove("selected");
         row.firstChild.setAttribute("aria-selected", "false");
       }
     }
-
-    // Update the input when there is no selection.
-    if (idx < 0) {
-      this.input.value = this._stickyInputValue;
-    }
   },
 
   get numSuggestions() {
     return this._table.children.length;
   },
 
+  selectAndUpdateInput: function (idx) {
+    this.selectedIndex = idx;
+    this.input.value = idx >= 0 ? this.suggestionAtIndex(idx) :
+                       this._stickyInputValue;
+  },
+
   suggestionAtIndex: function (idx) {
     let row = this._table.children[idx];
     return row ? row.textContent : null;
   },
 
   deleteSuggestionAtIndex: function (idx) {
     // Only form history suggestions can be deleted.
     if (this.isFormHistorySuggestionAtIndex(idx)) {
       let suggestionStr = this.suggestionAtIndex(idx);
       this._sendMsg("RemoveFormHistoryEntry", suggestionStr);
       this._table.children[idx].remove();
-      this.selectedIndex = -1;
+      this.selectAndUpdateInput(-1);
     }
   },
 
   isFormHistorySuggestionAtIndex: function (idx) {
     let row = this._table.children[idx];
     return row && row.classList.contains("formHistory");
   },
 
@@ -145,17 +145,17 @@ SearchSuggestionUIController.prototype =
   _onInput: function () {
     if (this.input.value) {
       this._getSuggestions();
     }
     else {
       this._stickyInputValue = "";
       this._hideSuggestions();
     }
-    this.selectedIndex = -1;
+    this.selectAndUpdateInput(-1);
   },
 
   _onKeypress: function (event) {
     let selectedIndexDelta = 0;
     switch (event.keyCode) {
     case event.DOM_VK_UP:
       if (this.numSuggestions) {
         selectedIndexDelta = -1;
@@ -196,31 +196,35 @@ SearchSuggestionUIController.prototype =
       // Update the selection.
       let newSelectedIndex = this.selectedIndex + selectedIndexDelta;
       if (newSelectedIndex < -1) {
         newSelectedIndex = this.numSuggestions - 1;
       }
       else if (this.numSuggestions <= newSelectedIndex) {
         newSelectedIndex = -1;
       }
-      this.selectedIndex = newSelectedIndex;
+      this.selectAndUpdateInput(newSelectedIndex);
 
       // Prevent the input's caret from moving.
       event.preventDefault();
     }
   },
 
   _onFocus: function () {
     this._speculativeConnect();
   },
 
   _onBlur: function () {
     this._hideSuggestions();
   },
 
+  _onMousemove: function (event) {
+    this.selectedIndex = this._indexOfTableRowOrDescendent(event.target);
+  },
+
   _onMousedown: function (event) {
     let idx = this._indexOfTableRowOrDescendent(event.target);
     let suggestion = this.suggestionAtIndex(idx);
     this._stickyInputValue = suggestion;
     this.input.value = suggestion;
     this._hideSuggestions();
     if (this.onClick) {
       this.onClick.call(null);
@@ -286,16 +290,17 @@ SearchSuggestionUIController.prototype =
     }
   },
 
   _makeTableRow: function (type, suggestionStr, currentRow, searchWords) {
     let row = document.createElementNS(HTML_NS, "tr");
     row.classList.add("searchSuggestionRow");
     row.classList.add(type);
     row.setAttribute("role", "presentation");
+    row.addEventListener("mousemove", this);
     row.addEventListener("mousedown", this);
 
     let entry = document.createElementNS(HTML_NS, "td");
     entry.classList.add("searchSuggestionEntry");
     entry.setAttribute("role", "option");
     entry.id = this._idPrefix + SUGGESTION_ID_PREFIX + currentRow;
     entry.setAttribute("aria-selected", "false");
 
@@ -329,17 +334,17 @@ SearchSuggestionUIController.prototype =
   },
 
   _hideSuggestions: function () {
     this.input.setAttribute("aria-expanded", "false");
     this._table.hidden = true;
     while (this._table.firstElementChild) {
       this._table.firstElementChild.remove();
     }
-    this.selectedIndex = -1;
+    this.selectAndUpdateInput(-1);
   },
 
   _indexOfTableRowOrDescendent: function (row) {
     while (row && row.localName != "tr") {
       row = row.parentNode;
     }
     if (!row) {
       throw new Error("Element is not a row");
--- a/browser/base/content/test/general/browser_searchSuggestionUI.js
+++ b/browser/base/content/test/general/browser_searchSuggestionUI.js
@@ -97,16 +97,24 @@ add_task(rightArrowOrReturn("VK_RIGHT"))
 add_task(rightArrowOrReturn("VK_RETURN"));
 
 add_task(function* mouse() {
   yield setUp();
 
   let state = yield msg("key", { key: "x", waitForSuggestions: true });
   checkState(state, "x", ["xfoo", "xbar"], -1);
 
+  // Mouse over the first suggestion.
+  state = yield msg("mousemove", 0);
+  checkState(state, "x", ["xfoo", "xbar"], 0);
+
+  // Mouse over the second suggestion.
+  state = yield msg("mousemove", 1);
+  checkState(state, "x", ["xfoo", "xbar"], 1);
+
   // Click the second suggestion.  This should make it sticky.  To make sure it
   // sticks, trigger suggestions again and cycle through them by pressing Down
   // until nothing is selected again.
   state = yield msg("mousedown", 1);
   checkState(state, "xbar", [], -1);
 
   state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
   checkState(state, "xbar", ["xbarfoo", "xbarbar"], -1);
--- a/browser/base/content/test/general/searchSuggestionUI.js
+++ b/browser/base/content/test/general/searchSuggestionUI.js
@@ -32,16 +32,43 @@ let messageHandlers = {
     ack();
   },
 
   blur: function () {
     gController.input.blur();
     ack();
   },
 
+  mousemove: function (suggestionIdx) {
+    // Copied from widget/tests/test_panel_mouse_coords.xul and
+    // browser/base/content/test/newtab/head.js
+    let row = gController._table.children[suggestionIdx];
+    let rect = row.getBoundingClientRect();
+    let left = content.mozInnerScreenX + rect.left;
+    let x = left + rect.width / 2;
+    let y = content.mozInnerScreenY + rect.top + rect.height / 2;
+
+    let utils = content.SpecialPowers.getDOMWindowUtils(content);
+    let scale = utils.screenPixelsPerCSSPixel;
+
+    let widgetToolkit = content.SpecialPowers.
+                        Cc["@mozilla.org/xre/app-info;1"].
+                        getService(content.SpecialPowers.Ci.nsIXULRuntime).
+                        widgetToolkit;
+    let nativeMsg = widgetToolkit == "cocoa" ? 5 : // NSMouseMoved
+                    widgetToolkit == "windows" ? 1 : // MOUSEEVENTF_MOVE
+                    3; // GDK_MOTION_NOTIFY
+
+    row.addEventListener("mousemove", function onMove() {
+      row.removeEventListener("mousemove", onMove);
+      ack();
+    });
+    utils.sendNativeMouseEvent(x * scale, y * scale, nativeMsg, 0, null);
+  },
+
   mousedown: function (suggestionIdx) {
     gController.onClick = () => {
       gController.onClick = null;
       ack();
     };
     let row = gController._table.children[suggestionIdx];
     content.sendMouseEvent({ type: "mousedown" }, row);
   },