Bug 720589 - mMatchCounts may be accessed with a nonexisting index. r=neil a=ritu ba=jorgev
authorMarco Bonardo <mbonardo@mozilla.com>
Tue, 10 Nov 2015 20:18:24 +0100
changeset 305854 abf1e06c22c999073d0311268572998ef49bcf61
parent 305853 e58f733c4778d95ac1ce6521e54d044173a407c2
child 305855 432c275bd01cd62cc072f81a780a871249a50d03
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersneil, ritu
bugs720589
milestone43.0
Bug 720589 - mMatchCounts may be accessed with a nonexisting index. r=neil a=ritu ba=jorgev
toolkit/components/autocomplete/nsAutoCompleteController.cpp
toolkit/components/autocomplete/nsAutoCompleteController.h
toolkit/components/autocomplete/nsAutoCompleteSimpleResult.cpp
toolkit/components/autocomplete/nsAutoCompleteSimpleResult.h
toolkit/components/autocomplete/nsIAutoCompleteSimpleResult.idl
toolkit/components/autocomplete/tests/unit/test_393191.js
toolkit/components/autocomplete/tests/unit/test_660156.js
toolkit/components/satchel/nsFormAutoComplete.js
toolkit/components/satchel/nsFormAutoCompleteResult.jsm
toolkit/components/satchel/nsFormFillController.cpp
toolkit/components/satchel/nsInputListAutoComplete.js
--- a/toolkit/components/autocomplete/nsAutoCompleteController.cpp
+++ b/toolkit/components/autocomplete/nsAutoCompleteController.cpp
@@ -127,17 +127,16 @@ nsAutoCompleteController::SetInput(nsIAu
   mSearchesOngoing = 0;
   mCompletedSelectionIndex = -1;
 
   // Initialize our list of search objects
   uint32_t searchCount;
   aInput->GetSearchCount(&searchCount);
   mResults.SetCapacity(searchCount);
   mSearches.SetCapacity(searchCount);
-  mMatchCounts.SetLength(searchCount);
   mImmediateSearchesCount = 0;
 
   const char *searchCID = kAutoCompleteSearchCID;
 
   // Since the controller can be used as a service it's important to reset this.
   mClearingAutoFillSearchesAgain = false;
 
   for (uint32_t i = 0; i < searchCount; ++i) {
@@ -624,17 +623,17 @@ nsAutoCompleteController::HandleDelete(b
     // No row is selected in the list
     HandleText();
     return NS_OK;
   }
 
   RowIndexToSearch(index, &searchIndex, &rowIndex);
   NS_ENSURE_TRUE(searchIndex >= 0 && rowIndex >= 0, NS_ERROR_FAILURE);
 
-  nsIAutoCompleteResult *result = mResults[searchIndex];
+  nsIAutoCompleteResult *result = mResults.SafeObjectAt(searchIndex);
   NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);
 
   nsAutoString search;
   input->GetSearchParam(search);
 
   // Clear the row in our result and in the DB.
   result->RemoveValueAt(rowIndex, true);
   --mRowCount;
@@ -686,17 +685,17 @@ nsAutoCompleteController::HandleDelete(b
 nsresult 
 nsAutoCompleteController::GetResultAt(int32_t aIndex, nsIAutoCompleteResult** aResult,
                                       int32_t* aRowIndex)
 {
   int32_t searchIndex;
   RowIndexToSearch(aIndex, &searchIndex, aRowIndex);
   NS_ENSURE_TRUE(searchIndex >= 0 && *aRowIndex >= 0, NS_ERROR_FAILURE);
 
-  *aResult = mResults[searchIndex];
+  *aResult = mResults.SafeObjectAt(searchIndex);
   NS_ENSURE_TRUE(*aResult, NS_ERROR_FAILURE);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAutoCompleteController::GetValueAt(int32_t aIndex, nsAString & _retval)
 {
   GetResultLabelAt(aIndex, _retval);
@@ -1517,64 +1516,90 @@ nsAutoCompleteController::RevertTextValu
 
   return NS_OK;
 }
 
 nsresult
 nsAutoCompleteController::ProcessResult(int32_t aSearchIndex, nsIAutoCompleteResult *aResult)
 {
   NS_ENSURE_STATE(mInput);
+  MOZ_ASSERT(aResult, "ProcessResult should always receive a result");
+  NS_ENSURE_ARG(aResult);
   nsCOMPtr<nsIAutoCompleteInput> input(mInput);
 
-  uint16_t result = 0;
-  if (aResult)
-    aResult->GetSearchResult(&result);
-
-  uint32_t oldMatchCount = 0;
-  uint32_t matchCount = 0;
-  if (aResult)
-    aResult->GetMatchCount(&matchCount);
+  uint16_t searchResult = 0;
+  aResult->GetSearchResult(&searchResult);
 
-  int32_t resultIndex = mResults.IndexOf(aResult);
-  if (resultIndex == -1) {
-    // cache the result
-    mResults.AppendObject(aResult);
-    mMatchCounts.AppendElement(matchCount);
-    resultIndex = mResults.Count() - 1;
+  // The following code supports incremental updating results in 2 ways:
+  //  * The search may reuse the same result, just by adding entries to it.
+  //  * The search may send a new result every time.  In this case we merge
+  //    the results and proceed on the same code path as before.
+  // This way both mSearches and mResults can be indexed by the search index,
+  // cause we'll always have only one result per search.
+  if (mResults.IndexOf(aResult) == -1) {
+    nsIAutoCompleteResult* oldResult = mResults.SafeObjectAt(aSearchIndex);
+    if (oldResult) {
+      MOZ_ASSERT(false, "Passing new matches to OnSearchResult with a new "
+                        "nsIAutoCompleteResult every time is deprecated, please "
+                        "update the same result until the search is done");
+      // Build a new nsIAutocompleteSimpleResult and merge results into it.
+      RefPtr<nsAutoCompleteSimpleResult> mergedResult =
+        new nsAutoCompleteSimpleResult();
+      mergedResult->AppendResult(oldResult);
+      mergedResult->AppendResult(aResult);
+      mResults.ReplaceObjectAt(mergedResult, aSearchIndex);
+    } else {
+      // This inserts and grows the array if needed.
+      mResults.ReplaceObjectAt(aResult, aSearchIndex);
+    }
   }
-  else {
-    oldMatchCount = mMatchCounts[aSearchIndex];
-    mMatchCounts[resultIndex] = matchCount;
-  }
+  // When found the result should have the same index as the search.
+  MOZ_ASSERT_IF(mResults.IndexOf(aResult) != -1,
+                mResults.IndexOf(aResult) == aSearchIndex);
+  MOZ_ASSERT(mResults.Count() >= aSearchIndex + 1,
+             "aSearchIndex should always be valid for mResults");
 
   bool isTypeAheadResult = false;
-  if (aResult) {
-    aResult->GetTypeAheadResult(&isTypeAheadResult);
-  }
+  aResult->GetTypeAheadResult(&isTypeAheadResult);
 
   if (!isTypeAheadResult) {
     uint32_t oldRowCount = mRowCount;
     // If the search failed, increase the match count to include the error
     // description.
-    if (result == nsIAutoCompleteResult::RESULT_FAILURE) {
+    if (searchResult == nsIAutoCompleteResult::RESULT_FAILURE) {
       nsAutoString error;
       aResult->GetErrorDescription(error);
       if (!error.IsEmpty()) {
         ++mRowCount;
         if (mTree) {
           mTree->RowCountChanged(oldRowCount, 1);
         }
       }
-    } else if (result == nsIAutoCompleteResult::RESULT_SUCCESS ||
-               result == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
+    } else if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS ||
+               searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
       // Increase the match count for all matches in this result.
-      mRowCount += matchCount - oldMatchCount;
+      uint32_t totalMatchCount = 0;
+      for (uint32_t i = 0; i < mResults.Length(); i++) {
+        nsIAutoCompleteResult* result = mResults.SafeObjectAt(i);
+        if (result) {
+          // not all results implement this, so it can likely fail.
+          bool typeAhead = false;
+          result->GetTypeAheadResult(&typeAhead);
+          if (!typeAhead) {
+            uint32_t matchCount = 0;
+            result->GetMatchCount(&matchCount);
+            totalMatchCount += matchCount;
+          }
+        }
+      }
+      uint32_t delta = totalMatchCount - oldRowCount;
 
+      mRowCount += delta;
       if (mTree) {
-        mTree->RowCountChanged(oldRowCount, matchCount - oldMatchCount);
+        mTree->RowCountChanged(oldRowCount, delta);
       }
     }
 
     // Refresh the popup view to display the new search results
     nsCOMPtr<nsIAutoCompletePopup> popup;
     input->GetPopup(getter_AddRefs(popup));
     NS_ENSURE_TRUE(popup != nullptr, NS_ERROR_FAILURE);
     popup->Invalidate();
@@ -1587,20 +1612,20 @@ nsAutoCompleteController::ProcessResult(
     // get results in the future to avoid unnecessarily canceling searches.
     if (mRowCount || !minResults) {
       OpenPopup();
     } else if (mSearchesOngoing == 0) {
       ClosePopup();
     }
   }
 
-  if (result == nsIAutoCompleteResult::RESULT_SUCCESS ||
-      result == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
+  if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS ||
+      searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
     // Try to autocomplete the default index for this search.
-    CompleteDefaultIndex(resultIndex);
+    CompleteDefaultIndex(aSearchIndex);
   }
 
   return NS_OK;
 }
 
 nsresult
 nsAutoCompleteController::PostSearchCleanup()
 {
@@ -1628,17 +1653,16 @@ nsAutoCompleteController::PostSearchClea
 }
 
 nsresult
 nsAutoCompleteController::ClearResults()
 {
   int32_t oldRowCount = mRowCount;
   mRowCount = 0;
   mResults.Clear();
-  mMatchCounts.Clear();
   if (oldRowCount != 0) {
     if (mTree)
       mTree->RowCountChanged(0, -oldRowCount);
     else if (mInput) {
       nsCOMPtr<nsIAutoCompletePopup> popup;
       mInput->GetPopup(getter_AddRefs(popup));
       NS_ENSURE_TRUE(popup != nullptr, NS_ERROR_FAILURE);
       // if we had a tree, RowCountChanged() would have cleared the selection
@@ -1696,24 +1720,26 @@ nsAutoCompleteController::GetDefaultComp
                                                    nsIAutoCompleteResult** _result,
                                                    int32_t* _defaultIndex)
 {
   *_defaultIndex = -1;
   int32_t resultIndex = aResultIndex;
 
   // If a result index was not provided, find the first defaultIndex result.
   for (int32_t i = 0; resultIndex < 0 && i < mResults.Count(); ++i) {
-    nsIAutoCompleteResult *result = mResults[i];
+    nsIAutoCompleteResult *result = mResults.SafeObjectAt(i);
     if (result &&
         NS_SUCCEEDED(result->GetDefaultIndex(_defaultIndex)) &&
         *_defaultIndex >= 0) {
       resultIndex = i;
     }
   }
-  NS_ENSURE_TRUE(resultIndex >= 0, NS_ERROR_FAILURE);
+  if (resultIndex < 0) {
+    return NS_ERROR_FAILURE;
+  }
 
   *_result = mResults.SafeObjectAt(resultIndex);
   NS_ENSURE_TRUE(*_result, NS_ERROR_FAILURE);
 
   if (*_defaultIndex < 0) {
     // The search must explicitly provide a default index in order
     // for us to be able to complete.
     (*_result)->GetDefaultIndex(_defaultIndex);
--- a/toolkit/components/autocomplete/nsAutoCompleteController.h
+++ b/toolkit/components/autocomplete/nsAutoCompleteController.h
@@ -118,20 +118,18 @@ protected:
   nsresult RowIndexToSearch(int32_t aRowIndex,
                             int32_t *aSearchIndex, int32_t *aItemIndex);
 
   // members //////////////////////////////////////////
   
   nsCOMPtr<nsIAutoCompleteInput> mInput;
 
   nsCOMArray<nsIAutoCompleteSearch> mSearches;
+  // This is used as a sparse array, always use SafeObjectAt to access it.
   nsCOMArray<nsIAutoCompleteResult> mResults;
-  // Caches the match counts for the current ongoing results to allow
-  // incremental results to keep the rowcount up to date.
-  nsTArray<uint32_t> mMatchCounts;
   // Temporarily keeps the results alive while invoking startSearch() for each
   // search.  This is needed to allow the searches to reuse the previous result,
   // since otherwise the first search clears mResults.
   nsCOMArray<nsIAutoCompleteResult> mResultCache;
 
   nsCOMPtr<nsITimer> mTimer;
   nsCOMPtr<nsITreeSelection> mSelection;
   nsCOMPtr<nsITreeBoxObject> mTree;
--- a/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.cpp
+++ b/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.cpp
@@ -17,39 +17,110 @@ NS_IMPL_ISUPPORTS(nsAutoCompleteSimpleRe
                   nsIAutoCompleteSimpleResult)
 
 struct AutoCompleteSimpleResultMatch
 {
   AutoCompleteSimpleResultMatch(const nsAString& aValue,
                                 const nsAString& aComment,
                                 const nsAString& aImage,
                                 const nsAString& aStyle,
-                                const nsAString& aFinalCompleteValue)
+                                const nsAString& aFinalCompleteValue,
+                                const nsAString& aLabel)
     : mValue(aValue)
     , mComment(aComment)
     , mImage(aImage)
     , mStyle(aStyle)
     , mFinalCompleteValue(aFinalCompleteValue)
+    , mLabel(aLabel)
   {
   }
 
   nsString mValue;
   nsString mComment;
   nsString mImage;
   nsString mStyle;
   nsString mFinalCompleteValue;
+  nsString mLabel;
 };
 
 nsAutoCompleteSimpleResult::nsAutoCompleteSimpleResult() :
   mDefaultIndex(-1),
   mSearchResult(RESULT_NOMATCH),
   mTypeAheadResult(false)
 {
 }
 
+nsresult
+nsAutoCompleteSimpleResult::AppendResult(nsIAutoCompleteResult* aResult)
+{
+  nsAutoString searchString;
+  nsresult rv = aResult->GetSearchString(searchString);
+  NS_ENSURE_SUCCESS(rv, rv);
+  mSearchString = searchString;
+
+  uint16_t searchResult;
+  rv = aResult->GetSearchResult(&searchResult);
+  NS_ENSURE_SUCCESS(rv, rv);
+  mSearchResult = searchResult;
+
+  nsAutoString errorDescription;
+  if (NS_SUCCEEDED(aResult->GetErrorDescription(errorDescription)) &&
+      !errorDescription.IsEmpty()) {
+    mErrorDescription = errorDescription;
+  }
+
+  bool typeAheadResult = false;
+  if (NS_SUCCEEDED(aResult->GetTypeAheadResult(&typeAheadResult)) &&
+      typeAheadResult) {
+    mTypeAheadResult = typeAheadResult;
+  }
+
+  int32_t defaultIndex = -1;
+  if (NS_SUCCEEDED(aResult->GetDefaultIndex(&defaultIndex)) &&
+      defaultIndex >= 0) {
+    mDefaultIndex = defaultIndex;
+  }
+
+  nsCOMPtr<nsIAutoCompleteSimpleResult> simpleResult =
+    do_QueryInterface(aResult);
+  if (simpleResult) {
+    nsCOMPtr<nsIAutoCompleteSimpleResultListener> listener;
+    if (NS_SUCCEEDED(simpleResult->GetListener(getter_AddRefs(listener))) &&
+        listener) {
+      listener.swap(mListener);
+    }
+  }
+
+  // Copy matches.
+  uint32_t matchCount = 0;
+  rv = aResult->GetMatchCount(&matchCount);
+  NS_ENSURE_SUCCESS(rv, rv);
+  for (size_t i = 0; i < matchCount; ++i) {
+    nsAutoString value, comment, image, style, finalCompleteValue, label;
+
+    rv = aResult->GetValueAt(i, value);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = aResult->GetCommentAt(i, comment);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = aResult->GetImageAt(i, image);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = aResult->GetStyleAt(i, style);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = aResult->GetFinalCompleteValueAt(i, finalCompleteValue);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = aResult->GetLabelAt(i, label);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = AppendMatch(value, comment, image, style, finalCompleteValue, label);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
 // searchString
 NS_IMETHODIMP
 nsAutoCompleteSimpleResult::GetSearchString(nsAString &aSearchString)
 {
   aSearchString = mSearchString;
   return NS_OK;
 }
 NS_IMETHODIMP
@@ -117,38 +188,40 @@ nsAutoCompleteSimpleResult::SetTypeAhead
 }
 
 NS_IMETHODIMP
 nsAutoCompleteSimpleResult::InsertMatchAt(int32_t aIndex,
                                           const nsAString& aValue,
                                           const nsAString& aComment,
                                           const nsAString& aImage,
                                           const nsAString& aStyle,
-                                          const nsAString& aFinalCompleteValue)
+                                          const nsAString& aFinalCompleteValue,
+                                          const nsAString& aLabel)
 {
   CHECK_MATCH_INDEX(aIndex, true);
 
-  AutoCompleteSimpleResultMatch match(aValue, aComment, aImage, aStyle, aFinalCompleteValue);
+  AutoCompleteSimpleResultMatch match(aValue, aComment, aImage, aStyle, aFinalCompleteValue, aLabel);
 
   if (!mMatches.InsertElementAt(aIndex, match)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAutoCompleteSimpleResult::AppendMatch(const nsAString& aValue,
                                         const nsAString& aComment,
                                         const nsAString& aImage,
                                         const nsAString& aStyle,
-                                        const nsAString& aFinalCompleteValue)
+                                        const nsAString& aFinalCompleteValue,
+                                        const nsAString& aLabel)
 {
   return InsertMatchAt(mMatches.Length(), aValue, aComment, aImage, aStyle,
-                       aFinalCompleteValue);
+                       aFinalCompleteValue, aLabel);
 }
 
 NS_IMETHODIMP
 nsAutoCompleteSimpleResult::GetMatchCount(uint32_t *aMatchCount)
 {
   *aMatchCount = mMatches.Length();
   return NS_OK;
 }
@@ -159,17 +232,22 @@ nsAutoCompleteSimpleResult::GetValueAt(i
   CHECK_MATCH_INDEX(aIndex, false);
   _retval = mMatches[aIndex].mValue;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAutoCompleteSimpleResult::GetLabelAt(int32_t aIndex, nsAString& _retval)
 {
-  return GetValueAt(aIndex, _retval);
+  CHECK_MATCH_INDEX(aIndex, false);
+  _retval = mMatches[aIndex].mLabel;
+  if (_retval.IsEmpty()) {
+    _retval = mMatches[aIndex].mValue;
+  }
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAutoCompleteSimpleResult::GetCommentAt(int32_t aIndex, nsAString& _retval)
 {
   CHECK_MATCH_INDEX(aIndex, false);
   _retval = mMatches[aIndex].mComment;
   return NS_OK;
@@ -192,34 +270,44 @@ nsAutoCompleteSimpleResult::GetStyleAt(i
 }
 
 NS_IMETHODIMP
 nsAutoCompleteSimpleResult::GetFinalCompleteValueAt(int32_t aIndex,
                                                     nsAString& _retval)
 {
   CHECK_MATCH_INDEX(aIndex, false);
   _retval = mMatches[aIndex].mFinalCompleteValue;
-  if (_retval.Length() == 0)
+  if (_retval.IsEmpty()) {
     _retval = mMatches[aIndex].mValue;
+  }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAutoCompleteSimpleResult::SetListener(nsIAutoCompleteSimpleResultListener* aListener)
 {
   mListener = aListener;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsAutoCompleteSimpleResult::GetListener(nsIAutoCompleteSimpleResultListener** aListener)
+{
+  nsCOMPtr<nsIAutoCompleteSimpleResultListener> listener(mListener);
+  listener.forget(aListener);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsAutoCompleteSimpleResult::RemoveValueAt(int32_t aRowIndex,
                                           bool aRemoveFromDb)
 {
   CHECK_MATCH_INDEX(aRowIndex, false);
 
   nsString value = mMatches[aRowIndex].mValue;
   mMatches.RemoveElementAt(aRowIndex);
 
-  if (mListener)
+  if (mListener) {
     mListener->OnValueRemoved(this, value, aRemoveFromDb);
+  }
 
   return NS_OK;
 }
--- a/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.h
+++ b/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.h
@@ -19,16 +19,18 @@ class nsAutoCompleteSimpleResult final :
 {
 public:
   nsAutoCompleteSimpleResult();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIAUTOCOMPLETERESULT
   NS_DECL_NSIAUTOCOMPLETESIMPLERESULT
 
+  nsresult AppendResult(nsIAutoCompleteResult* aResult);
+
 private:
   ~nsAutoCompleteSimpleResult() {}
 
 protected:
   typedef nsTArray<AutoCompleteSimpleResultMatch> MatchesArray;
    MatchesArray mMatches;
 
   nsString mSearchString;
--- a/toolkit/components/autocomplete/nsIAutoCompleteSimpleResult.idl
+++ b/toolkit/components/autocomplete/nsIAutoCompleteSimpleResult.idl
@@ -9,17 +9,17 @@ interface nsIAutoCompleteSimpleResultLis
 
 /**
  * This class implements nsIAutoCompleteResult and provides simple methods
  * for setting the value and result items. It can be used whenever some basic
  * auto complete results are needed that can be pre-generated and filled into
  * an array.
  */
 
-[scriptable, uuid(457ce8da-9631-45c5-b3b0-293ab0928df1)]
+[scriptable, uuid(23de9c96-becb-4d0d-a9bb-1d131ce361b5)]
 interface nsIAutoCompleteSimpleResult : nsIAutoCompleteResult
 {
   /**
    * A writer for the readonly attribute 'searchString' which should contain
    * the string that the user typed.
    */
   void setSearchString(in AString aSearchString);
 
@@ -64,17 +64,18 @@ interface nsIAutoCompleteSimpleResult : 
    *        Value used when the user confirms selecting this match. If not
    *        provided, aValue will be used.
    */
   void insertMatchAt(in long aIndex,
                      in AString aValue,
                      in AString aComment,
                      [optional] in AString aImage,
                      [optional] in AString aStyle,
-                     [optional] in AString aFinalCompleteValue);
+                     [optional] in AString aFinalCompleteValue,
+                     [optional] in AString aLabel);
 
   /**
    * Appends a match consisting of the given value, comment, image, style and
    * the value to use for defaultIndex completion.
    * @param aValue
    *        The value to autocomplete to
    * @param aComment
    *        Comment shown in the autocomplete widget to describe this match
@@ -85,17 +86,23 @@ interface nsIAutoCompleteSimpleResult : 
    * @param aFinalCompleteValue
    *        Value used when the user confirms selecting this match. If not
    *        provided, aValue will be used.
    */
   void appendMatch(in AString aValue,
                    in AString aComment,
                    [optional] in AString aImage,
                    [optional] in AString aStyle,
-                   [optional] in AString aFinalCompleteValue);
+                   [optional] in AString aFinalCompleteValue,
+                   [optional] in AString aLabel);
+
+  /**
+   * Gets the listener for changes in the result.
+   */
+  nsIAutoCompleteSimpleResultListener getListener();
 
   /**
    * Sets a listener for changes in the result.
    */
   void setListener(in nsIAutoCompleteSimpleResultListener aListener);
 };
 
 [scriptable, uuid(004efdc5-1989-4874-8a7a-345bf2fa33af)]
--- a/toolkit/components/autocomplete/tests/unit/test_393191.js
+++ b/toolkit/components/autocomplete/tests/unit/test_393191.js
@@ -141,25 +141,26 @@ AutoCompleteResult.prototype = {
 
 
 /** 
  * nsIAutoCompleteSearch implementation that always returns
  * the same result set.
  */
 function AutoCompleteSearch(aName, aResult) {
   this.name = aName;
+  this._result = aResult;
 }
 AutoCompleteSearch.prototype = {
   constructor: AutoCompleteSearch,
   
   // Search name. Used by AutoCompleteController
   name: null,
 
   // AutoCompleteResult
-  _result:null,  
+  _result: null,
   
   
   /**
    * Return the same result set for every search
    */
   startSearch: function(aSearchString, 
                         aSearchParam, 
                         aPreviousResult, 
--- a/toolkit/components/autocomplete/tests/unit/test_660156.js
+++ b/toolkit/components/autocomplete/tests/unit/test_660156.js
@@ -1,25 +1,27 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
 /**
  * Search object that returns results at different times.
  * First, the search that returns results asynchronously.
  */
 function AutoCompleteAsyncSearch(aName, aResult) {
   this.name = aName;
   this._result = aResult;
 }
 AutoCompleteAsyncSearch.prototype = Object.create(AutoCompleteSearchBase.prototype);
-AutoCompleteAsyncSearch.prototype.startSearch = function(aSearchString, 
-                                                         aSearchParam, 
-                                                         aPreviousResult, 
+AutoCompleteAsyncSearch.prototype.startSearch = function(aSearchString,
+                                                         aSearchParam,
+                                                         aPreviousResult,
                                                          aListener) {
-  setTimeout(this._returnResults.bind(this), 500, aListener);
+  this._result.searchResult = Ci.nsIAutoCompleteResult.RESULT_NOMATCH_ONGOING;
+  aListener.onSearchResult(this, this._result);
+
+  do_timeout(500, () => {
+    this._returnResults(aListener);
+  });
 };
 
 AutoCompleteAsyncSearch.prototype._returnResults = function(aListener) {
   var result = this._result;
 
   result.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
   aListener.onSearchResult(this, result);
 };
@@ -27,57 +29,57 @@ AutoCompleteAsyncSearch.prototype._retur
 /**
  * The synchronous version
  */
 function AutoCompleteSyncSearch(aName, aResult) {
   this.name = aName;
   this._result = aResult;
 }
 AutoCompleteSyncSearch.prototype = Object.create(AutoCompleteAsyncSearch.prototype);
-AutoCompleteSyncSearch.prototype.startSearch = function(aSearchString, 
-                                                        aSearchParam, 
-                                                        aPreviousResult, 
+AutoCompleteSyncSearch.prototype.startSearch = function(aSearchString,
+                                                        aSearchParam,
+                                                        aPreviousResult,
                                                         aListener) {
   this._returnResults(aListener);
 };
 
 /**
  * Results object
  */
 function AutoCompleteResult(aValues, aDefaultIndex) {
   this._values = aValues;
   this.defaultIndex = aDefaultIndex;
 }
 AutoCompleteResult.prototype = Object.create(AutoCompleteResultBase.prototype);
 
 
-/** 
+/**
  * Test AutoComplete with multiple AutoCompleteSearch sources, with one of them
  * (index != 0) returning before the rest.
  */
 function run_test() {
   do_test_pending();
 
   var results = ["mozillaTest"];
   var inputStr = "moz";
 
   // Async search
-  var asyncSearch = new AutoCompleteAsyncSearch("Async", 
+  var asyncSearch = new AutoCompleteAsyncSearch("Async",
                                                 new AutoCompleteResult(results, -1));
   // Sync search
   var syncSearch = new AutoCompleteSyncSearch("Sync",
                                               new AutoCompleteResult(results, 0));
-  
+
   // Register searches so AutoCompleteController can find them
   registerAutoCompleteSearch(asyncSearch);
   registerAutoCompleteSearch(syncSearch);
-    
+
   var controller = Cc["@mozilla.org/autocomplete/controller;1"].
-                   getService(Ci.nsIAutoCompleteController);  
-  
+                   getService(Ci.nsIAutoCompleteController);
+
   // Make an AutoCompleteInput that uses our searches
   // and confirms results on search complete.
   // Async search MUST be FIRST to trigger the bug this tests.
   var input = new AutoCompleteInputBase([asyncSearch.name, syncSearch.name]);
   input.completeDefaultIndex = true;
   input.textValue = inputStr;
 
   // Caret must be at the end. Autofill doesn't happen unless you're typing
--- a/toolkit/components/satchel/nsFormAutoComplete.js
+++ b/toolkit/components/satchel/nsFormAutoComplete.js
@@ -280,17 +280,17 @@ FormAutoComplete.prototype = {
             let processEntry = (aEntries) => {
                 if (aField && aField.maxLength > -1) {
                     result.entries =
                         aEntries.filter(function (el) { return el.text.length <= aField.maxLength; });
                 } else {
                     result.entries = aEntries;
                 }
 
-                if (aDatalistResult) {
+                if (aDatalistResult && aDatalistResult.matchCount > 0) {
                     result = this.mergeResults(result, aDatalistResult);
                 }
 
                 if (aListener) {
                     aListener.onSearchCompletion(result);
                 }
             }
 
--- a/toolkit/components/satchel/nsFormAutoCompleteResult.jsm
+++ b/toolkit/components/satchel/nsFormAutoCompleteResult.jsm
@@ -105,17 +105,17 @@ FormAutoCompleteResult.prototype = {
    */
   getValueAt: function(index) {
     this._checkIndexBounds(index);
     return this._values[index];
   },
 
   getLabelAt: function(index) {
     this._checkIndexBounds(index);
-    return this._labels[index];
+    return this._labels[index] || this._values[index];
   },
 
   /**
    * Retrieves a comment (metadata instance)
    * @param  index    the index of the comment requested
    * @return          the comment at the specified index
    */
   getCommentAt: function(index) {
--- a/toolkit/components/satchel/nsFormFillController.cpp
+++ b/toolkit/components/satchel/nsFormFillController.cpp
@@ -722,21 +722,22 @@ class UpdateSearchResultRunnable : publi
 {
 public:
   UpdateSearchResultRunnable(nsIAutoCompleteObserver* aObserver,
                              nsIAutoCompleteSearch* aSearch,
                              nsIAutoCompleteResult* aResult)
     : mObserver(aObserver)
     , mSearch(aSearch)
     , mResult(aResult)
-  {}
+  {
+    MOZ_ASSERT(mResult, "Should have a valid result");
+    MOZ_ASSERT(mObserver, "You shouldn't call this runnable with a null observer!");
+  }
 
   NS_IMETHOD Run() {
-    NS_ASSERTION(mObserver, "You shouldn't call this runnable with a null observer!");
-
     mObserver->OnUpdateSearchResult(mSearch, mResult);
     return NS_OK;
   }
 
 private:
   nsCOMPtr<nsIAutoCompleteObserver> mObserver;
   nsCOMPtr<nsIAutoCompleteSearch> mSearch;
   nsCOMPtr<nsIAutoCompleteResult> mResult;
--- a/toolkit/components/satchel/nsInputListAutoComplete.js
+++ b/toolkit/components/satchel/nsInputListAutoComplete.js
@@ -11,22 +11,22 @@ Components.utils.import("resource://gre/
 function InputListAutoComplete() {}
 
 InputListAutoComplete.prototype = {
   classID       : Components.ID("{bf1e01d0-953e-11df-981c-0800200c9a66}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIInputListAutoComplete]),
 
   autoCompleteSearch : function (aUntrimmedSearchString, aField) {
     let [values, labels] = this.getListSuggestions(aField);
-    if (values.length === 0)
-      return null;
+    let searchResult = values.length > 0 ? Ci.nsIAutoCompleteResult.RESULT_SUCCESS
+                                         : Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
+    let defaultIndex = values.length > 0 ? 0 : -1;
     return new FormAutoCompleteResult(aUntrimmedSearchString,
-                                      Ci.nsIAutoCompleteResult.RESULT_SUCCESS,
-                                      0, "", values, labels,
-                                      [], null);
+                                      searchResult, defaultIndex, "",
+                                      values, labels, [], null);
   },
 
   getListSuggestions : function (aField) {
     let values = [];
     let labels = [];
 
     if (aField) {
       let filter = !aField.hasAttribute("mozNoFilter");