forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1478 lines
		
	
	
	
		
			41 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1478 lines
		
	
	
	
		
			41 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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/dom/Element.h"
 | |
| #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
 | |
| #include "mozilla/dom/HTMLInputElement.h"
 | |
| #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 "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;
 | |
| using namespace mozilla::dom;
 | |
| using mozilla::ErrorResult;
 | |
| 
 | |
| 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, 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),
 | |
|   // 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/LoginManagerContent.jsm.
 | |
|   mFocusAfterRightClickThreshold(400),
 | |
|   mTimeout(50),
 | |
|   mMinResultsForPopup(1),
 | |
|   mMaxRows(0),
 | |
|   mLastRightClickTimeStamp(TimeStamp()),
 | |
|   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);
 | |
|   mAutofillInputs.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) && !mAutofillInputs.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::MarkAsAutofillField(nsIDOMHTMLInputElement *aInput)
 | |
| {
 | |
|   /*
 | |
|    * Support other components implementing form autofill and handle autocomplete
 | |
|    * for the field.
 | |
|    */
 | |
|   nsCOMPtr<nsINode> node = do_QueryInterface(aInput);
 | |
|   NS_ENSURE_STATE(node);
 | |
| 
 | |
|   if (mAutofillInputs.Get(node)) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   mAutofillInputs.Put(node, true);
 | |
|   node->AddMutationObserverUnlessExists(this);
 | |
| 
 | |
|   nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(aInput);
 | |
|   txtCtrl->EnablePreview();
 | |
| 
 | |
|   nsFocusManager *fm = nsFocusManager::GetFocusManager();
 | |
|   if (fm) {
 | |
|     nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedContent();
 | |
|     if (SameCOMIdentity(focusedContent, node)) {
 | |
|       nsCOMPtr<nsIDOMHTMLInputElement> input = do_QueryInterface(node);
 | |
|       MaybeStartControllingInput(input);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   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<Element> 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)
 | |
| {
 | |
|   if (mAutofillInputs.Get(mFocusedInputNode)) {
 | |
|     nsCOMPtr<nsIAutoCompleteSearch> profileSearch = do_GetService("@mozilla.org/autocomplete/search;1?name=autofill-profiles");
 | |
|     if (profileSearch) {
 | |
|       _retval.AssignLiteral("autofill-profiles");
 | |
|       return NS_OK;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   _retval.AssignLiteral("form-history");
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsFormFillController::GetTextValue(nsAString & aTextValue)
 | |
| {
 | |
|   if (mFocusedInput) {
 | |
|     nsCOMPtr<nsIContent> content = do_QueryInterface(mFocusedInput);
 | |
|     HTMLInputElement::FromContent(content)->GetValue(aTextValue,
 | |
|                                                      CallerType::System);
 | |
|   } 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)
 | |
| {
 | |
|   nsCOMPtr<nsIContent> content = do_QueryInterface(mFocusedInput);
 | |
|   if (!content) {
 | |
|     return NS_ERROR_UNEXPECTED;
 | |
|   }
 | |
|   ErrorResult rv;
 | |
|   *aSelectionStart =
 | |
|     HTMLInputElement::FromContent(content)->GetSelectionStartIgnoringType(rv);
 | |
|   return rv.StealNSResult();
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsFormFillController::GetSelectionEnd(int32_t *aSelectionEnd)
 | |
| {
 | |
|   nsCOMPtr<nsIContent> content = do_QueryInterface(mFocusedInput);
 | |
|   if (!content) {
 | |
|     return NS_ERROR_UNEXPECTED;
 | |
|   }
 | |
|   ErrorResult rv;
 | |
|   *aSelectionEnd =
 | |
|     HTMLInputElement::FromContent(content)->GetSelectionEndIgnoringType(rv);
 | |
|   return rv.StealNSResult();
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsFormFillController::SelectTextRange(int32_t aStartIndex, int32_t aEndIndex)
 | |
| {
 | |
|   nsCOMPtr<nsIContent> content = do_QueryInterface(mFocusedInput);
 | |
|     if (!content) {
 | |
|     return NS_ERROR_UNEXPECTED;
 | |
|   }
 | |
|   ErrorResult rv;
 | |
|   HTMLInputElement::FromContent(content)->
 | |
|     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(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->ControlType() == 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);
 | |
|     }
 | |
| 
 | |
|     auto formAutoComplete = GetFormAutoComplete();
 | |
|     NS_ENSURE_TRUE(formAutoComplete, NS_ERROR_FAILURE);
 | |
| 
 | |
|     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;
 | |
| }
 | |
| 
 | |
| void nsFormFillController::RevalidateDataList()
 | |
| {
 | |
|   if (!mLastListener) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIAutoCompleteController> controller(do_QueryInterface(mLastListener));
 | |
|   if (!controller) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   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;
 | |
|   } 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)
 | |
| {
 | |
|   WidgetEvent* internalEvent = aEvent->WidgetEventPtr();
 | |
|   NS_ENSURE_STATE(internalEvent);
 | |
| 
 | |
|   switch (internalEvent->mMessage) {
 | |
|   case eFocus:
 | |
|     return Focus(aEvent);
 | |
|   case eMouseDown:
 | |
|     return MouseDown(aEvent);
 | |
|   case eKeyPress:
 | |
|     return KeyPress(aEvent);
 | |
|   case eEditorInput:
 | |
|     {
 | |
|       bool unused = false;
 | |
|       return (!mSuppressOnInput && mController && mFocusedInput) ?
 | |
|              mController->HandleText(&unused) : NS_OK;
 | |
|     }
 | |
|   case eBlur:
 | |
|     if (mFocusedInput) {
 | |
|       StopControllingInput();
 | |
|     }
 | |
|     return NS_OK;
 | |
|   case eCompositionStart:
 | |
|     NS_ASSERTION(mController, "should have a controller!");
 | |
|     if (mController && mFocusedInput) {
 | |
|       mController->HandleStartComposition();
 | |
|     }
 | |
|     return NS_OK;
 | |
|   case eCompositionEnd:
 | |
|     NS_ASSERTION(mController, "should have a controller!");
 | |
|     if (mController && mFocusedInput) {
 | |
|       mController->HandleEndComposition();
 | |
|     }
 | |
|     return NS_OK;
 | |
|   case eContextMenu:
 | |
|     if (mFocusedPopup) {
 | |
|       mFocusedPopup->ClosePopup();
 | |
|     }
 | |
|     return NS_OK;
 | |
|   case ePageHide:
 | |
|     {
 | |
|       nsCOMPtr<nsIDocument> doc = do_QueryInterface(
 | |
|         aEvent->InternalDOMEvent()->GetTarget());
 | |
|       if (!doc) {
 | |
|         return NS_OK;
 | |
|       }
 | |
| 
 | |
|       if (mFocusedInput) {
 | |
|         if (doc == mFocusedInputNode->OwnerDoc()) {
 | |
|           StopControllingInput();
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       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::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();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   for (auto iter = mAutofillInputs.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->ControlType() == 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) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
| #ifndef ANDROID
 | |
|   nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(mFocusedInputNode);
 | |
|   MOZ_ASSERT(formControl);
 | |
| 
 | |
|   // 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 (formControl->ControlType() != NS_FORM_INPUT_PASSWORD) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // If we have not seen a right click yet, just show the popup.
 | |
|   if (mLastRightClickTimeStamp.IsNull()) {
 | |
|     ShowPopup();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   uint64_t timeDiff = (TimeStamp::Now() - mLastRightClickTimeStamp).ToMilliseconds();
 | |
|   if (timeDiff > mFocusAfterRightClickThreshold) {
 | |
|     ShowPopup();
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   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);
 | |
| 
 | |
|   // 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<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);
 | |
| 
 | |
|     auto formAutoComplete = GetFormAutoComplete();
 | |
|     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;
 | 
