Commit 6a3f8d9a authored by rouslan's avatar rouslan Committed by Commit bot

[autofill] Allow only a user gesture to trigger autofill.

If a script inserts text into an input field without a user gesture,
then do not show the autofill popup.

TEST=AutofillRendererTest.IgnoreNonUserGestureTextFieldChanges
BUG=353001

Review URL: https://codereview.chromium.org/1026493002

Cr-Commit-Position: refs/heads/master@{#327204}
parent 992492e6
......@@ -22,6 +22,7 @@
#include "third_party/WebKit/public/web/WebFormElement.h"
#include "third_party/WebKit/public/web/WebInputElement.h"
#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "third_party/WebKit/public/web/WebView.h"
using base::ASCIIToUTF16;
using blink::WebDocument;
......@@ -245,6 +246,31 @@ TEST_F(AutofillRendererTest, DynamicallyAddedUnownedFormElements) {
EXPECT_FORM_FIELD_DATA_EQUALS(expected, forms[0].fields[8]);
}
TEST_F(AutofillRendererTest, IgnoreNonUserGestureTextFieldChanges) {
LoadHTML("<form method='post'>"
" <input type='text' id='full_name'/>"
"</form>");
blink::WebInputElement full_name =
GetMainFrame()->document().getElementById("full_name")
.to<blink::WebInputElement>();
while (!full_name.focused())
GetMainFrame()->view()->advanceFocus(false);
// Not a user gesture, so no IPC message to browser.
full_name.setValue("Alice", true);
GetMainFrame()->toWebLocalFrame()->autofillClient()->textFieldDidChange(
full_name);
base::MessageLoop::current()->RunUntilIdle();
ASSERT_EQ(nullptr, render_thread_->sink().GetFirstMessageMatching(
AutofillHostMsg_TextFieldDidChange::ID));
// A user gesture will send a message to the browser.
SimulateUserInputChangeForElement(&full_name, "Alice");
ASSERT_NE(nullptr, render_thread_->sink().GetFirstMessageMatching(
AutofillHostMsg_TextFieldDidChange::ID));
}
class RequestAutocompleteRendererTest : public AutofillRendererTest {
public:
RequestAutocompleteRendererTest()
......
......@@ -19,6 +19,7 @@
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "third_party/WebKit/public/web/WebWidget.h"
#include "ui/events/keycodes/keyboard_codes.h"
using blink::WebDocument;
using blink::WebElement;
......@@ -309,29 +310,18 @@ TEST_F(PasswordGenerationAgentTest, EditingTest) {
EXPECT_EQ(password, second_password_element.value());
// After editing the first field they are still the same.
base::string16 edited_password = base::ASCIIToUTF16("edited_password");
first_password_element.setValue(edited_password);
// Cast to WebAutofillClient where textFieldDidChange() is public.
static_cast<blink::WebAutofillClient*>(autofill_agent_)->textFieldDidChange(
first_password_element);
// textFieldDidChange posts a task, so we need to wait until it's been
// processed.
base::MessageLoop::current()->RunUntilIdle();
std::string edited_password_ascii = "edited_password";
SimulateUserInputChangeForElement(&first_password_element,
edited_password_ascii);
base::string16 edited_password = base::ASCIIToUTF16(edited_password_ascii);
EXPECT_EQ(edited_password, first_password_element.value());
EXPECT_EQ(edited_password, second_password_element.value());
// Verify that password mirroring works correctly even when the password
// is deleted.
base::string16 empty_password;
first_password_element.setValue(empty_password);
// Cast to WebAutofillClient where textFieldDidChange() is public.
static_cast<blink::WebAutofillClient*>(autofill_agent_)->textFieldDidChange(
first_password_element);
// textFieldDidChange posts a task, so we need to wait until it's been
// processed.
base::MessageLoop::current()->RunUntilIdle();
EXPECT_EQ(empty_password, first_password_element.value());
EXPECT_EQ(empty_password, second_password_element.value());
SimulateUserInputChangeForElement(&first_password_element, std::string());
EXPECT_EQ(base::string16(), first_password_element.value());
EXPECT_EQ(base::string16(), second_password_element.value());
}
TEST_F(PasswordGenerationAgentTest, BlacklistedTest) {
......@@ -394,15 +384,9 @@ TEST_F(PasswordGenerationAgentTest, MaximumOfferSize) {
WebInputElement first_password_element = element.to<WebInputElement>();
// Make a password just under maximum offer size.
first_password_element.setValue(
base::ASCIIToUTF16(
std::string(password_generation_->kMaximumOfferSize - 1, 'a')));
// Cast to WebAutofillClient where textFieldDidChange() is public.
static_cast<blink::WebAutofillClient*>(autofill_agent_)->textFieldDidChange(
first_password_element);
// textFieldDidChange posts a task, so we need to wait until it's been
// processed.
base::MessageLoop::current()->RunUntilIdle();
SimulateUserInputChangeForElement(
&first_password_element,
std::string(password_generation_->kMaximumOfferSize - 1, 'a'));
// There should now be a message to show the UI.
ASSERT_EQ(1u, password_generation_->messages().size());
EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID,
......@@ -410,15 +394,8 @@ TEST_F(PasswordGenerationAgentTest, MaximumOfferSize) {
password_generation_->clear_messages();
// Simulate a user typing a password just over maximum offer size.
first_password_element.setValue(
base::ASCIIToUTF16(
std::string(password_generation_->kMaximumOfferSize + 1, 'a')));
// Cast to WebAutofillClient where textFieldDidChange() is public.
static_cast<blink::WebAutofillClient*>(autofill_agent_)->textFieldDidChange(
first_password_element);
// textFieldDidChange posts a task, so we need to wait until it's been
// processed.
base::MessageLoop::current()->RunUntilIdle();
SimulateUserTypingASCIICharacter('a', false);
SimulateUserTypingASCIICharacter('a', true);
// There should now be a message to hide the UI.
ASSERT_EQ(1u, password_generation_->messages().size());
EXPECT_EQ(AutofillHostMsg_HidePasswordGenerationPopup::ID,
......@@ -427,15 +404,7 @@ TEST_F(PasswordGenerationAgentTest, MaximumOfferSize) {
// Simulate the user deleting characters. The generation popup should be shown
// again.
first_password_element.setValue(
base::ASCIIToUTF16(
std::string(password_generation_->kMaximumOfferSize, 'a')));
// Cast to WebAutofillClient where textFieldDidChange() is public.
static_cast<blink::WebAutofillClient*>(autofill_agent_)->textFieldDidChange(
first_password_element);
// textFieldDidChange posts a task, so we need to wait until it's been
// processed.
base::MessageLoop::current()->RunUntilIdle();
SimulateUserTypingASCIICharacter(ui::VKEY_BACK, true);
// There should now be a message to show the UI.
ASSERT_EQ(1u, password_generation_->messages().size());
EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID,
......
......@@ -4,6 +4,7 @@
#include "components/autofill/content/renderer/autofill_agent.h"
#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/message_loop/message_loop.h"
......@@ -43,6 +44,7 @@
#include "third_party/WebKit/public/web/WebNode.h"
#include "third_party/WebKit/public/web/WebOptionElement.h"
#include "third_party/WebKit/public/web/WebTextAreaElement.h"
#include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
#include "third_party/WebKit/public/web/WebView.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/keycodes/keyboard_codes.h"
......@@ -62,6 +64,7 @@ using blink::WebNode;
using blink::WebOptionElement;
using blink::WebString;
using blink::WebTextAreaElement;
using blink::WebUserGestureIndicator;
using blink::WebVector;
namespace autofill {
......@@ -149,7 +152,6 @@ AutofillAgent::AutofillAgent(content::RenderFrame* render_frame,
autofill_query_id_(0),
was_query_node_autofilled_(false),
has_shown_autofill_popup_for_current_edit_(false),
did_set_node_text_(false),
ignore_text_changes_(false),
is_popup_possibly_visible_(false),
weak_ptr_factory_(this) {
......@@ -378,15 +380,12 @@ void AutofillAgent::textFieldDidEndEditing(const WebInputElement& element) {
}
void AutofillAgent::textFieldDidChange(const WebFormControlElement& element) {
DCHECK(toWebInputElement(&element) || IsTextAreaElement(element));
if (ignore_text_changes_)
return;
DCHECK(toWebInputElement(&element) || IsTextAreaElement(element));
if (did_set_node_text_) {
did_set_node_text_ = false;
if (!WebUserGestureIndicator::isProcessingUserGesture())
return;
}
// We post a task for doing the Autofill as the caret position is not set
// properly at this point (http://bugs.webkit.org/show_bug.cgi?id=16976) and
......@@ -714,7 +713,7 @@ void AutofillAgent::QueryAutofillSuggestions(
void AutofillAgent::FillFieldWithValue(const base::string16& value,
WebInputElement* node) {
did_set_node_text_ = true;
base::AutoReset<bool> auto_reset(&ignore_text_changes_, true);
node->setEditingValue(value.substr(0, node->maxLength()));
}
......
......@@ -252,9 +252,6 @@ class AutofillAgent : public content::RenderFrameObserver,
// currently editing? Used to keep track of state for metrics logging.
bool has_shown_autofill_popup_for_current_edit_;
// If true we just set the node text so we shouldn't show the popup.
bool did_set_node_text_;
// Whether or not to ignore text changes. Useful for when we're committing
// a composition when we are defocusing the WebView and we don't want to
// trigger an autofill popup to show.
......
......@@ -4,6 +4,8 @@
#include "content/public/test/render_view_test.h"
#include <cctype>
#include "base/run_loop.h"
#include "components/scheduler/renderer/renderer_scheduler.h"
#include "content/common/dom_storage/dom_storage_types.h"
......@@ -27,13 +29,16 @@
#include "content/test/test_content_client.h"
#include "third_party/WebKit/public/platform/WebScreenInfo.h"
#include "third_party/WebKit/public/platform/WebURLRequest.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebHistoryItem.h"
#include "third_party/WebKit/public/web/WebInputElement.h"
#include "third_party/WebKit/public/web/WebInputEvent.h"
#include "third_party/WebKit/public/web/WebKit.h"
#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "third_party/WebKit/public/web/WebScriptSource.h"
#include "third_party/WebKit/public/web/WebView.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "v8/include/v8.h"
#if defined(OS_MACOSX)
......@@ -49,6 +54,7 @@ using blink::WebString;
using blink::WebURLRequest;
namespace {
const int32 kOpenerId = -2;
const int32 kRouteId = 5;
const int32 kMainFrameRouteId = 6;
......@@ -56,6 +62,32 @@ const int32 kNewWindowRouteId = 7;
const int32 kNewFrameRouteId = 10;
const int32 kSurfaceId = 42;
// Converts |ascii_character| into |key_code| and returns true on success.
// Handles only the characters needed by tests.
bool GetWindowsKeyCode(char ascii_character, int* key_code) {
if (isalnum(ascii_character)) {
*key_code = base::ToUpperASCII(ascii_character);
return true;
}
switch (ascii_character) {
case '@':
*key_code = '2';
return true;
case '_':
*key_code = ui::VKEY_OEM_MINUS;
return true;
case '.':
*key_code = ui::VKEY_OEM_PERIOD;
return true;
case ui::VKEY_BACK:
*key_code = ui::VKEY_BACK;
return true;
default:
return false;
}
}
} // namespace
namespace content {
......@@ -395,6 +427,55 @@ void RenderViewTest::Resize(gfx::Size new_size,
OnMessageReceived(*resize_message);
}
void RenderViewTest::SimulateUserTypingASCIICharacter(char ascii_character,
bool flush_message_loop) {
blink::WebKeyboardEvent event;
event.text[0] = ascii_character;
ASSERT_TRUE(GetWindowsKeyCode(ascii_character, &event.windowsKeyCode));
if (isupper(ascii_character) || ascii_character == '@' ||
ascii_character == '_') {
event.modifiers = blink::WebKeyboardEvent::ShiftKey;
}
event.type = blink::WebKeyboardEvent::RawKeyDown;
SendWebKeyboardEvent(event);
event.type = blink::WebKeyboardEvent::Char;
SendWebKeyboardEvent(event);
event.type = blink::WebKeyboardEvent::KeyUp;
SendWebKeyboardEvent(event);
if (flush_message_loop) {
// Processing is delayed because of a Blink bug:
// https://bugs.webkit.org/show_bug.cgi?id=16976 See
// PasswordAutofillAgent::TextDidChangeInTextField() for details.
base::MessageLoop::current()->RunUntilIdle();
}
}
void RenderViewTest::SimulateUserInputChangeForElement(
blink::WebInputElement* input,
const std::string& new_value) {
ASSERT_TRUE(base::IsStringASCII(new_value));
while (!input->focused())
input->document().frame()->view()->advanceFocus(false);
size_t previous_length = input->value().length();
for (size_t i = 0; i < previous_length; ++i)
SimulateUserTypingASCIICharacter(ui::VKEY_BACK, false);
EXPECT_TRUE(input->value().utf8().empty());
for (size_t i = 0; i < new_value.size(); ++i)
SimulateUserTypingASCIICharacter(new_value[i], false);
// Compare only beginning, because autocomplete may have filled out the
// form.
EXPECT_EQ(new_value, input->value().utf8().substr(0, new_value.length()));
base::MessageLoop::current()->RunUntilIdle();
}
bool RenderViewTest::OnMessageReceived(const IPC::Message& msg) {
RenderViewImpl* impl = static_cast<RenderViewImpl*>(view_);
return impl->OnMessageReceived(msg);
......
......@@ -22,6 +22,7 @@
struct ViewMsg_Resize_Params;
namespace blink {
class WebInputElement;
class WebWidget;
}
......@@ -99,7 +100,7 @@ class RenderViewTest : public testing::Test {
void SendWebKeyboardEvent(const blink::WebKeyboardEvent& key_event);
// Send a raw mouse event to the renderer.
void SendWebMouseEvent(const blink::WebMouseEvent& key_event);
void SendWebMouseEvent(const blink::WebMouseEvent& mouse_event);
// Returns the bounds (coordinates and size) of the element with id
// |element_id|. Returns an empty rect if such an element was not found.
......@@ -130,6 +131,18 @@ class RenderViewTest : public testing::Test {
gfx::Rect resizer_rect,
bool is_fullscreen);
// Simulates typing the |ascii_character| into this render view. Also accepts
// ui::VKEY_BACK for backspace. Will flush the message loop if
// |flush_message_loop| is true.
void SimulateUserTypingASCIICharacter(char ascii_character,
bool flush_message_loop);
// Simulates user focusing |input|, erasing all text, and typing the
// |new_value| instead. Will process input events for autofill. This is a user
// gesture.
void SimulateUserInputChangeForElement(blink::WebInputElement* input,
const std::string& new_value);
// These are all methods from RenderViewImpl that we expose to testing code.
bool OnMessageReceived(const IPC::Message& msg);
void DidNavigateWithinPage(blink::WebLocalFrame* frame,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment