michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsFormFillController.h" michael@0: michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent() michael@0: #include "nsIFormAutoComplete.h" michael@0: #include "nsIInputListAutoComplete.h" michael@0: #include "nsIAutoCompleteSimpleResult.h" michael@0: #include "nsString.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIInterfaceRequestor.h" michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsIDocShellTreeItem.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsIWebNavigation.h" michael@0: #include "nsIContentViewer.h" michael@0: #include "nsIDOMKeyEvent.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsIFormControl.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsRect.h" michael@0: #include "nsIDOMHTMLFormElement.h" michael@0: #include "nsILoginManager.h" michael@0: #include "nsIDOMMouseEvent.h" michael@0: #include "mozilla/ModuleUtils.h" michael@0: #include "nsToolkitCompsCID.h" michael@0: #include "nsEmbedCID.h" michael@0: #include "nsIDOMNSEditableElement.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsILoadContext.h" michael@0: michael@0: using namespace mozilla::dom; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsFormFillController, michael@0: nsIFormFillController, michael@0: nsIAutoCompleteInput, michael@0: nsIAutoCompleteSearch, michael@0: nsIDOMEventListener, michael@0: nsIFormAutoCompleteObserver, michael@0: nsIMutationObserver) michael@0: michael@0: nsFormFillController::nsFormFillController() : michael@0: mFocusedInput(nullptr), michael@0: mFocusedInputNode(nullptr), michael@0: mListNode(nullptr), michael@0: mTimeout(50), michael@0: mMinResultsForPopup(1), michael@0: mMaxRows(0), michael@0: mDisableAutoComplete(false), michael@0: mCompleteDefaultIndex(false), michael@0: mCompleteSelectedIndex(false), michael@0: mForceComplete(false), michael@0: mSuppressOnInput(false) michael@0: { michael@0: mController = do_GetService("@mozilla.org/autocomplete/controller;1"); michael@0: } michael@0: michael@0: struct PwmgrInputsEnumData michael@0: { michael@0: PwmgrInputsEnumData(nsFormFillController* aFFC, nsIDocument* aDoc) michael@0: : mFFC(aFFC), mDoc(aDoc) {} michael@0: michael@0: nsFormFillController* mFFC; michael@0: nsCOMPtr mDoc; michael@0: }; michael@0: michael@0: nsFormFillController::~nsFormFillController() michael@0: { michael@0: if (mListNode) { michael@0: mListNode->RemoveMutationObserver(this); michael@0: mListNode = nullptr; michael@0: } michael@0: if (mFocusedInputNode) { michael@0: MaybeRemoveMutationObserver(mFocusedInputNode); michael@0: mFocusedInputNode = nullptr; michael@0: mFocusedInput = nullptr; michael@0: } michael@0: PwmgrInputsEnumData ed(this, nullptr); michael@0: mPwmgrInputs.Enumerate(RemoveForDocumentEnumerator, &ed); michael@0: michael@0: // Remove ourselves as a focus listener from all cached docShells michael@0: uint32_t count = mDocShells.Length(); michael@0: for (uint32_t i = 0; i < count; ++i) { michael@0: nsCOMPtr domWindow = GetWindowForDocShell(mDocShells[i]); michael@0: RemoveWindowListeners(domWindow); michael@0: } michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: //// nsIMutationObserver michael@0: // michael@0: michael@0: void michael@0: nsFormFillController::AttributeChanged(nsIDocument* aDocument, michael@0: mozilla::dom::Element* aElement, michael@0: int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, int32_t aModType) michael@0: { michael@0: if (mListNode && mListNode->Contains(aElement)) { michael@0: RevalidateDataList(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsFormFillController::ContentAppended(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t aIndexInContainer) michael@0: { michael@0: if (mListNode && mListNode->Contains(aContainer)) { michael@0: RevalidateDataList(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsFormFillController::ContentInserted(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t aIndexInContainer) michael@0: { michael@0: if (mListNode && mListNode->Contains(aContainer)) { michael@0: RevalidateDataList(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsFormFillController::ContentRemoved(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t aIndexInContainer, michael@0: nsIContent* aPreviousSibling) michael@0: { michael@0: if (mListNode && mListNode->Contains(aContainer)) { michael@0: RevalidateDataList(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsFormFillController::CharacterDataWillChange(nsIDocument* aDocument, michael@0: nsIContent* aContent, michael@0: CharacterDataChangeInfo* aInfo) michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsFormFillController::CharacterDataChanged(nsIDocument* aDocument, michael@0: nsIContent* aContent, michael@0: CharacterDataChangeInfo* aInfo) michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsFormFillController::AttributeWillChange(nsIDocument* aDocument, michael@0: mozilla::dom::Element* aElement, michael@0: int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, int32_t aModType) michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsFormFillController::ParentChainChanged(nsIContent* aContent) michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsFormFillController::NodeWillBeDestroyed(const nsINode* aNode) michael@0: { michael@0: mPwmgrInputs.Remove(aNode); michael@0: if (aNode == mListNode) { michael@0: mListNode = nullptr; michael@0: RevalidateDataList(); michael@0: } else if (aNode == mFocusedInputNode) { michael@0: mFocusedInputNode = nullptr; michael@0: mFocusedInput = nullptr; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsFormFillController::MaybeRemoveMutationObserver(nsINode* aNode) michael@0: { michael@0: // Nodes being tracked in mPwmgrInputs will have their observers removed when michael@0: // they stop being tracked. michael@0: bool dummy; michael@0: if (!mPwmgrInputs.Get(aNode, &dummy)) { michael@0: aNode->RemoveMutationObserver(this); michael@0: } michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: //// nsIFormFillController michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::AttachToBrowser(nsIDocShell *aDocShell, nsIAutoCompletePopup *aPopup) michael@0: { michael@0: NS_ENSURE_TRUE(aDocShell && aPopup, NS_ERROR_ILLEGAL_VALUE); michael@0: michael@0: mDocShells.AppendElement(aDocShell); michael@0: mPopups.AppendElement(aPopup); michael@0: michael@0: // Listen for focus events on the domWindow of the docShell michael@0: nsCOMPtr domWindow = GetWindowForDocShell(aDocShell); michael@0: AddWindowListeners(domWindow); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::DetachFromBrowser(nsIDocShell *aDocShell) michael@0: { michael@0: int32_t index = GetIndexOfDocShell(aDocShell); michael@0: NS_ENSURE_TRUE(index >= 0, NS_ERROR_FAILURE); michael@0: michael@0: // Stop listening for focus events on the domWindow of the docShell michael@0: nsCOMPtr domWindow = michael@0: GetWindowForDocShell(mDocShells.SafeElementAt(index)); michael@0: RemoveWindowListeners(domWindow); michael@0: michael@0: mDocShells.RemoveElementAt(index); michael@0: mPopups.RemoveElementAt(index); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::MarkAsLoginManagerField(nsIDOMHTMLInputElement *aInput) michael@0: { michael@0: /* michael@0: * The Login Manager can supply autocomplete results for username fields, michael@0: * when a user has multiple logins stored for a site. It uses this michael@0: * interface to indicate that the form manager shouldn't handle the michael@0: * autocomplete. The form manager also checks for this tag when saving michael@0: * form history (so it doesn't save usernames). michael@0: */ michael@0: nsCOMPtr node = do_QueryInterface(aInput); michael@0: NS_ENSURE_STATE(node); michael@0: mPwmgrInputs.Put(node, true); michael@0: node->AddMutationObserverUnlessExists(this); michael@0: michael@0: if (!mLoginManager) michael@0: mLoginManager = do_GetService("@mozilla.org/login-manager;1"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: //// nsIAutoCompleteInput michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::GetPopup(nsIAutoCompletePopup **aPopup) michael@0: { michael@0: *aPopup = mFocusedPopup; michael@0: NS_IF_ADDREF(*aPopup); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::GetController(nsIAutoCompleteController **aController) michael@0: { michael@0: *aController = mController; michael@0: NS_IF_ADDREF(*aController); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::GetPopupOpen(bool *aPopupOpen) michael@0: { michael@0: if (mFocusedPopup) michael@0: mFocusedPopup->GetPopupOpen(aPopupOpen); michael@0: else michael@0: *aPopupOpen = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::SetPopupOpen(bool aPopupOpen) michael@0: { michael@0: if (mFocusedPopup) { michael@0: if (aPopupOpen) { michael@0: // make sure input field is visible before showing popup (bug 320938) michael@0: nsCOMPtr content = do_QueryInterface(mFocusedInput); michael@0: NS_ENSURE_STATE(content); michael@0: nsCOMPtr docShell = GetDocShellForInput(mFocusedInput); michael@0: NS_ENSURE_STATE(docShell); michael@0: nsCOMPtr presShell = docShell->GetPresShell(); michael@0: NS_ENSURE_STATE(presShell); michael@0: presShell->ScrollContentIntoView(content, michael@0: nsIPresShell::ScrollAxis( michael@0: nsIPresShell::SCROLL_MINIMUM, michael@0: nsIPresShell::SCROLL_IF_NOT_VISIBLE), michael@0: nsIPresShell::ScrollAxis( michael@0: nsIPresShell::SCROLL_MINIMUM, michael@0: nsIPresShell::SCROLL_IF_NOT_VISIBLE), michael@0: nsIPresShell::SCROLL_OVERFLOW_HIDDEN); michael@0: // mFocusedPopup can be destroyed after ScrollContentIntoView, see bug 420089 michael@0: if (mFocusedPopup) { michael@0: nsCOMPtr element = do_QueryInterface(mFocusedInput); michael@0: mFocusedPopup->OpenAutocompletePopup(this, element); michael@0: } michael@0: } else michael@0: mFocusedPopup->ClosePopup(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::GetDisableAutoComplete(bool *aDisableAutoComplete) michael@0: { michael@0: *aDisableAutoComplete = mDisableAutoComplete; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::SetDisableAutoComplete(bool aDisableAutoComplete) michael@0: { michael@0: mDisableAutoComplete = aDisableAutoComplete; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::GetCompleteDefaultIndex(bool *aCompleteDefaultIndex) michael@0: { michael@0: *aCompleteDefaultIndex = mCompleteDefaultIndex; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::SetCompleteDefaultIndex(bool aCompleteDefaultIndex) michael@0: { michael@0: mCompleteDefaultIndex = aCompleteDefaultIndex; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::GetCompleteSelectedIndex(bool *aCompleteSelectedIndex) michael@0: { michael@0: *aCompleteSelectedIndex = mCompleteSelectedIndex; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::SetCompleteSelectedIndex(bool aCompleteSelectedIndex) michael@0: { michael@0: mCompleteSelectedIndex = aCompleteSelectedIndex; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::GetForceComplete(bool *aForceComplete) michael@0: { michael@0: *aForceComplete = mForceComplete; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsFormFillController::SetForceComplete(bool aForceComplete) michael@0: { michael@0: mForceComplete = aForceComplete; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::GetMinResultsForPopup(uint32_t *aMinResultsForPopup) michael@0: { michael@0: *aMinResultsForPopup = mMinResultsForPopup; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsFormFillController::SetMinResultsForPopup(uint32_t aMinResultsForPopup) michael@0: { michael@0: mMinResultsForPopup = aMinResultsForPopup; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::GetMaxRows(uint32_t *aMaxRows) michael@0: { michael@0: *aMaxRows = mMaxRows; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::SetMaxRows(uint32_t aMaxRows) michael@0: { michael@0: mMaxRows = aMaxRows; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::GetShowImageColumn(bool *aShowImageColumn) michael@0: { michael@0: *aShowImageColumn = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsFormFillController::SetShowImageColumn(bool aShowImageColumn) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::GetShowCommentColumn(bool *aShowCommentColumn) michael@0: { michael@0: *aShowCommentColumn = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsFormFillController::SetShowCommentColumn(bool aShowCommentColumn) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::GetTimeout(uint32_t *aTimeout) michael@0: { michael@0: *aTimeout = mTimeout; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsFormFillController::SetTimeout(uint32_t aTimeout) michael@0: { michael@0: mTimeout = aTimeout; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::SetSearchParam(const nsAString &aSearchParam) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::GetSearchParam(nsAString &aSearchParam) michael@0: { michael@0: if (!mFocusedInput) { michael@0: NS_WARNING("mFocusedInput is null for some reason! avoiding a crash. should find out why... - ben"); michael@0: return NS_ERROR_FAILURE; // XXX why? fix me. michael@0: } michael@0: michael@0: mFocusedInput->GetName(aSearchParam); michael@0: if (aSearchParam.IsEmpty()) { michael@0: nsCOMPtr element = do_QueryInterface(mFocusedInput); michael@0: element->GetId(aSearchParam); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::GetSearchCount(uint32_t *aSearchCount) michael@0: { michael@0: *aSearchCount = 1; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::GetSearchAt(uint32_t index, nsACString & _retval) michael@0: { michael@0: _retval.Assign("form-history"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::GetTextValue(nsAString & aTextValue) michael@0: { michael@0: if (mFocusedInput) { michael@0: mFocusedInput->GetValue(aTextValue); michael@0: } else { michael@0: aTextValue.Truncate(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::SetTextValue(const nsAString & aTextValue) michael@0: { michael@0: nsCOMPtr editable = do_QueryInterface(mFocusedInput); michael@0: if (editable) { michael@0: mSuppressOnInput = true; michael@0: editable->SetUserInput(aTextValue); michael@0: mSuppressOnInput = false; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::GetSelectionStart(int32_t *aSelectionStart) michael@0: { michael@0: if (mFocusedInput) michael@0: mFocusedInput->GetSelectionStart(aSelectionStart); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::GetSelectionEnd(int32_t *aSelectionEnd) michael@0: { michael@0: if (mFocusedInput) michael@0: mFocusedInput->GetSelectionEnd(aSelectionEnd); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::SelectTextRange(int32_t aStartIndex, int32_t aEndIndex) michael@0: { michael@0: if (mFocusedInput) michael@0: mFocusedInput->SetSelectionRange(aStartIndex, aEndIndex, EmptyString()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::OnSearchBegin() michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::OnSearchComplete() michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::OnTextEntered(bool* aPrevent) michael@0: { michael@0: NS_ENSURE_ARG(aPrevent); michael@0: NS_ENSURE_TRUE(mFocusedInput, NS_OK); michael@0: // Fire off a DOMAutoComplete event michael@0: nsCOMPtr domDoc; michael@0: nsCOMPtr element = do_QueryInterface(mFocusedInput); michael@0: element->GetOwnerDocument(getter_AddRefs(domDoc)); michael@0: NS_ENSURE_STATE(domDoc); michael@0: michael@0: nsCOMPtr event; michael@0: domDoc->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event)); michael@0: NS_ENSURE_STATE(event); michael@0: michael@0: event->InitEvent(NS_LITERAL_STRING("DOMAutoComplete"), true, true); michael@0: michael@0: // XXXjst: We mark this event as a trusted event, it's up to the michael@0: // callers of this to ensure that it's only called from trusted michael@0: // code. michael@0: event->SetTrusted(true); michael@0: michael@0: nsCOMPtr targ = do_QueryInterface(mFocusedInput); michael@0: michael@0: bool defaultActionEnabled; michael@0: targ->DispatchEvent(event, &defaultActionEnabled); michael@0: *aPrevent = !defaultActionEnabled; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::OnTextReverted(bool *_retval) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::GetConsumeRollupEvent(bool *aConsumeRollupEvent) michael@0: { michael@0: *aConsumeRollupEvent = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::GetInPrivateContext(bool *aInPrivateContext) michael@0: { michael@0: if (!mFocusedInput) { michael@0: *aInPrivateContext = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr inputDoc; michael@0: nsCOMPtr element = do_QueryInterface(mFocusedInput); michael@0: element->GetOwnerDocument(getter_AddRefs(inputDoc)); michael@0: nsCOMPtr doc = do_QueryInterface(inputDoc); michael@0: nsCOMPtr docShell = doc->GetDocShell(); michael@0: nsCOMPtr loadContext = doc->GetLoadContext(); michael@0: *aInPrivateContext = loadContext && loadContext->UsePrivateBrowsing(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: //// nsIAutoCompleteSearch michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::StartSearch(const nsAString &aSearchString, const nsAString &aSearchParam, michael@0: nsIAutoCompleteResult *aPreviousResult, nsIAutoCompleteObserver *aListener) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr result; michael@0: michael@0: // If the login manager has indicated it's responsible for this field, let it michael@0: // handle the autocomplete. Otherwise, handle with form history. michael@0: bool dummy; michael@0: if (mPwmgrInputs.Get(mFocusedInputNode, &dummy)) { michael@0: // XXX aPreviousResult shouldn't ever be a historyResult type, since we're not letting michael@0: // satchel manage the field? michael@0: rv = mLoginManager->AutoCompleteSearch(aSearchString, michael@0: aPreviousResult, michael@0: mFocusedInput, michael@0: getter_AddRefs(result)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (aListener) { michael@0: aListener->OnSearchResult(this, result); michael@0: } michael@0: } else { michael@0: mLastListener = aListener; michael@0: michael@0: // It appears that mFocusedInput is always null when we are focusing a XUL michael@0: // element. Scary :) michael@0: if (!mFocusedInput || nsContentUtils::IsAutocompleteEnabled(mFocusedInput)) { michael@0: nsCOMPtr formAutoComplete = michael@0: do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: formAutoComplete->AutoCompleteSearchAsync(aSearchParam, michael@0: aSearchString, michael@0: mFocusedInput, michael@0: aPreviousResult, michael@0: this); michael@0: mLastFormAutoComplete = formAutoComplete; michael@0: } else { michael@0: mLastSearchString = aSearchString; michael@0: michael@0: // Even if autocomplete is disabled, handle the inputlist anyway as that was michael@0: // specifically requested by the page. This is so a field can have the default michael@0: // autocomplete disabled and replaced with a custom inputlist autocomplete. michael@0: return PerformInputListAutoComplete(aPreviousResult); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsFormFillController::PerformInputListAutoComplete(nsIAutoCompleteResult* aPreviousResult) michael@0: { michael@0: // If an is focused, check if it has a list="" which can michael@0: // provide the list of suggestions. michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr result; michael@0: michael@0: nsCOMPtr inputListAutoComplete = michael@0: do_GetService("@mozilla.org/satchel/inputlist-autocomplete;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = inputListAutoComplete->AutoCompleteSearch(aPreviousResult, michael@0: mLastSearchString, michael@0: mFocusedInput, michael@0: getter_AddRefs(result)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (mFocusedInput) { michael@0: nsCOMPtr list; michael@0: mFocusedInput->GetList(getter_AddRefs(list)); michael@0: michael@0: // Add a mutation observer to check for changes to the items in the michael@0: // and update the suggestions accordingly. michael@0: nsCOMPtr node = do_QueryInterface(list); michael@0: if (mListNode != node) { michael@0: if (mListNode) { michael@0: mListNode->RemoveMutationObserver(this); michael@0: mListNode = nullptr; michael@0: } michael@0: if (node) { michael@0: node->AddMutationObserverUnlessExists(this); michael@0: mListNode = node; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (mLastListener) { michael@0: mLastListener->OnSearchResult(this, result); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: class UpdateSearchResultRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: UpdateSearchResultRunnable(nsIAutoCompleteObserver* aObserver, michael@0: nsIAutoCompleteSearch* aSearch, michael@0: nsIAutoCompleteResult* aResult) michael@0: : mObserver(aObserver) michael@0: , mSearch(aSearch) michael@0: , mResult(aResult) michael@0: {} michael@0: michael@0: NS_IMETHOD Run() { michael@0: NS_ASSERTION(mObserver, "You shouldn't call this runnable with a null observer!"); michael@0: michael@0: mObserver->OnUpdateSearchResult(mSearch, mResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsCOMPtr mObserver; michael@0: nsCOMPtr mSearch; michael@0: nsCOMPtr mResult; michael@0: }; michael@0: michael@0: void nsFormFillController::RevalidateDataList() michael@0: { michael@0: if (!mLastListener) { michael@0: return; michael@0: } michael@0: nsresult rv; michael@0: nsCOMPtr inputListAutoComplete = michael@0: do_GetService("@mozilla.org/satchel/inputlist-autocomplete;1", &rv); michael@0: michael@0: nsCOMPtr result; michael@0: michael@0: rv = inputListAutoComplete->AutoCompleteSearch(mLastSearchResult, michael@0: mLastSearchString, michael@0: mFocusedInput, michael@0: getter_AddRefs(result)); michael@0: michael@0: nsCOMPtr event = michael@0: new UpdateSearchResultRunnable(mLastListener, this, result); michael@0: NS_DispatchToCurrentThread(event); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::StopSearch() michael@0: { michael@0: // Make sure to stop and clear this, otherwise the controller will prevent michael@0: // mLastFormAutoComplete from being deleted. michael@0: if (mLastFormAutoComplete) { michael@0: mLastFormAutoComplete->StopAutoCompleteSearch(); michael@0: mLastFormAutoComplete = nullptr; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: //// nsIFormAutoCompleteObserver michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::OnSearchCompletion(nsIAutoCompleteResult *aResult) michael@0: { michael@0: nsCOMPtr resultParam = do_QueryInterface(aResult); michael@0: michael@0: nsAutoString searchString; michael@0: resultParam->GetSearchString(searchString); michael@0: mLastSearchResult = aResult; michael@0: mLastSearchString = searchString; michael@0: michael@0: return PerformInputListAutoComplete(resultParam); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: //// nsIDOMEventListener michael@0: michael@0: NS_IMETHODIMP michael@0: nsFormFillController::HandleEvent(nsIDOMEvent* aEvent) michael@0: { michael@0: nsAutoString type; michael@0: aEvent->GetType(type); michael@0: michael@0: if (type.EqualsLiteral("focus")) { michael@0: return Focus(aEvent); michael@0: } michael@0: if (type.EqualsLiteral("mousedown")) { michael@0: return MouseDown(aEvent); michael@0: } michael@0: if (type.EqualsLiteral("keypress")) { michael@0: return KeyPress(aEvent); michael@0: } michael@0: if (type.EqualsLiteral("input")) { michael@0: return (!mSuppressOnInput && mController && mFocusedInput) ? michael@0: mController->HandleText() : NS_OK; michael@0: } michael@0: if (type.EqualsLiteral("blur")) { michael@0: if (mFocusedInput) michael@0: StopControllingInput(); michael@0: return NS_OK; michael@0: } michael@0: if (type.EqualsLiteral("compositionstart")) { michael@0: NS_ASSERTION(mController, "should have a controller!"); michael@0: if (mController && mFocusedInput) michael@0: mController->HandleStartComposition(); michael@0: return NS_OK; michael@0: } michael@0: if (type.EqualsLiteral("compositionend")) { michael@0: NS_ASSERTION(mController, "should have a controller!"); michael@0: if (mController && mFocusedInput) michael@0: mController->HandleEndComposition(); michael@0: return NS_OK; michael@0: } michael@0: if (type.EqualsLiteral("contextmenu")) { michael@0: if (mFocusedPopup) michael@0: mFocusedPopup->ClosePopup(); michael@0: return NS_OK; michael@0: } michael@0: if (type.EqualsLiteral("pagehide")) { michael@0: michael@0: nsCOMPtr doc = do_QueryInterface( michael@0: aEvent->InternalDOMEvent()->GetTarget()); michael@0: if (!doc) michael@0: return NS_OK; michael@0: michael@0: if (mFocusedInput) { michael@0: if (doc == mFocusedInputNode->OwnerDoc()) michael@0: StopControllingInput(); michael@0: } michael@0: michael@0: PwmgrInputsEnumData ed(this, doc); michael@0: mPwmgrInputs.Enumerate(RemoveForDocumentEnumerator, &ed); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /* static */ PLDHashOperator michael@0: nsFormFillController::RemoveForDocumentEnumerator(const nsINode* aKey, michael@0: bool& aEntry, michael@0: void* aUserData) michael@0: { michael@0: PwmgrInputsEnumData* ed = static_cast(aUserData); michael@0: if (aKey && (!ed->mDoc || aKey->OwnerDoc() == ed->mDoc)) { michael@0: // mFocusedInputNode's observer is tracked separately, don't remove it here. michael@0: if (aKey != ed->mFFC->mFocusedInputNode) { michael@0: const_cast(aKey)->RemoveMutationObserver(ed->mFFC); michael@0: } michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: nsresult michael@0: nsFormFillController::Focus(nsIDOMEvent* aEvent) michael@0: { michael@0: nsCOMPtr input = do_QueryInterface( michael@0: aEvent->InternalDOMEvent()->GetTarget()); michael@0: nsCOMPtr inputNode = do_QueryInterface(input); michael@0: if (!inputNode) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr formControl = do_QueryInterface(input); michael@0: if (!formControl || !formControl->IsSingleLineTextControl(true)) michael@0: return NS_OK; michael@0: michael@0: bool isReadOnly = false; michael@0: input->GetReadOnly(&isReadOnly); michael@0: if (isReadOnly) michael@0: return NS_OK; michael@0: michael@0: bool autocomplete = nsContentUtils::IsAutocompleteEnabled(input); michael@0: michael@0: nsCOMPtr datalist; michael@0: input->GetList(getter_AddRefs(datalist)); michael@0: bool hasList = datalist != nullptr; michael@0: michael@0: bool dummy; michael@0: bool isPwmgrInput = false; michael@0: if (mPwmgrInputs.Get(inputNode, &dummy)) michael@0: isPwmgrInput = true; michael@0: michael@0: if (isPwmgrInput || hasList || autocomplete) { michael@0: StartControllingInput(input); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsFormFillController::KeyPress(nsIDOMEvent* aEvent) michael@0: { michael@0: NS_ASSERTION(mController, "should have a controller!"); michael@0: if (!mFocusedInput || !mController) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr keyEvent = do_QueryInterface(aEvent); michael@0: if (!keyEvent) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: bool cancel = false; michael@0: michael@0: uint32_t k; michael@0: keyEvent->GetKeyCode(&k); michael@0: switch (k) { michael@0: case nsIDOMKeyEvent::DOM_VK_DELETE: michael@0: #ifndef XP_MACOSX michael@0: mController->HandleDelete(&cancel); michael@0: break; michael@0: case nsIDOMKeyEvent::DOM_VK_BACK_SPACE: michael@0: mController->HandleText(); michael@0: break; michael@0: #else michael@0: case nsIDOMKeyEvent::DOM_VK_BACK_SPACE: michael@0: { michael@0: bool isShift = false; michael@0: keyEvent->GetShiftKey(&isShift); michael@0: michael@0: if (isShift) michael@0: mController->HandleDelete(&cancel); michael@0: else michael@0: mController->HandleText(); michael@0: michael@0: break; michael@0: } michael@0: #endif michael@0: case nsIDOMKeyEvent::DOM_VK_PAGE_UP: michael@0: case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN: michael@0: { michael@0: bool isCtrl, isAlt, isMeta; michael@0: keyEvent->GetCtrlKey(&isCtrl); michael@0: keyEvent->GetAltKey(&isAlt); michael@0: keyEvent->GetMetaKey(&isMeta); michael@0: if (isCtrl || isAlt || isMeta) michael@0: break; michael@0: } michael@0: /* fall through */ michael@0: case nsIDOMKeyEvent::DOM_VK_UP: michael@0: case nsIDOMKeyEvent::DOM_VK_DOWN: michael@0: case nsIDOMKeyEvent::DOM_VK_LEFT: michael@0: case nsIDOMKeyEvent::DOM_VK_RIGHT: michael@0: mController->HandleKeyNavigation(k, &cancel); michael@0: break; michael@0: case nsIDOMKeyEvent::DOM_VK_ESCAPE: michael@0: mController->HandleEscape(&cancel); michael@0: break; michael@0: case nsIDOMKeyEvent::DOM_VK_TAB: michael@0: mController->HandleTab(); michael@0: cancel = false; michael@0: break; michael@0: case nsIDOMKeyEvent::DOM_VK_RETURN: michael@0: mController->HandleEnter(false, &cancel); michael@0: break; michael@0: } michael@0: michael@0: if (cancel) { michael@0: aEvent->PreventDefault(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsFormFillController::MouseDown(nsIDOMEvent* aEvent) michael@0: { michael@0: nsCOMPtr mouseEvent(do_QueryInterface(aEvent)); michael@0: if (!mouseEvent) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr targetInput = do_QueryInterface( michael@0: aEvent->InternalDOMEvent()->GetTarget()); michael@0: if (!targetInput) michael@0: return NS_OK; michael@0: michael@0: int16_t button; michael@0: mouseEvent->GetButton(&button); michael@0: if (button != 0) michael@0: return NS_OK; michael@0: michael@0: bool isOpen = false; michael@0: GetPopupOpen(&isOpen); michael@0: if (isOpen) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr input; michael@0: mController->GetInput(getter_AddRefs(input)); michael@0: if (!input) michael@0: return NS_OK; michael@0: michael@0: nsAutoString value; michael@0: input->GetTextValue(value); michael@0: if (value.Length() > 0) { michael@0: // Show the popup with a filtered result set michael@0: mController->SetSearchString(EmptyString()); michael@0: mController->HandleText(); michael@0: } else { michael@0: // Show the popup with the complete result set. Can't use HandleText() michael@0: // because it doesn't display the popup if the input is blank. michael@0: bool cancel = false; michael@0: mController->HandleKeyNavigation(nsIDOMKeyEvent::DOM_VK_DOWN, &cancel); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: //// nsFormFillController michael@0: michael@0: void michael@0: nsFormFillController::AddWindowListeners(nsIDOMWindow *aWindow) michael@0: { michael@0: if (!aWindow) michael@0: return; michael@0: michael@0: nsCOMPtr privateDOMWindow(do_QueryInterface(aWindow)); michael@0: EventTarget* target = nullptr; michael@0: if (privateDOMWindow) michael@0: target = privateDOMWindow->GetChromeEventHandler(); michael@0: michael@0: if (!target) michael@0: return; michael@0: michael@0: target->AddEventListener(NS_LITERAL_STRING("focus"), this, michael@0: true, false); michael@0: target->AddEventListener(NS_LITERAL_STRING("blur"), this, michael@0: true, false); michael@0: target->AddEventListener(NS_LITERAL_STRING("pagehide"), this, michael@0: true, false); michael@0: target->AddEventListener(NS_LITERAL_STRING("mousedown"), this, michael@0: true, false); michael@0: target->AddEventListener(NS_LITERAL_STRING("input"), this, michael@0: true, false); michael@0: target->AddEventListener(NS_LITERAL_STRING("compositionstart"), this, michael@0: true, false); michael@0: target->AddEventListener(NS_LITERAL_STRING("compositionend"), this, michael@0: true, false); michael@0: target->AddEventListener(NS_LITERAL_STRING("contextmenu"), this, michael@0: true, false); michael@0: michael@0: // Note that any additional listeners added should ensure that they ignore michael@0: // untrusted events, which might be sent by content that's up to no good. michael@0: } michael@0: michael@0: void michael@0: nsFormFillController::RemoveWindowListeners(nsIDOMWindow *aWindow) michael@0: { michael@0: if (!aWindow) michael@0: return; michael@0: michael@0: StopControllingInput(); michael@0: michael@0: nsCOMPtr domDoc; michael@0: aWindow->GetDocument(getter_AddRefs(domDoc)); michael@0: nsCOMPtr doc = do_QueryInterface(domDoc); michael@0: PwmgrInputsEnumData ed(this, doc); michael@0: mPwmgrInputs.Enumerate(RemoveForDocumentEnumerator, &ed); michael@0: michael@0: nsCOMPtr privateDOMWindow(do_QueryInterface(aWindow)); michael@0: EventTarget* target = nullptr; michael@0: if (privateDOMWindow) michael@0: target = privateDOMWindow->GetChromeEventHandler(); michael@0: michael@0: if (!target) michael@0: return; michael@0: michael@0: target->RemoveEventListener(NS_LITERAL_STRING("focus"), this, true); michael@0: target->RemoveEventListener(NS_LITERAL_STRING("blur"), this, true); michael@0: target->RemoveEventListener(NS_LITERAL_STRING("pagehide"), this, true); michael@0: target->RemoveEventListener(NS_LITERAL_STRING("mousedown"), this, true); michael@0: target->RemoveEventListener(NS_LITERAL_STRING("input"), this, true); michael@0: target->RemoveEventListener(NS_LITERAL_STRING("compositionstart"), this, michael@0: true); michael@0: target->RemoveEventListener(NS_LITERAL_STRING("compositionend"), this, michael@0: true); michael@0: target->RemoveEventListener(NS_LITERAL_STRING("contextmenu"), this, true); michael@0: } michael@0: michael@0: void michael@0: nsFormFillController::AddKeyListener(nsINode* aInput) michael@0: { michael@0: aInput->AddEventListener(NS_LITERAL_STRING("keypress"), this, michael@0: true, false); michael@0: } michael@0: michael@0: void michael@0: nsFormFillController::RemoveKeyListener() michael@0: { michael@0: if (!mFocusedInputNode) michael@0: return; michael@0: michael@0: mFocusedInputNode->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true); michael@0: } michael@0: michael@0: void michael@0: nsFormFillController::StartControllingInput(nsIDOMHTMLInputElement *aInput) michael@0: { michael@0: // Make sure we're not still attached to an input michael@0: StopControllingInput(); michael@0: michael@0: // Find the currently focused docShell michael@0: nsCOMPtr docShell = GetDocShellForInput(aInput); michael@0: int32_t index = GetIndexOfDocShell(docShell); michael@0: if (index < 0) michael@0: return; michael@0: michael@0: // Cache the popup for the focused docShell michael@0: mFocusedPopup = mPopups.SafeElementAt(index); michael@0: michael@0: nsCOMPtr node = do_QueryInterface(aInput); michael@0: if (!node) { michael@0: return; michael@0: } michael@0: michael@0: AddKeyListener(node); michael@0: michael@0: node->AddMutationObserverUnlessExists(this); michael@0: mFocusedInputNode = node; michael@0: mFocusedInput = aInput; michael@0: michael@0: nsCOMPtr list; michael@0: mFocusedInput->GetList(getter_AddRefs(list)); michael@0: nsCOMPtr listNode = do_QueryInterface(list); michael@0: if (listNode) { michael@0: listNode->AddMutationObserverUnlessExists(this); michael@0: mListNode = listNode; michael@0: } michael@0: michael@0: // Now we are the autocomplete controller's bitch michael@0: mController->SetInput(this); michael@0: } michael@0: michael@0: void michael@0: nsFormFillController::StopControllingInput() michael@0: { michael@0: RemoveKeyListener(); michael@0: michael@0: if (mListNode) { michael@0: mListNode->RemoveMutationObserver(this); michael@0: mListNode = nullptr; michael@0: } michael@0: michael@0: // Reset the controller's input, but not if it has been switched michael@0: // to another input already, which might happen if the user switches michael@0: // focus by clicking another autocomplete textbox michael@0: nsCOMPtr input; michael@0: mController->GetInput(getter_AddRefs(input)); michael@0: if (input == this) michael@0: mController->SetInput(nullptr); michael@0: michael@0: if (mFocusedInputNode) { michael@0: MaybeRemoveMutationObserver(mFocusedInputNode); michael@0: mFocusedInputNode = nullptr; michael@0: mFocusedInput = nullptr; michael@0: } michael@0: mFocusedPopup = nullptr; michael@0: } michael@0: michael@0: nsIDocShell * michael@0: nsFormFillController::GetDocShellForInput(nsIDOMHTMLInputElement *aInput) michael@0: { michael@0: nsCOMPtr domDoc; michael@0: nsCOMPtr element = do_QueryInterface(aInput); michael@0: element->GetOwnerDocument(getter_AddRefs(domDoc)); michael@0: nsCOMPtr doc = do_QueryInterface(domDoc); michael@0: NS_ENSURE_TRUE(doc, nullptr); michael@0: nsCOMPtr webNav = do_GetInterface(doc->GetWindow()); michael@0: nsCOMPtr docShell = do_QueryInterface(webNav); michael@0: return docShell; michael@0: } michael@0: michael@0: nsIDOMWindow * michael@0: nsFormFillController::GetWindowForDocShell(nsIDocShell *aDocShell) michael@0: { michael@0: nsCOMPtr contentViewer; michael@0: aDocShell->GetContentViewer(getter_AddRefs(contentViewer)); michael@0: NS_ENSURE_TRUE(contentViewer, nullptr); michael@0: michael@0: nsCOMPtr domDoc; michael@0: contentViewer->GetDOMDocument(getter_AddRefs(domDoc)); michael@0: nsCOMPtr doc = do_QueryInterface(domDoc); michael@0: NS_ENSURE_TRUE(doc, nullptr); michael@0: michael@0: return doc->GetWindow(); michael@0: } michael@0: michael@0: int32_t michael@0: nsFormFillController::GetIndexOfDocShell(nsIDocShell *aDocShell) michael@0: { michael@0: if (!aDocShell) michael@0: return -1; michael@0: michael@0: // Loop through our cached docShells looking for the given docShell michael@0: uint32_t count = mDocShells.Length(); michael@0: for (uint32_t i = 0; i < count; ++i) { michael@0: if (mDocShells[i] == aDocShell) michael@0: return i; michael@0: } michael@0: michael@0: // Recursively check the parent docShell of this one michael@0: nsCOMPtr treeItem = do_QueryInterface(aDocShell); michael@0: nsCOMPtr parentItem; michael@0: treeItem->GetParent(getter_AddRefs(parentItem)); michael@0: if (parentItem) { michael@0: nsCOMPtr parentShell = do_QueryInterface(parentItem); michael@0: return GetIndexOfDocShell(parentShell); michael@0: } michael@0: michael@0: return -1; michael@0: } michael@0: michael@0: NS_GENERIC_FACTORY_CONSTRUCTOR(nsFormFillController) michael@0: michael@0: NS_DEFINE_NAMED_CID(NS_FORMFILLCONTROLLER_CID); michael@0: michael@0: static const mozilla::Module::CIDEntry kSatchelCIDs[] = { michael@0: { &kNS_FORMFILLCONTROLLER_CID, false, nullptr, nsFormFillControllerConstructor }, michael@0: { nullptr } michael@0: }; michael@0: michael@0: static const mozilla::Module::ContractIDEntry kSatchelContracts[] = { michael@0: { "@mozilla.org/satchel/form-fill-controller;1", &kNS_FORMFILLCONTROLLER_CID }, michael@0: { NS_FORMHISTORYAUTOCOMPLETE_CONTRACTID, &kNS_FORMFILLCONTROLLER_CID }, michael@0: { nullptr } michael@0: }; michael@0: michael@0: static const mozilla::Module kSatchelModule = { michael@0: mozilla::Module::kVersion, michael@0: kSatchelCIDs, michael@0: kSatchelContracts michael@0: }; michael@0: michael@0: NSMODULE_DEFN(satchel) = &kSatchelModule;