Bug 1337259 - Don't show password autocomplete upon a right click into the password field. r=mconley
MozReview-Commit-ID: JEODCfZOMRW
--- a/toolkit/components/passwordmgr/test/browser/browser_context_menu_autocomplete_interaction.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_context_menu_autocomplete_interaction.js
@@ -2,18 +2,16 @@
* Test the password manager context menu interaction with autocomplete.
*/
"use strict";
const TEST_HOSTNAME = "https://example.com";
const BASIC_FORM_PAGE_PATH = DIRECTORY_PATH + "form_basic.html";
-var gUnexpectedIsTODO = false;
-
/**
* Initialize logins needed for the tests and disable autofill
* for login forms for easier testing of manual fill.
*/
add_task(function* test_initialize() {
let autocompletePopup = document.getElementById("PopupAutoComplete");
Services.prefs.setBoolPref("signon.autofillForms", false);
registerCleanupFunction(() => {
@@ -35,35 +33,30 @@ add_task(function* test_context_menu_use
let contextMenu = document.getElementById("contentAreaContextMenu");
Assert.equal(contextMenu.state, "open", "Context menu opened");
contextMenu.hidePopup();
});
});
add_task(function* test_context_menu_password() {
- gUnexpectedIsTODO = true;
yield BrowserTestUtils.withNewTab({
gBrowser,
url: TEST_HOSTNAME + BASIC_FORM_PAGE_PATH,
}, function* (browser) {
yield openContextMenu(browser, "#form-basic-password");
let contextMenu = document.getElementById("contentAreaContextMenu");
Assert.equal(contextMenu.state, "open", "Context menu opened");
contextMenu.hidePopup();
});
});
function autocompleteUnexpectedPopupShowing(event) {
- if (gUnexpectedIsTODO) {
- todo(false, "Autocomplete shouldn't appear");
- } else {
- Assert.ok(false, "Autocomplete shouldn't appear");
- }
+ Assert.ok(false, "Autocomplete shouldn't appear");
event.target.hidePopup();
}
/**
* Synthesize mouse clicks to open the context menu popup
* for a target login input element.
*/
function* openContextMenu(browser, loginInput) {
--- a/toolkit/components/satchel/nsFormFillController.cpp
+++ b/toolkit/components/satchel/nsFormFillController.cpp
@@ -19,34 +19,35 @@
#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"
+#include "nsThreadUtils.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)
@@ -62,20 +63,23 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFormFi
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFormFillController)
nsFormFillController::nsFormFillController() :
mFocusedInput(nullptr),
mFocusedInputNode(nullptr),
mListNode(nullptr),
+ // This matches the threshold in
+ // toolkit/components/passwordmgr/LoginManagerContent.jsm.
+ mFocusAfterContextMenuThreshold(250),
mTimeout(50),
mMinResultsForPopup(1),
mMaxRows(0),
- mContextMenuFiredBeforeFocus(false),
+ mLastContextMenuEventTimeStamp(TimeStamp::Now()),
mDisableAutoComplete(false),
mCompleteDefaultIndex(false),
mCompleteSelectedIndex(false),
mForceComplete(false),
mSuppressOnInput(false)
{
mController = do_GetService("@mozilla.org/autocomplete/controller;1");
MOZ_ASSERT(mController);
@@ -915,17 +919,19 @@ nsFormFillController::HandleEvent(nsIDOM
}
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;
+ // Set timestamp to check for a recent contextmenu
+ // call in Focus(), to avoid showing the popup.
+ mLastContextMenuEventTimeStamp = TimeStamp::Now();
if (mFocusedPopup)
mFocusedPopup->ClosePopup();
return NS_OK;
}
if (type.EqualsLiteral("pagehide")) {
nsCOMPtr<nsIDocument> doc = do_QueryInterface(
aEvent->InternalDOMEvent()->GetTarget());
@@ -999,42 +1005,57 @@ nsFormFillController::MaybeStartControll
isPwmgrInput = true;
}
if (isPwmgrInput || hasList || autocomplete) {
StartControllingInput(aInput);
}
}
+void
+nsFormFillController::FocusEventDelayedCallback(nsIFormControl* aFormControl)
+{
+ nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(mFocusedInputNode);
+
+ if (!formControl || formControl != aFormControl) {
+ return;
+ }
+
+ uint64_t timeDiff = fabs((TimeStamp::Now() - mLastContextMenuEventTimeStamp).ToMilliseconds());
+ // If this focus doesn't follow a contextmenu event 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. The threshold should be a low amount of time that
+ // makes it impossible for the user to accidentally trigger this condition.
+ if (timeDiff > mFocusAfterContextMenuThreshold
+ && formControl->GetType() == NS_FORM_INPUT_PASSWORD) {
+ ShowPopup();
+ }
+}
+
nsresult
nsFormFillController::Focus(nsIDOMEvent* aEvent)
{
nsCOMPtr<nsIDOMHTMLInputElement> input = do_QueryInterface(
aEvent->InternalDOMEvent()->GetTarget());
MaybeStartControllingInput(input);
// Bail if we didn't start controlling the input.
if (!mFocusedInputNode) {
- mContextMenuFiredBeforeFocus = false;
return NS_OK;
}
#ifndef ANDROID
nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(mFocusedInputNode);
MOZ_ASSERT(formControl);
- // If this focus doesn't immediately follow a contextmenu event then show
- // the autocomplete popup for all password fields.
- if (!mContextMenuFiredBeforeFocus
- && formControl->GetType() == NS_FORM_INPUT_PASSWORD) {
- ShowPopup();
- }
+ NS_DispatchToMainThread(NewRunnableMethod<nsCOMPtr<nsIFormControl>>(
+ this, &nsFormFillController::FocusEventDelayedCallback, formControl));
#endif
- mContextMenuFiredBeforeFocus = false;
return NS_OK;
}
nsresult
nsFormFillController::KeyPress(nsIDOMEvent* aEvent)
{
NS_ASSERTION(mController, "should have a controller!");
if (!mFocusedInput || !mController)
--- a/toolkit/components/satchel/nsFormFillController.h
+++ b/toolkit/components/satchel/nsFormFillController.h
@@ -25,16 +25,17 @@
// X.h defines KeyPress
#ifdef KeyPress
#undef KeyPress
#endif
class nsFormHistory;
class nsINode;
class nsPIDOMWindowOuter;
+class nsIFormControl;
class nsFormFillController final : public nsIFormFillController,
public nsIAutoCompleteInput,
public nsIAutoCompleteSearch,
public nsIDOMEventListener,
public nsIFormAutoCompleteObserver,
public nsIMutationObserver
{
@@ -81,16 +82,19 @@ protected:
inline nsIDocShell *GetDocShellForInput(nsIDOMHTMLInputElement *aInput);
inline nsPIDOMWindowOuter *GetWindowForDocShell(nsIDocShell *aDocShell);
inline int32_t GetIndexOfDocShell(nsIDocShell *aDocShell);
void MaybeRemoveMutationObserver(nsINode* aNode);
void RemoveForDocument(nsIDocument* aDoc);
bool IsEventTrusted(nsIDOMEvent *aEvent);
+
+ void FocusEventDelayedCallback(nsIFormControl* formControl);
+
// members //////////////////////////////////////////
nsCOMPtr<nsIAutoCompleteController> mController;
nsCOMPtr<nsILoginManager> mLoginManager;
nsIDOMHTMLInputElement* mFocusedInput;
nsINode* mFocusedInputNode;
// mListNode is a <datalist> element which, is set, has the form fill controller
@@ -107,20 +111,21 @@ protected:
// This is cleared by StopSearch().
nsCOMPtr<nsIFormAutoComplete> mLastFormAutoComplete;
nsString mLastSearchString;
nsDataHashtable<nsPtrHashKey<const nsINode>, bool> mPwmgrInputs;
nsDataHashtable<nsPtrHashKey<const nsINode>, bool> mAutofillInputs;
+ uint16_t mFocusAfterContextMenuThreshold;
uint32_t mTimeout;
uint32_t mMinResultsForPopup;
uint32_t mMaxRows;
- bool mContextMenuFiredBeforeFocus;
+ mozilla::TimeStamp mLastContextMenuEventTimeStamp;
bool mDisableAutoComplete;
bool mCompleteDefaultIndex;
bool mCompleteSelectedIndex;
bool mForceComplete;
bool mSuppressOnInput;
};
#endif // __nsFormFillController__