diff options
author | rouslan <rouslan@chromium.org> | 2015-04-27 18:00:01 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-04-28 01:00:02 +0000 |
commit | 6a3f8d9afcbb397f562e3a359ffbe39d59d278b9 (patch) | |
tree | a00e18a07a30bf38e399277e81d7a2fb0db40bfb | |
parent | 992492e66abc07139da9b20aaccf405ca8866586 (diff) | |
download | chromium_src-6a3f8d9afcbb397f562e3a359ffbe39d59d278b9.zip chromium_src-6a3f8d9afcbb397f562e3a359ffbe39d59d278b9.tar.gz chromium_src-6a3f8d9afcbb397f562e3a359ffbe39d59d278b9.tar.bz2 |
[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}
7 files changed, 207 insertions, 203 deletions
diff --git a/chrome/renderer/autofill/autofill_renderer_browsertest.cc b/chrome/renderer/autofill/autofill_renderer_browsertest.cc index 0ff5d27..e6f4ec1 100644 --- a/chrome/renderer/autofill/autofill_renderer_browsertest.cc +++ b/chrome/renderer/autofill/autofill_renderer_browsertest.cc @@ -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() diff --git a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc index 0b57f44..7ff1938 100644 --- a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc +++ b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc @@ -297,40 +297,6 @@ class PasswordAutofillAgentTest : public ChromeRenderViewTest { ->textFieldDidEndEditing(input); } - void SimulateInputChangeForElement(const std::string& new_value, - bool move_caret_to_end, - WebFrame* input_frame, - WebInputElement& input, - bool is_user_input) { - input.setValue(WebString::fromUTF8(new_value), is_user_input); - // The field must have focus or AutofillAgent will think the - // change should be ignored. - while (!input.focused()) - input_frame->document().frame()->view()->advanceFocus(false); - if (move_caret_to_end) - input.setSelectionRange(new_value.length(), new_value.length()); - if (is_user_input) { - AutofillMsg_FirstUserGestureObservedInTab msg(0); - content::RenderFrame::FromWebFrame(input_frame)->OnMessageReceived(msg); - - // Also pass the message to the testing object. - if (input_frame == GetMainFrame()) - password_autofill_agent_->FirstUserGestureObserved(); - } - input_frame->toWebLocalFrame()->autofillClient()->textFieldDidChange(input); - // Processing is delayed because of a Blink bug: - // https://bugs.webkit.org/show_bug.cgi?id=16976 - // See PasswordAutofillAgent::TextDidChangeInTextField() for details. - - // Autocomplete will trigger a style recalculation when we put up the next - // frame, but we don't want to wait that long. Instead, trigger a style - // recalcuation manually after TextFieldDidChangeImpl runs. - base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind( - &PasswordAutofillAgentTest::LayoutMainFrame, base::Unretained(this))); - - base::MessageLoop::current()->RunUntilIdle(); - } - void SimulateSuggestionChoice(WebInputElement& username_input) { base::string16 username(base::ASCIIToUTF16(kAliceUsername)); base::string16 password(base::ASCIIToUTF16(kAlicePassword)); @@ -353,26 +319,12 @@ class PasswordAutofillAgentTest : public ChromeRenderViewTest { ->OnMessageReceived(msg); } - void LayoutMainFrame() { - GetMainFrame()->view()->layout(); + void SimulateUsernameChange(const std::string& username) { + SimulateUserInputChangeForElement(&username_element_, username); } - void SimulateUsernameChange(const std::string& username, - bool move_caret_to_end, - bool is_user_input = false) { - SimulateInputChangeForElement(username, - move_caret_to_end, - GetMainFrame(), - username_element_, - is_user_input); - } - - void SimulateKeyDownEvent(const WebInputElement& element, - ui::KeyboardCode key_code) { - blink::WebKeyboardEvent key_event; - key_event.windowsKeyCode = key_code; - static_cast<blink::WebAutofillClient*>(autofill_agent_) - ->textFieldDidReceiveKeyDown(element, key_event); + void SimulatePasswordChange(const std::string& password) { + SimulateUserInputChangeForElement(&password_element_, password); } void CheckTextFieldsStateForElements(const WebInputElement& username_element, @@ -688,7 +640,7 @@ TEST_F(PasswordAutofillAgentTest, PasswordClearOnEdit) { SimulateOnFillPasswordForm(fill_data_); // Simulate the user changing the username to some unknown username. - SimulateUsernameChange("alicia", true); + SimulateUsernameChange("alicia"); // The password should have been cleared. CheckTextFieldsState("alicia", false, std::string(), false); @@ -705,31 +657,31 @@ TEST_F(PasswordAutofillAgentTest, WaitUsername) { CheckTextFieldsState(std::string(), false, std::string(), false); // No autocomplete should happen when text is entered in the username. - SimulateUsernameChange("a", true); + SimulateUsernameChange("a"); CheckTextFieldsState("a", false, std::string(), false); - SimulateUsernameChange("al", true); + SimulateUsernameChange("al"); CheckTextFieldsState("al", false, std::string(), false); - SimulateUsernameChange(kAliceUsername, true); + SimulateUsernameChange(kAliceUsername); CheckTextFieldsState(kAliceUsername, false, std::string(), false); // Autocomplete should happen only when the username textfield is blurred with // a full match. - username_element_.setValue("a"); + SimulateUsernameChange("a"); static_cast<blink::WebAutofillClient*>(autofill_agent_) ->textFieldDidEndEditing(username_element_); CheckTextFieldsState("a", false, std::string(), false); - username_element_.setValue("al"); + SimulateUsernameChange("al"); static_cast<blink::WebAutofillClient*>(autofill_agent_) ->textFieldDidEndEditing(username_element_); CheckTextFieldsState("al", false, std::string(), false); - username_element_.setValue("alices"); + SimulateUsernameChange("alices"); static_cast<blink::WebAutofillClient*>(autofill_agent_) ->textFieldDidEndEditing(username_element_); CheckTextFieldsState("alices", false, std::string(), false); - username_element_.setValue(ASCIIToUTF16(kAliceUsername)); + SimulateUsernameChange(kAliceUsername); static_cast<blink::WebAutofillClient*>(autofill_agent_) ->textFieldDidEndEditing(username_element_); - CheckTextFieldsState(kAliceUsername, true, kAlicePassword, true); + CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true); } // Tests that inline autocompletion works properly. @@ -741,57 +693,55 @@ TEST_F(PasswordAutofillAgentTest, InlineAutocomplete) { // Simulate the user typing in the first letter of 'alice', a stored // username. - SimulateUsernameChange("a", true); + SimulateUsernameChange("a"); // Both the username and password text fields should reflect selection of the // stored login. - CheckTextFieldsState(kAliceUsername, true, kAlicePassword, true); + CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true); // And the selection should have been set to 'lice', the last 4 letters. CheckUsernameSelection(1, 5); // Now the user types the next letter of the same username, 'l'. - SimulateUsernameChange("al", true); + SimulateUserTypingASCIICharacter('l', true); // Now the fields should have the same value, but the selection should have a // different start value. - CheckTextFieldsState(kAliceUsername, true, kAlicePassword, true); + CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true); CheckUsernameSelection(2, 5); - // Test that deleting does not trigger autocomplete. - SimulateKeyDownEvent(username_element_, ui::VKEY_BACK); - SimulateUsernameChange("alic", true); - CheckTextFieldsState("alic", false, std::string(), false); - CheckUsernameSelection(4, 4); // No selection. - // Reset the last pressed key to something other than backspace. - SimulateKeyDownEvent(username_element_, ui::VKEY_A); + // Test that backspace will erase the selection and will stop autocompletion. + SimulateUserTypingASCIICharacter(ui::VKEY_BACK, true); + CheckTextFieldsState("al", false, std::string(), false); + CheckUsernameSelection(2, 2); // No selection. // Now lets say the user goes astray from the stored username and types the // letter 'f', spelling 'alf'. We don't know alf (that's just sad), so in // practice the username should no longer be 'alice' and the selected range // should be empty. - SimulateUsernameChange("alf", true); + SimulateUserTypingASCIICharacter('f', true); CheckTextFieldsState("alf", false, std::string(), false); CheckUsernameSelection(3, 3); // No selection. // Ok, so now the user removes all the text and enters the letter 'b'. - SimulateUsernameChange("b", true); + SimulateUsernameChange("b"); // The username and password fields should match the 'bob' entry. - CheckTextFieldsState(kBobUsername, true, kBobPassword, true); + CheckTextFieldsDOMState(kBobUsername, true, kBobPassword, true); CheckUsernameSelection(1, 3); // Then, the user again removes all the text and types an uppercase 'C'. - SimulateUsernameChange("C", true); + SimulateUsernameChange("C"); // The username and password fields should match the 'Carol' entry. - CheckTextFieldsState(kCarolUsername, true, kCarolPassword, true); + CheckTextFieldsDOMState(kCarolUsername, true, kCarolPassword, true); CheckUsernameSelection(1, 5); + // The user removes all the text and types a lowercase 'c'. We only // want case-sensitive autocompletion, so the username and the selected range // should be empty. - SimulateUsernameChange("c", true); + SimulateUsernameChange("c"); CheckTextFieldsState("c", false, std::string(), false); CheckUsernameSelection(1, 1); // Check that we complete other_possible_usernames as well. - SimulateUsernameChange("R", true); - CheckTextFieldsState(kCarolAlternateUsername, true, kCarolPassword, true); + SimulateUsernameChange("R"); + CheckTextFieldsDOMState(kCarolAlternateUsername, true, kCarolPassword, true); CheckUsernameSelection(1, 17); } @@ -920,8 +870,9 @@ TEST_F(PasswordAutofillAgentTest, IframeNoFillTest) { // Simulate the user typing in the username in the iframe which should cause // an autofill. - SimulateInputChangeForElement( - kAliceUsername, true, iframe, username_input, true); + content::RenderFrame::FromWebFrame(iframe) + ->OnMessageReceived(AutofillMsg_FirstUserGestureObservedInTab(0)); + SimulateUserInputChangeForElement(&username_input, kAliceUsername); CheckTextFieldsStateForElements(username_input, kAliceUsername, @@ -1029,9 +980,9 @@ TEST_F(PasswordAutofillAgentTest, // set directly. SimulateElementClick(kUsernameName); - // Simulate the user entering her username and selecting the matching autofill - // from the dropdown. - SimulateUsernameChange(kAliceUsername, true, true); + // Simulate the user entering the first letter of her username and selecting + // the matching autofill from the dropdown. + SimulateUsernameChange("a"); SimulateSuggestionChoice(username_element_); // The username and password should now have been autocompleted. @@ -1289,10 +1240,10 @@ TEST_F(PasswordAutofillAgentTest, ClearPreviewWithInlineAutocompletedUsername) { ClearUsernameAndPasswordFields(); // Simulate the user typing in the first letter of 'alice', a stored username. - SimulateUsernameChange("a", true); + SimulateUsernameChange("a"); // Both the username and password text fields should reflect selection of the // stored login. - CheckTextFieldsState(kAliceUsername, true, kAlicePassword, true); + CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true); // The selection should have been set to 'lice', the last 4 letters. CheckUsernameSelection(1, 5); @@ -1314,7 +1265,7 @@ TEST_F(PasswordAutofillAgentTest, ClearPreviewWithInlineAutocompletedUsername) { EXPECT_EQ(kAliceUsername, username_element_.value().utf8()); EXPECT_TRUE(username_element_.suggestedValue().isEmpty()); EXPECT_TRUE(username_element_.isAutofilled()); - EXPECT_TRUE(password_element_.value().isEmpty()); + EXPECT_EQ(kAlicePassword, password_element_.value().utf8()); EXPECT_TRUE(password_element_.suggestedValue().isEmpty()); EXPECT_TRUE(password_element_.isAutofilled()); CheckUsernameSelection(1, 5); @@ -1414,17 +1365,18 @@ TEST_F(PasswordAutofillAgentTest, CredentialsOnClick) { // Now simulate a user typing in an unrecognized username and then // clicking on the username element. This should also produce a message with // all the usernames. - SimulateUsernameChange("baz", true); + SimulateUsernameChange("baz"); render_thread_->sink().ClearMessages(); static_cast<PageClickListener*>(autofill_agent_) ->FormControlElementClicked(username_element_, true); CheckSuggestions("baz", true); + ClearUsernameAndPasswordFields(); // Now simulate a user typing in the first letter of the username and then // clicking on the username element. While the typing of the first letter will // inline autocomplete, clicking on the element should still produce a full // suggestion list. - SimulateUsernameChange("a", true); + SimulateUsernameChange("a"); render_thread_->sink().ClearMessages(); static_cast<PageClickListener*>(autofill_agent_) ->FormControlElementClicked(username_element_, true); @@ -1520,10 +1472,8 @@ TEST_F(PasswordAutofillAgentTest, NoCredentialsOnPasswordClick) { // typed by the user. TEST_F(PasswordAutofillAgentTest, RememberLastNonEmptyUsernameAndPasswordOnSubmit_ScriptCleared) { - SimulateInputChangeForElement( - "temp", true, GetMainFrame(), username_element_, true); - SimulateInputChangeForElement( - "random", true, GetMainFrame(), password_element_, true); + SimulateUsernameChange("temp"); + SimulatePasswordChange("random"); // Simulate that the username and the password value was cleared by the // site's JavaScript before submit. @@ -1543,16 +1493,12 @@ TEST_F(PasswordAutofillAgentTest, // remembered. TEST_F(PasswordAutofillAgentTest, RememberLastNonEmptyUsernameAndPasswordOnSubmit_UserCleared) { - SimulateInputChangeForElement( - "temp", true, GetMainFrame(), username_element_, true); - SimulateInputChangeForElement( - "random", true, GetMainFrame(), password_element_, true); + SimulateUsernameChange("temp"); + SimulatePasswordChange("random"); // Simulate that the user actually cleared the username and password again. - SimulateInputChangeForElement("", true, GetMainFrame(), username_element_, - true); - SimulateInputChangeForElement( - "", true, GetMainFrame(), password_element_, true); + SimulateUsernameChange(""); + SimulatePasswordChange(""); static_cast<content::RenderFrameObserver*>(password_autofill_agent_) ->WillSubmitForm(username_element_.form()); @@ -1574,10 +1520,8 @@ TEST_F(PasswordAutofillAgentTest, LoadHTML(kNewPasswordFormHTML); UpdateUsernameAndPasswordElements(); - SimulateInputChangeForElement( - "temp", true, GetMainFrame(), username_element_, true); - SimulateInputChangeForElement( - "random", true, GetMainFrame(), password_element_, true); + SimulateUsernameChange("temp"); + SimulatePasswordChange("random"); // Simulate that the username and the password value was cleared by // the site's JavaScript before submit. @@ -1602,22 +1546,14 @@ TEST_F(PasswordAutofillAgentTest, fill_data_.wait_for_username = true; SimulateOnFillPasswordForm(fill_data_); // Simulate that the user typed her name to make the autofill work. - SimulateInputChangeForElement(kAliceUsername, - /*move_caret_to_end=*/true, - GetMainFrame(), - username_element_, - /*is_user_input=*/true); + SimulateUsernameChange(kAliceUsername); SimulateDidEndEditing(GetMainFrame(), username_element_); const std::string old_username(username_element_.value().utf8()); const std::string old_password(password_element_.value().utf8()); const std::string new_password(old_password + "modify"); // The user changes the password. - SimulateInputChangeForElement(new_password, - /*move_caret_to_end=*/true, - GetMainFrame(), - password_element_, - /*is_user_input=*/true); + SimulatePasswordChange(new_password); // The user switches back into the username field, but leaves that without // changes. @@ -1637,14 +1573,10 @@ TEST_F(PasswordAutofillAgentTest, ClearUsernameAndPasswordFields(); // The user enters a password - SimulateInputChangeForElement("someOtherPassword", - /*move_caret_to_end=*/true, - GetMainFrame(), - password_element_, - /*is_user_input=*/true); + SimulatePasswordChange("someOtherPassword"); // Simulate the user typing a stored username. - SimulateUsernameChange(kAliceUsername, true); + SimulateUsernameChange(kAliceUsername); // The autofileld password should replace the typed one. CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true); } @@ -1655,10 +1587,8 @@ TEST_F(PasswordAutofillAgentTest, // typed by the user. TEST_F(PasswordAutofillAgentTest, RememberLastTypedUsernameAndPasswordOnSubmit_ScriptChanged) { - SimulateInputChangeForElement("temp", true, GetMainFrame(), username_element_, - true); - SimulateInputChangeForElement("random", true, GetMainFrame(), - password_element_, true); + SimulateUsernameChange("temp"); + SimulatePasswordChange("random"); // Simulate that the username and the password value was changed by the // site's JavaScript before submit. @@ -1705,10 +1635,8 @@ TEST_F( RememberLastTypedAfterAutofilledUsernameAndPasswordOnSubmit_ScriptChanged) { SimulateOnFillPasswordForm(fill_data_); - SimulateInputChangeForElement("temp", true, GetMainFrame(), username_element_, - true); - SimulateInputChangeForElement("random", true, GetMainFrame(), - password_element_, true); + SimulateUsernameChange("temp"); + SimulatePasswordChange("random"); // Simulate that the username and the password value was changed by the // site's JavaScript before submit. @@ -1728,12 +1656,10 @@ TEST_F( // PasswordAutofillAgent should remember the username that was autofilled, // not last typed. TEST_F(PasswordAutofillAgentTest, RememberAutofilledUsername) { - SimulateInputChangeForElement("Te", true, GetMainFrame(), username_element_, - true); + SimulateUsernameChange("Te"); // Simulate that the username was changed by autofilling. username_element_.setValue(WebString("temp")); - SimulateInputChangeForElement("random", true, GetMainFrame(), - password_element_, true); + SimulatePasswordChange("random"); static_cast<content::RenderFrameObserver*>(password_autofill_agent_) ->WillSendSubmitEvent(username_element_.form()); @@ -1878,12 +1804,9 @@ TEST_F(PasswordAutofillAgentTest, FindingUsernameWithoutAutofillPredictions) { LoadHTML(kFormHTMLWithTwoTextFields); UpdateUsernameAndPasswordElements(); blink::WebInputElement email_element = GetInputElementByID(kEmailName); - SimulateInputChangeForElement("temp", true, GetMainFrame(), username_element_, - true); - SimulateInputChangeForElement("temp@google.com", true, GetMainFrame(), - email_element, true); - SimulateInputChangeForElement("random", true, GetMainFrame(), - password_element_, true); + SimulateUsernameChange("temp"); + SimulateUserInputChangeForElement(&email_element, "temp@google.com"); + SimulatePasswordChange("random"); static_cast<content::RenderFrameObserver*>(password_autofill_agent_) ->WillSendSubmitEvent(username_element_.form()); static_cast<content::RenderFrameObserver*>(password_autofill_agent_) @@ -1900,13 +1823,9 @@ TEST_F(PasswordAutofillAgentTest, FindingUsernameWithAutofillPredictions) { LoadHTML(kFormHTMLWithTwoTextFields); UpdateUsernameAndPasswordElements(); blink::WebInputElement email_element = GetInputElementByID(kEmailName); - SimulateInputChangeForElement("temp", true, GetMainFrame(), username_element_, - true); - SimulateInputChangeForElement("temp@google.com", true, GetMainFrame(), - email_element, true); - SimulateInputChangeForElement("random", true, GetMainFrame(), - password_element_, true); - + SimulateUsernameChange("temp"); + SimulateUserInputChangeForElement(&email_element, "temp@google.com"); + SimulatePasswordChange("random"); // Find FormData for visible password form. blink::WebFormElement form_element = username_element_.form(); FormData form_data; diff --git a/chrome/renderer/autofill/password_generation_agent_browsertest.cc b/chrome/renderer/autofill/password_generation_agent_browsertest.cc index 3d0a401..801f0d2 100644 --- a/chrome/renderer/autofill/password_generation_agent_browsertest.cc +++ b/chrome/renderer/autofill/password_generation_agent_browsertest.cc @@ -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, diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc index 3106833..16099c0 100644 --- a/components/autofill/content/renderer/autofill_agent.cc +++ b/components/autofill/content/renderer/autofill_agent.cc @@ -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())); } diff --git a/components/autofill/content/renderer/autofill_agent.h b/components/autofill/content/renderer/autofill_agent.h index 4ec475c..39d6573 100644 --- a/components/autofill/content/renderer/autofill_agent.h +++ b/components/autofill/content/renderer/autofill_agent.h @@ -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. diff --git a/content/public/test/render_view_test.cc b/content/public/test/render_view_test.cc index 1e1a048..626b887 100644 --- a/content/public/test/render_view_test.cc +++ b/content/public/test/render_view_test.cc @@ -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); diff --git a/content/public/test/render_view_test.h b/content/public/test/render_view_test.h index d3fd776..91d0df6 100644 --- a/content/public/test/render_view_test.h +++ b/content/public/test/render_view_test.h @@ -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, |