Bug 1189618 [WIP] - Restyle password manager autocomplete popup. r?MattN
MozReview-Commit-ID: 5mtzxBKwuud
--- a/browser/base/content/browser-doctype.inc
+++ b/browser/base/content/browser-doctype.inc
@@ -16,10 +16,12 @@
#ifdef MOZ_SAFE_BROWSING
<!ENTITY % safebrowsingDTD SYSTEM "chrome://browser/locale/safebrowsing/phishing-afterload-warning-message.dtd">
%safebrowsingDTD;
#endif
<!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
%aboutHomeDTD;
<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
%syncBrandDTD;
+<!ENTITY % securityPrefsDTD SYSTEM "chrome://browser/locale/preferences/security.dtd">
+%securityPrefsDTD;
]>
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -136,17 +136,22 @@
<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" id="PopupAutoComplete" noautofocus="true" hidden="true">
+ <footer id="LoginAutoCompleteFooter">
+ <button class="plain" label="&savedLogins.label;"
+ oncommand="LoginHelper.openPasswordManager(window);"/>
+ </footer>
+ </panel>
<!-- 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/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1055,30 +1055,55 @@ file, You can obtain one at http://mozil
.copyStringToClipboard(val, Ci.nsIClipboard.kSelectionClipboard);
]]></handler>
</handlers>
</binding>
<!-- Note: this binding is applied to the autocomplete popup used in web page content and extended in search.xml for the searchbar. -->
<binding id="browser-autocomplete-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-result-popup">
+ <content ignorekeys="true" level="top" consumeoutsideclicks="never">
+ <xul:tree anonid="tree" class="autocomplete-tree plain" hidecolumnpicker="true" flex="1" 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:tree>
+ <xul:vbox anonid="footer" flex="1">
+ <children includes="footer"/>
+ </xul:vbox>
+ </content>
+
<implementation>
<field name="AppConstants" readonly="true">
(Components.utils.import("resource://gre/modules/AppConstants.jsm", {})).AppConstants;
</field>
+ <field name="footer" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "footer");
+ </field>
+
<method name="openAutocompletePopup">
<parameter name="aInput"/>
<parameter name="aElement"/>
<body>
<![CDATA[
// initially the panel is hidden
// to avoid impacting startup / new window performance
aInput.popup.hidden = false;
+ let controller =
+ Components.classes["@mozilla.org/satchel/form-fill-controller;1"]
+ .getService(Components.interfaces.nsIFormFillController);
+
+ // If this popup is being populated by the login manager,
+ // then we show a footer to the user to give them some
+ // options for logging in.
+ this.footer.hidden = !controller.isLoginManagerField(aElement);
+
// this method is defined on the base binding
this._openAutocompletePopup(aInput, aElement);
]]></body>
</method>
<method name="onPopupClick">
<parameter name="aEvent"/>
<body><![CDATA[
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -1829,16 +1829,51 @@ html|span.ac-emphasize-text-url {
color: GrayText;
font-size: smaller;
}
.autocomplete-treebody::-moz-tree-cell(suggesthint) {
border-top: 1px solid GrayText;
}
+.autocomplete-treebody::-moz-tree-image(login) {
+ padding-inline-start: 8px;
+ padding-inline-end: 8px;
+ list-style-image: url(chrome://browser/skin/permissions.svg#login-detailed);
+}
+
+.autocomplete-treebody::-moz-tree-row(login) {
+ height: 28px; /* <-- this totally doesn't work, unless I remove
+ * the login selector from -moz-tree-row, but then
+ * all autocomplete results can sized this way. :(
+ */
+}
+
+#LoginAutoCompleteFooter {
+ /** Copied from Downloads Panel... we might want to share this. **/
+ background: #e5e5e5;
+ border-top: 1px solid hsla(0,0%,0%,.1);
+ box-shadow: 0 -1px hsla(0,0%,100%,.5) inset, 0 1px 1px hsla(0,0%,0%,.03) inset;
+}
+
+#LoginAutoCompleteFooter > button {
+ -moz-appearance: none;
+ -moz-box-flex: 1;
+ border-top: 1px solid #ccc;
+ font-size: 10px;
+ font-weight: normal;
+ background-color: rgb(245, 245, 245);
+ margin: 0;
+ padding: 3px 6px;
+ color: #666;
+}
+
+#LoginAutoCompleteFooter > button > .button-box {
+ margin: 0.5em;
+}
/* ----- COMBINED GO/RELOAD/STOP BUTTON IN LOCATION BAR ----- */
#urlbar-go-button,
#urlbar-reload-button,
#urlbar-stop-button {
margin: 0;
list-style-image: url("chrome://browser/skin/reload-stop-go.png");
--- a/layout/xul/tree/nsTreeBodyFrame.cpp
+++ b/layout/xul/tree/nsTreeBodyFrame.cpp
@@ -2421,16 +2421,17 @@ nsTreeBodyFrame::GetImageSourceRect(nsSt
return r;
}
int32_t nsTreeBodyFrame::GetRowHeight()
{
// Look up the correct height. It is equal to the specified height
// + the specified margins.
mScratchArray.Clear();
+
nsStyleContext* rowContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow);
if (rowContext) {
const nsStylePosition* myPosition = rowContext->StylePosition();
nscoord minHeight = 0;
if (myPosition->mMinHeight.GetUnit() == eStyleUnit_Coord)
minHeight = myPosition->mMinHeight.GetCoordValue();
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -1242,17 +1242,17 @@ UserAutoCompleteResult.prototype = {
return this.getValueAt(index);
},
getCommentAt(index) {
return "";
},
getStyleAt(index) {
- return "";
+ return "login";
},
getImageAt(index) {
return "";
},
getFinalCompleteValueAt(index) {
return this.getValueAt(index);
--- a/toolkit/components/passwordmgr/LoginManagerParent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm
@@ -269,17 +269,17 @@ var LoginManagerParent = {
// XXX In the E10S case, we're responsible for showing our own
// autocomplete popup here because the autocomplete protocol hasn't
// been e10s-ized yet. In the non-e10s case, our caller is responsible
// for showing the autocomplete popup (via the regular
// nsAutoCompleteController).
if (remote) {
result = new UserAutoCompleteResult(searchString, matchingLogins);
- AutoCompleteE10S.showPopupWithResults(target.ownerDocument.defaultView, rect, result);
+ AutoCompleteE10S.showPopupWithResults(target.ownerDocument.defaultView, rect, result, true);
}
// Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
// doesn't support structured cloning.
var jsLogins = LoginHelper.loginsToVanillaObjects(matchingLogins);
target.messageManager.sendAsyncMessage("RemoteLogins:loginsAutoCompleted", {
requestId: requestId,
logins: jsLogins,
--- a/toolkit/components/satchel/AutoCompleteE10S.jsm
+++ b/toolkit/components/satchel/AutoCompleteE10S.jsm
@@ -40,17 +40,17 @@ var AutoCompleteE10SView = {
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.treeData.length - 1 },
toggleOpenState: function(idx) { },
getCellProperties: function(idx, column) { return this.properties[idx] || ""; },
- getRowProperties: function(idx) { return ""; },
+ getRowProperties: function(idx) { return this.properties[idx] || ""; },
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 ""; },
@@ -85,21 +85,22 @@ this.AutoCompleteE10S = {
messageManager.addMessageListener("FormAutoComplete:SelectBy", this);
messageManager.addMessageListener("FormAutoComplete:GetSelectedIndex", this);
messageManager.addMessageListener("FormAutoComplete:MaybeOpenPopup", this);
messageManager.addMessageListener("FormAutoComplete:ClosePopup", this);
messageManager.addMessageListener("FormAutoComplete:Disconnect", this);
messageManager.addMessageListener("FormAutoComplete:RemoveEntry", this);
},
- _initPopup: function(browserWindow, rect, direction) {
+ _initPopup: function(browserWindow, rect, forLogin, direction) {
this._popupCache = { browserWindow, rect, direction };
this.browser = browserWindow.gBrowser.selectedBrowser;
this.popup = this.browser.autoCompletePopup;
+ this.popup.footer.hidden = !forLogin;
this.popup.hidden = false;
// don't allow the popup to become overly narrow
this.popup.setAttribute("width", Math.max(100, rect.width));
this.popup.style.direction = direction;
this.x = rect.left;
this.y = rect.top;
this.width = rect.width;
@@ -137,18 +138,18 @@ this.AutoCompleteE10S = {
this._resultCache = results;
return resultsArray;
},
// This function is used by the login manager, which uses a single message
// to fill in the autocomplete results. See
// "RemoteLogins:autoCompleteLogins".
- showPopupWithResults: function(browserWindow, rect, results) {
- this._initPopup(browserWindow, rect);
+ showPopupWithResults: function(browserWindow, rect, results, forLogin=false) {
+ this._initPopup(browserWindow, rect, forLogin);
this._showPopup(results);
},
removeLogin(login) {
Services.logins.removeLogin(login);
// It's possible to race and have the deleted login no longer be in our
// resultCache's logins, so we remove it from the database above and only
@@ -178,17 +179,17 @@ this.AutoCompleteE10S = {
// This function is called in response to AutoComplete requests from the
// child (received via the message manager, see
// "FormHistory:AutoCompleteSearchAsync").
search: function(message) {
let browserWindow = message.target.ownerDocument.defaultView;
let rect = message.data;
let direction = message.data.direction;
- this._initPopup(browserWindow, rect, direction);
+ this._initPopup(browserWindow, rect, false, direction);
// NB: We use .wrappedJSObject here in order to pass our mock DOM object
// without being rejected by XPConnect (which attempts to enforce that DOM
// objects are implemented in C++.
let formAutoComplete = Cc["@mozilla.org/satchel/form-autocomplete;1"]
.getService(Ci.nsIFormAutoComplete).wrappedJSObject;
let values, labels;
@@ -243,23 +244,25 @@ this.AutoCompleteE10S = {
case "FormAutoComplete:RemoveEntry":
this.removeEntry(message.data.index);
break;
case "FormAutoComplete:MaybeOpenPopup":
if (AutoCompleteE10SView.treeData.length > 0 &&
!this.popup.popupOpen) {
+ let { forLogin } = msg.data;
// This happens when one of the arrow keys is pressed after a search
// has already been completed. nsAutoCompleteController tries to
// re-use its own cache of the results without re-doing the search.
// Detect that and show the popup here.
this.showPopupWithResults(this._popupCache.browserWindow,
this._popupCache.rect,
- this._resultCache);
+ this._resultCache,
+ forLogin);
}
break;
case "FormAutoComplete:ClosePopup":
this.popup.closePopup();
break;
case "FormAutoComplete:Disconnect":
--- a/toolkit/components/satchel/nsFormFillController.cpp
+++ b/toolkit/components/satchel/nsFormFillController.cpp
@@ -273,16 +273,23 @@ nsFormFillController::MarkAsLoginManager
node->AddMutationObserverUnlessExists(this);
if (!mLoginManager)
mLoginManager = do_GetService("@mozilla.org/login-manager;1");
return NS_OK;
}
+NS_IMETHODIMP
+nsFormFillController::IsLoginManagerField(nsIDOMHTMLInputElement *aInput,
+ bool* aResult)
+{
+ *aResult = mPwmgrInputs.Get(mFocusedInputNode);
+ return NS_OK;
+}
////////////////////////////////////////////////////////////////////////
//// nsIAutoCompleteInput
NS_IMETHODIMP
nsFormFillController::GetPopup(nsIAutoCompletePopup **aPopup)
{
*aPopup = mFocusedPopup;
--- a/toolkit/components/satchel/nsIFormFillController.idl
+++ b/toolkit/components/satchel/nsIFormFillController.idl
@@ -38,9 +38,17 @@ interface nsIFormFillController : nsISup
/*
* Mark the specified <input> element as being managed by password manager.
* Autocomplete requests will be handed off to the password manager, and will
* not be stored in form history.
*
* @param aInput - The HTML <input> element to tag
*/
void markAsLoginManagerField(in nsIDOMHTMLInputElement aInput);
+
+ /*
+ * See if the specified <input> element has been marked as being managed
+ * by the password manager via markAsLoginManagerField.
+ *
+ * @param aInput - the HTML <input> element to check
+ */
+ bool isLoginManagerField(in nsIDOMHTMLInputElement aInput);
};
--- a/toolkit/content/browser-child.js
+++ b/toolkit/content/browser-child.js
@@ -618,20 +618,25 @@ var AutoCompletePopup = {
return sendSyncMessage("FormAutoComplete:GetSelectedIndex", {});
},
get popupOpen () {
return this._popupOpen;
},
openAutocompletePopup: function (input, element) {
if (!this._popupOpen) {
+ let controller = Cc["@mozilla.org/satchel/form-fill-controller;1"]
+ .getService(Ci.nsIFormFillController);
+ // If we're populating the autocomplete from the login manager,
+ // tell the parent so that it can specially style the popup.
+ let forLogin = controller.isLoginManagerField(element);
// The search itself normally opens the popup itself, but in some cases,
// nsAutoCompleteController tries to use cached results so notify our
// popup to reuse the last results.
- sendAsyncMessage("FormAutoComplete:MaybeOpenPopup", {});
+ sendAsyncMessage("FormAutoComplete:MaybeOpenPopup", { forLogin });
}
this._input = input;
this._popupOpen = true;
},
closePopup: function () {
this._popupOpen = false;
sendAsyncMessage("FormAutoComplete:ClosePopup", {});