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.

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

mercurial