Bug 1296638 - Make Form Autocomplete popup use a richlistbox instead of a tree. r?MattN
MozReview-Commit-ID: 6ybBqAwxlmb
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -470,18 +470,22 @@ toolbar:not(#TabsToolbar) > #personal-bo
#PopupAutoCompleteRichResult[noactions] > richlistbox > richlistitem.overridable-action > .ac-type-icon {
list-style-image: none;
}
#urlbar:not([actiontype="switchtab"]) > #urlbar-display-box {
display: none;
}
-#PopupAutoComplete {
- -moz-binding: url("chrome://browser/content/urlbarBindings.xml#browser-autocomplete-result-popup");
+#PopupAutoComplete > richlistbox > richlistitem > .ac-type-icon,
+#PopupAutoComplete > richlistbox > richlistitem > .ac-site-icon,
+#PopupAutoComplete > richlistbox > richlistitem > .ac-tags,
+#PopupAutoComplete > richlistbox > richlistitem > .ac-separator,
+#PopupAutoComplete > richlistbox > richlistitem > .ac-url {
+ display: none;
}
#PopupSearchAutoComplete {
-moz-binding: url("chrome://browser/content/search/search.xml#browser-search-autocomplete-result-popup");
}
/* Overlay a badge on top of the icon of additional open search providers
in the search panel. */
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -136,17 +136,17 @@
<menupopup id="backForwardMenu"
onpopupshowing="return FillHistoryMenu(event.target);"
oncommand="gotoHistoryIndex(event); event.stopPropagation();"
onclick="checkForMiddleClick(this, event);"/>
<tooltip id="aHTMLTooltip" page="true"/>
<tooltip id="remoteBrowserTooltip"/>
<!-- for search and content formfill/pw manager -->
- <panel type="autocomplete" id="PopupAutoComplete" noautofocus="true" hidden="true"/>
+ <panel type="autocomplete-richlistbox" id="PopupAutoComplete" noautofocus="true" hidden="true"/>
<!-- for search with one-off buttons -->
<panel type="autocomplete" id="PopupSearchAutoComplete" noautofocus="true" hidden="true"/>
<!-- for url bar autocomplete -->
<panel type="autocomplete-richlistbox"
id="PopupAutoCompleteRichResult"
noautofocus="true"
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -909,16 +909,20 @@ notification[value="translation"] button
notification[value="translation"] button:not([type="menu"]) > .button-box {
margin-inline-end: 3ch;
}
notification[value="translation"] menulist > .menulist-dropmarker {
display: block;
}
+/* AutoComplete */
+
+%include ../shared/autocomplete.inc.css
+
#treecolAutoCompleteImage {
max-width : 36px;
}
.autocomplete-richlistbox {
padding: 4px;
}
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -1745,16 +1745,18 @@ toolbar .toolbarbutton-1 > .toolbarbutto
margin-bottom: 0;
color: GrayText;
}
%include ../shared/urlbarSearchSuggestionsNotification.inc.css
/* ----- AUTOCOMPLETE ----- */
+%include ../shared/autocomplete.inc.css
+
#treecolAutoCompleteImage {
max-width: 36px;
}
.autocomplete-richlistbox {
padding: 4px;
}
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/autocomplete.inc.css
@@ -0,0 +1,20 @@
+%if 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/. */
+%endif
+
+#PopupAutoComplete > richlistbox > richlistitem {
+ height: 20px;
+ min-height: 20px;
+ border-radius: 0;
+}
+
+#PopupAutoComplete > richlistbox > richlistitem > .ac-title {
+ font: icon;
+ margin-inline-start: 6px;
+}
+
+#PopupAutoComplete > richlistbox {
+ padding: 0;
+}
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -1527,16 +1527,18 @@ html|*.urlbar-input:-moz-lwtheme::placeh
}
/* page proxy icon */
%include ../shared/identity-block/identity-block.inc.css
/* autocomplete */
+%include ../shared/autocomplete.inc.css
+
#treecolAutoCompleteImage {
max-width: 36px;
}
.autocomplete-richlistbox {
padding: 4px;
}
--- a/toolkit/components/satchel/AutoCompletePopup.jsm
+++ b/toolkit/components/satchel/AutoCompletePopup.jsm
@@ -8,67 +8,78 @@ const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
this.EXPORTED_SYMBOLS = [ "AutoCompletePopup" ];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
-// nsITreeView implementation that feeds the autocomplete popup
-// with the search data.
-var AutoCompleteTreeView = {
+// AutoCompleteResultView is an abstraction around a list of results
+// we got back up from browser-content.js. It implements enough of
+// nsIAutoCompleteController and nsIAutoCompleteInput to make the
+// richlistbox popup work.
+var AutoCompleteResultView = {
// nsISupports
- QueryInterface: XPCOMUtils.generateQI([Ci.nsITreeView,
- Ci.nsIAutoCompleteController]),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteController,
+ Ci.nsIAutoCompleteInput]),
// Private variables
- treeBox: null,
results: [],
- // nsITreeView
- selection: null,
-
- get rowCount() { return this.results.length; },
- setTree: function(treeBox) { this.treeBox = treeBox; },
- getCellText: function(idx, column) { return this.results[idx].value },
- isContainer: function(idx) { return false; },
- getCellValue: function(idx, column) { return false },
- isContainerOpen: function(idx) { return false; },
- isContainerEmpty: function(idx) { return false; },
- isSeparator: function(idx) { return false; },
- isSorted: function() { return false; },
- isEditable: function(idx, column) { return false; },
- canDrop: function(idx, orientation, dt) { return false; },
- getLevel: function(idx) { return 0; },
- getParentIndex: function(idx) { return -1; },
- hasNextSibling: function(idx, after) { return idx < this.results.length - 1 },
- toggleOpenState: function(idx) { },
- getCellProperties: function(idx, column) { return this.results[idx].style || ""; },
- getRowProperties: function(idx) { return ""; },
- getImageSrc: function(idx, column) { return null; },
- getProgressMode : function(idx, column) { },
- cycleHeader: function(column) { },
- cycleCell: function(idx, column) { },
- selectionChanged: function() { },
- performAction: function(action) { },
- performActionOnCell: function(action, index, column) { },
- getColumnProperties: function(column) { return ""; },
-
// nsIAutoCompleteController
get matchCount() {
- return this.rowCount;
+ return this.results.length;
+ },
+
+ getValueAt(index) {
+ return this.results[index].value;
+ },
+
+ getLabelAt(index) {
+ // Unused by richlist autocomplete - see getCommentAt.
+ return "";
+ },
+
+ getCommentAt(index) {
+ // The richlist autocomplete popup uses comment for its main
+ // display of an item, which is why we're returning the label
+ // here instead.
+ return this.results[index].label;
+ },
+
+ getStyleAt(index) {
+ return this.results[index].style;
+ },
+
+ getImageAt(index) {
+ return this.results[index].image;
},
handleEnter: function(aIsPopupSelection) {
AutoCompletePopup.handleEnter(aIsPopupSelection);
},
stopSearch: function() {},
+ searchString: "",
+
+ // nsIAutoCompleteInput
+ get controller() {
+ return this;
+ },
+
+ get popup() {
+ return null;
+ },
+
+ _focus() {
+ AutoCompletePopup.requestFocus();
+ },
+
// Internal JS-only API
clearResults: function() {
this.results = [];
},
setResults: function(results) {
this.results = results;
},
@@ -101,17 +112,22 @@ this.AutoCompletePopup = {
handleEvent: function(evt) {
switch (evt.type) {
case "popupshowing": {
this.sendMessageToBrowser("FormAutoComplete:PopupOpened");
break;
}
case "popuphidden": {
+ AutoCompleteResultView.clearResults();
this.sendMessageToBrowser("FormAutoComplete:PopupClosed");
+ // adjustHeight clears the height from the popup so that
+ // we don't have a big shrink effect if we closed with a
+ // large list, and then open on a small one.
+ this.openedPopup.adjustHeight();
this.openedPopup = null;
this.weakBrowser = null;
evt.target.removeEventListener("popuphidden", this);
evt.target.removeEventListener("popupshowing", this);
break;
}
}
},
@@ -139,76 +155,74 @@ this.AutoCompletePopup = {
this.weakBrowser = Cu.getWeakReference(browser);
this.openedPopup = browser.autoCompletePopup;
this.openedPopup.hidden = false;
// don't allow the popup to become overly narrow
this.openedPopup.setAttribute("width", Math.max(100, rect.width));
this.openedPopup.style.direction = dir;
- AutoCompleteTreeView.setResults(results);
- this.openedPopup.view = AutoCompleteTreeView;
+ AutoCompleteResultView.setResults(results);
+ this.openedPopup.view = AutoCompleteResultView;
this.openedPopup.selectedIndex = -1;
- this.openedPopup.invalidate();
if (results.length) {
// Reset fields that were set from the last time the search popup was open
- this.openedPopup.mInput = null;
+ this.openedPopup.mInput = AutoCompleteResultView;
this.openedPopup.showCommentColumn = false;
this.openedPopup.showImageColumn = false;
this.openedPopup.addEventListener("popuphidden", this);
this.openedPopup.addEventListener("popupshowing", this);
this.openedPopup.openPopupAtScreenRect("after_start", rect.left, rect.top,
rect.width, rect.height, false,
false);
+ this.openedPopup.invalidate();
} else {
this.closePopup();
}
},
invalidate(results) {
if (!this.openedPopup) {
return;
}
if (!results.length) {
this.closePopup();
} else {
- AutoCompleteTreeView.setResults(results);
- // We need to re-set the view in order for the
- // tree to know the view has changed.
- this.openedPopup.view = AutoCompleteTreeView;
+ AutoCompleteResultView.setResults(results);
this.openedPopup.invalidate();
}
},
closePopup() {
if (this.openedPopup) {
// Note that hidePopup() closes the popup immediately,
// so popuphiding or popuphidden events will be fired
// and handled during this call.
this.openedPopup.hidePopup();
}
- AutoCompleteTreeView.clearResults();
},
removeLogin(login) {
Services.logins.removeLogin(login);
},
receiveMessage: function(message) {
if (!message.target.autoCompletePopup) {
// Returning false to pacify ESLint, but this return value is
// ignored by the messaging infrastructure.
return false;
}
switch (message.name) {
case "FormAutoComplete:SelectBy": {
- this.openedPopup.selectBy(message.data.reverse, message.data.page);
+ if (this.openedPopup) {
+ this.openedPopup.selectBy(message.data.reverse, message.data.page);
+ }
break;
}
case "FormAutoComplete:GetSelectedIndex": {
if (this.openedPopup) {
return this.openedPopup.selectedIndex;
}
// If the popup was closed, then the selection
@@ -242,17 +256,17 @@ this.AutoCompletePopup = {
break;
}
case "FormAutoComplete:Disconnect": {
// The controller stopped controlling the current input, so clear
// any cached data. This is necessary cause otherwise we'd clear data
// only when starting a new search, but the next input could not support
// autocomplete and it would end up inheriting the existing data.
- AutoCompleteTreeView.clearResults();
+ AutoCompleteResultView.clearResults();
break;
}
}
// Returning false to pacify ESLint, but this return value is
// ignored by the messaging infrastructure.
return false;
},
@@ -281,10 +295,20 @@ this.AutoCompletePopup = {
sendMessageToBrowser(msgName, data) {
let browser = this.weakBrowser ? this.weakBrowser.get()
: null;
if (browser) {
browser.messageManager.sendAsyncMessage(msgName, data);
}
},
- stopSearch: function() {}
+ stopSearch: function() {},
+
+ /**
+ * Sends a message to the browser requesting that the input
+ * that the AutoCompletePopup is open for be focused.
+ */
+ requestFocus: function() {
+ if (this.openedPopup) {
+ this.sendMessageToBrowser("FormAutoComplete:Focus");
+ }
+ },
}
--- a/toolkit/content/browser-content.js
+++ b/toolkit/content/browser-content.js
@@ -1396,18 +1396,19 @@ addEventListener("MozApplicationManifest
let AutoCompletePopup = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompletePopup]),
_connected: false,
MESSAGES: [
"FormAutoComplete:HandleEnter",
+ "FormAutoComplete:PopupClosed",
"FormAutoComplete:PopupOpened",
- "FormAutoComplete:PopupClosed",
+ "FormAutoComplete:RequestFocus",
],
init: function() {
addEventListener("unload", this);
addEventListener("DOMContentLoaded", this);
for (let messageName of this.MESSAGES) {
addMessageListener(messageName, this);
@@ -1474,16 +1475,23 @@ let AutoCompletePopup = {
this._popupOpen = false;
break;
}
case "FormAutoComplete:PopupOpened": {
this._popupOpen = true;
break;
}
+
+ case "FormAutoComplete:RequestFocus": {
+ if (this._input) {
+ this._input.focus();
+ }
+ break;
+ }
}
},
get input () { return this._input; },
get overrideValue () { return null; },
set selectedIndex (index) {
sendAsyncMessage("FormAutoComplete:SetSelectedIndex", { index });
},
@@ -1556,17 +1564,17 @@ let AutoCompletePopup = {
result.label = controller.getLabelAt(i);
result.comment = controller.getCommentAt(i);
result.style = controller.getStyleAt(i);
result.image = controller.getImageAt(i);
results.push(result);
}
return results;
- }
+ },
}
AutoCompletePopup.init();
/**
* DateTimePickerListener is the communication channel between the input box
* (content) for date/time input types and its picker (chrome).
*/
--- a/toolkit/content/widgets/autocomplete.xml
+++ b/toolkit/content/widgets/autocomplete.xml
@@ -1099,16 +1099,20 @@ extends="chrome://global/content/binding
</method>
<method name="_openAutocompletePopup">
<parameter name="aInput"/>
<parameter name="aElement"/>
<body>
<![CDATA[
if (!this.mPopupOpen) {
+ // It's possible that the panel is hidden initially
+ // to avoid impacting startup / new window performance
+ aInput.popup.hidden = false;
+
this.mInput = aInput;
// clear any previous selection, see bugs 400671 and 488357
this.selectedIndex = -1;
var width = aElement.getBoundingClientRect().width;
this.setAttribute("width", width > 100 ? width : 100);
// invalidate() depends on the width attribute
this._invalidate();