Bug 993197 - Asynchronize nsILoginManager::autoCompleteSearch. r=dolske

This commit is contained in:
Blake Kaplan 2014-06-28 11:09:45 -07:00
parent 16016e0fde
commit 564639d6e1
3 changed files with 83 additions and 72 deletions

View File

@ -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

View File

@ -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
*

View File

@ -599,7 +599,6 @@ nsFormFillController::StartSearch(const nsAString &aSearchString, const nsAStrin
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.
@ -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<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 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) {