summaryrefslogtreecommitdiffstats
path: root/toolkit/components/satchel/nsFormFillController.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/components/satchel/nsFormFillController.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/satchel/nsFormFillController.cpp')
-rw-r--r--toolkit/components/satchel/nsFormFillController.cpp1359
1 files changed, 1359 insertions, 0 deletions
diff --git a/toolkit/components/satchel/nsFormFillController.cpp b/toolkit/components/satchel/nsFormFillController.cpp
new file mode 100644
index 0000000000..ba55e678d4
--- /dev/null
+++ b/toolkit/components/satchel/nsFormFillController.cpp
@@ -0,0 +1,1359 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsFormFillController.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h" // for Event
+#include "mozilla/dom/HTMLDataListElement.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/KeyboardEvent.h"
+#include "mozilla/dom/KeyboardEventBinding.h"
+#include "mozilla/dom/MouseEvent.h"
+#include "mozilla/dom/PageTransitionEvent.h"
+#include "mozilla/Logging.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "nsCRT.h"
+#include "nsIFormAutoComplete.h"
+#include "nsIInputListAutoComplete.h"
+#include "nsString.h"
+#include "nsPIDOMWindow.h"
+#include "nsIAutoCompleteResult.h"
+#include "nsIContent.h"
+#include "nsInterfaceHashtable.h"
+#include "nsContentUtils.h"
+#include "nsGenericHTMLElement.h"
+#include "nsILoadContext.h"
+#include "nsIFrame.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsFocusManager.h"
+#include "nsQueryActor.h"
+#include "nsQueryObject.h"
+#include "nsServiceManagerUtils.h"
+#include "xpcpublic.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using mozilla::ErrorResult;
+using mozilla::LogLevel;
+
+static mozilla::LazyLogModule sLogger("satchel");
+
+static nsIFormAutoComplete* GetFormAutoComplete() {
+ static nsCOMPtr<nsIFormAutoComplete> sInstance;
+ static bool sInitialized = false;
+ if (!sInitialized) {
+ nsresult rv;
+ sInstance = do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv);
+
+ if (NS_SUCCEEDED(rv)) {
+ ClearOnShutdown(&sInstance);
+ sInitialized = true;
+ }
+ }
+ return sInstance;
+}
+
+NS_IMPL_CYCLE_COLLECTION(nsFormFillController, mController, mLoginManagerAC,
+ mLoginReputationService, mFocusedPopup, mPopups,
+ mLastListener, mLastFormAutoComplete)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFormFillController)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFormFillController)
+ NS_INTERFACE_MAP_ENTRY(nsIFormFillController)
+ NS_INTERFACE_MAP_ENTRY(nsIAutoCompleteInput)
+ NS_INTERFACE_MAP_ENTRY(nsIAutoCompleteSearch)
+ NS_INTERFACE_MAP_ENTRY(nsIFormAutoCompleteObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFormFillController)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFormFillController)
+
+nsFormFillController::nsFormFillController()
+ : mFocusedInput(nullptr),
+ mListNode(nullptr),
+ // The amount of time a context menu event supresses showing a
+ // popup from a focus event in ms. This matches the threshold in
+ // toolkit/components/passwordmgr/LoginManagerChild.jsm.
+ mFocusAfterRightClickThreshold(400),
+ mTimeout(50),
+ mMinResultsForPopup(1),
+ mMaxRows(0),
+ mLastRightClickTimeStamp(TimeStamp()),
+ mDisableAutoComplete(false),
+ mCompleteDefaultIndex(false),
+ mCompleteSelectedIndex(false),
+ mForceComplete(false),
+ mSuppressOnInput(false),
+ mPasswordPopupAutomaticallyOpened(false) {
+ mController = do_GetService("@mozilla.org/autocomplete/controller;1");
+ MOZ_ASSERT(mController);
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ MOZ_ASSERT(obs);
+
+ obs->AddObserver(this, "chrome-event-target-created", false);
+ obs->AddObserver(this, "autofill-fill-starting", false);
+ obs->AddObserver(this, "autofill-fill-complete", false);
+}
+
+nsFormFillController::~nsFormFillController() {
+ if (mListNode) {
+ mListNode->RemoveMutationObserver(this);
+ mListNode = nullptr;
+ }
+ if (mFocusedInput) {
+ MaybeRemoveMutationObserver(mFocusedInput);
+ mFocusedInput = nullptr;
+ }
+ RemoveForDocument(nullptr);
+}
+
+/* static */
+already_AddRefed<nsFormFillController> nsFormFillController::GetSingleton() {
+ static RefPtr<nsFormFillController> sSingleton;
+ if (!sSingleton) {
+ sSingleton = new nsFormFillController();
+ ClearOnShutdown(&sSingleton);
+ }
+ return do_AddRef(sSingleton);
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsIMutationObserver
+//
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY
+void nsFormFillController::AttributeChanged(mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ if ((aAttribute == nsGkAtoms::type || aAttribute == nsGkAtoms::readonly ||
+ aAttribute == nsGkAtoms::autocomplete) &&
+ aNameSpaceID == kNameSpaceID_None) {
+ RefPtr<HTMLInputElement> focusedInput(mFocusedInput);
+ // Reset the current state of the controller, unconditionally.
+ StopControllingInput();
+ // Then restart based on the new values. We have to delay this
+ // to avoid ending up in an endless loop due to re-registering our
+ // mutation observer (which would notify us again for *this* event).
+ nsCOMPtr<nsIRunnable> event =
+ mozilla::NewRunnableMethod<RefPtr<HTMLInputElement>>(
+ "nsFormFillController::MaybeStartControllingInput", this,
+ &nsFormFillController::MaybeStartControllingInput, focusedInput);
+ aElement->OwnerDoc()->Dispatch(TaskCategory::Other, event.forget());
+ }
+
+ if (mListNode && mListNode->Contains(aElement)) {
+ RevalidateDataList();
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY
+void nsFormFillController::ContentAppended(nsIContent* aChild) {
+ if (mListNode && mListNode->Contains(aChild->GetParent())) {
+ RevalidateDataList();
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY
+void nsFormFillController::ContentInserted(nsIContent* aChild) {
+ if (mListNode && mListNode->Contains(aChild->GetParent())) {
+ RevalidateDataList();
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY
+void nsFormFillController::ContentRemoved(nsIContent* aChild,
+ nsIContent* aPreviousSibling) {
+ if (mListNode && mListNode->Contains(aChild->GetParent())) {
+ RevalidateDataList();
+ }
+}
+
+void nsFormFillController::CharacterDataWillChange(
+ nsIContent* aContent, const CharacterDataChangeInfo&) {}
+
+void nsFormFillController::CharacterDataChanged(
+ nsIContent* aContent, const CharacterDataChangeInfo&) {}
+
+void nsFormFillController::AttributeWillChange(mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {}
+
+void nsFormFillController::ParentChainChanged(nsIContent* aContent) {}
+
+void nsFormFillController::ARIAAttributeDefaultWillChange(
+ mozilla::dom::Element* aElement, nsAtom* aAttribute, int32_t aModType) {}
+
+void nsFormFillController::ARIAAttributeDefaultChanged(
+ mozilla::dom::Element* aElement, nsAtom* aAttribute, int32_t aModType) {}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY
+void nsFormFillController::NodeWillBeDestroyed(nsINode* aNode) {
+ MOZ_LOG(sLogger, LogLevel::Verbose, ("NodeWillBeDestroyed: %p", aNode));
+ mPwmgrInputs.Remove(aNode);
+ mAutofillInputs.Remove(aNode);
+ MaybeRemoveMutationObserver(aNode);
+ if (aNode == mListNode) {
+ mListNode = nullptr;
+ RevalidateDataList();
+ } else if (aNode == mFocusedInput) {
+ mFocusedInput = nullptr;
+ }
+}
+
+void nsFormFillController::MaybeRemoveMutationObserver(nsINode* aNode) {
+ // Nodes being tracked in mPwmgrInputs will have their observers removed when
+ // they stop being tracked.
+ if (!mPwmgrInputs.Get(aNode) && !mAutofillInputs.Get(aNode)) {
+ aNode->RemoveMutationObserver(this);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsIFormFillController
+
+NS_IMETHODIMP
+nsFormFillController::AttachPopupElementToDocument(Document* aDocument,
+ dom::Element* aPopupEl) {
+ if (!xpc::IsInAutomation()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ MOZ_LOG(sLogger, LogLevel::Debug,
+ ("AttachPopupElementToDocument for document %p with popup %p",
+ aDocument, aPopupEl));
+ NS_ENSURE_TRUE(aDocument && aPopupEl, NS_ERROR_ILLEGAL_VALUE);
+
+ nsCOMPtr<nsIAutoCompletePopup> popup = aPopupEl->AsAutoCompletePopup();
+ NS_ENSURE_STATE(popup);
+
+ mPopups.InsertOrUpdate(aDocument, popup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::DetachFromDocument(Document* aDocument) {
+ if (!xpc::IsInAutomation()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ mPopups.Remove(aDocument);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::MarkAsLoginManagerField(HTMLInputElement* aInput) {
+ /*
+ * The Login Manager can supply autocomplete results for username fields,
+ * when a user has multiple logins stored for a site. It uses this
+ * interface to indicate that the form manager shouldn't handle the
+ * autocomplete. The form manager also checks for this tag when saving
+ * form history (so it doesn't save usernames).
+ */
+ NS_ENSURE_STATE(aInput);
+
+ // If the field was already marked, we don't want to show the popup again.
+ if (mPwmgrInputs.Get(aInput)) {
+ return NS_OK;
+ }
+
+ mPwmgrInputs.InsertOrUpdate(aInput, true);
+ aInput->AddMutationObserverUnlessExists(this);
+
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedElement();
+ if (focusedContent == aInput) {
+ if (!mFocusedInput) {
+ MaybeStartControllingInput(aInput);
+ } else {
+ // If we change who is responsible for searching the autocomplete
+ // result, notify the controller that the previous result is not valid
+ // anymore.
+ nsCOMPtr<nsIAutoCompleteController> controller = mController;
+ controller->ResetInternalState();
+ }
+ }
+ }
+
+ if (!mLoginManagerAC) {
+ mLoginManagerAC =
+ do_GetService("@mozilla.org/login-manager/autocompletesearch;1");
+ }
+
+ return NS_OK;
+}
+
+MOZ_CAN_RUN_SCRIPT NS_IMETHODIMP nsFormFillController::IsLoginManagerField(
+ HTMLInputElement* aInput, bool* isLoginManagerField) {
+ *isLoginManagerField = mPwmgrInputs.Get(aInput);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::MarkAsAutofillField(HTMLInputElement* aInput) {
+ /*
+ * Support other components implementing form autofill and handle autocomplete
+ * for the field.
+ */
+ NS_ENSURE_STATE(aInput);
+
+ MOZ_LOG(sLogger, LogLevel::Verbose,
+ ("MarkAsAutofillField: aInput = %p", aInput));
+
+ if (mAutofillInputs.Get(aInput)) {
+ return NS_OK;
+ }
+
+ mAutofillInputs.InsertOrUpdate(aInput, true);
+ aInput->AddMutationObserverUnlessExists(this);
+
+ aInput->EnablePreview();
+
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedElement();
+ if (focusedContent == aInput) {
+ if (!mFocusedInput) {
+ MaybeStartControllingInput(aInput);
+ } else {
+ // See `MarkAsLoginManagerField` for why this is needed.
+ nsCOMPtr<nsIAutoCompleteController> controller = mController;
+ controller->ResetInternalState();
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetFocusedInput(HTMLInputElement** aInput) {
+ *aInput = mFocusedInput;
+ NS_IF_ADDREF(*aInput);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsIAutoCompleteInput
+
+NS_IMETHODIMP
+nsFormFillController::GetPopup(nsIAutoCompletePopup** aPopup) {
+ *aPopup = mFocusedPopup;
+ NS_IF_ADDREF(*aPopup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetPopupElement(Element** aPopup) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetController(nsIAutoCompleteController** aController) {
+ *aController = mController;
+ NS_IF_ADDREF(*aController);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetPopupOpen(bool* aPopupOpen) {
+ if (mFocusedPopup) {
+ mFocusedPopup->GetPopupOpen(aPopupOpen);
+ } else {
+ *aPopupOpen = false;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::SetPopupOpen(bool aPopupOpen) {
+ if (mFocusedPopup) {
+ if (aPopupOpen) {
+ // make sure input field is visible before showing popup (bug 320938)
+ nsCOMPtr<nsIContent> content = mFocusedInput;
+ NS_ENSURE_STATE(content);
+ nsCOMPtr<nsIDocShell> docShell = GetDocShellForInput(mFocusedInput);
+ NS_ENSURE_STATE(docShell);
+ RefPtr<PresShell> presShell = docShell->GetPresShell();
+ NS_ENSURE_STATE(presShell);
+ presShell->ScrollContentIntoView(
+ content,
+ ScrollAxis(WhereToScroll::Nearest, WhenToScroll::IfNotVisible),
+ ScrollAxis(WhereToScroll::Nearest, WhenToScroll::IfNotVisible),
+ ScrollFlags::ScrollOverflowHidden);
+ // mFocusedPopup can be destroyed after ScrollContentIntoView, see bug
+ // 420089
+ if (mFocusedPopup) {
+ mFocusedPopup->OpenAutocompletePopup(this, mFocusedInput);
+ }
+ } else {
+ mFocusedPopup->ClosePopup();
+ mPasswordPopupAutomaticallyOpened = false;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetDisableAutoComplete(bool* aDisableAutoComplete) {
+ *aDisableAutoComplete = mDisableAutoComplete;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::SetDisableAutoComplete(bool aDisableAutoComplete) {
+ mDisableAutoComplete = aDisableAutoComplete;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetCompleteDefaultIndex(bool* aCompleteDefaultIndex) {
+ *aCompleteDefaultIndex = mCompleteDefaultIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::SetCompleteDefaultIndex(bool aCompleteDefaultIndex) {
+ mCompleteDefaultIndex = aCompleteDefaultIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetCompleteSelectedIndex(bool* aCompleteSelectedIndex) {
+ *aCompleteSelectedIndex = mCompleteSelectedIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::SetCompleteSelectedIndex(bool aCompleteSelectedIndex) {
+ mCompleteSelectedIndex = aCompleteSelectedIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetForceComplete(bool* aForceComplete) {
+ *aForceComplete = mForceComplete;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFormFillController::SetForceComplete(bool aForceComplete) {
+ mForceComplete = aForceComplete;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetMinResultsForPopup(uint32_t* aMinResultsForPopup) {
+ *aMinResultsForPopup = mMinResultsForPopup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFormFillController::SetMinResultsForPopup(
+ uint32_t aMinResultsForPopup) {
+ mMinResultsForPopup = aMinResultsForPopup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetMaxRows(uint32_t* aMaxRows) {
+ *aMaxRows = mMaxRows;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::SetMaxRows(uint32_t aMaxRows) {
+ mMaxRows = aMaxRows;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetTimeout(uint32_t* aTimeout) {
+ *aTimeout = mTimeout;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFormFillController::SetTimeout(uint32_t aTimeout) {
+ mTimeout = aTimeout;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::SetSearchParam(const nsAString& aSearchParam) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetSearchParam(nsAString& aSearchParam) {
+ if (!mFocusedInput) {
+ NS_WARNING(
+ "mFocusedInput is null for some reason! avoiding a crash. should find "
+ "out why... - ben");
+ return NS_ERROR_FAILURE; // XXX why? fix me.
+ }
+
+ mFocusedInput->GetName(aSearchParam);
+ if (aSearchParam.IsEmpty()) {
+ mFocusedInput->GetId(aSearchParam);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetSearchCount(uint32_t* aSearchCount) {
+ *aSearchCount = 1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetSearchAt(uint32_t index, nsACString& _retval) {
+ if (mAutofillInputs.Get(mFocusedInput)) {
+ MOZ_LOG(sLogger, LogLevel::Debug, ("GetSearchAt: autofill-profiles field"));
+ nsCOMPtr<nsIAutoCompleteSearch> profileSearch = do_GetService(
+ "@mozilla.org/autocomplete/search;1?name=autofill-profiles");
+ if (profileSearch) {
+ _retval.AssignLiteral("autofill-profiles");
+ return NS_OK;
+ }
+ }
+
+ MOZ_LOG(sLogger, LogLevel::Debug, ("GetSearchAt: form-history field"));
+ _retval.AssignLiteral("form-history");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetTextValue(nsAString& aTextValue) {
+ if (mFocusedInput) {
+ mFocusedInput->GetValue(aTextValue, CallerType::System);
+ } else {
+ aTextValue.Truncate();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::SetTextValue(const nsAString& aTextValue) {
+ if (mFocusedInput) {
+ mSuppressOnInput = true;
+ mFocusedInput->SetUserInput(aTextValue,
+ *nsContentUtils::GetSystemPrincipal());
+ mSuppressOnInput = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetSelectionStart(int32_t* aSelectionStart) {
+ if (!mFocusedInput) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ ErrorResult rv;
+ *aSelectionStart = mFocusedInput->GetSelectionStartIgnoringType(rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetSelectionEnd(int32_t* aSelectionEnd) {
+ if (!mFocusedInput) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ ErrorResult rv;
+ *aSelectionEnd = mFocusedInput->GetSelectionEndIgnoringType(rv);
+ return rv.StealNSResult();
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
+nsFormFillController::SelectTextRange(int32_t aStartIndex, int32_t aEndIndex) {
+ if (!mFocusedInput) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ RefPtr<HTMLInputElement> focusedInput(mFocusedInput);
+ ErrorResult rv;
+ focusedInput->SetSelectionRange(aStartIndex, aEndIndex, Optional<nsAString>(),
+ rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+nsFormFillController::OnSearchBegin() { return NS_OK; }
+
+NS_IMETHODIMP
+nsFormFillController::OnSearchComplete() { return NS_OK; }
+
+NS_IMETHODIMP
+nsFormFillController::OnTextEntered(Event* aEvent, bool itemWasSelected,
+ bool* aPrevent) {
+ NS_ENSURE_ARG(aPrevent);
+ NS_ENSURE_TRUE(mFocusedInput, NS_OK);
+
+ /**
+ * This function can get called when text wasn't actually entered
+ * into the field (e.g. if an autocomplete item wasn't selected) so
+ * we don't fire DOMAutoComplete in that case since nothing
+ * was actually autocompleted.
+ */
+ if (!itemWasSelected) {
+ return NS_OK;
+ }
+
+ // Fire off a DOMAutoComplete event
+
+ IgnoredErrorResult ignored;
+ RefPtr<Event> event = mFocusedInput->OwnerDoc()->CreateEvent(
+ u"Events"_ns, CallerType::System, ignored);
+ NS_ENSURE_STATE(event);
+
+ event->InitEvent(u"DOMAutoComplete"_ns, true, true);
+
+ // XXXjst: We mark this event as a trusted event, it's up to the
+ // callers of this to ensure that it's only called from trusted
+ // code.
+ event->SetTrusted(true);
+
+ bool defaultActionEnabled =
+ mFocusedInput->DispatchEvent(*event, CallerType::System, IgnoreErrors());
+ *aPrevent = !defaultActionEnabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::OnTextReverted(bool* _retval) {
+ mPasswordPopupAutomaticallyOpened = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetConsumeRollupEvent(bool* aConsumeRollupEvent) {
+ *aConsumeRollupEvent = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetInPrivateContext(bool* aInPrivateContext) {
+ if (!mFocusedInput) {
+ *aInPrivateContext = false;
+ return NS_OK;
+ }
+
+ RefPtr<Document> doc = mFocusedInput->OwnerDoc();
+ nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
+ *aInPrivateContext = loadContext && loadContext->UsePrivateBrowsing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetNoRollupOnCaretMove(bool* aNoRollupOnCaretMove) {
+ *aNoRollupOnCaretMove = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetNoRollupOnEmptySearch(bool* aNoRollupOnEmptySearch) {
+ if (mFocusedInput && (mPwmgrInputs.Get(mFocusedInput) ||
+ mFocusedInput->HasBeenTypePassword())) {
+ // Don't close the login popup when the field is cleared (bug 1534896).
+ *aNoRollupOnEmptySearch = true;
+ } else {
+ *aNoRollupOnEmptySearch = false;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetUserContextId(uint32_t* aUserContextId) {
+ *aUserContextId = nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetInvalidatePreviousResult(
+ bool* aInvalidatePreviousResult) {
+ *aInvalidatePreviousResult = mInvalidatePreviousResult;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsIAutoCompleteSearch
+
+NS_IMETHODIMP
+nsFormFillController::StartSearch(const nsAString& aSearchString,
+ const nsAString& aSearchParam,
+ nsIAutoCompleteResult* aPreviousResult,
+ nsIAutoCompleteObserver* aListener,
+ nsIPropertyBag2* aOptions) {
+ MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch for %p", mFocusedInput));
+
+ nsresult rv;
+
+ // If the login manager has indicated it's responsible for this field, let it
+ // handle the autocomplete. Otherwise, handle with form history.
+ // This method is sometimes called in unit tests and from XUL without a
+ // focused node.
+ if (mFocusedInput && (mPwmgrInputs.Get(mFocusedInput) ||
+ mFocusedInput->HasBeenTypePassword())) {
+ MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch: login field"));
+
+ // Handle the case where a password field is focused but
+ // MarkAsLoginManagerField wasn't called because password manager is
+ // disabled.
+ if (!mLoginManagerAC) {
+ mLoginManagerAC =
+ do_GetService("@mozilla.org/login-manager/autocompletesearch;1");
+ }
+
+ if (NS_WARN_IF(!mLoginManagerAC)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // XXX aPreviousResult shouldn't ever be a historyResult type, since we're
+ // not letting satchel manage the field?
+ mLastListener = aListener;
+ rv = mLoginManagerAC->StartSearch(aSearchString, aPreviousResult,
+ mFocusedInput, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch: non-login field"));
+ mLastListener = aListener;
+
+ nsCOMPtr<nsIAutoCompleteResult> datalistResult;
+ if (IsTextControl(mFocusedInput)) {
+ rv = PerformInputListAutoComplete(aSearchString,
+ getter_AddRefs(datalistResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ auto formAutoComplete = GetFormAutoComplete();
+ NS_ENSURE_TRUE(formAutoComplete, NS_ERROR_FAILURE);
+
+ formAutoComplete->AutoCompleteSearchAsync(aSearchParam, aSearchString,
+ mFocusedInput, aPreviousResult,
+ datalistResult, this, aOptions);
+ mLastFormAutoComplete = formAutoComplete;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsFormFillController::PerformInputListAutoComplete(
+ const nsAString& aSearch, nsIAutoCompleteResult** aResult) {
+ // If an <input> is focused, check if it has a list="<datalist>" which can
+ // provide the list of suggestions.
+
+ MOZ_ASSERT(!mPwmgrInputs.Get(mFocusedInput));
+ nsresult rv;
+
+ nsCOMPtr<nsIInputListAutoComplete> inputListAutoComplete =
+ do_GetService("@mozilla.org/satchel/inputlist-autocomplete;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = inputListAutoComplete->AutoCompleteSearch(aSearch, mFocusedInput,
+ aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mFocusedInput) {
+ Element* list = mFocusedInput->GetList();
+
+ // Add a mutation observer to check for changes to the items in the
+ // <datalist> and update the suggestions accordingly.
+ if (mListNode != list) {
+ if (mListNode) {
+ mListNode->RemoveMutationObserver(this);
+ mListNode = nullptr;
+ }
+ if (list) {
+ list->AddMutationObserverUnlessExists(this);
+ mListNode = list;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsFormFillController::RevalidateDataList() {
+ if (!mLastListener) {
+ return;
+ }
+
+ nsCOMPtr<nsIAutoCompleteController> controller(
+ do_QueryInterface(mLastListener));
+ if (!controller) {
+ return;
+ }
+
+ // We cannot use previous result since any items in search target are updated.
+ mInvalidatePreviousResult = true;
+ controller->StartSearch(mLastSearchString);
+}
+
+NS_IMETHODIMP
+nsFormFillController::StopSearch() {
+ // Make sure to stop and clear this, otherwise the controller will prevent
+ // mLastFormAutoComplete from being deleted.
+ if (mLastFormAutoComplete) {
+ mLastFormAutoComplete->StopAutoCompleteSearch();
+ mLastFormAutoComplete = nullptr;
+ }
+
+ if (mLoginManagerAC) {
+ mLoginManagerAC->StopSearch();
+ }
+ return NS_OK;
+}
+
+nsresult nsFormFillController::StartQueryLoginReputation(
+ HTMLInputElement* aInput) {
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsIFormAutoCompleteObserver
+
+NS_IMETHODIMP
+nsFormFillController::OnSearchCompletion(nsIAutoCompleteResult* aResult) {
+ nsAutoString searchString;
+ aResult->GetSearchString(searchString);
+
+ mLastSearchString = searchString;
+
+ if (mLastListener) {
+ nsCOMPtr<nsIAutoCompleteObserver> lastListener = mLastListener;
+ lastListener->OnSearchResult(this, aResult);
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsIObserver
+
+NS_IMETHODIMP
+nsFormFillController::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!nsCRT::strcmp(aTopic, "chrome-event-target-created")) {
+ if (RefPtr<EventTarget> eventTarget = do_QueryObject(aSubject)) {
+ AttachListeners(eventTarget);
+ }
+ } else if (!nsCRT::strcmp(aTopic, "autofill-fill-starting")) {
+ mAutoCompleteActive = true;
+ } else if (!nsCRT::strcmp(aTopic, "autofill-fill-complete")) {
+ mAutoCompleteActive = false;
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsIDOMEventListener
+
+NS_IMETHODIMP
+nsFormFillController::HandleEvent(Event* aEvent) {
+ EventTarget* target = aEvent->GetOriginalTarget();
+ NS_ENSURE_STATE(target);
+
+ mInvalidatePreviousResult = false;
+
+ nsIGlobalObject* global = target->GetOwnerGlobal();
+ NS_ENSURE_STATE(global);
+ nsPIDOMWindowInner* inner = global->AsInnerWindow();
+ NS_ENSURE_STATE(inner);
+
+ if (!inner->GetBrowsingContext()->IsContent()) {
+ return NS_OK;
+ }
+
+ if (aEvent->ShouldIgnoreChromeEventTargetListener()) {
+ return NS_OK;
+ }
+
+ WidgetEvent* internalEvent = aEvent->WidgetEventPtr();
+ NS_ENSURE_STATE(internalEvent);
+
+ switch (internalEvent->mMessage) {
+ case eFocus:
+ return Focus(aEvent);
+ case eMouseDown:
+ return MouseDown(aEvent);
+ case eKeyDown:
+ return KeyDown(aEvent);
+ case eEditorInput: {
+ if (!(mAutoCompleteActive || mSuppressOnInput)) {
+ nsCOMPtr<nsINode> input =
+ do_QueryInterface(aEvent->GetComposedTarget());
+ if (IsTextControl(input) && IsFocusedInputControlled()) {
+ nsCOMPtr<nsIAutoCompleteController> controller = mController;
+ bool unused = false;
+ return controller->HandleText(&unused);
+ }
+ }
+ return NS_OK;
+ }
+ case eBlur:
+ if (mFocusedInput && !StaticPrefs::ui_popup_disable_autohide()) {
+ StopControllingInput();
+ }
+ return NS_OK;
+ case eCompositionStart:
+ NS_ASSERTION(mController, "should have a controller!");
+ if (IsFocusedInputControlled()) {
+ nsCOMPtr<nsIAutoCompleteController> controller = mController;
+ controller->HandleStartComposition();
+ }
+ return NS_OK;
+ case eCompositionEnd:
+ NS_ASSERTION(mController, "should have a controller!");
+ if (IsFocusedInputControlled()) {
+ nsCOMPtr<nsIAutoCompleteController> controller = mController;
+ controller->HandleEndComposition();
+ }
+ return NS_OK;
+ case eContextMenu:
+ if (mFocusedPopup) {
+ mFocusedPopup->ClosePopup();
+ }
+ return NS_OK;
+ case ePageHide: {
+ nsCOMPtr<Document> doc = do_QueryInterface(aEvent->GetTarget());
+ if (!doc) {
+ return NS_OK;
+ }
+
+ if (mFocusedInput && doc == mFocusedInput->OwnerDoc()) {
+ StopControllingInput();
+ }
+
+ // Only remove the observer notifications and marked autofill and password
+ // manager fields if the page isn't going to be persisted (i.e. it's being
+ // unloaded) so that appropriate autocomplete handling works with bfcache.
+ bool persisted = aEvent->AsPageTransitionEvent()->Persisted();
+ if (!persisted) {
+ RemoveForDocument(doc);
+ }
+ } break;
+ default:
+ // Handling the default case to shut up stupid -Wswitch warnings.
+ // One day compilers will be smarter...
+ break;
+ }
+
+ return NS_OK;
+}
+
+void nsFormFillController::AttachListeners(EventTarget* aEventTarget) {
+ EventListenerManager* elm = aEventTarget->GetOrCreateListenerManager();
+ NS_ENSURE_TRUE_VOID(elm);
+
+ elm->AddEventListenerByType(this, u"focus"_ns, TrustedEventsAtCapture());
+ elm->AddEventListenerByType(this, u"blur"_ns, TrustedEventsAtCapture());
+ elm->AddEventListenerByType(this, u"pagehide"_ns, TrustedEventsAtCapture());
+ elm->AddEventListenerByType(this, u"mousedown"_ns, TrustedEventsAtCapture());
+ elm->AddEventListenerByType(this, u"input"_ns, TrustedEventsAtCapture());
+ elm->AddEventListenerByType(this, u"keydown"_ns, TrustedEventsAtCapture());
+ elm->AddEventListenerByType(this, u"keypress"_ns,
+ TrustedEventsAtSystemGroupCapture());
+ elm->AddEventListenerByType(this, u"compositionstart"_ns,
+ TrustedEventsAtCapture());
+ elm->AddEventListenerByType(this, u"compositionend"_ns,
+ TrustedEventsAtCapture());
+ elm->AddEventListenerByType(this, u"contextmenu"_ns,
+ TrustedEventsAtCapture());
+}
+
+void nsFormFillController::RemoveForDocument(Document* aDoc) {
+ MOZ_LOG(sLogger, LogLevel::Verbose, ("RemoveForDocument: %p", aDoc));
+ for (auto iter = mPwmgrInputs.Iter(); !iter.Done(); iter.Next()) {
+ const nsINode* key = iter.Key();
+ if (key && (!aDoc || key->OwnerDoc() == aDoc)) {
+ // mFocusedInput's observer is tracked separately, so don't remove it
+ // here.
+ if (key != mFocusedInput) {
+ const_cast<nsINode*>(key)->RemoveMutationObserver(this);
+ }
+ iter.Remove();
+ }
+ }
+
+ for (auto iter = mAutofillInputs.Iter(); !iter.Done(); iter.Next()) {
+ const nsINode* key = iter.Key();
+ if (key && (!aDoc || key->OwnerDoc() == aDoc)) {
+ // mFocusedInput's observer is tracked separately, so don't remove it
+ // here.
+ if (key != mFocusedInput) {
+ const_cast<nsINode*>(key)->RemoveMutationObserver(this);
+ }
+ iter.Remove();
+ }
+ }
+}
+
+bool nsFormFillController::IsTextControl(nsINode* aNode) {
+ nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(aNode);
+ return formControl && formControl->IsSingleLineTextControl(false);
+}
+
+void nsFormFillController::MaybeStartControllingInput(
+ HTMLInputElement* aInput) {
+ MOZ_LOG(sLogger, LogLevel::Verbose,
+ ("MaybeStartControllingInput for %p", aInput));
+ if (!aInput) {
+ return;
+ }
+
+ bool hasList = !!aInput->GetList();
+
+ if (!IsTextControl(aInput)) {
+ // Even if this is not a text control yet, it can become one in the future
+ if (hasList) {
+ StartControllingInput(aInput);
+ }
+ return;
+ }
+
+ bool autocomplete = nsContentUtils::IsAutocompleteEnabled(aInput);
+
+ bool isPwmgrInput = false;
+ if (mPwmgrInputs.Get(aInput) || aInput->HasBeenTypePassword()) {
+ isPwmgrInput = true;
+ }
+
+ bool isAutofillInput = false;
+ if (mAutofillInputs.Get(aInput)) {
+ isAutofillInput = true;
+ }
+
+ if (isAutofillInput || isPwmgrInput || hasList || autocomplete) {
+ StartControllingInput(aInput);
+ }
+
+#ifdef NIGHTLY_BUILD
+ // Trigger an asynchronous login reputation query when user focuses on the
+ // password field.
+ if (aInput->HasBeenTypePassword()) {
+ StartQueryLoginReputation(aInput);
+ }
+#endif
+}
+
+nsresult nsFormFillController::HandleFocus(HTMLInputElement* aInput) {
+ MaybeStartControllingInput(aInput);
+
+ // Bail if we didn't start controlling the input.
+ if (!mFocusedInput) {
+ return NS_OK;
+ }
+
+ // If this focus doesn't follow a right click within our specified
+ // threshold then show the autocomplete popup for all password fields.
+ // This is done to avoid showing both the context menu and the popup
+ // at the same time.
+ // We use a timestamp instead of a bool to avoid complexity when dealing with
+ // multiple input forms and the fact that a mousedown into an already focused
+ // field does not trigger another focus.
+
+ if (!mFocusedInput->HasBeenTypePassword()) {
+ return NS_OK;
+ }
+
+ // If we have not seen a right click yet, just show the popup.
+ if (mLastRightClickTimeStamp.IsNull()) {
+ mPasswordPopupAutomaticallyOpened = true;
+ ShowPopup();
+ return NS_OK;
+ }
+
+ uint64_t timeDiff =
+ (TimeStamp::Now() - mLastRightClickTimeStamp).ToMilliseconds();
+ if (timeDiff > mFocusAfterRightClickThreshold) {
+ mPasswordPopupAutomaticallyOpened = true;
+ ShowPopup();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsFormFillController::Focus(Event* aEvent) {
+ nsCOMPtr<nsIContent> input = do_QueryInterface(aEvent->GetComposedTarget());
+ return HandleFocus(MOZ_KnownLive(HTMLInputElement::FromNodeOrNull(input)));
+}
+
+nsresult nsFormFillController::KeyDown(Event* aEvent) {
+ NS_ASSERTION(mController, "should have a controller!");
+
+ mPasswordPopupAutomaticallyOpened = false;
+
+ if (!IsFocusedInputControlled()) {
+ return NS_OK;
+ }
+
+ RefPtr<KeyboardEvent> keyEvent = aEvent->AsKeyboardEvent();
+ if (!keyEvent) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool cancel = false;
+ bool unused = false;
+
+ uint32_t k = keyEvent->KeyCode();
+ switch (k) {
+ case KeyboardEvent_Binding::DOM_VK_RETURN: {
+ nsCOMPtr<nsIAutoCompleteController> controller = mController;
+ controller->HandleEnter(false, aEvent, &cancel);
+ break;
+ }
+ case KeyboardEvent_Binding::DOM_VK_DELETE:
+#ifndef XP_MACOSX
+ {
+ nsCOMPtr<nsIAutoCompleteController> controller = mController;
+ controller->HandleDelete(&cancel);
+ break;
+ }
+ case KeyboardEvent_Binding::DOM_VK_BACK_SPACE: {
+ nsCOMPtr<nsIAutoCompleteController> controller = mController;
+ controller->HandleText(&unused);
+ break;
+ }
+#else
+ case KeyboardEvent_Binding::DOM_VK_BACK_SPACE: {
+ if (keyEvent->ShiftKey()) {
+ nsCOMPtr<nsIAutoCompleteController> controller = mController;
+ controller->HandleDelete(&cancel);
+ } else {
+ nsCOMPtr<nsIAutoCompleteController> controller = mController;
+ controller->HandleText(&unused);
+ }
+ break;
+ }
+#endif
+ case KeyboardEvent_Binding::DOM_VK_PAGE_UP:
+ case KeyboardEvent_Binding::DOM_VK_PAGE_DOWN: {
+ if (keyEvent->CtrlKey() || keyEvent->AltKey() || keyEvent->MetaKey()) {
+ break;
+ }
+ }
+ [[fallthrough]];
+ case KeyboardEvent_Binding::DOM_VK_UP:
+ case KeyboardEvent_Binding::DOM_VK_DOWN:
+ case KeyboardEvent_Binding::DOM_VK_LEFT:
+ case KeyboardEvent_Binding::DOM_VK_RIGHT: {
+ // Get the writing-mode of the relevant input element,
+ // so that we can remap arrow keys if necessary.
+ mozilla::WritingMode wm;
+ if (mFocusedInput) {
+ nsIFrame* frame = mFocusedInput->GetPrimaryFrame();
+ if (frame) {
+ wm = frame->GetWritingMode();
+ }
+ }
+ if (wm.IsVertical()) {
+ switch (k) {
+ case KeyboardEvent_Binding::DOM_VK_LEFT:
+ k = wm.IsVerticalLR() ? KeyboardEvent_Binding::DOM_VK_UP
+ : KeyboardEvent_Binding::DOM_VK_DOWN;
+ break;
+ case KeyboardEvent_Binding::DOM_VK_RIGHT:
+ k = wm.IsVerticalLR() ? KeyboardEvent_Binding::DOM_VK_DOWN
+ : KeyboardEvent_Binding::DOM_VK_UP;
+ break;
+ case KeyboardEvent_Binding::DOM_VK_UP:
+ k = KeyboardEvent_Binding::DOM_VK_LEFT;
+ break;
+ case KeyboardEvent_Binding::DOM_VK_DOWN:
+ k = KeyboardEvent_Binding::DOM_VK_RIGHT;
+ break;
+ }
+ }
+ nsCOMPtr<nsIAutoCompleteController> controller = mController;
+ controller->HandleKeyNavigation(k, &cancel);
+ break;
+ }
+ case KeyboardEvent_Binding::DOM_VK_ESCAPE: {
+ nsCOMPtr<nsIAutoCompleteController> controller = mController;
+ controller->HandleEscape(&cancel);
+ break;
+ }
+ case KeyboardEvent_Binding::DOM_VK_TAB: {
+ nsCOMPtr<nsIAutoCompleteController> controller = mController;
+ controller->HandleTab();
+ cancel = false;
+ break;
+ }
+ }
+
+ if (cancel) {
+ aEvent->PreventDefault();
+ // Don't let the page see the RETURN event when the popup is open
+ // (indicated by cancel=true) so sites don't manually submit forms
+ // (e.g. via submit.click()) without the autocompleted value being filled.
+ // Bug 286933 will fix this for other key events.
+ if (k == KeyboardEvent_Binding::DOM_VK_RETURN) {
+ aEvent->StopPropagation();
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsFormFillController::MouseDown(Event* aEvent) {
+ MouseEvent* mouseEvent = aEvent->AsMouseEvent();
+ if (!mouseEvent) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsINode> targetNode = do_QueryInterface(aEvent->GetComposedTarget());
+ if (!HTMLInputElement::FromNodeOrNull(targetNode)) {
+ return NS_OK;
+ }
+
+ int16_t button = mouseEvent->Button();
+
+ // In case of a right click we set a timestamp that
+ // will be checked in Focus() to avoid showing
+ // both contextmenu and popup at the same time.
+ if (button == 2) {
+ mLastRightClickTimeStamp = TimeStamp::Now();
+ return NS_OK;
+ }
+
+ if (button != 0) {
+ return NS_OK;
+ }
+
+ return ShowPopup();
+}
+
+NS_IMETHODIMP
+nsFormFillController::ShowPopup() {
+ bool isOpen = false;
+ GetPopupOpen(&isOpen);
+ if (isOpen) {
+ return SetPopupOpen(false);
+ }
+
+ nsCOMPtr<nsIAutoCompleteController> controller = mController;
+
+ nsCOMPtr<nsIAutoCompleteInput> input;
+ controller->GetInput(getter_AddRefs(input));
+ if (!input) {
+ return NS_OK;
+ }
+
+ nsAutoString value;
+ input->GetTextValue(value);
+ if (value.Length() > 0) {
+ // Show the popup with a filtered result set
+ controller->SetSearchString(u""_ns);
+ bool unused = false;
+ controller->HandleText(&unused);
+ } else {
+ // Show the popup with the complete result set. Can't use HandleText()
+ // because it doesn't display the popup if the input is blank.
+ bool cancel = false;
+ controller->HandleKeyNavigation(KeyboardEvent_Binding::DOM_VK_DOWN,
+ &cancel);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFormFillController::GetPasswordPopupAutomaticallyOpened(
+ bool* _retval) {
+ *_retval = mPasswordPopupAutomaticallyOpened;
+ return NS_OK;
+}
+
+void nsFormFillController::StartControllingInput(HTMLInputElement* aInput) {
+ MOZ_LOG(sLogger, LogLevel::Verbose, ("StartControllingInput for %p", aInput));
+ // Make sure we're not still attached to an input
+ StopControllingInput();
+
+ if (!mController || !aInput) {
+ return;
+ }
+
+ nsCOMPtr<nsIAutoCompletePopup> popup = mPopups.Get(aInput->OwnerDoc());
+ if (!popup) {
+ popup = do_QueryActor("AutoComplete", aInput->OwnerDoc());
+ if (!popup) {
+ return;
+ }
+ }
+
+ mFocusedPopup = popup;
+
+ aInput->AddMutationObserverUnlessExists(this);
+ mFocusedInput = aInput;
+
+ if (Element* list = mFocusedInput->GetList()) {
+ list->AddMutationObserverUnlessExists(this);
+ mListNode = list;
+ }
+
+ if (!mFocusedInput->ReadOnly()) {
+ nsCOMPtr<nsIAutoCompleteController> controller = mController;
+ controller->SetInput(this);
+ }
+}
+
+bool nsFormFillController::IsFocusedInputControlled() const {
+ return mFocusedInput && mController && !mFocusedInput->ReadOnly();
+}
+
+void nsFormFillController::StopControllingInput() {
+ mPasswordPopupAutomaticallyOpened = false;
+
+ if (mListNode) {
+ mListNode->RemoveMutationObserver(this);
+ mListNode = nullptr;
+ }
+
+ if (nsCOMPtr<nsIAutoCompleteController> controller = mController) {
+ // Reset the controller's input, but not if it has been switched
+ // to another input already, which might happen if the user switches
+ // focus by clicking another autocomplete textbox
+ nsCOMPtr<nsIAutoCompleteInput> input;
+ controller->GetInput(getter_AddRefs(input));
+ if (input == this) {
+ MOZ_LOG(sLogger, LogLevel::Verbose,
+ ("StopControllingInput: Nulled controller input for %p", this));
+ controller->SetInput(nullptr);
+ }
+ }
+
+ MOZ_LOG(sLogger, LogLevel::Verbose,
+ ("StopControllingInput: Stopped controlling %p", mFocusedInput));
+ if (mFocusedInput) {
+ MaybeRemoveMutationObserver(mFocusedInput);
+ mFocusedInput = nullptr;
+ }
+
+ if (mFocusedPopup) {
+ mFocusedPopup->ClosePopup();
+ }
+ mFocusedPopup = nullptr;
+}
+
+nsIDocShell* nsFormFillController::GetDocShellForInput(
+ HTMLInputElement* aInput) {
+ NS_ENSURE_TRUE(aInput, nullptr);
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = aInput->OwnerDoc()->GetWindow();
+ NS_ENSURE_TRUE(win, nullptr);
+
+ return win->GetDocShell();
+}