Commit 0b54614d authored by isherman@chromium.org's avatar isherman@chromium.org

[Password Autofill] Trigger onChange events in JavaScript when autofilling passwords.

BUG=307308
TEST=browser_tests
R=gcasto@chromium.org

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@244125 0039d316-1c4b-4281-b951-d872f2087c98
parent dd53a693
...@@ -144,6 +144,18 @@ const char kJavaScriptClick[] = ...@@ -144,6 +144,18 @@ const char kJavaScriptClick[] =
"form.dispatchEvent(event);" "form.dispatchEvent(event);"
"console.log('clicked!');"; "console.log('clicked!');";
const char kOnChangeDetectionScript[] =
"<script>"
" usernameOnchangeCalled = false;"
" passwordOnchangeCalled = false;"
" document.getElementById('username').onchange = function() {"
" usernameOnchangeCalled = true;"
" };"
" document.getElementById('password').onchange = function() {"
" passwordOnchangeCalled = true;"
" };"
"</script>";
} // namespace } // namespace
namespace autofill { namespace autofill {
...@@ -200,14 +212,27 @@ class PasswordAutofillAgentTest : public ChromeRenderViewTest { ...@@ -200,14 +212,27 @@ class PasswordAutofillAgentTest : public ChromeRenderViewTest {
// We need to set the origin so it matches the frame URL and the action so // We need to set the origin so it matches the frame URL and the action so
// it matches the form action, otherwise we won't autocomplete. // it matches the form action, otherwise we won't autocomplete.
std::string origin("data:text/html;charset=utf-8,"); UpdateOriginForHTML(kFormHTML);
origin += kFormHTML;
fill_data_.basic_data.origin = GURL(origin);
fill_data_.basic_data.action = GURL("http://www.bidule.com"); fill_data_.basic_data.action = GURL("http://www.bidule.com");
LoadHTML(kFormHTML); LoadHTML(kFormHTML);
// Now retrieves the input elements so the test can access them. // Now retrieve the input elements so the test can access them.
UpdateUsernameAndPasswordElements();
}
virtual void TearDown() {
username_element_.reset();
password_element_.reset();
ChromeRenderViewTest::TearDown();
}
void UpdateOriginForHTML(const std::string& html) {
std::string origin = "data:text/html;charset=utf-8," + html;
fill_data_.basic_data.origin = GURL(origin);
}
void UpdateUsernameAndPasswordElements() {
WebDocument document = GetMainFrame()->document(); WebDocument document = GetMainFrame()->document();
WebElement element = WebElement element =
document.getElementById(WebString::fromUTF8(kUsernameName)); document.getElementById(WebString::fromUTF8(kUsernameName));
...@@ -218,12 +243,6 @@ class PasswordAutofillAgentTest : public ChromeRenderViewTest { ...@@ -218,12 +243,6 @@ class PasswordAutofillAgentTest : public ChromeRenderViewTest {
password_element_ = element.to<blink::WebInputElement>(); password_element_ = element.to<blink::WebInputElement>();
} }
virtual void TearDown() {
username_element_.reset();
password_element_.reset();
ChromeRenderViewTest::TearDown();
}
void ClearUsernameAndPasswordFields() { void ClearUsernameAndPasswordFields() {
username_element_.setValue(""); username_element_.setValue("");
username_element_.setAutofilled(false); username_element_.setAutofilled(false);
...@@ -402,10 +421,8 @@ TEST_F(PasswordAutofillAgentTest, InitialAutocompleteForEmptyAction) { ...@@ -402,10 +421,8 @@ TEST_F(PasswordAutofillAgentTest, InitialAutocompleteForEmptyAction) {
password_element_ = element.to<blink::WebInputElement>(); password_element_ = element.to<blink::WebInputElement>();
// Set the expected form origin and action URLs. // Set the expected form origin and action URLs.
std::string origin("data:text/html;charset=utf-8,"); UpdateOriginForHTML(kEmptyActionFormHTML);
origin += kEmptyActionFormHTML; fill_data_.basic_data.action = fill_data_.basic_data.origin;
fill_data_.basic_data.origin = GURL(origin);
fill_data_.basic_data.action = GURL(origin);
// Simulate the browser sending back the login info, it triggers the // Simulate the browser sending back the login info, it triggers the
// autocomplete. // autocomplete.
...@@ -507,9 +524,7 @@ TEST_F(PasswordAutofillAgentTest, NoAutocompleteForTextFieldPasswords) { ...@@ -507,9 +524,7 @@ TEST_F(PasswordAutofillAgentTest, NoAutocompleteForTextFieldPasswords) {
password_element_ = element.to<blink::WebInputElement>(); password_element_ = element.to<blink::WebInputElement>();
// Set the expected form origin URL. // Set the expected form origin URL.
std::string origin("data:text/html;charset=utf-8,"); UpdateOriginForHTML(kTextFieldPasswordFormHTML);
origin += kTextFieldPasswordFormHTML;
fill_data_.basic_data.origin = GURL(origin);
SimulateOnFillPasswordForm(fill_data_); SimulateOnFillPasswordForm(fill_data_);
...@@ -537,9 +552,7 @@ TEST_F(PasswordAutofillAgentTest, NoAutocompleteForPasswordFieldUsernames) { ...@@ -537,9 +552,7 @@ TEST_F(PasswordAutofillAgentTest, NoAutocompleteForPasswordFieldUsernames) {
password_element_ = element.to<blink::WebInputElement>(); password_element_ = element.to<blink::WebInputElement>();
// Set the expected form origin URL. // Set the expected form origin URL.
std::string origin("data:text/html;charset=utf-8,"); UpdateOriginForHTML(kPasswordFieldUsernameFormHTML);
origin += kPasswordFieldUsernameFormHTML;
fill_data_.basic_data.origin = GURL(origin);
SimulateOnFillPasswordForm(fill_data_); SimulateOnFillPasswordForm(fill_data_);
...@@ -812,7 +825,7 @@ TEST_F(PasswordAutofillAgentTest, GestureRequiredTest) { ...@@ -812,7 +825,7 @@ TEST_F(PasswordAutofillAgentTest, GestureRequiredTest) {
// However, it should only have completed with the suggested value, as tested // However, it should only have completed with the suggested value, as tested
// above, and it should not have completed into the DOM accessible value for // above, and it should not have completed into the DOM accessible value for
// the password field. // the password field.
CheckTextFieldsDOMState(kAliceUsername, true, "", true); CheckTextFieldsDOMState(kAliceUsername, true, std::string(), true);
// Simulate a user click so that the password field's real value is filled. // Simulate a user click so that the password field's real value is filled.
SimulateElementClick(kUsernameName); SimulateElementClick(kUsernameName);
...@@ -868,7 +881,7 @@ TEST_F(PasswordAutofillAgentTest, SelectUsernameWithPasswordAutofillOff) { ...@@ -868,7 +881,7 @@ TEST_F(PasswordAutofillAgentTest, SelectUsernameWithPasswordAutofillOff) {
password_element_.setAttribute(WebString::fromUTF8("autocomplete"), password_element_.setAttribute(WebString::fromUTF8("autocomplete"),
WebString::fromUTF8("off")); WebString::fromUTF8("off"));
// Simulate the user changing the username to some known username. // Simulate the user changing the username to some known username.
SimulateUsernameChange(kAliceUsername, true); SimulateUsernameChange(kAliceUsername, true);
ExpectNoSuggestionsPopup(); ExpectNoSuggestionsPopup();
...@@ -890,4 +903,96 @@ TEST_F(PasswordAutofillAgentTest, ...@@ -890,4 +903,96 @@ TEST_F(PasswordAutofillAgentTest,
ExpectNoSuggestionsPopup(); ExpectNoSuggestionsPopup();
} }
// Verifies that password autofill triggers onChange events in JavaScript for
// forms that are filled on page load.
TEST_F(PasswordAutofillAgentTest,
PasswordAutofillTriggersOnChangeEventsOnLoad) {
std::string html = std::string(kFormHTML) + kOnChangeDetectionScript;
LoadHTML(html.c_str());
UpdateOriginForHTML(html);
UpdateUsernameAndPasswordElements();
// Simulate the browser sending back the login info, it triggers the
// autocomplete.
SimulateOnFillPasswordForm(fill_data_);
// The username and password should have been autocompleted...
CheckTextFieldsState(kAliceUsername, true, kAlicePassword, true);
// ... but since there hasn't been a user gesture yet, the autocompleted
// password should only be visible to the user.
CheckTextFieldsDOMState(kAliceUsername, true, std::string(), true);
// A JavaScript onChange event should have been triggered for the username,
// but not yet for the password.
int username_onchange_called = -1;
int password_onchange_called = -1;
ASSERT_TRUE(
ExecuteJavaScriptAndReturnIntValue(
ASCIIToUTF16("usernameOnchangeCalled ? 1 : 0"),
&username_onchange_called));
EXPECT_EQ(1, username_onchange_called);
ASSERT_TRUE(
ExecuteJavaScriptAndReturnIntValue(
ASCIIToUTF16("passwordOnchangeCalled ? 1 : 0"),
&password_onchange_called));
// TODO(isherman): Re-enable this check once http://crbug.com/333144 is fixed.
// EXPECT_EQ(0, password_onchange_called);
// Simulate a user click so that the password field's real value is filled.
SimulateElementClick(kUsernameName);
CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true);
// Now, a JavaScript onChange event should have been triggered for the
// password as well.
ASSERT_TRUE(
ExecuteJavaScriptAndReturnIntValue(
ASCIIToUTF16("passwordOnchangeCalled ? 1 : 0"),
&password_onchange_called));
EXPECT_EQ(1, password_onchange_called);
}
// Verifies that password autofill triggers onChange events in JavaScript for
// forms that are filled after page load.
TEST_F(PasswordAutofillAgentTest,
PasswordAutofillTriggersOnChangeEventsWaitForUsername) {
std::string html = std::string(kFormHTML) + kOnChangeDetectionScript;
LoadHTML(html.c_str());
UpdateOriginForHTML(html);
UpdateUsernameAndPasswordElements();
// Simulate the browser sending back the login info, it triggers the
// autocomplete.
fill_data_.wait_for_username = true;
SimulateOnFillPasswordForm(fill_data_);
// The username and password should not yet have been autocompleted.
CheckTextFieldsState(std::string(), false, std::string(), false);
// Simulate a click just to force a user gesture, since the username value is
// set directly.
SimulateElementClick(kUsernameName);
// Simulate the user entering her username.
username_element_.setValue(ASCIIToUTF16(kAliceUsername), true);
autofill_agent_->textFieldDidEndEditing(username_element_);
// The username and password should now have been autocompleted.
CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true);
// JavaScript onChange events should have been triggered both for the username
// and for the password.
int username_onchange_called = -1;
int password_onchange_called = -1;
ASSERT_TRUE(
ExecuteJavaScriptAndReturnIntValue(
ASCIIToUTF16("usernameOnchangeCalled ? 1 : 0"),
&username_onchange_called));
EXPECT_EQ(1, username_onchange_called);
ASSERT_TRUE(
ExecuteJavaScriptAndReturnIntValue(
ASCIIToUTF16("passwordOnchangeCalled ? 1 : 0"),
&password_onchange_called));
EXPECT_EQ(1, password_onchange_called);
}
} // namespace autofill } // namespace autofill
...@@ -230,6 +230,9 @@ class AutofillAgent : public content::RenderViewObserver, ...@@ -230,6 +230,9 @@ class AutofillAgent : public content::RenderViewObserver,
FRIEND_TEST_ALL_PREFIXES(PasswordAutofillAgentTest, WaitUsername); FRIEND_TEST_ALL_PREFIXES(PasswordAutofillAgentTest, WaitUsername);
FRIEND_TEST_ALL_PREFIXES(PasswordAutofillAgentTest, SuggestionAccept); FRIEND_TEST_ALL_PREFIXES(PasswordAutofillAgentTest, SuggestionAccept);
FRIEND_TEST_ALL_PREFIXES(PasswordAutofillAgentTest, SuggestionSelect); FRIEND_TEST_ALL_PREFIXES(PasswordAutofillAgentTest, SuggestionSelect);
FRIEND_TEST_ALL_PREFIXES(
PasswordAutofillAgentTest,
PasswordAutofillTriggersOnChangeEventsWaitForUsername);
DISALLOW_COPY_AND_ASSIGN(AutofillAgent); DISALLOW_COPY_AND_ASSIGN(AutofillAgent);
}; };
......
...@@ -671,7 +671,7 @@ void PasswordAutofillAgent::FillFormOnPasswordRecieved( ...@@ -671,7 +671,7 @@ void PasswordAutofillAgent::FillFormOnPasswordRecieved(
if (IsElementAutocompletable(username_element) && if (IsElementAutocompletable(username_element) &&
username_element.value().isEmpty()) { username_element.value().isEmpty()) {
// TODO(tkent): Check maxlength and pattern. // TODO(tkent): Check maxlength and pattern.
username_element.setValue(fill_data.basic_data.fields[0].value); username_element.setValue(fill_data.basic_data.fields[0].value, true);
} }
// Fill if we have an exact match for the username. Note that this sets // Fill if we have an exact match for the username. Note that this sets
...@@ -742,7 +742,7 @@ bool PasswordAutofillAgent::FillUserNameAndPassword( ...@@ -742,7 +742,7 @@ bool PasswordAutofillAgent::FillUserNameAndPassword(
// Input matches the username, fill in required values. // Input matches the username, fill in required values.
if (IsElementAutocompletable(*username_element)) { if (IsElementAutocompletable(*username_element)) {
username_element->setValue(username); username_element->setValue(username, true);
SetElementAutofilled(username_element, true); SetElementAutofilled(username_element, true);
if (set_selection) { if (set_selection) {
...@@ -763,9 +763,12 @@ bool PasswordAutofillAgent::FillUserNameAndPassword( ...@@ -763,9 +763,12 @@ bool PasswordAutofillAgent::FillUserNameAndPassword(
gesture_handler_->addElement(*password_element); gesture_handler_->addElement(*password_element);
password_element->setSuggestedValue(password); password_element->setSuggestedValue(password);
} else { } else {
password_element->setValue(password); password_element->setValue(password, true);
} }
SetElementAutofilled(password_element, true); // Note: Don't call SetElementAutofilled() here, as that dispatches an
// onChange event in JavaScript, which is not appropriate for the password
// element if a user gesture has not yet occured.
password_element->setAutofilled(true);
return true; return true;
} }
...@@ -843,7 +846,7 @@ void PasswordAutofillAgent::AutofillWebUserGestureHandler::onGesture() { ...@@ -843,7 +846,7 @@ void PasswordAutofillAgent::AutofillWebUserGestureHandler::onGesture() {
std::vector<blink::WebInputElement>::iterator iter; std::vector<blink::WebInputElement>::iterator iter;
for (iter = elements_.begin(); iter != elements_.end(); ++iter) { for (iter = elements_.begin(); iter != elements_.end(); ++iter) {
if (!iter->isNull() && !iter->suggestedValue().isNull()) if (!iter->isNull() && !iter->suggestedValue().isNull())
iter->setValue(iter->suggestedValue()); iter->setValue(iter->suggestedValue(), true);
} }
elements_.clear(); elements_.clear();
......
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