forked from mirrors/gecko-dev
In the case where scroll-snap-type is specified for the scroll container, the scroll-padding is also factored into in ScrollFrameHelper::ComputeScrollSnapInfo which is called via ScrollFrameHelper::ScrollToWithOrigin. It doesn't double the scroll-padding value, but it's actually redundant, we should avoid it. We could separate the functionality of ScrollToWithOrigin, one is to scroll to a given element, the other is to scroll to a given position. The former will be used for Element.scrollIntoElement and relevant stuff, the latter will be used for Element.scrollTo and relevant stuff. That's being said, as of now, we have still the old scroll snap implementation, so the separation will introduce complexity, the separation should be done once after the old implementation removed. There are 9 call sites of nsIPresShell::ScrollContentIntoView: nsIPresShell::GoToAnchor nsIPresShell::ScrollToAnchor Element::ScrollIntoView We definitely needs scroll-padding and scroll-margin for these functions. nsCoreUtils::ScrollTo This is used for Accesible::ScrollTo which scrolls to a given accesible node, probably we should behave as what Element::ScrollIntoView does. Accessible::DispatchClickEvent Similar to the above, similated various mouse events on a given target node. PresShell::EventHandler::PrepareToUseCaretPosition PresShell::EventHandler::GetCurrentItemAndPositionForElement Both are for context menu, we shouldn't consider scroll-padding and scroll-margin. nsFormFillController::SetPopupOpen This is used for autocompletion popup, we shouldn't consider scroll-padding and scroll-margin. nsFocusManager::ScrollIntoView This is bit unfortunate, we should use scroll-padding and scroll-margin depending on call site of this function. Bug 1535232 is for this case. cssom-view/scrollIntoView-scrollPadding.html which has some tests that is actually testing scroll-padding with scrollIntoView passes with this change. The reftest in this change is a test case that the browser navigates to an element with specifying the anchor to the element. Differential Revision: https://phabricator.services.mozilla.com/D23084 --HG-- extra : moz-landing-system : lando
1425 lines
42 KiB
C++
1425 lines
42 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/EventListenerManager.h"
|
|
#include "mozilla/dom/Element.h"
|
|
#include "mozilla/dom/Event.h" // for Event
|
|
#include "mozilla/dom/HTMLInputElement.h"
|
|
#include "mozilla/dom/KeyboardEvent.h"
|
|
#include "mozilla/dom/KeyboardEventBinding.h"
|
|
#include "mozilla/dom/MouseEvent.h"
|
|
#include "mozilla/dom/PageTransitionEvent.h"
|
|
#include "mozilla/Logging.h"
|
|
#include "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 "mozilla/dom/Document.h"
|
|
#include "nsIContent.h"
|
|
#include "nsIPresShell.h"
|
|
#include "nsRect.h"
|
|
#include "nsILoginManager.h"
|
|
#include "nsToolkitCompsCID.h"
|
|
#include "nsEmbedCID.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsGenericHTMLElement.h"
|
|
#include "nsILoadContext.h"
|
|
#include "nsIFrame.h"
|
|
#include "nsIScriptSecurityManager.h"
|
|
#include "nsFocusManager.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
using mozilla::ErrorResult;
|
|
using mozilla::LogLevel;
|
|
|
|
static mozilla::LazyLogModule sLogger("satchel");
|
|
|
|
static nsIFormAutoComplete* GetFormAutoComplete() {
|
|
static nsCOMPtr<nsIFormAutoComplete> sInstance;
|
|
static bool sInitialized = false;
|
|
if (!sInitialized) {
|
|
nsresult rv;
|
|
sInstance = do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv);
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
ClearOnShutdown(&sInstance);
|
|
sInitialized = true;
|
|
}
|
|
}
|
|
return sInstance;
|
|
}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION(nsFormFillController, mController, mLoginManager,
|
|
mLoginReputationService, 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),
|
|
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),
|
|
mPasswordPopupAutomaticallyOpened(false) {
|
|
mController = do_GetService("@mozilla.org/autocomplete/controller;1");
|
|
MOZ_ASSERT(mController);
|
|
}
|
|
|
|
nsFormFillController::~nsFormFillController() {
|
|
if (mListNode) {
|
|
mListNode->RemoveMutationObserver(this);
|
|
mListNode = nullptr;
|
|
}
|
|
if (mFocusedInput) {
|
|
MaybeRemoveMutationObserver(mFocusedInput);
|
|
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(mozilla::dom::Element* aElement,
|
|
int32_t aNameSpaceID,
|
|
nsAtom* aAttribute,
|
|
int32_t aModType,
|
|
const nsAttrValue* aOldValue) {
|
|
if ((aAttribute == nsGkAtoms::type || aAttribute == nsGkAtoms::readonly ||
|
|
aAttribute == nsGkAtoms::autocomplete) &&
|
|
aNameSpaceID == kNameSpaceID_None) {
|
|
RefPtr<HTMLInputElement> focusedInput(mFocusedInput);
|
|
// Reset the current state of the controller, unconditionally.
|
|
StopControllingInput();
|
|
// Then restart based on the new values. We have to delay this
|
|
// to avoid ending up in an endless loop due to re-registering our
|
|
// mutation observer (which would notify us again for *this* event).
|
|
nsCOMPtr<nsIRunnable> event =
|
|
mozilla::NewRunnableMethod<RefPtr<HTMLInputElement>>(
|
|
"nsFormFillController::MaybeStartControllingInput", this,
|
|
&nsFormFillController::MaybeStartControllingInput, focusedInput);
|
|
aElement->OwnerDoc()->Dispatch(TaskCategory::Other, event.forget());
|
|
}
|
|
|
|
if (mListNode && mListNode->Contains(aElement)) {
|
|
RevalidateDataList();
|
|
}
|
|
}
|
|
|
|
void nsFormFillController::ContentAppended(nsIContent* aChild) {
|
|
if (mListNode && mListNode->Contains(aChild->GetParent())) {
|
|
RevalidateDataList();
|
|
}
|
|
}
|
|
|
|
void nsFormFillController::ContentInserted(nsIContent* aChild) {
|
|
if (mListNode && mListNode->Contains(aChild->GetParent())) {
|
|
RevalidateDataList();
|
|
}
|
|
}
|
|
|
|
void nsFormFillController::ContentRemoved(nsIContent* aChild,
|
|
nsIContent* aPreviousSibling) {
|
|
if (mListNode && mListNode->Contains(aChild->GetParent())) {
|
|
RevalidateDataList();
|
|
}
|
|
}
|
|
|
|
void nsFormFillController::CharacterDataWillChange(
|
|
nsIContent* aContent, const CharacterDataChangeInfo&) {}
|
|
|
|
void nsFormFillController::CharacterDataChanged(
|
|
nsIContent* aContent, const CharacterDataChangeInfo&) {}
|
|
|
|
void nsFormFillController::AttributeWillChange(mozilla::dom::Element* aElement,
|
|
int32_t aNameSpaceID,
|
|
nsAtom* aAttribute,
|
|
int32_t aModType) {}
|
|
|
|
void nsFormFillController::NativeAnonymousChildListChange(nsIContent* aContent,
|
|
bool aIsRemove) {}
|
|
|
|
void nsFormFillController::ParentChainChanged(nsIContent* aContent) {}
|
|
|
|
void nsFormFillController::NodeWillBeDestroyed(const nsINode* aNode) {
|
|
MOZ_LOG(sLogger, LogLevel::Verbose, ("NodeWillBeDestroyed: %p", aNode));
|
|
mPwmgrInputs.Remove(aNode);
|
|
mAutofillInputs.Remove(aNode);
|
|
if (aNode == mListNode) {
|
|
mListNode = nullptr;
|
|
RevalidateDataList();
|
|
} else if (aNode == mFocusedInput) {
|
|
mFocusedInput = nullptr;
|
|
}
|
|
}
|
|
|
|
void nsFormFillController::MaybeRemoveMutationObserver(nsINode* aNode) {
|
|
// Nodes being tracked in mPwmgrInputs will have their observers removed when
|
|
// they stop being tracked.
|
|
if (!mPwmgrInputs.Get(aNode) && !mAutofillInputs.Get(aNode)) {
|
|
aNode->RemoveMutationObserver(this);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//// nsIFormFillController
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::AttachToBrowser(nsIDocShell* aDocShell,
|
|
nsIAutoCompletePopup* aPopup) {
|
|
MOZ_LOG(sLogger, LogLevel::Debug,
|
|
("AttachToBrowser for docShell %p with popup %p", aDocShell, 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);
|
|
|
|
nsFocusManager* fm = nsFocusManager::GetFocusManager();
|
|
if (fm) {
|
|
nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedElement();
|
|
this->HandleFocus(HTMLInputElement::FromNodeOrNull(focusedContent));
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::AttachPopupElementToBrowser(nsIDocShell* aDocShell,
|
|
dom::Element* aPopupEl) {
|
|
MOZ_LOG(sLogger, LogLevel::Debug,
|
|
("AttachPopupElementToBrowser for docShell %p with popup %p",
|
|
aDocShell, aPopupEl));
|
|
NS_ENSURE_TRUE(aDocShell && aPopupEl, NS_ERROR_ILLEGAL_VALUE);
|
|
|
|
nsCOMPtr<nsIAutoCompletePopup> popup = aPopupEl->AsAutoCompletePopup();
|
|
NS_ENSURE_STATE(popup);
|
|
|
|
return AttachToBrowser(aDocShell, popup);
|
|
}
|
|
|
|
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(HTMLInputElement* aInput) {
|
|
/*
|
|
* The Login Manager can supply autocomplete results for username fields,
|
|
* when a user has multiple logins stored for a site. It uses this
|
|
* interface to indicate that the form manager shouldn't handle the
|
|
* autocomplete. The form manager also checks for this tag when saving
|
|
* form history (so it doesn't save usernames).
|
|
*/
|
|
NS_ENSURE_STATE(aInput);
|
|
|
|
// If the field was already marked, we don't want to show the popup again.
|
|
if (mPwmgrInputs.Get(aInput)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
mPwmgrInputs.Put(aInput, true);
|
|
aInput->AddMutationObserverUnlessExists(this);
|
|
|
|
nsFocusManager* fm = nsFocusManager::GetFocusManager();
|
|
if (fm) {
|
|
nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedElement();
|
|
if (focusedContent == aInput) {
|
|
if (!mFocusedInput) {
|
|
MaybeStartControllingInput(aInput);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!mLoginManager) {
|
|
mLoginManager = do_GetService("@mozilla.org/login-manager;1");
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::MarkAsAutofillField(HTMLInputElement* aInput) {
|
|
/*
|
|
* Support other components implementing form autofill and handle autocomplete
|
|
* for the field.
|
|
*/
|
|
NS_ENSURE_STATE(aInput);
|
|
|
|
MOZ_LOG(sLogger, LogLevel::Verbose,
|
|
("MarkAsAutofillField: aInput = %p", aInput));
|
|
|
|
if (mAutofillInputs.Get(aInput)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
mAutofillInputs.Put(aInput, true);
|
|
aInput->AddMutationObserverUnlessExists(this);
|
|
|
|
aInput->EnablePreview();
|
|
|
|
nsFocusManager* fm = nsFocusManager::GetFocusManager();
|
|
if (fm) {
|
|
nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedElement();
|
|
if (focusedContent == aInput) {
|
|
MaybeStartControllingInput(aInput);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetFocusedInput(HTMLInputElement** aInput) {
|
|
*aInput = mFocusedInput;
|
|
NS_IF_ADDREF(*aInput);
|
|
return NS_OK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//// nsIAutoCompleteInput
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetPopup(nsIAutoCompletePopup** aPopup) {
|
|
*aPopup = mFocusedPopup;
|
|
NS_IF_ADDREF(*aPopup);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetPopupElement(Element** aPopup) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetController(nsIAutoCompleteController** aController) {
|
|
*aController = mController;
|
|
NS_IF_ADDREF(*aController);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetPopupOpen(bool* aPopupOpen) {
|
|
if (mFocusedPopup) {
|
|
mFocusedPopup->GetPopupOpen(aPopupOpen);
|
|
} else {
|
|
*aPopupOpen = false;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::SetPopupOpen(bool aPopupOpen) {
|
|
if (mFocusedPopup) {
|
|
if (aPopupOpen) {
|
|
// make sure input field is visible before showing popup (bug 320938)
|
|
nsCOMPtr<nsIContent> content = mFocusedInput;
|
|
NS_ENSURE_STATE(content);
|
|
nsCOMPtr<nsIDocShell> docShell = GetDocShellForInput(mFocusedInput);
|
|
NS_ENSURE_STATE(docShell);
|
|
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 |
|
|
nsIPresShell::SCROLL_IGNORE_SCROLL_MARGIN_AND_PADDING);
|
|
// mFocusedPopup can be destroyed after ScrollContentIntoView, see bug
|
|
// 420089
|
|
if (mFocusedPopup) {
|
|
mFocusedPopup->OpenAutocompletePopup(this, mFocusedInput);
|
|
}
|
|
} else {
|
|
mFocusedPopup->ClosePopup();
|
|
mPasswordPopupAutomaticallyOpened = false;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetDisableAutoComplete(bool* aDisableAutoComplete) {
|
|
*aDisableAutoComplete = mDisableAutoComplete;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::SetDisableAutoComplete(bool aDisableAutoComplete) {
|
|
mDisableAutoComplete = aDisableAutoComplete;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetCompleteDefaultIndex(bool* aCompleteDefaultIndex) {
|
|
*aCompleteDefaultIndex = mCompleteDefaultIndex;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::SetCompleteDefaultIndex(bool aCompleteDefaultIndex) {
|
|
mCompleteDefaultIndex = aCompleteDefaultIndex;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetCompleteSelectedIndex(bool* aCompleteSelectedIndex) {
|
|
*aCompleteSelectedIndex = mCompleteSelectedIndex;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::SetCompleteSelectedIndex(bool aCompleteSelectedIndex) {
|
|
mCompleteSelectedIndex = aCompleteSelectedIndex;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetForceComplete(bool* aForceComplete) {
|
|
*aForceComplete = mForceComplete;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsFormFillController::SetForceComplete(bool aForceComplete) {
|
|
mForceComplete = aForceComplete;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetMinResultsForPopup(uint32_t* aMinResultsForPopup) {
|
|
*aMinResultsForPopup = mMinResultsForPopup;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsFormFillController::SetMinResultsForPopup(
|
|
uint32_t aMinResultsForPopup) {
|
|
mMinResultsForPopup = aMinResultsForPopup;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetMaxRows(uint32_t* aMaxRows) {
|
|
*aMaxRows = mMaxRows;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::SetMaxRows(uint32_t aMaxRows) {
|
|
mMaxRows = aMaxRows;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetTimeout(uint32_t* aTimeout) {
|
|
*aTimeout = mTimeout;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsFormFillController::SetTimeout(uint32_t aTimeout) {
|
|
mTimeout = aTimeout;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::SetSearchParam(const nsAString& aSearchParam) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetSearchParam(nsAString& aSearchParam) {
|
|
if (!mFocusedInput) {
|
|
NS_WARNING(
|
|
"mFocusedInput is null for some reason! avoiding a crash. should find "
|
|
"out why... - ben");
|
|
return NS_ERROR_FAILURE; // XXX why? fix me.
|
|
}
|
|
|
|
mFocusedInput->GetName(aSearchParam);
|
|
if (aSearchParam.IsEmpty()) {
|
|
mFocusedInput->GetId(aSearchParam);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetSearchCount(uint32_t* aSearchCount) {
|
|
*aSearchCount = 1;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetSearchAt(uint32_t index, nsACString& _retval) {
|
|
if (mAutofillInputs.Get(mFocusedInput)) {
|
|
MOZ_LOG(sLogger, LogLevel::Debug, ("GetSearchAt: autofill-profiles field"));
|
|
nsCOMPtr<nsIAutoCompleteSearch> profileSearch = do_GetService(
|
|
"@mozilla.org/autocomplete/search;1?name=autofill-profiles");
|
|
if (profileSearch) {
|
|
_retval.AssignLiteral("autofill-profiles");
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
MOZ_LOG(sLogger, LogLevel::Debug, ("GetSearchAt: form-history field"));
|
|
_retval.AssignLiteral("form-history");
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetTextValue(nsAString& aTextValue) {
|
|
if (mFocusedInput) {
|
|
mFocusedInput->GetValue(aTextValue, CallerType::System);
|
|
} else {
|
|
aTextValue.Truncate();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::SetTextValue(const nsAString& aTextValue) {
|
|
if (mFocusedInput) {
|
|
mSuppressOnInput = true;
|
|
mFocusedInput->SetUserInput(aTextValue,
|
|
*nsContentUtils::GetSystemPrincipal());
|
|
mSuppressOnInput = false;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::SetTextValueWithReason(const nsAString& aTextValue,
|
|
uint16_t aReason) {
|
|
return SetTextValue(aTextValue);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetSelectionStart(int32_t* aSelectionStart) {
|
|
if (!mFocusedInput) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
ErrorResult rv;
|
|
*aSelectionStart = mFocusedInput->GetSelectionStartIgnoringType(rv);
|
|
return rv.StealNSResult();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetSelectionEnd(int32_t* aSelectionEnd) {
|
|
if (!mFocusedInput) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
ErrorResult rv;
|
|
*aSelectionEnd = mFocusedInput->GetSelectionEndIgnoringType(rv);
|
|
return rv.StealNSResult();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::SelectTextRange(int32_t aStartIndex, int32_t aEndIndex) {
|
|
if (!mFocusedInput) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
ErrorResult rv;
|
|
mFocusedInput->SetSelectionRange(aStartIndex, aEndIndex,
|
|
Optional<nsAString>(), rv);
|
|
return rv.StealNSResult();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::OnSearchBegin() { return NS_OK; }
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::OnSearchComplete() { return NS_OK; }
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::OnTextEntered(Event* aEvent, bool itemWasSelected,
|
|
bool* aPrevent) {
|
|
NS_ENSURE_ARG(aPrevent);
|
|
NS_ENSURE_TRUE(mFocusedInput, NS_OK);
|
|
|
|
/**
|
|
* This function can get called when text wasn't actually entered
|
|
* into the field (e.g. if an autocomplete item wasn't selected) so
|
|
* we don't fire DOMAutoComplete in that case since nothing
|
|
* was actually autocompleted.
|
|
*/
|
|
if (!itemWasSelected) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Fire off a DOMAutoComplete event
|
|
|
|
IgnoredErrorResult ignored;
|
|
RefPtr<Event> event = mFocusedInput->OwnerDoc()->CreateEvent(
|
|
NS_LITERAL_STRING("Events"), CallerType::System, ignored);
|
|
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);
|
|
|
|
bool defaultActionEnabled =
|
|
mFocusedInput->DispatchEvent(*event, CallerType::System, IgnoreErrors());
|
|
*aPrevent = !defaultActionEnabled;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::OnTextReverted(bool* _retval) {
|
|
mPasswordPopupAutomaticallyOpened = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetConsumeRollupEvent(bool* aConsumeRollupEvent) {
|
|
*aConsumeRollupEvent = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetInPrivateContext(bool* aInPrivateContext) {
|
|
if (!mFocusedInput) {
|
|
*aInPrivateContext = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
RefPtr<Document> doc = mFocusedInput->OwnerDoc();
|
|
nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
|
|
*aInPrivateContext = loadContext && loadContext->UsePrivateBrowsing();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetNoRollupOnCaretMove(bool* aNoRollupOnCaretMove) {
|
|
*aNoRollupOnCaretMove = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetNoRollupOnEmptySearch(bool* aNoRollupOnEmptySearch) {
|
|
if (mFocusedInput &&
|
|
(mPwmgrInputs.Get(mFocusedInput) ||
|
|
mFocusedInput->ControlType() == NS_FORM_INPUT_PASSWORD)) {
|
|
// Don't close the login popup when the field is cleared (bug 1534896).
|
|
*aNoRollupOnEmptySearch = true;
|
|
} else {
|
|
*aNoRollupOnEmptySearch = false;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetUserContextId(uint32_t* aUserContextId) {
|
|
*aUserContextId = nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID;
|
|
return NS_OK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//// nsIAutoCompleteSearch
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::StartSearch(const nsAString& aSearchString,
|
|
const nsAString& aSearchParam,
|
|
nsIAutoCompleteResult* aPreviousResult,
|
|
nsIAutoCompleteObserver* aListener) {
|
|
MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch for %p", mFocusedInput));
|
|
|
|
nsresult rv;
|
|
|
|
// If the login manager has indicated it's responsible for this field, let it
|
|
// handle the autocomplete. Otherwise, handle with form history.
|
|
// This method is sometimes called in unit tests and from XUL without a
|
|
// focused node.
|
|
if (mFocusedInput &&
|
|
(mPwmgrInputs.Get(mFocusedInput) ||
|
|
mFocusedInput->ControlType() == NS_FORM_INPUT_PASSWORD)) {
|
|
MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch: login field"));
|
|
|
|
// Handle the case where a password field is focused but
|
|
// MarkAsLoginManagerField wasn't called because password manager is
|
|
// disabled.
|
|
if (!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 {
|
|
MOZ_LOG(sLogger, LogLevel::Debug, ("StartSearch: non-login field"));
|
|
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(mFocusedInput));
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsIInputListAutoComplete> inputListAutoComplete =
|
|
do_GetService("@mozilla.org/satchel/inputlist-autocomplete;1", &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
rv = inputListAutoComplete->AutoCompleteSearch(aSearch, mFocusedInput,
|
|
aResult);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (mFocusedInput) {
|
|
Element* list = mFocusedInput->GetList();
|
|
|
|
// Add a mutation observer to check for changes to the items in the
|
|
// <datalist> and update the suggestions accordingly.
|
|
if (mListNode != list) {
|
|
if (mListNode) {
|
|
mListNode->RemoveMutationObserver(this);
|
|
mListNode = nullptr;
|
|
}
|
|
if (list) {
|
|
list->AddMutationObserverUnlessExists(this);
|
|
mListNode = list;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsFormFillController::RevalidateDataList() {
|
|
if (!mLastListener) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIAutoCompleteController> controller(
|
|
do_QueryInterface(mLastListener));
|
|
if (!controller) {
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
nsresult nsFormFillController::StartQueryLoginReputation(
|
|
HTMLInputElement* aInput) {
|
|
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(Event* aEvent) {
|
|
WidgetEvent* internalEvent = aEvent->WidgetEventPtr();
|
|
NS_ENSURE_STATE(internalEvent);
|
|
|
|
switch (internalEvent->mMessage) {
|
|
case eFocus:
|
|
return Focus(aEvent);
|
|
case eMouseDown:
|
|
return MouseDown(aEvent);
|
|
case eKeyDown:
|
|
return KeyDown(aEvent);
|
|
case eKeyPress:
|
|
return KeyPress(aEvent);
|
|
case eEditorInput: {
|
|
nsCOMPtr<nsINode> input = do_QueryInterface(aEvent->GetComposedTarget());
|
|
if (!IsTextControl(input)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
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<Document> doc = do_QueryInterface(aEvent->GetTarget());
|
|
if (!doc) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (mFocusedInput) {
|
|
if (doc == mFocusedInput->OwnerDoc()) {
|
|
StopControllingInput();
|
|
}
|
|
}
|
|
|
|
// Only remove the observer notifications and marked autofill and password
|
|
// manager fields if the page isn't going to be persisted (i.e. it's being
|
|
// unloaded) so that appropriate autocomplete handling works with bfcache.
|
|
bool persisted = aEvent->AsPageTransitionEvent()->Persisted();
|
|
if (!persisted) {
|
|
RemoveForDocument(doc);
|
|
}
|
|
} break;
|
|
default:
|
|
// Handling the default case to shut up stupid -Wswitch warnings.
|
|
// One day compilers will be smarter...
|
|
break;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsFormFillController::RemoveForDocument(Document* aDoc) {
|
|
MOZ_LOG(sLogger, LogLevel::Verbose, ("RemoveForDocument: %p", aDoc));
|
|
for (auto iter = mPwmgrInputs.Iter(); !iter.Done(); iter.Next()) {
|
|
const nsINode* key = iter.Key();
|
|
if (key && (!aDoc || key->OwnerDoc() == aDoc)) {
|
|
// mFocusedInput's observer is tracked separately, so don't remove it
|
|
// here.
|
|
if (key != mFocusedInput) {
|
|
const_cast<nsINode*>(key)->RemoveMutationObserver(this);
|
|
}
|
|
iter.Remove();
|
|
}
|
|
}
|
|
|
|
for (auto iter = mAutofillInputs.Iter(); !iter.Done(); iter.Next()) {
|
|
const nsINode* key = iter.Key();
|
|
if (key && (!aDoc || key->OwnerDoc() == aDoc)) {
|
|
// mFocusedInput's observer is tracked separately, so don't remove it
|
|
// here.
|
|
if (key != mFocusedInput) {
|
|
const_cast<nsINode*>(key)->RemoveMutationObserver(this);
|
|
}
|
|
iter.Remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool nsFormFillController::IsTextControl(nsINode* aNode) {
|
|
nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(aNode);
|
|
return formControl && formControl->IsSingleLineTextControl(false);
|
|
}
|
|
|
|
void nsFormFillController::MaybeStartControllingInput(
|
|
HTMLInputElement* aInput) {
|
|
MOZ_LOG(sLogger, LogLevel::Verbose,
|
|
("MaybeStartControllingInput for %p", aInput));
|
|
if (!aInput) {
|
|
return;
|
|
}
|
|
|
|
if (!IsTextControl(aInput)) {
|
|
return;
|
|
}
|
|
|
|
if (aInput->ReadOnly()) {
|
|
return;
|
|
}
|
|
|
|
bool autocomplete = nsContentUtils::IsAutocompleteEnabled(aInput);
|
|
|
|
bool hasList = aInput->GetList() != nullptr;
|
|
|
|
bool isPwmgrInput = false;
|
|
if (mPwmgrInputs.Get(aInput) ||
|
|
aInput->ControlType() == NS_FORM_INPUT_PASSWORD) {
|
|
isPwmgrInput = true;
|
|
}
|
|
|
|
bool isAutofillInput = false;
|
|
if (mAutofillInputs.Get(aInput)) {
|
|
isAutofillInput = true;
|
|
}
|
|
|
|
if (isAutofillInput || isPwmgrInput || hasList || autocomplete) {
|
|
StartControllingInput(aInput);
|
|
}
|
|
|
|
#ifdef NIGHTLY_BUILD
|
|
// Trigger an asynchronous login reputation query when user focuses on the
|
|
// password field.
|
|
if (aInput->ControlType() == NS_FORM_INPUT_PASSWORD) {
|
|
StartQueryLoginReputation(aInput);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
nsresult nsFormFillController::HandleFocus(HTMLInputElement* aInput) {
|
|
MaybeStartControllingInput(aInput);
|
|
|
|
// Bail if we didn't start controlling the input.
|
|
if (!mFocusedInput) {
|
|
return NS_OK;
|
|
}
|
|
|
|
#ifndef ANDROID
|
|
// If this focus doesn't follow a right click within our specified
|
|
// threshold then show the autocomplete popup for all password fields.
|
|
// This is done to avoid showing both the context menu and the popup
|
|
// at the same time.
|
|
// We use a timestamp instead of a bool to avoid complexity when dealing with
|
|
// multiple input forms and the fact that a mousedown into an already focused
|
|
// field does not trigger another focus.
|
|
|
|
if (mFocusedInput->ControlType() != NS_FORM_INPUT_PASSWORD) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// If we have not seen a right click yet, just show the popup.
|
|
if (mLastRightClickTimeStamp.IsNull()) {
|
|
mPasswordPopupAutomaticallyOpened = true;
|
|
ShowPopup();
|
|
return NS_OK;
|
|
}
|
|
|
|
uint64_t timeDiff =
|
|
(TimeStamp::Now() - mLastRightClickTimeStamp).ToMilliseconds();
|
|
if (timeDiff > mFocusAfterRightClickThreshold) {
|
|
mPasswordPopupAutomaticallyOpened = true;
|
|
ShowPopup();
|
|
}
|
|
#endif
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsFormFillController::Focus(Event* aEvent) {
|
|
nsCOMPtr<nsIContent> input = do_QueryInterface(aEvent->GetComposedTarget());
|
|
return this->HandleFocus(HTMLInputElement::FromNodeOrNull(input));
|
|
}
|
|
|
|
nsresult nsFormFillController::KeyDown(Event* aEvent) {
|
|
NS_ASSERTION(mController, "should have a controller!");
|
|
|
|
mPasswordPopupAutomaticallyOpened = false;
|
|
|
|
if (!mFocusedInput || !mController) {
|
|
return NS_OK;
|
|
}
|
|
|
|
RefPtr<KeyboardEvent> keyEvent = aEvent->AsKeyboardEvent();
|
|
if (!keyEvent) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
bool cancel = false;
|
|
uint32_t k = keyEvent->KeyCode();
|
|
switch (k) {
|
|
case KeyboardEvent_Binding::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 == KeyboardEvent_Binding::DOM_VK_RETURN) {
|
|
aEvent->StopPropagation();
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsFormFillController::KeyPress(Event* aEvent) {
|
|
NS_ASSERTION(mController, "should have a controller!");
|
|
|
|
mPasswordPopupAutomaticallyOpened = false;
|
|
|
|
if (!mFocusedInput || !mController) {
|
|
return NS_OK;
|
|
}
|
|
|
|
RefPtr<KeyboardEvent> keyEvent = aEvent->AsKeyboardEvent();
|
|
if (!keyEvent) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
bool cancel = false;
|
|
bool unused = false;
|
|
|
|
uint32_t k = keyEvent->KeyCode();
|
|
switch (k) {
|
|
case KeyboardEvent_Binding::DOM_VK_DELETE:
|
|
#ifndef XP_MACOSX
|
|
mController->HandleDelete(&cancel);
|
|
break;
|
|
case KeyboardEvent_Binding::DOM_VK_BACK_SPACE:
|
|
mController->HandleText(&unused);
|
|
break;
|
|
#else
|
|
case KeyboardEvent_Binding::DOM_VK_BACK_SPACE: {
|
|
if (keyEvent->ShiftKey()) {
|
|
mController->HandleDelete(&cancel);
|
|
} else {
|
|
mController->HandleText(&unused);
|
|
}
|
|
|
|
break;
|
|
}
|
|
#endif
|
|
case KeyboardEvent_Binding::DOM_VK_PAGE_UP:
|
|
case KeyboardEvent_Binding::DOM_VK_PAGE_DOWN: {
|
|
if (keyEvent->CtrlKey() || keyEvent->AltKey() || keyEvent->MetaKey()) {
|
|
break;
|
|
}
|
|
}
|
|
MOZ_FALLTHROUGH;
|
|
case KeyboardEvent_Binding::DOM_VK_UP:
|
|
case KeyboardEvent_Binding::DOM_VK_DOWN:
|
|
case KeyboardEvent_Binding::DOM_VK_LEFT:
|
|
case KeyboardEvent_Binding::DOM_VK_RIGHT: {
|
|
// Get the writing-mode of the relevant input element,
|
|
// so that we can remap arrow keys if necessary.
|
|
mozilla::WritingMode wm;
|
|
if (mFocusedInput) {
|
|
nsIFrame* frame = mFocusedInput->GetPrimaryFrame();
|
|
if (frame) {
|
|
wm = frame->GetWritingMode();
|
|
}
|
|
}
|
|
if (wm.IsVertical()) {
|
|
switch (k) {
|
|
case KeyboardEvent_Binding::DOM_VK_LEFT:
|
|
k = wm.IsVerticalLR() ? KeyboardEvent_Binding::DOM_VK_UP
|
|
: KeyboardEvent_Binding::DOM_VK_DOWN;
|
|
break;
|
|
case KeyboardEvent_Binding::DOM_VK_RIGHT:
|
|
k = wm.IsVerticalLR() ? KeyboardEvent_Binding::DOM_VK_DOWN
|
|
: KeyboardEvent_Binding::DOM_VK_UP;
|
|
break;
|
|
case KeyboardEvent_Binding::DOM_VK_UP:
|
|
k = KeyboardEvent_Binding::DOM_VK_LEFT;
|
|
break;
|
|
case KeyboardEvent_Binding::DOM_VK_DOWN:
|
|
k = KeyboardEvent_Binding::DOM_VK_RIGHT;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
mController->HandleKeyNavigation(k, &cancel);
|
|
break;
|
|
case KeyboardEvent_Binding::DOM_VK_ESCAPE:
|
|
mController->HandleEscape(&cancel);
|
|
break;
|
|
case KeyboardEvent_Binding::DOM_VK_TAB:
|
|
mController->HandleTab();
|
|
cancel = false;
|
|
break;
|
|
}
|
|
|
|
if (cancel) {
|
|
aEvent->PreventDefault();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsFormFillController::MouseDown(Event* aEvent) {
|
|
MouseEvent* mouseEvent = aEvent->AsMouseEvent();
|
|
if (!mouseEvent) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsCOMPtr<nsINode> targetNode = do_QueryInterface(aEvent->GetComposedTarget());
|
|
if (!HTMLInputElement::FromNodeOrNull(targetNode)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
int16_t button = mouseEvent->Button();
|
|
|
|
// In case of a right click we set a timestamp that
|
|
// will be checked in Focus() to avoid showing
|
|
// both contextmenu and popup at the same time.
|
|
if (button == 2) {
|
|
mLastRightClickTimeStamp = TimeStamp::Now();
|
|
return NS_OK;
|
|
}
|
|
|
|
if (button != 0) {
|
|
return NS_OK;
|
|
}
|
|
|
|
return ShowPopup();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::ShowPopup() {
|
|
bool isOpen = false;
|
|
GetPopupOpen(&isOpen);
|
|
if (isOpen) {
|
|
return SetPopupOpen(false);
|
|
}
|
|
|
|
nsCOMPtr<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(KeyboardEvent_Binding::DOM_VK_DOWN,
|
|
&cancel);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsFormFillController::GetPasswordPopupAutomaticallyOpened(
|
|
bool* _retval) {
|
|
*_retval = mPasswordPopupAutomaticallyOpened;
|
|
return NS_OK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//// nsFormFillController
|
|
|
|
void nsFormFillController::AddWindowListeners(nsPIDOMWindowOuter* aWindow) {
|
|
MOZ_LOG(sLogger, LogLevel::Debug,
|
|
("AddWindowListeners for window %p", aWindow));
|
|
if (!aWindow) {
|
|
return;
|
|
}
|
|
|
|
EventTarget* target = aWindow->GetChromeEventHandler();
|
|
if (!target) {
|
|
return;
|
|
}
|
|
|
|
EventListenerManager* elm = target->GetOrCreateListenerManager();
|
|
if (NS_WARN_IF(!elm)) {
|
|
return;
|
|
}
|
|
|
|
elm->AddEventListenerByType(this, NS_LITERAL_STRING("focus"),
|
|
TrustedEventsAtCapture());
|
|
elm->AddEventListenerByType(this, NS_LITERAL_STRING("blur"),
|
|
TrustedEventsAtCapture());
|
|
elm->AddEventListenerByType(this, NS_LITERAL_STRING("pagehide"),
|
|
TrustedEventsAtCapture());
|
|
elm->AddEventListenerByType(this, NS_LITERAL_STRING("mousedown"),
|
|
TrustedEventsAtCapture());
|
|
elm->AddEventListenerByType(this, NS_LITERAL_STRING("input"),
|
|
TrustedEventsAtCapture());
|
|
elm->AddEventListenerByType(this, NS_LITERAL_STRING("keydown"),
|
|
TrustedEventsAtCapture());
|
|
elm->AddEventListenerByType(this, NS_LITERAL_STRING("keypress"),
|
|
TrustedEventsAtSystemGroupCapture());
|
|
elm->AddEventListenerByType(this, NS_LITERAL_STRING("compositionstart"),
|
|
TrustedEventsAtCapture());
|
|
elm->AddEventListenerByType(this, NS_LITERAL_STRING("compositionend"),
|
|
TrustedEventsAtCapture());
|
|
elm->AddEventListenerByType(this, NS_LITERAL_STRING("contextmenu"),
|
|
TrustedEventsAtCapture());
|
|
|
|
// 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) {
|
|
MOZ_LOG(sLogger, LogLevel::Debug,
|
|
("RemoveWindowListeners for window %p", aWindow));
|
|
if (!aWindow) {
|
|
return;
|
|
}
|
|
|
|
StopControllingInput();
|
|
|
|
RefPtr<Document> doc = aWindow->GetDoc();
|
|
RemoveForDocument(doc);
|
|
|
|
EventTarget* target = aWindow->GetChromeEventHandler();
|
|
if (!target) {
|
|
return;
|
|
}
|
|
|
|
EventListenerManager* elm = target->GetOrCreateListenerManager();
|
|
if (NS_WARN_IF(!elm)) {
|
|
return;
|
|
}
|
|
|
|
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("focus"),
|
|
TrustedEventsAtCapture());
|
|
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("blur"),
|
|
TrustedEventsAtCapture());
|
|
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("pagehide"),
|
|
TrustedEventsAtCapture());
|
|
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("mousedown"),
|
|
TrustedEventsAtCapture());
|
|
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("input"),
|
|
TrustedEventsAtCapture());
|
|
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("keydown"),
|
|
TrustedEventsAtCapture());
|
|
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("keypress"),
|
|
TrustedEventsAtSystemGroupCapture());
|
|
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("compositionstart"),
|
|
TrustedEventsAtCapture());
|
|
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("compositionend"),
|
|
TrustedEventsAtCapture());
|
|
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("contextmenu"),
|
|
TrustedEventsAtCapture());
|
|
}
|
|
|
|
void nsFormFillController::StartControllingInput(HTMLInputElement* aInput) {
|
|
MOZ_LOG(sLogger, LogLevel::Verbose, ("StartControllingInput for %p", aInput));
|
|
// Make sure we're not still attached to an input
|
|
StopControllingInput();
|
|
|
|
if (!mController) {
|
|
return;
|
|
}
|
|
|
|
// Find the currently focused docShell
|
|
nsCOMPtr<nsIDocShell> docShell = GetDocShellForInput(aInput);
|
|
int32_t index = GetIndexOfDocShell(docShell);
|
|
if (index < 0) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(aInput, "How did we get a docshell index??");
|
|
|
|
// Cache the popup for the focused docShell
|
|
mFocusedPopup = mPopups.SafeElementAt(index);
|
|
|
|
aInput->AddMutationObserverUnlessExists(this);
|
|
mFocusedInput = aInput;
|
|
|
|
Element* list = mFocusedInput->GetList();
|
|
if (list) {
|
|
list->AddMutationObserverUnlessExists(this);
|
|
mListNode = list;
|
|
}
|
|
|
|
mController->SetInput(this);
|
|
}
|
|
|
|
void nsFormFillController::StopControllingInput() {
|
|
mPasswordPopupAutomaticallyOpened = false;
|
|
|
|
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) {
|
|
MOZ_LOG(sLogger, LogLevel::Verbose,
|
|
("StopControllingInput: Nulled controller input for %p", this));
|
|
mController->SetInput(nullptr);
|
|
}
|
|
}
|
|
|
|
MOZ_LOG(sLogger, LogLevel::Verbose,
|
|
("StopControllingInput: Stopped controlling %p", mFocusedInput));
|
|
if (mFocusedInput) {
|
|
MaybeRemoveMutationObserver(mFocusedInput);
|
|
|
|
mFocusedInput = nullptr;
|
|
}
|
|
|
|
if (mFocusedPopup) {
|
|
mFocusedPopup->ClosePopup();
|
|
}
|
|
mFocusedPopup = nullptr;
|
|
}
|
|
|
|
nsIDocShell* nsFormFillController::GetDocShellForInput(
|
|
HTMLInputElement* aInput) {
|
|
NS_ENSURE_TRUE(aInput, nullptr);
|
|
|
|
nsCOMPtr<nsPIDOMWindowOuter> win = aInput->OwnerDoc()->GetWindow();
|
|
NS_ENSURE_TRUE(win, nullptr);
|
|
|
|
return win->GetDocShell();
|
|
}
|
|
|
|
nsPIDOMWindowOuter* nsFormFillController::GetWindowForDocShell(
|
|
nsIDocShell* aDocShell) {
|
|
nsCOMPtr<nsIContentViewer> contentViewer;
|
|
aDocShell->GetContentViewer(getter_AddRefs(contentViewer));
|
|
NS_ENSURE_TRUE(contentViewer, nullptr);
|
|
|
|
RefPtr<Document> doc = contentViewer->GetDocument();
|
|
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 = aDocShell;
|
|
nsCOMPtr<nsIDocShellTreeItem> parentItem;
|
|
treeItem->GetParent(getter_AddRefs(parentItem));
|
|
if (parentItem) {
|
|
nsCOMPtr<nsIDocShell> parentShell = do_QueryInterface(parentItem);
|
|
return GetIndexOfDocShell(parentShell);
|
|
}
|
|
|
|
return -1;
|
|
}
|