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