toolkit/components/autocomplete/nsAutoCompleteController.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/components/autocomplete/nsAutoCompleteController.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1761 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +#include "nsAutoCompleteController.h"
    1.10 +#include "nsAutoCompleteSimpleResult.h"
    1.11 +
    1.12 +#include "nsAutoPtr.h"
    1.13 +#include "nsNetCID.h"
    1.14 +#include "nsIIOService.h"
    1.15 +#include "nsToolkitCompsCID.h"
    1.16 +#include "nsIServiceManager.h"
    1.17 +#include "nsIAtomService.h"
    1.18 +#include "nsReadableUtils.h"
    1.19 +#include "nsUnicharUtils.h"
    1.20 +#include "nsITreeBoxObject.h"
    1.21 +#include "nsITreeColumns.h"
    1.22 +#include "nsIObserverService.h"
    1.23 +#include "nsIDOMKeyEvent.h"
    1.24 +#include "mozilla/Services.h"
    1.25 +#include "mozilla/ModuleUtils.h"
    1.26 +
    1.27 +static const char *kAutoCompleteSearchCID = "@mozilla.org/autocomplete/search;1?name=";
    1.28 +
    1.29 +NS_IMPL_CYCLE_COLLECTION_CLASS(nsAutoCompleteController)
    1.30 +
    1.31 +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsAutoCompleteController)
    1.32 +  tmp->SetInput(nullptr);
    1.33 +NS_IMPL_CYCLE_COLLECTION_UNLINK_END
    1.34 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsAutoCompleteController)
    1.35 +  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInput)
    1.36 +  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSearches)
    1.37 +  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResults)
    1.38 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
    1.39 +
    1.40 +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsAutoCompleteController)
    1.41 +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsAutoCompleteController)
    1.42 +NS_INTERFACE_TABLE_HEAD(nsAutoCompleteController)
    1.43 +  NS_INTERFACE_TABLE(nsAutoCompleteController, nsIAutoCompleteController,
    1.44 +                     nsIAutoCompleteObserver, nsITimerCallback, nsITreeView)
    1.45 +  NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsAutoCompleteController)
    1.46 +NS_INTERFACE_MAP_END
    1.47 +
    1.48 +nsAutoCompleteController::nsAutoCompleteController() :
    1.49 +  mDefaultIndexCompleted(false),
    1.50 +  mBackspaced(false),
    1.51 +  mPopupClosedByCompositionStart(false),
    1.52 +  mCompositionState(eCompositionState_None),
    1.53 +  mSearchStatus(nsAutoCompleteController::STATUS_NONE),
    1.54 +  mRowCount(0),
    1.55 +  mSearchesOngoing(0),
    1.56 +  mSearchesFailed(0),
    1.57 +  mFirstSearchResult(false),
    1.58 +  mImmediateSearchesCount(0)
    1.59 +{
    1.60 +}
    1.61 +
    1.62 +nsAutoCompleteController::~nsAutoCompleteController()
    1.63 +{
    1.64 +  SetInput(nullptr);
    1.65 +}
    1.66 +
    1.67 +////////////////////////////////////////////////////////////////////////
    1.68 +//// nsIAutoCompleteController
    1.69 +
    1.70 +NS_IMETHODIMP
    1.71 +nsAutoCompleteController::GetSearchStatus(uint16_t *aSearchStatus)
    1.72 +{
    1.73 +  *aSearchStatus = mSearchStatus;
    1.74 +  return NS_OK;
    1.75 +}
    1.76 +
    1.77 +NS_IMETHODIMP
    1.78 +nsAutoCompleteController::GetMatchCount(uint32_t *aMatchCount)
    1.79 +{
    1.80 +  *aMatchCount = mRowCount;
    1.81 +  return NS_OK;
    1.82 +}
    1.83 +
    1.84 +NS_IMETHODIMP
    1.85 +nsAutoCompleteController::GetInput(nsIAutoCompleteInput **aInput)
    1.86 +{
    1.87 +  *aInput = mInput;
    1.88 +  NS_IF_ADDREF(*aInput);
    1.89 +  return NS_OK;
    1.90 +}
    1.91 +
    1.92 +NS_IMETHODIMP
    1.93 +nsAutoCompleteController::SetInput(nsIAutoCompleteInput *aInput)
    1.94 +{
    1.95 +  // Don't do anything if the input isn't changing.
    1.96 +  if (mInput == aInput)
    1.97 +    return NS_OK;
    1.98 +
    1.99 +  // Clear out the current search context
   1.100 +  if (mInput) {
   1.101 +    // Stop all searches in case they are async.
   1.102 +    StopSearch();
   1.103 +    ClearResults();
   1.104 +    ClosePopup();
   1.105 +    mSearches.Clear();
   1.106 +  }
   1.107 +
   1.108 +  mInput = aInput;
   1.109 +
   1.110 +  // Nothing more to do if the input was just being set to null.
   1.111 +  if (!aInput)
   1.112 +    return NS_OK;
   1.113 +
   1.114 +  nsAutoString newValue;
   1.115 +  aInput->GetTextValue(newValue);
   1.116 +
   1.117 +  // Clear out this reference in case the new input's popup has no tree
   1.118 +  mTree = nullptr;
   1.119 +
   1.120 +  // Reset all search state members to default values
   1.121 +  mSearchString = newValue;
   1.122 +  mDefaultIndexCompleted = false;
   1.123 +  mBackspaced = false;
   1.124 +  mSearchStatus = nsIAutoCompleteController::STATUS_NONE;
   1.125 +  mRowCount = 0;
   1.126 +  mSearchesOngoing = 0;
   1.127 +
   1.128 +  // Initialize our list of search objects
   1.129 +  uint32_t searchCount;
   1.130 +  aInput->GetSearchCount(&searchCount);
   1.131 +  mResults.SetCapacity(searchCount);
   1.132 +  mSearches.SetCapacity(searchCount);
   1.133 +  mMatchCounts.SetLength(searchCount);
   1.134 +  mImmediateSearchesCount = 0;
   1.135 +
   1.136 +  const char *searchCID = kAutoCompleteSearchCID;
   1.137 +
   1.138 +  for (uint32_t i = 0; i < searchCount; ++i) {
   1.139 +    // Use the search name to create the contract id string for the search service
   1.140 +    nsAutoCString searchName;
   1.141 +    aInput->GetSearchAt(i, searchName);
   1.142 +    nsAutoCString cid(searchCID);
   1.143 +    cid.Append(searchName);
   1.144 +
   1.145 +    // Use the created cid to get a pointer to the search service and store it for later
   1.146 +    nsCOMPtr<nsIAutoCompleteSearch> search = do_GetService(cid.get());
   1.147 +    if (search) {
   1.148 +      mSearches.AppendObject(search);
   1.149 +
   1.150 +      // Count immediate searches.
   1.151 +      uint16_t searchType = nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_DELAYED;
   1.152 +      nsCOMPtr<nsIAutoCompleteSearchDescriptor> searchDesc =
   1.153 +        do_QueryInterface(search);
   1.154 +      if (searchDesc && NS_SUCCEEDED(searchDesc->GetSearchType(&searchType)) &&
   1.155 +          searchType == nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_IMMEDIATE)
   1.156 +        mImmediateSearchesCount++;
   1.157 +    }
   1.158 +  }
   1.159 +
   1.160 +  return NS_OK;
   1.161 +}
   1.162 +
   1.163 +NS_IMETHODIMP
   1.164 +nsAutoCompleteController::StartSearch(const nsAString &aSearchString)
   1.165 +{
   1.166 +  mSearchString = aSearchString;
   1.167 +  StartSearches();
   1.168 +  return NS_OK;
   1.169 +}
   1.170 +
   1.171 +NS_IMETHODIMP
   1.172 +nsAutoCompleteController::HandleText()
   1.173 +{
   1.174 +  // Note: the events occur in the following order when IME is used.
   1.175 +  // 1. a compositionstart event(HandleStartComposition)
   1.176 +  // 2. some input events (HandleText), eCompositionState_Composing
   1.177 +  // 3. a compositionend event(HandleEndComposition)
   1.178 +  // 4. an input event(HandleText), eCompositionState_Committing
   1.179 +  // We should do nothing during composition.
   1.180 +  if (mCompositionState == eCompositionState_Composing) {
   1.181 +    return NS_OK;
   1.182 +  }
   1.183 +
   1.184 +  bool handlingCompositionCommit =
   1.185 +    (mCompositionState == eCompositionState_Committing);
   1.186 +  bool popupClosedByCompositionStart = mPopupClosedByCompositionStart;
   1.187 +  if (handlingCompositionCommit) {
   1.188 +    mCompositionState = eCompositionState_None;
   1.189 +    mPopupClosedByCompositionStart = false;
   1.190 +  }
   1.191 +
   1.192 +  if (!mInput) {
   1.193 +    // Stop all searches in case they are async.
   1.194 +    StopSearch();
   1.195 +    // Note: if now is after blur and IME end composition,
   1.196 +    // check mInput before calling.
   1.197 +    // See https://bugzilla.mozilla.org/show_bug.cgi?id=193544#c31
   1.198 +    NS_ERROR("Called before attaching to the control or after detaching from the control");
   1.199 +    return NS_OK;
   1.200 +  }
   1.201 +
   1.202 +  nsAutoString newValue;
   1.203 +  nsCOMPtr<nsIAutoCompleteInput> input(mInput);
   1.204 +  input->GetTextValue(newValue);
   1.205 +
   1.206 +  // Stop all searches in case they are async.
   1.207 +  StopSearch();
   1.208 +
   1.209 +  if (!mInput) {
   1.210 +    // StopSearch() can call PostSearchCleanup() which might result
   1.211 +    // in a blur event, which could null out mInput, so we need to check it
   1.212 +    // again.  See bug #395344 for more details
   1.213 +    return NS_OK;
   1.214 +  }
   1.215 +
   1.216 +  bool disabled;
   1.217 +  input->GetDisableAutoComplete(&disabled);
   1.218 +  NS_ENSURE_TRUE(!disabled, NS_OK);
   1.219 +
   1.220 +  // Don't search again if the new string is the same as the last search
   1.221 +  // However, if this is called immediately after compositionend event,
   1.222 +  // we need to search the same value again since the search was canceled
   1.223 +  // at compositionstart event handler.
   1.224 +  if (!handlingCompositionCommit && newValue.Length() > 0 &&
   1.225 +      newValue.Equals(mSearchString)) {
   1.226 +    return NS_OK;
   1.227 +  }
   1.228 +
   1.229 +  // Determine if the user has removed text from the end (probably by backspacing)
   1.230 +  if (newValue.Length() < mSearchString.Length() &&
   1.231 +      Substring(mSearchString, 0, newValue.Length()).Equals(newValue))
   1.232 +  {
   1.233 +    // We need to throw away previous results so we don't try to search through them again
   1.234 +    ClearResults();
   1.235 +    mBackspaced = true;
   1.236 +  } else
   1.237 +    mBackspaced = false;
   1.238 +
   1.239 +  mSearchString = newValue;
   1.240 +
   1.241 +  // Don't search if the value is empty
   1.242 +  if (newValue.Length() == 0) {
   1.243 +    // If autocomplete popup was closed by compositionstart event handler,
   1.244 +    // we should reopen it forcibly even if the value is empty.
   1.245 +    if (popupClosedByCompositionStart && handlingCompositionCommit) {
   1.246 +      bool cancel;
   1.247 +      HandleKeyNavigation(nsIDOMKeyEvent::DOM_VK_DOWN, &cancel);
   1.248 +      return NS_OK;
   1.249 +    }
   1.250 +    ClosePopup();
   1.251 +    return NS_OK;
   1.252 +  }
   1.253 +
   1.254 +  StartSearches();
   1.255 +
   1.256 +  return NS_OK;
   1.257 +}
   1.258 +
   1.259 +NS_IMETHODIMP
   1.260 +nsAutoCompleteController::HandleEnter(bool aIsPopupSelection, bool *_retval)
   1.261 +{
   1.262 +  *_retval = false;
   1.263 +  if (!mInput)
   1.264 +    return NS_OK;
   1.265 +
   1.266 +  // allow the event through unless there is something selected in the popup
   1.267 +  mInput->GetPopupOpen(_retval);
   1.268 +  if (*_retval) {
   1.269 +    nsCOMPtr<nsIAutoCompletePopup> popup;
   1.270 +    mInput->GetPopup(getter_AddRefs(popup));
   1.271 +
   1.272 +    if (popup) {
   1.273 +      int32_t selectedIndex;
   1.274 +      popup->GetSelectedIndex(&selectedIndex);
   1.275 +      *_retval = selectedIndex >= 0;
   1.276 +    }
   1.277 +  }
   1.278 +
   1.279 +  // Stop the search, and handle the enter.
   1.280 +  StopSearch();
   1.281 +  EnterMatch(aIsPopupSelection);
   1.282 +
   1.283 +  return NS_OK;
   1.284 +}
   1.285 +
   1.286 +NS_IMETHODIMP
   1.287 +nsAutoCompleteController::HandleEscape(bool *_retval)
   1.288 +{
   1.289 +  *_retval = false;
   1.290 +  if (!mInput)
   1.291 +    return NS_OK;
   1.292 +
   1.293 +  // allow the event through if the popup is closed
   1.294 +  mInput->GetPopupOpen(_retval);
   1.295 +
   1.296 +  // Stop all searches in case they are async.
   1.297 +  StopSearch();
   1.298 +  ClearResults();
   1.299 +  RevertTextValue();
   1.300 +  ClosePopup();
   1.301 +
   1.302 +  return NS_OK;
   1.303 +}
   1.304 +
   1.305 +NS_IMETHODIMP
   1.306 +nsAutoCompleteController::HandleStartComposition()
   1.307 +{
   1.308 +  NS_ENSURE_TRUE(mCompositionState != eCompositionState_Composing, NS_OK);
   1.309 +
   1.310 +  mPopupClosedByCompositionStart = false;
   1.311 +  mCompositionState = eCompositionState_Composing;
   1.312 +
   1.313 +  if (!mInput)
   1.314 +    return NS_OK;
   1.315 +
   1.316 +  nsCOMPtr<nsIAutoCompleteInput> input(mInput);
   1.317 +  bool disabled;
   1.318 +  input->GetDisableAutoComplete(&disabled);
   1.319 +  if (disabled)
   1.320 +    return NS_OK;
   1.321 +
   1.322 +  // Stop all searches in case they are async.
   1.323 +  StopSearch();
   1.324 +
   1.325 +  bool isOpen = false;
   1.326 +  input->GetPopupOpen(&isOpen);
   1.327 +  if (isOpen) {
   1.328 +    ClosePopup();
   1.329 +
   1.330 +    bool stillOpen = false;
   1.331 +    input->GetPopupOpen(&stillOpen);
   1.332 +    mPopupClosedByCompositionStart = !stillOpen;
   1.333 +  }
   1.334 +  return NS_OK;
   1.335 +}
   1.336 +
   1.337 +NS_IMETHODIMP
   1.338 +nsAutoCompleteController::HandleEndComposition()
   1.339 +{
   1.340 +  NS_ENSURE_TRUE(mCompositionState == eCompositionState_Composing, NS_OK);
   1.341 +
   1.342 +  // We can't yet retrieve the committed value from the editor, since it isn't
   1.343 +  // completely committed yet. Set mCompositionState to
   1.344 +  // eCompositionState_Committing, so that when HandleText() is called (in
   1.345 +  // response to the "input" event), we know that we should handle the
   1.346 +  // committed text.
   1.347 +  mCompositionState = eCompositionState_Committing;
   1.348 +  return NS_OK;
   1.349 +}
   1.350 +
   1.351 +NS_IMETHODIMP
   1.352 +nsAutoCompleteController::HandleTab()
   1.353 +{
   1.354 +  bool cancel;
   1.355 +  return HandleEnter(false, &cancel);
   1.356 +}
   1.357 +
   1.358 +NS_IMETHODIMP
   1.359 +nsAutoCompleteController::HandleKeyNavigation(uint32_t aKey, bool *_retval)
   1.360 +{
   1.361 +  // By default, don't cancel the event
   1.362 +  *_retval = false;
   1.363 +
   1.364 +  if (!mInput) {
   1.365 +    // Stop all searches in case they are async.
   1.366 +    StopSearch();
   1.367 +    // Note: if now is after blur and IME end composition,
   1.368 +    // check mInput before calling.
   1.369 +    // See https://bugzilla.mozilla.org/show_bug.cgi?id=193544#c31
   1.370 +    NS_ERROR("Called before attaching to the control or after detaching from the control");
   1.371 +    return NS_OK;
   1.372 +  }
   1.373 +
   1.374 +  nsCOMPtr<nsIAutoCompleteInput> input(mInput);
   1.375 +  nsCOMPtr<nsIAutoCompletePopup> popup;
   1.376 +  input->GetPopup(getter_AddRefs(popup));
   1.377 +  NS_ENSURE_TRUE(popup != nullptr, NS_ERROR_FAILURE);
   1.378 +
   1.379 +  bool disabled;
   1.380 +  input->GetDisableAutoComplete(&disabled);
   1.381 +  NS_ENSURE_TRUE(!disabled, NS_OK);
   1.382 +
   1.383 +  if (aKey == nsIDOMKeyEvent::DOM_VK_UP ||
   1.384 +      aKey == nsIDOMKeyEvent::DOM_VK_DOWN ||
   1.385 +      aKey == nsIDOMKeyEvent::DOM_VK_PAGE_UP ||
   1.386 +      aKey == nsIDOMKeyEvent::DOM_VK_PAGE_DOWN)
   1.387 +  {
   1.388 +    // Prevent the input from handling up/down events, as it may move
   1.389 +    // the cursor to home/end on some systems
   1.390 +    *_retval = true;
   1.391 +
   1.392 +    bool isOpen = false;
   1.393 +    input->GetPopupOpen(&isOpen);
   1.394 +    if (isOpen) {
   1.395 +      bool reverse = aKey == nsIDOMKeyEvent::DOM_VK_UP ||
   1.396 +                      aKey == nsIDOMKeyEvent::DOM_VK_PAGE_UP ? true : false;
   1.397 +      bool page = aKey == nsIDOMKeyEvent::DOM_VK_PAGE_UP ||
   1.398 +                    aKey == nsIDOMKeyEvent::DOM_VK_PAGE_DOWN ? true : false;
   1.399 +
   1.400 +      // Fill in the value of the textbox with whatever is selected in the popup
   1.401 +      // if the completeSelectedIndex attribute is set.  We check this before
   1.402 +      // calling SelectBy of an earlier attempt to avoid crashing.
   1.403 +      bool completeSelection;
   1.404 +      input->GetCompleteSelectedIndex(&completeSelection);
   1.405 +
   1.406 +      // Instruct the result view to scroll by the given amount and direction
   1.407 +      popup->SelectBy(reverse, page);
   1.408 +
   1.409 +      if (completeSelection)
   1.410 +      {
   1.411 +        int32_t selectedIndex;
   1.412 +        popup->GetSelectedIndex(&selectedIndex);
   1.413 +        if (selectedIndex >= 0) {
   1.414 +          //  A result is selected, so fill in its value
   1.415 +          nsAutoString value;
   1.416 +          if (NS_SUCCEEDED(GetResultValueAt(selectedIndex, false, value))) {
   1.417 +            input->SetTextValue(value);
   1.418 +            input->SelectTextRange(value.Length(), value.Length());
   1.419 +          }
   1.420 +        } else {
   1.421 +          // Nothing is selected, so fill in the last typed value
   1.422 +          input->SetTextValue(mSearchString);
   1.423 +          input->SelectTextRange(mSearchString.Length(), mSearchString.Length());
   1.424 +        }
   1.425 +      }
   1.426 +    } else {
   1.427 +#ifdef XP_MACOSX
   1.428 +      // on Mac, only show the popup if the caret is at the start or end of
   1.429 +      // the input and there is no selection, so that the default defined key
   1.430 +      // shortcuts for up and down move to the beginning and end of the field
   1.431 +      // otherwise.
   1.432 +      int32_t start, end;
   1.433 +      if (aKey == nsIDOMKeyEvent::DOM_VK_UP) {
   1.434 +        input->GetSelectionStart(&start);
   1.435 +        input->GetSelectionEnd(&end);
   1.436 +        if (start > 0 || start != end)
   1.437 +          *_retval = false;
   1.438 +      }
   1.439 +      else if (aKey == nsIDOMKeyEvent::DOM_VK_DOWN) {
   1.440 +        nsAutoString text;
   1.441 +        input->GetTextValue(text);
   1.442 +        input->GetSelectionStart(&start);
   1.443 +        input->GetSelectionEnd(&end);
   1.444 +        if (start != end || end < (int32_t)text.Length())
   1.445 +          *_retval = false;
   1.446 +      }
   1.447 +#endif
   1.448 +      if (*_retval) {
   1.449 +        // Open the popup if there has been a previous search, or else kick off a new search
   1.450 +        if (!mResults.IsEmpty()) {
   1.451 +          if (mRowCount) {
   1.452 +            OpenPopup();
   1.453 +          }
   1.454 +        } else {
   1.455 +          // Stop all searches in case they are async.
   1.456 +          StopSearch();
   1.457 +
   1.458 +          if (!mInput) {
   1.459 +            // StopSearch() can call PostSearchCleanup() which might result
   1.460 +            // in a blur event, which could null out mInput, so we need to check it
   1.461 +            // again.  See bug #395344 for more details
   1.462 +            return NS_OK;
   1.463 +          }
   1.464 +
   1.465 +          StartSearches();
   1.466 +        }
   1.467 +      }
   1.468 +    }
   1.469 +  } else if (   aKey == nsIDOMKeyEvent::DOM_VK_LEFT
   1.470 +             || aKey == nsIDOMKeyEvent::DOM_VK_RIGHT
   1.471 +#ifndef XP_MACOSX
   1.472 +             || aKey == nsIDOMKeyEvent::DOM_VK_HOME
   1.473 +#endif
   1.474 +            )
   1.475 +  {
   1.476 +    // The user hit a text-navigation key.
   1.477 +    bool isOpen = false;
   1.478 +    input->GetPopupOpen(&isOpen);
   1.479 +    if (isOpen) {
   1.480 +      int32_t selectedIndex;
   1.481 +      popup->GetSelectedIndex(&selectedIndex);
   1.482 +      bool shouldComplete;
   1.483 +      input->GetCompleteDefaultIndex(&shouldComplete);
   1.484 +      if (selectedIndex >= 0) {
   1.485 +        // The pop-up is open and has a selection, take its value
   1.486 +        nsAutoString value;
   1.487 +        if (NS_SUCCEEDED(GetResultValueAt(selectedIndex, false, value))) {
   1.488 +          input->SetTextValue(value);
   1.489 +          input->SelectTextRange(value.Length(), value.Length());
   1.490 +        }
   1.491 +      }
   1.492 +      else if (shouldComplete) {
   1.493 +        // We usually try to preserve the casing of what user has typed, but
   1.494 +        // if he wants to autocomplete, we will replace the value with the
   1.495 +        // actual autocomplete result.
   1.496 +        // The user wants explicitely to use that result, so this ensures
   1.497 +        // association of the result with the autocompleted text.
   1.498 +        nsAutoString value;
   1.499 +        nsAutoString inputValue;
   1.500 +        input->GetTextValue(inputValue);
   1.501 +        if (NS_SUCCEEDED(GetDefaultCompleteValue(-1, false, value)) &&
   1.502 +            value.Equals(inputValue, nsCaseInsensitiveStringComparator())) {
   1.503 +          input->SetTextValue(value);
   1.504 +          input->SelectTextRange(value.Length(), value.Length());
   1.505 +        }
   1.506 +      }
   1.507 +      // Close the pop-up even if nothing was selected
   1.508 +      ClearSearchTimer();
   1.509 +      ClosePopup();
   1.510 +    }
   1.511 +    // Update last-searched string to the current input, since the input may
   1.512 +    // have changed.  Without this, subsequent backspaces look like text
   1.513 +    // additions, not text deletions.
   1.514 +    nsAutoString value;
   1.515 +    input->GetTextValue(value);
   1.516 +    mSearchString = value;
   1.517 +  }
   1.518 +
   1.519 +  return NS_OK;
   1.520 +}
   1.521 +
   1.522 +NS_IMETHODIMP
   1.523 +nsAutoCompleteController::HandleDelete(bool *_retval)
   1.524 +{
   1.525 +  *_retval = false;
   1.526 +  if (!mInput)
   1.527 +    return NS_OK;
   1.528 +
   1.529 +  nsCOMPtr<nsIAutoCompleteInput> input(mInput);
   1.530 +  bool isOpen = false;
   1.531 +  input->GetPopupOpen(&isOpen);
   1.532 +  if (!isOpen || mRowCount <= 0) {
   1.533 +    // Nothing left to delete, proceed as normal
   1.534 +    HandleText();
   1.535 +    return NS_OK;
   1.536 +  }
   1.537 +
   1.538 +  nsCOMPtr<nsIAutoCompletePopup> popup;
   1.539 +  input->GetPopup(getter_AddRefs(popup));
   1.540 +
   1.541 +  int32_t index, searchIndex, rowIndex;
   1.542 +  popup->GetSelectedIndex(&index);
   1.543 +  if (index == -1) {
   1.544 +    // No row is selected in the list
   1.545 +    HandleText();
   1.546 +    return NS_OK;
   1.547 +  }
   1.548 +
   1.549 +  RowIndexToSearch(index, &searchIndex, &rowIndex);
   1.550 +  NS_ENSURE_TRUE(searchIndex >= 0 && rowIndex >= 0, NS_ERROR_FAILURE);
   1.551 +
   1.552 +  nsIAutoCompleteResult *result = mResults[searchIndex];
   1.553 +  NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);
   1.554 +
   1.555 +  nsAutoString search;
   1.556 +  input->GetSearchParam(search);
   1.557 +
   1.558 +  // Clear the row in our result and in the DB.
   1.559 +  result->RemoveValueAt(rowIndex, true);
   1.560 +  --mRowCount;
   1.561 +
   1.562 +  // We removed it, so make sure we cancel the event that triggered this call.
   1.563 +  *_retval = true;
   1.564 +
   1.565 +  // Unselect the current item.
   1.566 +  popup->SetSelectedIndex(-1);
   1.567 +
   1.568 +  // Tell the tree that the row count changed.
   1.569 +  if (mTree)
   1.570 +    mTree->RowCountChanged(mRowCount, -1);
   1.571 +
   1.572 +  // Adjust index, if needed.
   1.573 +  if (index >= (int32_t)mRowCount)
   1.574 +    index = mRowCount - 1;
   1.575 +
   1.576 +  if (mRowCount > 0) {
   1.577 +    // There are still rows in the popup, select the current index again.
   1.578 +    popup->SetSelectedIndex(index);
   1.579 +
   1.580 +    // Complete to the new current value.
   1.581 +    bool shouldComplete = false;
   1.582 +    input->GetCompleteDefaultIndex(&shouldComplete);
   1.583 +    if (shouldComplete) {
   1.584 +      nsAutoString value;
   1.585 +      if (NS_SUCCEEDED(GetResultValueAt(index, false, value))) {
   1.586 +        CompleteValue(value);
   1.587 +      }
   1.588 +    }
   1.589 +
   1.590 +    // Invalidate the popup.
   1.591 +    popup->Invalidate();
   1.592 +  } else {
   1.593 +    // Nothing left in the popup, clear any pending search timers and
   1.594 +    // close the popup.
   1.595 +    ClearSearchTimer();
   1.596 +    uint32_t minResults;
   1.597 +    input->GetMinResultsForPopup(&minResults);
   1.598 +    if (minResults) {
   1.599 +      ClosePopup();
   1.600 +    }
   1.601 +  }
   1.602 +
   1.603 +  return NS_OK;
   1.604 +}
   1.605 +
   1.606 +nsresult 
   1.607 +nsAutoCompleteController::GetResultAt(int32_t aIndex, nsIAutoCompleteResult** aResult,
   1.608 +                                      int32_t* aRowIndex)
   1.609 +{
   1.610 +  int32_t searchIndex;
   1.611 +  RowIndexToSearch(aIndex, &searchIndex, aRowIndex);
   1.612 +  NS_ENSURE_TRUE(searchIndex >= 0 && *aRowIndex >= 0, NS_ERROR_FAILURE);
   1.613 +
   1.614 +  *aResult = mResults[searchIndex];
   1.615 +  NS_ENSURE_TRUE(*aResult, NS_ERROR_FAILURE);
   1.616 +  return NS_OK;
   1.617 +}
   1.618 +
   1.619 +NS_IMETHODIMP
   1.620 +nsAutoCompleteController::GetValueAt(int32_t aIndex, nsAString & _retval)
   1.621 +{
   1.622 +  GetResultLabelAt(aIndex, _retval);
   1.623 +
   1.624 +  return NS_OK;
   1.625 +}
   1.626 +
   1.627 +NS_IMETHODIMP
   1.628 +nsAutoCompleteController::GetLabelAt(int32_t aIndex, nsAString & _retval)
   1.629 +{
   1.630 +  GetResultLabelAt(aIndex, _retval);
   1.631 +
   1.632 +  return NS_OK;
   1.633 +}
   1.634 +
   1.635 +NS_IMETHODIMP
   1.636 +nsAutoCompleteController::GetCommentAt(int32_t aIndex, nsAString & _retval)
   1.637 +{
   1.638 +  int32_t rowIndex;
   1.639 +  nsIAutoCompleteResult* result;
   1.640 +  nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
   1.641 +  NS_ENSURE_SUCCESS(rv, rv);
   1.642 +
   1.643 +  return result->GetCommentAt(rowIndex, _retval);
   1.644 +}
   1.645 +
   1.646 +NS_IMETHODIMP
   1.647 +nsAutoCompleteController::GetStyleAt(int32_t aIndex, nsAString & _retval)
   1.648 +{
   1.649 +  int32_t rowIndex;
   1.650 +  nsIAutoCompleteResult* result;
   1.651 +  nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
   1.652 +  NS_ENSURE_SUCCESS(rv, rv);
   1.653 +
   1.654 +  return result->GetStyleAt(rowIndex, _retval);
   1.655 +}
   1.656 +
   1.657 +NS_IMETHODIMP
   1.658 +nsAutoCompleteController::GetImageAt(int32_t aIndex, nsAString & _retval)
   1.659 +{
   1.660 +  int32_t rowIndex;
   1.661 +  nsIAutoCompleteResult* result;
   1.662 +  nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
   1.663 +  NS_ENSURE_SUCCESS(rv, rv);
   1.664 +
   1.665 +  return result->GetImageAt(rowIndex, _retval);
   1.666 +}
   1.667 +
   1.668 +NS_IMETHODIMP
   1.669 +nsAutoCompleteController::GetFinalCompleteValueAt(int32_t aIndex,
   1.670 +                                                  nsAString & _retval)
   1.671 +{
   1.672 +  int32_t rowIndex;
   1.673 +  nsIAutoCompleteResult* result;
   1.674 +  nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
   1.675 +  NS_ENSURE_SUCCESS(rv, rv);
   1.676 +
   1.677 +  return result->GetFinalCompleteValueAt(rowIndex, _retval);
   1.678 +}
   1.679 +
   1.680 +NS_IMETHODIMP
   1.681 +nsAutoCompleteController::SetSearchString(const nsAString &aSearchString)
   1.682 +{
   1.683 +  mSearchString = aSearchString;
   1.684 +  return NS_OK;
   1.685 +}
   1.686 +
   1.687 +NS_IMETHODIMP
   1.688 +nsAutoCompleteController::GetSearchString(nsAString &aSearchString)
   1.689 +{
   1.690 +  aSearchString = mSearchString;
   1.691 +  return NS_OK;
   1.692 +}
   1.693 +
   1.694 +
   1.695 +////////////////////////////////////////////////////////////////////////
   1.696 +//// nsIAutoCompleteObserver
   1.697 +
   1.698 +NS_IMETHODIMP
   1.699 +nsAutoCompleteController::OnUpdateSearchResult(nsIAutoCompleteSearch *aSearch, nsIAutoCompleteResult* aResult)
   1.700 +{
   1.701 +  ClearResults();
   1.702 +  return OnSearchResult(aSearch, aResult);
   1.703 +}
   1.704 +
   1.705 +NS_IMETHODIMP
   1.706 +nsAutoCompleteController::OnSearchResult(nsIAutoCompleteSearch *aSearch, nsIAutoCompleteResult* aResult)
   1.707 +{
   1.708 +  // look up the index of the search which is returning
   1.709 +  for (uint32_t i = 0; i < mSearches.Length(); ++i) {
   1.710 +    if (mSearches[i] == aSearch) {
   1.711 +      ProcessResult(i, aResult);
   1.712 +    }
   1.713 +  }
   1.714 +
   1.715 +  return NS_OK;
   1.716 +}
   1.717 +
   1.718 +////////////////////////////////////////////////////////////////////////
   1.719 +//// nsITimerCallback
   1.720 +
   1.721 +NS_IMETHODIMP
   1.722 +nsAutoCompleteController::Notify(nsITimer *timer)
   1.723 +{
   1.724 +  mTimer = nullptr;
   1.725 +
   1.726 +  if (mImmediateSearchesCount == 0) {
   1.727 +    // If there were no immediate searches, BeforeSearches has not yet been
   1.728 +    // called, so do it now.
   1.729 +    nsresult rv = BeforeSearches();
   1.730 +    if (NS_FAILED(rv))
   1.731 +      return rv;
   1.732 +  }
   1.733 +  StartSearch(nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_DELAYED);
   1.734 +  AfterSearches();
   1.735 +  return NS_OK;
   1.736 +}
   1.737 +
   1.738 +////////////////////////////////////////////////////////////////////////
   1.739 +// nsITreeView
   1.740 +
   1.741 +NS_IMETHODIMP
   1.742 +nsAutoCompleteController::GetRowCount(int32_t *aRowCount)
   1.743 +{
   1.744 +  *aRowCount = mRowCount;
   1.745 +  return NS_OK;
   1.746 +}
   1.747 +
   1.748 +NS_IMETHODIMP
   1.749 +nsAutoCompleteController::GetRowProperties(int32_t index, nsAString& aProps)
   1.750 +{
   1.751 +  return NS_OK;
   1.752 +}
   1.753 +
   1.754 +NS_IMETHODIMP
   1.755 +nsAutoCompleteController::GetCellProperties(int32_t row, nsITreeColumn* col,
   1.756 +                                            nsAString& aProps)
   1.757 +{
   1.758 +  if (row >= 0) {
   1.759 +    GetStyleAt(row, aProps);
   1.760 +  }
   1.761 +
   1.762 +  return NS_OK;
   1.763 +}
   1.764 +
   1.765 +NS_IMETHODIMP
   1.766 +nsAutoCompleteController::GetColumnProperties(nsITreeColumn* col, nsAString& aProps)
   1.767 +{
   1.768 +  return NS_OK;
   1.769 +}
   1.770 +
   1.771 +NS_IMETHODIMP
   1.772 +nsAutoCompleteController::GetImageSrc(int32_t row, nsITreeColumn* col, nsAString& _retval)
   1.773 +{
   1.774 +  const char16_t* colID;
   1.775 +  col->GetIdConst(&colID);
   1.776 +
   1.777 +  if (NS_LITERAL_STRING("treecolAutoCompleteValue").Equals(colID))
   1.778 +    return GetImageAt(row, _retval);
   1.779 +
   1.780 +  return NS_OK;
   1.781 +}
   1.782 +
   1.783 +NS_IMETHODIMP
   1.784 +nsAutoCompleteController::GetProgressMode(int32_t row, nsITreeColumn* col, int32_t* _retval)
   1.785 +{
   1.786 +  NS_NOTREACHED("tree has no progress cells");
   1.787 +  return NS_OK;
   1.788 +}
   1.789 +
   1.790 +NS_IMETHODIMP
   1.791 +nsAutoCompleteController::GetCellValue(int32_t row, nsITreeColumn* col, nsAString& _retval)
   1.792 +{
   1.793 +  NS_NOTREACHED("all of our cells are text");
   1.794 +  return NS_OK;
   1.795 +}
   1.796 +
   1.797 +NS_IMETHODIMP
   1.798 +nsAutoCompleteController::GetCellText(int32_t row, nsITreeColumn* col, nsAString& _retval)
   1.799 +{
   1.800 +  const char16_t* colID;
   1.801 +  col->GetIdConst(&colID);
   1.802 +
   1.803 +  if (NS_LITERAL_STRING("treecolAutoCompleteValue").Equals(colID))
   1.804 +    GetValueAt(row, _retval);
   1.805 +  else if (NS_LITERAL_STRING("treecolAutoCompleteComment").Equals(colID))
   1.806 +    GetCommentAt(row, _retval);
   1.807 +
   1.808 +  return NS_OK;
   1.809 +}
   1.810 +
   1.811 +NS_IMETHODIMP
   1.812 +nsAutoCompleteController::IsContainer(int32_t index, bool *_retval)
   1.813 +{
   1.814 +  *_retval = false;
   1.815 +  return NS_OK;
   1.816 +}
   1.817 +
   1.818 +NS_IMETHODIMP
   1.819 +nsAutoCompleteController::IsContainerOpen(int32_t index, bool *_retval)
   1.820 +{
   1.821 +  NS_NOTREACHED("no container cells");
   1.822 +  return NS_OK;
   1.823 +}
   1.824 +
   1.825 +NS_IMETHODIMP
   1.826 +nsAutoCompleteController::IsContainerEmpty(int32_t index, bool *_retval)
   1.827 +{
   1.828 +  NS_NOTREACHED("no container cells");
   1.829 +  return NS_OK;
   1.830 +}
   1.831 +
   1.832 +NS_IMETHODIMP
   1.833 +nsAutoCompleteController::GetLevel(int32_t index, int32_t *_retval)
   1.834 +{
   1.835 +  *_retval = 0;
   1.836 +  return NS_OK;
   1.837 +}
   1.838 +
   1.839 +NS_IMETHODIMP
   1.840 +nsAutoCompleteController::GetParentIndex(int32_t rowIndex, int32_t *_retval)
   1.841 +{
   1.842 +  *_retval = -1;
   1.843 +  return NS_OK;
   1.844 +}
   1.845 +
   1.846 +NS_IMETHODIMP
   1.847 +nsAutoCompleteController::HasNextSibling(int32_t rowIndex, int32_t afterIndex, bool *_retval)
   1.848 +{
   1.849 +  *_retval = false;
   1.850 +  return NS_OK;
   1.851 +}
   1.852 +
   1.853 +NS_IMETHODIMP
   1.854 +nsAutoCompleteController::ToggleOpenState(int32_t index)
   1.855 +{
   1.856 +  return NS_OK;
   1.857 +}
   1.858 +
   1.859 +NS_IMETHODIMP
   1.860 +nsAutoCompleteController::SetTree(nsITreeBoxObject *tree)
   1.861 +{
   1.862 +  mTree = tree;
   1.863 +  return NS_OK;
   1.864 +}
   1.865 +
   1.866 +NS_IMETHODIMP
   1.867 +nsAutoCompleteController::GetSelection(nsITreeSelection * *aSelection)
   1.868 +{
   1.869 +  *aSelection = mSelection;
   1.870 +  NS_IF_ADDREF(*aSelection);
   1.871 +  return NS_OK;
   1.872 +}
   1.873 +
   1.874 +NS_IMETHODIMP nsAutoCompleteController::SetSelection(nsITreeSelection * aSelection)
   1.875 +{
   1.876 +  mSelection = aSelection;
   1.877 +  return NS_OK;
   1.878 +}
   1.879 +
   1.880 +NS_IMETHODIMP
   1.881 +nsAutoCompleteController::SelectionChanged()
   1.882 +{
   1.883 +  return NS_OK;
   1.884 +}
   1.885 +
   1.886 +NS_IMETHODIMP
   1.887 +nsAutoCompleteController::SetCellValue(int32_t row, nsITreeColumn* col, const nsAString& value)
   1.888 +{
   1.889 +  return NS_OK;
   1.890 +}
   1.891 +
   1.892 +NS_IMETHODIMP
   1.893 +nsAutoCompleteController::SetCellText(int32_t row, nsITreeColumn* col, const nsAString& value)
   1.894 +{
   1.895 +  return NS_OK;
   1.896 +}
   1.897 +
   1.898 +NS_IMETHODIMP
   1.899 +nsAutoCompleteController::CycleHeader(nsITreeColumn* col)
   1.900 +{
   1.901 +  return NS_OK;
   1.902 +}
   1.903 +
   1.904 +NS_IMETHODIMP
   1.905 +nsAutoCompleteController::CycleCell(int32_t row, nsITreeColumn* col)
   1.906 +{
   1.907 +  return NS_OK;
   1.908 +}
   1.909 +
   1.910 +NS_IMETHODIMP
   1.911 +nsAutoCompleteController::IsEditable(int32_t row, nsITreeColumn* col, bool *_retval)
   1.912 +{
   1.913 +  *_retval = false;
   1.914 +  return NS_OK;
   1.915 +}
   1.916 +
   1.917 +NS_IMETHODIMP
   1.918 +nsAutoCompleteController::IsSelectable(int32_t row, nsITreeColumn* col, bool *_retval)
   1.919 +{
   1.920 +  *_retval = false;
   1.921 +  return NS_OK;
   1.922 +}
   1.923 +
   1.924 +NS_IMETHODIMP
   1.925 +nsAutoCompleteController::IsSeparator(int32_t index, bool *_retval)
   1.926 +{
   1.927 +  *_retval = false;
   1.928 +  return NS_OK;
   1.929 +}
   1.930 +
   1.931 +NS_IMETHODIMP
   1.932 +nsAutoCompleteController::IsSorted(bool *_retval)
   1.933 +{
   1.934 +  *_retval = false;
   1.935 +  return NS_OK;
   1.936 +}
   1.937 +
   1.938 +NS_IMETHODIMP
   1.939 +nsAutoCompleteController::CanDrop(int32_t index, int32_t orientation,
   1.940 +                                  nsIDOMDataTransfer* dataTransfer, bool *_retval)
   1.941 +{
   1.942 +  return NS_OK;
   1.943 +}
   1.944 +
   1.945 +NS_IMETHODIMP
   1.946 +nsAutoCompleteController::Drop(int32_t row, int32_t orientation, nsIDOMDataTransfer* dataTransfer)
   1.947 +{
   1.948 +  return NS_OK;
   1.949 +}
   1.950 +
   1.951 +NS_IMETHODIMP
   1.952 +nsAutoCompleteController::PerformAction(const char16_t *action)
   1.953 +{
   1.954 +  return NS_OK;
   1.955 +}
   1.956 +
   1.957 +NS_IMETHODIMP
   1.958 +nsAutoCompleteController::PerformActionOnRow(const char16_t *action, int32_t row)
   1.959 +{
   1.960 +  return NS_OK;
   1.961 +}
   1.962 +
   1.963 +NS_IMETHODIMP
   1.964 +nsAutoCompleteController::PerformActionOnCell(const char16_t* action, int32_t row, nsITreeColumn* col)
   1.965 +{
   1.966 +  return NS_OK;
   1.967 +}
   1.968 +
   1.969 +////////////////////////////////////////////////////////////////////////
   1.970 +//// nsAutoCompleteController
   1.971 +
   1.972 +nsresult
   1.973 +nsAutoCompleteController::OpenPopup()
   1.974 +{
   1.975 +  uint32_t minResults;
   1.976 +  mInput->GetMinResultsForPopup(&minResults);
   1.977 +
   1.978 +  if (mRowCount >= minResults) {
   1.979 +    return mInput->SetPopupOpen(true);
   1.980 +  }
   1.981 +
   1.982 +  return NS_OK;
   1.983 +}
   1.984 +
   1.985 +nsresult
   1.986 +nsAutoCompleteController::ClosePopup()
   1.987 +{
   1.988 +  if (!mInput) {
   1.989 +    return NS_OK;
   1.990 +  }
   1.991 +
   1.992 +  bool isOpen = false;
   1.993 +  mInput->GetPopupOpen(&isOpen);
   1.994 +  if (!isOpen)
   1.995 +    return NS_OK;
   1.996 +
   1.997 +  nsCOMPtr<nsIAutoCompletePopup> popup;
   1.998 +  mInput->GetPopup(getter_AddRefs(popup));
   1.999 +  NS_ENSURE_TRUE(popup != nullptr, NS_ERROR_FAILURE);
  1.1000 +  popup->SetSelectedIndex(-1);
  1.1001 +  return mInput->SetPopupOpen(false);
  1.1002 +}
  1.1003 +
  1.1004 +nsresult
  1.1005 +nsAutoCompleteController::BeforeSearches()
  1.1006 +{
  1.1007 +  NS_ENSURE_STATE(mInput);
  1.1008 +
  1.1009 +  mSearchStatus = nsIAutoCompleteController::STATUS_SEARCHING;
  1.1010 +  mDefaultIndexCompleted = false;
  1.1011 +
  1.1012 +  // The first search result will clear mResults array, though we should pass
  1.1013 +  // the previous result to each search to allow them to reuse it.  So we
  1.1014 +  // temporarily cache current results till AfterSearches().
  1.1015 +  if (!mResultCache.AppendObjects(mResults)) {
  1.1016 +    return NS_ERROR_OUT_OF_MEMORY;
  1.1017 +  }
  1.1018 +
  1.1019 +  mSearchesOngoing = mSearches.Length();
  1.1020 +  mSearchesFailed = 0;
  1.1021 +  mFirstSearchResult = true;
  1.1022 +
  1.1023 +  // notify the input that the search is beginning
  1.1024 +  mInput->OnSearchBegin();
  1.1025 +
  1.1026 +  return NS_OK;
  1.1027 +}
  1.1028 +
  1.1029 +nsresult
  1.1030 +nsAutoCompleteController::StartSearch(uint16_t aSearchType)
  1.1031 +{
  1.1032 +  NS_ENSURE_STATE(mInput);
  1.1033 +  nsCOMPtr<nsIAutoCompleteInput> input = mInput;
  1.1034 +
  1.1035 +  for (uint32_t i = 0; i < mSearches.Length(); ++i) {
  1.1036 +    nsCOMPtr<nsIAutoCompleteSearch> search = mSearches[i];
  1.1037 +
  1.1038 +    // Filter on search type.  Not all the searches implement this interface,
  1.1039 +    // in such a case just consider them delayed.
  1.1040 +    uint16_t searchType = nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_DELAYED;
  1.1041 +    nsCOMPtr<nsIAutoCompleteSearchDescriptor> searchDesc =
  1.1042 +      do_QueryInterface(search);
  1.1043 +    if (searchDesc)
  1.1044 +      searchDesc->GetSearchType(&searchType);
  1.1045 +    if (searchType != aSearchType)
  1.1046 +      continue;
  1.1047 +
  1.1048 +    nsIAutoCompleteResult *result = mResultCache.SafeObjectAt(i);
  1.1049 +
  1.1050 +    if (result) {
  1.1051 +      uint16_t searchResult;
  1.1052 +      result->GetSearchResult(&searchResult);
  1.1053 +      if (searchResult != nsIAutoCompleteResult::RESULT_SUCCESS &&
  1.1054 +          searchResult != nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING &&
  1.1055 +          searchResult != nsIAutoCompleteResult::RESULT_NOMATCH)
  1.1056 +        result = nullptr;
  1.1057 +    }
  1.1058 +
  1.1059 +    nsAutoString searchParam;
  1.1060 +    nsresult rv = input->GetSearchParam(searchParam);
  1.1061 +    if (NS_FAILED(rv))
  1.1062 +        return rv;
  1.1063 +
  1.1064 +    rv = search->StartSearch(mSearchString, searchParam, result, static_cast<nsIAutoCompleteObserver *>(this));
  1.1065 +    if (NS_FAILED(rv)) {
  1.1066 +      ++mSearchesFailed;
  1.1067 +      --mSearchesOngoing;
  1.1068 +    }
  1.1069 +    // Because of the joy of nested event loops (which can easily happen when some
  1.1070 +    // code uses a generator for an asynchronous AutoComplete search),
  1.1071 +    // nsIAutoCompleteSearch::StartSearch might cause us to be detached from our input
  1.1072 +    // field.  The next time we iterate, we'd be touching something that we shouldn't
  1.1073 +    // be, and result in a crash.
  1.1074 +    if (!mInput) {
  1.1075 +      // The search operation has been finished.
  1.1076 +      return NS_OK;
  1.1077 +    }
  1.1078 +  }
  1.1079 +
  1.1080 +  return NS_OK;
  1.1081 +}
  1.1082 +
  1.1083 +void
  1.1084 +nsAutoCompleteController::AfterSearches()
  1.1085 +{
  1.1086 +  mResultCache.Clear();
  1.1087 +  if (mSearchesFailed == mSearches.Length())
  1.1088 +    PostSearchCleanup();
  1.1089 +}
  1.1090 +
  1.1091 +NS_IMETHODIMP
  1.1092 +nsAutoCompleteController::StopSearch()
  1.1093 +{
  1.1094 +  // Stop the timer if there is one
  1.1095 +  ClearSearchTimer();
  1.1096 +
  1.1097 +  // Stop any ongoing asynchronous searches
  1.1098 +  if (mSearchStatus == nsIAutoCompleteController::STATUS_SEARCHING) {
  1.1099 +    for (uint32_t i = 0; i < mSearches.Length(); ++i) {
  1.1100 +      nsCOMPtr<nsIAutoCompleteSearch> search = mSearches[i];
  1.1101 +      search->StopSearch();
  1.1102 +    }
  1.1103 +    mSearchesOngoing = 0;
  1.1104 +    // since we were searching, but now we've stopped,
  1.1105 +    // we need to call PostSearchCleanup()
  1.1106 +    PostSearchCleanup();
  1.1107 +  }
  1.1108 +  return NS_OK;
  1.1109 +}
  1.1110 +
  1.1111 +nsresult
  1.1112 +nsAutoCompleteController::StartSearches()
  1.1113 +{
  1.1114 +  // Don't create a new search timer if we're already waiting for one to fire.
  1.1115 +  // If we don't check for this, we won't be able to cancel the original timer
  1.1116 +  // and may crash when it fires (bug 236659).
  1.1117 +  if (mTimer || !mInput)
  1.1118 +    return NS_OK;
  1.1119 +
  1.1120 +  // Get the timeout for delayed searches.
  1.1121 +  uint32_t timeout;
  1.1122 +  mInput->GetTimeout(&timeout);
  1.1123 +
  1.1124 +  uint32_t immediateSearchesCount = mImmediateSearchesCount;
  1.1125 +  if (timeout == 0) {
  1.1126 +    // All the searches should be executed immediately.
  1.1127 +    immediateSearchesCount = mSearches.Length();
  1.1128 +  }
  1.1129 +
  1.1130 +  if (immediateSearchesCount > 0) {
  1.1131 +    nsresult rv = BeforeSearches();
  1.1132 +    if (NS_FAILED(rv))
  1.1133 +      return rv;
  1.1134 +    StartSearch(nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_IMMEDIATE);
  1.1135 +
  1.1136 +    if (mSearches.Length() == immediateSearchesCount) {
  1.1137 +      // Either all searches are immediate, or the timeout is 0.  In the
  1.1138 +      // latter case we still have to execute the delayed searches, otherwise
  1.1139 +      // this will be a no-op.
  1.1140 +      StartSearch(nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_DELAYED);
  1.1141 +
  1.1142 +      // All the searches have been started, just finish.
  1.1143 +      AfterSearches();
  1.1144 +      return NS_OK;
  1.1145 +    }
  1.1146 +  }
  1.1147 +
  1.1148 +  MOZ_ASSERT(timeout > 0, "Trying to delay searches with a 0 timeout!");
  1.1149 +
  1.1150 +  // Now start the delayed searches.
  1.1151 +  nsresult rv;
  1.1152 +  mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
  1.1153 +  if (NS_FAILED(rv))
  1.1154 +      return rv;
  1.1155 +  rv = mTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT);
  1.1156 +  if (NS_FAILED(rv))
  1.1157 +      mTimer = nullptr;
  1.1158 +
  1.1159 +  return rv;
  1.1160 +}
  1.1161 +
  1.1162 +nsresult
  1.1163 +nsAutoCompleteController::ClearSearchTimer()
  1.1164 +{
  1.1165 +  if (mTimer) {
  1.1166 +    mTimer->Cancel();
  1.1167 +    mTimer = nullptr;
  1.1168 +  }
  1.1169 +  return NS_OK;
  1.1170 +}
  1.1171 +
  1.1172 +nsresult
  1.1173 +nsAutoCompleteController::EnterMatch(bool aIsPopupSelection)
  1.1174 +{
  1.1175 +  nsCOMPtr<nsIAutoCompleteInput> input(mInput);
  1.1176 +  nsCOMPtr<nsIAutoCompletePopup> popup;
  1.1177 +  input->GetPopup(getter_AddRefs(popup));
  1.1178 +  NS_ENSURE_TRUE(popup != nullptr, NS_ERROR_FAILURE);
  1.1179 +
  1.1180 +  bool forceComplete;
  1.1181 +  input->GetForceComplete(&forceComplete);
  1.1182 +
  1.1183 +  // Ask the popup if it wants to enter a special value into the textbox
  1.1184 +  nsAutoString value;
  1.1185 +  popup->GetOverrideValue(value);
  1.1186 +  if (value.IsEmpty()) {
  1.1187 +    bool shouldComplete;
  1.1188 +    input->GetCompleteDefaultIndex(&shouldComplete);
  1.1189 +    bool completeSelection;
  1.1190 +    input->GetCompleteSelectedIndex(&completeSelection);
  1.1191 +
  1.1192 +    int32_t selectedIndex;
  1.1193 +    popup->GetSelectedIndex(&selectedIndex);
  1.1194 +    if (selectedIndex >= 0) {
  1.1195 +      // If completeselectedindex is false or a row was selected from the popup,
  1.1196 +      // enter it into the textbox. If completeselectedindex is true, or
  1.1197 +      // EnterMatch was called via other means, for instance pressing Enter,
  1.1198 +      // don't fill in the value as it will have already been filled in as
  1.1199 +      // needed, unless the final complete value differs.
  1.1200 +      nsAutoString finalValue, inputValue;
  1.1201 +      GetResultValueAt(selectedIndex, true, finalValue);
  1.1202 +      input->GetTextValue(inputValue);
  1.1203 +      if (!completeSelection || aIsPopupSelection ||
  1.1204 +          !finalValue.Equals(inputValue)) {
  1.1205 +        value = finalValue;
  1.1206 +      }
  1.1207 +    }
  1.1208 +    else if (shouldComplete) {
  1.1209 +      // We usually try to preserve the casing of what user has typed, but
  1.1210 +      // if he wants to autocomplete, we will replace the value with the
  1.1211 +      // actual autocomplete result.
  1.1212 +      // The user wants explicitely to use that result, so this ensures
  1.1213 +      // association of the result with the autocompleted text.
  1.1214 +      nsAutoString defaultIndexValue;
  1.1215 +      if (NS_SUCCEEDED(GetFinalDefaultCompleteValue(defaultIndexValue)))
  1.1216 +        value = defaultIndexValue;
  1.1217 +    }
  1.1218 +
  1.1219 +    if (forceComplete && value.IsEmpty()) {
  1.1220 +      // Since nothing was selected, and forceComplete is specified, that means
  1.1221 +      // we have to find the first default match and enter it instead
  1.1222 +      for (uint32_t i = 0; i < mResults.Length(); ++i) {
  1.1223 +        nsIAutoCompleteResult *result = mResults[i];
  1.1224 +
  1.1225 +        if (result) {
  1.1226 +          int32_t defaultIndex;
  1.1227 +          result->GetDefaultIndex(&defaultIndex);
  1.1228 +          if (defaultIndex >= 0) {
  1.1229 +            result->GetFinalCompleteValueAt(defaultIndex, value);
  1.1230 +            break;
  1.1231 +          }
  1.1232 +        }
  1.1233 +      }
  1.1234 +    }
  1.1235 +  }
  1.1236 +
  1.1237 +  nsCOMPtr<nsIObserverService> obsSvc =
  1.1238 +    mozilla::services::GetObserverService();
  1.1239 +  NS_ENSURE_STATE(obsSvc);
  1.1240 +  obsSvc->NotifyObservers(input, "autocomplete-will-enter-text", nullptr);
  1.1241 +
  1.1242 +  if (!value.IsEmpty()) {
  1.1243 +    input->SetTextValue(value);
  1.1244 +    input->SelectTextRange(value.Length(), value.Length());
  1.1245 +    mSearchString = value;
  1.1246 +  }
  1.1247 +
  1.1248 +  obsSvc->NotifyObservers(input, "autocomplete-did-enter-text", nullptr);
  1.1249 +  ClosePopup();
  1.1250 +
  1.1251 +  bool cancel;
  1.1252 +  input->OnTextEntered(&cancel);
  1.1253 +
  1.1254 +  return NS_OK;
  1.1255 +}
  1.1256 +
  1.1257 +nsresult
  1.1258 +nsAutoCompleteController::RevertTextValue()
  1.1259 +{
  1.1260 +  // StopSearch() can call PostSearchCleanup() which might result
  1.1261 +  // in a blur event, which could null out mInput, so we need to check it
  1.1262 +  // again.  See bug #408463 for more details
  1.1263 +  if (!mInput)
  1.1264 +    return NS_OK;
  1.1265 +
  1.1266 +  nsAutoString oldValue(mSearchString);
  1.1267 +  nsCOMPtr<nsIAutoCompleteInput> input(mInput);
  1.1268 +
  1.1269 +  bool cancel = false;
  1.1270 +  input->OnTextReverted(&cancel);
  1.1271 +
  1.1272 +  if (!cancel) {
  1.1273 +    nsCOMPtr<nsIObserverService> obsSvc =
  1.1274 +      mozilla::services::GetObserverService();
  1.1275 +    NS_ENSURE_STATE(obsSvc);
  1.1276 +    obsSvc->NotifyObservers(input, "autocomplete-will-revert-text", nullptr);
  1.1277 +
  1.1278 +    nsAutoString inputValue;
  1.1279 +    input->GetTextValue(inputValue);
  1.1280 +    // Don't change the value if it is the same to prevent sending useless events.
  1.1281 +    // NOTE: how can |RevertTextValue| be called with inputValue != oldValue?
  1.1282 +    if (!oldValue.Equals(inputValue)) {
  1.1283 +      input->SetTextValue(oldValue);
  1.1284 +    }
  1.1285 +
  1.1286 +    obsSvc->NotifyObservers(input, "autocomplete-did-revert-text", nullptr);
  1.1287 +  }
  1.1288 +
  1.1289 +  return NS_OK;
  1.1290 +}
  1.1291 +
  1.1292 +nsresult
  1.1293 +nsAutoCompleteController::ProcessResult(int32_t aSearchIndex, nsIAutoCompleteResult *aResult)
  1.1294 +{
  1.1295 +  NS_ENSURE_STATE(mInput);
  1.1296 +  nsCOMPtr<nsIAutoCompleteInput> input(mInput);
  1.1297 +
  1.1298 +  // If this is the first search result we are processing
  1.1299 +  // we should clear out the previously cached results
  1.1300 +  if (mFirstSearchResult) {
  1.1301 +    ClearResults();
  1.1302 +    mFirstSearchResult = false;
  1.1303 +  }
  1.1304 +
  1.1305 +  uint16_t result = 0;
  1.1306 +  if (aResult)
  1.1307 +    aResult->GetSearchResult(&result);
  1.1308 +
  1.1309 +  // if our results are incremental, the search is still ongoing
  1.1310 +  if (result != nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING &&
  1.1311 +      result != nsIAutoCompleteResult::RESULT_NOMATCH_ONGOING) {
  1.1312 +    --mSearchesOngoing;
  1.1313 +  }
  1.1314 +
  1.1315 +  uint32_t oldMatchCount = 0;
  1.1316 +  uint32_t matchCount = 0;
  1.1317 +  if (aResult)
  1.1318 +    aResult->GetMatchCount(&matchCount);
  1.1319 +
  1.1320 +  int32_t resultIndex = mResults.IndexOf(aResult);
  1.1321 +  if (resultIndex == -1) {
  1.1322 +    // cache the result
  1.1323 +    mResults.AppendObject(aResult);
  1.1324 +    mMatchCounts.AppendElement(matchCount);
  1.1325 +    resultIndex = mResults.Count() - 1;
  1.1326 +  }
  1.1327 +  else {
  1.1328 +    oldMatchCount = mMatchCounts[aSearchIndex];
  1.1329 +    mMatchCounts[resultIndex] = matchCount;
  1.1330 +  }
  1.1331 +
  1.1332 +  bool isTypeAheadResult = false;
  1.1333 +  if (aResult) {
  1.1334 +    aResult->GetTypeAheadResult(&isTypeAheadResult);
  1.1335 +  }
  1.1336 +
  1.1337 +  if (!isTypeAheadResult) {
  1.1338 +    uint32_t oldRowCount = mRowCount;
  1.1339 +    // If the search failed, increase the match count to include the error
  1.1340 +    // description.
  1.1341 +    if (result == nsIAutoCompleteResult::RESULT_FAILURE) {
  1.1342 +      nsAutoString error;
  1.1343 +      aResult->GetErrorDescription(error);
  1.1344 +      if (!error.IsEmpty()) {
  1.1345 +        ++mRowCount;
  1.1346 +        if (mTree) {
  1.1347 +          mTree->RowCountChanged(oldRowCount, 1);
  1.1348 +        }
  1.1349 +      }
  1.1350 +    } else if (result == nsIAutoCompleteResult::RESULT_SUCCESS ||
  1.1351 +               result == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
  1.1352 +      // Increase the match count for all matches in this result.
  1.1353 +      mRowCount += matchCount - oldMatchCount;
  1.1354 +
  1.1355 +      if (mTree) {
  1.1356 +        mTree->RowCountChanged(oldRowCount, matchCount - oldMatchCount);
  1.1357 +      }
  1.1358 +    }
  1.1359 +
  1.1360 +    // Refresh the popup view to display the new search results
  1.1361 +    nsCOMPtr<nsIAutoCompletePopup> popup;
  1.1362 +    input->GetPopup(getter_AddRefs(popup));
  1.1363 +    NS_ENSURE_TRUE(popup != nullptr, NS_ERROR_FAILURE);
  1.1364 +    popup->Invalidate();
  1.1365 +
  1.1366 +    uint32_t minResults;
  1.1367 +    input->GetMinResultsForPopup(&minResults);
  1.1368 +
  1.1369 +    // Make sure the popup is open, if necessary, since we now have at least one
  1.1370 +    // search result ready to display. Don't force the popup closed if we might
  1.1371 +    // get results in the future to avoid unnecessarily canceling searches.
  1.1372 +    if (mRowCount || !minResults) {
  1.1373 +      OpenPopup();
  1.1374 +    } else if (result != nsIAutoCompleteResult::RESULT_NOMATCH_ONGOING) {
  1.1375 +      ClosePopup();
  1.1376 +    }
  1.1377 +  }
  1.1378 +
  1.1379 +  if (result == nsIAutoCompleteResult::RESULT_SUCCESS ||
  1.1380 +      result == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
  1.1381 +    // Try to autocomplete the default index for this search.
  1.1382 +    CompleteDefaultIndex(resultIndex);
  1.1383 +  }
  1.1384 +
  1.1385 +  if (mSearchesOngoing == 0) {
  1.1386 +    // If this is the last search to return, cleanup.
  1.1387 +    PostSearchCleanup();
  1.1388 +  }
  1.1389 +
  1.1390 +  return NS_OK;
  1.1391 +}
  1.1392 +
  1.1393 +nsresult
  1.1394 +nsAutoCompleteController::PostSearchCleanup()
  1.1395 +{
  1.1396 +  NS_ENSURE_STATE(mInput);
  1.1397 +  nsCOMPtr<nsIAutoCompleteInput> input(mInput);
  1.1398 +
  1.1399 +  uint32_t minResults;
  1.1400 +  input->GetMinResultsForPopup(&minResults);
  1.1401 +
  1.1402 +  if (mRowCount || minResults == 0) {
  1.1403 +    OpenPopup();
  1.1404 +    if (mRowCount)
  1.1405 +      mSearchStatus = nsIAutoCompleteController::STATUS_COMPLETE_MATCH;
  1.1406 +    else
  1.1407 +      mSearchStatus = nsIAutoCompleteController::STATUS_COMPLETE_NO_MATCH;
  1.1408 +  } else {
  1.1409 +    mSearchStatus = nsIAutoCompleteController::STATUS_COMPLETE_NO_MATCH;
  1.1410 +    ClosePopup();
  1.1411 +  }
  1.1412 +
  1.1413 +  // notify the input that the search is complete
  1.1414 +  input->OnSearchComplete();
  1.1415 +
  1.1416 +  return NS_OK;
  1.1417 +}
  1.1418 +
  1.1419 +nsresult
  1.1420 +nsAutoCompleteController::ClearResults()
  1.1421 +{
  1.1422 +  int32_t oldRowCount = mRowCount;
  1.1423 +  mRowCount = 0;
  1.1424 +  mResults.Clear();
  1.1425 +  mMatchCounts.Clear();
  1.1426 +  if (oldRowCount != 0) {
  1.1427 +    if (mTree)
  1.1428 +      mTree->RowCountChanged(0, -oldRowCount);
  1.1429 +    else if (mInput) {
  1.1430 +      nsCOMPtr<nsIAutoCompletePopup> popup;
  1.1431 +      mInput->GetPopup(getter_AddRefs(popup));
  1.1432 +      NS_ENSURE_TRUE(popup != nullptr, NS_ERROR_FAILURE);
  1.1433 +      // if we had a tree, RowCountChanged() would have cleared the selection
  1.1434 +      // when the selected row was removed.  But since we don't have a tree,
  1.1435 +      // we need to clear the selection manually.
  1.1436 +      popup->SetSelectedIndex(-1);
  1.1437 +    }
  1.1438 +  }
  1.1439 +  return NS_OK;
  1.1440 +}
  1.1441 +
  1.1442 +nsresult
  1.1443 +nsAutoCompleteController::CompleteDefaultIndex(int32_t aResultIndex)
  1.1444 +{
  1.1445 +  if (mDefaultIndexCompleted || mBackspaced || mSearchString.Length() == 0 || !mInput)
  1.1446 +    return NS_OK;
  1.1447 +
  1.1448 +  int32_t selectionStart;
  1.1449 +  mInput->GetSelectionStart(&selectionStart);
  1.1450 +  int32_t selectionEnd;
  1.1451 +  mInput->GetSelectionEnd(&selectionEnd);
  1.1452 +
  1.1453 +  // Don't try to automatically complete to the first result if there's already
  1.1454 +  // a selection or the cursor isn't at the end of the input
  1.1455 +  if (selectionEnd != selectionStart ||
  1.1456 +      selectionEnd != (int32_t)mSearchString.Length())
  1.1457 +    return NS_OK;
  1.1458 +
  1.1459 +  bool shouldComplete;
  1.1460 +  mInput->GetCompleteDefaultIndex(&shouldComplete);
  1.1461 +  if (!shouldComplete)
  1.1462 +    return NS_OK;
  1.1463 +
  1.1464 +  nsAutoString resultValue;
  1.1465 +  if (NS_SUCCEEDED(GetDefaultCompleteValue(aResultIndex, true, resultValue)))
  1.1466 +    CompleteValue(resultValue);
  1.1467 +
  1.1468 +  mDefaultIndexCompleted = true;
  1.1469 +
  1.1470 +  return NS_OK;
  1.1471 +}
  1.1472 +
  1.1473 +nsresult
  1.1474 +nsAutoCompleteController::GetDefaultCompleteResult(int32_t aResultIndex,
  1.1475 +                                                   nsIAutoCompleteResult** _result,
  1.1476 +                                                   int32_t* _defaultIndex)
  1.1477 +{
  1.1478 +  *_defaultIndex = -1;
  1.1479 +  int32_t resultIndex = aResultIndex;
  1.1480 +
  1.1481 +  // If a result index was not provided, find the first defaultIndex result.
  1.1482 +  for (int32_t i = 0; resultIndex < 0 && i < mResults.Count(); ++i) {
  1.1483 +    nsIAutoCompleteResult *result = mResults[i];
  1.1484 +    if (result &&
  1.1485 +        NS_SUCCEEDED(result->GetDefaultIndex(_defaultIndex)) &&
  1.1486 +        *_defaultIndex >= 0) {
  1.1487 +      resultIndex = i;
  1.1488 +    }
  1.1489 +  }
  1.1490 +  NS_ENSURE_TRUE(resultIndex >= 0, NS_ERROR_FAILURE);
  1.1491 +
  1.1492 +  *_result = mResults.SafeObjectAt(resultIndex);
  1.1493 +  NS_ENSURE_TRUE(*_result, NS_ERROR_FAILURE);
  1.1494 +
  1.1495 +  if (*_defaultIndex < 0) {
  1.1496 +    // The search must explicitly provide a default index in order
  1.1497 +    // for us to be able to complete.
  1.1498 +    (*_result)->GetDefaultIndex(_defaultIndex);
  1.1499 +  }
  1.1500 +
  1.1501 +  if (*_defaultIndex < 0) {
  1.1502 +    // We were given a result index, but that result doesn't want to
  1.1503 +    // be autocompleted.
  1.1504 +    return NS_ERROR_FAILURE;
  1.1505 +  }
  1.1506 +
  1.1507 +  // If the result wrongly notifies a RESULT_SUCCESS with no matches, or
  1.1508 +  // provides a defaultIndex greater than its matchCount, avoid trying to
  1.1509 +  // complete to an empty value.
  1.1510 +  uint32_t matchCount = 0;
  1.1511 +  (*_result)->GetMatchCount(&matchCount);
  1.1512 +  // Here defaultIndex is surely non-negative, so can be cast to unsigned.
  1.1513 +  if ((uint32_t)(*_defaultIndex) >= matchCount) {
  1.1514 +    return NS_ERROR_FAILURE;
  1.1515 +  }
  1.1516 +
  1.1517 +  return NS_OK;
  1.1518 +}
  1.1519 +
  1.1520 +nsresult
  1.1521 +nsAutoCompleteController::GetDefaultCompleteValue(int32_t aResultIndex,
  1.1522 +                                                  bool aPreserveCasing,
  1.1523 +                                                  nsAString &_retval)
  1.1524 +{
  1.1525 +  nsIAutoCompleteResult *result;
  1.1526 +  int32_t defaultIndex = -1;
  1.1527 +  nsresult rv = GetDefaultCompleteResult(aResultIndex, &result, &defaultIndex);
  1.1528 +  if (NS_FAILED(rv)) return rv;
  1.1529 +
  1.1530 +  nsAutoString resultValue;
  1.1531 +  result->GetValueAt(defaultIndex, resultValue);
  1.1532 +  if (aPreserveCasing &&
  1.1533 +      StringBeginsWith(resultValue, mSearchString,
  1.1534 +                       nsCaseInsensitiveStringComparator())) {
  1.1535 +    // We try to preserve user casing, otherwise we would end up changing
  1.1536 +    // the case of what he typed, if we have a result with a different casing.
  1.1537 +    // For example if we have result "Test", and user starts writing "tuna",
  1.1538 +    // after digiting t, we would convert it to T trying to autocomplete "Test".
  1.1539 +    // We will still complete to cased "Test" if the user explicitely choose
  1.1540 +    // that result, by either selecting it in the results popup, or with
  1.1541 +    // keyboard navigation or if autocompleting in the middle.
  1.1542 +    nsAutoString casedResultValue;
  1.1543 +    casedResultValue.Assign(mSearchString);
  1.1544 +    // Use what the user has typed so far.
  1.1545 +    casedResultValue.Append(Substring(resultValue,
  1.1546 +                                      mSearchString.Length(),
  1.1547 +                                      resultValue.Length()));
  1.1548 +    _retval = casedResultValue;
  1.1549 +  }
  1.1550 +  else
  1.1551 +    _retval = resultValue;
  1.1552 +
  1.1553 +  return NS_OK;
  1.1554 +}
  1.1555 +
  1.1556 +nsresult
  1.1557 +nsAutoCompleteController::GetFinalDefaultCompleteValue(nsAString &_retval)
  1.1558 +{
  1.1559 +  MOZ_ASSERT(mInput, "Must have a valid input");
  1.1560 +  nsIAutoCompleteResult *result;
  1.1561 +  int32_t defaultIndex = -1;
  1.1562 +  nsresult rv = GetDefaultCompleteResult(-1, &result, &defaultIndex);
  1.1563 +  if (NS_FAILED(rv)) return rv;
  1.1564 +
  1.1565 +  result->GetValueAt(defaultIndex, _retval);
  1.1566 +  nsAutoString inputValue;
  1.1567 +  mInput->GetTextValue(inputValue);
  1.1568 +  if (!_retval.Equals(inputValue, nsCaseInsensitiveStringComparator())) {
  1.1569 +    return NS_ERROR_FAILURE;
  1.1570 +  }
  1.1571 +
  1.1572 +  nsAutoString finalCompleteValue;
  1.1573 +  rv = result->GetFinalCompleteValueAt(defaultIndex, finalCompleteValue);
  1.1574 +  if (NS_SUCCEEDED(rv)) {
  1.1575 +    _retval = finalCompleteValue;
  1.1576 +  }
  1.1577 +
  1.1578 +  MOZ_ASSERT(FindInReadable(inputValue, _retval, nsCaseInsensitiveStringComparator()),
  1.1579 +             "Return value must include input value.");
  1.1580 +  return NS_OK;
  1.1581 +}
  1.1582 +
  1.1583 +nsresult
  1.1584 +nsAutoCompleteController::CompleteValue(nsString &aValue)
  1.1585 +/* mInput contains mSearchString, which we want to autocomplete to aValue.  If
  1.1586 + * selectDifference is true, select the remaining portion of aValue not
  1.1587 + * contained in mSearchString. */
  1.1588 +{
  1.1589 +  MOZ_ASSERT(mInput, "Must have a valid input");
  1.1590 +  const int32_t mSearchStringLength = mSearchString.Length();
  1.1591 +  int32_t endSelect = aValue.Length();  // By default, select all of aValue.
  1.1592 +
  1.1593 +  if (aValue.IsEmpty() ||
  1.1594 +      StringBeginsWith(aValue, mSearchString,
  1.1595 +                       nsCaseInsensitiveStringComparator())) {
  1.1596 +    // aValue is empty (we were asked to clear mInput), or mSearchString
  1.1597 +    // matches the beginning of aValue.  In either case we can simply
  1.1598 +    // autocomplete to aValue.
  1.1599 +    mInput->SetTextValue(aValue);
  1.1600 +  } else {
  1.1601 +    nsresult rv;
  1.1602 +    nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
  1.1603 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1604 +    nsAutoCString scheme;
  1.1605 +    if (NS_SUCCEEDED(ios->ExtractScheme(NS_ConvertUTF16toUTF8(aValue), scheme))) {
  1.1606 +      // Trying to autocomplete a URI from somewhere other than the beginning.
  1.1607 +      // Only succeed if the missing portion is "http://"; otherwise do not
  1.1608 +      // autocomplete.  This prevents us from "helpfully" autocompleting to a
  1.1609 +      // URI that isn't equivalent to what the user expected.
  1.1610 +      const int32_t findIndex = 7; // length of "http://"
  1.1611 +
  1.1612 +      if ((endSelect < findIndex + mSearchStringLength) ||
  1.1613 +          !scheme.LowerCaseEqualsLiteral("http") ||
  1.1614 +          !Substring(aValue, findIndex, mSearchStringLength).Equals(
  1.1615 +            mSearchString, nsCaseInsensitiveStringComparator())) {
  1.1616 +        return NS_OK;
  1.1617 +      }
  1.1618 +
  1.1619 +      mInput->SetTextValue(mSearchString +
  1.1620 +                           Substring(aValue, mSearchStringLength + findIndex,
  1.1621 +                                     endSelect));
  1.1622 +
  1.1623 +      endSelect -= findIndex; // We're skipping this many characters of aValue.
  1.1624 +    } else {
  1.1625 +      // Autocompleting something other than a URI from the middle.
  1.1626 +      // Use the format "searchstring >> full string" to indicate to the user
  1.1627 +      // what we are going to replace their search string with.
  1.1628 +      mInput->SetTextValue(mSearchString + NS_LITERAL_STRING(" >> ") + aValue);
  1.1629 +
  1.1630 +      endSelect = mSearchString.Length() + 4 + aValue.Length();
  1.1631 +    }
  1.1632 +  }
  1.1633 +
  1.1634 +  mInput->SelectTextRange(mSearchStringLength, endSelect);
  1.1635 +
  1.1636 +  return NS_OK;
  1.1637 +}
  1.1638 +
  1.1639 +nsresult
  1.1640 +nsAutoCompleteController::GetResultLabelAt(int32_t aIndex, nsAString & _retval)
  1.1641 +{
  1.1642 +  return GetResultValueLabelAt(aIndex, false, false, _retval);
  1.1643 +}
  1.1644 +
  1.1645 +nsresult
  1.1646 +nsAutoCompleteController::GetResultValueAt(int32_t aIndex, bool aGetFinalValue,
  1.1647 +                                           nsAString & _retval)
  1.1648 +{
  1.1649 +  return GetResultValueLabelAt(aIndex, aGetFinalValue, true, _retval);
  1.1650 +}
  1.1651 +
  1.1652 +nsresult
  1.1653 +nsAutoCompleteController::GetResultValueLabelAt(int32_t aIndex,
  1.1654 +                                                bool aGetFinalValue,
  1.1655 +                                                bool aGetValue,
  1.1656 +                                                nsAString & _retval)
  1.1657 +{
  1.1658 +  NS_ENSURE_TRUE(aIndex >= 0 && (uint32_t) aIndex < mRowCount, NS_ERROR_ILLEGAL_VALUE);
  1.1659 +
  1.1660 +  int32_t rowIndex;
  1.1661 +  nsIAutoCompleteResult *result;
  1.1662 +  nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
  1.1663 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1664 +
  1.1665 +  uint16_t searchResult;
  1.1666 +  result->GetSearchResult(&searchResult);
  1.1667 +
  1.1668 +  if (searchResult == nsIAutoCompleteResult::RESULT_FAILURE) {
  1.1669 +    if (aGetValue)
  1.1670 +      return NS_ERROR_FAILURE;
  1.1671 +    result->GetErrorDescription(_retval);
  1.1672 +  } else if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS ||
  1.1673 +             searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
  1.1674 +    if (aGetFinalValue)
  1.1675 +      result->GetFinalCompleteValueAt(rowIndex, _retval);
  1.1676 +    else if (aGetValue)
  1.1677 +      result->GetValueAt(rowIndex, _retval);
  1.1678 +    else
  1.1679 +      result->GetLabelAt(rowIndex, _retval);
  1.1680 +  }
  1.1681 +
  1.1682 +  return NS_OK;
  1.1683 +}
  1.1684 +
  1.1685 +/**
  1.1686 + * Given the index of a row in the autocomplete popup, find the
  1.1687 + * corresponding nsIAutoCompleteSearch index, and sub-index into
  1.1688 + * the search's results list.
  1.1689 + */
  1.1690 +nsresult
  1.1691 +nsAutoCompleteController::RowIndexToSearch(int32_t aRowIndex, int32_t *aSearchIndex, int32_t *aItemIndex)
  1.1692 +{
  1.1693 +  *aSearchIndex = -1;
  1.1694 +  *aItemIndex = -1;
  1.1695 +
  1.1696 +  uint32_t index = 0;
  1.1697 +
  1.1698 +  // Move index through the results of each registered nsIAutoCompleteSearch
  1.1699 +  // until we find the given row
  1.1700 +  for (uint32_t i = 0; i < mSearches.Length(); ++i) {
  1.1701 +    nsIAutoCompleteResult *result = mResults.SafeObjectAt(i);
  1.1702 +    if (!result)
  1.1703 +      continue;
  1.1704 +
  1.1705 +    uint32_t rowCount = 0;
  1.1706 +
  1.1707 +    // Skip past the result completely if it is marked as hidden
  1.1708 +    bool isTypeAheadResult = false;
  1.1709 +    result->GetTypeAheadResult(&isTypeAheadResult);
  1.1710 +
  1.1711 +    if (!isTypeAheadResult) {
  1.1712 +      uint16_t searchResult;
  1.1713 +      result->GetSearchResult(&searchResult);
  1.1714 +
  1.1715 +      // Find out how many results were provided by the
  1.1716 +      // current nsIAutoCompleteSearch.
  1.1717 +      if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS ||
  1.1718 +          searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
  1.1719 +        result->GetMatchCount(&rowCount);
  1.1720 +      }
  1.1721 +    }
  1.1722 +
  1.1723 +    // If the given row index is within the results range
  1.1724 +    // of the current nsIAutoCompleteSearch then return the
  1.1725 +    // search index and sub-index into the results array
  1.1726 +    if ((rowCount != 0) && (index + rowCount-1 >= (uint32_t) aRowIndex)) {
  1.1727 +      *aSearchIndex = i;
  1.1728 +      *aItemIndex = aRowIndex - index;
  1.1729 +      return NS_OK;
  1.1730 +    }
  1.1731 +
  1.1732 +    // Advance the popup table index cursor past the
  1.1733 +    // results of the current search.
  1.1734 +    index += rowCount;
  1.1735 +  }
  1.1736 +
  1.1737 +  return NS_OK;
  1.1738 +}
  1.1739 +
  1.1740 +NS_GENERIC_FACTORY_CONSTRUCTOR(nsAutoCompleteController)
  1.1741 +NS_GENERIC_FACTORY_CONSTRUCTOR(nsAutoCompleteSimpleResult)
  1.1742 +
  1.1743 +NS_DEFINE_NAMED_CID(NS_AUTOCOMPLETECONTROLLER_CID);
  1.1744 +NS_DEFINE_NAMED_CID(NS_AUTOCOMPLETESIMPLERESULT_CID);
  1.1745 +
  1.1746 +static const mozilla::Module::CIDEntry kAutoCompleteCIDs[] = {
  1.1747 +  { &kNS_AUTOCOMPLETECONTROLLER_CID, false, nullptr, nsAutoCompleteControllerConstructor },
  1.1748 +  { &kNS_AUTOCOMPLETESIMPLERESULT_CID, false, nullptr, nsAutoCompleteSimpleResultConstructor },
  1.1749 +  { nullptr }
  1.1750 +};
  1.1751 +
  1.1752 +static const mozilla::Module::ContractIDEntry kAutoCompleteContracts[] = {
  1.1753 +  { NS_AUTOCOMPLETECONTROLLER_CONTRACTID, &kNS_AUTOCOMPLETECONTROLLER_CID },
  1.1754 +  { NS_AUTOCOMPLETESIMPLERESULT_CONTRACTID, &kNS_AUTOCOMPLETESIMPLERESULT_CID },
  1.1755 +  { nullptr }
  1.1756 +};
  1.1757 +
  1.1758 +static const mozilla::Module kAutoCompleteModule = {
  1.1759 +  mozilla::Module::kVersion,
  1.1760 +  kAutoCompleteCIDs,
  1.1761 +  kAutoCompleteContracts
  1.1762 +};
  1.1763 +
  1.1764 +NSMODULE_DEFN(tkAutoCompleteModule) = &kAutoCompleteModule;

mercurial