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