// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include "base/macros.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "chrome/renderer/autofill/password_generation_test_utils.h" #include "chrome/test/base/chrome_render_view_test.h" #include "components/autofill/content/common/autofill_messages.h" #include "components/autofill/content/renderer/autofill_agent.h" #include "components/autofill/content/renderer/form_autofill_util.h" #include "components/autofill/content/renderer/password_autofill_agent.h" #include "components/autofill/content/renderer/test_password_autofill_agent.h" #include "components/autofill/content/renderer/test_password_generation_agent.h" #include "components/autofill/core/common/autofill_constants.h" #include "components/autofill/core/common/autofill_switches.h" #include "components/autofill/core/common/form_data.h" #include "components/autofill/core/common/form_field_data.h" #include "components/autofill/core/common/password_form_field_prediction_map.h" #include "content/public/renderer/render_frame.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/WebKit/public/platform/WebString.h" #include "third_party/WebKit/public/platform/WebVector.h" #include "third_party/WebKit/public/web/WebDocument.h" #include "third_party/WebKit/public/web/WebElement.h" #include "third_party/WebKit/public/web/WebFormControlElement.h" #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/WebNode.h" #include "third_party/WebKit/public/web/WebView.h" #include "ui/events/keycodes/keyboard_codes.h" using autofill::PasswordForm; using base::ASCIIToUTF16; using base::UTF16ToUTF8; using blink::WebDocument; using blink::WebElement; using blink::WebFrame; using blink::WebInputElement; using blink::WebString; using blink::WebView; namespace { const int kPasswordFillFormDataId = 1234; // The name of the username/password element in the form. const char kUsernameName[] = "username"; const char kPasswordName[] = "password"; const char kEmailName[] = "email"; const char kCreditCardOwnerName[] = "creditcardowner"; const char kCreditCardNumberName[] = "creditcardnumber"; const char kCreditCardVerificationName[] = "cvc"; const char kAliceUsername[] = "alice"; const char kAlicePassword[] = "password"; const char kBobUsername[] = "bob"; const char kBobPassword[] = "secret"; const char kCarolUsername[] = "Carol"; const char kCarolPassword[] = "test"; const char kCarolAlternateUsername[] = "RealCarolUsername"; const char kFormHTML[] = "
" " " " " " " " " "
"; const char kVisibleFormWithNoUsernameHTML[] = " " "" "
" "
" " " "
" "
" ""; const char kEmptyFormHTML[] = " " "
"; const char kNonVisibleFormHTML[] = " " "" "
" "
" " " "
" "
" ""; const char kSignupFormHTML[] = "
" " " " " " " " " "
"; const char kEmptyWebpage[] = "" " " " " " " " " ""; const char kRedirectionWebpage[] = "" " " " " " Redirection page" " " " " " " " " " " ""; const char kSimpleWebpage[] = "" " " " " " Title" " " " " "
" " " " " " " "
" " " ""; const char kWebpageWithDynamicContent[] = "" " " " " " Title" " " " " " " " " ""; const char kJavaScriptClick[] = "var event = new MouseEvent('click', {" " 'view': window," " 'bubbles': true," " 'cancelable': true" "});" "var form = document.getElementById('myform1');" "form.dispatchEvent(event);" "console.log('clicked!');"; const char kOnChangeDetectionScript[] = ""; const char kFormHTMLWithTwoTextFields[] = "
" " " " " " " " " "
"; const char kPasswordChangeFormHTML[] = "
" " " " " " " " " " " "
"; const char kCreditCardFormHTML[] = "
" " " " " " " " " "
"; const char kNoFormHTML[] = " " " "; const char kTwoNoUsernameFormsHTML[] = "
" " " " " "
" "
" " " " " "
"; // Sets the "readonly" attribute of |element| to the value given by |read_only|. void SetElementReadOnly(WebInputElement& element, bool read_only) { element.setAttribute(WebString::fromUTF8("readonly"), read_only ? WebString::fromUTF8("true") : WebString()); } } // namespace namespace autofill { class PasswordAutofillAgentTest : public ChromeRenderViewTest { public: PasswordAutofillAgentTest() { } // Simulates the fill password form message being sent to the renderer. // We use that so we don't have to make RenderView::OnFillPasswordForm() // protected. void SimulateOnFillPasswordForm( const PasswordFormFillData& fill_data) { AutofillMsg_FillPasswordForm msg(0, kPasswordFillFormDataId, fill_data); static_cast(password_autofill_agent_) ->OnMessageReceived(msg); } // As above, but fills for an iframe. void SimulateOnFillPasswordFormForFrame( WebFrame* frame, const PasswordFormFillData& fill_data) { AutofillMsg_FillPasswordForm msg(0, kPasswordFillFormDataId, fill_data); content::RenderFrame::FromWebFrame(frame)->OnMessageReceived(msg); } void SendVisiblePasswordForms() { static_cast(password_autofill_agent_) ->DidFinishLoad(); } void SetUp() override { ChromeRenderViewTest::SetUp(); // Add a preferred login and an additional login to the FillData. username1_ = ASCIIToUTF16(kAliceUsername); password1_ = ASCIIToUTF16(kAlicePassword); username2_ = ASCIIToUTF16(kBobUsername); password2_ = ASCIIToUTF16(kBobPassword); username3_ = ASCIIToUTF16(kCarolUsername); password3_ = ASCIIToUTF16(kCarolPassword); alternate_username3_ = ASCIIToUTF16(kCarolAlternateUsername); FormFieldData username_field; username_field.name = ASCIIToUTF16(kUsernameName); username_field.value = username1_; fill_data_.username_field = username_field; FormFieldData password_field; password_field.name = ASCIIToUTF16(kPasswordName); password_field.value = password1_; password_field.form_control_type = "password"; fill_data_.password_field = password_field; PasswordAndRealm password2; password2.password = password2_; fill_data_.additional_logins[username2_] = password2; PasswordAndRealm password3; password3.password = password3_; fill_data_.additional_logins[username3_] = password3; UsernamesCollectionKey key; key.username = username3_; key.password = password3_; key.realm = "google.com"; fill_data_.other_possible_usernames[key].push_back(alternate_username3_); // 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. UpdateOriginForHTML(kFormHTML); fill_data_.action = GURL("http://www.bidule.com"); LoadHTML(kFormHTML); // Necessary for SimulateElementClick() to work correctly. GetWebWidget()->resize(blink::WebSize(500, 500)); GetWebWidget()->setFocus(true); // Now retrieve the input elements so the test can access them. UpdateUsernameAndPasswordElements(); } void TearDown() override { 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_.origin = GURL(origin); } void UpdateUsernameAndPasswordElements() { WebDocument document = GetMainFrame()->document(); WebElement element = document.getElementById(WebString::fromUTF8(kUsernameName)); ASSERT_FALSE(element.isNull()); username_element_ = element.to(); element = document.getElementById(WebString::fromUTF8(kPasswordName)); ASSERT_FALSE(element.isNull()); password_element_ = element.to(); } blink::WebInputElement GetInputElementByID(const std::string& id) { WebDocument document = GetMainFrame()->document(); WebElement element = document.getElementById(WebString::fromUTF8(id.c_str())); return element.to(); } void ClearUsernameAndPasswordFields() { username_element_.setValue(""); username_element_.setAutofilled(false); password_element_.setValue(""); password_element_.setAutofilled(false); } void SimulateDidEndEditing(WebFrame* input_frame, WebInputElement& input) { static_cast(autofill_agent_) ->textFieldDidEndEditing(input); } void SimulateSuggestionChoice(WebInputElement& username_input) { base::string16 username(base::ASCIIToUTF16(kAliceUsername)); base::string16 password(base::ASCIIToUTF16(kAlicePassword)); SimulateSuggestionChoiceOfUsernameAndPassword(username_input, username, password); } void SimulateSuggestionChoiceOfUsernameAndPassword( WebInputElement& input, const base::string16& username, const base::string16& password) { // This call is necessary to setup the autofill agent appropriate for the // user selection; simulates the menu actually popping up. render_thread_->sink().ClearMessages(); static_cast(autofill_agent_) ->FormControlElementClicked(input, false); AutofillMsg_FillPasswordSuggestion msg(0, username, password); static_cast(autofill_agent_) ->OnMessageReceived(msg); } void SimulateUsernameChange(const std::string& username) { SimulateUserInputChangeForElement(&username_element_, username); } void SimulatePasswordChange(const std::string& password) { SimulateUserInputChangeForElement(&password_element_, password); } void CheckTextFieldsStateForElements(const WebInputElement& username_element, const std::string& username, bool username_autofilled, const WebInputElement& password_element, const std::string& password, bool password_autofilled, bool checkSuggestedValue) { EXPECT_EQ(username, static_cast(username_element.value().utf8())); EXPECT_EQ(username_autofilled, username_element.isAutofilled()); EXPECT_EQ(password, static_cast( checkSuggestedValue ? password_element.suggestedValue().utf8() : password_element.value().utf8())) << "checkSuggestedValue == " << checkSuggestedValue; EXPECT_EQ(password_autofilled, password_element.isAutofilled()); } // Checks the DOM-accessible value of the username element and the // *suggested* value of the password element. void CheckTextFieldsState(const std::string& username, bool username_autofilled, const std::string& password, bool password_autofilled) { CheckTextFieldsStateForElements(username_element_, username, username_autofilled, password_element_, password, password_autofilled, true); } // Checks the DOM-accessible value of the username element and the // DOM-accessible value of the password element. void CheckTextFieldsDOMState(const std::string& username, bool username_autofilled, const std::string& password, bool password_autofilled) { CheckTextFieldsStateForElements(username_element_, username, username_autofilled, password_element_, password, password_autofilled, false); } void CheckUsernameSelection(int start, int end) { EXPECT_EQ(start, username_element_.selectionStart()); EXPECT_EQ(end, username_element_.selectionEnd()); } // Checks the message sent to PasswordAutofillManager to build the suggestion // list. |username| is the expected username field value, and |show_all| is // the expected flag for the PasswordAutofillManager, whether to show all // suggestions, or only those starting with |username|. void CheckSuggestions(const std::string& username, bool show_all) { const IPC::Message* message = render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_ShowPasswordSuggestions::ID); ASSERT_TRUE(message); std::tuple args; AutofillHostMsg_ShowPasswordSuggestions::Read(message, &args); EXPECT_EQ(kPasswordFillFormDataId, std::get<0>(args)); EXPECT_EQ(ASCIIToUTF16(username), std::get<2>(args)); EXPECT_EQ(show_all, static_cast(std::get<3>(args) & autofill::SHOW_ALL)); render_thread_->sink().ClearMessages(); } void ExpectFormSubmittedWithUsernameAndPasswords( const std::string& username_value, const std::string& password_value, const std::string& new_password_value) { const IPC::Message* message = render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_PasswordFormSubmitted::ID); ASSERT_TRUE(message); std::tuple args; AutofillHostMsg_PasswordFormSubmitted::Read(message, &args); EXPECT_EQ(ASCIIToUTF16(username_value), std::get<0>(args).username_value); EXPECT_EQ(ASCIIToUTF16(password_value), std::get<0>(args).password_value); EXPECT_EQ(ASCIIToUTF16(new_password_value), std::get<0>(args).new_password_value); } void ExpectInPageNavigationWithUsernameAndPasswords( const std::string& username_value, const std::string& password_value, const std::string& new_password_value) { const IPC::Message* message = render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_InPageNavigation::ID); ASSERT_TRUE(message); std::tuple args; AutofillHostMsg_InPageNavigation::Read(message, &args); EXPECT_EQ(ASCIIToUTF16(username_value), std::get<0>(args).username_value); EXPECT_EQ(ASCIIToUTF16(password_value), std::get<0>(args).password_value); EXPECT_EQ(ASCIIToUTF16(new_password_value), std::get<0>(args).new_password_value); } base::string16 username1_; base::string16 username2_; base::string16 username3_; base::string16 password1_; base::string16 password2_; base::string16 password3_; base::string16 alternate_username3_; PasswordFormFillData fill_data_; WebInputElement username_element_; WebInputElement password_element_; private: DISALLOW_COPY_AND_ASSIGN(PasswordAutofillAgentTest); }; // Tests that the password login is autocompleted as expected when the browser // sends back the password info. TEST_F(PasswordAutofillAgentTest, InitialAutocomplete) { /* * Right now we are not sending the message to the browser because we are * loading a data URL and the security origin canAccessPasswordManager() * returns false. May be we should mock URL loading to cirmcuvent this? TODO(jcivelli): find a way to make the security origin not deny access to the password manager and then reenable this code. // The form has been loaded, we should have sent the browser a message about // the form. const IPC::Message* msg = render_thread_.sink().GetFirstMessageMatching( AutofillHostMsg_PasswordFormsParsed::ID); ASSERT_TRUE(msg != NULL); std::tuple > forms; AutofillHostMsg_PasswordFormsParsed::Read(msg, &forms); ASSERT_EQ(1U, forms.a.size()); PasswordForm password_form = forms.a[0]; EXPECT_EQ(PasswordForm::SCHEME_HTML, password_form.scheme); EXPECT_EQ(ASCIIToUTF16(kUsernameName), password_form.username_element); EXPECT_EQ(ASCIIToUTF16(kPasswordName), password_form.password_element); */ // 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); } // Tests that we correctly fill forms having an empty 'action' attribute. TEST_F(PasswordAutofillAgentTest, InitialAutocompleteForEmptyAction) { const char kEmptyActionFormHTML[] = "
" " " " " " " "
"; LoadHTML(kEmptyActionFormHTML); // Retrieve the input elements so the test can access them. WebDocument document = GetMainFrame()->document(); WebElement element = document.getElementById(WebString::fromUTF8(kUsernameName)); ASSERT_FALSE(element.isNull()); username_element_ = element.to(); element = document.getElementById(WebString::fromUTF8(kPasswordName)); ASSERT_FALSE(element.isNull()); password_element_ = element.to(); // Set the expected form origin and action URLs. UpdateOriginForHTML(kEmptyActionFormHTML); fill_data_.action = GURL(); // 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); } // Tests that if a password is marked as readonly, neither field is autofilled // on page load. TEST_F(PasswordAutofillAgentTest, NoInitialAutocompleteForReadOnlyPassword) { SetElementReadOnly(password_element_, true); // Simulate the browser sending back the login info, it triggers the // autocomplete. SimulateOnFillPasswordForm(fill_data_); CheckTextFieldsState(std::string(), false, std::string(), false); } // Can still fill a password field if the username is set to a value that // matches. TEST_F(PasswordAutofillAgentTest, AutocompletePasswordForReadonlyUsernameMatched) { username_element_.setValue(username3_); SetElementReadOnly(username_element_, true); // Filled even though username is not the preferred match. SimulateOnFillPasswordForm(fill_data_); CheckTextFieldsState(UTF16ToUTF8(username3_), false, UTF16ToUTF8(password3_), true); } // If a username field is empty and readonly, don't autofill. TEST_F(PasswordAutofillAgentTest, NoAutocompletePasswordForReadonlyUsernameUnmatched) { username_element_.setValue(WebString::fromUTF8("")); SetElementReadOnly(username_element_, true); SimulateOnFillPasswordForm(fill_data_); CheckTextFieldsState(std::string(), false, std::string(), false); } // Tests that having a non-matching username precludes the autocomplete. TEST_F(PasswordAutofillAgentTest, NoAutocompleteForFilledFieldUnmatched) { username_element_.setValue(WebString::fromUTF8("bogus")); // Simulate the browser sending back the login info, it triggers the // autocomplete. SimulateOnFillPasswordForm(fill_data_); // Neither field should be autocompleted. CheckTextFieldsState("bogus", false, std::string(), false); } // Don't try to complete a prefilled value even if it's a partial match // to a username. TEST_F(PasswordAutofillAgentTest, NoPartialMatchForPrefilledUsername) { username_element_.setValue(WebString::fromUTF8("ali")); SimulateOnFillPasswordForm(fill_data_); CheckTextFieldsState("ali", false, std::string(), false); } TEST_F(PasswordAutofillAgentTest, InputWithNoForms) { const char kNoFormInputs[] = "" ""; LoadHTML(kNoFormInputs); SimulateOnFillPasswordForm(fill_data_); // Input elements that aren't in a
won't autofill. CheckTextFieldsState(std::string(), false, std::string(), false); } TEST_F(PasswordAutofillAgentTest, NoAutocompleteForTextFieldPasswords) { const char kTextFieldPasswordFormHTML[] = "" " " " " " " "
"; LoadHTML(kTextFieldPasswordFormHTML); // Retrieve the input elements so the test can access them. WebDocument document = GetMainFrame()->document(); WebElement element = document.getElementById(WebString::fromUTF8(kUsernameName)); ASSERT_FALSE(element.isNull()); username_element_ = element.to(); element = document.getElementById(WebString::fromUTF8(kPasswordName)); ASSERT_FALSE(element.isNull()); password_element_ = element.to(); // Set the expected form origin URL. UpdateOriginForHTML(kTextFieldPasswordFormHTML); SimulateOnFillPasswordForm(fill_data_); // Fields should still be empty. CheckTextFieldsState(std::string(), false, std::string(), false); } TEST_F(PasswordAutofillAgentTest, NoAutocompleteForPasswordFieldUsernames) { const char kPasswordFieldUsernameFormHTML[] = "
" " " " " " " "
"; LoadHTML(kPasswordFieldUsernameFormHTML); // Retrieve the input elements so the test can access them. WebDocument document = GetMainFrame()->document(); WebElement element = document.getElementById(WebString::fromUTF8(kUsernameName)); ASSERT_FALSE(element.isNull()); username_element_ = element.to(); element = document.getElementById(WebString::fromUTF8(kPasswordName)); ASSERT_FALSE(element.isNull()); password_element_ = element.to(); // Set the expected form origin URL. UpdateOriginForHTML(kPasswordFieldUsernameFormHTML); SimulateOnFillPasswordForm(fill_data_); // Fields should still be empty. CheckTextFieldsState(std::string(), false, std::string(), false); } // Tests that having a matching username does not preclude the autocomplete. TEST_F(PasswordAutofillAgentTest, InitialAutocompleteForMatchingFilledField) { username_element_.setValue(WebString::fromUTF8(kAliceUsername)); // 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); } TEST_F(PasswordAutofillAgentTest, PasswordNotClearedOnEdit) { // Simulate the browser sending back the login info, it triggers the // autocomplete. SimulateOnFillPasswordForm(fill_data_); // Simulate the user changing the username to some unknown username. SimulateUsernameChange("alicia"); // The password should not have been cleared. CheckTextFieldsDOMState("alicia", false, kAlicePassword, true); } // Tests that we only autocomplete on focus lost and with a full username match // when |wait_for_username| is true. TEST_F(PasswordAutofillAgentTest, WaitUsername) { // Simulate the browser sending back the login info. fill_data_.wait_for_username = true; SimulateOnFillPasswordForm(fill_data_); // No auto-fill should have taken place. CheckTextFieldsState(std::string(), false, std::string(), false); // No autocomplete should happen when text is entered in the username. SimulateUsernameChange("a"); CheckTextFieldsState("a", false, std::string(), false); SimulateUsernameChange("al"); CheckTextFieldsState("al", false, std::string(), false); SimulateUsernameChange(kAliceUsername); CheckTextFieldsState(kAliceUsername, false, std::string(), false); // Autocomplete should happen only when the username textfield is blurred with // a full match. SimulateUsernameChange("a"); static_cast(autofill_agent_) ->textFieldDidEndEditing(username_element_); CheckTextFieldsState("a", false, std::string(), false); SimulateUsernameChange("al"); static_cast(autofill_agent_) ->textFieldDidEndEditing(username_element_); CheckTextFieldsState("al", false, std::string(), false); SimulateUsernameChange("alices"); static_cast(autofill_agent_) ->textFieldDidEndEditing(username_element_); CheckTextFieldsState("alices", false, std::string(), false); SimulateUsernameChange(kAliceUsername); static_cast(autofill_agent_) ->textFieldDidEndEditing(username_element_); CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true); } TEST_F(PasswordAutofillAgentTest, IsWebNodeVisibleTest) { blink::WebVector forms1, forms2, forms3; blink::WebFrame* frame; LoadHTML(kVisibleFormWithNoUsernameHTML); frame = GetMainFrame(); frame->document().forms(forms1); ASSERT_EQ(1u, forms1.size()); EXPECT_TRUE(form_util::IsWebNodeVisible(forms1[0])); LoadHTML(kEmptyFormHTML); frame = GetMainFrame(); frame->document().forms(forms2); ASSERT_EQ(1u, forms2.size()); EXPECT_FALSE(form_util::IsWebNodeVisible(forms2[0])); LoadHTML(kNonVisibleFormHTML); frame = GetMainFrame(); frame->document().forms(forms3); ASSERT_EQ(1u, forms3.size()); EXPECT_FALSE(form_util::IsWebNodeVisible(forms3[0])); } TEST_F(PasswordAutofillAgentTest, SendPasswordFormsTest) { render_thread_->sink().ClearMessages(); LoadHTML(kVisibleFormWithNoUsernameHTML); const IPC::Message* message = render_thread_->sink() .GetFirstMessageMatching(AutofillHostMsg_PasswordFormsRendered::ID); EXPECT_TRUE(message); std::tuple, bool> param; AutofillHostMsg_PasswordFormsRendered::Read(message, ¶m); EXPECT_TRUE(std::get<0>(param).size()); render_thread_->sink().ClearMessages(); LoadHTML(kEmptyFormHTML); message = render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_PasswordFormsRendered::ID); EXPECT_TRUE(message); AutofillHostMsg_PasswordFormsRendered::Read(message, ¶m); EXPECT_FALSE(std::get<0>(param).size()); render_thread_->sink().ClearMessages(); LoadHTML(kNonVisibleFormHTML); message = render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_PasswordFormsRendered::ID); EXPECT_TRUE(message); AutofillHostMsg_PasswordFormsRendered::Read(message, ¶m); EXPECT_FALSE(std::get<0>(param).size()); } TEST_F(PasswordAutofillAgentTest, SendPasswordFormsTest_Redirection) { render_thread_->sink().ClearMessages(); LoadHTML(kEmptyWebpage); EXPECT_FALSE(render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_PasswordFormsRendered::ID)); render_thread_->sink().ClearMessages(); LoadHTML(kRedirectionWebpage); EXPECT_FALSE(render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_PasswordFormsRendered::ID)); render_thread_->sink().ClearMessages(); LoadHTML(kSimpleWebpage); EXPECT_TRUE(render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_PasswordFormsRendered::ID)); render_thread_->sink().ClearMessages(); LoadHTML(kWebpageWithDynamicContent); EXPECT_TRUE(render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_PasswordFormsRendered::ID)); } // Tests that a password will only be filled as a suggested and will not be // accessible by the DOM until a user gesture has occurred. TEST_F(PasswordAutofillAgentTest, GestureRequiredTest) { // Trigger the initial autocomplete. SimulateOnFillPasswordForm(fill_data_); // The username and password should have been autocompleted. CheckTextFieldsState(kAliceUsername, true, kAlicePassword, true); // 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 // the password field. CheckTextFieldsDOMState(kAliceUsername, true, std::string(), true); // Simulate a user click so that the password field's real value is filled. SimulateElementClick(kUsernameName); CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true); } // Verfies that a DOM-activated UI event will not cause an autofill. TEST_F(PasswordAutofillAgentTest, NoDOMActivationTest) { // Trigger the initial autocomplete. SimulateOnFillPasswordForm(fill_data_); ExecuteJavaScriptForTests(kJavaScriptClick); CheckTextFieldsDOMState(kAliceUsername, true, "", true); } // 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 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. 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); } // Tests that |FillSuggestion| properly fills the username and password. TEST_F(PasswordAutofillAgentTest, FillSuggestion) { // Simulate the browser sending the login info, but set |wait_for_username| // to prevent the form from being immediately filled. fill_data_.wait_for_username = true; SimulateOnFillPasswordForm(fill_data_); // Neither field should have been autocompleted. CheckTextFieldsDOMState(std::string(), false, std::string(), false); // If the password field is not autocompletable, it should not be affected. SetElementReadOnly(password_element_, true); EXPECT_FALSE(password_autofill_agent_->FillSuggestion( username_element_, kAliceUsername, kAlicePassword)); CheckTextFieldsDOMState(std::string(), false, std::string(), false); SetElementReadOnly(password_element_, false); // After filling with the suggestion, both fields should be autocompleted. EXPECT_TRUE(password_autofill_agent_->FillSuggestion( username_element_, kAliceUsername, kAlicePassword)); CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true); int username_length = strlen(kAliceUsername); CheckUsernameSelection(username_length, username_length); // Try Filling with a suggestion with password different from the one that was // initially sent to the renderer. EXPECT_TRUE(password_autofill_agent_->FillSuggestion( username_element_, kBobUsername, kCarolPassword)); CheckTextFieldsDOMState(kBobUsername, true, kCarolPassword, true); username_length = strlen(kBobUsername); CheckUsernameSelection(username_length, username_length); } // Tests that |PreviewSuggestion| properly previews the username and password. TEST_F(PasswordAutofillAgentTest, PreviewSuggestion) { // Simulate the browser sending the login info, but set |wait_for_username| // to prevent the form from being immediately filled. fill_data_.wait_for_username = true; SimulateOnFillPasswordForm(fill_data_); // Neither field should have been autocompleted. CheckTextFieldsDOMState(std::string(), false, std::string(), false); // If the password field is not autocompletable, it should not be affected. SetElementReadOnly(password_element_, true); EXPECT_FALSE(password_autofill_agent_->PreviewSuggestion( username_element_, kAliceUsername, kAlicePassword)); EXPECT_EQ(std::string(), username_element_.suggestedValue().utf8()); EXPECT_FALSE(username_element_.isAutofilled()); EXPECT_EQ(std::string(), password_element_.suggestedValue().utf8()); EXPECT_FALSE(password_element_.isAutofilled()); SetElementReadOnly(password_element_, false); // After selecting the suggestion, both fields should be previewed // with suggested values. EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion( username_element_, kAliceUsername, kAlicePassword)); EXPECT_EQ( kAliceUsername, static_cast(username_element_.suggestedValue().utf8())); EXPECT_TRUE(username_element_.isAutofilled()); EXPECT_EQ( kAlicePassword, static_cast(password_element_.suggestedValue().utf8())); EXPECT_TRUE(password_element_.isAutofilled()); int username_length = strlen(kAliceUsername); CheckUsernameSelection(0, username_length); // Try previewing with a password different from the one that was initially // sent to the renderer. EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion( username_element_, kBobUsername, kCarolPassword)); EXPECT_EQ( kBobUsername, static_cast(username_element_.suggestedValue().utf8())); EXPECT_TRUE(username_element_.isAutofilled()); EXPECT_EQ( kCarolPassword, static_cast(password_element_.suggestedValue().utf8())); EXPECT_TRUE(password_element_.isAutofilled()); username_length = strlen(kBobUsername); CheckUsernameSelection(0, username_length); } // Tests that |PreviewSuggestion| properly sets the username selection range. TEST_F(PasswordAutofillAgentTest, PreviewSuggestionSelectionRange) { username_element_.setValue(WebString::fromUTF8("ali")); username_element_.setSelectionRange(3, 3); username_element_.setAutofilled(true); CheckTextFieldsDOMState("ali", true, std::string(), false); // Simulate the browser sending the login info, but set |wait_for_username| // to prevent the form from being immediately filled. fill_data_.wait_for_username = true; SimulateOnFillPasswordForm(fill_data_); EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion( username_element_, kAliceUsername, kAlicePassword)); EXPECT_EQ( kAliceUsername, static_cast(username_element_.suggestedValue().utf8())); EXPECT_TRUE(username_element_.isAutofilled()); EXPECT_EQ( kAlicePassword, static_cast(password_element_.suggestedValue().utf8())); EXPECT_TRUE(password_element_.isAutofilled()); int username_length = strlen(kAliceUsername); CheckUsernameSelection(3, username_length); } // Tests that |ClearPreview| properly clears previewed username and password // with password being previously autofilled. TEST_F(PasswordAutofillAgentTest, ClearPreviewWithPasswordAutofilled) { password_element_.setValue(WebString::fromUTF8("sec")); password_element_.setAutofilled(true); // Simulate the browser sending the login info, but set |wait_for_username| // to prevent the form from being immediately filled. fill_data_.wait_for_username = true; SimulateOnFillPasswordForm(fill_data_); CheckTextFieldsDOMState(std::string(), false, "sec", true); EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion( username_element_, kAliceUsername, kAlicePassword)); EXPECT_TRUE( password_autofill_agent_->DidClearAutofillSelection(username_element_)); EXPECT_TRUE(username_element_.value().isEmpty()); EXPECT_TRUE(username_element_.suggestedValue().isEmpty()); EXPECT_FALSE(username_element_.isAutofilled()); EXPECT_EQ(ASCIIToUTF16("sec"), password_element_.value()); EXPECT_TRUE(password_element_.suggestedValue().isEmpty()); EXPECT_TRUE(password_element_.isAutofilled()); CheckUsernameSelection(0, 0); } // Tests that |ClearPreview| properly clears previewed username and password // with username being previously autofilled. TEST_F(PasswordAutofillAgentTest, ClearPreviewWithUsernameAutofilled) { username_element_.setValue(WebString::fromUTF8("ali")); username_element_.setSelectionRange(3, 3); username_element_.setAutofilled(true); // Simulate the browser sending the login info, but set |wait_for_username| // to prevent the form from being immediately filled. fill_data_.wait_for_username = true; SimulateOnFillPasswordForm(fill_data_); CheckTextFieldsDOMState("ali", true, std::string(), false); EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion( username_element_, kAliceUsername, kAlicePassword)); EXPECT_TRUE( password_autofill_agent_->DidClearAutofillSelection(username_element_)); EXPECT_EQ(ASCIIToUTF16("ali"), username_element_.value()); EXPECT_TRUE(username_element_.suggestedValue().isEmpty()); EXPECT_TRUE(username_element_.isAutofilled()); EXPECT_TRUE(password_element_.value().isEmpty()); EXPECT_TRUE(password_element_.suggestedValue().isEmpty()); EXPECT_FALSE(password_element_.isAutofilled()); CheckUsernameSelection(3, 3); } // Tests that |ClearPreview| properly clears previewed username and password // with username and password being previously autofilled. TEST_F(PasswordAutofillAgentTest, ClearPreviewWithAutofilledUsernameAndPassword) { username_element_.setValue(WebString::fromUTF8("ali")); username_element_.setSelectionRange(3, 3); username_element_.setAutofilled(true); password_element_.setValue(WebString::fromUTF8("sec")); password_element_.setAutofilled(true); // Simulate the browser sending the login info, but set |wait_for_username| // to prevent the form from being immediately filled. fill_data_.wait_for_username = true; SimulateOnFillPasswordForm(fill_data_); CheckTextFieldsDOMState("ali", true, "sec", true); EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion( username_element_, kAliceUsername, kAlicePassword)); EXPECT_TRUE( password_autofill_agent_->DidClearAutofillSelection(username_element_)); EXPECT_EQ(ASCIIToUTF16("ali"), username_element_.value()); EXPECT_TRUE(username_element_.suggestedValue().isEmpty()); EXPECT_TRUE(username_element_.isAutofilled()); EXPECT_EQ(ASCIIToUTF16("sec"), password_element_.value()); EXPECT_TRUE(password_element_.suggestedValue().isEmpty()); EXPECT_TRUE(password_element_.isAutofilled()); CheckUsernameSelection(3, 3); } // Tests that |ClearPreview| properly clears previewed username and password // with neither username nor password being previously autofilled. TEST_F(PasswordAutofillAgentTest, ClearPreviewWithNotAutofilledUsernameAndPassword) { // Simulate the browser sending the login info, but set |wait_for_username| // to prevent the form from being immediately filled. fill_data_.wait_for_username = true; SimulateOnFillPasswordForm(fill_data_); CheckTextFieldsDOMState(std::string(), false, std::string(), false); EXPECT_TRUE(password_autofill_agent_->PreviewSuggestion( username_element_, kAliceUsername, kAlicePassword)); EXPECT_TRUE( password_autofill_agent_->DidClearAutofillSelection(username_element_)); EXPECT_TRUE(username_element_.value().isEmpty()); EXPECT_TRUE(username_element_.suggestedValue().isEmpty()); EXPECT_FALSE(username_element_.isAutofilled()); EXPECT_TRUE(password_element_.value().isEmpty()); EXPECT_TRUE(password_element_.suggestedValue().isEmpty()); EXPECT_FALSE(password_element_.isAutofilled()); CheckUsernameSelection(0, 0); } // Tests that logging is off by default. TEST_F(PasswordAutofillAgentTest, OnChangeLoggingState_NoMessage) { render_thread_->sink().ClearMessages(); SendVisiblePasswordForms(); const IPC::Message* message = render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_RecordSavePasswordProgress::ID); EXPECT_FALSE(message); } // Test that logging can be turned on by a message. TEST_F(PasswordAutofillAgentTest, OnChangeLoggingState_Activated) { // Turn the logging on. AutofillMsg_SetLoggingState msg_activate(0, true); // Up-cast to access OnMessageReceived, which is private in the agent. EXPECT_TRUE(static_cast(password_autofill_agent_) ->OnMessageReceived(msg_activate)); render_thread_->sink().ClearMessages(); SendVisiblePasswordForms(); const IPC::Message* message = render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_RecordSavePasswordProgress::ID); EXPECT_TRUE(message); } // Test that logging can be turned off by a message. TEST_F(PasswordAutofillAgentTest, OnChangeLoggingState_Deactivated) { // Turn the logging on and then off. AutofillMsg_SetLoggingState msg_activate(0, /*active=*/true); // Up-cast to access OnMessageReceived, which is private in the agent. EXPECT_TRUE(static_cast(password_autofill_agent_) ->OnMessageReceived(msg_activate)); AutofillMsg_SetLoggingState msg_deactivate(0, /*active=*/false); EXPECT_TRUE(static_cast(password_autofill_agent_) ->OnMessageReceived(msg_deactivate)); render_thread_->sink().ClearMessages(); SendVisiblePasswordForms(); const IPC::Message* message = render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_RecordSavePasswordProgress::ID); EXPECT_FALSE(message); } // Test that the agent sends an IPC call to get the current activity state of // password saving logging soon after construction. TEST_F(PasswordAutofillAgentTest, SendsLoggingStateUpdatePingOnConstruction) { const IPC::Message* message = render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_PasswordAutofillAgentConstructed::ID); EXPECT_TRUE(message); } // Tests that one user click on a username field is sufficient to bring up a // credential suggestion popup, and the user can autocomplete the password by // selecting the credential from the popup. TEST_F(PasswordAutofillAgentTest, ClickAndSelect) { // SimulateElementClick() is called so that a user gesture is actually made // and the password can be filled. However, SimulateElementClick() does not // actually lead to the AutofillAgent's InputElementClicked() method being // called, so SimulateSuggestionChoice has to manually call // InputElementClicked(). ClearUsernameAndPasswordFields(); SimulateOnFillPasswordForm(fill_data_); SimulateElementClick(kUsernameName); SimulateSuggestionChoice(username_element_); CheckSuggestions(kAliceUsername, true); CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true); } // Tests the autosuggestions that are given when the element is clicked. // Specifically, tests when the user clicks on the username element after page // load and the element is autofilled, when the user clicks on an element that // has a non-matching username, and when the user clicks on an element that's // already been autofilled and they've already modified. TEST_F(PasswordAutofillAgentTest, CredentialsOnClick) { // Simulate the browser sending back the login info. SimulateOnFillPasswordForm(fill_data_); // Clear the text fields to start fresh. ClearUsernameAndPasswordFields(); // Call SimulateElementClick() to produce a user gesture on the page so // autofill will actually fill. SimulateElementClick(kUsernameName); // Simulate a user clicking on the username element. This should produce a // message with all the usernames. render_thread_->sink().ClearMessages(); static_cast(autofill_agent_) ->FormControlElementClicked(username_element_, false); CheckSuggestions(std::string(), false); // 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"); render_thread_->sink().ClearMessages(); static_cast(autofill_agent_) ->FormControlElementClicked(username_element_, true); CheckSuggestions("baz", true); ClearUsernameAndPasswordFields(); } // Tests that there are no autosuggestions from the password manager when the // user clicks on the password field and the username field is editable when // FillOnAccountSelect is enabled. TEST_F(PasswordAutofillAgentTest, FillOnAccountSelectOnlyNoCredentialsOnPasswordClick) { base::CommandLine::ForCurrentProcess()->AppendSwitch( autofill::switches::kEnableFillOnAccountSelect); // Simulate the browser sending back the login info. SimulateOnFillPasswordForm(fill_data_); // Clear the text fields to start fresh. ClearUsernameAndPasswordFields(); // Call SimulateElementClick() to produce a user gesture on the page so // autofill will actually fill. SimulateElementClick(kUsernameName); // Simulate a user clicking on the password element. This should produce no // message. render_thread_->sink().ClearMessages(); static_cast(autofill_agent_) ->FormControlElementClicked(password_element_, false); EXPECT_FALSE(render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_ShowPasswordSuggestions::ID)); } // Tests the autosuggestions that are given when a password element is clicked, // the username element is not editable, and FillOnAccountSelect is enabled. // Specifically, tests when the user clicks on the password element after page // load, and the corresponding username element is readonly (and thus // uneditable), that the credentials for the already-filled username are // suggested. TEST_F(PasswordAutofillAgentTest, FillOnAccountSelectOnlyCredentialsOnPasswordClick) { base::CommandLine::ForCurrentProcess()->AppendSwitch( autofill::switches::kEnableFillOnAccountSelect); // Simulate the browser sending back the login info. SimulateOnFillPasswordForm(fill_data_); // Clear the text fields to start fresh. ClearUsernameAndPasswordFields(); // Simulate the page loading with a prefilled username element that is // uneditable. username_element_.setValue("alicia"); SetElementReadOnly(username_element_, true); // Call SimulateElementClick() to produce a user gesture on the page so // autofill will actually fill. SimulateElementClick(kUsernameName); // Simulate a user clicking on the password element. This should produce a // message with "alicia" suggested as the credential. render_thread_->sink().ClearMessages(); static_cast(autofill_agent_) ->FormControlElementClicked(password_element_, false); CheckSuggestions("alicia", false); } // Tests that there are no autosuggestions from the password manager when the // user clicks on the password field (not the username field). TEST_F(PasswordAutofillAgentTest, NoCredentialsOnPasswordClick) { // Simulate the browser sending back the login info. SimulateOnFillPasswordForm(fill_data_); // Clear the text fields to start fresh. ClearUsernameAndPasswordFields(); // Call SimulateElementClick() to produce a user gesture on the page so // autofill will actually fill. SimulateElementClick(kUsernameName); // Simulate a user clicking on the password element. This should produce no // message. render_thread_->sink().ClearMessages(); static_cast(autofill_agent_) ->FormControlElementClicked(password_element_, false); EXPECT_FALSE(render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_ShowPasswordSuggestions::ID)); } // The user types in a username and a password, but then just before sending // the form off, a script clears them. This test checks that // PasswordAutofillAgent can still remember the username and the password // typed by the user. TEST_F(PasswordAutofillAgentTest, RememberLastNonEmptyUsernameAndPasswordOnSubmit_ScriptCleared) { SimulateUsernameChange("temp"); SimulatePasswordChange("random"); // Simulate that the username and the password value was cleared by the // site's JavaScript before submit. username_element_.setValue(WebString()); password_element_.setValue(WebString()); static_cast(password_autofill_agent_) ->WillSubmitForm(username_element_.form()); // Observe that the PasswordAutofillAgent still remembered the last non-empty // username and password and sent that to the browser. ExpectFormSubmittedWithUsernameAndPasswords("temp", "random", ""); } // Similar to RememberLastNonEmptyPasswordOnSubmit_ScriptCleared, but this time // it's the user who clears the username and the password. This test checks // that in that case, the last non-empty username and password are not // remembered. TEST_F(PasswordAutofillAgentTest, RememberLastNonEmptyUsernameAndPasswordOnSubmit_UserCleared) { SimulateUsernameChange("temp"); SimulatePasswordChange("random"); // Simulate that the user actually cleared the username and password again. SimulateUsernameChange(""); SimulatePasswordChange(""); static_cast(password_autofill_agent_) ->WillSubmitForm(username_element_.form()); // Observe that the PasswordAutofillAgent respects the user having cleared the // password. ExpectFormSubmittedWithUsernameAndPasswords("", "", ""); } // Similar to RememberLastNonEmptyPasswordOnSubmit_ScriptCleared, but uses the // new password instead of the current password. TEST_F(PasswordAutofillAgentTest, RememberLastNonEmptyUsernameAndPasswordOnSubmit_New) { const char kNewPasswordFormHTML[] = "
" " " " " " " "
"; LoadHTML(kNewPasswordFormHTML); UpdateUsernameAndPasswordElements(); SimulateUsernameChange("temp"); SimulatePasswordChange("random"); // Simulate that the username and the password value was cleared by // the site's JavaScript before submit. username_element_.setValue(WebString()); password_element_.setValue(WebString()); static_cast(password_autofill_agent_) ->WillSubmitForm(username_element_.form()); // Observe that the PasswordAutofillAgent still remembered the last non-empty // password and sent that to the browser. ExpectFormSubmittedWithUsernameAndPasswords("temp", "", "random"); } // The user first accepts a suggestion, but then overwrites the password. This // test checks that the overwritten password is not reverted back if the user // triggers autofill through focusing (but not changing) the username again. TEST_F(PasswordAutofillAgentTest, NoopEditingDoesNotOverwriteManuallyEditedPassword) { // Simulate having credentials which needed to wait until the user starts // typing the username to be filled (e.g., PSL-matched credentials). Those are // the ones which can be filled as a result of TextFieldDidEndEditing. fill_data_.wait_for_username = true; SimulateOnFillPasswordForm(fill_data_); // Simulate that the user typed her name to make the autofill work. 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. SimulatePasswordChange(new_password); // The user switches back into the username field, but leaves that without // changes. SimulateDidEndEditing(GetMainFrame(), username_element_); // The password should have stayed as the user changed it. CheckTextFieldsDOMState(old_username, true, new_password, false); // The password should not have a suggested value. CheckTextFieldsState(old_username, true, std::string(), false); } // The user types in a username and a password, but then just before sending // the form off, a script changes them. This test checks that // PasswordAutofillAgent can still remember the username and the password // typed by the user. TEST_F(PasswordAutofillAgentTest, RememberLastTypedUsernameAndPasswordOnSubmit_ScriptChanged) { SimulateUsernameChange("temp"); SimulatePasswordChange("random"); // Simulate that the username and the password value was changed by the // site's JavaScript before submit. username_element_.setValue(WebString("new username")); password_element_.setValue(WebString("new password")); static_cast(password_autofill_agent_) ->WillSendSubmitEvent(username_element_.form()); static_cast(password_autofill_agent_) ->WillSubmitForm(username_element_.form()); // Observe that the PasswordAutofillAgent still remembered the last typed // username and password and sent that to the browser. ExpectFormSubmittedWithUsernameAndPasswords("temp", "random", ""); } // The username/password is autofilled by password manager then just before // sending the form off, a script changes them. This test checks that // PasswordAutofillAgent can still get the username and the password autofilled. TEST_F(PasswordAutofillAgentTest, RememberLastAutofilledUsernameAndPasswordOnSubmit_ScriptChanged) { SimulateOnFillPasswordForm(fill_data_); // Simulate that the username and the password value was changed by the // site's JavaScript before submit. username_element_.setValue(WebString("new username")); password_element_.setValue(WebString("new password")); static_cast(password_autofill_agent_) ->WillSendSubmitEvent(username_element_.form()); static_cast(password_autofill_agent_) ->WillSubmitForm(username_element_.form()); // Observe that the PasswordAutofillAgent still remembered the autofilled // username and password and sent that to the browser. ExpectFormSubmittedWithUsernameAndPasswords(kAliceUsername, kAlicePassword, ""); } // The username/password is autofilled by password manager then user types in a // username and a password. Then just before sending the form off, a script // changes them. This test checks that PasswordAutofillAgent can still remember // the username and the password typed by the user. TEST_F( PasswordAutofillAgentTest, RememberLastTypedAfterAutofilledUsernameAndPasswordOnSubmit_ScriptChanged) { SimulateOnFillPasswordForm(fill_data_); SimulateUsernameChange("temp"); SimulatePasswordChange("random"); // Simulate that the username and the password value was changed by the // site's JavaScript before submit. username_element_.setValue(WebString("new username")); password_element_.setValue(WebString("new password")); static_cast(password_autofill_agent_) ->WillSendSubmitEvent(username_element_.form()); static_cast(password_autofill_agent_) ->WillSubmitForm(username_element_.form()); // Observe that the PasswordAutofillAgent still remembered the last typed // username and password and sent that to the browser. ExpectFormSubmittedWithUsernameAndPasswords("temp", "random", ""); } // The user starts typing username then it is autofilled. // PasswordAutofillAgent should remember the username that was autofilled, // not last typed. TEST_F(PasswordAutofillAgentTest, RememberAutofilledUsername) { SimulateUsernameChange("Te"); // Simulate that the username was changed by autofilling. username_element_.setValue(WebString("temp")); SimulatePasswordChange("random"); static_cast(password_autofill_agent_) ->WillSendSubmitEvent(username_element_.form()); static_cast(password_autofill_agent_) ->WillSubmitForm(username_element_.form()); // Observe that the PasswordAutofillAgent still remembered the last typed // username and password and sent that to the browser. ExpectFormSubmittedWithUsernameAndPasswords("temp", "random", ""); } TEST_F(PasswordAutofillAgentTest, FormFillDataMustHaveUsername) { ClearUsernameAndPasswordFields(); PasswordFormFillData no_username_fill_data = fill_data_; no_username_fill_data.username_field.name = base::string16(); SimulateOnFillPasswordForm(no_username_fill_data); // The username and password should not have been autocompleted. CheckTextFieldsState("", false, "", false); } TEST_F(PasswordAutofillAgentTest, FillOnAccountSelectOnly) { base::CommandLine::ForCurrentProcess()->AppendSwitch( autofill::switches::kEnableFillOnAccountSelect); ClearUsernameAndPasswordFields(); // Simulate the browser sending back the login info for an initial page load. SimulateOnFillPasswordForm(fill_data_); CheckTextFieldsState(std::string(), true, std::string(), false); } TEST_F(PasswordAutofillAgentTest, FillOnAccountSelectOnlyReadonlyUsername) { base::CommandLine::ForCurrentProcess()->AppendSwitch( autofill::switches::kEnableFillOnAccountSelect); ClearUsernameAndPasswordFields(); username_element_.setValue("alice"); SetElementReadOnly(username_element_, true); // Simulate the browser sending back the login info for an initial page load. SimulateOnFillPasswordForm(fill_data_); CheckTextFieldsState(std::string("alice"), false, std::string(), true); } TEST_F(PasswordAutofillAgentTest, FillOnAccountSelectOnlyReadonlyNotPreferredUsername) { base::CommandLine::ForCurrentProcess()->AppendSwitch( autofill::switches::kEnableFillOnAccountSelect); ClearUsernameAndPasswordFields(); username_element_.setValue("Carol"); SetElementReadOnly(username_element_, true); // Simulate the browser sending back the login info for an initial page load. SimulateOnFillPasswordForm(fill_data_); CheckTextFieldsState(std::string("Carol"), false, std::string(), true); } TEST_F(PasswordAutofillAgentTest, FillOnAccountSelectOnlyNoUsername) { base::CommandLine::ForCurrentProcess()->AppendSwitch( autofill::switches::kEnableFillOnAccountSelect); // Load a form with no username and update test data. LoadHTML(kVisibleFormWithNoUsernameHTML); username_element_.reset(); WebDocument document = GetMainFrame()->document(); WebElement element = document.getElementById(WebString::fromUTF8(kPasswordName)); ASSERT_FALSE(element.isNull()); password_element_ = element.to(); fill_data_.username_field = FormFieldData(); UpdateOriginForHTML(kVisibleFormWithNoUsernameHTML); fill_data_.additional_logins.clear(); fill_data_.other_possible_usernames.clear(); password_element_.setValue(""); password_element_.setAutofilled(false); // Simulate the browser sending back the login info for an initial page load. SimulateOnFillPasswordForm(fill_data_); EXPECT_TRUE(password_element_.suggestedValue().isEmpty()); EXPECT_TRUE(password_element_.isAutofilled()); } TEST_F(PasswordAutofillAgentTest, ShowPopupOnEmptyPasswordField) { // Load a form with no username and update test data. LoadHTML(kVisibleFormWithNoUsernameHTML); username_element_.reset(); WebDocument document = GetMainFrame()->document(); WebElement element = document.getElementById(WebString::fromUTF8(kPasswordName)); ASSERT_FALSE(element.isNull()); password_element_ = element.to(); fill_data_.username_field = FormFieldData(); UpdateOriginForHTML(kVisibleFormWithNoUsernameHTML); fill_data_.additional_logins.clear(); fill_data_.other_possible_usernames.clear(); password_element_.setValue(""); password_element_.setAutofilled(false); // Simulate the browser sending back the login info for an initial page load. SimulateOnFillPasswordForm(fill_data_); // Show popup suggesstion when the password field is empty. password_element_.setValue(""); password_element_.setAutofilled(false); SimulateSuggestionChoiceOfUsernameAndPassword( password_element_, base::string16(), ASCIIToUTF16(kAlicePassword)); CheckSuggestions(std::string(), false); EXPECT_EQ(ASCIIToUTF16(kAlicePassword), password_element_.value()); EXPECT_TRUE(password_element_.isAutofilled()); } TEST_F(PasswordAutofillAgentTest, ShowPopupOnAutofilledPasswordField) { // Load a form with no username and update test data. LoadHTML(kVisibleFormWithNoUsernameHTML); username_element_.reset(); WebDocument document = GetMainFrame()->document(); WebElement element = document.getElementById(WebString::fromUTF8(kPasswordName)); ASSERT_FALSE(element.isNull()); password_element_ = element.to(); fill_data_.username_field = FormFieldData(); UpdateOriginForHTML(kVisibleFormWithNoUsernameHTML); fill_data_.additional_logins.clear(); fill_data_.other_possible_usernames.clear(); password_element_.setValue(""); password_element_.setAutofilled(false); // Simulate the browser sending back the login info for an initial page load. SimulateOnFillPasswordForm(fill_data_); // Show popup suggesstion when the password field is autofilled. password_element_.setValue("123"); password_element_.setAutofilled(true); SimulateSuggestionChoiceOfUsernameAndPassword( password_element_, base::string16(), ASCIIToUTF16(kAlicePassword)); CheckSuggestions(std::string(), false); EXPECT_EQ(ASCIIToUTF16(kAlicePassword), password_element_.value()); EXPECT_TRUE(password_element_.isAutofilled()); } TEST_F(PasswordAutofillAgentTest, NotShowPopupPasswordField) { // Load a form with no username and update test data. LoadHTML(kVisibleFormWithNoUsernameHTML); username_element_.reset(); WebDocument document = GetMainFrame()->document(); WebElement element = document.getElementById(WebString::fromUTF8(kPasswordName)); ASSERT_FALSE(element.isNull()); password_element_ = element.to(); fill_data_.username_field = FormFieldData(); UpdateOriginForHTML(kVisibleFormWithNoUsernameHTML); fill_data_.additional_logins.clear(); fill_data_.other_possible_usernames.clear(); password_element_.setValue(""); password_element_.setAutofilled(false); // Simulate the browser sending back the login info for an initial page load. SimulateOnFillPasswordForm(fill_data_); // Do not show popup suggesstion when the password field is not-empty and not // autofilled. password_element_.setValue("123"); password_element_.setAutofilled(false); SimulateSuggestionChoiceOfUsernameAndPassword( password_element_, base::string16(), ASCIIToUTF16(kAlicePassword)); ASSERT_FALSE(render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_ShowPasswordSuggestions::ID)); } // Tests with fill-on-account-select enabled that if the username element is // read-only and filled with an unknown username, then the password field is not // highlighted as autofillable (regression test for https://crbug.com/442564). TEST_F(PasswordAutofillAgentTest, FillOnAccountSelectOnlyReadonlyUnknownUsername) { base::CommandLine::ForCurrentProcess()->AppendSwitch( autofill::switches::kEnableFillOnAccountSelect); ClearUsernameAndPasswordFields(); username_element_.setValue("foobar"); SetElementReadOnly(username_element_, true); CheckTextFieldsState(std::string("foobar"), false, std::string(), false); } // Test that the last plain text field before a password field is chosen as a // username, in a form with 2 plain text fields without username predictions. TEST_F(PasswordAutofillAgentTest, FindingUsernameWithoutAutofillPredictions) { LoadHTML(kFormHTMLWithTwoTextFields); UpdateUsernameAndPasswordElements(); blink::WebInputElement email_element = GetInputElementByID(kEmailName); SimulateUsernameChange("temp"); SimulateUserInputChangeForElement(&email_element, "temp@google.com"); SimulatePasswordChange("random"); static_cast(password_autofill_agent_) ->WillSendSubmitEvent(username_element_.form()); static_cast(password_autofill_agent_) ->WillSubmitForm(username_element_.form()); // Observe that the PasswordAutofillAgent identifies the second field (e-mail) // as username. ExpectFormSubmittedWithUsernameAndPasswords("temp@google.com", "random", ""); } // Tests that field predictions are followed when identifying the username // and password in a password form with two plain text fields. TEST_F(PasswordAutofillAgentTest, FindingFieldsWithAutofillPredictions) { LoadHTML(kFormHTMLWithTwoTextFields); UpdateUsernameAndPasswordElements(); blink::WebInputElement email_element = GetInputElementByID(kEmailName); 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; ASSERT_TRUE( WebFormElementToFormData(form_element, blink::WebFormControlElement(), form_util::EXTRACT_NONE, &form_data, nullptr)); // Simulate Autofill predictions: the first field is username, the third // one is password. std::map predictions; predictions[form_data][form_data.fields[0]] = PREDICTION_USERNAME; predictions[form_data][form_data.fields[2]] = PREDICTION_NEW_PASSWORD; AutofillMsg_AutofillUsernameAndPasswordDataReceived msg(0, predictions); static_cast(password_autofill_agent_) ->OnMessageReceived(msg); // The predictions should still match even if the form changes, as long // as the particular elements don't change. std::string add_field_to_form = "var form = document.getElementById('LoginTestForm');" "var new_input = document.createElement('input');" "new_input.setAttribute('type', 'text');" "new_input.setAttribute('id', 'other_field');" "form.appendChild(new_input);"; ExecuteJavaScriptForTests(add_field_to_form.c_str()); static_cast(password_autofill_agent_) ->WillSendSubmitEvent(username_element_.form()); static_cast(password_autofill_agent_) ->WillSubmitForm(username_element_.form()); // Observe that the PasswordAutofillAgent identifies the first field as // username. // TODO(msramek): We should also test that adding another password field // won't override the password field prediction either. However, the password // field predictions are not taken into account yet. ExpectFormSubmittedWithUsernameAndPasswords("temp", "random", ""); } // The user types in a username and a password. Then JavaScript changes password // field to readonly state before submit. PasswordAutofillAgent can correctly // process readonly password field. This test models behaviour of gmail.com. TEST_F(PasswordAutofillAgentTest, ReadonlyPasswordFieldOnSubmit) { SimulateUsernameChange("temp"); SimulatePasswordChange("random"); // Simulate that JavaScript makes password field readonly. SetElementReadOnly(password_element_, true); static_cast(password_autofill_agent_) ->WillSubmitForm(username_element_.form()); // Observe that the PasswordAutofillAgent can correctly process submitted // form. ExpectFormSubmittedWithUsernameAndPasswords("temp", "random", ""); } // Verify that typed passwords are saved correctly when autofill and generation // both trigger. Regression test for https://crbug.com/493455 TEST_F(PasswordAutofillAgentTest, PasswordGenerationTriggered_TypedPassword) { SimulateOnFillPasswordForm(fill_data_); SetNotBlacklistedMessage(password_generation_, kFormHTML); SetAccountCreationFormsDetectedMessage(password_generation_, GetMainFrame()->document(), 0, 2); SimulateUsernameChange("NewGuy"); SimulatePasswordChange("NewPassword"); static_cast(password_autofill_agent_) ->WillSendSubmitEvent(username_element_.form()); static_cast(password_autofill_agent_) ->WillSubmitForm(username_element_.form()); ExpectFormSubmittedWithUsernameAndPasswords("NewGuy", "NewPassword", ""); } // Verify that generated passwords are saved correctly when autofill and // generation both trigger. Regression test for https://crbug.com/493455. TEST_F(PasswordAutofillAgentTest, PasswordGenerationTriggered_GeneratedPassword) { SimulateOnFillPasswordForm(fill_data_); SetNotBlacklistedMessage(password_generation_, kFormHTML); SetAccountCreationFormsDetectedMessage(password_generation_, GetMainFrame()->document(), 0, 2); base::string16 password = base::ASCIIToUTF16("NewPass22"); AutofillMsg_GeneratedPasswordAccepted msg(0, password); password_generation_->OnMessageReceived(msg); static_cast(password_autofill_agent_) ->WillSendSubmitEvent(username_element_.form()); static_cast(password_autofill_agent_) ->WillSubmitForm(username_element_.form()); ExpectFormSubmittedWithUsernameAndPasswords(kAliceUsername, "NewPass22", ""); } // If password generation is enabled for a field, password autofill should not // show UI. TEST_F(PasswordAutofillAgentTest, PasswordGenerationSupersedesAutofill) { LoadHTML(kSignupFormHTML); // Update password_element_; WebDocument document = GetMainFrame()->document(); WebElement element = document.getElementById(WebString::fromUTF8("new_password")); ASSERT_FALSE(element.isNull()); password_element_ = element.to(); // Update fill_data_ for the new form and simulate filling. Pretend as if // the password manager didn't detect a username field so it will try to // show UI when the password field is focused. fill_data_.wait_for_username = true; fill_data_.username_field = FormFieldData(); fill_data_.password_field.name = base::ASCIIToUTF16("new_password"); UpdateOriginForHTML(kSignupFormHTML); SimulateOnFillPasswordForm(fill_data_); // Simulate generation triggering. SetNotBlacklistedMessage(password_generation_, kSignupFormHTML); SetAccountCreationFormsDetectedMessage(password_generation_, GetMainFrame()->document(), 0, 1); // Simulate the field being clicked to start typing. This should trigger // generation but not password autofill. SetFocused(password_element_); SimulateElementClick("new_password"); EXPECT_EQ(nullptr, render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_ShowPasswordSuggestions::ID)); EXPECT_TRUE(render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_ShowPasswordGenerationPopup::ID)); } // Tests that a password change form is properly filled with the username and // password. TEST_F(PasswordAutofillAgentTest, FillSuggestionPasswordChangeForms) { LoadHTML(kPasswordChangeFormHTML); UpdateOriginForHTML(kPasswordChangeFormHTML); UpdateUsernameAndPasswordElements(); // Simulate the browser sending the login info, but set |wait_for_username| // to prevent the form from being immediately filled. fill_data_.wait_for_username = true; fill_data_.is_possible_change_password_form = true; SimulateOnFillPasswordForm(fill_data_); // Neither field should have been autocompleted. CheckTextFieldsDOMState(std::string(), false, std::string(), false); EXPECT_TRUE(password_autofill_agent_->FillSuggestion( username_element_, kAliceUsername, kAlicePassword)); CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true); } // Tests that a password change form is properly filled with the password when // the user click on the password field. TEST_F(PasswordAutofillAgentTest, FillSuggestionPasswordChangeFormsOnlyPassword) { LoadHTML(kPasswordChangeFormHTML); UpdateOriginForHTML(kPasswordChangeFormHTML); UpdateUsernameAndPasswordElements(); // Simulate the browser sending the login info, but set |wait_for_username| // to prevent the form from being immediately filled. fill_data_.wait_for_username = true; fill_data_.is_possible_change_password_form = true; SimulateOnFillPasswordForm(fill_data_); // Neither field should have been autocompleted. CheckTextFieldsDOMState(std::string(), false, std::string(), false); EXPECT_TRUE(password_autofill_agent_->FillSuggestion( password_element_, kAliceUsername, kAlicePassword)); CheckTextFieldsDOMState("", false, kAlicePassword, true); } // Tests that one user click on a username field is sufficient to bring up a // credential suggestion popup on a change password form. TEST_F(PasswordAutofillAgentTest, SuggestionsOnUsernameFieldOfChangePasswordForm) { LoadHTML(kPasswordChangeFormHTML); UpdateOriginForHTML(kPasswordChangeFormHTML); UpdateUsernameAndPasswordElements(); ClearUsernameAndPasswordFields(); fill_data_.wait_for_username = true; fill_data_.is_possible_change_password_form = true; SimulateOnFillPasswordForm(fill_data_); // Simulate a user clicking on the username element. This should produce a // message. render_thread_->sink().ClearMessages(); static_cast(autofill_agent_) ->FormControlElementClicked(username_element_, true); CheckSuggestions("", true); } // Tests that one user click on a password field is sufficient to bring up a // credential suggestion popup on a change password form. TEST_F(PasswordAutofillAgentTest, SuggestionsOnPasswordFieldOfChangePasswordForm) { LoadHTML(kPasswordChangeFormHTML); UpdateOriginForHTML(kPasswordChangeFormHTML); UpdateUsernameAndPasswordElements(); ClearUsernameAndPasswordFields(); fill_data_.wait_for_username = true; fill_data_.is_possible_change_password_form = true; SimulateOnFillPasswordForm(fill_data_); // Simulate a user clicking on the password element. This should produce a // message. render_thread_->sink().ClearMessages(); static_cast(autofill_agent_) ->FormControlElementClicked(password_element_, true); CheckSuggestions("", false); } // Tests that there are no autosuggestions from the password manager when the // user clicks on the password field of change password form after the user // typed in the username field. TEST_F(PasswordAutofillAgentTest, NoSuggestionsOnPasswordFieldOfChangePasswordFormAfterUsernameTyping) { LoadHTML(kPasswordChangeFormHTML); UpdateOriginForHTML(kPasswordChangeFormHTML); UpdateUsernameAndPasswordElements(); ClearUsernameAndPasswordFields(); fill_data_.wait_for_username = true; fill_data_.is_possible_change_password_form = true; SimulateOnFillPasswordForm(fill_data_); // Clear the text fields to start fresh. SimulateUsernameChange("temp"); // Simulate a user clicking on the password element. This should produce no // message. render_thread_->sink().ClearMessages(); static_cast(autofill_agent_) ->FormControlElementClicked(password_element_, false); EXPECT_FALSE(render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_ShowPasswordSuggestions::ID)); } // Tests that NOT_PASSWORD field predictions are followed so that no password // form is submitted. TEST_F(PasswordAutofillAgentTest, IgnoreNotPasswordFields) { LoadHTML(kCreditCardFormHTML); blink::WebInputElement credit_card_owner_element = GetInputElementByID(kCreditCardOwnerName); blink::WebInputElement credit_card_number_element = GetInputElementByID(kCreditCardNumberName); blink::WebInputElement credit_card_verification_element = GetInputElementByID(kCreditCardVerificationName); SimulateUserInputChangeForElement(&credit_card_owner_element, "JohnSmith"); SimulateUserInputChangeForElement(&credit_card_number_element, "1234123412341234"); SimulateUserInputChangeForElement(&credit_card_verification_element, "123"); // Find FormData for visible form. blink::WebFormElement form_element = credit_card_number_element.form(); FormData form_data; ASSERT_TRUE( WebFormElementToFormData(form_element, blink::WebFormControlElement(), form_util::EXTRACT_NONE, &form_data, nullptr)); // Simulate Autofill predictions: the third field is not a password. std::map predictions; predictions[form_data][form_data.fields[2]] = PREDICTION_NOT_PASSWORD; AutofillMsg_AutofillUsernameAndPasswordDataReceived msg(0, predictions); static_cast(password_autofill_agent_) ->OnMessageReceived(msg); static_cast(password_autofill_agent_) ->WillSendSubmitEvent(form_element); static_cast(password_autofill_agent_) ->WillSubmitForm(form_element); const IPC::Message* message = render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_PasswordFormSubmitted::ID); ASSERT_FALSE(message); } // Tests that only the password field is autocompleted when the browser sends // back data with only one credentials and empty username. TEST_F(PasswordAutofillAgentTest, NotAutofillNoUsername) { fill_data_.username_field.value.clear(); fill_data_.additional_logins.clear(); SimulateOnFillPasswordForm(fill_data_); CheckTextFieldsState("", false, kAlicePassword, true); } // Tests that both the username and the password fields are autocompleted // despite the empty username, when the browser sends back data more than one // credential. TEST_F(PasswordAutofillAgentTest, AutofillNoUsernameWhenOtherCredentialsStored) { fill_data_.username_field.value.clear(); ASSERT_FALSE(fill_data_.additional_logins.empty()); SimulateOnFillPasswordForm(fill_data_); CheckTextFieldsState("", true, kAlicePassword, true); } TEST_F(PasswordAutofillAgentTest, NoForm_PromptForAJAXSubmitWithoutNavigation) { LoadHTML(kNoFormHTML); UpdateUsernameAndPasswordElements(); SimulateUsernameChange("Bob"); SimulatePasswordChange("mypassword"); username_element_.setAttribute("style", "display:none;"); password_element_.setAttribute("style", "display:none;"); password_autofill_agent_->AJAXSucceeded(); ExpectInPageNavigationWithUsernameAndPasswords("Bob", "mypassword", ""); } TEST_F(PasswordAutofillAgentTest, NoForm_NoPromptForAJAXSubmitWithoutNavigationAndElementsVisible) { LoadHTML(kNoFormHTML); UpdateUsernameAndPasswordElements(); SimulateUsernameChange("Bob"); SimulatePasswordChange("mypassword"); password_autofill_agent_->AJAXSucceeded(); const IPC::Message* message = render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_PasswordFormSubmitted::ID); ASSERT_FALSE(message); } // Tests that credential suggestions are autofilled on a password (and change // password) forms having either ambiguous or empty name. TEST_F(PasswordAutofillAgentTest, SuggestionsOnFormContainingAmbiguousOrEmptyNames) { const char kEmpty[] = ""; const char kDummyUsernameField[] = "anonymous_username"; const char kDummyPasswordField[] = "anonymous_password"; const char kFormContainsEmptyNamesHTML[] = "
" " " " " " " "
"; const char kFormContainsAmbiguousNamesHTML[] = "
" " " " " " " "
"; const char kChangePasswordFormContainsEmptyNamesHTML[] = "
" " " " " " " " " "
"; const char kChangePasswordFormButNoUsername[] = "
" " " " " " " "
"; const char kChangePasswordFormButNoOldPassword[] = "
" " " " " " " " " "
"; const char kChangePasswordFormButNoAutocompleteAttribute[] = "
" " " " " " " " " "
"; const struct { const char* html_form; bool is_possible_change_password_form; bool does_trigger_autocomplete_on_fill; const char* fill_data_username_field_name; const char* fill_data_password_field_name; const char* expected_username_suggestions; const char* expected_password_suggestions; bool expected_is_username_autofillable; bool expected_is_password_autofillable; } test_cases[] = { // Password form without name or id attributes specified for the input // fields. {kFormContainsEmptyNamesHTML, false, true, kDummyUsernameField, kDummyPasswordField, kAliceUsername, kAlicePassword, true, true}, // Password form with ambiguous name or id attributes specified for the // input fields. {kFormContainsAmbiguousNamesHTML, false, true, "credentials", "credentials", kAliceUsername, kAlicePassword, true, true}, // Change password form without name or id attributes specified for the // input fields and |autocomplete='current-password'| attribute for old // password field. {kChangePasswordFormContainsEmptyNamesHTML, true, true, kDummyUsernameField, kDummyPasswordField, kAliceUsername, kAlicePassword, true, true}, // Change password form without username field. {kChangePasswordFormButNoUsername, true, true, kEmpty, kDummyPasswordField, kEmpty, kAlicePassword, false, true}, // Change password form without name or id attributes specified for the // input fields and |autocomplete='new-password'| attribute for new // password fields. This form *do not* trigger |OnFillPasswordForm| from // browser. {kChangePasswordFormButNoOldPassword, true, false, kDummyUsernameField, kDummyPasswordField, kEmpty, kEmpty, false, false}, // Change password form without name or id attributes specified for the // input fields but |autocomplete='current-password'| or // |autocomplete='new-password'| attributes are missing for old and new // password fields respectively. {kChangePasswordFormButNoAutocompleteAttribute, true, true, kDummyUsernameField, kDummyPasswordField, kAliceUsername, kAlicePassword, true, true}, }; for (const auto& test_case : test_cases) { SCOPED_TRACE(testing::Message() << "html_form: " << test_case.html_form << ", fill_data_username_field_name: " << test_case.fill_data_username_field_name << ", fill_data_password_field_name: " << test_case.fill_data_password_field_name); // Load a password form. LoadHTML(test_case.html_form); // Get the username and password form input elelments. blink::WebDocument document = GetMainFrame()->document(); blink::WebVector forms; document.forms(forms); blink::WebFormElement form_element = forms[0]; std::vector control_elements = form_util::ExtractAutofillableElementsInForm(form_element); bool has_fillable_username = (kEmpty != test_case.fill_data_username_field_name); if (has_fillable_username) { username_element_ = control_elements[0].to(); password_element_ = control_elements[1].to(); } else { password_element_ = control_elements[0].to(); } UpdateOriginForHTML(test_case.html_form); if (test_case.does_trigger_autocomplete_on_fill) { // Prepare |fill_data_| to trigger autocomplete. fill_data_.is_possible_change_password_form = test_case.is_possible_change_password_form; fill_data_.username_field.name = ASCIIToUTF16(test_case.fill_data_username_field_name); fill_data_.password_field.name = ASCIIToUTF16(test_case.fill_data_password_field_name); fill_data_.additional_logins.clear(); fill_data_.other_possible_usernames.clear(); ClearUsernameAndPasswordFields(); // Simulate the browser sending back the login info, it triggers the // autocomplete. SimulateOnFillPasswordForm(fill_data_); if (has_fillable_username) { SimulateSuggestionChoice(username_element_); } else { SimulateSuggestionChoice(password_element_); } // The username and password should now have been autocompleted. CheckTextFieldsDOMState(test_case.expected_username_suggestions, test_case.expected_is_username_autofillable, test_case.expected_password_suggestions, test_case.expected_is_password_autofillable); } } } // The password manager autofills credentials, the user chooses another // credentials option from a suggestion dropdown and then the user submits a // form. This test verifies that the browser process receives submitted // username/password from the renderer process. TEST_F(PasswordAutofillAgentTest, RememberChosenUsernamePassword) { SimulateOnFillPasswordForm(fill_data_); SimulateSuggestionChoiceOfUsernameAndPassword(username_element_, ASCIIToUTF16(kBobUsername), ASCIIToUTF16(kBobPassword)); static_cast(password_autofill_agent_) ->WillSendSubmitEvent(username_element_.form()); static_cast(password_autofill_agent_) ->WillSubmitForm(username_element_.form()); // Observe that the PasswordAutofillAgent sends to the browser selected // credentials. ExpectFormSubmittedWithUsernameAndPasswords(kBobUsername, kBobPassword, ""); } // Tests that we can correctly suggest to autofill two forms without username // fields. TEST_F(PasswordAutofillAgentTest, ShowSuggestionForNonUsernameFieldForms) { LoadHTML(kTwoNoUsernameFormsHTML); fill_data_.username_field.name.clear(); fill_data_.username_field.value.clear(); UpdateOriginForHTML(kTwoNoUsernameFormsHTML); SimulateOnFillPasswordForm(fill_data_); SimulateElementClick("password1"); CheckSuggestions(std::string(), false); SimulateElementClick("password2"); CheckSuggestions(std::string(), false); } TEST_F(PasswordAutofillAgentTest, UsernameChangedAfterPasswordInput_InPageNavigation) { LoadHTML(kNoFormHTML); UpdateUsernameAndPasswordElements(); SimulateUsernameChange("Bob"); SimulatePasswordChange("mypassword"); SimulateUsernameChange("Alice"); // Hide form elements to simulate successful login. username_element_.setAttribute("style", "display:none;"); password_element_.setAttribute("style", "display:none;"); password_autofill_agent_->AJAXSucceeded(); ExpectInPageNavigationWithUsernameAndPasswords("Alice", "mypassword", ""); } TEST_F(PasswordAutofillAgentTest, UsernameChangedAfterPasswordInput_FormSubmitted) { SimulateUsernameChange("Bob"); SimulatePasswordChange("mypassword"); SimulateUsernameChange("Alice"); static_cast(password_autofill_agent_) ->WillSendSubmitEvent(username_element_.form()); static_cast(password_autofill_agent_) ->WillSubmitForm(username_element_.form()); ExpectFormSubmittedWithUsernameAndPasswords("Alice", "mypassword", ""); } } // namespace autofill