Bug 993197 - Asynchronize nsILoginManager::autoCompleteSearch. r=dolske
--- a/toolkit/components/passwordmgr/nsILoginManager.idl
+++ b/toolkit/components/passwordmgr/nsILoginManager.idl
@@ -3,22 +3,22 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsIURI;
interface nsILoginInfo;
interface nsIAutoCompleteResult;
+interface nsIFormAutoCompleteObserver;
interface nsIDOMHTMLInputElement;
interface nsIDOMHTMLFormElement;
interface nsIPropertyBag;
-[scriptable, uuid(f5f2a39a-dffe-4eb9-ad28-340afd53b1a3)]
-
+[scriptable, uuid(f441b0a3-6588-455e-baa8-2e2dbba84655)]
interface nsILoginManager : nsISupports {
/**
* This promise is resolved when initialization is complete, and is rejected
* in case initialization failed. This includes the initial loading of the
* login data as well as any migration from previous versions.
*
* Calling any method of nsILoginManager before this promise is resolved
* might trigger the synchronous initialization fallback.
@@ -205,19 +205,20 @@ interface nsILoginManager : nsISupports
/**
* Generate results for a userfield autocomplete menu.
*
* NOTE: This interface is provided for use only by the FormFillController,
* which calls it directly. This isn't really ideal, it should
* probably be callback registered through the FFC.
*/
- nsIAutoCompleteResult autoCompleteSearch(in AString aSearchString,
- in nsIAutoCompleteResult aPreviousResult,
- in nsIDOMHTMLInputElement aElement);
+ void autoCompleteSearchAsync(in AString aSearchString,
+ in nsIAutoCompleteResult aPreviousResult,
+ in nsIDOMHTMLInputElement aElement,
+ in nsIFormAutoCompleteObserver aListener);
/**
* Fill a form with login information if we have it. This method will fill
* aForm regardless of the signon.autofillForms preference.
*
* @param aForm
* The form to fill
* @return Success of attempt fill form
--- a/toolkit/components/passwordmgr/nsLoginManager.js
+++ b/toolkit/components/passwordmgr/nsLoginManager.js
@@ -3,16 +3,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Cc = Components.classes;
const Ci = Components.interfaces;
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/Timer.jsm");
Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
"resource://gre/modules/LoginManagerContent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
@@ -403,42 +404,44 @@ LoginManager.prototype = {
// Nulls won't round-trip with getAllDisabledHosts().
if (hostname.indexOf("\0") != -1)
throw "Invalid hostname";
log("Login saving for", hostname, "now enabled?", enabled);
return this._storage.setLoginSavingEnabled(hostname, enabled);
},
-
/*
- * autoCompleteSearch
+ * autoCompleteSearchAsync
*
* Yuck. This is called directly by satchel:
* nsFormFillController::StartSearch()
- * [toolkit/components/satchel/src/nsFormFillController.cpp]
+ * [toolkit/components/satchel/nsFormFillController.cpp]
*
* We really ought to have a simple way for code to register an
* auto-complete provider, and not have satchel calling pwmgr directly.
*/
- autoCompleteSearch : function (aSearchString, aPreviousResult, aElement) {
+ autoCompleteSearchAsync : function (aSearchString, aPreviousResult,
+ aElement, aCallback) {
// aPreviousResult & aResult are nsIAutoCompleteResult,
// aElement is nsIDOMHTMLInputElement
- if (!this._remember)
- return null;
+ if (!this._remember) {
+ setTimeout(function() {
+ aCallback.onSearchCompletion(new UserAutoCompleteResult(aSearchString, []));
+ }, 0);
+ return;
+ }
log("AutoCompleteSearch invoked. Search is:", aSearchString);
- var result = null;
-
if (aPreviousResult &&
aSearchString.substr(0, aPreviousResult.searchString.length) == aPreviousResult.searchString) {
log("Using previous autocomplete result");
- result = aPreviousResult;
+ let result = aPreviousResult;
result.wrappedJSObject.searchString = aSearchString;
// We have a list of results for a shorter search string, so just
// filter them further based on the new search string.
// Count backwards, because result.matchCount is decremented
// when we remove an entry.
for (var i = result.matchCount - 1; i >= 0; i--) {
var match = result.getValueAt(i);
@@ -447,57 +450,57 @@ LoginManager.prototype = {
if (aSearchString.length > match.length ||
aSearchString.toLowerCase() !=
match.substr(0, aSearchString.length).toLowerCase())
{
log("Removing autocomplete entry:", match);
result.removeValueAt(i, false);
}
}
+
+ setTimeout(function() { aCallback.onSearchCompletion(result); }, 0);
} else {
log("Creating new autocomplete search result.");
- var doc = aElement.ownerDocument;
- var origin = this._getPasswordOrigin(doc.documentURI);
- var actionOrigin = this._getActionOrigin(aElement.form);
+ setTimeout(function() {
+ var doc = aElement.ownerDocument;
+ var origin = this._getPasswordOrigin(doc.documentURI);
+ var actionOrigin = this._getActionOrigin(aElement.form);
- // This shouldn't trigger a master password prompt, because we
- // don't attach to the input until after we successfully obtain
- // logins for the form.
- var logins = this.findLogins({}, origin, actionOrigin, null);
- var matchingLogins = [];
+ // This shouldn't trigger a master password prompt, because we
+ // don't attach to the input until after we successfully obtain
+ // logins for the form.
+ var logins = this.findLogins({}, origin, actionOrigin, null);
+ var matchingLogins = [];
- // Filter out logins that don't match the search prefix. Also
- // filter logins without a username, since that's confusing to see
- // in the dropdown and we can't autocomplete them anyway.
- for (i = 0; i < logins.length; i++) {
- var username = logins[i].username.toLowerCase();
- if (username &&
- aSearchString.length <= username.length &&
- aSearchString.toLowerCase() ==
- username.substr(0, aSearchString.length))
- {
- matchingLogins.push(logins[i]);
+ // Filter out logins that don't match the search prefix. Also
+ // filter logins without a username, since that's confusing to see
+ // in the dropdown and we can't autocomplete them anyway.
+ for (let i = 0; i < logins.length; i++) {
+ var username = logins[i].username.toLowerCase();
+ log(username);
+ if (username &&
+ aSearchString.length <= username.length &&
+ aSearchString.toLowerCase() ==
+ username.substr(0, aSearchString.length))
+ {
+ matchingLogins.push(logins[i]);
+ }
}
- }
- log(matchingLogins.length, "autocomplete logins avail.");
- result = new UserAutoCompleteResult(aSearchString, matchingLogins);
+ log(matchingLogins.length, "autocomplete logins avail.");
+ aCallback.onSearchCompletion(new UserAutoCompleteResult(aSearchString,
+ matchingLogins));
+ }.bind(this), 0);
}
-
- return result;
},
-
-
/* ------- Internal methods / callbacks for document integration ------- */
-
-
/*
* _getPasswordOrigin
*
* Get the parts of the URL we want for identification.
*/
_getPasswordOrigin : function (uriString, allowJS) {
var realm = "";
try {
--- a/toolkit/components/satchel/nsFormFillController.cpp
+++ b/toolkit/components/satchel/nsFormFillController.cpp
@@ -594,32 +594,29 @@ nsFormFillController::GetInPrivateContex
////////////////////////////////////////////////////////////////////////
//// nsIAutoCompleteSearch
NS_IMETHODIMP
nsFormFillController::StartSearch(const nsAString &aSearchString, const nsAString &aSearchParam,
nsIAutoCompleteResult *aPreviousResult, nsIAutoCompleteObserver *aListener)
{
nsresult rv;
- nsCOMPtr<nsIAutoCompleteResult> result;
// If the login manager has indicated it's responsible for this field, let it
// handle the autocomplete. Otherwise, handle with form history.
bool dummy;
if (mPwmgrInputs.Get(mFocusedInputNode, &dummy)) {
// XXX aPreviousResult shouldn't ever be a historyResult type, since we're not letting
// satchel manage the field?
- rv = mLoginManager->AutoCompleteSearch(aSearchString,
- aPreviousResult,
- mFocusedInput,
- getter_AddRefs(result));
+ mLastListener = aListener;
+ rv = mLoginManager->AutoCompleteSearchAsync(aSearchString,
+ aPreviousResult,
+ mFocusedInput,
+ this);
NS_ENSURE_SUCCESS(rv, rv);
- if (aListener) {
- aListener->OnSearchResult(this, result);
- }
} else {
mLastListener = aListener;
// It appears that mFocusedInput is always null when we are focusing a XUL
// element. Scary :)
if (!mFocusedInput || nsContentUtils::IsAutocompleteEnabled(mFocusedInput)) {
nsCOMPtr <nsIFormAutoComplete> formAutoComplete =
do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv);
@@ -648,42 +645,47 @@ nsresult
nsFormFillController::PerformInputListAutoComplete(nsIAutoCompleteResult* aPreviousResult)
{
// If an <input> is focused, check if it has a list="<datalist>" which can
// provide the list of suggestions.
nsresult rv;
nsCOMPtr<nsIAutoCompleteResult> result;
- nsCOMPtr <nsIInputListAutoComplete> inputListAutoComplete =
- do_GetService("@mozilla.org/satchel/inputlist-autocomplete;1", &rv);
- NS_ENSURE_SUCCESS(rv, rv);
- rv = inputListAutoComplete->AutoCompleteSearch(aPreviousResult,
- mLastSearchString,
- mFocusedInput,
- getter_AddRefs(result));
- NS_ENSURE_SUCCESS(rv, rv);
+ bool dummy;
+ if (!mPwmgrInputs.Get(mFocusedInputNode, &dummy)) {
+ nsCOMPtr <nsIInputListAutoComplete> inputListAutoComplete =
+ do_GetService("@mozilla.org/satchel/inputlist-autocomplete;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = inputListAutoComplete->AutoCompleteSearch(aPreviousResult,
+ mLastSearchString,
+ mFocusedInput,
+ getter_AddRefs(result));
+ NS_ENSURE_SUCCESS(rv, rv);
- if (mFocusedInput) {
- nsCOMPtr<nsIDOMHTMLElement> list;
- mFocusedInput->GetList(getter_AddRefs(list));
+ if (mFocusedInput) {
+ nsCOMPtr<nsIDOMHTMLElement> list;
+ mFocusedInput->GetList(getter_AddRefs(list));
- // Add a mutation observer to check for changes to the items in the <datalist>
- // and update the suggestions accordingly.
- nsCOMPtr<nsINode> node = do_QueryInterface(list);
- if (mListNode != node) {
- if (mListNode) {
- mListNode->RemoveMutationObserver(this);
- mListNode = nullptr;
- }
- if (node) {
- node->AddMutationObserverUnlessExists(this);
- mListNode = node;
+ // Add a mutation observer to check for changes to the items in the <datalist>
+ // and update the suggestions accordingly.
+ nsCOMPtr<nsINode> node = do_QueryInterface(list);
+ if (mListNode != node) {
+ if (mListNode) {
+ mListNode->RemoveMutationObserver(this);
+ mListNode = nullptr;
+ }
+ if (node) {
+ node->AddMutationObserverUnlessExists(this);
+ mListNode = node;
+ }
}
}
+ } else {
+ result = aPreviousResult;
}
if (mLastListener) {
mLastListener->OnSearchResult(this, result);
}
return NS_OK;
}