Coverage Report

Created: 2018-09-25 14:53

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