Bug 993197 - Asynchronize nsILoginManager::autoCompleteSearch. r=dolske
☠☠ backed out by 6f291b1b47c7 ☠ ☠
authorBlake Kaplan <mrbkap@gmail.com>
Tue, 24 Jun 2014 14:34:02 -0400
changeset 190564 654c1f0c88bc39f7ddb5589ee763f6689052c4e9
parent 190563 225ad76a4c79bc7a3e857143f16281d15302cc31
child 190565 2baf5c61cbf5900ceb2b6508bd15e3a9d77fd2ac
push idunknown
push userunknown
push dateunknown
reviewersdolske
bugs993197
milestone33.0a1
Bug 993197 - Asynchronize nsILoginManager::autoCompleteSearch. r=dolske
toolkit/components/passwordmgr/nsILoginManager.idl
toolkit/components/passwordmgr/nsLoginManager.js
toolkit/components/satchel/nsFormFillController.cpp
--- 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;
 }