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"
26 #include "nsIFormAutoComplete.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"
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;
55 sInstance
= do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv
);
57 if (NS_SUCCEEDED(rv
)) {
58 ClearOnShutdown(&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
)
80 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFormFillController
)
81 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFormFillController
)
83 nsFormFillController::nsFormFillController()
84 : mFocusedInput(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),
91 mMinResultsForPopup(1),
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();
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() {
112 mListNode
->RemoveMutationObserver(this);
116 MaybeRemoveMutationObserver(mFocusedInput
);
117 mFocusedInput
= nullptr;
119 RemoveForDocument(nullptr);
123 already_AddRefed
<nsFormFillController
> nsFormFillController::GetSingleton() {
124 static RefPtr
<nsFormFillController
> 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
,
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
,
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
) {
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
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
);
249 nsFormFillController::DetachFromDocument(Document
* aDocument
) {
250 if (!xpc::IsInAutomation()) {
251 return NS_ERROR_NOT_AVAILABLE
;
253 mPopups
.Remove(aDocument
);
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
)) {
273 mPwmgrInputs
.InsertOrUpdate(aInput
, true);
274 aInput
->AddMutationObserverUnlessExists(this);
276 nsFocusManager
* fm
= nsFocusManager::GetFocusManager();
278 nsCOMPtr
<nsIContent
> focusedContent
= fm
->GetFocusedElement();
279 if (focusedContent
== aInput
) {
280 if (!mFocusedInput
) {
281 MaybeStartControllingInput(aInput
);
283 // If we change who is responsible for searching the autocomplete
284 // result, notify the controller that the previous result is not valid
286 nsCOMPtr
<nsIAutoCompleteController
> controller
= mController
;
287 controller
->ResetInternalState();
292 if (!mLoginManagerAC
) {
294 do_GetService("@mozilla.org/login-manager/autocompletesearch;1");
300 MOZ_CAN_RUN_SCRIPT NS_IMETHODIMP
nsFormFillController::IsLoginManagerField(
301 HTMLInputElement
* aInput
, bool* isLoginManagerField
) {
302 *isLoginManagerField
= mPwmgrInputs
.Get(aInput
);
307 nsFormFillController::MarkAsAutofillField(HTMLInputElement
* aInput
) {
309 * Support other components implementing form autofill and handle autocomplete
312 NS_ENSURE_STATE(aInput
);
314 MOZ_LOG(sLogger
, LogLevel::Verbose
,
315 ("MarkAsAutofillField: aInput = %p", aInput
));
317 if (mAutofillInputs
.Get(aInput
)) {
321 mAutofillInputs
.InsertOrUpdate(aInput
, true);
322 aInput
->AddMutationObserverUnlessExists(this);
324 aInput
->EnablePreview();
326 nsFocusManager
* fm
= nsFocusManager::GetFocusManager();
328 nsCOMPtr
<nsIContent
> focusedContent
= fm
->GetFocusedElement();
329 if (focusedContent
== aInput
) {
330 if (!mFocusedInput
) {
331 MaybeStartControllingInput(aInput
);
333 // See `MarkAsLoginManagerField` for why this is needed.
334 nsCOMPtr
<nsIAutoCompleteController
> controller
= mController
;
335 controller
->ResetInternalState();
344 nsFormFillController::GetFocusedInput(HTMLInputElement
** aInput
) {
345 *aInput
= mFocusedInput
;
346 NS_IF_ADDREF(*aInput
);
350 ////////////////////////////////////////////////////////////////////////
351 //// nsIAutoCompleteInput
354 nsFormFillController::GetPopup(nsIAutoCompletePopup
** aPopup
) {
355 *aPopup
= mFocusedPopup
;
356 NS_IF_ADDREF(*aPopup
);
361 nsFormFillController::GetPopupElement(Element
** aPopup
) {
362 return NS_ERROR_NOT_IMPLEMENTED
;
366 nsFormFillController::GetController(nsIAutoCompleteController
** aController
) {
367 *aController
= mController
;
368 NS_IF_ADDREF(*aController
);
373 nsFormFillController::GetPopupOpen(bool* aPopupOpen
) {
375 mFocusedPopup
->GetPopupOpen(aPopupOpen
);
383 nsFormFillController::SetPopupOpen(bool 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(
395 ScrollAxis(WhereToScroll::Nearest
, WhenToScroll::IfNotVisible
),
396 ScrollAxis(WhereToScroll::Nearest
, WhenToScroll::IfNotVisible
),
397 ScrollFlags::ScrollOverflowHidden
);
398 // mFocusedPopup can be destroyed after ScrollContentIntoView, see bug
401 mFocusedPopup
->OpenAutocompletePopup(this, mFocusedInput
);
404 mFocusedPopup
->ClosePopup();
405 mPasswordPopupAutomaticallyOpened
= false;
413 nsFormFillController::GetDisableAutoComplete(bool* aDisableAutoComplete
) {
414 *aDisableAutoComplete
= mDisableAutoComplete
;
419 nsFormFillController::SetDisableAutoComplete(bool aDisableAutoComplete
) {
420 mDisableAutoComplete
= aDisableAutoComplete
;
425 nsFormFillController::GetCompleteDefaultIndex(bool* aCompleteDefaultIndex
) {
426 *aCompleteDefaultIndex
= mCompleteDefaultIndex
;
431 nsFormFillController::SetCompleteDefaultIndex(bool aCompleteDefaultIndex
) {
432 mCompleteDefaultIndex
= aCompleteDefaultIndex
;
437 nsFormFillController::GetCompleteSelectedIndex(bool* aCompleteSelectedIndex
) {
438 *aCompleteSelectedIndex
= mCompleteSelectedIndex
;
443 nsFormFillController::SetCompleteSelectedIndex(bool aCompleteSelectedIndex
) {
444 mCompleteSelectedIndex
= aCompleteSelectedIndex
;
449 nsFormFillController::GetForceComplete(bool* aForceComplete
) {
450 *aForceComplete
= mForceComplete
;
454 NS_IMETHODIMP
nsFormFillController::SetForceComplete(bool aForceComplete
) {
455 mForceComplete
= aForceComplete
;
460 nsFormFillController::GetMinResultsForPopup(uint32_t* aMinResultsForPopup
) {
461 *aMinResultsForPopup
= mMinResultsForPopup
;
465 NS_IMETHODIMP
nsFormFillController::SetMinResultsForPopup(
466 uint32_t aMinResultsForPopup
) {
467 mMinResultsForPopup
= aMinResultsForPopup
;
472 nsFormFillController::GetMaxRows(uint32_t* aMaxRows
) {
473 *aMaxRows
= mMaxRows
;
478 nsFormFillController::SetMaxRows(uint32_t aMaxRows
) {
484 nsFormFillController::GetTimeout(uint32_t* aTimeout
) {
485 *aTimeout
= mTimeout
;
489 NS_IMETHODIMP
nsFormFillController::SetTimeout(uint32_t aTimeout
) {
495 nsFormFillController::SetSearchParam(const nsAString
& aSearchParam
) {
496 return NS_ERROR_NOT_IMPLEMENTED
;
500 nsFormFillController::GetSearchParam(nsAString
& aSearchParam
) {
501 if (!mFocusedInput
) {
503 "mFocusedInput is null for some reason! avoiding a crash. should find "
505 return NS_ERROR_FAILURE
; // XXX why? fix me.
508 mFocusedInput
->GetName(aSearchParam
);
509 if (aSearchParam
.IsEmpty()) {
510 mFocusedInput
->GetId(aSearchParam
);
517 nsFormFillController::GetSearchCount(uint32_t* aSearchCount
) {
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");
529 _retval
.AssignLiteral("autofill-profiles");
534 MOZ_LOG(sLogger
, LogLevel::Debug
, ("GetSearchAt: form-history field"));
535 _retval
.AssignLiteral("form-history");
540 nsFormFillController::GetTextValue(nsAString
& aTextValue
) {
542 mFocusedInput
->GetValue(aTextValue
, CallerType::System
);
544 aTextValue
.Truncate();
550 nsFormFillController::SetTextValue(const nsAString
& aTextValue
) {
552 mSuppressOnInput
= true;
553 mFocusedInput
->SetUserInput(aTextValue
,
554 *nsContentUtils::GetSystemPrincipal());
555 mSuppressOnInput
= false;
562 nsFormFillController::GetSelectionStart(int32_t* aSelectionStart
) {
563 if (!mFocusedInput
) {
564 return NS_ERROR_UNEXPECTED
;
567 *aSelectionStart
= mFocusedInput
->GetSelectionStartIgnoringType(rv
);
568 return rv
.StealNSResult();
572 nsFormFillController::GetSelectionEnd(int32_t* aSelectionEnd
) {
573 if (!mFocusedInput
) {
574 return NS_ERROR_UNEXPECTED
;
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
);
588 focusedInput
->SetSelectionRange(aStartIndex
, aEndIndex
, Optional
<nsAString
>(),
590 return rv
.StealNSResult();
594 nsFormFillController::OnSearchBegin() { return NS_OK
; }
597 nsFormFillController::OnSearchComplete() { return NS_OK
; }
600 nsFormFillController::OnTextEntered(Event
* aEvent
) {
601 NS_ENSURE_TRUE(mFocusedInput
, NS_OK
);
606 nsFormFillController::OnTextReverted(bool* _retval
) {
607 mPasswordPopupAutomaticallyOpened
= false;
612 nsFormFillController::GetConsumeRollupEvent(bool* aConsumeRollupEvent
) {
613 *aConsumeRollupEvent
= false;
618 nsFormFillController::GetInPrivateContext(bool* aInPrivateContext
) {
619 if (!mFocusedInput
) {
620 *aInPrivateContext
= false;
624 RefPtr
<Document
> doc
= mFocusedInput
->OwnerDoc();
625 nsCOMPtr
<nsILoadContext
> loadContext
= doc
->GetLoadContext();
626 *aInPrivateContext
= loadContext
&& loadContext
->UsePrivateBrowsing();
631 nsFormFillController::GetNoRollupOnCaretMove(bool* aNoRollupOnCaretMove
) {
632 *aNoRollupOnCaretMove
= false;
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;
643 *aNoRollupOnEmptySearch
= false;
649 nsFormFillController::GetUserContextId(uint32_t* aUserContextId
) {
650 *aUserContextId
= nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID
;
655 nsFormFillController::GetInvalidatePreviousResult(
656 bool* aInvalidatePreviousResult
) {
657 *aInvalidatePreviousResult
= mInvalidatePreviousResult
;
661 ////////////////////////////////////////////////////////////////////////
662 //// nsIAutoCompleteSearch
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
));
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
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
684 if (!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
);
700 MOZ_LOG(sLogger
, LogLevel::Debug
, ("StartSearch: non-login field"));
701 mLastListener
= aListener
;
703 bool addDataList
= IsTextControl(mFocusedInput
);
705 MaybeObserveDataListMutations();
708 auto formAutoComplete
= GetFormAutoComplete();
709 NS_ENSURE_TRUE(formAutoComplete
, NS_ERROR_FAILURE
);
711 formAutoComplete
->AutoCompleteSearchAsync(aSearchParam
, aSearchString
,
712 mFocusedInput
, aPreviousResult
,
714 mLastFormAutoComplete
= formAutoComplete
;
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
));
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
) {
733 mListNode
->RemoveMutationObserver(this);
737 list
->AddMutationObserverUnlessExists(this);
744 void nsFormFillController::RevalidateDataList() {
745 if (!mLastListener
) {
749 nsCOMPtr
<nsIAutoCompleteController
> controller(
750 do_QueryInterface(mLastListener
));
755 // We cannot use previous result since any items in search target are updated.
756 mInvalidatePreviousResult
= true;
757 controller
->StartSearch(mLastSearchString
);
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();
775 ////////////////////////////////////////////////////////////////////////
776 //// nsIFormAutoCompleteObserver
779 nsFormFillController::OnSearchCompletion(nsIAutoCompleteResult
* aResult
) {
780 nsAutoString searchString
;
781 aResult
->GetSearchString(searchString
);
783 mLastSearchString
= searchString
;
786 nsCOMPtr
<nsIAutoCompleteObserver
> lastListener
= mLastListener
;
787 lastListener
->OnSearchResult(this, aResult
);
793 ////////////////////////////////////////////////////////////////////////
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;
811 ////////////////////////////////////////////////////////////////////////
812 //// nsIDOMEventListener
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()) {
830 if (aEvent
->ShouldIgnoreChromeEventTargetListener()) {
834 WidgetEvent
* internalEvent
= aEvent
->WidgetEventPtr();
835 NS_ENSURE_STATE(internalEvent
);
837 switch (internalEvent
->mMessage
) {
839 return Focus(aEvent
);
841 return MouseDown(aEvent
);
843 return KeyDown(aEvent
);
845 if (!(mAutoCompleteActive
|| mSuppressOnInput
)) {
846 nsCOMPtr
<nsINode
> input
=
847 do_QueryInterface(aEvent
->GetComposedTarget());
848 if (IsTextControl(input
) && IsFocusedInputControlled()) {
849 nsCOMPtr
<nsIAutoCompleteController
> controller
= mController
;
851 return controller
->HandleText(&unused
);
857 if (mFocusedInput
&& !StaticPrefs::ui_popup_disable_autohide()) {
858 StopControllingInput();
861 case eCompositionStart
:
862 NS_ASSERTION(mController
, "should have a controller!");
863 if (IsFocusedInputControlled()) {
864 nsCOMPtr
<nsIAutoCompleteController
> controller
= mController
;
865 controller
->HandleStartComposition();
868 case eCompositionEnd
:
869 NS_ASSERTION(mController
, "should have a controller!");
870 if (IsFocusedInputControlled()) {
871 nsCOMPtr
<nsIAutoCompleteController
> controller
= mController
;
872 controller
->HandleEndComposition();
877 mFocusedPopup
->ClosePopup();
881 nsCOMPtr
<Document
> doc
= do_QueryInterface(aEvent
->GetTarget());
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();
895 RemoveForDocument(doc
);
899 // Handling the default case to shut up stupid -Wswitch warnings.
900 // One day compilers will be smarter...
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
934 if (key
!= mFocusedInput
) {
935 const_cast<nsINode
*>(key
)->RemoveMutationObserver(this);
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
946 if (key
!= mFocusedInput
) {
947 const_cast<nsINode
*>(key
)->RemoveMutationObserver(this);
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
));
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
972 StartControllingInput(aInput
);
977 bool autocomplete
= nsContentUtils::IsAutocompleteEnabled(aInput
);
979 bool isPwmgrInput
= false;
980 if (mPwmgrInputs
.Get(aInput
) || aInput
->HasBeenTypePassword()) {
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
) {
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()) {
1014 // If we have not seen a right click yet, just show the popup.
1015 if (mLastRightClickTimeStamp
.IsNull()) {
1016 mPasswordPopupAutomaticallyOpened
= true;
1022 (TimeStamp::Now() - mLastRightClickTimeStamp
).ToMilliseconds();
1023 if (timeDiff
> mFocusAfterRightClickThreshold
) {
1024 mPasswordPopupAutomaticallyOpened
= true;
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()) {
1045 RefPtr
<KeyboardEvent
> keyEvent
= aEvent
->AsKeyboardEvent();
1047 return NS_ERROR_FAILURE
;
1050 bool cancel
= false;
1051 bool unused
= false;
1053 uint32_t k
= keyEvent
->KeyCode();
1055 case KeyboardEvent_Binding::DOM_VK_RETURN
: {
1056 nsCOMPtr
<nsIAutoCompleteController
> controller
= mController
;
1057 controller
->HandleEnter(false, aEvent
, &cancel
);
1060 case KeyboardEvent_Binding::DOM_VK_DELETE
:
1063 nsCOMPtr
<nsIAutoCompleteController
> controller
= mController
;
1064 controller
->HandleDelete(&cancel
);
1067 case KeyboardEvent_Binding::DOM_VK_BACK_SPACE
: {
1068 nsCOMPtr
<nsIAutoCompleteController
> controller
= mController
;
1069 controller
->HandleText(&unused
);
1073 case KeyboardEvent_Binding::DOM_VK_BACK_SPACE
: {
1074 if (keyEvent
->ShiftKey()) {
1075 nsCOMPtr
<nsIAutoCompleteController
> controller
= mController
;
1076 controller
->HandleDelete(&cancel
);
1078 nsCOMPtr
<nsIAutoCompleteController
> controller
= mController
;
1079 controller
->HandleText(&unused
);
1084 case KeyboardEvent_Binding::DOM_VK_PAGE_UP
:
1085 case KeyboardEvent_Binding::DOM_VK_PAGE_DOWN
: {
1086 if (keyEvent
->CtrlKey() || keyEvent
->AltKey() || keyEvent
->MetaKey()) {
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();
1101 wm
= frame
->GetWritingMode();
1104 if (wm
.IsVertical()) {
1106 case KeyboardEvent_Binding::DOM_VK_LEFT
:
1107 k
= wm
.IsVerticalLR() ? KeyboardEvent_Binding::DOM_VK_UP
1108 : KeyboardEvent_Binding::DOM_VK_DOWN
;
1110 case KeyboardEvent_Binding::DOM_VK_RIGHT
:
1111 k
= wm
.IsVerticalLR() ? KeyboardEvent_Binding::DOM_VK_DOWN
1112 : KeyboardEvent_Binding::DOM_VK_UP
;
1114 case KeyboardEvent_Binding::DOM_VK_UP
:
1115 k
= KeyboardEvent_Binding::DOM_VK_LEFT
;
1117 case KeyboardEvent_Binding::DOM_VK_DOWN
:
1118 k
= KeyboardEvent_Binding::DOM_VK_RIGHT
;
1122 nsCOMPtr
<nsIAutoCompleteController
> controller
= mController
;
1123 controller
->HandleKeyNavigation(k
, &cancel
);
1126 case KeyboardEvent_Binding::DOM_VK_ESCAPE
: {
1127 nsCOMPtr
<nsIAutoCompleteController
> controller
= mController
;
1128 controller
->HandleEscape(&cancel
);
1131 case KeyboardEvent_Binding::DOM_VK_TAB
: {
1132 nsCOMPtr
<nsIAutoCompleteController
> controller
= mController
;
1133 controller
->HandleTab();
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();
1153 nsresult
nsFormFillController::MouseDown(Event
* aEvent
) {
1154 MouseEvent
* mouseEvent
= aEvent
->AsMouseEvent();
1156 return NS_ERROR_FAILURE
;
1159 nsCOMPtr
<nsINode
> targetNode
= do_QueryInterface(aEvent
->GetComposedTarget());
1160 if (!HTMLInputElement::FromNodeOrNull(targetNode
)) {
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.
1170 mLastRightClickTimeStamp
= TimeStamp::Now();
1182 nsFormFillController::ShowPopup() {
1183 bool isOpen
= false;
1184 GetPopupOpen(&isOpen
);
1186 return SetPopupOpen(false);
1189 nsCOMPtr
<nsIAutoCompleteController
> controller
= mController
;
1191 nsCOMPtr
<nsIAutoCompleteInput
> input
;
1192 controller
->GetInput(getter_AddRefs(input
));
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
);
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
,
1215 NS_IMETHODIMP
nsFormFillController::GetPasswordPopupAutomaticallyOpened(
1217 *_retval
= mPasswordPopupAutomaticallyOpened
;
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
) {
1230 nsCOMPtr
<nsIAutoCompletePopup
> popup
= mPopups
.Get(aInput
->OwnerDoc());
1232 popup
= do_QueryActor("AutoComplete", aInput
->OwnerDoc());
1238 mFocusedPopup
= popup
;
1240 aInput
->AddMutationObserverUnlessExists(this);
1241 mFocusedInput
= aInput
;
1243 if (Element
* list
= mFocusedInput
->GetList()) {
1244 list
->AddMutationObserverUnlessExists(this);
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;
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();