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