--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1734,19 +1734,17 @@ file, You can obtain one at http://mozil
</method>
<!-- This handles keypress changes to the selection among the one-off
search buttons and between the one-offs and the listbox. It returns
true if the keypress was consumed and false if not. -->
<method name="handleKeyPress">
<parameter name="aEvent"/>
<body><![CDATA[
- this.oneOffSearchButtons.handleKeyPress(aEvent, this._matchCount,
- !this._isFirstResultHeuristic,
- gBrowser.userTypedValue);
+ this.oneOffSearchButtons.handleKeyPress(aEvent, this._matchCount);
return aEvent.defaultPrevented;
]]></body>
</method>
<!-- This is called when a one-off is clicked and when "search in new tab"
is selected from a one-off context menu. -->
<method name="handleOneOffSearch">
<parameter name="event"/>
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -817,17 +817,17 @@
if (aEvent.getModifierState("Accel"))
return;
let suggestions =
document.getAnonymousElementByAttribute(popup, "anonid", "tree");
let suggestionsHidden =
suggestions.getAttribute("collapsed") == "true";
let numItems = suggestionsHidden ? 0 : this.popup.view.rowCount;
- this.popup.oneOffButtons.handleKeyPress(aEvent, numItems, true);
+ this.popup.oneOffButtons.handleKeyPress(aEvent, numItems);
]]></body>
</method>
<!-- nsIController -->
<field name="searchbarController" readonly="true"><![CDATA[({
_self: this,
supportsCommand(aCommand) {
return aCommand == "cmd_clearhistory" ||
@@ -921,17 +921,17 @@
role="presentation"/>
</xul:hbox>
<xul:tree anonid="tree" flex="1"
class="autocomplete-tree plain search-panel-tree"
hidecolumnpicker="true" seltype="single">
<xul:treecols anonid="treecols">
<xul:treecol id="treecolAutoCompleteValue" class="autocomplete-treecol" flex="1" overflow="true"/>
</xul:treecols>
- <xul:treechildren class="autocomplete-treebody"/>
+ <xul:treechildren class="autocomplete-treebody searchbar-treebody"/>
</xul:tree>
<xul:vbox anonid="search-one-off-buttons" class="search-one-offs"/>
</content>
<implementation>
<field name="AppConstants" readonly="true">
(Components.utils.import("resource://gre/modules/AppConstants.jsm", {})).AppConstants;
</field>
@@ -1134,16 +1134,29 @@
return;
}
this.oneOffButtons.handleSearchCommand(event, engine);
]]></handler>
</handlers>
</binding>
+
+ <!-- This is the same as the autocomplete-treebody binding except it does not
+ select rows on mousemove. -->
+ <binding id="searchbar-treebody"
+ extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-treebody">
+ <handlers>
+ <handler event="mousemove"><![CDATA[
+ // Cancel the event so that the base binding doesn't select the row.
+ event.preventDefault();
+ ]]></handler>
+ </handlers>
+ </binding>
+
<!-- Used for additional open search providers in the search panel. -->
<binding id="addengine-icon" extends="xul:box">
<content>
<xul:image class="addengine-icon" xbl:inherits="src"/>
<xul:image class="addengine-badge"/>
</content>
</binding>
@@ -1302,17 +1315,37 @@
<!-- The selected one-off, a xul:button, including the add-engine button
and the search-settings button. Null if no one-off is selected. -->
<property name="selectedButton">
<getter><![CDATA[
return this._selectedButton;
]]></getter>
<setter><![CDATA[
- this._changeVisuallySelectedButton(val, true);
+ if (val && val.classList.contains("dummy")) {
+ // Never select dummy buttons.
+ val = null;
+ }
+ if (this._selectedButton) {
+ this._selectedButton.removeAttribute("selected");
+ }
+ if (val) {
+ val.setAttribute("selected", "true");
+ }
+ this._selectedButton = val;
+ this._updateStateForButton(null);
+ if (val && !val.engine) {
+ // If the button doesn't have an engine, then clear the popup's
+ // selection to indicate that pressing Return while the button is
+ // selected will do the button's command, not search.
+ this.popup.selectedIndex = -1;
+ }
+ let event = document.createEvent("Events");
+ event.initEvent("SelectedOneOffButtonChanged", true, false);
+ this.dispatchEvent(event);
return val;
]]></setter>
</property>
<!-- The index of the selected one-off, including the add-engine button
and the search-settings button. -1 if no one-off is selected. -->
<property name="selectedButtonIndex">
<getter><![CDATA[
@@ -1326,30 +1359,16 @@
]]></getter>
<setter><![CDATA[
let buttons = this.getSelectableButtons(true);
this.selectedButton = buttons[val];
return val;
]]></setter>
</property>
- <!-- The visually selected one-off is the same as the selected one-off
- unless a one-off is moused over. In that case, the visually selected
- one-off is the moused-over one-off, which may be different from the
- selected one-off. The visually selected one-off is always the one
- that is visually highlighted. Includes the add-engine button and the
- search-settings button. A xul:button. -->
- <property name="visuallySelectedButton" readonly="true">
- <getter><![CDATA[
- return this.getSelectableButtons(true).find(button => {
- return button.getAttribute("selected") == "true";
- });
- ]]></getter>
- </property>
-
<property name="compact" readonly="true">
<getter><![CDATA[
return this.getAttribute("compact") == "true";
]]></getter>
</property>
<property name="settingsButton" readonly="true">
<getter><![CDATA[
@@ -1733,62 +1752,60 @@
<method name="_buttonForEngine">
<parameter name="engine"/>
<body><![CDATA[
return document.getElementById(this._buttonIDForEngine(engine));
]]></body>
</method>
- <method name="_changeVisuallySelectedButton">
- <parameter name="val"/>
- <parameter name="aUpdateLogicallySelectedButton"/>
+ <!--
+ Updates the popup and textbox for the currently selected or moused-over
+ button.
+
+ @param mousedOverButton
+ The currently moused-over button, or null if there isn't one.
+ -->
+ <method name="_updateStateForButton">
+ <parameter name="mousedOverButton"/>
<body><![CDATA[
- let visuallySelectedButton = this.visuallySelectedButton;
- if (visuallySelectedButton)
- visuallySelectedButton.removeAttribute("selected");
-
+ let button = mousedOverButton;
let header =
document.getAnonymousElementByAttribute(this, "anonid",
"search-panel-one-offs-header");
- // Avoid selecting dummy buttons.
- if (val && !val.classList.contains("dummy")) {
- val.setAttribute("selected", "true");
- if (val.classList.contains("searchbar-engine-one-off-item") &&
- val.engine) {
- let headerEngineText =
- document.getAnonymousElementByAttribute(this, "anonid",
- "searchbar-oneoffheader-engine");
- header.selectedIndex = 2;
- headerEngineText.value = val.engine.name;
- } else {
- header.selectedIndex = this.query ? 1 : 0;
- }
- if (this.textbox) {
- this.textbox.setAttribute("aria-activedescendant", val.id);
- }
- } else {
- val = null;
+
+ // Ignore dummy buttons.
+ if (button && button.classList.contains("dummy")) {
+ button = null;
+ }
+
+ // If there's no moused-over button, then the one-offs should reflect
+ // the selected button, if any.
+ button = button || this.selectedButton;
+
+ if (!button) {
header.selectedIndex = this.query ? 1 : 0;
if (this.textbox) {
this.textbox.removeAttribute("aria-activedescendant");
}
+ return;
}
- if (aUpdateLogicallySelectedButton) {
- this._selectedButton = val;
- if (val && !val.engine) {
- // If the button doesn't have an engine, then clear the popup's
- // selection to indicate that pressing Return while the button is
- // selected will do the button's command, not search.
- this.popup.selectedIndex = -1;
- }
- let event = document.createEvent("Events");
- event.initEvent("SelectedOneOffButtonChanged", true, false);
- this.dispatchEvent(event);
+ if (button.classList.contains("searchbar-engine-one-off-item") &&
+ button.engine) {
+ let headerEngineText =
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "searchbar-oneoffheader-engine");
+ header.selectedIndex = 2;
+ headerEngineText.value = button.engine.name;
+ } else {
+ header.selectedIndex = this.query ? 1 : 0;
+ }
+ if (this.textbox) {
+ this.textbox.setAttribute("aria-activedescendant", button.id);
}
]]></body>
</method>
<method name="getSelectableButtons">
<parameter name="aIncludeNonEngineButtons"/>
<body><![CDATA[
let buttons = [];
@@ -1854,221 +1871,203 @@
</method>
<!--
Increments or decrements the index of the currently selected one-off.
@param aForward
If true, the index is incremented, and if false, the index is
decremented.
+ @param aIncludeNonEngineButtons
+ If true, non-engine buttons are included.
@param aWrapAround
- This has a couple of effects, depending on whether there is
- currently a selection.
- (1) If true and the last one-off is currently selected,
- incrementing the index will cause the selection to be cleared and
- this method to return true. Calling advanceSelection again after
- that (again with aForward=true) will select the first one-off.
- Likewise if decrementing the index when the first one-off is
- selected, except in the opposite direction of course.
- (2) If true and there currently is no selection, decrementing the
- index will cause the last one-off to become selected and this
- method to return true. Only the aForward=false case is affected
- because it is always the case that if aForward=true and there
- currently is no selection, the first one-off becomes selected and
- this method returns true.
- @param aCycleEngines
- If true, only engine buttons are included.
+ If true, the selection wraps around between the first and last
+ buttons.
@return True if the selection can continue to advance after this method
returns and false if not.
-->
<method name="advanceSelection">
<parameter name="aForward"/>
+ <parameter name="aIncludeNonEngineButtons"/>
<parameter name="aWrapAround"/>
- <parameter name="aCycleEngines"/>
<body><![CDATA[
- let selectedButton = this.selectedButton;
- let buttons = this.getSelectableButtons(aCycleEngines);
-
- if (selectedButton) {
- // cycle through one-off buttons.
- let index = buttons.indexOf(selectedButton);
- if (aForward)
- ++index;
- else
- --index;
-
- if (index >= 0 && index < buttons.length)
- this.selectedButton = buttons[index];
- else
- this.selectedButton = null;
-
- if (this.selectedButton || aWrapAround)
- return true;
-
- return false;
+ let buttons = this.getSelectableButtons(aIncludeNonEngineButtons);
+ let index;
+ if (this.selectedButton) {
+ let inc = aForward ? 1 : -1;
+ let oldIndex = buttons.indexOf(this.selectedButton);
+ index = ((oldIndex + inc) + buttons.length) % buttons.length;
+ if (!aWrapAround &&
+ ((aForward && index <= oldIndex) ||
+ (!aForward && oldIndex <= index))) {
+ // The index has wrapped around, but wrapping around isn't
+ // allowed.
+ index = -1;
+ }
+ } else {
+ index = aForward ? 0 : buttons.length - 1;
}
-
- // If no selection, select the first button or ...
- if (aForward) {
- this.selectedButton = buttons[0];
- return true;
- }
-
- if (!aForward && aWrapAround) {
- // the last button.
- this.selectedButton = buttons[buttons.length - 1];
- return true;
- }
-
- return false;
+ this.selectedButton = index < 0 ? null : buttons[index];
]]></body>
</method>
<!--
This handles key presses specific to the one-off buttons like Tab and
- Alt-Up/Down, and Up/Down keys within the buttons. Since one-off buttons
+ Alt+Up/Down, and Up/Down keys within the buttons. Since one-off buttons
are always used in conjunction with a list of some sort (in this.popup),
it also handles Up/Down keys that cross the boundaries between list
items and the one-off buttons.
+ If this method handles the key press, then event.defaultPrevented will
+ be true when it returns.
+
@param event
The key event.
@param numListItems
The number of items in the list. The reason that this is a
parameter at all is that the list may contain items at the end
that should be ignored, depending on the consumer. That's true
for the urlbar for example.
- @param allowEmptySelection
- Pass true if it's OK that neither the list nor the one-off
- buttons contains a selection. Pass false if either the list or
- the one-off buttons (or both) should always contain a selection.
- @param textboxUserValue
- When the last list item is selected and the user presses Down,
- the first one-off becomes selected and the textbox value is
- restored to the value that the user typed. Pass that value here.
- However, if you pass true for allowEmptySelection, you don't need
- to pass anything for this parameter. (Pass undefined or null.)
- @return True if this method handled the keypress and false if not. If
- false, then you should let the autocomplete controller handle
- the keypress. The value of event.defaultPrevented will be the
- same as this return value.
-->
<method name="handleKeyPress">
<parameter name="event"/>
<parameter name="numListItems"/>
- <parameter name="allowEmptySelection"/>
- <parameter name="textboxUserValue"/>
<body><![CDATA[
if (!this.popup) {
- return false;
+ return;
}
-
- let stopEvent = false;
+ let handled = this._handleKeyPress(event, numListItems);
+ if (handled) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ ]]></body>
+ </method>
- // Tab cycles through the one-offs and moves the focus out at the end.
- // But only if non-Shift modifiers aren't also pressed, to avoid
- // clobbering other shortcuts.
- if (event.keyCode == KeyEvent.DOM_VK_TAB &&
- !event.altKey &&
- !event.ctrlKey &&
- !event.metaKey &&
- this.getAttribute("disabletab") != "true") {
- stopEvent = this.advanceSelection(!event.shiftKey, false, true);
- } else if (event.altKey &&
- (event.keyCode == KeyEvent.DOM_VK_DOWN ||
- event.keyCode == KeyEvent.DOM_VK_UP)) {
- // Alt + up/down is very similar to (shift +) tab but differs in that
- // it loops through the list, whereas tab will move the focus out.
- stopEvent =
- this.advanceSelection(event.keyCode == KeyEvent.DOM_VK_DOWN,
- true, false);
- } else if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_UP) {
- if (numListItems > 0) {
- if (this.popup.selectedIndex > 0) {
- // The autocomplete controller should handle this case.
- } else if (this.popup.selectedIndex == 0) {
- if (!allowEmptySelection) {
- // Wrap around the selection to the last one-off.
- this.selectedButton = null;
- this.popup.selectedIndex = -1;
- // Call advanceSelection after setting selectedIndex so that
- // screen readers see the newly selected one-off. Both trigger
- // accessibility events.
- this.advanceSelection(false, true, true);
- stopEvent = true;
- }
- } else {
- let firstButtonSelected =
- this.selectedButton &&
- this.selectedButton == this.getSelectableButtons(true)[0];
- if (firstButtonSelected) {
- this.selectedButton = null;
- } else {
- stopEvent = this.advanceSelection(false, true, true);
- }
- }
- } else {
- stopEvent = this.advanceSelection(false, true, true);
- }
- } else if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_DOWN) {
- if (numListItems > 0) {
- if (this.popup.selectedIndex >= 0 &&
- this.popup.selectedIndex < numListItems - 1) {
- // The autocomplete controller should handle this case.
- } else if (this.popup.selectedIndex == numListItems - 1) {
- this.selectedButton = null;
- if (!allowEmptySelection) {
- this.popup.selectedIndex = -1;
- stopEvent = true;
- }
- if (this.textbox && typeof(textboxUserValue) == "string") {
- this.textbox.value = textboxUserValue;
- }
- // Call advanceSelection after setting selectedIndex so that
- // screen readers see the newly selected one-off. Both trigger
- // accessibility events.
- this.advanceSelection(true, true, true);
- } else {
- let buttons = this.getSelectableButtons(true);
- let lastButtonSelected =
- this.selectedButton &&
- this.selectedButton == buttons[buttons.length - 1];
- if (lastButtonSelected) {
- this.selectedButton = null;
- stopEvent = allowEmptySelection;
- } else if (this.selectedButton) {
- stopEvent = this.advanceSelection(true, true, true);
- } else {
- // The autocomplete controller should handle this case.
- }
- }
- } else {
- stopEvent = this.advanceSelection(true, true, true);
- }
- } else if (this.selectedButton &&
- this.selectedButton.getAttribute("anonid") ==
- "addengine-menu-button" &&
- event.keyCode == KeyEvent.DOM_VK_RIGHT) {
+ <method name="_handleKeyPress">
+ <parameter name="event"/>
+ <parameter name="numListItems"/>
+ <body><![CDATA[
+ if (event.keyCode == KeyEvent.DOM_VK_RIGHT &&
+ this.selectedButton &&
+ this.selectedButton.getAttribute("anonid") ==
+ "addengine-menu-button") {
// If the add-engine overflow menu item is selected and the user
// presses the right arrow key, open the submenu. Unfortunately
// handling the left arrow key -- to close the popup -- isn't
// straightforward. Once the popup is open, it consumes all key
// events. Setting ignorekeys=handled on it doesn't help, since the
// popup handles all arrow keys. Setting ignorekeys=true on it does
- // mean that the popup no longer consumes the left arrow key, but then
- // it no longer handles up/down keys to select items in the popup.
+ // mean that the popup no longer consumes the left arrow key, but
+ // then it no longer handles up/down keys to select items in the
+ // popup.
this.selectedButton.open = true;
- stopEvent = true;
+ return true;
+ }
+
+ // Handle the Tab key, but only if non-Shift modifiers aren't also
+ // pressed to avoid clobbering other shortcuts (like the Alt+Tab
+ // browser tab switcher).
+ if (event.keyCode == KeyEvent.DOM_VK_TAB &&
+ !event.altKey &&
+ !event.ctrlKey &&
+ !event.metaKey) {
+ if (this.getAttribute("disabletab") == "true") {
+ this.selectedButton = null;
+ return false;
+ }
+ this.popup.selectedIndex = -1;
+ this.advanceSelection(!event.shiftKey, true, false);
+ return !!this.selectedButton;
+ }
+
+ if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_UP) {
+ if (event.altKey) {
+ // Keep a secondary "alt" selection in the list, and move the
+ // selection up within the buttons.
+ this.advanceSelection(false, false, true);
+ return true;
+ }
+ if (this.popup.selectedIndex > 0) {
+ // Moving up within the list. The autocomplete controller should
+ // handle this case. A button may be selected, so null it.
+ this.selectedButton = null;
+ return false;
+ }
+ if (this.selectedButton) {
+ if (this.selectedButtonIndex == 0) {
+ // Moving up from the buttons to the bottom of the list.
+ this.selectedButton = null;
+ return false;
+ }
+ // Moving up/left within the buttons.
+ this.advanceSelection(false, true, false);
+ return true;
+ }
+ // Moving up from the top of the list back down to the buttons.
+ this.popup.selectedIndex = -1;
+ this.selectedButtonIndex =
+ this.getSelectableButtons(true).length - 1;
+ return true;
}
- if (stopEvent) {
- event.preventDefault();
- event.stopPropagation();
- return true;
+ if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_DOWN) {
+ if (event.altKey) {
+ // Keep a secondary "alt" selection in the list, and move the
+ // selection down within the buttons.
+ this.advanceSelection(true, false, true);
+ return true;
+ }
+ if (this.popup.selectedIndex >= 0 &&
+ this.popup.selectedIndex < numListItems - 1) {
+ // Moving down within the list. The autocomplete controller
+ // should handle this case. A button may be selected, so null it.
+ this.selectedButton = null;
+ return false;
+ }
+ if (this.popup.selectedIndex == numListItems - 1) {
+ // Moving down from the last item in the list to the buttons.
+ this.popup.selectedIndex = -1;
+ this.selectedButtonIndex = 0;
+ return true;
+ }
+ if (this.selectedButton) {
+ let buttons = this.getSelectableButtons(true);
+ if (this.selectedButtonIndex == buttons.length - 1) {
+ // Moving down from the buttons back up to the top of the list.
+ this.selectedButton = null;
+ return false;
+ }
+ // Moving down/right within the buttons.
+ this.advanceSelection(true, true, false);
+ return true;
+ }
+ return false;
}
+
+ if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_LEFT) {
+ if (this.selectedButton &&
+ (this.compact || this.selectedButton.engine)) {
+ // Moving left within the buttons.
+ this.advanceSelection(false, this.compact, true);
+ return true;
+ }
+ return false;
+ }
+
+ if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RIGHT) {
+ if (this.selectedButton &&
+ (this.compact || this.selectedButton.engine)) {
+ // Moving right within the buttons.
+ this.advanceSelection(true, this.compact, true);
+ return true;
+ }
+ return false;
+ }
+
return false;
]]></body>
</method>
<!--
If the given event is related to the one-offs, this method records
one-off telemetry for it. this.telemetryOrigin will be appended to the
computed source, so make sure you set that first.
@@ -2169,24 +2168,20 @@
<handler event="mousemove"><![CDATA[
let target = event.originalTarget;
// Handle mouseover on the add-engine menu button and its popup items.
if (target.getAttribute("anonid") == "addengine-menu-button" ||
(target.localName == "menuitem" &&
target.classList.contains("addengine-item"))) {
- // Make the menu button visually selected. It's highlighted in the
- // CSS when the popup is open, but the popup doesn't open until a
- // short timeout has elapsed. Making the button visually selected now
- // provides better feedback to the user.
let menuButton = document.getAnonymousElementByAttribute(
this, "anonid", "addengine-menu-button"
);
- this._changeVisuallySelectedButton(menuButton);
+ this._updateStateForButton(menuButton);
this._addEngineMenuShouldBeOpen = true;
this._resetAddEngineMenuTimeout();
return;
}
if (target.localName != "button")
return;
@@ -2195,53 +2190,43 @@
return;
let isOneOff =
target.classList.contains("searchbar-engine-one-off-item") &&
!target.classList.contains("dummy");
if (isOneOff ||
target.classList.contains("addengine-item") ||
target.classList.contains("search-setting-button")) {
- this._changeVisuallySelectedButton(target);
+ this._updateStateForButton(target);
}
]]></handler>
<handler event="mouseout"><![CDATA[
let target = event.originalTarget;
// Handle mouseout on the add-engine menu button and its popup items.
if (target.getAttribute("anonid") == "addengine-menu-button" ||
(target.localName == "menuitem" &&
target.classList.contains("addengine-item"))) {
- // The menu button will appear selected since the mouse is either over
- // it or over one of the menu items in the popup. Make it unselected.
- this._changeVisuallySelectedButton(null);
+ this._updateStateForButton(null);
this._addEngineMenuShouldBeOpen = false;
this._resetAddEngineMenuTimeout();
return;
}
if (target.localName != "button") {
return;
}
- // Don't deselect the current button if the context menu is open.
+ // Don't update the mouseover state if the context menu is open.
if (this._ignoreMouseEvents)
return;
- // Unfortunately this will fire before mouseover hits another item.
- // If this button is selected, we replace that selection only if
- // we're not moving to a different one-off item:
- if (target.getAttribute("selected") == "true" &&
- (!event.relatedTarget ||
- !event.relatedTarget.classList.contains("searchbar-engine-one-off-item") ||
- event.relatedTarget.classList.contains("dummy"))) {
- this._changeVisuallySelectedButton(this.selectedButton);
- }
+ this._updateStateForButton(null);
]]></handler>
<handler event="click"><![CDATA[
if (event.button == 2)
return; // ignore right clicks.
let button = event.originalTarget;
let engine = button.engine;
--- a/browser/components/search/content/searchbarBindings.css
+++ b/browser/components/search/content/searchbarBindings.css
@@ -3,16 +3,20 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
.searchbar-textbox {
-moz-binding: url("chrome://browser/content/search/search.xml#searchbar-textbox");
}
+.searchbar-treebody {
+ -moz-binding: url("chrome://browser/content/search/search.xml#searchbar-treebody");
+}
+
.search-one-offs {
-moz-binding: url("chrome://browser/content/search/search.xml#search-one-offs");
}
.search-setting-button[compact=true],
.search-setting-button-compact:not([compact=true]) {
display: none;
}
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -921,17 +921,24 @@ notification[value="translation"] menuli
.autocomplete-richlistitem {
height: 30px;
min-height: 30px;
font: message-box;
border-radius: 2px;
border: 1px solid transparent;
}
-.autocomplete-richlistitem[selected=true] {
+.autocomplete-richlistitem:hover,
+treechildren.searchbar-treebody::-moz-tree-row(hover) {
+ background-color: hsla(0, 0%, 0%, 0.06);
+ border-color: hsla(0, 0%, 0%, 0.1);
+}
+
+.autocomplete-richlistitem[selected],
+treechildren.searchbar-treebody::-moz-tree-row(selected) {
background-color: Highlight;
}
.ac-title {
font-size: 1.05em;
}
.ac-tags {
--- a/browser/themes/linux/searchbar.css
+++ b/browser/themes/linux/searchbar.css
@@ -216,17 +216,24 @@ menuitem[cmd="cmd_clearhistory"][disable
.search-panel-one-offs:not([compact=true]) > .searchbar-engine-one-off-item.last-of-row,
.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.last-of-row:not(.dummy),
.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.dummy:not(.last-of-row),
.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.last-engine,
.search-setting-button-compact {
background-image: none;
}
-.searchbar-engine-one-off-item[selected] {
+.searchbar-engine-one-off-item:not(.dummy):hover,
+.search-setting-button:hover,
+.addengine-item:hover {
+ background-color: hsla(0, 0%, 0%, 0.06);
+ color: inherit;
+}
+
+.searchbar-engine-one-off-item[selected]:not(.dummy) {
background-color: Highlight;
background-image: none;
color: HighlightText;
}
.searchbar-engine-one-off-item > .button-box {
border: none;
padding: 0;
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -1715,20 +1715,30 @@ toolbar .toolbarbutton-1 > .toolbarbutto
.autocomplete-richlistitem {
height: 30px;
min-height: 30px;
font: message-box;
border-radius: 2px;
border: 1px solid transparent;
}
+.autocomplete-richlistitem:hover,
+treechildren.searchbar-treebody::-moz-tree-row(hover) {
+ background-color: hsla(0, 0%, 0%, 0.06);
+ border-color: hsla(0, 0%, 0%, 0.1);
+}
+
.autocomplete-richlistitem[selected] {
background-color: hsl(210, 80%, 52%);
}
+treechildren.searchbar-treebody::-moz-tree-row(selected) {
+ background-color: Highlight;
+}
+
.ac-title {
font-size: 14px;
color: hsl(0, 0%, 0%);
}
.ac-tags {
font-size: 12px;
}
--- a/browser/themes/osx/searchbar.css
+++ b/browser/themes/osx/searchbar.css
@@ -205,17 +205,24 @@
.search-panel-one-offs:not([compact=true]) > .searchbar-engine-one-off-item.last-of-row,
.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.last-of-row:not(.dummy),
.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.dummy:not(.last-of-row),
.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.last-engine,
.search-setting-button-compact {
background-image: none;
}
-.searchbar-engine-one-off-item[selected] {
+.searchbar-engine-one-off-item:not(.dummy):hover,
+.search-setting-button:hover,
+.addengine-item:hover {
+ background-color: hsla(0, 0%, 0%, 0.06);
+ color: inherit;
+}
+
+.searchbar-engine-one-off-item[selected]:not(.dummy) {
background-color: Highlight;
background-image: none;
color: HighlightText;
}
.searchbar-engine-one-off-item > .button-box > .button-text {
display: none;
}
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -1420,20 +1420,30 @@ html|*.urlbar-input:-moz-lwtheme::placeh
.autocomplete-richlistitem {
height: 30px;
min-height: 30px;
font: message-box;
border-radius: 2px;
border: 1px solid transparent;
}
-.autocomplete-richlistitem[selected=true] {
+.autocomplete-richlistitem:hover,
+treechildren.searchbar-treebody::-moz-tree-row(hover) {
+ background-color: hsla(0, 0%, 0%, 0.06);
+ border-color: hsla(0, 0%, 0%, 0.1);
+}
+
+.autocomplete-richlistitem[selected] {
background-color: hsl(210, 80%, 52%);
}
+treechildren.searchbar-treebody::-moz-tree-row(selected) {
+ background-color: Highlight;
+}
+
.ac-title {
font-size: 14px;
color: hsl(0, 0%, 0%);
}
.ac-tags {
font-size: 12px;
}
--- a/browser/themes/windows/searchbar.css
+++ b/browser/themes/windows/searchbar.css
@@ -213,17 +213,24 @@
.search-panel-one-offs:not([compact=true]) > .searchbar-engine-one-off-item.last-of-row,
.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.last-of-row:not(.dummy),
.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.dummy:not(.last-of-row),
.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.last-engine,
.search-setting-button-compact {
background-image: none;
}
-.searchbar-engine-one-off-item[selected] {
+.searchbar-engine-one-off-item:not(.dummy):hover,
+.search-setting-button:hover,
+.addengine-item:hover {
+ background-color: hsla(0, 0%, 0%, 0.06);
+ color: inherit;
+}
+
+.searchbar-engine-one-off-item[selected]:not(.dummy) {
background-color: Highlight;
background-image: none;
color: HighlightText;
}
.searchbar-engine-one-off-item > .button-box {
border: none;
padding: 0;
--- a/toolkit/content/widgets/autocomplete.xml
+++ b/toolkit/content/widgets/autocomplete.xml
@@ -1094,17 +1094,17 @@ extends="chrome://global/content/binding
return val;
]]>
</setter>
</property>
<method name="onSearchBegin">
<body><![CDATA[
- this.richlistbox.mouseSelectedIndex = -1;
+ this.richlistbox.mousedOverIndex = -1;
if (typeof this._onSearchBegin == "function") {
this._onSearchBegin();
}
]]></body>
</method>
<method name="openAutocompletePopup">
@@ -1336,24 +1336,24 @@ extends="chrome://global/content/binding
item.setAttribute("ac-image", image);
item.setAttribute("ac-value", value);
item.setAttribute("ac-label", label);
item.setAttribute("ac-comment", comment);
item.setAttribute("ac-text", trimmedSearchString);
// Completely reuse the existing richlistitem for invalidation
// due to new results, but only when: the item is the same, *OR*
- // we are about to replace the currently mouse-selected item, to
+ // we are about to replace the currently moused-over item, to
// avoid surprising the user.
let iface = Components.interfaces.nsIAutoCompletePopup;
if (reusable &&
originalText == trimmedSearchString &&
invalidateReason == iface.INVALIDATE_REASON_NEW_RESULT &&
(originalValue == value ||
- this.richlistbox.mouseSelectedIndex === this._currentIndex)) {
+ this.richlistbox.mousedOverIndex === this._currentIndex)) {
// try to re-use the existing item
let reused = item._reuseAcItem();
if (reused) {
this._currentIndex++;
continue;
}
} else {
@@ -2485,17 +2485,17 @@ extends="chrome://global/content/binding
<children/>
</xul:treerows>
</content>
</binding>
<binding id="autocomplete-richlistbox" extends="chrome://global/content/bindings/richlistbox.xml#richlistbox">
<implementation>
<field name="mLastMoveTime">Date.now()</field>
- <field name="mouseSelectedIndex">-1</field>
+ <field name="mousedOverIndex">-1</field>
</implementation>
<handlers>
<handler event="mouseup">
<![CDATA[
// don't call onPopupClick for the scrollbar buttons, thumb, slider, etc.
let item = event.originalTarget;
while (item && item.localName != "richlistitem") {
item = item.parentNode;
@@ -2505,32 +2505,31 @@ extends="chrome://global/content/binding
return;
this.parentNode.onPopupClick(event);
]]>
</handler>
<handler event="mousemove">
<![CDATA[
- if (Date.now() - this.mLastMoveTime > 30) {
- let item = event.target;
- while (item && item.localName != "richlistitem") {
- item = item.parentNode;
- }
+ if (Date.now() - this.mLastMoveTime <= 30) {
+ return;
+ }
- if (!item)
- return;
+ let item = event.target;
+ while (item && item.localName != "richlistitem") {
+ item = item.parentNode;
+ }
- let index = this.getIndexOfItem(item);
- if (index != this.selectedIndex) {
- this.mouseSelectedIndex = this.selectedIndex = index;
- }
+ if (!item) {
+ return;
+ }
- this.mLastMoveTime = Date.now();
- }
+ this.mousedOverIndex = this.getIndexOfItem(item);
+ this.mLastMoveTime = Date.now();
]]>
</handler>
</handlers>
</binding>
<binding id="autocomplete-treebody">
<implementation>
<field name="mLastMoveTime">Date.now()</field>
@@ -2541,16 +2540,21 @@ extends="chrome://global/content/binding
<handler event="mousedown"><![CDATA[
var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY);
if (rc != this.parentNode.currentIndex)
this.parentNode.view.selection.select(rc);
]]></handler>
<handler event="mousemove"><![CDATA[
+ if (event.defaultPrevented) {
+ // Allow bindings that extend this one to cancel the event so that
+ // nothing is selected.
+ return;
+ }
if (Date.now() - this.mLastMoveTime > 30) {
var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY);
if (rc != this.parentNode.currentIndex)
this.parentNode.view.selection.select(rc);
this.mLastMoveTime = Date.now();
}
]]></handler>
</handlers>