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