diff --git a/toolkit/components/passwordmgr/nsILoginManager.idl b/toolkit/components/passwordmgr/nsILoginManager.idl index 4beee995d173..8f8807c8ae5d 100644 --- a/toolkit/components/passwordmgr/nsILoginManager.idl +++ b/toolkit/components/passwordmgr/nsILoginManager.idl @@ -8,12 +8,12 @@ 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 @@ -210,9 +210,10 @@ interface nsILoginManager : nsISupports { * 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 diff --git a/toolkit/components/passwordmgr/nsLoginManager.js b/toolkit/components/passwordmgr/nsLoginManager.js index 9e54764049cc..55952bdfeb5f 100644 --- a/toolkit/components/passwordmgr/nsLoginManager.js +++ b/toolkit/components/passwordmgr/nsLoginManager.js @@ -8,6 +8,7 @@ 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", @@ -408,32 +409,34 @@ LoginManager.prototype = { 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 @@ -452,47 +455,47 @@ LoginManager.prototype = { 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 * diff --git a/toolkit/components/satchel/nsFormFillController.cpp b/toolkit/components/satchel/nsFormFillController.cpp index d753959acc7e..777364362110 100644 --- a/toolkit/components/satchel/nsFormFillController.cpp +++ b/toolkit/components/satchel/nsFormFillController.cpp @@ -599,7 +599,6 @@ nsFormFillController::StartSearch(const nsAString &aSearchString, const nsAStrin nsIAutoCompleteResult *aPreviousResult, nsIAutoCompleteObserver *aListener) { nsresult rv; - nsCOMPtr result; // If the login manager has indicated it's responsible for this field, let it // handle the autocomplete. Otherwise, handle with form history. @@ -607,14 +606,12 @@ nsFormFillController::StartSearch(const nsAString &aSearchString, const nsAStrin 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; @@ -653,32 +650,42 @@ nsFormFillController::PerformInputListAutoComplete(nsIAutoCompleteResult* aPrevi nsresult rv; nsCOMPtr result; - nsCOMPtr 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 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 list; - mFocusedInput->GetList(getter_AddRefs(list)); + if (mFocusedInput) { + nsCOMPtr list; + mFocusedInput->GetList(getter_AddRefs(list)); - // Add a mutation observer to check for changes to the items in the - // and update the suggestions accordingly. - nsCOMPtr 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 + // and update the suggestions accordingly. + nsCOMPtr 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 this is a password manager input mLastSearchResult will be a JS + // object (wrapped in an XPConnect reflector), so we need to take care not + // to hold onto it for too long. + mLastSearchResult = nullptr; } if (mLastListener) {