summaryrefslogtreecommitdiffstats
path: root/toolkit/components/satchel/nsFormFillController.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/satchel/nsFormFillController.cpp')
-rw-r--r--toolkit/components/satchel/nsFormFillController.cpp1382
1 files changed, 1382 insertions, 0 deletions
diff --git a/toolkit/components/satchel/nsFormFillController.cpp b/toolkit/components/satchel/nsFormFillController.cpp
new file mode 100644
index 000000000..d70036635
--- /dev/null
+++ b/toolkit/components/satchel/nsFormFillController.cpp
@@ -0,0 +1,1382 @@
+/* -*- 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/dom/Element.h"
+#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
+#include "nsIFormAutoComplete.h"
+#include "nsIInputListAutoComplete.h"
+#include "nsIAutoCompleteSimpleResult.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsIServiceManager.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsPIDOMWindow.h"
+#include "nsIWebNavigation.h"
+#include "nsIContentViewer.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMElement.h"
+#include "nsIFormControl.h"
+#include "nsIDocument.h"
+#include "nsIContent.h"
+#include "nsIPresShell.h"
+#include "nsRect.h"
+#include "nsIDOMHTMLFormElement.h"
+#include "nsILoginManager.h"
+#include "nsIDOMMouseEvent.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsToolkitCompsCID.h"
+#include "nsEmbedCID.h"
+#include "nsIDOMNSEditableElement.h"
+#include "nsContentUtils.h"
+#include "nsILoadContext.h"
+#include "nsIFrame.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsFocusManager.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION(nsFormFillController,
+ mController, mLoginManager, mFocusedPopup, mDocShells,
+ 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(nsIDOMEventListener)
+ NS_INTERFACE_MAP_ENTRY(nsIFormAutoCompleteObserver)
+ 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),
+ mFocusedInputNode(nullptr),
+ mListNode(nullptr),
+ mTimeout(50),
+ mMinResultsForPopup(1),
+ mMaxRows(0),
+ mContextMenuFiredBeforeFocus(false),
+ mDisableAutoComplete(false),
+ mCompleteDefaultIndex(false),
+ mCompleteSelectedIndex(false),
+ mForceComplete(false),
+ mSuppressOnInput(false)
+{
+ mController = do_GetService("@mozilla.org/autocomplete/controller;1");
+ MOZ_ASSERT(mController);
+}
+
+nsFormFillController::~nsFormFillController()
+{
+ if (mListNode) {
+ mListNode->RemoveMutationObserver(this);
+ mListNode = nullptr;
+ }
+ if (mFocusedInputNode) {
+ MaybeRemoveMutationObserver(mFocusedInputNode);
+ mFocusedInputNode = nullptr;
+ mFocusedInput = nullptr;
+ }
+ RemoveForDocument(nullptr);
+
+ // Remove ourselves as a focus listener from all cached docShells
+ uint32_t count = mDocShells.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = GetWindowForDocShell(mDocShells[i]);
+ RemoveWindowListeners(window);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsIMutationObserver
+//
+
+void
+nsFormFillController::AttributeChanged(nsIDocument* aDocument,
+ mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ if ((aAttribute == nsGkAtoms::type || aAttribute == nsGkAtoms::readonly ||
+ aAttribute == nsGkAtoms::autocomplete) &&
+ aNameSpaceID == kNameSpaceID_None) {
+ nsCOMPtr<nsIDOMHTMLInputElement> 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<nsCOMPtr<nsIDOMHTMLInputElement>>
+ (this, &nsFormFillController::MaybeStartControllingInput, focusedInput);
+ NS_DispatchToCurrentThread(event);
+ }
+
+ if (mListNode && mListNode->Contains(aElement)) {
+ RevalidateDataList();
+ }
+}
+
+void
+nsFormFillController::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ if (mListNode && mListNode->Contains(aContainer)) {
+ RevalidateDataList();
+ }
+}
+
+void
+nsFormFillController::ContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ if (mListNode && mListNode->Contains(aContainer)) {
+ RevalidateDataList();
+ }
+}
+
+void
+nsFormFillController::ContentRemoved(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ if (mListNode && mListNode->Contains(aContainer)) {
+ RevalidateDataList();
+ }
+}
+
+void
+nsFormFillController::CharacterDataWillChange(nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+}
+
+void
+nsFormFillController::CharacterDataChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+}
+
+void
+nsFormFillController::AttributeWillChange(nsIDocument* aDocument,
+ mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aNewValue)
+{
+}
+
+void
+nsFormFillController::NativeAnonymousChildListChange(nsIDocument* aDocument,
+ nsIContent* aContent,
+ bool aIsRemove)
+{
+}
+
+void
+nsFormFillController::ParentChainChanged(nsIContent* aContent)
+{
+}
+
+void
+nsFormFillController::NodeWillBeDestroyed(const nsINode* aNode)
+{
+ mPwmgrInputs.Remove(aNode);
+ if (aNode == mListNode) {
+ mListNode = nullptr;
+ RevalidateDataList();
+ } else if (aNode == mFocusedInputNode) {
+ mFocusedInputNode = nullptr;
+ 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)) {
+ aNode->RemoveMutationObserver(this);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsIFormFillController
+
+NS_IMETHODIMP
+nsFormFillController::AttachToBrowser(nsIDocShell *aDocShell, nsIAutoCompletePopup *aPopup)
+{
+ NS_ENSURE_TRUE(aDocShell && aPopup, NS_ERROR_ILLEGAL_VALUE);
+
+ mDocShells.AppendElement(aDocShell);
+ mPopups.AppendElement(aPopup);
+
+ // Listen for focus events on the domWindow of the docShell
+ nsCOMPtr<nsPIDOMWindowOuter> window = GetWindowForDocShell(aDocShell);
+ AddWindowListeners(window);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::DetachFromBrowser(nsIDocShell *aDocShell)
+{
+ int32_t index = GetIndexOfDocShell(aDocShell);
+ NS_ENSURE_TRUE(index >= 0, NS_ERROR_FAILURE);
+
+ // Stop listening for focus events on the domWindow of the docShell
+ nsCOMPtr<nsPIDOMWindowOuter> window =
+ GetWindowForDocShell(mDocShells.SafeElementAt(index));
+ RemoveWindowListeners(window);
+
+ mDocShells.RemoveElementAt(index);
+ mPopups.RemoveElementAt(index);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsFormFillController::MarkAsLoginManagerField(nsIDOMHTMLInputElement *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).
+ */
+ nsCOMPtr<nsINode> node = do_QueryInterface(aInput);
+ NS_ENSURE_STATE(node);
+
+ // If the field was already marked, we don't want to show the popup again.
+ if (mPwmgrInputs.Get(node)) {
+ return NS_OK;
+ }
+
+ mPwmgrInputs.Put(node, true);
+ node->AddMutationObserverUnlessExists(this);
+
+ nsFocusManager *fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedContent();
+ if (SameCOMIdentity(focusedContent, node)) {
+ nsCOMPtr<nsIDOMHTMLInputElement> input = do_QueryInterface(node);
+ if (!mFocusedInput) {
+ MaybeStartControllingInput(input);
+ }
+ }
+ }
+
+ if (!mLoginManager)
+ mLoginManager = do_GetService("@mozilla.org/login-manager;1");
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetFocusedInput(nsIDOMHTMLInputElement **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::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 = do_QueryInterface(mFocusedInput);
+ NS_ENSURE_STATE(content);
+ nsCOMPtr<nsIDocShell> docShell = GetDocShellForInput(mFocusedInput);
+ NS_ENSURE_STATE(docShell);
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ NS_ENSURE_STATE(presShell);
+ presShell->ScrollContentIntoView(content,
+ nsIPresShell::ScrollAxis(
+ nsIPresShell::SCROLL_MINIMUM,
+ nsIPresShell::SCROLL_IF_NOT_VISIBLE),
+ nsIPresShell::ScrollAxis(
+ nsIPresShell::SCROLL_MINIMUM,
+ nsIPresShell::SCROLL_IF_NOT_VISIBLE),
+ nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
+ // mFocusedPopup can be destroyed after ScrollContentIntoView, see bug 420089
+ if (mFocusedPopup) {
+ nsCOMPtr<nsIDOMElement> element = do_QueryInterface(mFocusedInput);
+ mFocusedPopup->OpenAutocompletePopup(this, element);
+ }
+ } else
+ mFocusedPopup->ClosePopup();
+ }
+
+ 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::GetShowImageColumn(bool *aShowImageColumn)
+{
+ *aShowImageColumn = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFormFillController::SetShowImageColumn(bool aShowImageColumn)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+nsFormFillController::GetShowCommentColumn(bool *aShowCommentColumn)
+{
+ *aShowCommentColumn = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFormFillController::SetShowCommentColumn(bool aShowCommentColumn)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+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()) {
+ nsCOMPtr<nsIDOMHTMLElement> element = do_QueryInterface(mFocusedInput);
+ element->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)
+{
+ _retval.AssignLiteral("form-history");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetTextValue(nsAString & aTextValue)
+{
+ if (mFocusedInput) {
+ nsCOMPtr<nsIDOMHTMLInputElement> input = mFocusedInput;
+ input->GetValue(aTextValue);
+ } else {
+ aTextValue.Truncate();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::SetTextValue(const nsAString & aTextValue)
+{
+ nsCOMPtr<nsIDOMNSEditableElement> editable = do_QueryInterface(mFocusedInput);
+ if (editable) {
+ mSuppressOnInput = true;
+ editable->SetUserInput(aTextValue);
+ mSuppressOnInput = false;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::SetTextValueWithReason(const nsAString & aTextValue,
+ uint16_t aReason)
+{
+ return SetTextValue(aTextValue);
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetSelectionStart(int32_t *aSelectionStart)
+{
+ if (mFocusedInput) {
+ nsCOMPtr<nsIDOMHTMLInputElement> input = mFocusedInput;
+ input->GetSelectionStart(aSelectionStart);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetSelectionEnd(int32_t *aSelectionEnd)
+{
+ if (mFocusedInput) {
+ nsCOMPtr<nsIDOMHTMLInputElement> input = mFocusedInput;
+ input->GetSelectionEnd(aSelectionEnd);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::SelectTextRange(int32_t aStartIndex, int32_t aEndIndex)
+{
+ if (mFocusedInput) {
+ nsCOMPtr<nsIDOMHTMLInputElement> input = mFocusedInput;
+ input->SetSelectionRange(aStartIndex, aEndIndex, EmptyString());
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::OnSearchBegin()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::OnSearchComplete()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::OnTextEntered(nsIDOMEvent* aEvent,
+ bool* aPrevent)
+{
+ NS_ENSURE_ARG(aPrevent);
+ NS_ENSURE_TRUE(mFocusedInput, NS_OK);
+ // Fire off a DOMAutoComplete event
+ nsCOMPtr<nsIDOMDocument> domDoc;
+ nsCOMPtr<nsIDOMElement> element = do_QueryInterface(mFocusedInput);
+ element->GetOwnerDocument(getter_AddRefs(domDoc));
+ NS_ENSURE_STATE(domDoc);
+
+ nsCOMPtr<nsIDOMEvent> event;
+ domDoc->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event));
+ NS_ENSURE_STATE(event);
+
+ event->InitEvent(NS_LITERAL_STRING("DOMAutoComplete"), 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);
+
+ nsCOMPtr<EventTarget> targ = do_QueryInterface(mFocusedInput);
+
+ bool defaultActionEnabled;
+ targ->DispatchEvent(event, &defaultActionEnabled);
+ *aPrevent = !defaultActionEnabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::OnTextReverted(bool *_retval)
+{
+ 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;
+ }
+
+ nsCOMPtr<nsIDOMDocument> inputDoc;
+ nsCOMPtr<nsIDOMElement> element = do_QueryInterface(mFocusedInput);
+ element->GetOwnerDocument(getter_AddRefs(inputDoc));
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(inputDoc);
+ 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::GetUserContextId(uint32_t* aUserContextId)
+{
+ *aUserContextId = nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsIAutoCompleteSearch
+
+NS_IMETHODIMP
+nsFormFillController::StartSearch(const nsAString &aSearchString, const nsAString &aSearchParam,
+ nsIAutoCompleteResult *aPreviousResult, nsIAutoCompleteObserver *aListener)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(mFocusedInputNode);
+
+ // 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 (mFocusedInputNode && (mPwmgrInputs.Get(mFocusedInputNode) ||
+ formControl->GetType() == NS_FORM_INPUT_PASSWORD)) {
+
+ // Handle the case where a password field is focused but
+ // MarkAsLoginManagerField wasn't called because password manager is disabled.
+ if (!mLoginManager) {
+ mLoginManager = do_GetService("@mozilla.org/login-manager;1");
+ }
+
+ if (NS_WARN_IF(!mLoginManager)) {
+ 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 = mLoginManager->AutoCompleteSearchAsync(aSearchString,
+ aPreviousResult,
+ mFocusedInput,
+ this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ mLastListener = aListener;
+
+ nsCOMPtr<nsIAutoCompleteResult> datalistResult;
+ if (mFocusedInput) {
+ rv = PerformInputListAutoComplete(aSearchString,
+ getter_AddRefs(datalistResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr <nsIFormAutoComplete> formAutoComplete =
+ do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ formAutoComplete->AutoCompleteSearchAsync(aSearchParam,
+ aSearchString,
+ mFocusedInput,
+ aPreviousResult,
+ datalistResult,
+ this);
+ 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(mFocusedInputNode));
+ 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) {
+ nsCOMPtr<nsIDOMHTMLElement> list;
+ mFocusedInput->GetList(getter_AddRefs(list));
+
+ // Add a mutation observer to check for changes to the items in the <datalist>
+ // and update the suggestions accordingly.
+ nsCOMPtr<nsINode> node = do_QueryInterface(list);
+ if (mListNode != node) {
+ if (mListNode) {
+ mListNode->RemoveMutationObserver(this);
+ mListNode = nullptr;
+ }
+ if (node) {
+ node->AddMutationObserverUnlessExists(this);
+ mListNode = node;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+class UpdateSearchResultRunnable : public mozilla::Runnable
+{
+public:
+ UpdateSearchResultRunnable(nsIAutoCompleteObserver* aObserver,
+ nsIAutoCompleteSearch* aSearch,
+ nsIAutoCompleteResult* aResult)
+ : mObserver(aObserver)
+ , mSearch(aSearch)
+ , mResult(aResult)
+ {
+ MOZ_ASSERT(mResult, "Should have a valid result");
+ MOZ_ASSERT(mObserver, "You shouldn't call this runnable with a null observer!");
+ }
+
+ NS_IMETHOD Run() override {
+ mObserver->OnUpdateSearchResult(mSearch, mResult);
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIAutoCompleteObserver> mObserver;
+ nsCOMPtr<nsIAutoCompleteSearch> mSearch;
+ nsCOMPtr<nsIAutoCompleteResult> mResult;
+};
+
+void nsFormFillController::RevalidateDataList()
+{
+ if (!mLastListener) {
+ return;
+ }
+
+ if (XRE_IsContentProcess()) {
+ nsCOMPtr<nsIAutoCompleteController> controller(do_QueryInterface(mLastListener));
+ if (!controller) {
+ return;
+ }
+
+ controller->StartSearch(mLastSearchString);
+ return;
+ }
+
+ nsresult rv;
+ nsCOMPtr <nsIInputListAutoComplete> inputListAutoComplete =
+ do_GetService("@mozilla.org/satchel/inputlist-autocomplete;1", &rv);
+
+ nsCOMPtr<nsIAutoCompleteResult> result;
+
+ rv = inputListAutoComplete->AutoCompleteSearch(mLastSearchString,
+ mFocusedInput,
+ getter_AddRefs(result));
+
+ nsCOMPtr<nsIRunnable> event =
+ new UpdateSearchResultRunnable(mLastListener, this, result);
+ NS_DispatchToCurrentThread(event);
+}
+
+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;
+ } else if (mLoginManager) {
+ mLoginManager->StopSearch();
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsIFormAutoCompleteObserver
+
+NS_IMETHODIMP
+nsFormFillController::OnSearchCompletion(nsIAutoCompleteResult *aResult)
+{
+ nsAutoString searchString;
+ aResult->GetSearchString(searchString);
+
+ mLastSearchString = searchString;
+
+ if (mLastListener) {
+ mLastListener->OnSearchResult(this, aResult);
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsIDOMEventListener
+
+NS_IMETHODIMP
+nsFormFillController::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsAutoString type;
+ aEvent->GetType(type);
+
+ if (type.EqualsLiteral("focus")) {
+ return Focus(aEvent);
+ }
+ if (type.EqualsLiteral("mousedown")) {
+ return MouseDown(aEvent);
+ }
+ if (type.EqualsLiteral("keypress")) {
+ return KeyPress(aEvent);
+ }
+ if (type.EqualsLiteral("input")) {
+ bool unused = false;
+ return (!mSuppressOnInput && mController && mFocusedInput) ?
+ mController->HandleText(&unused) : NS_OK;
+ }
+ if (type.EqualsLiteral("blur")) {
+ if (mFocusedInput)
+ StopControllingInput();
+ return NS_OK;
+ }
+ if (type.EqualsLiteral("compositionstart")) {
+ NS_ASSERTION(mController, "should have a controller!");
+ if (mController && mFocusedInput)
+ mController->HandleStartComposition();
+ return NS_OK;
+ }
+ if (type.EqualsLiteral("compositionend")) {
+ NS_ASSERTION(mController, "should have a controller!");
+ if (mController && mFocusedInput)
+ mController->HandleEndComposition();
+ return NS_OK;
+ }
+ if (type.EqualsLiteral("contextmenu")) {
+ mContextMenuFiredBeforeFocus = true;
+ if (mFocusedPopup)
+ mFocusedPopup->ClosePopup();
+ return NS_OK;
+ }
+ if (type.EqualsLiteral("pagehide")) {
+
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(
+ aEvent->InternalDOMEvent()->GetTarget());
+ if (!doc)
+ return NS_OK;
+
+ if (mFocusedInput) {
+ if (doc == mFocusedInputNode->OwnerDoc())
+ StopControllingInput();
+ }
+
+ RemoveForDocument(doc);
+ }
+
+ return NS_OK;
+}
+
+void
+nsFormFillController::RemoveForDocument(nsIDocument* aDoc)
+{
+ for (auto iter = mPwmgrInputs.Iter(); !iter.Done(); iter.Next()) {
+ const nsINode* key = iter.Key();
+ if (key && (!aDoc || key->OwnerDoc() == aDoc)) {
+ // mFocusedInputNode's observer is tracked separately, so don't remove it
+ // here.
+ if (key != mFocusedInputNode) {
+ const_cast<nsINode*>(key)->RemoveMutationObserver(this);
+ }
+ iter.Remove();
+ }
+ }
+}
+
+void
+nsFormFillController::MaybeStartControllingInput(nsIDOMHTMLInputElement* aInput)
+{
+ nsCOMPtr<nsINode> inputNode = do_QueryInterface(aInput);
+ if (!inputNode)
+ return;
+
+ nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(aInput);
+ if (!formControl || !formControl->IsSingleLineTextControl(false))
+ return;
+
+ bool isReadOnly = false;
+ aInput->GetReadOnly(&isReadOnly);
+ if (isReadOnly)
+ return;
+
+ bool autocomplete = nsContentUtils::IsAutocompleteEnabled(aInput);
+
+ nsCOMPtr<nsIDOMHTMLElement> datalist;
+ aInput->GetList(getter_AddRefs(datalist));
+ bool hasList = datalist != nullptr;
+
+ bool isPwmgrInput = false;
+ if (mPwmgrInputs.Get(inputNode) ||
+ formControl->GetType() == NS_FORM_INPUT_PASSWORD) {
+ isPwmgrInput = true;
+ }
+
+ if (isPwmgrInput || hasList || autocomplete) {
+ StartControllingInput(aInput);
+ }
+}
+
+nsresult
+nsFormFillController::Focus(nsIDOMEvent* aEvent)
+{
+ nsCOMPtr<nsIDOMHTMLInputElement> input = do_QueryInterface(
+ aEvent->InternalDOMEvent()->GetTarget());
+ MaybeStartControllingInput(input);
+
+ // Bail if we didn't start controlling the input.
+ if (!mFocusedInputNode) {
+ mContextMenuFiredBeforeFocus = false;
+ return NS_OK;
+ }
+
+#ifndef ANDROID
+ nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(mFocusedInputNode);
+ MOZ_ASSERT(formControl);
+
+ // If this focus doesn't immediately follow a contextmenu event then show
+ // the autocomplete popup for all password fields.
+ if (!mContextMenuFiredBeforeFocus
+ && formControl->GetType() == NS_FORM_INPUT_PASSWORD) {
+ ShowPopup();
+ }
+#endif
+
+ mContextMenuFiredBeforeFocus = false;
+ return NS_OK;
+}
+
+nsresult
+nsFormFillController::KeyPress(nsIDOMEvent* aEvent)
+{
+ NS_ASSERTION(mController, "should have a controller!");
+ if (!mFocusedInput || !mController)
+ return NS_OK;
+
+ nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
+ if (!keyEvent)
+ return NS_ERROR_FAILURE;
+
+ bool cancel = false;
+ bool unused = false;
+
+ uint32_t k;
+ keyEvent->GetKeyCode(&k);
+ switch (k) {
+ case nsIDOMKeyEvent::DOM_VK_DELETE:
+#ifndef XP_MACOSX
+ mController->HandleDelete(&cancel);
+ break;
+ case nsIDOMKeyEvent::DOM_VK_BACK_SPACE:
+ mController->HandleText(&unused);
+ break;
+#else
+ case nsIDOMKeyEvent::DOM_VK_BACK_SPACE:
+ {
+ bool isShift = false;
+ keyEvent->GetShiftKey(&isShift);
+
+ if (isShift) {
+ mController->HandleDelete(&cancel);
+ } else {
+ mController->HandleText(&unused);
+ }
+
+ break;
+ }
+#endif
+ case nsIDOMKeyEvent::DOM_VK_PAGE_UP:
+ case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN:
+ {
+ bool isCtrl, isAlt, isMeta;
+ keyEvent->GetCtrlKey(&isCtrl);
+ keyEvent->GetAltKey(&isAlt);
+ keyEvent->GetMetaKey(&isMeta);
+ if (isCtrl || isAlt || isMeta)
+ break;
+ }
+ MOZ_FALLTHROUGH;
+ case nsIDOMKeyEvent::DOM_VK_UP:
+ case nsIDOMKeyEvent::DOM_VK_DOWN:
+ case nsIDOMKeyEvent::DOM_VK_LEFT:
+ case nsIDOMKeyEvent::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 (mFocusedInputNode && mFocusedInputNode->IsElement()) {
+ mozilla::dom::Element *elem = mFocusedInputNode->AsElement();
+ nsIFrame *frame = elem->GetPrimaryFrame();
+ if (frame) {
+ wm = frame->GetWritingMode();
+ }
+ }
+ if (wm.IsVertical()) {
+ switch (k) {
+ case nsIDOMKeyEvent::DOM_VK_LEFT:
+ k = wm.IsVerticalLR() ? nsIDOMKeyEvent::DOM_VK_UP
+ : nsIDOMKeyEvent::DOM_VK_DOWN;
+ break;
+ case nsIDOMKeyEvent::DOM_VK_RIGHT:
+ k = wm.IsVerticalLR() ? nsIDOMKeyEvent::DOM_VK_DOWN
+ : nsIDOMKeyEvent::DOM_VK_UP;
+ break;
+ case nsIDOMKeyEvent::DOM_VK_UP:
+ k = nsIDOMKeyEvent::DOM_VK_LEFT;
+ break;
+ case nsIDOMKeyEvent::DOM_VK_DOWN:
+ k = nsIDOMKeyEvent::DOM_VK_RIGHT;
+ break;
+ }
+ }
+ }
+ mController->HandleKeyNavigation(k, &cancel);
+ break;
+ case nsIDOMKeyEvent::DOM_VK_ESCAPE:
+ mController->HandleEscape(&cancel);
+ break;
+ case nsIDOMKeyEvent::DOM_VK_TAB:
+ mController->HandleTab();
+ cancel = false;
+ break;
+ case nsIDOMKeyEvent::DOM_VK_RETURN:
+ mController->HandleEnter(false, aEvent, &cancel);
+ 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 == nsIDOMKeyEvent::DOM_VK_RETURN) {
+ aEvent->StopPropagation();
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsFormFillController::MouseDown(nsIDOMEvent* aEvent)
+{
+ nsCOMPtr<nsIDOMMouseEvent> mouseEvent(do_QueryInterface(aEvent));
+ if (!mouseEvent)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIDOMHTMLInputElement> targetInput = do_QueryInterface(
+ aEvent->InternalDOMEvent()->GetTarget());
+ if (!targetInput)
+ return NS_OK;
+
+ int16_t button;
+ mouseEvent->GetButton(&button);
+ if (button != 0)
+ return NS_OK;
+
+ return ShowPopup();
+}
+
+NS_IMETHODIMP
+nsFormFillController::ShowPopup()
+{
+ bool isOpen = false;
+ GetPopupOpen(&isOpen);
+ if (isOpen) {
+ return SetPopupOpen(false);
+ }
+
+ nsCOMPtr<nsIAutoCompleteInput> input;
+ mController->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
+ mController->SetSearchString(EmptyString());
+ bool unused = false;
+ mController->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;
+ mController->HandleKeyNavigation(nsIDOMKeyEvent::DOM_VK_DOWN, &cancel);
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsFormFillController
+
+void
+nsFormFillController::AddWindowListeners(nsPIDOMWindowOuter* aWindow)
+{
+ if (!aWindow)
+ return;
+
+ EventTarget* target = aWindow->GetChromeEventHandler();
+
+ if (!target)
+ return;
+
+ target->AddEventListener(NS_LITERAL_STRING("focus"), this,
+ true, false);
+ target->AddEventListener(NS_LITERAL_STRING("blur"), this,
+ true, false);
+ target->AddEventListener(NS_LITERAL_STRING("pagehide"), this,
+ true, false);
+ target->AddEventListener(NS_LITERAL_STRING("mousedown"), this,
+ true, false);
+ target->AddEventListener(NS_LITERAL_STRING("input"), this,
+ true, false);
+ target->AddEventListener(NS_LITERAL_STRING("keypress"), this, true, false);
+ target->AddEventListener(NS_LITERAL_STRING("compositionstart"), this,
+ true, false);
+ target->AddEventListener(NS_LITERAL_STRING("compositionend"), this,
+ true, false);
+ target->AddEventListener(NS_LITERAL_STRING("contextmenu"), this,
+ true, false);
+
+ // Note that any additional listeners added should ensure that they ignore
+ // untrusted events, which might be sent by content that's up to no good.
+}
+
+void
+nsFormFillController::RemoveWindowListeners(nsPIDOMWindowOuter* aWindow)
+{
+ if (!aWindow)
+ return;
+
+ StopControllingInput();
+
+ nsCOMPtr<nsIDocument> doc = aWindow->GetDoc();
+ RemoveForDocument(doc);
+
+ EventTarget* target = aWindow->GetChromeEventHandler();
+
+ if (!target)
+ return;
+
+ target->RemoveEventListener(NS_LITERAL_STRING("focus"), this, true);
+ target->RemoveEventListener(NS_LITERAL_STRING("blur"), this, true);
+ target->RemoveEventListener(NS_LITERAL_STRING("pagehide"), this, true);
+ target->RemoveEventListener(NS_LITERAL_STRING("mousedown"), this, true);
+ target->RemoveEventListener(NS_LITERAL_STRING("input"), this, true);
+ target->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true);
+ target->RemoveEventListener(NS_LITERAL_STRING("compositionstart"), this,
+ true);
+ target->RemoveEventListener(NS_LITERAL_STRING("compositionend"), this,
+ true);
+ target->RemoveEventListener(NS_LITERAL_STRING("contextmenu"), this, true);
+}
+
+void
+nsFormFillController::StartControllingInput(nsIDOMHTMLInputElement *aInput)
+{
+ // Make sure we're not still attached to an input
+ StopControllingInput();
+
+ if (!mController) {
+ return;
+ }
+
+ // Find the currently focused docShell
+ nsCOMPtr<nsIDocShell> docShell = GetDocShellForInput(aInput);
+ int32_t index = GetIndexOfDocShell(docShell);
+ if (index < 0)
+ return;
+
+ // Cache the popup for the focused docShell
+ mFocusedPopup = mPopups.SafeElementAt(index);
+
+ nsCOMPtr<nsINode> node = do_QueryInterface(aInput);
+ if (!node) {
+ return;
+ }
+
+ node->AddMutationObserverUnlessExists(this);
+ mFocusedInputNode = node;
+ mFocusedInput = aInput;
+
+ nsCOMPtr<nsIDOMHTMLElement> list;
+ mFocusedInput->GetList(getter_AddRefs(list));
+ nsCOMPtr<nsINode> listNode = do_QueryInterface(list);
+ if (listNode) {
+ listNode->AddMutationObserverUnlessExists(this);
+ mListNode = listNode;
+ }
+
+ mController->SetInput(this);
+}
+
+void
+nsFormFillController::StopControllingInput()
+{
+ if (mListNode) {
+ mListNode->RemoveMutationObserver(this);
+ mListNode = nullptr;
+ }
+
+ if (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;
+ mController->GetInput(getter_AddRefs(input));
+ if (input == this)
+ mController->SetInput(nullptr);
+ }
+
+ if (mFocusedInputNode) {
+ MaybeRemoveMutationObserver(mFocusedInputNode);
+
+ nsresult rv;
+ nsCOMPtr <nsIFormAutoComplete> formAutoComplete =
+ do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv);
+ if (formAutoComplete) {
+ formAutoComplete->StopControllingInput(mFocusedInput);
+ }
+
+ mFocusedInputNode = nullptr;
+ mFocusedInput = nullptr;
+ }
+
+ if (mFocusedPopup) {
+ mFocusedPopup->ClosePopup();
+ }
+ mFocusedPopup = nullptr;
+}
+
+nsIDocShell *
+nsFormFillController::GetDocShellForInput(nsIDOMHTMLInputElement *aInput)
+{
+ nsCOMPtr<nsINode> node = do_QueryInterface(aInput);
+ NS_ENSURE_TRUE(node, nullptr);
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = node->OwnerDoc()->GetWindow();
+ NS_ENSURE_TRUE(win, nullptr);
+
+ return win->GetDocShell();
+}
+
+nsPIDOMWindowOuter*
+nsFormFillController::GetWindowForDocShell(nsIDocShell *aDocShell)
+{
+ nsCOMPtr<nsIContentViewer> contentViewer;
+ aDocShell->GetContentViewer(getter_AddRefs(contentViewer));
+ NS_ENSURE_TRUE(contentViewer, nullptr);
+
+ nsCOMPtr<nsIDOMDocument> domDoc;
+ contentViewer->GetDOMDocument(getter_AddRefs(domDoc));
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
+ NS_ENSURE_TRUE(doc, nullptr);
+
+ return doc->GetWindow();
+}
+
+int32_t
+nsFormFillController::GetIndexOfDocShell(nsIDocShell *aDocShell)
+{
+ if (!aDocShell)
+ return -1;
+
+ // Loop through our cached docShells looking for the given docShell
+ uint32_t count = mDocShells.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ if (mDocShells[i] == aDocShell)
+ return i;
+ }
+
+ // Recursively check the parent docShell of this one
+ nsCOMPtr<nsIDocShellTreeItem> treeItem = do_QueryInterface(aDocShell);
+ nsCOMPtr<nsIDocShellTreeItem> parentItem;
+ treeItem->GetParent(getter_AddRefs(parentItem));
+ if (parentItem) {
+ nsCOMPtr<nsIDocShell> parentShell = do_QueryInterface(parentItem);
+ return GetIndexOfDocShell(parentShell);
+ }
+
+ return -1;
+}
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsFormFillController)
+
+NS_DEFINE_NAMED_CID(NS_FORMFILLCONTROLLER_CID);
+
+static const mozilla::Module::CIDEntry kSatchelCIDs[] = {
+ { &kNS_FORMFILLCONTROLLER_CID, false, nullptr, nsFormFillControllerConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kSatchelContracts[] = {
+ { "@mozilla.org/satchel/form-fill-controller;1", &kNS_FORMFILLCONTROLLER_CID },
+ { NS_FORMHISTORYAUTOCOMPLETE_CONTRACTID, &kNS_FORMFILLCONTROLLER_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kSatchelModule = {
+ mozilla::Module::kVersion,
+ kSatchelCIDs,
+ kSatchelContracts
+};
+
+NSMODULE_DEFN(satchel) = &kSatchelModule;