mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-08-15 08:53:07 +09:00
The autocomplete module listens to keypress events for both printable keys and non-printable keys a lot. However, we're stopping dispatching keypress events for non-printable keys in the default event group of web content. This means that autocomplete should listen to keypress events in the system event group. Note that it's difficult to globally change keypress event listeners to keydown event listeners because if we stop keypress events at preceding keydown event in autocomplete or satchel modules, some other modules fail to handle keydown or keypress events before autocomplete, and it's not easy to investigate which keypress event listener in which modules should be changed to a keydown event listener. Therefore, this patch doesn't do that, and uses the event group approach.
1412 lines
40 KiB
C++
1412 lines
40 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* 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/EventListenerManager.h"
|
|
#include "mozilla/dom/Element.h"
|
|
#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
|
|
#include "nsIFormAutoComplete.h"
|
|
#include "nsIInputListAutoComplete.h"
|
|
#include "nsIAutoCompleteSimpleResult.h"
|
|
#include "nsString.h"
|
|
#include "nsReadableUtils.h"
|
|
#include "nsIServiceManager.h"
|
|
#include "nsIInterfaceRequestor.h"
|
|
#include "nsIInterfaceRequestorUtils.h"
|
|
#include "nsIDocShellTreeItem.h"
|
|
#include "nsPIDOMWindow.h"
|
|
#include "nsIWebNavigation.h"
|
|
#include "nsIContentViewer.h"
|
|
#include "nsIDOMKeyEvent.h"
|
|
#include "nsIDOMDocument.h"
|
|
#include "nsIDOMElement.h"
|
|
#include "nsIFormControl.h"
|
|
#include "nsIDocument.h"
|
|
#include "nsIContent.h"
|
|
#include "nsIPresShell.h"
|
|
#include "nsRect.h"
|
|
#include "nsIDOMHTMLFormElement.h"
|
|
#include "nsILoginManager.h"
|
|
#include "nsIDOMMouseEvent.h"
|
|
#include "mozilla/ModuleUtils.h"
|
|
#include "nsToolkitCompsCID.h"
|
|
#include "nsEmbedCID.h"
|
|
#include "nsIDOMNSEditableElement.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsILoadContext.h"
|
|
#include "nsIFrame.h"
|
|
#include "nsIScriptSecurityManager.h"
|
|
#include "nsFocusManager.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
|
|
NS_IMPL_CYCLE_COLLECTION(nsFormFillController,
|
|
mController, mLoginManager, mFocusedPopup, mDocShells,
|
|
mPopups, mLastListener, mLastFormAutoComplete)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFormFillController)
|
|
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFormFillController)
|
|
NS_INTERFACE_MAP_ENTRY(nsIFormFillController)
|
|
NS_INTERFACE_MAP_ENTRY(nsIAutoCompleteInput)
|
|
NS_INTERFACE_MAP_ENTRY(nsIAutoCompleteSearch)
|
|
NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
|
|
NS_INTERFACE_MAP_ENTRY(nsIFormAutoCompleteObserver)
|
|
NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFormFillController)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFormFillController)
|
|
|
|
|
|
|
|
nsFormFillController::nsFormFillController() :
|
|
mFocusedInput(nullptr),
|
|
mFocusedInputNode(nullptr),
|
|
mListNode(nullptr),
|
|
mTimeout(50),
|
|
mMinResultsForPopup(1),
|
|
mMaxRows(0),
|
|
mContextMenuFiredBeforeFocus(false),
|
|
mDisableAutoComplete(false),
|
|
mCompleteDefaultIndex(false),
|
|
mCompleteSelectedIndex(false),
|
|
mForceComplete(false),
|
|
mSuppressOnInput(false)
|
|
{
|
|
mController = do_GetService("@mozilla.org/autocomplete/controller;1");
|
|
MOZ_ASSERT(mController);
|
|
}
|
|
|
|
nsFormFillController::~nsFormFillController()
|
|
{
|
|
if (mListNode) {
|
|
mListNode->RemoveMutationObserver(this);
|
|
mListNode = nullptr;
|
|
}
|
|
if (mFocusedInputNode) {
|
|
MaybeRemoveMutationObserver(mFocusedInputNode);
|
|
mFocusedInputNode = nullptr;
|
|
mFocusedInput = nullptr;
|
|
}
|
|
RemoveForDocument(nullptr);
|
|
|
|
// Remove ourselves as a focus listener from all cached docShells
|
|
uint32_t count = mDocShells.Length();
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
nsCOMPtr<nsPIDOMWindowOuter> window = GetWindowForDocShell(mDocShells[i]);
|
|
RemoveWindowListeners(window);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//// nsIMutationObserver
|
|
//
|
|
|
|
void
|
|
nsFormFillController::AttributeChanged(nsIDocument* aDocument,
|
|
mozilla::dom::Element* aElement,
|
|
int32_t aNameSpaceID,
|
|
nsIAtom* aAttribute, int32_t aModType,
|
|
const nsAttrValue* aOldValue)
|
|
{
|
|
if ((aAttribute == nsGkAtoms::type || aAttribute == nsGkAtoms::readonly ||
|
|
aAttribute == nsGkAtoms::autocomplete) &&
|
|
aNameSpaceID == kNameSpaceID_None) {
|
|
nsCOMPtr<nsIDOMHTMLInputElement> focusedInput(mFocusedInput);
|
|
// Reset the current state of the controller, unconditionally.
|
|
StopControllingInput();
|
|
// Then restart based on the new values. We have to delay this
|
|
// to avoid ending up in an endless loop due to re-registering our
|
|
// mutation observer (which would notify us again for *this* event).
|
|
nsCOMPtr<nsIRunnable> event =
|
|
mozilla::NewRunnableMethod<nsCOMPtr<nsIDOMHTMLInputElement>>
|
|
(this, &nsFormFillController::MaybeStartControllingInput, focusedInput);
|
|
NS_DispatchToCurrentThread(event);
|
|
}
|
|
|
|
if (mListNode && mListNode->Contains(aElement)) {
|
|
RevalidateDataList();
|
|
}
|
|
}
|
|
|
|
void
|
|
nsFormFillController::ContentAppended(nsIDocument* aDocument,
|
|
nsIContent* aContainer,
|
|
nsIContent* aChild,
|
|
int32_t aIndexInContainer)
|
|
{
|
|
if (mListNode && mListNode->Contains(aContainer)) {
|
|
RevalidateDataList();
|
|
}
|
|
}
|
|
|
|
void
|
|
nsFormFillController::ContentInserted(nsIDocument* aDocument,
|
|
nsIContent* aContainer,
|
|
nsIContent* aChild,
|
|
int32_t aIndexInContainer)
|
|
{
|
|
if (mListNode && mListNode->Contains(aContainer)) {
|
|
RevalidateDataList();
|
|
}
|
|
}
|
|
|
|
void
|
|
nsFormFillController::ContentRemoved(nsIDocument* aDocument,
|
|
nsIContent* aContainer,
|
|
nsIContent* aChild,
|
|
int32_t aIndexInContainer,
|
|
nsIContent* aPreviousSibling)
|
|
{
|
|
if (mListNode && mListNode->Contains(aContainer)) {
|
|
RevalidateDataList();
|
|
}
|
|
}
|
|
|
|
void
|
|
nsFormFillController::CharacterDataWillChange(nsIDocument* aDocument,
|
|
nsIContent* aContent,
|
|
CharacterDataChangeInfo* aInfo)
|
|
{
|
|
}
|
|
|
|
void
|
|
nsFormFillController::CharacterDataChanged(nsIDocument* aDocument,
|
|
nsIContent* aContent,
|
|
CharacterDataChangeInfo* aInfo)
|
|
{
|
|
}
|
|
|
|
void
|
|
nsFormFillController::AttributeWillChange(nsIDocument* aDocument,
|
|
mozilla::dom::Element* aElement,
|
|
int32_t aNameSpaceID,
|
|
nsIAtom* aAttribute, int32_t aModType,
|
|
const nsAttrValue* aNewValue)
|
|
{
|
|
}
|
|
|
|
void
|
|
nsFormFillController::NativeAnonymousChildListChange(nsIDocument* aDocument,
|
|
nsIContent* aContent,
|
|
bool aIsRemove)
|
|
{
|
|
}
|
|
|
|
void
|
|
nsFormFillController::ParentChainChanged(nsIContent* aContent)
|
|
{
|
|
}
|
|
|
|
void
|
|
nsFormFillController::NodeWillBeDestroyed(const nsINode* aNode)
|
|
{
|
|
mPwmgrInputs.Remove(aNode);
|
|
if (aNode == mListNode) {
|
|
mListNode = nullptr;
|
|
RevalidateDataList();
|
|
} else if (aNode == mFocusedInputNode) {
|
|
mFocusedInputNode = nullptr;
|
|
mFocusedInput = nullptr;
|
|
}
|
|
}
|
|
|
|
void
|
|
nsFormFillController::MaybeRemoveMutationObserver(nsINode* aNode)
|
|
{
|
|
// Nodes being tracked in mPwmgrInputs will have their observers removed when
|
|
// they stop being tracked.
|
|
if (!mPwmgrInputs.Get(aNode)) {
|
|
aNode->RemoveMutationObserver(this);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//// nsIFormFillController
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::AttachToBrowser(nsIDocShell *aDocShell, nsIAutoCompletePopup *aPopup)
|
|
{
|
|
NS_ENSURE_TRUE(aDocShell && aPopup, NS_ERROR_ILLEGAL_VALUE);
|
|
|
|
mDocShells.AppendElement(aDocShell);
|
|
mPopups.AppendElement(aPopup);
|
|
|
|
// Listen for focus events on the domWindow of the docShell
|
|
nsCOMPtr<nsPIDOMWindowOuter> window = GetWindowForDocShell(aDocShell);
|
|
AddWindowListeners(window);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::DetachFromBrowser(nsIDocShell *aDocShell)
|
|
{
|
|
int32_t index = GetIndexOfDocShell(aDocShell);
|
|
NS_ENSURE_TRUE(index >= 0, NS_ERROR_FAILURE);
|
|
|
|
// Stop listening for focus events on the domWindow of the docShell
|
|
nsCOMPtr<nsPIDOMWindowOuter> window =
|
|
GetWindowForDocShell(mDocShells.SafeElementAt(index));
|
|
RemoveWindowListeners(window);
|
|
|
|
mDocShells.RemoveElementAt(index);
|
|
mPopups.RemoveElementAt(index);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::MarkAsLoginManagerField(nsIDOMHTMLInputElement *aInput)
|
|
{
|
|
/*
|
|
* The Login Manager can supply autocomplete results for username fields,
|
|
* when a user has multiple logins stored for a site. It uses this
|
|
* interface to indicate that the form manager shouldn't handle the
|
|
* autocomplete. The form manager also checks for this tag when saving
|
|
* form history (so it doesn't save usernames).
|
|
*/
|
|
nsCOMPtr<nsINode> node = do_QueryInterface(aInput);
|
|
NS_ENSURE_STATE(node);
|
|
|
|
// If the field was already marked, we don't want to show the popup again.
|
|
if (mPwmgrInputs.Get(node)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
mPwmgrInputs.Put(node, true);
|
|
node->AddMutationObserverUnlessExists(this);
|
|
|
|
nsFocusManager *fm = nsFocusManager::GetFocusManager();
|
|
if (fm) {
|
|
nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedContent();
|
|
if (SameCOMIdentity(focusedContent, node)) {
|
|
nsCOMPtr<nsIDOMHTMLInputElement> input = do_QueryInterface(node);
|
|
if (!mFocusedInput) {
|
|
MaybeStartControllingInput(input);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!mLoginManager)
|
|
mLoginManager = do_GetService("@mozilla.org/login-manager;1");
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetFocusedInput(nsIDOMHTMLInputElement **aInput) {
|
|
*aInput = mFocusedInput;
|
|
NS_IF_ADDREF(*aInput);
|
|
return NS_OK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//// nsIAutoCompleteInput
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetPopup(nsIAutoCompletePopup **aPopup)
|
|
{
|
|
*aPopup = mFocusedPopup;
|
|
NS_IF_ADDREF(*aPopup);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetController(nsIAutoCompleteController **aController)
|
|
{
|
|
*aController = mController;
|
|
NS_IF_ADDREF(*aController);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetPopupOpen(bool *aPopupOpen)
|
|
{
|
|
if (mFocusedPopup)
|
|
mFocusedPopup->GetPopupOpen(aPopupOpen);
|
|
else
|
|
*aPopupOpen = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::SetPopupOpen(bool aPopupOpen)
|
|
{
|
|
if (mFocusedPopup) {
|
|
if (aPopupOpen) {
|
|
// make sure input field is visible before showing popup (bug 320938)
|
|
nsCOMPtr<nsIContent> content = do_QueryInterface(mFocusedInput);
|
|
NS_ENSURE_STATE(content);
|
|
nsCOMPtr<nsIDocShell> docShell = GetDocShellForInput(mFocusedInput);
|
|
NS_ENSURE_STATE(docShell);
|
|
nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
|
|
NS_ENSURE_STATE(presShell);
|
|
presShell->ScrollContentIntoView(content,
|
|
nsIPresShell::ScrollAxis(
|
|
nsIPresShell::SCROLL_MINIMUM,
|
|
nsIPresShell::SCROLL_IF_NOT_VISIBLE),
|
|
nsIPresShell::ScrollAxis(
|
|
nsIPresShell::SCROLL_MINIMUM,
|
|
nsIPresShell::SCROLL_IF_NOT_VISIBLE),
|
|
nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
|
|
// mFocusedPopup can be destroyed after ScrollContentIntoView, see bug 420089
|
|
if (mFocusedPopup) {
|
|
nsCOMPtr<nsIDOMElement> element = do_QueryInterface(mFocusedInput);
|
|
mFocusedPopup->OpenAutocompletePopup(this, element);
|
|
}
|
|
} else
|
|
mFocusedPopup->ClosePopup();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetDisableAutoComplete(bool *aDisableAutoComplete)
|
|
{
|
|
*aDisableAutoComplete = mDisableAutoComplete;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::SetDisableAutoComplete(bool aDisableAutoComplete)
|
|
{
|
|
mDisableAutoComplete = aDisableAutoComplete;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetCompleteDefaultIndex(bool *aCompleteDefaultIndex)
|
|
{
|
|
*aCompleteDefaultIndex = mCompleteDefaultIndex;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::SetCompleteDefaultIndex(bool aCompleteDefaultIndex)
|
|
{
|
|
mCompleteDefaultIndex = aCompleteDefaultIndex;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetCompleteSelectedIndex(bool *aCompleteSelectedIndex)
|
|
{
|
|
*aCompleteSelectedIndex = mCompleteSelectedIndex;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::SetCompleteSelectedIndex(bool aCompleteSelectedIndex)
|
|
{
|
|
mCompleteSelectedIndex = aCompleteSelectedIndex;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetForceComplete(bool *aForceComplete)
|
|
{
|
|
*aForceComplete = mForceComplete;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsFormFillController::SetForceComplete(bool aForceComplete)
|
|
{
|
|
mForceComplete = aForceComplete;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetMinResultsForPopup(uint32_t *aMinResultsForPopup)
|
|
{
|
|
*aMinResultsForPopup = mMinResultsForPopup;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsFormFillController::SetMinResultsForPopup(uint32_t aMinResultsForPopup)
|
|
{
|
|
mMinResultsForPopup = aMinResultsForPopup;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetMaxRows(uint32_t *aMaxRows)
|
|
{
|
|
*aMaxRows = mMaxRows;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::SetMaxRows(uint32_t aMaxRows)
|
|
{
|
|
mMaxRows = aMaxRows;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetShowImageColumn(bool *aShowImageColumn)
|
|
{
|
|
*aShowImageColumn = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsFormFillController::SetShowImageColumn(bool aShowImageColumn)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetShowCommentColumn(bool *aShowCommentColumn)
|
|
{
|
|
*aShowCommentColumn = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsFormFillController::SetShowCommentColumn(bool aShowCommentColumn)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetTimeout(uint32_t *aTimeout)
|
|
{
|
|
*aTimeout = mTimeout;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsFormFillController::SetTimeout(uint32_t aTimeout)
|
|
{
|
|
mTimeout = aTimeout;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::SetSearchParam(const nsAString &aSearchParam)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetSearchParam(nsAString &aSearchParam)
|
|
{
|
|
if (!mFocusedInput) {
|
|
NS_WARNING("mFocusedInput is null for some reason! avoiding a crash. should find out why... - ben");
|
|
return NS_ERROR_FAILURE; // XXX why? fix me.
|
|
}
|
|
|
|
mFocusedInput->GetName(aSearchParam);
|
|
if (aSearchParam.IsEmpty()) {
|
|
nsCOMPtr<nsIDOMHTMLElement> element = do_QueryInterface(mFocusedInput);
|
|
element->GetId(aSearchParam);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetSearchCount(uint32_t *aSearchCount)
|
|
{
|
|
*aSearchCount = 1;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetSearchAt(uint32_t index, nsACString & _retval)
|
|
{
|
|
_retval.AssignLiteral("form-history");
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetTextValue(nsAString & aTextValue)
|
|
{
|
|
if (mFocusedInput) {
|
|
nsCOMPtr<nsIDOMHTMLInputElement> input = mFocusedInput;
|
|
input->GetValue(aTextValue);
|
|
} else {
|
|
aTextValue.Truncate();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::SetTextValue(const nsAString & aTextValue)
|
|
{
|
|
nsCOMPtr<nsIDOMNSEditableElement> editable = do_QueryInterface(mFocusedInput);
|
|
if (editable) {
|
|
mSuppressOnInput = true;
|
|
editable->SetUserInput(aTextValue);
|
|
mSuppressOnInput = false;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::SetTextValueWithReason(const nsAString & aTextValue,
|
|
uint16_t aReason)
|
|
{
|
|
return SetTextValue(aTextValue);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetSelectionStart(int32_t *aSelectionStart)
|
|
{
|
|
if (mFocusedInput) {
|
|
nsCOMPtr<nsIDOMHTMLInputElement> input = mFocusedInput;
|
|
input->GetSelectionStart(aSelectionStart);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetSelectionEnd(int32_t *aSelectionEnd)
|
|
{
|
|
if (mFocusedInput) {
|
|
nsCOMPtr<nsIDOMHTMLInputElement> input = mFocusedInput;
|
|
input->GetSelectionEnd(aSelectionEnd);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::SelectTextRange(int32_t aStartIndex, int32_t aEndIndex)
|
|
{
|
|
if (mFocusedInput) {
|
|
nsCOMPtr<nsIDOMHTMLInputElement> input = mFocusedInput;
|
|
input->SetSelectionRange(aStartIndex, aEndIndex, EmptyString());
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::OnSearchBegin()
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::OnSearchComplete()
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::OnTextEntered(nsIDOMEvent* aEvent,
|
|
bool* aPrevent)
|
|
{
|
|
NS_ENSURE_ARG(aPrevent);
|
|
NS_ENSURE_TRUE(mFocusedInput, NS_OK);
|
|
// Fire off a DOMAutoComplete event
|
|
nsCOMPtr<nsIDOMDocument> domDoc;
|
|
nsCOMPtr<nsIDOMElement> element = do_QueryInterface(mFocusedInput);
|
|
element->GetOwnerDocument(getter_AddRefs(domDoc));
|
|
NS_ENSURE_STATE(domDoc);
|
|
|
|
nsCOMPtr<nsIDOMEvent> event;
|
|
domDoc->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event));
|
|
NS_ENSURE_STATE(event);
|
|
|
|
event->InitEvent(NS_LITERAL_STRING("DOMAutoComplete"), true, true);
|
|
|
|
// XXXjst: We mark this event as a trusted event, it's up to the
|
|
// callers of this to ensure that it's only called from trusted
|
|
// code.
|
|
event->SetTrusted(true);
|
|
|
|
nsCOMPtr<EventTarget> targ = do_QueryInterface(mFocusedInput);
|
|
|
|
bool defaultActionEnabled;
|
|
targ->DispatchEvent(event, &defaultActionEnabled);
|
|
*aPrevent = !defaultActionEnabled;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::OnTextReverted(bool *_retval)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetConsumeRollupEvent(bool *aConsumeRollupEvent)
|
|
{
|
|
*aConsumeRollupEvent = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetInPrivateContext(bool *aInPrivateContext)
|
|
{
|
|
if (!mFocusedInput) {
|
|
*aInPrivateContext = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMDocument> inputDoc;
|
|
nsCOMPtr<nsIDOMElement> element = do_QueryInterface(mFocusedInput);
|
|
element->GetOwnerDocument(getter_AddRefs(inputDoc));
|
|
nsCOMPtr<nsIDocument> doc = do_QueryInterface(inputDoc);
|
|
nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
|
|
*aInPrivateContext = loadContext && loadContext->UsePrivateBrowsing();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetNoRollupOnCaretMove(bool *aNoRollupOnCaretMove)
|
|
{
|
|
*aNoRollupOnCaretMove = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetUserContextId(uint32_t* aUserContextId)
|
|
{
|
|
*aUserContextId = nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::GetInvalidatePreviousResult(
|
|
bool* aInvalidatePreviousResult) {
|
|
*aInvalidatePreviousResult = mInvalidatePreviousResult;
|
|
return NS_OK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//// nsIAutoCompleteSearch
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::StartSearch(const nsAString &aSearchString, const nsAString &aSearchParam,
|
|
nsIAutoCompleteResult *aPreviousResult, nsIAutoCompleteObserver *aListener)
|
|
{
|
|
nsresult rv;
|
|
nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(mFocusedInputNode);
|
|
|
|
// If the login manager has indicated it's responsible for this field, let it
|
|
// handle the autocomplete. Otherwise, handle with form history.
|
|
// This method is sometimes called in unit tests and from XUL without a focused node.
|
|
if (mFocusedInputNode && (mPwmgrInputs.Get(mFocusedInputNode) ||
|
|
formControl->GetType() == NS_FORM_INPUT_PASSWORD)) {
|
|
|
|
// Handle the case where a password field is focused but
|
|
// MarkAsLoginManagerField wasn't called because password manager is disabled.
|
|
if (!mLoginManager) {
|
|
mLoginManager = do_GetService("@mozilla.org/login-manager;1");
|
|
}
|
|
|
|
if (NS_WARN_IF(!mLoginManager)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// XXX aPreviousResult shouldn't ever be a historyResult type, since we're not letting
|
|
// satchel manage the field?
|
|
mLastListener = aListener;
|
|
rv = mLoginManager->AutoCompleteSearchAsync(aSearchString,
|
|
aPreviousResult,
|
|
mFocusedInput,
|
|
this);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
} else {
|
|
mLastListener = aListener;
|
|
|
|
nsCOMPtr<nsIAutoCompleteResult> datalistResult;
|
|
if (mFocusedInput) {
|
|
rv = PerformInputListAutoComplete(aSearchString,
|
|
getter_AddRefs(datalistResult));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
nsCOMPtr <nsIFormAutoComplete> formAutoComplete =
|
|
do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
formAutoComplete->AutoCompleteSearchAsync(aSearchParam,
|
|
aSearchString,
|
|
mFocusedInput,
|
|
aPreviousResult,
|
|
datalistResult,
|
|
this);
|
|
mLastFormAutoComplete = formAutoComplete;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsFormFillController::PerformInputListAutoComplete(const nsAString& aSearch,
|
|
nsIAutoCompleteResult** aResult)
|
|
{
|
|
// If an <input> is focused, check if it has a list="<datalist>" which can
|
|
// provide the list of suggestions.
|
|
|
|
MOZ_ASSERT(!mPwmgrInputs.Get(mFocusedInputNode));
|
|
nsresult rv;
|
|
|
|
nsCOMPtr <nsIInputListAutoComplete> inputListAutoComplete =
|
|
do_GetService("@mozilla.org/satchel/inputlist-autocomplete;1", &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
rv = inputListAutoComplete->AutoCompleteSearch(aSearch,
|
|
mFocusedInput,
|
|
aResult);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (mFocusedInput) {
|
|
nsCOMPtr<nsIDOMHTMLElement> list;
|
|
mFocusedInput->GetList(getter_AddRefs(list));
|
|
|
|
// Add a mutation observer to check for changes to the items in the <datalist>
|
|
// and update the suggestions accordingly.
|
|
nsCOMPtr<nsINode> node = do_QueryInterface(list);
|
|
if (mListNode != node) {
|
|
if (mListNode) {
|
|
mListNode->RemoveMutationObserver(this);
|
|
mListNode = nullptr;
|
|
}
|
|
if (node) {
|
|
node->AddMutationObserverUnlessExists(this);
|
|
mListNode = node;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
class UpdateSearchResultRunnable : public mozilla::Runnable
|
|
{
|
|
public:
|
|
UpdateSearchResultRunnable(nsIAutoCompleteObserver* aObserver,
|
|
nsIAutoCompleteSearch* aSearch,
|
|
nsIAutoCompleteResult* aResult)
|
|
: mObserver(aObserver)
|
|
, mSearch(aSearch)
|
|
, mResult(aResult)
|
|
{
|
|
MOZ_ASSERT(mResult, "Should have a valid result");
|
|
MOZ_ASSERT(mObserver, "You shouldn't call this runnable with a null observer!");
|
|
}
|
|
|
|
NS_IMETHOD Run() override {
|
|
mObserver->OnUpdateSearchResult(mSearch, mResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
nsCOMPtr<nsIAutoCompleteObserver> mObserver;
|
|
nsCOMPtr<nsIAutoCompleteSearch> mSearch;
|
|
nsCOMPtr<nsIAutoCompleteResult> mResult;
|
|
};
|
|
|
|
void nsFormFillController::RevalidateDataList()
|
|
{
|
|
if (!mLastListener) {
|
|
return;
|
|
}
|
|
|
|
if (XRE_IsContentProcess()) {
|
|
nsCOMPtr<nsIAutoCompleteController> controller(do_QueryInterface(mLastListener));
|
|
if (!controller) {
|
|
return;
|
|
}
|
|
|
|
// We cannot use previous result since any items in search target are updated.
|
|
mInvalidatePreviousResult = true;
|
|
controller->StartSearch(mLastSearchString);
|
|
return;
|
|
}
|
|
|
|
nsresult rv;
|
|
nsCOMPtr <nsIInputListAutoComplete> inputListAutoComplete =
|
|
do_GetService("@mozilla.org/satchel/inputlist-autocomplete;1", &rv);
|
|
|
|
nsCOMPtr<nsIAutoCompleteResult> result;
|
|
|
|
// We cannot use previous result since any items in search target are updated.
|
|
mInvalidatePreviousResult = true;
|
|
rv = inputListAutoComplete->AutoCompleteSearch(mLastSearchString,
|
|
mFocusedInput,
|
|
getter_AddRefs(result));
|
|
|
|
nsCOMPtr<nsIRunnable> event =
|
|
new UpdateSearchResultRunnable(mLastListener, this, result);
|
|
NS_DispatchToCurrentThread(event);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::StopSearch()
|
|
{
|
|
// Make sure to stop and clear this, otherwise the controller will prevent
|
|
// mLastFormAutoComplete from being deleted.
|
|
if (mLastFormAutoComplete) {
|
|
mLastFormAutoComplete->StopAutoCompleteSearch();
|
|
mLastFormAutoComplete = nullptr;
|
|
} else if (mLoginManager) {
|
|
mLoginManager->StopSearch();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//// nsIFormAutoCompleteObserver
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::OnSearchCompletion(nsIAutoCompleteResult *aResult)
|
|
{
|
|
nsAutoString searchString;
|
|
aResult->GetSearchString(searchString);
|
|
|
|
mLastSearchString = searchString;
|
|
|
|
if (mLastListener) {
|
|
mLastListener->OnSearchResult(this, aResult);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//// nsIDOMEventListener
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::HandleEvent(nsIDOMEvent* aEvent)
|
|
{
|
|
mInvalidatePreviousResult = false;
|
|
|
|
nsAutoString type;
|
|
aEvent->GetType(type);
|
|
|
|
if (type.EqualsLiteral("focus")) {
|
|
return Focus(aEvent);
|
|
}
|
|
if (type.EqualsLiteral("mousedown")) {
|
|
return MouseDown(aEvent);
|
|
}
|
|
if (type.EqualsLiteral("keypress")) {
|
|
return KeyPress(aEvent);
|
|
}
|
|
if (type.EqualsLiteral("input")) {
|
|
bool unused = false;
|
|
return (!mSuppressOnInput && mController && mFocusedInput) ?
|
|
mController->HandleText(&unused) : NS_OK;
|
|
}
|
|
if (type.EqualsLiteral("blur")) {
|
|
if (mFocusedInput)
|
|
StopControllingInput();
|
|
return NS_OK;
|
|
}
|
|
if (type.EqualsLiteral("compositionstart")) {
|
|
NS_ASSERTION(mController, "should have a controller!");
|
|
if (mController && mFocusedInput)
|
|
mController->HandleStartComposition();
|
|
return NS_OK;
|
|
}
|
|
if (type.EqualsLiteral("compositionend")) {
|
|
NS_ASSERTION(mController, "should have a controller!");
|
|
if (mController && mFocusedInput)
|
|
mController->HandleEndComposition();
|
|
return NS_OK;
|
|
}
|
|
if (type.EqualsLiteral("contextmenu")) {
|
|
mContextMenuFiredBeforeFocus = true;
|
|
if (mFocusedPopup)
|
|
mFocusedPopup->ClosePopup();
|
|
return NS_OK;
|
|
}
|
|
if (type.EqualsLiteral("pagehide")) {
|
|
|
|
nsCOMPtr<nsIDocument> doc = do_QueryInterface(
|
|
aEvent->InternalDOMEvent()->GetTarget());
|
|
if (!doc)
|
|
return NS_OK;
|
|
|
|
if (mFocusedInput) {
|
|
if (doc == mFocusedInputNode->OwnerDoc())
|
|
StopControllingInput();
|
|
}
|
|
|
|
RemoveForDocument(doc);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsFormFillController::RemoveForDocument(nsIDocument* aDoc)
|
|
{
|
|
for (auto iter = mPwmgrInputs.Iter(); !iter.Done(); iter.Next()) {
|
|
const nsINode* key = iter.Key();
|
|
if (key && (!aDoc || key->OwnerDoc() == aDoc)) {
|
|
// mFocusedInputNode's observer is tracked separately, so don't remove it
|
|
// here.
|
|
if (key != mFocusedInputNode) {
|
|
const_cast<nsINode*>(key)->RemoveMutationObserver(this);
|
|
}
|
|
iter.Remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
nsFormFillController::MaybeStartControllingInput(nsIDOMHTMLInputElement* aInput)
|
|
{
|
|
nsCOMPtr<nsINode> inputNode = do_QueryInterface(aInput);
|
|
if (!inputNode)
|
|
return;
|
|
|
|
nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(aInput);
|
|
if (!formControl || !formControl->IsSingleLineTextControl(false))
|
|
return;
|
|
|
|
bool isReadOnly = false;
|
|
aInput->GetReadOnly(&isReadOnly);
|
|
if (isReadOnly)
|
|
return;
|
|
|
|
bool autocomplete = nsContentUtils::IsAutocompleteEnabled(aInput);
|
|
|
|
nsCOMPtr<nsIDOMHTMLElement> datalist;
|
|
aInput->GetList(getter_AddRefs(datalist));
|
|
bool hasList = datalist != nullptr;
|
|
|
|
bool isPwmgrInput = false;
|
|
if (mPwmgrInputs.Get(inputNode) ||
|
|
formControl->GetType() == NS_FORM_INPUT_PASSWORD) {
|
|
isPwmgrInput = true;
|
|
}
|
|
|
|
if (isPwmgrInput || hasList || autocomplete) {
|
|
StartControllingInput(aInput);
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
nsFormFillController::Focus(nsIDOMEvent* aEvent)
|
|
{
|
|
nsCOMPtr<nsIDOMHTMLInputElement> input = do_QueryInterface(
|
|
aEvent->InternalDOMEvent()->GetTarget());
|
|
MaybeStartControllingInput(input);
|
|
|
|
// Bail if we didn't start controlling the input.
|
|
if (!mFocusedInputNode) {
|
|
mContextMenuFiredBeforeFocus = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(mFocusedInputNode);
|
|
MOZ_ASSERT(formControl);
|
|
|
|
// If this focus doesn't immediately follow a contextmenu event then show
|
|
// the autocomplete popup for all password fields.
|
|
if (!mContextMenuFiredBeforeFocus
|
|
&& formControl->GetType() == NS_FORM_INPUT_PASSWORD) {
|
|
ShowPopup();
|
|
}
|
|
|
|
mContextMenuFiredBeforeFocus = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsFormFillController::KeyPress(nsIDOMEvent* aEvent)
|
|
{
|
|
NS_ASSERTION(mController, "should have a controller!");
|
|
if (!mFocusedInput || !mController)
|
|
return NS_OK;
|
|
|
|
nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
|
|
if (!keyEvent)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
bool cancel = false;
|
|
bool unused = false;
|
|
|
|
uint32_t k;
|
|
keyEvent->GetKeyCode(&k);
|
|
switch (k) {
|
|
case nsIDOMKeyEvent::DOM_VK_DELETE:
|
|
#ifndef XP_MACOSX
|
|
mController->HandleDelete(&cancel);
|
|
break;
|
|
case nsIDOMKeyEvent::DOM_VK_BACK_SPACE:
|
|
mController->HandleText(&unused);
|
|
break;
|
|
#else
|
|
case nsIDOMKeyEvent::DOM_VK_BACK_SPACE:
|
|
{
|
|
bool isShift = false;
|
|
keyEvent->GetShiftKey(&isShift);
|
|
|
|
if (isShift) {
|
|
mController->HandleDelete(&cancel);
|
|
} else {
|
|
mController->HandleText(&unused);
|
|
}
|
|
|
|
break;
|
|
}
|
|
#endif
|
|
case nsIDOMKeyEvent::DOM_VK_PAGE_UP:
|
|
case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN:
|
|
{
|
|
bool isCtrl, isAlt, isMeta;
|
|
keyEvent->GetCtrlKey(&isCtrl);
|
|
keyEvent->GetAltKey(&isAlt);
|
|
keyEvent->GetMetaKey(&isMeta);
|
|
if (isCtrl || isAlt || isMeta)
|
|
break;
|
|
}
|
|
MOZ_FALLTHROUGH;
|
|
case nsIDOMKeyEvent::DOM_VK_UP:
|
|
case nsIDOMKeyEvent::DOM_VK_DOWN:
|
|
case nsIDOMKeyEvent::DOM_VK_LEFT:
|
|
case nsIDOMKeyEvent::DOM_VK_RIGHT:
|
|
{
|
|
// Get the writing-mode of the relevant input element,
|
|
// so that we can remap arrow keys if necessary.
|
|
mozilla::WritingMode wm;
|
|
if (mFocusedInputNode && mFocusedInputNode->IsElement()) {
|
|
mozilla::dom::Element *elem = mFocusedInputNode->AsElement();
|
|
nsIFrame *frame = elem->GetPrimaryFrame();
|
|
if (frame) {
|
|
wm = frame->GetWritingMode();
|
|
}
|
|
}
|
|
if (wm.IsVertical()) {
|
|
switch (k) {
|
|
case nsIDOMKeyEvent::DOM_VK_LEFT:
|
|
k = wm.IsVerticalLR() ? nsIDOMKeyEvent::DOM_VK_UP
|
|
: nsIDOMKeyEvent::DOM_VK_DOWN;
|
|
break;
|
|
case nsIDOMKeyEvent::DOM_VK_RIGHT:
|
|
k = wm.IsVerticalLR() ? nsIDOMKeyEvent::DOM_VK_DOWN
|
|
: nsIDOMKeyEvent::DOM_VK_UP;
|
|
break;
|
|
case nsIDOMKeyEvent::DOM_VK_UP:
|
|
k = nsIDOMKeyEvent::DOM_VK_LEFT;
|
|
break;
|
|
case nsIDOMKeyEvent::DOM_VK_DOWN:
|
|
k = nsIDOMKeyEvent::DOM_VK_RIGHT;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
mController->HandleKeyNavigation(k, &cancel);
|
|
break;
|
|
case nsIDOMKeyEvent::DOM_VK_ESCAPE:
|
|
mController->HandleEscape(&cancel);
|
|
break;
|
|
case nsIDOMKeyEvent::DOM_VK_TAB:
|
|
mController->HandleTab();
|
|
cancel = false;
|
|
break;
|
|
case nsIDOMKeyEvent::DOM_VK_RETURN:
|
|
mController->HandleEnter(false, aEvent, &cancel);
|
|
break;
|
|
}
|
|
|
|
if (cancel) {
|
|
aEvent->PreventDefault();
|
|
// Don't let the page see the RETURN event when the popup is open
|
|
// (indicated by cancel=true) so sites don't manually submit forms
|
|
// (e.g. via submit.click()) without the autocompleted value being filled.
|
|
// Bug 286933 will fix this for other key events.
|
|
if (k == nsIDOMKeyEvent::DOM_VK_RETURN) {
|
|
aEvent->StopPropagation();
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsFormFillController::MouseDown(nsIDOMEvent* aEvent)
|
|
{
|
|
nsCOMPtr<nsIDOMMouseEvent> mouseEvent(do_QueryInterface(aEvent));
|
|
if (!mouseEvent)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
nsCOMPtr<nsIDOMHTMLInputElement> targetInput = do_QueryInterface(
|
|
aEvent->InternalDOMEvent()->GetTarget());
|
|
if (!targetInput)
|
|
return NS_OK;
|
|
|
|
int16_t button;
|
|
mouseEvent->GetButton(&button);
|
|
if (button != 0)
|
|
return NS_OK;
|
|
|
|
return ShowPopup();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFormFillController::ShowPopup()
|
|
{
|
|
bool isOpen = false;
|
|
GetPopupOpen(&isOpen);
|
|
if (isOpen) {
|
|
return SetPopupOpen(false);
|
|
}
|
|
|
|
nsCOMPtr<nsIAutoCompleteInput> input;
|
|
mController->GetInput(getter_AddRefs(input));
|
|
if (!input)
|
|
return NS_OK;
|
|
|
|
nsAutoString value;
|
|
input->GetTextValue(value);
|
|
if (value.Length() > 0) {
|
|
// Show the popup with a filtered result set
|
|
mController->SetSearchString(EmptyString());
|
|
bool unused = false;
|
|
mController->HandleText(&unused);
|
|
} else {
|
|
// Show the popup with the complete result set. Can't use HandleText()
|
|
// because it doesn't display the popup if the input is blank.
|
|
bool cancel = false;
|
|
mController->HandleKeyNavigation(nsIDOMKeyEvent::DOM_VK_DOWN, &cancel);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//// nsFormFillController
|
|
|
|
void
|
|
nsFormFillController::AddWindowListeners(nsPIDOMWindowOuter* aWindow)
|
|
{
|
|
if (!aWindow)
|
|
return;
|
|
|
|
EventTarget* target = aWindow->GetChromeEventHandler();
|
|
|
|
if (!target)
|
|
return;
|
|
|
|
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("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)
|
|
{
|
|
if (!aWindow)
|
|
return;
|
|
|
|
StopControllingInput();
|
|
|
|
nsCOMPtr<nsIDocument> 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("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(nsIDOMHTMLInputElement *aInput)
|
|
{
|
|
// Make sure we're not still attached to an input
|
|
StopControllingInput();
|
|
|
|
if (!mController) {
|
|
return;
|
|
}
|
|
|
|
// Find the currently focused docShell
|
|
nsCOMPtr<nsIDocShell> docShell = GetDocShellForInput(aInput);
|
|
int32_t index = GetIndexOfDocShell(docShell);
|
|
if (index < 0)
|
|
return;
|
|
|
|
// Cache the popup for the focused docShell
|
|
mFocusedPopup = mPopups.SafeElementAt(index);
|
|
|
|
nsCOMPtr<nsINode> node = do_QueryInterface(aInput);
|
|
if (!node) {
|
|
return;
|
|
}
|
|
|
|
node->AddMutationObserverUnlessExists(this);
|
|
mFocusedInputNode = node;
|
|
mFocusedInput = aInput;
|
|
|
|
nsCOMPtr<nsIDOMHTMLElement> list;
|
|
mFocusedInput->GetList(getter_AddRefs(list));
|
|
nsCOMPtr<nsINode> listNode = do_QueryInterface(list);
|
|
if (listNode) {
|
|
listNode->AddMutationObserverUnlessExists(this);
|
|
mListNode = listNode;
|
|
}
|
|
|
|
mController->SetInput(this);
|
|
}
|
|
|
|
void
|
|
nsFormFillController::StopControllingInput()
|
|
{
|
|
if (mListNode) {
|
|
mListNode->RemoveMutationObserver(this);
|
|
mListNode = nullptr;
|
|
}
|
|
|
|
if (mController) {
|
|
// Reset the controller's input, but not if it has been switched
|
|
// to another input already, which might happen if the user switches
|
|
// focus by clicking another autocomplete textbox
|
|
nsCOMPtr<nsIAutoCompleteInput> input;
|
|
mController->GetInput(getter_AddRefs(input));
|
|
if (input == this)
|
|
mController->SetInput(nullptr);
|
|
}
|
|
|
|
if (mFocusedInputNode) {
|
|
MaybeRemoveMutationObserver(mFocusedInputNode);
|
|
|
|
nsresult rv;
|
|
nsCOMPtr <nsIFormAutoComplete> formAutoComplete =
|
|
do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv);
|
|
if (formAutoComplete) {
|
|
formAutoComplete->StopControllingInput(mFocusedInput);
|
|
}
|
|
|
|
mFocusedInputNode = nullptr;
|
|
mFocusedInput = nullptr;
|
|
}
|
|
|
|
if (mFocusedPopup) {
|
|
mFocusedPopup->ClosePopup();
|
|
}
|
|
mFocusedPopup = nullptr;
|
|
}
|
|
|
|
nsIDocShell *
|
|
nsFormFillController::GetDocShellForInput(nsIDOMHTMLInputElement *aInput)
|
|
{
|
|
nsCOMPtr<nsINode> node = do_QueryInterface(aInput);
|
|
NS_ENSURE_TRUE(node, nullptr);
|
|
|
|
nsCOMPtr<nsPIDOMWindowOuter> win = node->OwnerDoc()->GetWindow();
|
|
NS_ENSURE_TRUE(win, nullptr);
|
|
|
|
return win->GetDocShell();
|
|
}
|
|
|
|
nsPIDOMWindowOuter*
|
|
nsFormFillController::GetWindowForDocShell(nsIDocShell *aDocShell)
|
|
{
|
|
nsCOMPtr<nsIContentViewer> contentViewer;
|
|
aDocShell->GetContentViewer(getter_AddRefs(contentViewer));
|
|
NS_ENSURE_TRUE(contentViewer, nullptr);
|
|
|
|
nsCOMPtr<nsIDOMDocument> domDoc;
|
|
contentViewer->GetDOMDocument(getter_AddRefs(domDoc));
|
|
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
|
|
NS_ENSURE_TRUE(doc, nullptr);
|
|
|
|
return doc->GetWindow();
|
|
}
|
|
|
|
int32_t
|
|
nsFormFillController::GetIndexOfDocShell(nsIDocShell *aDocShell)
|
|
{
|
|
if (!aDocShell)
|
|
return -1;
|
|
|
|
// Loop through our cached docShells looking for the given docShell
|
|
uint32_t count = mDocShells.Length();
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
if (mDocShells[i] == aDocShell)
|
|
return i;
|
|
}
|
|
|
|
// Recursively check the parent docShell of this one
|
|
nsCOMPtr<nsIDocShellTreeItem> treeItem = do_QueryInterface(aDocShell);
|
|
nsCOMPtr<nsIDocShellTreeItem> parentItem;
|
|
treeItem->GetParent(getter_AddRefs(parentItem));
|
|
if (parentItem) {
|
|
nsCOMPtr<nsIDocShell> parentShell = do_QueryInterface(parentItem);
|
|
return GetIndexOfDocShell(parentShell);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
NS_GENERIC_FACTORY_CONSTRUCTOR(nsFormFillController)
|
|
|
|
NS_DEFINE_NAMED_CID(NS_FORMFILLCONTROLLER_CID);
|
|
|
|
static const mozilla::Module::CIDEntry kSatchelCIDs[] = {
|
|
{ &kNS_FORMFILLCONTROLLER_CID, false, nullptr, nsFormFillControllerConstructor },
|
|
{ nullptr }
|
|
};
|
|
|
|
static const mozilla::Module::ContractIDEntry kSatchelContracts[] = {
|
|
{ "@mozilla.org/satchel/form-fill-controller;1", &kNS_FORMFILLCONTROLLER_CID },
|
|
{ NS_FORMHISTORYAUTOCOMPLETE_CONTRACTID, &kNS_FORMFILLCONTROLLER_CID },
|
|
{ nullptr }
|
|
};
|
|
|
|
static const mozilla::Module kSatchelModule = {
|
|
mozilla::Module::kVersion,
|
|
kSatchelCIDs,
|
|
kSatchelContracts
|
|
};
|
|
|
|
NSMODULE_DEFN(satchel) = &kSatchelModule;
|