toolkit/components/autocomplete/nsAutoCompleteController.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "nsAutoCompleteController.h"
     7 #include "nsAutoCompleteSimpleResult.h"
     9 #include "nsAutoPtr.h"
    10 #include "nsNetCID.h"
    11 #include "nsIIOService.h"
    12 #include "nsToolkitCompsCID.h"
    13 #include "nsIServiceManager.h"
    14 #include "nsIAtomService.h"
    15 #include "nsReadableUtils.h"
    16 #include "nsUnicharUtils.h"
    17 #include "nsITreeBoxObject.h"
    18 #include "nsITreeColumns.h"
    19 #include "nsIObserverService.h"
    20 #include "nsIDOMKeyEvent.h"
    21 #include "mozilla/Services.h"
    22 #include "mozilla/ModuleUtils.h"
    24 static const char *kAutoCompleteSearchCID = "@mozilla.org/autocomplete/search;1?name=";
    26 NS_IMPL_CYCLE_COLLECTION_CLASS(nsAutoCompleteController)
    28 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsAutoCompleteController)
    29   tmp->SetInput(nullptr);
    30 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
    31 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsAutoCompleteController)
    32   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInput)
    33   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSearches)
    34   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResults)
    35 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
    37 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsAutoCompleteController)
    38 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsAutoCompleteController)
    39 NS_INTERFACE_TABLE_HEAD(nsAutoCompleteController)
    40   NS_INTERFACE_TABLE(nsAutoCompleteController, nsIAutoCompleteController,
    41                      nsIAutoCompleteObserver, nsITimerCallback, nsITreeView)
    42   NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsAutoCompleteController)
    43 NS_INTERFACE_MAP_END
    45 nsAutoCompleteController::nsAutoCompleteController() :
    46   mDefaultIndexCompleted(false),
    47   mBackspaced(false),
    48   mPopupClosedByCompositionStart(false),
    49   mCompositionState(eCompositionState_None),
    50   mSearchStatus(nsAutoCompleteController::STATUS_NONE),
    51   mRowCount(0),
    52   mSearchesOngoing(0),
    53   mSearchesFailed(0),
    54   mFirstSearchResult(false),
    55   mImmediateSearchesCount(0)
    56 {
    57 }
    59 nsAutoCompleteController::~nsAutoCompleteController()
    60 {
    61   SetInput(nullptr);
    62 }
    64 ////////////////////////////////////////////////////////////////////////
    65 //// nsIAutoCompleteController
    67 NS_IMETHODIMP
    68 nsAutoCompleteController::GetSearchStatus(uint16_t *aSearchStatus)
    69 {
    70   *aSearchStatus = mSearchStatus;
    71   return NS_OK;
    72 }
    74 NS_IMETHODIMP
    75 nsAutoCompleteController::GetMatchCount(uint32_t *aMatchCount)
    76 {
    77   *aMatchCount = mRowCount;
    78   return NS_OK;
    79 }
    81 NS_IMETHODIMP
    82 nsAutoCompleteController::GetInput(nsIAutoCompleteInput **aInput)
    83 {
    84   *aInput = mInput;
    85   NS_IF_ADDREF(*aInput);
    86   return NS_OK;
    87 }
    89 NS_IMETHODIMP
    90 nsAutoCompleteController::SetInput(nsIAutoCompleteInput *aInput)
    91 {
    92   // Don't do anything if the input isn't changing.
    93   if (mInput == aInput)
    94     return NS_OK;
    96   // Clear out the current search context
    97   if (mInput) {
    98     // Stop all searches in case they are async.
    99     StopSearch();
   100     ClearResults();
   101     ClosePopup();
   102     mSearches.Clear();
   103   }
   105   mInput = aInput;
   107   // Nothing more to do if the input was just being set to null.
   108   if (!aInput)
   109     return NS_OK;
   111   nsAutoString newValue;
   112   aInput->GetTextValue(newValue);
   114   // Clear out this reference in case the new input's popup has no tree
   115   mTree = nullptr;
   117   // Reset all search state members to default values
   118   mSearchString = newValue;
   119   mDefaultIndexCompleted = false;
   120   mBackspaced = false;
   121   mSearchStatus = nsIAutoCompleteController::STATUS_NONE;
   122   mRowCount = 0;
   123   mSearchesOngoing = 0;
   125   // Initialize our list of search objects
   126   uint32_t searchCount;
   127   aInput->GetSearchCount(&searchCount);
   128   mResults.SetCapacity(searchCount);
   129   mSearches.SetCapacity(searchCount);
   130   mMatchCounts.SetLength(searchCount);
   131   mImmediateSearchesCount = 0;
   133   const char *searchCID = kAutoCompleteSearchCID;
   135   for (uint32_t i = 0; i < searchCount; ++i) {
   136     // Use the search name to create the contract id string for the search service
   137     nsAutoCString searchName;
   138     aInput->GetSearchAt(i, searchName);
   139     nsAutoCString cid(searchCID);
   140     cid.Append(searchName);
   142     // Use the created cid to get a pointer to the search service and store it for later
   143     nsCOMPtr<nsIAutoCompleteSearch> search = do_GetService(cid.get());
   144     if (search) {
   145       mSearches.AppendObject(search);
   147       // Count immediate searches.
   148       uint16_t searchType = nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_DELAYED;
   149       nsCOMPtr<nsIAutoCompleteSearchDescriptor> searchDesc =
   150         do_QueryInterface(search);
   151       if (searchDesc && NS_SUCCEEDED(searchDesc->GetSearchType(&searchType)) &&
   152           searchType == nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_IMMEDIATE)
   153         mImmediateSearchesCount++;
   154     }
   155   }
   157   return NS_OK;
   158 }
   160 NS_IMETHODIMP
   161 nsAutoCompleteController::StartSearch(const nsAString &aSearchString)
   162 {
   163   mSearchString = aSearchString;
   164   StartSearches();
   165   return NS_OK;
   166 }
   168 NS_IMETHODIMP
   169 nsAutoCompleteController::HandleText()
   170 {
   171   // Note: the events occur in the following order when IME is used.
   172   // 1. a compositionstart event(HandleStartComposition)
   173   // 2. some input events (HandleText), eCompositionState_Composing
   174   // 3. a compositionend event(HandleEndComposition)
   175   // 4. an input event(HandleText), eCompositionState_Committing
   176   // We should do nothing during composition.
   177   if (mCompositionState == eCompositionState_Composing) {
   178     return NS_OK;
   179   }
   181   bool handlingCompositionCommit =
   182     (mCompositionState == eCompositionState_Committing);
   183   bool popupClosedByCompositionStart = mPopupClosedByCompositionStart;
   184   if (handlingCompositionCommit) {
   185     mCompositionState = eCompositionState_None;
   186     mPopupClosedByCompositionStart = false;
   187   }
   189   if (!mInput) {
   190     // Stop all searches in case they are async.
   191     StopSearch();
   192     // Note: if now is after blur and IME end composition,
   193     // check mInput before calling.
   194     // See https://bugzilla.mozilla.org/show_bug.cgi?id=193544#c31
   195     NS_ERROR("Called before attaching to the control or after detaching from the control");
   196     return NS_OK;
   197   }
   199   nsAutoString newValue;
   200   nsCOMPtr<nsIAutoCompleteInput> input(mInput);
   201   input->GetTextValue(newValue);
   203   // Stop all searches in case they are async.
   204   StopSearch();
   206   if (!mInput) {
   207     // StopSearch() can call PostSearchCleanup() which might result
   208     // in a blur event, which could null out mInput, so we need to check it
   209     // again.  See bug #395344 for more details
   210     return NS_OK;
   211   }
   213   bool disabled;
   214   input->GetDisableAutoComplete(&disabled);
   215   NS_ENSURE_TRUE(!disabled, NS_OK);
   217   // Don't search again if the new string is the same as the last search
   218   // However, if this is called immediately after compositionend event,
   219   // we need to search the same value again since the search was canceled
   220   // at compositionstart event handler.
   221   if (!handlingCompositionCommit && newValue.Length() > 0 &&
   222       newValue.Equals(mSearchString)) {
   223     return NS_OK;
   224   }
   226   // Determine if the user has removed text from the end (probably by backspacing)
   227   if (newValue.Length() < mSearchString.Length() &&
   228       Substring(mSearchString, 0, newValue.Length()).Equals(newValue))
   229   {
   230     // We need to throw away previous results so we don't try to search through them again
   231     ClearResults();
   232     mBackspaced = true;
   233   } else
   234     mBackspaced = false;
   236   mSearchString = newValue;
   238   // Don't search if the value is empty
   239   if (newValue.Length() == 0) {
   240     // If autocomplete popup was closed by compositionstart event handler,
   241     // we should reopen it forcibly even if the value is empty.
   242     if (popupClosedByCompositionStart && handlingCompositionCommit) {
   243       bool cancel;
   244       HandleKeyNavigation(nsIDOMKeyEvent::DOM_VK_DOWN, &cancel);
   245       return NS_OK;
   246     }
   247     ClosePopup();
   248     return NS_OK;
   249   }
   251   StartSearches();
   253   return NS_OK;
   254 }
   256 NS_IMETHODIMP
   257 nsAutoCompleteController::HandleEnter(bool aIsPopupSelection, bool *_retval)
   258 {
   259   *_retval = false;
   260   if (!mInput)
   261     return NS_OK;
   263   // allow the event through unless there is something selected in the popup
   264   mInput->GetPopupOpen(_retval);
   265   if (*_retval) {
   266     nsCOMPtr<nsIAutoCompletePopup> popup;
   267     mInput->GetPopup(getter_AddRefs(popup));
   269     if (popup) {
   270       int32_t selectedIndex;
   271       popup->GetSelectedIndex(&selectedIndex);
   272       *_retval = selectedIndex >= 0;
   273     }
   274   }
   276   // Stop the search, and handle the enter.
   277   StopSearch();
   278   EnterMatch(aIsPopupSelection);
   280   return NS_OK;
   281 }
   283 NS_IMETHODIMP
   284 nsAutoCompleteController::HandleEscape(bool *_retval)
   285 {
   286   *_retval = false;
   287   if (!mInput)
   288     return NS_OK;
   290   // allow the event through if the popup is closed
   291   mInput->GetPopupOpen(_retval);
   293   // Stop all searches in case they are async.
   294   StopSearch();
   295   ClearResults();
   296   RevertTextValue();
   297   ClosePopup();
   299   return NS_OK;
   300 }
   302 NS_IMETHODIMP
   303 nsAutoCompleteController::HandleStartComposition()
   304 {
   305   NS_ENSURE_TRUE(mCompositionState != eCompositionState_Composing, NS_OK);
   307   mPopupClosedByCompositionStart = false;
   308   mCompositionState = eCompositionState_Composing;
   310   if (!mInput)
   311     return NS_OK;
   313   nsCOMPtr<nsIAutoCompleteInput> input(mInput);
   314   bool disabled;
   315   input->GetDisableAutoComplete(&disabled);
   316   if (disabled)
   317     return NS_OK;
   319   // Stop all searches in case they are async.
   320   StopSearch();
   322   bool isOpen = false;
   323   input->GetPopupOpen(&isOpen);
   324   if (isOpen) {
   325     ClosePopup();
   327     bool stillOpen = false;
   328     input->GetPopupOpen(&stillOpen);
   329     mPopupClosedByCompositionStart = !stillOpen;
   330   }
   331   return NS_OK;
   332 }
   334 NS_IMETHODIMP
   335 nsAutoCompleteController::HandleEndComposition()
   336 {
   337   NS_ENSURE_TRUE(mCompositionState == eCompositionState_Composing, NS_OK);
   339   // We can't yet retrieve the committed value from the editor, since it isn't
   340   // completely committed yet. Set mCompositionState to
   341   // eCompositionState_Committing, so that when HandleText() is called (in
   342   // response to the "input" event), we know that we should handle the
   343   // committed text.
   344   mCompositionState = eCompositionState_Committing;
   345   return NS_OK;
   346 }
   348 NS_IMETHODIMP
   349 nsAutoCompleteController::HandleTab()
   350 {
   351   bool cancel;
   352   return HandleEnter(false, &cancel);
   353 }
   355 NS_IMETHODIMP
   356 nsAutoCompleteController::HandleKeyNavigation(uint32_t aKey, bool *_retval)
   357 {
   358   // By default, don't cancel the event
   359   *_retval = false;
   361   if (!mInput) {
   362     // Stop all searches in case they are async.
   363     StopSearch();
   364     // Note: if now is after blur and IME end composition,
   365     // check mInput before calling.
   366     // See https://bugzilla.mozilla.org/show_bug.cgi?id=193544#c31
   367     NS_ERROR("Called before attaching to the control or after detaching from the control");
   368     return NS_OK;
   369   }
   371   nsCOMPtr<nsIAutoCompleteInput> input(mInput);
   372   nsCOMPtr<nsIAutoCompletePopup> popup;
   373   input->GetPopup(getter_AddRefs(popup));
   374   NS_ENSURE_TRUE(popup != nullptr, NS_ERROR_FAILURE);
   376   bool disabled;
   377   input->GetDisableAutoComplete(&disabled);
   378   NS_ENSURE_TRUE(!disabled, NS_OK);
   380   if (aKey == nsIDOMKeyEvent::DOM_VK_UP ||
   381       aKey == nsIDOMKeyEvent::DOM_VK_DOWN ||
   382       aKey == nsIDOMKeyEvent::DOM_VK_PAGE_UP ||
   383       aKey == nsIDOMKeyEvent::DOM_VK_PAGE_DOWN)
   384   {
   385     // Prevent the input from handling up/down events, as it may move
   386     // the cursor to home/end on some systems
   387     *_retval = true;
   389     bool isOpen = false;
   390     input->GetPopupOpen(&isOpen);
   391     if (isOpen) {
   392       bool reverse = aKey == nsIDOMKeyEvent::DOM_VK_UP ||
   393                       aKey == nsIDOMKeyEvent::DOM_VK_PAGE_UP ? true : false;
   394       bool page = aKey == nsIDOMKeyEvent::DOM_VK_PAGE_UP ||
   395                     aKey == nsIDOMKeyEvent::DOM_VK_PAGE_DOWN ? true : false;
   397       // Fill in the value of the textbox with whatever is selected in the popup
   398       // if the completeSelectedIndex attribute is set.  We check this before
   399       // calling SelectBy of an earlier attempt to avoid crashing.
   400       bool completeSelection;
   401       input->GetCompleteSelectedIndex(&completeSelection);
   403       // Instruct the result view to scroll by the given amount and direction
   404       popup->SelectBy(reverse, page);
   406       if (completeSelection)
   407       {
   408         int32_t selectedIndex;
   409         popup->GetSelectedIndex(&selectedIndex);
   410         if (selectedIndex >= 0) {
   411           //  A result is selected, so fill in its value
   412           nsAutoString value;
   413           if (NS_SUCCEEDED(GetResultValueAt(selectedIndex, false, value))) {
   414             input->SetTextValue(value);
   415             input->SelectTextRange(value.Length(), value.Length());
   416           }
   417         } else {
   418           // Nothing is selected, so fill in the last typed value
   419           input->SetTextValue(mSearchString);
   420           input->SelectTextRange(mSearchString.Length(), mSearchString.Length());
   421         }
   422       }
   423     } else {
   424 #ifdef XP_MACOSX
   425       // on Mac, only show the popup if the caret is at the start or end of
   426       // the input and there is no selection, so that the default defined key
   427       // shortcuts for up and down move to the beginning and end of the field
   428       // otherwise.
   429       int32_t start, end;
   430       if (aKey == nsIDOMKeyEvent::DOM_VK_UP) {
   431         input->GetSelectionStart(&start);
   432         input->GetSelectionEnd(&end);
   433         if (start > 0 || start != end)
   434           *_retval = false;
   435       }
   436       else if (aKey == nsIDOMKeyEvent::DOM_VK_DOWN) {
   437         nsAutoString text;
   438         input->GetTextValue(text);
   439         input->GetSelectionStart(&start);
   440         input->GetSelectionEnd(&end);
   441         if (start != end || end < (int32_t)text.Length())
   442           *_retval = false;
   443       }
   444 #endif
   445       if (*_retval) {
   446         // Open the popup if there has been a previous search, or else kick off a new search
   447         if (!mResults.IsEmpty()) {
   448           if (mRowCount) {
   449             OpenPopup();
   450           }
   451         } else {
   452           // Stop all searches in case they are async.
   453           StopSearch();
   455           if (!mInput) {
   456             // StopSearch() can call PostSearchCleanup() which might result
   457             // in a blur event, which could null out mInput, so we need to check it
   458             // again.  See bug #395344 for more details
   459             return NS_OK;
   460           }
   462           StartSearches();
   463         }
   464       }
   465     }
   466   } else if (   aKey == nsIDOMKeyEvent::DOM_VK_LEFT
   467              || aKey == nsIDOMKeyEvent::DOM_VK_RIGHT
   468 #ifndef XP_MACOSX
   469              || aKey == nsIDOMKeyEvent::DOM_VK_HOME
   470 #endif
   471             )
   472   {
   473     // The user hit a text-navigation key.
   474     bool isOpen = false;
   475     input->GetPopupOpen(&isOpen);
   476     if (isOpen) {
   477       int32_t selectedIndex;
   478       popup->GetSelectedIndex(&selectedIndex);
   479       bool shouldComplete;
   480       input->GetCompleteDefaultIndex(&shouldComplete);
   481       if (selectedIndex >= 0) {
   482         // The pop-up is open and has a selection, take its value
   483         nsAutoString value;
   484         if (NS_SUCCEEDED(GetResultValueAt(selectedIndex, false, value))) {
   485           input->SetTextValue(value);
   486           input->SelectTextRange(value.Length(), value.Length());
   487         }
   488       }
   489       else if (shouldComplete) {
   490         // We usually try to preserve the casing of what user has typed, but
   491         // if he wants to autocomplete, we will replace the value with the
   492         // actual autocomplete result.
   493         // The user wants explicitely to use that result, so this ensures
   494         // association of the result with the autocompleted text.
   495         nsAutoString value;
   496         nsAutoString inputValue;
   497         input->GetTextValue(inputValue);
   498         if (NS_SUCCEEDED(GetDefaultCompleteValue(-1, false, value)) &&
   499             value.Equals(inputValue, nsCaseInsensitiveStringComparator())) {
   500           input->SetTextValue(value);
   501           input->SelectTextRange(value.Length(), value.Length());
   502         }
   503       }
   504       // Close the pop-up even if nothing was selected
   505       ClearSearchTimer();
   506       ClosePopup();
   507     }
   508     // Update last-searched string to the current input, since the input may
   509     // have changed.  Without this, subsequent backspaces look like text
   510     // additions, not text deletions.
   511     nsAutoString value;
   512     input->GetTextValue(value);
   513     mSearchString = value;
   514   }
   516   return NS_OK;
   517 }
   519 NS_IMETHODIMP
   520 nsAutoCompleteController::HandleDelete(bool *_retval)
   521 {
   522   *_retval = false;
   523   if (!mInput)
   524     return NS_OK;
   526   nsCOMPtr<nsIAutoCompleteInput> input(mInput);
   527   bool isOpen = false;
   528   input->GetPopupOpen(&isOpen);
   529   if (!isOpen || mRowCount <= 0) {
   530     // Nothing left to delete, proceed as normal
   531     HandleText();
   532     return NS_OK;
   533   }
   535   nsCOMPtr<nsIAutoCompletePopup> popup;
   536   input->GetPopup(getter_AddRefs(popup));
   538   int32_t index, searchIndex, rowIndex;
   539   popup->GetSelectedIndex(&index);
   540   if (index == -1) {
   541     // No row is selected in the list
   542     HandleText();
   543     return NS_OK;
   544   }
   546   RowIndexToSearch(index, &searchIndex, &rowIndex);
   547   NS_ENSURE_TRUE(searchIndex >= 0 && rowIndex >= 0, NS_ERROR_FAILURE);
   549   nsIAutoCompleteResult *result = mResults[searchIndex];
   550   NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);
   552   nsAutoString search;
   553   input->GetSearchParam(search);
   555   // Clear the row in our result and in the DB.
   556   result->RemoveValueAt(rowIndex, true);
   557   --mRowCount;
   559   // We removed it, so make sure we cancel the event that triggered this call.
   560   *_retval = true;
   562   // Unselect the current item.
   563   popup->SetSelectedIndex(-1);
   565   // Tell the tree that the row count changed.
   566   if (mTree)
   567     mTree->RowCountChanged(mRowCount, -1);
   569   // Adjust index, if needed.
   570   if (index >= (int32_t)mRowCount)
   571     index = mRowCount - 1;
   573   if (mRowCount > 0) {
   574     // There are still rows in the popup, select the current index again.
   575     popup->SetSelectedIndex(index);
   577     // Complete to the new current value.
   578     bool shouldComplete = false;
   579     input->GetCompleteDefaultIndex(&shouldComplete);
   580     if (shouldComplete) {
   581       nsAutoString value;
   582       if (NS_SUCCEEDED(GetResultValueAt(index, false, value))) {
   583         CompleteValue(value);
   584       }
   585     }
   587     // Invalidate the popup.
   588     popup->Invalidate();
   589   } else {
   590     // Nothing left in the popup, clear any pending search timers and
   591     // close the popup.
   592     ClearSearchTimer();
   593     uint32_t minResults;
   594     input->GetMinResultsForPopup(&minResults);
   595     if (minResults) {
   596       ClosePopup();
   597     }
   598   }
   600   return NS_OK;
   601 }
   603 nsresult 
   604 nsAutoCompleteController::GetResultAt(int32_t aIndex, nsIAutoCompleteResult** aResult,
   605                                       int32_t* aRowIndex)
   606 {
   607   int32_t searchIndex;
   608   RowIndexToSearch(aIndex, &searchIndex, aRowIndex);
   609   NS_ENSURE_TRUE(searchIndex >= 0 && *aRowIndex >= 0, NS_ERROR_FAILURE);
   611   *aResult = mResults[searchIndex];
   612   NS_ENSURE_TRUE(*aResult, NS_ERROR_FAILURE);
   613   return NS_OK;
   614 }
   616 NS_IMETHODIMP
   617 nsAutoCompleteController::GetValueAt(int32_t aIndex, nsAString & _retval)
   618 {
   619   GetResultLabelAt(aIndex, _retval);
   621   return NS_OK;
   622 }
   624 NS_IMETHODIMP
   625 nsAutoCompleteController::GetLabelAt(int32_t aIndex, nsAString & _retval)
   626 {
   627   GetResultLabelAt(aIndex, _retval);
   629   return NS_OK;
   630 }
   632 NS_IMETHODIMP
   633 nsAutoCompleteController::GetCommentAt(int32_t aIndex, nsAString & _retval)
   634 {
   635   int32_t rowIndex;
   636   nsIAutoCompleteResult* result;
   637   nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
   638   NS_ENSURE_SUCCESS(rv, rv);
   640   return result->GetCommentAt(rowIndex, _retval);
   641 }
   643 NS_IMETHODIMP
   644 nsAutoCompleteController::GetStyleAt(int32_t aIndex, nsAString & _retval)
   645 {
   646   int32_t rowIndex;
   647   nsIAutoCompleteResult* result;
   648   nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
   649   NS_ENSURE_SUCCESS(rv, rv);
   651   return result->GetStyleAt(rowIndex, _retval);
   652 }
   654 NS_IMETHODIMP
   655 nsAutoCompleteController::GetImageAt(int32_t aIndex, nsAString & _retval)
   656 {
   657   int32_t rowIndex;
   658   nsIAutoCompleteResult* result;
   659   nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
   660   NS_ENSURE_SUCCESS(rv, rv);
   662   return result->GetImageAt(rowIndex, _retval);
   663 }
   665 NS_IMETHODIMP
   666 nsAutoCompleteController::GetFinalCompleteValueAt(int32_t aIndex,
   667                                                   nsAString & _retval)
   668 {
   669   int32_t rowIndex;
   670   nsIAutoCompleteResult* result;
   671   nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
   672   NS_ENSURE_SUCCESS(rv, rv);
   674   return result->GetFinalCompleteValueAt(rowIndex, _retval);
   675 }
   677 NS_IMETHODIMP
   678 nsAutoCompleteController::SetSearchString(const nsAString &aSearchString)
   679 {
   680   mSearchString = aSearchString;
   681   return NS_OK;
   682 }
   684 NS_IMETHODIMP
   685 nsAutoCompleteController::GetSearchString(nsAString &aSearchString)
   686 {
   687   aSearchString = mSearchString;
   688   return NS_OK;
   689 }
   692 ////////////////////////////////////////////////////////////////////////
   693 //// nsIAutoCompleteObserver
   695 NS_IMETHODIMP
   696 nsAutoCompleteController::OnUpdateSearchResult(nsIAutoCompleteSearch *aSearch, nsIAutoCompleteResult* aResult)
   697 {
   698   ClearResults();
   699   return OnSearchResult(aSearch, aResult);
   700 }
   702 NS_IMETHODIMP
   703 nsAutoCompleteController::OnSearchResult(nsIAutoCompleteSearch *aSearch, nsIAutoCompleteResult* aResult)
   704 {
   705   // look up the index of the search which is returning
   706   for (uint32_t i = 0; i < mSearches.Length(); ++i) {
   707     if (mSearches[i] == aSearch) {
   708       ProcessResult(i, aResult);
   709     }
   710   }
   712   return NS_OK;
   713 }
   715 ////////////////////////////////////////////////////////////////////////
   716 //// nsITimerCallback
   718 NS_IMETHODIMP
   719 nsAutoCompleteController::Notify(nsITimer *timer)
   720 {
   721   mTimer = nullptr;
   723   if (mImmediateSearchesCount == 0) {
   724     // If there were no immediate searches, BeforeSearches has not yet been
   725     // called, so do it now.
   726     nsresult rv = BeforeSearches();
   727     if (NS_FAILED(rv))
   728       return rv;
   729   }
   730   StartSearch(nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_DELAYED);
   731   AfterSearches();
   732   return NS_OK;
   733 }
   735 ////////////////////////////////////////////////////////////////////////
   736 // nsITreeView
   738 NS_IMETHODIMP
   739 nsAutoCompleteController::GetRowCount(int32_t *aRowCount)
   740 {
   741   *aRowCount = mRowCount;
   742   return NS_OK;
   743 }
   745 NS_IMETHODIMP
   746 nsAutoCompleteController::GetRowProperties(int32_t index, nsAString& aProps)
   747 {
   748   return NS_OK;
   749 }
   751 NS_IMETHODIMP
   752 nsAutoCompleteController::GetCellProperties(int32_t row, nsITreeColumn* col,
   753                                             nsAString& aProps)
   754 {
   755   if (row >= 0) {
   756     GetStyleAt(row, aProps);
   757   }
   759   return NS_OK;
   760 }
   762 NS_IMETHODIMP
   763 nsAutoCompleteController::GetColumnProperties(nsITreeColumn* col, nsAString& aProps)
   764 {
   765   return NS_OK;
   766 }
   768 NS_IMETHODIMP
   769 nsAutoCompleteController::GetImageSrc(int32_t row, nsITreeColumn* col, nsAString& _retval)
   770 {
   771   const char16_t* colID;
   772   col->GetIdConst(&colID);
   774   if (NS_LITERAL_STRING("treecolAutoCompleteValue").Equals(colID))
   775     return GetImageAt(row, _retval);
   777   return NS_OK;
   778 }
   780 NS_IMETHODIMP
   781 nsAutoCompleteController::GetProgressMode(int32_t row, nsITreeColumn* col, int32_t* _retval)
   782 {
   783   NS_NOTREACHED("tree has no progress cells");
   784   return NS_OK;
   785 }
   787 NS_IMETHODIMP
   788 nsAutoCompleteController::GetCellValue(int32_t row, nsITreeColumn* col, nsAString& _retval)
   789 {
   790   NS_NOTREACHED("all of our cells are text");
   791   return NS_OK;
   792 }
   794 NS_IMETHODIMP
   795 nsAutoCompleteController::GetCellText(int32_t row, nsITreeColumn* col, nsAString& _retval)
   796 {
   797   const char16_t* colID;
   798   col->GetIdConst(&colID);
   800   if (NS_LITERAL_STRING("treecolAutoCompleteValue").Equals(colID))
   801     GetValueAt(row, _retval);
   802   else if (NS_LITERAL_STRING("treecolAutoCompleteComment").Equals(colID))
   803     GetCommentAt(row, _retval);
   805   return NS_OK;
   806 }
   808 NS_IMETHODIMP
   809 nsAutoCompleteController::IsContainer(int32_t index, bool *_retval)
   810 {
   811   *_retval = false;
   812   return NS_OK;
   813 }
   815 NS_IMETHODIMP
   816 nsAutoCompleteController::IsContainerOpen(int32_t index, bool *_retval)
   817 {
   818   NS_NOTREACHED("no container cells");
   819   return NS_OK;
   820 }
   822 NS_IMETHODIMP
   823 nsAutoCompleteController::IsContainerEmpty(int32_t index, bool *_retval)
   824 {
   825   NS_NOTREACHED("no container cells");
   826   return NS_OK;
   827 }
   829 NS_IMETHODIMP
   830 nsAutoCompleteController::GetLevel(int32_t index, int32_t *_retval)
   831 {
   832   *_retval = 0;
   833   return NS_OK;
   834 }
   836 NS_IMETHODIMP
   837 nsAutoCompleteController::GetParentIndex(int32_t rowIndex, int32_t *_retval)
   838 {
   839   *_retval = -1;
   840   return NS_OK;
   841 }
   843 NS_IMETHODIMP
   844 nsAutoCompleteController::HasNextSibling(int32_t rowIndex, int32_t afterIndex, bool *_retval)
   845 {
   846   *_retval = false;
   847   return NS_OK;
   848 }
   850 NS_IMETHODIMP
   851 nsAutoCompleteController::ToggleOpenState(int32_t index)
   852 {
   853   return NS_OK;
   854 }
   856 NS_IMETHODIMP
   857 nsAutoCompleteController::SetTree(nsITreeBoxObject *tree)
   858 {
   859   mTree = tree;
   860   return NS_OK;
   861 }
   863 NS_IMETHODIMP
   864 nsAutoCompleteController::GetSelection(nsITreeSelection * *aSelection)
   865 {
   866   *aSelection = mSelection;
   867   NS_IF_ADDREF(*aSelection);
   868   return NS_OK;
   869 }
   871 NS_IMETHODIMP nsAutoCompleteController::SetSelection(nsITreeSelection * aSelection)
   872 {
   873   mSelection = aSelection;
   874   return NS_OK;
   875 }
   877 NS_IMETHODIMP
   878 nsAutoCompleteController::SelectionChanged()
   879 {
   880   return NS_OK;
   881 }
   883 NS_IMETHODIMP
   884 nsAutoCompleteController::SetCellValue(int32_t row, nsITreeColumn* col, const nsAString& value)
   885 {
   886   return NS_OK;
   887 }
   889 NS_IMETHODIMP
   890 nsAutoCompleteController::SetCellText(int32_t row, nsITreeColumn* col, const nsAString& value)
   891 {
   892   return NS_OK;
   893 }
   895 NS_IMETHODIMP
   896 nsAutoCompleteController::CycleHeader(nsITreeColumn* col)
   897 {
   898   return NS_OK;
   899 }
   901 NS_IMETHODIMP
   902 nsAutoCompleteController::CycleCell(int32_t row, nsITreeColumn* col)
   903 {
   904   return NS_OK;
   905 }
   907 NS_IMETHODIMP
   908 nsAutoCompleteController::IsEditable(int32_t row, nsITreeColumn* col, bool *_retval)
   909 {
   910   *_retval = false;
   911   return NS_OK;
   912 }
   914 NS_IMETHODIMP
   915 nsAutoCompleteController::IsSelectable(int32_t row, nsITreeColumn* col, bool *_retval)
   916 {
   917   *_retval = false;
   918   return NS_OK;
   919 }
   921 NS_IMETHODIMP
   922 nsAutoCompleteController::IsSeparator(int32_t index, bool *_retval)
   923 {
   924   *_retval = false;
   925   return NS_OK;
   926 }
   928 NS_IMETHODIMP
   929 nsAutoCompleteController::IsSorted(bool *_retval)
   930 {
   931   *_retval = false;
   932   return NS_OK;
   933 }
   935 NS_IMETHODIMP
   936 nsAutoCompleteController::CanDrop(int32_t index, int32_t orientation,
   937                                   nsIDOMDataTransfer* dataTransfer, bool *_retval)
   938 {
   939   return NS_OK;
   940 }
   942 NS_IMETHODIMP
   943 nsAutoCompleteController::Drop(int32_t row, int32_t orientation, nsIDOMDataTransfer* dataTransfer)
   944 {
   945   return NS_OK;
   946 }
   948 NS_IMETHODIMP
   949 nsAutoCompleteController::PerformAction(const char16_t *action)
   950 {
   951   return NS_OK;
   952 }
   954 NS_IMETHODIMP
   955 nsAutoCompleteController::PerformActionOnRow(const char16_t *action, int32_t row)
   956 {
   957   return NS_OK;
   958 }
   960 NS_IMETHODIMP
   961 nsAutoCompleteController::PerformActionOnCell(const char16_t* action, int32_t row, nsITreeColumn* col)
   962 {
   963   return NS_OK;
   964 }
   966 ////////////////////////////////////////////////////////////////////////
   967 //// nsAutoCompleteController
   969 nsresult
   970 nsAutoCompleteController::OpenPopup()
   971 {
   972   uint32_t minResults;
   973   mInput->GetMinResultsForPopup(&minResults);
   975   if (mRowCount >= minResults) {
   976     return mInput->SetPopupOpen(true);
   977   }
   979   return NS_OK;
   980 }
   982 nsresult
   983 nsAutoCompleteController::ClosePopup()
   984 {
   985   if (!mInput) {
   986     return NS_OK;
   987   }
   989   bool isOpen = false;
   990   mInput->GetPopupOpen(&isOpen);
   991   if (!isOpen)
   992     return NS_OK;
   994   nsCOMPtr<nsIAutoCompletePopup> popup;
   995   mInput->GetPopup(getter_AddRefs(popup));
   996   NS_ENSURE_TRUE(popup != nullptr, NS_ERROR_FAILURE);
   997   popup->SetSelectedIndex(-1);
   998   return mInput->SetPopupOpen(false);
   999 }
  1001 nsresult
  1002 nsAutoCompleteController::BeforeSearches()
  1004   NS_ENSURE_STATE(mInput);
  1006   mSearchStatus = nsIAutoCompleteController::STATUS_SEARCHING;
  1007   mDefaultIndexCompleted = false;
  1009   // The first search result will clear mResults array, though we should pass
  1010   // the previous result to each search to allow them to reuse it.  So we
  1011   // temporarily cache current results till AfterSearches().
  1012   if (!mResultCache.AppendObjects(mResults)) {
  1013     return NS_ERROR_OUT_OF_MEMORY;
  1016   mSearchesOngoing = mSearches.Length();
  1017   mSearchesFailed = 0;
  1018   mFirstSearchResult = true;
  1020   // notify the input that the search is beginning
  1021   mInput->OnSearchBegin();
  1023   return NS_OK;
  1026 nsresult
  1027 nsAutoCompleteController::StartSearch(uint16_t aSearchType)
  1029   NS_ENSURE_STATE(mInput);
  1030   nsCOMPtr<nsIAutoCompleteInput> input = mInput;
  1032   for (uint32_t i = 0; i < mSearches.Length(); ++i) {
  1033     nsCOMPtr<nsIAutoCompleteSearch> search = mSearches[i];
  1035     // Filter on search type.  Not all the searches implement this interface,
  1036     // in such a case just consider them delayed.
  1037     uint16_t searchType = nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_DELAYED;
  1038     nsCOMPtr<nsIAutoCompleteSearchDescriptor> searchDesc =
  1039       do_QueryInterface(search);
  1040     if (searchDesc)
  1041       searchDesc->GetSearchType(&searchType);
  1042     if (searchType != aSearchType)
  1043       continue;
  1045     nsIAutoCompleteResult *result = mResultCache.SafeObjectAt(i);
  1047     if (result) {
  1048       uint16_t searchResult;
  1049       result->GetSearchResult(&searchResult);
  1050       if (searchResult != nsIAutoCompleteResult::RESULT_SUCCESS &&
  1051           searchResult != nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING &&
  1052           searchResult != nsIAutoCompleteResult::RESULT_NOMATCH)
  1053         result = nullptr;
  1056     nsAutoString searchParam;
  1057     nsresult rv = input->GetSearchParam(searchParam);
  1058     if (NS_FAILED(rv))
  1059         return rv;
  1061     rv = search->StartSearch(mSearchString, searchParam, result, static_cast<nsIAutoCompleteObserver *>(this));
  1062     if (NS_FAILED(rv)) {
  1063       ++mSearchesFailed;
  1064       --mSearchesOngoing;
  1066     // Because of the joy of nested event loops (which can easily happen when some
  1067     // code uses a generator for an asynchronous AutoComplete search),
  1068     // nsIAutoCompleteSearch::StartSearch might cause us to be detached from our input
  1069     // field.  The next time we iterate, we'd be touching something that we shouldn't
  1070     // be, and result in a crash.
  1071     if (!mInput) {
  1072       // The search operation has been finished.
  1073       return NS_OK;
  1077   return NS_OK;
  1080 void
  1081 nsAutoCompleteController::AfterSearches()
  1083   mResultCache.Clear();
  1084   if (mSearchesFailed == mSearches.Length())
  1085     PostSearchCleanup();
  1088 NS_IMETHODIMP
  1089 nsAutoCompleteController::StopSearch()
  1091   // Stop the timer if there is one
  1092   ClearSearchTimer();
  1094   // Stop any ongoing asynchronous searches
  1095   if (mSearchStatus == nsIAutoCompleteController::STATUS_SEARCHING) {
  1096     for (uint32_t i = 0; i < mSearches.Length(); ++i) {
  1097       nsCOMPtr<nsIAutoCompleteSearch> search = mSearches[i];
  1098       search->StopSearch();
  1100     mSearchesOngoing = 0;
  1101     // since we were searching, but now we've stopped,
  1102     // we need to call PostSearchCleanup()
  1103     PostSearchCleanup();
  1105   return NS_OK;
  1108 nsresult
  1109 nsAutoCompleteController::StartSearches()
  1111   // Don't create a new search timer if we're already waiting for one to fire.
  1112   // If we don't check for this, we won't be able to cancel the original timer
  1113   // and may crash when it fires (bug 236659).
  1114   if (mTimer || !mInput)
  1115     return NS_OK;
  1117   // Get the timeout for delayed searches.
  1118   uint32_t timeout;
  1119   mInput->GetTimeout(&timeout);
  1121   uint32_t immediateSearchesCount = mImmediateSearchesCount;
  1122   if (timeout == 0) {
  1123     // All the searches should be executed immediately.
  1124     immediateSearchesCount = mSearches.Length();
  1127   if (immediateSearchesCount > 0) {
  1128     nsresult rv = BeforeSearches();
  1129     if (NS_FAILED(rv))
  1130       return rv;
  1131     StartSearch(nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_IMMEDIATE);
  1133     if (mSearches.Length() == immediateSearchesCount) {
  1134       // Either all searches are immediate, or the timeout is 0.  In the
  1135       // latter case we still have to execute the delayed searches, otherwise
  1136       // this will be a no-op.
  1137       StartSearch(nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_DELAYED);
  1139       // All the searches have been started, just finish.
  1140       AfterSearches();
  1141       return NS_OK;
  1145   MOZ_ASSERT(timeout > 0, "Trying to delay searches with a 0 timeout!");
  1147   // Now start the delayed searches.
  1148   nsresult rv;
  1149   mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
  1150   if (NS_FAILED(rv))
  1151       return rv;
  1152   rv = mTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT);
  1153   if (NS_FAILED(rv))
  1154       mTimer = nullptr;
  1156   return rv;
  1159 nsresult
  1160 nsAutoCompleteController::ClearSearchTimer()
  1162   if (mTimer) {
  1163     mTimer->Cancel();
  1164     mTimer = nullptr;
  1166   return NS_OK;
  1169 nsresult
  1170 nsAutoCompleteController::EnterMatch(bool aIsPopupSelection)
  1172   nsCOMPtr<nsIAutoCompleteInput> input(mInput);
  1173   nsCOMPtr<nsIAutoCompletePopup> popup;
  1174   input->GetPopup(getter_AddRefs(popup));
  1175   NS_ENSURE_TRUE(popup != nullptr, NS_ERROR_FAILURE);
  1177   bool forceComplete;
  1178   input->GetForceComplete(&forceComplete);
  1180   // Ask the popup if it wants to enter a special value into the textbox
  1181   nsAutoString value;
  1182   popup->GetOverrideValue(value);
  1183   if (value.IsEmpty()) {
  1184     bool shouldComplete;
  1185     input->GetCompleteDefaultIndex(&shouldComplete);
  1186     bool completeSelection;
  1187     input->GetCompleteSelectedIndex(&completeSelection);
  1189     int32_t selectedIndex;
  1190     popup->GetSelectedIndex(&selectedIndex);
  1191     if (selectedIndex >= 0) {
  1192       // If completeselectedindex is false or a row was selected from the popup,
  1193       // enter it into the textbox. If completeselectedindex is true, or
  1194       // EnterMatch was called via other means, for instance pressing Enter,
  1195       // don't fill in the value as it will have already been filled in as
  1196       // needed, unless the final complete value differs.
  1197       nsAutoString finalValue, inputValue;
  1198       GetResultValueAt(selectedIndex, true, finalValue);
  1199       input->GetTextValue(inputValue);
  1200       if (!completeSelection || aIsPopupSelection ||
  1201           !finalValue.Equals(inputValue)) {
  1202         value = finalValue;
  1205     else if (shouldComplete) {
  1206       // We usually try to preserve the casing of what user has typed, but
  1207       // if he wants to autocomplete, we will replace the value with the
  1208       // actual autocomplete result.
  1209       // The user wants explicitely to use that result, so this ensures
  1210       // association of the result with the autocompleted text.
  1211       nsAutoString defaultIndexValue;
  1212       if (NS_SUCCEEDED(GetFinalDefaultCompleteValue(defaultIndexValue)))
  1213         value = defaultIndexValue;
  1216     if (forceComplete && value.IsEmpty()) {
  1217       // Since nothing was selected, and forceComplete is specified, that means
  1218       // we have to find the first default match and enter it instead
  1219       for (uint32_t i = 0; i < mResults.Length(); ++i) {
  1220         nsIAutoCompleteResult *result = mResults[i];
  1222         if (result) {
  1223           int32_t defaultIndex;
  1224           result->GetDefaultIndex(&defaultIndex);
  1225           if (defaultIndex >= 0) {
  1226             result->GetFinalCompleteValueAt(defaultIndex, value);
  1227             break;
  1234   nsCOMPtr<nsIObserverService> obsSvc =
  1235     mozilla::services::GetObserverService();
  1236   NS_ENSURE_STATE(obsSvc);
  1237   obsSvc->NotifyObservers(input, "autocomplete-will-enter-text", nullptr);
  1239   if (!value.IsEmpty()) {
  1240     input->SetTextValue(value);
  1241     input->SelectTextRange(value.Length(), value.Length());
  1242     mSearchString = value;
  1245   obsSvc->NotifyObservers(input, "autocomplete-did-enter-text", nullptr);
  1246   ClosePopup();
  1248   bool cancel;
  1249   input->OnTextEntered(&cancel);
  1251   return NS_OK;
  1254 nsresult
  1255 nsAutoCompleteController::RevertTextValue()
  1257   // StopSearch() can call PostSearchCleanup() which might result
  1258   // in a blur event, which could null out mInput, so we need to check it
  1259   // again.  See bug #408463 for more details
  1260   if (!mInput)
  1261     return NS_OK;
  1263   nsAutoString oldValue(mSearchString);
  1264   nsCOMPtr<nsIAutoCompleteInput> input(mInput);
  1266   bool cancel = false;
  1267   input->OnTextReverted(&cancel);
  1269   if (!cancel) {
  1270     nsCOMPtr<nsIObserverService> obsSvc =
  1271       mozilla::services::GetObserverService();
  1272     NS_ENSURE_STATE(obsSvc);
  1273     obsSvc->NotifyObservers(input, "autocomplete-will-revert-text", nullptr);
  1275     nsAutoString inputValue;
  1276     input->GetTextValue(inputValue);
  1277     // Don't change the value if it is the same to prevent sending useless events.
  1278     // NOTE: how can |RevertTextValue| be called with inputValue != oldValue?
  1279     if (!oldValue.Equals(inputValue)) {
  1280       input->SetTextValue(oldValue);
  1283     obsSvc->NotifyObservers(input, "autocomplete-did-revert-text", nullptr);
  1286   return NS_OK;
  1289 nsresult
  1290 nsAutoCompleteController::ProcessResult(int32_t aSearchIndex, nsIAutoCompleteResult *aResult)
  1292   NS_ENSURE_STATE(mInput);
  1293   nsCOMPtr<nsIAutoCompleteInput> input(mInput);
  1295   // If this is the first search result we are processing
  1296   // we should clear out the previously cached results
  1297   if (mFirstSearchResult) {
  1298     ClearResults();
  1299     mFirstSearchResult = false;
  1302   uint16_t result = 0;
  1303   if (aResult)
  1304     aResult->GetSearchResult(&result);
  1306   // if our results are incremental, the search is still ongoing
  1307   if (result != nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING &&
  1308       result != nsIAutoCompleteResult::RESULT_NOMATCH_ONGOING) {
  1309     --mSearchesOngoing;
  1312   uint32_t oldMatchCount = 0;
  1313   uint32_t matchCount = 0;
  1314   if (aResult)
  1315     aResult->GetMatchCount(&matchCount);
  1317   int32_t resultIndex = mResults.IndexOf(aResult);
  1318   if (resultIndex == -1) {
  1319     // cache the result
  1320     mResults.AppendObject(aResult);
  1321     mMatchCounts.AppendElement(matchCount);
  1322     resultIndex = mResults.Count() - 1;
  1324   else {
  1325     oldMatchCount = mMatchCounts[aSearchIndex];
  1326     mMatchCounts[resultIndex] = matchCount;
  1329   bool isTypeAheadResult = false;
  1330   if (aResult) {
  1331     aResult->GetTypeAheadResult(&isTypeAheadResult);
  1334   if (!isTypeAheadResult) {
  1335     uint32_t oldRowCount = mRowCount;
  1336     // If the search failed, increase the match count to include the error
  1337     // description.
  1338     if (result == nsIAutoCompleteResult::RESULT_FAILURE) {
  1339       nsAutoString error;
  1340       aResult->GetErrorDescription(error);
  1341       if (!error.IsEmpty()) {
  1342         ++mRowCount;
  1343         if (mTree) {
  1344           mTree->RowCountChanged(oldRowCount, 1);
  1347     } else if (result == nsIAutoCompleteResult::RESULT_SUCCESS ||
  1348                result == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
  1349       // Increase the match count for all matches in this result.
  1350       mRowCount += matchCount - oldMatchCount;
  1352       if (mTree) {
  1353         mTree->RowCountChanged(oldRowCount, matchCount - oldMatchCount);
  1357     // Refresh the popup view to display the new search results
  1358     nsCOMPtr<nsIAutoCompletePopup> popup;
  1359     input->GetPopup(getter_AddRefs(popup));
  1360     NS_ENSURE_TRUE(popup != nullptr, NS_ERROR_FAILURE);
  1361     popup->Invalidate();
  1363     uint32_t minResults;
  1364     input->GetMinResultsForPopup(&minResults);
  1366     // Make sure the popup is open, if necessary, since we now have at least one
  1367     // search result ready to display. Don't force the popup closed if we might
  1368     // get results in the future to avoid unnecessarily canceling searches.
  1369     if (mRowCount || !minResults) {
  1370       OpenPopup();
  1371     } else if (result != nsIAutoCompleteResult::RESULT_NOMATCH_ONGOING) {
  1372       ClosePopup();
  1376   if (result == nsIAutoCompleteResult::RESULT_SUCCESS ||
  1377       result == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
  1378     // Try to autocomplete the default index for this search.
  1379     CompleteDefaultIndex(resultIndex);
  1382   if (mSearchesOngoing == 0) {
  1383     // If this is the last search to return, cleanup.
  1384     PostSearchCleanup();
  1387   return NS_OK;
  1390 nsresult
  1391 nsAutoCompleteController::PostSearchCleanup()
  1393   NS_ENSURE_STATE(mInput);
  1394   nsCOMPtr<nsIAutoCompleteInput> input(mInput);
  1396   uint32_t minResults;
  1397   input->GetMinResultsForPopup(&minResults);
  1399   if (mRowCount || minResults == 0) {
  1400     OpenPopup();
  1401     if (mRowCount)
  1402       mSearchStatus = nsIAutoCompleteController::STATUS_COMPLETE_MATCH;
  1403     else
  1404       mSearchStatus = nsIAutoCompleteController::STATUS_COMPLETE_NO_MATCH;
  1405   } else {
  1406     mSearchStatus = nsIAutoCompleteController::STATUS_COMPLETE_NO_MATCH;
  1407     ClosePopup();
  1410   // notify the input that the search is complete
  1411   input->OnSearchComplete();
  1413   return NS_OK;
  1416 nsresult
  1417 nsAutoCompleteController::ClearResults()
  1419   int32_t oldRowCount = mRowCount;
  1420   mRowCount = 0;
  1421   mResults.Clear();
  1422   mMatchCounts.Clear();
  1423   if (oldRowCount != 0) {
  1424     if (mTree)
  1425       mTree->RowCountChanged(0, -oldRowCount);
  1426     else if (mInput) {
  1427       nsCOMPtr<nsIAutoCompletePopup> popup;
  1428       mInput->GetPopup(getter_AddRefs(popup));
  1429       NS_ENSURE_TRUE(popup != nullptr, NS_ERROR_FAILURE);
  1430       // if we had a tree, RowCountChanged() would have cleared the selection
  1431       // when the selected row was removed.  But since we don't have a tree,
  1432       // we need to clear the selection manually.
  1433       popup->SetSelectedIndex(-1);
  1436   return NS_OK;
  1439 nsresult
  1440 nsAutoCompleteController::CompleteDefaultIndex(int32_t aResultIndex)
  1442   if (mDefaultIndexCompleted || mBackspaced || mSearchString.Length() == 0 || !mInput)
  1443     return NS_OK;
  1445   int32_t selectionStart;
  1446   mInput->GetSelectionStart(&selectionStart);
  1447   int32_t selectionEnd;
  1448   mInput->GetSelectionEnd(&selectionEnd);
  1450   // Don't try to automatically complete to the first result if there's already
  1451   // a selection or the cursor isn't at the end of the input
  1452   if (selectionEnd != selectionStart ||
  1453       selectionEnd != (int32_t)mSearchString.Length())
  1454     return NS_OK;
  1456   bool shouldComplete;
  1457   mInput->GetCompleteDefaultIndex(&shouldComplete);
  1458   if (!shouldComplete)
  1459     return NS_OK;
  1461   nsAutoString resultValue;
  1462   if (NS_SUCCEEDED(GetDefaultCompleteValue(aResultIndex, true, resultValue)))
  1463     CompleteValue(resultValue);
  1465   mDefaultIndexCompleted = true;
  1467   return NS_OK;
  1470 nsresult
  1471 nsAutoCompleteController::GetDefaultCompleteResult(int32_t aResultIndex,
  1472                                                    nsIAutoCompleteResult** _result,
  1473                                                    int32_t* _defaultIndex)
  1475   *_defaultIndex = -1;
  1476   int32_t resultIndex = aResultIndex;
  1478   // If a result index was not provided, find the first defaultIndex result.
  1479   for (int32_t i = 0; resultIndex < 0 && i < mResults.Count(); ++i) {
  1480     nsIAutoCompleteResult *result = mResults[i];
  1481     if (result &&
  1482         NS_SUCCEEDED(result->GetDefaultIndex(_defaultIndex)) &&
  1483         *_defaultIndex >= 0) {
  1484       resultIndex = i;
  1487   NS_ENSURE_TRUE(resultIndex >= 0, NS_ERROR_FAILURE);
  1489   *_result = mResults.SafeObjectAt(resultIndex);
  1490   NS_ENSURE_TRUE(*_result, NS_ERROR_FAILURE);
  1492   if (*_defaultIndex < 0) {
  1493     // The search must explicitly provide a default index in order
  1494     // for us to be able to complete.
  1495     (*_result)->GetDefaultIndex(_defaultIndex);
  1498   if (*_defaultIndex < 0) {
  1499     // We were given a result index, but that result doesn't want to
  1500     // be autocompleted.
  1501     return NS_ERROR_FAILURE;
  1504   // If the result wrongly notifies a RESULT_SUCCESS with no matches, or
  1505   // provides a defaultIndex greater than its matchCount, avoid trying to
  1506   // complete to an empty value.
  1507   uint32_t matchCount = 0;
  1508   (*_result)->GetMatchCount(&matchCount);
  1509   // Here defaultIndex is surely non-negative, so can be cast to unsigned.
  1510   if ((uint32_t)(*_defaultIndex) >= matchCount) {
  1511     return NS_ERROR_FAILURE;
  1514   return NS_OK;
  1517 nsresult
  1518 nsAutoCompleteController::GetDefaultCompleteValue(int32_t aResultIndex,
  1519                                                   bool aPreserveCasing,
  1520                                                   nsAString &_retval)
  1522   nsIAutoCompleteResult *result;
  1523   int32_t defaultIndex = -1;
  1524   nsresult rv = GetDefaultCompleteResult(aResultIndex, &result, &defaultIndex);
  1525   if (NS_FAILED(rv)) return rv;
  1527   nsAutoString resultValue;
  1528   result->GetValueAt(defaultIndex, resultValue);
  1529   if (aPreserveCasing &&
  1530       StringBeginsWith(resultValue, mSearchString,
  1531                        nsCaseInsensitiveStringComparator())) {
  1532     // We try to preserve user casing, otherwise we would end up changing
  1533     // the case of what he typed, if we have a result with a different casing.
  1534     // For example if we have result "Test", and user starts writing "tuna",
  1535     // after digiting t, we would convert it to T trying to autocomplete "Test".
  1536     // We will still complete to cased "Test" if the user explicitely choose
  1537     // that result, by either selecting it in the results popup, or with
  1538     // keyboard navigation or if autocompleting in the middle.
  1539     nsAutoString casedResultValue;
  1540     casedResultValue.Assign(mSearchString);
  1541     // Use what the user has typed so far.
  1542     casedResultValue.Append(Substring(resultValue,
  1543                                       mSearchString.Length(),
  1544                                       resultValue.Length()));
  1545     _retval = casedResultValue;
  1547   else
  1548     _retval = resultValue;
  1550   return NS_OK;
  1553 nsresult
  1554 nsAutoCompleteController::GetFinalDefaultCompleteValue(nsAString &_retval)
  1556   MOZ_ASSERT(mInput, "Must have a valid input");
  1557   nsIAutoCompleteResult *result;
  1558   int32_t defaultIndex = -1;
  1559   nsresult rv = GetDefaultCompleteResult(-1, &result, &defaultIndex);
  1560   if (NS_FAILED(rv)) return rv;
  1562   result->GetValueAt(defaultIndex, _retval);
  1563   nsAutoString inputValue;
  1564   mInput->GetTextValue(inputValue);
  1565   if (!_retval.Equals(inputValue, nsCaseInsensitiveStringComparator())) {
  1566     return NS_ERROR_FAILURE;
  1569   nsAutoString finalCompleteValue;
  1570   rv = result->GetFinalCompleteValueAt(defaultIndex, finalCompleteValue);
  1571   if (NS_SUCCEEDED(rv)) {
  1572     _retval = finalCompleteValue;
  1575   MOZ_ASSERT(FindInReadable(inputValue, _retval, nsCaseInsensitiveStringComparator()),
  1576              "Return value must include input value.");
  1577   return NS_OK;
  1580 nsresult
  1581 nsAutoCompleteController::CompleteValue(nsString &aValue)
  1582 /* mInput contains mSearchString, which we want to autocomplete to aValue.  If
  1583  * selectDifference is true, select the remaining portion of aValue not
  1584  * contained in mSearchString. */
  1586   MOZ_ASSERT(mInput, "Must have a valid input");
  1587   const int32_t mSearchStringLength = mSearchString.Length();
  1588   int32_t endSelect = aValue.Length();  // By default, select all of aValue.
  1590   if (aValue.IsEmpty() ||
  1591       StringBeginsWith(aValue, mSearchString,
  1592                        nsCaseInsensitiveStringComparator())) {
  1593     // aValue is empty (we were asked to clear mInput), or mSearchString
  1594     // matches the beginning of aValue.  In either case we can simply
  1595     // autocomplete to aValue.
  1596     mInput->SetTextValue(aValue);
  1597   } else {
  1598     nsresult rv;
  1599     nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
  1600     NS_ENSURE_SUCCESS(rv, rv);
  1601     nsAutoCString scheme;
  1602     if (NS_SUCCEEDED(ios->ExtractScheme(NS_ConvertUTF16toUTF8(aValue), scheme))) {
  1603       // Trying to autocomplete a URI from somewhere other than the beginning.
  1604       // Only succeed if the missing portion is "http://"; otherwise do not
  1605       // autocomplete.  This prevents us from "helpfully" autocompleting to a
  1606       // URI that isn't equivalent to what the user expected.
  1607       const int32_t findIndex = 7; // length of "http://"
  1609       if ((endSelect < findIndex + mSearchStringLength) ||
  1610           !scheme.LowerCaseEqualsLiteral("http") ||
  1611           !Substring(aValue, findIndex, mSearchStringLength).Equals(
  1612             mSearchString, nsCaseInsensitiveStringComparator())) {
  1613         return NS_OK;
  1616       mInput->SetTextValue(mSearchString +
  1617                            Substring(aValue, mSearchStringLength + findIndex,
  1618                                      endSelect));
  1620       endSelect -= findIndex; // We're skipping this many characters of aValue.
  1621     } else {
  1622       // Autocompleting something other than a URI from the middle.
  1623       // Use the format "searchstring >> full string" to indicate to the user
  1624       // what we are going to replace their search string with.
  1625       mInput->SetTextValue(mSearchString + NS_LITERAL_STRING(" >> ") + aValue);
  1627       endSelect = mSearchString.Length() + 4 + aValue.Length();
  1631   mInput->SelectTextRange(mSearchStringLength, endSelect);
  1633   return NS_OK;
  1636 nsresult
  1637 nsAutoCompleteController::GetResultLabelAt(int32_t aIndex, nsAString & _retval)
  1639   return GetResultValueLabelAt(aIndex, false, false, _retval);
  1642 nsresult
  1643 nsAutoCompleteController::GetResultValueAt(int32_t aIndex, bool aGetFinalValue,
  1644                                            nsAString & _retval)
  1646   return GetResultValueLabelAt(aIndex, aGetFinalValue, true, _retval);
  1649 nsresult
  1650 nsAutoCompleteController::GetResultValueLabelAt(int32_t aIndex,
  1651                                                 bool aGetFinalValue,
  1652                                                 bool aGetValue,
  1653                                                 nsAString & _retval)
  1655   NS_ENSURE_TRUE(aIndex >= 0 && (uint32_t) aIndex < mRowCount, NS_ERROR_ILLEGAL_VALUE);
  1657   int32_t rowIndex;
  1658   nsIAutoCompleteResult *result;
  1659   nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
  1660   NS_ENSURE_SUCCESS(rv, rv);
  1662   uint16_t searchResult;
  1663   result->GetSearchResult(&searchResult);
  1665   if (searchResult == nsIAutoCompleteResult::RESULT_FAILURE) {
  1666     if (aGetValue)
  1667       return NS_ERROR_FAILURE;
  1668     result->GetErrorDescription(_retval);
  1669   } else if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS ||
  1670              searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
  1671     if (aGetFinalValue)
  1672       result->GetFinalCompleteValueAt(rowIndex, _retval);
  1673     else if (aGetValue)
  1674       result->GetValueAt(rowIndex, _retval);
  1675     else
  1676       result->GetLabelAt(rowIndex, _retval);
  1679   return NS_OK;
  1682 /**
  1683  * Given the index of a row in the autocomplete popup, find the
  1684  * corresponding nsIAutoCompleteSearch index, and sub-index into
  1685  * the search's results list.
  1686  */
  1687 nsresult
  1688 nsAutoCompleteController::RowIndexToSearch(int32_t aRowIndex, int32_t *aSearchIndex, int32_t *aItemIndex)
  1690   *aSearchIndex = -1;
  1691   *aItemIndex = -1;
  1693   uint32_t index = 0;
  1695   // Move index through the results of each registered nsIAutoCompleteSearch
  1696   // until we find the given row
  1697   for (uint32_t i = 0; i < mSearches.Length(); ++i) {
  1698     nsIAutoCompleteResult *result = mResults.SafeObjectAt(i);
  1699     if (!result)
  1700       continue;
  1702     uint32_t rowCount = 0;
  1704     // Skip past the result completely if it is marked as hidden
  1705     bool isTypeAheadResult = false;
  1706     result->GetTypeAheadResult(&isTypeAheadResult);
  1708     if (!isTypeAheadResult) {
  1709       uint16_t searchResult;
  1710       result->GetSearchResult(&searchResult);
  1712       // Find out how many results were provided by the
  1713       // current nsIAutoCompleteSearch.
  1714       if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS ||
  1715           searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
  1716         result->GetMatchCount(&rowCount);
  1720     // If the given row index is within the results range
  1721     // of the current nsIAutoCompleteSearch then return the
  1722     // search index and sub-index into the results array
  1723     if ((rowCount != 0) && (index + rowCount-1 >= (uint32_t) aRowIndex)) {
  1724       *aSearchIndex = i;
  1725       *aItemIndex = aRowIndex - index;
  1726       return NS_OK;
  1729     // Advance the popup table index cursor past the
  1730     // results of the current search.
  1731     index += rowCount;
  1734   return NS_OK;
  1737 NS_GENERIC_FACTORY_CONSTRUCTOR(nsAutoCompleteController)
  1738 NS_GENERIC_FACTORY_CONSTRUCTOR(nsAutoCompleteSimpleResult)
  1740 NS_DEFINE_NAMED_CID(NS_AUTOCOMPLETECONTROLLER_CID);
  1741 NS_DEFINE_NAMED_CID(NS_AUTOCOMPLETESIMPLERESULT_CID);
  1743 static const mozilla::Module::CIDEntry kAutoCompleteCIDs[] = {
  1744   { &kNS_AUTOCOMPLETECONTROLLER_CID, false, nullptr, nsAutoCompleteControllerConstructor },
  1745   { &kNS_AUTOCOMPLETESIMPLERESULT_CID, false, nullptr, nsAutoCompleteSimpleResultConstructor },
  1746   { nullptr }
  1747 };
  1749 static const mozilla::Module::ContractIDEntry kAutoCompleteContracts[] = {
  1750   { NS_AUTOCOMPLETECONTROLLER_CONTRACTID, &kNS_AUTOCOMPLETECONTROLLER_CID },
  1751   { NS_AUTOCOMPLETESIMPLERESULT_CONTRACTID, &kNS_AUTOCOMPLETESIMPLERESULT_CID },
  1752   { nullptr }
  1753 };
  1755 static const mozilla::Module kAutoCompleteModule = {
  1756   mozilla::Module::kVersion,
  1757   kAutoCompleteCIDs,
  1758   kAutoCompleteContracts
  1759 };
  1761 NSMODULE_DEFN(tkAutoCompleteModule) = &kAutoCompleteModule;

mercurial