Backed out 3 changesets (bug 1884623) for causing multiple failures CLOSED TREE
[gecko.git] / toolkit / components / satchel / nsFormFillController.cpp
blob7872ab36c86ec27a5c82b88059704fd847f25842
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsFormFillController.h"
9 #include "mozilla/ClearOnShutdown.h"
10 #include "mozilla/ErrorResult.h"
11 #include "mozilla/EventListenerManager.h"
12 #include "mozilla/dom/Document.h"
13 #include "mozilla/dom/Element.h"
14 #include "mozilla/dom/Event.h" // for Event
15 #include "mozilla/dom/HTMLDataListElement.h"
16 #include "mozilla/dom/HTMLInputElement.h"
17 #include "mozilla/dom/KeyboardEvent.h"
18 #include "mozilla/dom/KeyboardEventBinding.h"
19 #include "mozilla/dom/MouseEvent.h"
20 #include "mozilla/dom/PageTransitionEvent.h"
21 #include "mozilla/Logging.h"
22 #include "mozilla/PresShell.h"
23 #include "mozilla/Services.h"
24 #include "mozilla/StaticPrefs_ui.h"
25 #include "nsCRT.h"
26 #include "nsIFormAutoComplete.h"
27 #include "nsString.h"
28 #include "nsPIDOMWindow.h"
29 #include "nsIAutoCompleteResult.h"
30 #include "nsIContent.h"
31 #include "nsInterfaceHashtable.h"
32 #include "nsContentUtils.h"
33 #include "nsGenericHTMLElement.h"
34 #include "nsILoadContext.h"
35 #include "nsIFrame.h"
36 #include "nsIScriptSecurityManager.h"
37 #include "nsFocusManager.h"
38 #include "nsQueryActor.h"
39 #include "nsQueryObject.h"
40 #include "nsServiceManagerUtils.h"
41 #include "xpcpublic.h"
43 using namespace mozilla;
44 using namespace mozilla::dom;
45 using mozilla::ErrorResult;
46 using mozilla::LogLevel;
48 static mozilla::LazyLogModule sLogger("satchel");
50 static nsIFormAutoComplete* GetFormAutoComplete() {
51 static nsCOMPtr<nsIFormAutoComplete> sInstance;
52 static bool sInitialized = false;
53 if (!sInitialized) {
54 nsresult rv;
55 sInstance = do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv);
57 if (NS_SUCCEEDED(rv)) {
58 ClearOnShutdown(&sInstance);
59 sInitialized = true;
62 return sInstance;
65 NS_IMPL_CYCLE_COLLECTION(nsFormFillController, mController, mLoginManagerAC,
66 mFocusedPopup, mPopups, mLastListener,
67 mLastFormAutoComplete)
69 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFormFillController)
70 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFormFillController)
71 NS_INTERFACE_MAP_ENTRY(nsIFormFillController)
72 NS_INTERFACE_MAP_ENTRY(nsIAutoCompleteInput)
73 NS_INTERFACE_MAP_ENTRY(nsIAutoCompleteSearch)
74 NS_INTERFACE_MAP_ENTRY(nsIFormAutoCompleteObserver)
75 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
76 NS_INTERFACE_MAP_ENTRY(nsIObserver)
77 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
78 NS_INTERFACE_MAP_END
80 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFormFillController)
81 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFormFillController)
83 nsFormFillController::nsFormFillController()
84 : mFocusedInput(nullptr),
85 mListNode(nullptr),
86 // The amount of time a context menu event supresses showing a
87 // popup from a focus event in ms. This matches the threshold in
88 // toolkit/components/passwordmgr/LoginManagerChild.jsm.
89 mFocusAfterRightClickThreshold(400),
90 mTimeout(50),
91 mMinResultsForPopup(1),
92 mMaxRows(0),
93 mDisableAutoComplete(false),
94 mCompleteDefaultIndex(false),
95 mCompleteSelectedIndex(false),
96 mForceComplete(false),
97 mSuppressOnInput(false),
98 mPasswordPopupAutomaticallyOpened(false) {
99 mController = do_GetService("@mozilla.org/autocomplete/controller;1");
100 MOZ_ASSERT(mController);
102 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
103 MOZ_ASSERT(obs);
105 obs->AddObserver(this, "chrome-event-target-created", false);
106 obs->AddObserver(this, "autofill-fill-starting", false);
107 obs->AddObserver(this, "autofill-fill-complete", false);
110 nsFormFillController::~nsFormFillController() {
111 if (mListNode) {
112 mListNode->RemoveMutationObserver(this);
113 mListNode = nullptr;
115 if (mFocusedInput) {
116 MaybeRemoveMutationObserver(mFocusedInput);
117 mFocusedInput = nullptr;
119 RemoveForDocument(nullptr);
122 /* static */
123 already_AddRefed<nsFormFillController> nsFormFillController::GetSingleton() {
124 static RefPtr<nsFormFillController> sSingleton;
125 if (!sSingleton) {
126 sSingleton = new nsFormFillController();
127 ClearOnShutdown(&sSingleton);
129 return do_AddRef(sSingleton);
132 ////////////////////////////////////////////////////////////////////////
133 //// nsIMutationObserver
136 MOZ_CAN_RUN_SCRIPT_BOUNDARY
137 void nsFormFillController::AttributeChanged(mozilla::dom::Element* aElement,
138 int32_t aNameSpaceID,
139 nsAtom* aAttribute,
140 int32_t aModType,
141 const nsAttrValue* aOldValue) {
142 if ((aAttribute == nsGkAtoms::type || aAttribute == nsGkAtoms::readonly ||
143 aAttribute == nsGkAtoms::autocomplete) &&
144 aNameSpaceID == kNameSpaceID_None) {
145 RefPtr<HTMLInputElement> focusedInput(mFocusedInput);
146 // Reset the current state of the controller, unconditionally.
147 StopControllingInput();
148 // Then restart based on the new values. We have to delay this
149 // to avoid ending up in an endless loop due to re-registering our
150 // mutation observer (which would notify us again for *this* event).
151 nsCOMPtr<nsIRunnable> event =
152 mozilla::NewRunnableMethod<RefPtr<HTMLInputElement>>(
153 "nsFormFillController::MaybeStartControllingInput", this,
154 &nsFormFillController::MaybeStartControllingInput, focusedInput);
155 aElement->OwnerDoc()->Dispatch(event.forget());
158 if (mListNode && mListNode->Contains(aElement)) {
159 RevalidateDataList();
163 MOZ_CAN_RUN_SCRIPT_BOUNDARY
164 void nsFormFillController::ContentAppended(nsIContent* aChild) {
165 if (mListNode && mListNode->Contains(aChild->GetParent())) {
166 RevalidateDataList();
170 MOZ_CAN_RUN_SCRIPT_BOUNDARY
171 void nsFormFillController::ContentInserted(nsIContent* aChild) {
172 if (mListNode && mListNode->Contains(aChild->GetParent())) {
173 RevalidateDataList();
177 MOZ_CAN_RUN_SCRIPT_BOUNDARY
178 void nsFormFillController::ContentRemoved(nsIContent* aChild,
179 nsIContent* aPreviousSibling) {
180 if (mListNode && mListNode->Contains(aChild->GetParent())) {
181 RevalidateDataList();
185 void nsFormFillController::CharacterDataWillChange(
186 nsIContent* aContent, const CharacterDataChangeInfo&) {}
188 void nsFormFillController::CharacterDataChanged(
189 nsIContent* aContent, const CharacterDataChangeInfo&) {}
191 void nsFormFillController::AttributeWillChange(mozilla::dom::Element* aElement,
192 int32_t aNameSpaceID,
193 nsAtom* aAttribute,
194 int32_t aModType) {}
196 void nsFormFillController::ParentChainChanged(nsIContent* aContent) {}
198 void nsFormFillController::ARIAAttributeDefaultWillChange(
199 mozilla::dom::Element* aElement, nsAtom* aAttribute, int32_t aModType) {}
201 void nsFormFillController::ARIAAttributeDefaultChanged(
202 mozilla::dom::Element* aElement, nsAtom* aAttribute, int32_t aModType) {}
204 MOZ_CAN_RUN_SCRIPT_BOUNDARY
205 void nsFormFillController::NodeWillBeDestroyed(nsINode* aNode) {
206 MOZ_LOG(sLogger, LogLevel::Verbose, ("NodeWillBeDestroyed: %p", aNode));
207 mPwmgrInputs.Remove(aNode);
208 mAutofillInputs.Remove(aNode);
209 MaybeRemoveMutationObserver(aNode);
210 if (aNode == mListNode) {
211 mListNode = nullptr;
212 RevalidateDataList();
213 } else if (aNode == mFocusedInput) {
214 mFocusedInput = nullptr;
218 void nsFormFillController::MaybeRemoveMutationObserver(nsINode* aNode) {
219 // Nodes being tracked in mPwmgrInputs will have their observers removed when
220 // they stop being tracked.
221 if (!mPwmgrInputs.Get(aNode) && !mAutofillInputs.Get(aNode)) {
222 aNode->RemoveMutationObserver(this);
226 ////////////////////////////////////////////////////////////////////////
227 //// nsIFormFillController
229 NS_IMETHODIMP
230 nsFormFillController::AttachPopupElementToDocument(Document* aDocument,
231 dom::Element* aPopupEl) {
232 if (!xpc::IsInAutomation()) {
233 return NS_ERROR_NOT_AVAILABLE;
236 MOZ_LOG(sLogger, LogLevel::Debug,
237 ("AttachPopupElementToDocument for document %p with popup %p",
238 aDocument, aPopupEl));
239 NS_ENSURE_TRUE(aDocument && aPopupEl, NS_ERROR_ILLEGAL_VALUE);
241 nsCOMPtr<nsIAutoCompletePopup> popup = aPopupEl->AsAutoCompletePopup();
242 NS_ENSURE_STATE(popup);
244 mPopups.InsertOrUpdate(aDocument, popup);
245 return NS_OK;
248 NS_IMETHODIMP
249 nsFormFillController::DetachFromDocument(Document* aDocument) {
250 if (!xpc::IsInAutomation()) {
251 return NS_ERROR_NOT_AVAILABLE;
253 mPopups.Remove(aDocument);
254 return NS_OK;
257 NS_IMETHODIMP
258 nsFormFillController::MarkAsLoginManagerField(HTMLInputElement* aInput) {
260 * The Login Manager can supply autocomplete results for username fields,
261 * when a user has multiple logins stored for a site. It uses this
262 * interface to indicate that the form manager shouldn't handle the
263 * autocomplete. The form manager also checks for this tag when saving
264 * form history (so it doesn't save usernames).
266 NS_ENSURE_STATE(aInput);
268 // If the field was already marked, we don't want to show the popup again.
269 if (mPwmgrInputs.Get(aInput)) {
270 return NS_OK;
273 mPwmgrInputs.InsertOrUpdate(aInput, true);
274 aInput->AddMutationObserverUnlessExists(this);
276 nsFocusManager* fm = nsFocusManager::GetFocusManager();
277 if (fm) {
278 nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedElement();
279 if (focusedContent == aInput) {
280 if (!mFocusedInput) {
281 MaybeStartControllingInput(aInput);
282 } else {
283 // If we change who is responsible for searching the autocomplete
284 // result, notify the controller that the previous result is not valid
285 // anymore.
286 nsCOMPtr<nsIAutoCompleteController> controller = mController;
287 controller->ResetInternalState();
292 if (!mLoginManagerAC) {
293 mLoginManagerAC =
294 do_GetService("@mozilla.org/login-manager/autocompletesearch;1");
297 return NS_OK;
300 MOZ_CAN_RUN_SCRIPT NS_IMETHODIMP nsFormFillController::IsLoginManagerField(
301 HTMLInputElement* aInput, bool* isLoginManagerField) {
302 *isLoginManagerField = mPwmgrInputs.Get(aInput);
303 return NS_OK;
306 NS_IMETHODIMP
307 nsFormFillController::MarkAsAutofillField(HTMLInputElement* aInput) {
309 * Support other components implementing form autofill and handle autocomplete
310 * for the field.
312 NS_ENSURE_STATE(aInput);
314 MOZ_LOG(sLogger, LogLevel::Verbose,
315 ("MarkAsAutofillField: aInput = %p", aInput));
317 if (mAutofillInputs.Get(aInput)) {
318 return NS_OK;
321 mAutofillInputs.InsertOrUpdate(aInput, true);
322 aInput->AddMutationObserverUnlessExists(this);
324 aInput->EnablePreview();
326 nsFocusManager* fm = nsFocusManager::GetFocusManager();
327 if (fm) {
328 nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedElement();
329 if (focusedContent == aInput) {
330 if (!mFocusedInput) {
331 MaybeStartControllingInput(aInput);
332 } else {
333 // See `MarkAsLoginManagerField` for why this is needed.
334 nsCOMPtr<nsIAutoCompleteController> controller = mController;
335 controller->ResetInternalState();
340 return NS_OK;
343 NS_IMETHODIMP
344 nsFormFillController::GetFocusedInput(HTMLInputElement** aInput) {
345 *aInput = mFocusedInput;
346 NS_IF_ADDREF(*aInput);
347 return NS_OK;
350 ////////////////////////////////////////////////////////////////////////
351 //// nsIAutoCompleteInput
353 NS_IMETHODIMP
354 nsFormFillController::GetPopup(nsIAutoCompletePopup** aPopup) {
355 *aPopup = mFocusedPopup;
356 NS_IF_ADDREF(*aPopup);
357 return NS_OK;
360 NS_IMETHODIMP
361 nsFormFillController::GetPopupElement(Element** aPopup) {
362 return NS_ERROR_NOT_IMPLEMENTED;
365 NS_IMETHODIMP
366 nsFormFillController::GetController(nsIAutoCompleteController** aController) {
367 *aController = mController;
368 NS_IF_ADDREF(*aController);
369 return NS_OK;
372 NS_IMETHODIMP
373 nsFormFillController::GetPopupOpen(bool* aPopupOpen) {
374 if (mFocusedPopup) {
375 mFocusedPopup->GetPopupOpen(aPopupOpen);
376 } else {
377 *aPopupOpen = false;
379 return NS_OK;
382 NS_IMETHODIMP
383 nsFormFillController::SetPopupOpen(bool aPopupOpen) {
384 if (mFocusedPopup) {
385 if (aPopupOpen) {
386 // make sure input field is visible before showing popup (bug 320938)
387 nsCOMPtr<nsIContent> content = mFocusedInput;
388 NS_ENSURE_STATE(content);
389 nsCOMPtr<nsIDocShell> docShell = GetDocShellForInput(mFocusedInput);
390 NS_ENSURE_STATE(docShell);
391 RefPtr<PresShell> presShell = docShell->GetPresShell();
392 NS_ENSURE_STATE(presShell);
393 presShell->ScrollContentIntoView(
394 content,
395 ScrollAxis(WhereToScroll::Nearest, WhenToScroll::IfNotVisible),
396 ScrollAxis(WhereToScroll::Nearest, WhenToScroll::IfNotVisible),
397 ScrollFlags::ScrollOverflowHidden);
398 // mFocusedPopup can be destroyed after ScrollContentIntoView, see bug
399 // 420089
400 if (mFocusedPopup) {
401 mFocusedPopup->OpenAutocompletePopup(this, mFocusedInput);
403 } else {
404 mFocusedPopup->ClosePopup();
405 mPasswordPopupAutomaticallyOpened = false;
409 return NS_OK;
412 NS_IMETHODIMP
413 nsFormFillController::GetDisableAutoComplete(bool* aDisableAutoComplete) {
414 *aDisableAutoComplete = mDisableAutoComplete;
415 return NS_OK;
418 NS_IMETHODIMP
419 nsFormFillController::SetDisableAutoComplete(bool aDisableAutoComplete) {
420 mDisableAutoComplete = aDisableAutoComplete;
421 return NS_OK;
424 NS_IMETHODIMP
425 nsFormFillController::GetCompleteDefaultIndex(bool* aCompleteDefaultIndex) {
426 *aCompleteDefaultIndex = mCompleteDefaultIndex;
427 return NS_OK;
430 NS_IMETHODIMP
431 nsFormFillController::SetCompleteDefaultIndex(bool aCompleteDefaultIndex) {
432 mCompleteDefaultIndex = aCompleteDefaultIndex;
433 return NS_OK;
436 NS_IMETHODIMP
437 nsFormFillController::GetCompleteSelectedIndex(bool* aCompleteSelectedIndex) {
438 *aCompleteSelectedIndex = mCompleteSelectedIndex;
439 return NS_OK;
442 NS_IMETHODIMP
443 nsFormFillController::SetCompleteSelectedIndex(bool aCompleteSelectedIndex) {
444 mCompleteSelectedIndex = aCompleteSelectedIndex;
445 return NS_OK;
448 NS_IMETHODIMP
449 nsFormFillController::GetForceComplete(bool* aForceComplete) {
450 *aForceComplete = mForceComplete;
451 return NS_OK;
454 NS_IMETHODIMP nsFormFillController::SetForceComplete(bool aForceComplete) {
455 mForceComplete = aForceComplete;
456 return NS_OK;
459 NS_IMETHODIMP
460 nsFormFillController::GetMinResultsForPopup(uint32_t* aMinResultsForPopup) {
461 *aMinResultsForPopup = mMinResultsForPopup;
462 return NS_OK;
465 NS_IMETHODIMP nsFormFillController::SetMinResultsForPopup(
466 uint32_t aMinResultsForPopup) {
467 mMinResultsForPopup = aMinResultsForPopup;
468 return NS_OK;
471 NS_IMETHODIMP
472 nsFormFillController::GetMaxRows(uint32_t* aMaxRows) {
473 *aMaxRows = mMaxRows;
474 return NS_OK;
477 NS_IMETHODIMP
478 nsFormFillController::SetMaxRows(uint32_t aMaxRows) {
479 mMaxRows = aMaxRows;
480 return NS_OK;
483 NS_IMETHODIMP
484 nsFormFillController::GetTimeout(uint32_t* aTimeout) {
485 *aTimeout = mTimeout;
486 return NS_OK;
489 NS_IMETHODIMP nsFormFillController::SetTimeout(uint32_t aTimeout) {
490 mTimeout = aTimeout;
491 return NS_OK;
494 NS_IMETHODIMP
495 nsFormFillController::SetSearchParam(const nsAString& aSearchParam) {
496 return NS_ERROR_NOT_IMPLEMENTED;
499 NS_IMETHODIMP
500 nsFormFillController::GetSearchParam(nsAString& aSearchParam) {
501 if (!mFocusedInput) {
502 NS_WARNING(
503 "mFocusedInput is null for some reason! avoiding a crash. should find "
504 "out why... - ben");
505 return NS_ERROR_FAILURE; // XXX why? fix me.
508 mFocusedInput->GetName(aSearchParam);
509 if (aSearchParam.IsEmpty()) {
510 mFocusedInput->GetId(aSearchParam);
513 return NS_OK;
516 NS_IMETHODIMP
517 nsFormFillController::GetSearchCount(uint32_t* aSearchCount) {
518 *aSearchCount = 1;
519 return NS_OK;
522 NS_IMETHODIMP
523 nsFormFillController::GetSearchAt(uint32_t index, nsACString& _retval) {
524 if (mAutofillInputs.Get(mFocusedInput)) {
525 MOZ_LOG(sLogger, LogLevel::Debug, ("GetSearchAt: autofill-profiles field"));
526 nsCOMPtr<nsIAutoCompleteSearch> profileSearch = do_GetService(
527 "@mozilla.org/autocomplete/search;1?name=autofill-profiles");
528 if (profileSearch) {
529 _retval.AssignLiteral("autofill-profiles");
530 return NS_OK;
534 MOZ_LOG(sLogger, LogLevel::Debug, ("GetSearchAt: form-history field"));
535 _retval.AssignLiteral("form-history");
536 return NS_OK;
539 NS_IMETHODIMP
540 nsFormFillController::GetTextValue(nsAString& aTextValue) {
541 if (mFocusedInput) {
542 mFocusedInput->GetValue(aTextValue, CallerType::System);
543 } else {
544 aTextValue.Truncate();
546 return NS_OK;
549 NS_IMETHODIMP
550 nsFormFillController::SetTextValue(const nsAString& aTextValue) {
551 if (mFocusedInput) {
552 mSuppressOnInput = true;
553 mFocusedInput->SetUserInput(aTextValue,
554 *nsContentUtils::GetSystemPrincipal());
555 mSuppressOnInput = false;
558 return NS_OK;
561 NS_IMETHODIMP
562 nsFormFillController::GetSelectionStart(int32_t* aSelectionStart) {
563 if (!mFocusedInput) {
564 return NS_ERROR_UNEXPECTED;
566 ErrorResult rv;
567 *aSelectionStart = mFocusedInput->GetSelectionStartIgnoringType(rv);
568 return rv.StealNSResult();
571 NS_IMETHODIMP
572 nsFormFillController::GetSelectionEnd(int32_t* aSelectionEnd) {
573 if (!mFocusedInput) {
574 return NS_ERROR_UNEXPECTED;
576 ErrorResult rv;
577 *aSelectionEnd = mFocusedInput->GetSelectionEndIgnoringType(rv);
578 return rv.StealNSResult();
581 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
582 nsFormFillController::SelectTextRange(int32_t aStartIndex, int32_t aEndIndex) {
583 if (!mFocusedInput) {
584 return NS_ERROR_UNEXPECTED;
586 RefPtr<HTMLInputElement> focusedInput(mFocusedInput);
587 ErrorResult rv;
588 focusedInput->SetSelectionRange(aStartIndex, aEndIndex, Optional<nsAString>(),
589 rv);
590 return rv.StealNSResult();
593 NS_IMETHODIMP
594 nsFormFillController::OnSearchBegin() { return NS_OK; }
596 NS_IMETHODIMP
597 nsFormFillController::OnSearchComplete() { return NS_OK; }
599 NS_IMETHODIMP
600 nsFormFillController::OnTextEntered(Event* aEvent) {
601 NS_ENSURE_TRUE(mFocusedInput, NS_OK);
602 return NS_OK;
605 NS_IMETHODIMP
606 nsFormFillController::OnTextReverted(bool* _retval) {
607 mPasswordPopupAutomaticallyOpened = false;
608 return NS_OK;
611 NS_IMETHODIMP
612 nsFormFillController::GetConsumeRollupEvent(bool* aConsumeRollupEvent) {
613 *aConsumeRollupEvent = false;
614 return NS_OK;
617 NS_IMETHODIMP
618 nsFormFillController::GetInPrivateContext(bool* aInPrivateContext) {
619 if (!mFocusedInput) {
620 *aInPrivateContext = false;
621 return NS_OK;
624 RefPtr<Document> doc = mFocusedInput->OwnerDoc();
625 nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
626 *aInPrivateContext = loadContext && loadContext->UsePrivateBrowsing();
627 return NS_OK;
630 NS_IMETHODIMP
631 nsFormFillController::GetNoRollupOnCaretMove(bool* aNoRollupOnCaretMove) {
632 *aNoRollupOnCaretMove = false;
633 return NS_OK;
636 NS_IMETHODIMP
637 nsFormFillController::GetNoRollupOnEmptySearch(bool* aNoRollupOnEmptySearch) {
638 if (mFocusedInput && (mPwmgrInputs.Get(mFocusedInput) ||
639 mFocusedInput->HasBeenTypePassword())) {
640 // Don't close the login popup when the field is cleared (bug 1534896).
641 *aNoRollupOnEmptySearch = true;
642 } else {
643 *aNoRollupOnEmptySearch = false;
645 return NS_OK;
648 NS_IMETHODIMP
649 nsFormFillController::GetUserContextId(uint32_t* aUserContextId) {
650 *aUserContextId = nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID;
651 return NS_OK;
654 NS_IMETHODIMP
655 nsFormFillController::GetInvalidatePreviousResult(
656 bool* aInvalidatePreviousResult) {
657 *aInvalidatePreviousResult = mInvalidatePreviousResult;
658 return NS_OK;
661 ////////////////////////////////////////////////////////////////////////
662 //// nsIAutoCompleteSearch
664 NS_IMETHODIMP
665 nsFormFillController::StartSearch(const nsAString& aSearchString,
666 const nsAString& aSearchParam,
667 nsIAutoCompleteResult* aPreviousResult,
668 nsIAutoCompleteObserver* aListener) {
669 MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch for %p", mFocusedInput));
671 nsresult rv;
673 // If the login manager has indicated it's responsible for this field, let it
674 // handle the autocomplete. Otherwise, handle with form history.
675 // This method is sometimes called in unit tests and from XUL without a
676 // focused node.
677 if (mFocusedInput && (mPwmgrInputs.Get(mFocusedInput) ||
678 mFocusedInput->HasBeenTypePassword())) {
679 MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch: login field"));
681 // Handle the case where a password field is focused but
682 // MarkAsLoginManagerField wasn't called because password manager is
683 // disabled.
684 if (!mLoginManagerAC) {
685 mLoginManagerAC =
686 do_GetService("@mozilla.org/login-manager/autocompletesearch;1");
689 if (NS_WARN_IF(!mLoginManagerAC)) {
690 return NS_ERROR_FAILURE;
693 // XXX aPreviousResult shouldn't ever be a historyResult type, since we're
694 // not letting satchel manage the field?
695 mLastListener = aListener;
696 rv = mLoginManagerAC->StartSearch(aSearchString, aPreviousResult,
697 mFocusedInput, this);
698 NS_ENSURE_SUCCESS(rv, rv);
699 } else {
700 MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch: non-login field"));
701 mLastListener = aListener;
703 bool addDataList = IsTextControl(mFocusedInput);
704 if (addDataList) {
705 MaybeObserveDataListMutations();
708 auto formAutoComplete = GetFormAutoComplete();
709 NS_ENSURE_TRUE(formAutoComplete, NS_ERROR_FAILURE);
711 formAutoComplete->AutoCompleteSearchAsync(aSearchParam, aSearchString,
712 mFocusedInput, aPreviousResult,
713 addDataList, this);
714 mLastFormAutoComplete = formAutoComplete;
717 return NS_OK;
720 void nsFormFillController::MaybeObserveDataListMutations() {
721 // If an <input> is focused, check if it has a list="<datalist>" which can
722 // provide the list of suggestions.
724 MOZ_ASSERT(!mPwmgrInputs.Get(mFocusedInput));
726 if (mFocusedInput) {
727 Element* list = mFocusedInput->GetList();
729 // Add a mutation observer to check for changes to the items in the
730 // <datalist> and update the suggestions accordingly.
731 if (mListNode != list) {
732 if (mListNode) {
733 mListNode->RemoveMutationObserver(this);
734 mListNode = nullptr;
736 if (list) {
737 list->AddMutationObserverUnlessExists(this);
738 mListNode = list;
744 void nsFormFillController::RevalidateDataList() {
745 if (!mLastListener) {
746 return;
749 nsCOMPtr<nsIAutoCompleteController> controller(
750 do_QueryInterface(mLastListener));
751 if (!controller) {
752 return;
755 // We cannot use previous result since any items in search target are updated.
756 mInvalidatePreviousResult = true;
757 controller->StartSearch(mLastSearchString);
760 NS_IMETHODIMP
761 nsFormFillController::StopSearch() {
762 // Make sure to stop and clear this, otherwise the controller will prevent
763 // mLastFormAutoComplete from being deleted.
764 if (mLastFormAutoComplete) {
765 mLastFormAutoComplete->StopAutoCompleteSearch();
766 mLastFormAutoComplete = nullptr;
769 if (mLoginManagerAC) {
770 mLoginManagerAC->StopSearch();
772 return NS_OK;
775 ////////////////////////////////////////////////////////////////////////
776 //// nsIFormAutoCompleteObserver
778 NS_IMETHODIMP
779 nsFormFillController::OnSearchCompletion(nsIAutoCompleteResult* aResult) {
780 nsAutoString searchString;
781 aResult->GetSearchString(searchString);
783 mLastSearchString = searchString;
785 if (mLastListener) {
786 nsCOMPtr<nsIAutoCompleteObserver> lastListener = mLastListener;
787 lastListener->OnSearchResult(this, aResult);
790 return NS_OK;
793 ////////////////////////////////////////////////////////////////////////
794 //// nsIObserver
796 NS_IMETHODIMP
797 nsFormFillController::Observe(nsISupports* aSubject, const char* aTopic,
798 const char16_t* aData) {
799 if (!nsCRT::strcmp(aTopic, "chrome-event-target-created")) {
800 if (RefPtr<EventTarget> eventTarget = do_QueryObject(aSubject)) {
801 AttachListeners(eventTarget);
803 } else if (!nsCRT::strcmp(aTopic, "autofill-fill-starting")) {
804 mAutoCompleteActive = true;
805 } else if (!nsCRT::strcmp(aTopic, "autofill-fill-complete")) {
806 mAutoCompleteActive = false;
808 return NS_OK;
811 ////////////////////////////////////////////////////////////////////////
812 //// nsIDOMEventListener
814 NS_IMETHODIMP
815 nsFormFillController::HandleEvent(Event* aEvent) {
816 EventTarget* target = aEvent->GetOriginalTarget();
817 NS_ENSURE_STATE(target);
819 mInvalidatePreviousResult = false;
821 nsIGlobalObject* global = target->GetOwnerGlobal();
822 NS_ENSURE_STATE(global);
823 nsPIDOMWindowInner* inner = global->GetAsInnerWindow();
824 NS_ENSURE_STATE(inner);
826 if (!inner->GetBrowsingContext()->IsContent()) {
827 return NS_OK;
830 if (aEvent->ShouldIgnoreChromeEventTargetListener()) {
831 return NS_OK;
834 WidgetEvent* internalEvent = aEvent->WidgetEventPtr();
835 NS_ENSURE_STATE(internalEvent);
837 switch (internalEvent->mMessage) {
838 case eFocus:
839 return Focus(aEvent);
840 case eMouseDown:
841 return MouseDown(aEvent);
842 case eKeyDown:
843 return KeyDown(aEvent);
844 case eEditorInput: {
845 if (!(mAutoCompleteActive || mSuppressOnInput)) {
846 nsCOMPtr<nsINode> input =
847 do_QueryInterface(aEvent->GetComposedTarget());
848 if (IsTextControl(input) && IsFocusedInputControlled()) {
849 nsCOMPtr<nsIAutoCompleteController> controller = mController;
850 bool unused = false;
851 return controller->HandleText(&unused);
854 return NS_OK;
856 case eBlur:
857 if (mFocusedInput && !StaticPrefs::ui_popup_disable_autohide()) {
858 StopControllingInput();
860 return NS_OK;
861 case eCompositionStart:
862 NS_ASSERTION(mController, "should have a controller!");
863 if (IsFocusedInputControlled()) {
864 nsCOMPtr<nsIAutoCompleteController> controller = mController;
865 controller->HandleStartComposition();
867 return NS_OK;
868 case eCompositionEnd:
869 NS_ASSERTION(mController, "should have a controller!");
870 if (IsFocusedInputControlled()) {
871 nsCOMPtr<nsIAutoCompleteController> controller = mController;
872 controller->HandleEndComposition();
874 return NS_OK;
875 case eContextMenu:
876 if (mFocusedPopup) {
877 mFocusedPopup->ClosePopup();
879 return NS_OK;
880 case ePageHide: {
881 nsCOMPtr<Document> doc = do_QueryInterface(aEvent->GetTarget());
882 if (!doc) {
883 return NS_OK;
886 if (mFocusedInput && doc == mFocusedInput->OwnerDoc()) {
887 StopControllingInput();
890 // Only remove the observer notifications and marked autofill and password
891 // manager fields if the page isn't going to be persisted (i.e. it's being
892 // unloaded) so that appropriate autocomplete handling works with bfcache.
893 bool persisted = aEvent->AsPageTransitionEvent()->Persisted();
894 if (!persisted) {
895 RemoveForDocument(doc);
897 } break;
898 default:
899 // Handling the default case to shut up stupid -Wswitch warnings.
900 // One day compilers will be smarter...
901 break;
904 return NS_OK;
907 void nsFormFillController::AttachListeners(EventTarget* aEventTarget) {
908 EventListenerManager* elm = aEventTarget->GetOrCreateListenerManager();
909 NS_ENSURE_TRUE_VOID(elm);
911 elm->AddEventListenerByType(this, u"focus"_ns, TrustedEventsAtCapture());
912 elm->AddEventListenerByType(this, u"blur"_ns, TrustedEventsAtCapture());
913 elm->AddEventListenerByType(this, u"pagehide"_ns, TrustedEventsAtCapture());
914 elm->AddEventListenerByType(this, u"mousedown"_ns, TrustedEventsAtCapture());
915 elm->AddEventListenerByType(this, u"input"_ns, TrustedEventsAtCapture());
916 elm->AddEventListenerByType(this, u"keydown"_ns, TrustedEventsAtCapture());
917 elm->AddEventListenerByType(this, u"keypress"_ns,
918 TrustedEventsAtSystemGroupCapture());
919 elm->AddEventListenerByType(this, u"compositionstart"_ns,
920 TrustedEventsAtCapture());
921 elm->AddEventListenerByType(this, u"compositionend"_ns,
922 TrustedEventsAtCapture());
923 elm->AddEventListenerByType(this, u"contextmenu"_ns,
924 TrustedEventsAtCapture());
927 void nsFormFillController::RemoveForDocument(Document* aDoc) {
928 MOZ_LOG(sLogger, LogLevel::Verbose, ("RemoveForDocument: %p", aDoc));
929 for (auto iter = mPwmgrInputs.Iter(); !iter.Done(); iter.Next()) {
930 const nsINode* key = iter.Key();
931 if (key && (!aDoc || key->OwnerDoc() == aDoc)) {
932 // mFocusedInput's observer is tracked separately, so don't remove it
933 // here.
934 if (key != mFocusedInput) {
935 const_cast<nsINode*>(key)->RemoveMutationObserver(this);
937 iter.Remove();
941 for (auto iter = mAutofillInputs.Iter(); !iter.Done(); iter.Next()) {
942 const nsINode* key = iter.Key();
943 if (key && (!aDoc || key->OwnerDoc() == aDoc)) {
944 // mFocusedInput's observer is tracked separately, so don't remove it
945 // here.
946 if (key != mFocusedInput) {
947 const_cast<nsINode*>(key)->RemoveMutationObserver(this);
949 iter.Remove();
954 bool nsFormFillController::IsTextControl(nsINode* aNode) {
955 nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(aNode);
956 return formControl && formControl->IsSingleLineTextControl(false);
959 void nsFormFillController::MaybeStartControllingInput(
960 HTMLInputElement* aInput) {
961 MOZ_LOG(sLogger, LogLevel::Verbose,
962 ("MaybeStartControllingInput for %p", aInput));
963 if (!aInput) {
964 return;
967 bool hasList = !!aInput->GetList();
969 if (!IsTextControl(aInput)) {
970 // Even if this is not a text control yet, it can become one in the future
971 if (hasList) {
972 StartControllingInput(aInput);
974 return;
977 bool autocomplete = nsContentUtils::IsAutocompleteEnabled(aInput);
979 bool isPwmgrInput = false;
980 if (mPwmgrInputs.Get(aInput) || aInput->HasBeenTypePassword()) {
981 isPwmgrInput = true;
984 bool isAutofillInput = false;
985 if (mAutofillInputs.Get(aInput)) {
986 isAutofillInput = true;
989 if (isAutofillInput || isPwmgrInput || hasList || autocomplete) {
990 StartControllingInput(aInput);
994 nsresult nsFormFillController::HandleFocus(HTMLInputElement* aInput) {
995 MaybeStartControllingInput(aInput);
997 // Bail if we didn't start controlling the input.
998 if (!mFocusedInput) {
999 return NS_OK;
1002 // If this focus doesn't follow a right click within our specified
1003 // threshold then show the autocomplete popup for all password fields.
1004 // This is done to avoid showing both the context menu and the popup
1005 // at the same time.
1006 // We use a timestamp instead of a bool to avoid complexity when dealing with
1007 // multiple input forms and the fact that a mousedown into an already focused
1008 // field does not trigger another focus.
1010 if (!mFocusedInput->HasBeenTypePassword()) {
1011 return NS_OK;
1014 // If we have not seen a right click yet, just show the popup.
1015 if (mLastRightClickTimeStamp.IsNull()) {
1016 mPasswordPopupAutomaticallyOpened = true;
1017 ShowPopup();
1018 return NS_OK;
1021 uint64_t timeDiff =
1022 (TimeStamp::Now() - mLastRightClickTimeStamp).ToMilliseconds();
1023 if (timeDiff > mFocusAfterRightClickThreshold) {
1024 mPasswordPopupAutomaticallyOpened = true;
1025 ShowPopup();
1028 return NS_OK;
1031 nsresult nsFormFillController::Focus(Event* aEvent) {
1032 nsCOMPtr<nsIContent> input = do_QueryInterface(aEvent->GetComposedTarget());
1033 return HandleFocus(MOZ_KnownLive(HTMLInputElement::FromNodeOrNull(input)));
1036 nsresult nsFormFillController::KeyDown(Event* aEvent) {
1037 NS_ASSERTION(mController, "should have a controller!");
1039 mPasswordPopupAutomaticallyOpened = false;
1041 if (!IsFocusedInputControlled()) {
1042 return NS_OK;
1045 RefPtr<KeyboardEvent> keyEvent = aEvent->AsKeyboardEvent();
1046 if (!keyEvent) {
1047 return NS_ERROR_FAILURE;
1050 bool cancel = false;
1051 bool unused = false;
1053 uint32_t k = keyEvent->KeyCode();
1054 switch (k) {
1055 case KeyboardEvent_Binding::DOM_VK_RETURN: {
1056 nsCOMPtr<nsIAutoCompleteController> controller = mController;
1057 controller->HandleEnter(false, aEvent, &cancel);
1058 break;
1060 case KeyboardEvent_Binding::DOM_VK_DELETE:
1061 #ifndef XP_MACOSX
1063 nsCOMPtr<nsIAutoCompleteController> controller = mController;
1064 controller->HandleDelete(&cancel);
1065 break;
1067 case KeyboardEvent_Binding::DOM_VK_BACK_SPACE: {
1068 nsCOMPtr<nsIAutoCompleteController> controller = mController;
1069 controller->HandleText(&unused);
1070 break;
1072 #else
1073 case KeyboardEvent_Binding::DOM_VK_BACK_SPACE: {
1074 if (keyEvent->ShiftKey()) {
1075 nsCOMPtr<nsIAutoCompleteController> controller = mController;
1076 controller->HandleDelete(&cancel);
1077 } else {
1078 nsCOMPtr<nsIAutoCompleteController> controller = mController;
1079 controller->HandleText(&unused);
1081 break;
1083 #endif
1084 case KeyboardEvent_Binding::DOM_VK_PAGE_UP:
1085 case KeyboardEvent_Binding::DOM_VK_PAGE_DOWN: {
1086 if (keyEvent->CtrlKey() || keyEvent->AltKey() || keyEvent->MetaKey()) {
1087 break;
1090 [[fallthrough]];
1091 case KeyboardEvent_Binding::DOM_VK_UP:
1092 case KeyboardEvent_Binding::DOM_VK_DOWN:
1093 case KeyboardEvent_Binding::DOM_VK_LEFT:
1094 case KeyboardEvent_Binding::DOM_VK_RIGHT: {
1095 // Get the writing-mode of the relevant input element,
1096 // so that we can remap arrow keys if necessary.
1097 mozilla::WritingMode wm;
1098 if (mFocusedInput) {
1099 nsIFrame* frame = mFocusedInput->GetPrimaryFrame();
1100 if (frame) {
1101 wm = frame->GetWritingMode();
1104 if (wm.IsVertical()) {
1105 switch (k) {
1106 case KeyboardEvent_Binding::DOM_VK_LEFT:
1107 k = wm.IsVerticalLR() ? KeyboardEvent_Binding::DOM_VK_UP
1108 : KeyboardEvent_Binding::DOM_VK_DOWN;
1109 break;
1110 case KeyboardEvent_Binding::DOM_VK_RIGHT:
1111 k = wm.IsVerticalLR() ? KeyboardEvent_Binding::DOM_VK_DOWN
1112 : KeyboardEvent_Binding::DOM_VK_UP;
1113 break;
1114 case KeyboardEvent_Binding::DOM_VK_UP:
1115 k = KeyboardEvent_Binding::DOM_VK_LEFT;
1116 break;
1117 case KeyboardEvent_Binding::DOM_VK_DOWN:
1118 k = KeyboardEvent_Binding::DOM_VK_RIGHT;
1119 break;
1122 nsCOMPtr<nsIAutoCompleteController> controller = mController;
1123 controller->HandleKeyNavigation(k, &cancel);
1124 break;
1126 case KeyboardEvent_Binding::DOM_VK_ESCAPE: {
1127 nsCOMPtr<nsIAutoCompleteController> controller = mController;
1128 controller->HandleEscape(&cancel);
1129 break;
1131 case KeyboardEvent_Binding::DOM_VK_TAB: {
1132 nsCOMPtr<nsIAutoCompleteController> controller = mController;
1133 controller->HandleTab();
1134 cancel = false;
1135 break;
1139 if (cancel) {
1140 aEvent->PreventDefault();
1141 // Don't let the page see the RETURN event when the popup is open
1142 // (indicated by cancel=true) so sites don't manually submit forms
1143 // (e.g. via submit.click()) without the autocompleted value being filled.
1144 // Bug 286933 will fix this for other key events.
1145 if (k == KeyboardEvent_Binding::DOM_VK_RETURN) {
1146 aEvent->StopPropagation();
1150 return NS_OK;
1153 nsresult nsFormFillController::MouseDown(Event* aEvent) {
1154 MouseEvent* mouseEvent = aEvent->AsMouseEvent();
1155 if (!mouseEvent) {
1156 return NS_ERROR_FAILURE;
1159 nsCOMPtr<nsINode> targetNode = do_QueryInterface(aEvent->GetComposedTarget());
1160 if (!HTMLInputElement::FromNodeOrNull(targetNode)) {
1161 return NS_OK;
1164 int16_t button = mouseEvent->Button();
1166 // In case of a right click we set a timestamp that
1167 // will be checked in Focus() to avoid showing
1168 // both contextmenu and popup at the same time.
1169 if (button == 2) {
1170 mLastRightClickTimeStamp = TimeStamp::Now();
1171 return NS_OK;
1174 if (button != 0) {
1175 return NS_OK;
1178 return ShowPopup();
1181 NS_IMETHODIMP
1182 nsFormFillController::ShowPopup() {
1183 bool isOpen = false;
1184 GetPopupOpen(&isOpen);
1185 if (isOpen) {
1186 return SetPopupOpen(false);
1189 nsCOMPtr<nsIAutoCompleteController> controller = mController;
1191 nsCOMPtr<nsIAutoCompleteInput> input;
1192 controller->GetInput(getter_AddRefs(input));
1193 if (!input) {
1194 return NS_OK;
1197 nsAutoString value;
1198 input->GetTextValue(value);
1199 if (value.Length() > 0) {
1200 // Show the popup with a filtered result set
1201 controller->SetSearchString(u""_ns);
1202 bool unused = false;
1203 controller->HandleText(&unused);
1204 } else {
1205 // Show the popup with the complete result set. Can't use HandleText()
1206 // because it doesn't display the popup if the input is blank.
1207 bool cancel = false;
1208 controller->HandleKeyNavigation(KeyboardEvent_Binding::DOM_VK_DOWN,
1209 &cancel);
1212 return NS_OK;
1215 NS_IMETHODIMP nsFormFillController::GetPasswordPopupAutomaticallyOpened(
1216 bool* _retval) {
1217 *_retval = mPasswordPopupAutomaticallyOpened;
1218 return NS_OK;
1221 void nsFormFillController::StartControllingInput(HTMLInputElement* aInput) {
1222 MOZ_LOG(sLogger, LogLevel::Verbose, ("StartControllingInput for %p", aInput));
1223 // Make sure we're not still attached to an input
1224 StopControllingInput();
1226 if (!mController || !aInput) {
1227 return;
1230 nsCOMPtr<nsIAutoCompletePopup> popup = mPopups.Get(aInput->OwnerDoc());
1231 if (!popup) {
1232 popup = do_QueryActor("AutoComplete", aInput->OwnerDoc());
1233 if (!popup) {
1234 return;
1238 mFocusedPopup = popup;
1240 aInput->AddMutationObserverUnlessExists(this);
1241 mFocusedInput = aInput;
1243 if (Element* list = mFocusedInput->GetList()) {
1244 list->AddMutationObserverUnlessExists(this);
1245 mListNode = list;
1248 if (!mFocusedInput->ReadOnly()) {
1249 nsCOMPtr<nsIAutoCompleteController> controller = mController;
1250 controller->SetInput(this);
1254 bool nsFormFillController::IsFocusedInputControlled() const {
1255 return mFocusedInput && mController && !mFocusedInput->ReadOnly();
1258 void nsFormFillController::StopControllingInput() {
1259 mPasswordPopupAutomaticallyOpened = false;
1261 if (mListNode) {
1262 mListNode->RemoveMutationObserver(this);
1263 mListNode = nullptr;
1266 if (nsCOMPtr<nsIAutoCompleteController> controller = mController) {
1267 // Reset the controller's input, but not if it has been switched
1268 // to another input already, which might happen if the user switches
1269 // focus by clicking another autocomplete textbox
1270 nsCOMPtr<nsIAutoCompleteInput> input;
1271 controller->GetInput(getter_AddRefs(input));
1272 if (input == this) {
1273 MOZ_LOG(sLogger, LogLevel::Verbose,
1274 ("StopControllingInput: Nulled controller input for %p", this));
1275 controller->SetInput(nullptr);
1279 MOZ_LOG(sLogger, LogLevel::Verbose,
1280 ("StopControllingInput: Stopped controlling %p", mFocusedInput));
1281 if (mFocusedInput) {
1282 MaybeRemoveMutationObserver(mFocusedInput);
1283 mFocusedInput = nullptr;
1286 if (mFocusedPopup) {
1287 mFocusedPopup->ClosePopup();
1289 mFocusedPopup = nullptr;
1292 nsIDocShell* nsFormFillController::GetDocShellForInput(
1293 HTMLInputElement* aInput) {
1294 NS_ENSURE_TRUE(aInput, nullptr);
1296 nsCOMPtr<nsPIDOMWindowOuter> win = aInput->OwnerDoc()->GetWindow();
1297 NS_ENSURE_TRUE(win, nullptr);
1299 return win->GetDocShell();