/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; |