diff options
author | gcasto <gcasto@chromium.org> | 2015-06-11 12:39:01 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-06-11 19:39:23 +0000 |
commit | 2f3f266efe6e18df27705fb2530489bcb42269d3 (patch) | |
tree | 70c9bfb9c5ff61bdd0a2824f2096c4d96348217a | |
parent | fe9483ef6c14824c57ba6261d969e7cda91203d1 (diff) | |
download | chromium_src-2f3f266efe6e18df27705fb2530489bcb42269d3.zip chromium_src-2f3f266efe6e18df27705fb2530489bcb42269d3.tar.gz chromium_src-2f3f266efe6e18df27705fb2530489bcb42269d3.tar.bz2 |
[Password Manager] Support <input> elements not in a <form> element
BUG=451644
Review URL: https://codereview.chromium.org/1117983003
Cr-Commit-Position: refs/heads/master@{#334010}
16 files changed, 545 insertions, 324 deletions
diff --git a/chrome/browser/password_manager/password_manager_browsertest.cc b/chrome/browser/password_manager/password_manager_browsertest.cc index 3a41c29..e0151f6 100644 --- a/chrome/browser/password_manager/password_manager_browsertest.cc +++ b/chrome/browser/password_manager/password_manager_browsertest.cc @@ -339,6 +339,12 @@ class PasswordManagerBrowserTest : public InProcessBrowserTest { observer.Wait(); } + void VerifyPasswordIsSavedAndFilled(const std::string& filename, + const std::string& submission_script, + const std::string& second_load_script, + const std::string& expected_element, + const std::string& expected_value); + // Waits until the "value" attribute of the HTML element with |element_id| is // equal to |expected_value|. If the current value is not as expected, this // waits until the "change" event is fired for the element. This also @@ -465,6 +471,48 @@ void PasswordManagerBrowserTest::CheckElementValue( << ", expected_value = " << expected_value; } +void PasswordManagerBrowserTest::VerifyPasswordIsSavedAndFilled( + const std::string& filename, + const std::string& submission_script, + const std::string& second_load_script, + const std::string& expected_element, + const std::string& expected_value) { + password_manager::TestPasswordStore* password_store = + static_cast<password_manager::TestPasswordStore*>( + PasswordStoreFactory::GetForProfile( + browser()->profile(), ServiceAccessType::IMPLICIT_ACCESS).get()); + EXPECT_TRUE(password_store->IsEmpty()); + + NavigateToFile(filename); + + NavigationObserver observer(WebContents()); + scoped_ptr<PromptObserver> prompt_observer( + PromptObserver::Create(WebContents())); + ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), submission_script)); + observer.Wait(); + + prompt_observer->Accept(); + + // Spin the message loop to make sure the password store had a chance to save + // the password. + base::RunLoop run_loop; + run_loop.RunUntilIdle(); + EXPECT_FALSE(password_store->IsEmpty()); + + NavigateToFile(filename); + + // Let the user interact with the page, so that DOM gets modification events, + // needed for autofilling fields. + content::SimulateMouseClickAt( + WebContents(), 0, blink::WebMouseEvent::ButtonLeft, gfx::Point(1, 1)); + + ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), + second_load_script)); + + // Wait until that interaction causes the password value to be revealed. + WaitForElementValue(expected_element, expected_value); +} + // Actual tests --------------------------------------------------------------- IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, PromptForNormalSubmit) { @@ -1522,44 +1570,11 @@ IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, AutofillSuggetionsForPasswordFormWithoutUsernameField) { - password_manager::TestPasswordStore* password_store = - static_cast<password_manager::TestPasswordStore*>( - PasswordStoreFactory::GetForProfile( - browser()->profile(), ServiceAccessType::IMPLICIT_ACCESS).get()); - - EXPECT_TRUE(password_store->IsEmpty()); - - // Password form without username-field. - NavigateToFile("/password/form_with_only_password_field.html"); - - NavigationObserver observer(WebContents()); - scoped_ptr<PromptObserver> prompt_observer( - PromptObserver::Create(WebContents())); std::string submit = "document.getElementById('password').value = 'mypassword';" "document.getElementById('submit-button').click();"; - ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), submit)); - observer.Wait(); - - prompt_observer->Accept(); - - // Spin the message loop to make sure the password store had a chance to save - // the password. - base::RunLoop run_loop; - run_loop.RunUntilIdle(); - EXPECT_FALSE(password_store->IsEmpty()); - - // Now, navigate to same html password form and verify whether password is - // autofilled. - NavigateToFile("/password/form_with_only_password_field.html"); - - // Let the user interact with the page, so that DOM gets modification events, - // needed for autofilling fields. - content::SimulateMouseClickAt( - WebContents(), 0, blink::WebMouseEvent::ButtonLeft, gfx::Point(1, 1)); - - // Wait until that interaction causes the password value to be revealed. - WaitForElementValue("password", "mypassword"); + VerifyPasswordIsSavedAndFilled("/password/form_with_only_password_field.html", + submit, "", "password", "mypassword"); } // Test that if a form gets autofilled, then it gets autofilled on re-creation @@ -1897,85 +1912,23 @@ IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, // login form still gets autofilled. IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, AutofillSuggetionsForLoginSignupForm) { - password_manager::TestPasswordStore* password_store = - static_cast<password_manager::TestPasswordStore*>( - PasswordStoreFactory::GetForProfile( - browser()->profile(), ServiceAccessType::IMPLICIT_ACCESS).get()); - - EXPECT_TRUE(password_store->IsEmpty()); - - NavigateToFile("/password/login_signup_form.html"); - - NavigationObserver observer(WebContents()); - scoped_ptr<PromptObserver> prompt_observer( - PromptObserver::Create(WebContents())); std::string submit = "document.getElementById('username').value = 'myusername';" "document.getElementById('password').value = 'mypassword';" "document.getElementById('submit').click();"; - ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), submit)); - observer.Wait(); - - prompt_observer->Accept(); - - // Spin the message loop to make sure the password store had a chance to save - // the password. - base::RunLoop run_loop; - run_loop.RunUntilIdle(); - EXPECT_FALSE(password_store->IsEmpty()); - - // Now, navigate to the same html password form and verify whether password is - // autofilled. - NavigateToFile("/password/login_signup_form.html"); - - // Let the user interact with the page, so that DOM gets modification events, - // needed for autofilling fields. - content::SimulateMouseClickAt( - WebContents(), 0, blink::WebMouseEvent::ButtonLeft, gfx::Point(1, 1)); - - // Wait until that interaction causes the password value to be revealed. - WaitForElementValue("password", "mypassword"); + VerifyPasswordIsSavedAndFilled("/password/login_signup_form.html", + submit, "", "password", "mypassword"); } // Check that we can fill in cases where <base href> is set and the action of // the form is not set. Regression test for https://crbug.com/360230. IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, BaseTagWithNoActionTest) { - password_manager::TestPasswordStore* password_store = - static_cast<password_manager::TestPasswordStore*>( - PasswordStoreFactory::GetForProfile( - browser()->profile(), ServiceAccessType::IMPLICIT_ACCESS).get()); - - EXPECT_TRUE(password_store->IsEmpty()); - - NavigateToFile("/password/password_xhr_submit.html"); - - NavigationObserver observer(WebContents()); - scoped_ptr<PromptObserver> prompt_observer( - PromptObserver::Create(WebContents())); std::string submit = "document.getElementById('username_field').value = 'myusername';" "document.getElementById('password_field').value = 'mypassword';" "document.getElementById('submit_button').click();"; - ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), submit)); - observer.Wait(); - - prompt_observer->Accept(); - - // Spin the message loop to make sure the password store had a chance to save - // the password. - base::RunLoop run_loop; - run_loop.RunUntilIdle(); - EXPECT_FALSE(password_store->IsEmpty()); - - NavigateToFile("/password/password_xhr_submit.html"); - - // Let the user interact with the page, so that DOM gets modification events, - // needed for autofilling fields. - content::SimulateMouseClickAt( - WebContents(), 0, blink::WebMouseEvent::ButtonLeft, gfx::Point(1, 1)); - - // Wait until that interaction causes the password value to be revealed. - WaitForElementValue("password_field", "mypassword"); + VerifyPasswordIsSavedAndFilled("/password/password_xhr_submit.html", + submit, "", "password_field", "mypassword"); } // Check that a password form in an iframe of different origin will not be @@ -2125,5 +2078,34 @@ IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, // Verify username has been autofilled CheckElementValue("iframe", "username_field", "temp"); +} +IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, NoFormElementTest) { + std::string submit = + "create_input_fields(true);" + "document.getElementById('username_field').value = 'myusername';" + "document.getElementById('password_field').value = 'mypassword';" + "send_xhr();"; + std::string create_fields = "create_input_fields(true);"; + VerifyPasswordIsSavedAndFilled("/password/no_form_element.html", + submit, + create_fields, + "password_field", + "mypassword"); +} + +// Verify that we correctly save and fill elements outside a <form> if they +// are identifed by "id" instead of "name". +IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, NoFormElementNoNameTest) { + std::string submit = + "create_input_fields(false);" + "document.getElementById('username_field').value = 'myusername';" + "document.getElementById('password_field').value = 'mypassword';" + "send_xhr();"; + std::string create_fields = "create_input_fields(false);"; + VerifyPasswordIsSavedAndFilled("/password/no_form_element.html", + submit, + create_fields, + "password_field", + "mypassword"); } diff --git a/chrome/renderer/autofill/form_autofill_browsertest.cc b/chrome/renderer/autofill/form_autofill_browsertest.cc index 88d8994..e2efd96 100644 --- a/chrome/renderer/autofill/form_autofill_browsertest.cc +++ b/chrome/renderer/autofill/form_autofill_browsertest.cc @@ -3958,7 +3958,7 @@ TEST_F(FormAutofillTest, ASSERT_EQ(2U, fieldsets.size()); FormData form; - EXPECT_TRUE(UnownedFormElementsAndFieldSetsToFormData( + EXPECT_TRUE(UnownedCheckoutFormElementsAndFieldSetsToFormData( fieldsets, control_elements, nullptr, frame->document(), extract_mask, &form, nullptr)); @@ -4018,7 +4018,7 @@ TEST_F(FormAutofillTest, ASSERT_EQ(1U, fieldsets.size()); FormData form; - EXPECT_TRUE(UnownedFormElementsAndFieldSetsToFormData( + EXPECT_TRUE(UnownedCheckoutFormElementsAndFieldSetsToFormData( fieldsets, control_elements, nullptr, frame->document(), extract_mask, &form, nullptr)); @@ -4067,7 +4067,7 @@ TEST_F(FormAutofillTest, UnownedFormElementsAndFieldSetsToFormDataWithForm) { ASSERT_TRUE(fieldsets.empty()); FormData form; - EXPECT_FALSE(UnownedFormElementsAndFieldSetsToFormData( + EXPECT_FALSE(UnownedCheckoutFormElementsAndFieldSetsToFormData( fieldsets, control_elements, nullptr, frame->document(), extract_mask, &form, nullptr)); } diff --git a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc index 11a4e11..ea85042 100644 --- a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc +++ b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc @@ -175,6 +175,10 @@ const char kFormHTMLWithTwoTextFields[] = " <INPUT type='submit' value='Login'/>" "</FORM>"; +const char kNoFormHTML[] = + " <INPUT type='text' id='username'/>" + " <INPUT type='password' id='password'/>"; + // 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"), @@ -420,6 +424,22 @@ class PasswordAutofillAgentTest : public ChromeRenderViewTest { base::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); + base::Tuple<autofill::PasswordForm> args; + AutofillHostMsg_InPageNavigation::Read(message, &args); + EXPECT_EQ(ASCIIToUTF16(username_value), base::get<0>(args).username_value); + EXPECT_EQ(ASCIIToUTF16(password_value), base::get<0>(args).password_value); + EXPECT_EQ(ASCIIToUTF16(new_password_value), + base::get<0>(args).new_password_value); + } + base::string16 username1_; base::string16 username2_; base::string16 username3_; @@ -1863,4 +1883,35 @@ TEST_F(PasswordAutofillAgentTest, ExpectFormSubmittedWithUsernameAndPasswords(kAliceUsername, "NewPass22", ""); } +TEST_F(PasswordAutofillAgentTest, NoForm_PromptForXHRSubmitWithoutNavigation) { + LoadHTML(kNoFormHTML); + UpdateUsernameAndPasswordElements(); + + SimulateUsernameChange("Bob"); + SimulatePasswordChange("mypassword"); + + username_element_.setAttribute("style", "display:none;"); + password_element_.setAttribute("style", "display:none;"); + + password_autofill_agent_->XHRSucceeded(); + + ExpectInPageNavigationWithUsernameAndPasswords("Bob", "mypassword", ""); +} + +TEST_F(PasswordAutofillAgentTest, + NoForm_NoPromptForXHRSubmitWithoutNavigationAndElementsVisible) { + LoadHTML(kNoFormHTML); + UpdateUsernameAndPasswordElements(); + + SimulateUsernameChange("Bob"); + SimulatePasswordChange("mypassword"); + + password_autofill_agent_->XHRSucceeded(); + + const IPC::Message* message = + render_thread_->sink().GetFirstMessageMatching( + AutofillHostMsg_PasswordFormSubmitted::ID); + ASSERT_FALSE(message); +} + } // namespace autofill diff --git a/chrome/test/data/password/no_form_element.html b/chrome/test/data/password/no_form_element.html new file mode 100644 index 0000000..e4ca6b9 --- /dev/null +++ b/chrome/test/data/password/no_form_element.html @@ -0,0 +1,41 @@ +<html> +<head> + <script> + + function state_changed(xhr) { + if (xhr.readyState == 4) { + window.top.location.href = "done.html"; + } + } + + function send_xhr() { + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { state_changed(xhr); }; + xhr.open("GET", "password_xhr_submit.html", true); + xhr.send(null); + } + + function create_input_fields(enable_name_attributes) { + var usernameField = document.createElement('input'); + usernameField.setAttribute('type', 'text'); + usernameField.setAttribute('id', 'username_field'); + if (enable_name_attributes) { + usernameField.setAttribute('name', 'username_field'); + } + + var passwordField = document.createElement('input'); + passwordField.setAttribute('type', 'password'); + passwordField.setAttribute('id', 'password_field'); + if (enable_name_attributes) { + passwordField.setAttribute('name', 'password_field') + } + + document.body.appendChild(usernameField); + document.body.appendChild(passwordField); + } + </script> +</head> +<body> +Sometext +</body> +</html> diff --git a/components/autofill/content/renderer/form_autofill_util.cc b/components/autofill/content/renderer/form_autofill_util.cc index 3a00eb2..ee69592 100644 --- a/components/autofill/content/renderer/form_autofill_util.cc +++ b/components/autofill/content/renderer/form_autofill_util.cc @@ -1086,6 +1086,22 @@ bool FormOrFieldsetsToFormData( return true; } +bool UnownedFormElementsAndFieldSetsToFormData( + const std::vector<blink::WebElement>& fieldsets, + const std::vector<blink::WebFormControlElement>& control_elements, + const blink::WebFormControlElement* element, + const blink::WebDocument& document, + ExtractMask extract_mask, + FormData* form, + FormFieldData* field) { + form->origin = document.url(); + form->user_submitted = false; + form->is_form_tag = false; + + return FormOrFieldsetsToFormData(nullptr, element, fieldsets, + control_elements, extract_mask, form, field); +} + } // namespace const size_t kMaxParseableFields = 200; @@ -1295,7 +1311,7 @@ GetUnownedAutofillableFormFieldElements( return ExtractAutofillableElementsFromSet(unowned_fieldset_children); } -bool UnownedFormElementsAndFieldSetsToFormData( +bool UnownedCheckoutFormElementsAndFieldSetsToFormData( const std::vector<blink::WebElement>& fieldsets, const std::vector<blink::WebFormControlElement>& control_elements, const blink::WebFormControlElement* element, @@ -1304,30 +1320,43 @@ bool UnownedFormElementsAndFieldSetsToFormData( FormData* form, FormFieldData* field) { // Only attempt formless Autofill on checkout flows. This avoids the many - // false positives found on the non-checkout web. See http://crbug.com/462375 - // For now this early abort only applies to English-language pages, because - // the regex is not translated. Note that an empty "lang" attribute counts as - // English. A potential problem is that this only checks document.title(), but - // should actually check the main frame's title. Thus it may make bad - // decisions for iframes. + // false positives found on the non-checkout web. See + // http://crbug.com/462375. For now this early abort only applies to + // English-language pages, because the regex is not translated. Note that + // an empty "lang" attribute counts as English. A potential problem is that + // this only checks document.title(), but should actually check the main + // frame's title. Thus it may make bad decisions for iframes. WebElement html_element = document.documentElement(); std::string lang; if (!html_element.isNull()) lang = html_element.getAttribute("lang").utf8(); if ((lang.empty() || StartsWithASCII(lang, "en", false)) && - !MatchesPattern(document.title(), + !MatchesPattern( + document.title(), base::UTF8ToUTF16("payment|checkout|address|delivery|shipping"))) { return false; } - form->origin = document.url(); - form->user_submitted = false; - form->is_form_tag = false; + return UnownedFormElementsAndFieldSetsToFormData( + fieldsets, control_elements, element, document, extract_mask, form, + field); - return FormOrFieldsetsToFormData(nullptr, element, fieldsets, - control_elements, extract_mask, form, field); } +bool UnownedPasswordFormElementsAndFieldSetsToFormData( + const std::vector<blink::WebElement>& fieldsets, + const std::vector<blink::WebFormControlElement>& control_elements, + const blink::WebFormControlElement* element, + const blink::WebDocument& document, + ExtractMask extract_mask, + FormData* form, + FormFieldData* field) { + return UnownedFormElementsAndFieldSetsToFormData( + fieldsets, control_elements, element, document, extract_mask, form, + field); +} + + bool FindFormAndFieldForFormControlElement(const WebFormControlElement& element, FormData* form, FormFieldData* field) { @@ -1343,7 +1372,7 @@ bool FindFormAndFieldForFormControlElement(const WebFormControlElement& element, std::vector<WebElement> fieldsets; std::vector<WebFormControlElement> control_elements = GetUnownedAutofillableFormFieldElements(document.all(), &fieldsets); - return UnownedFormElementsAndFieldSetsToFormData( + return UnownedCheckoutFormElementsAndFieldSetsToFormData( fieldsets, control_elements, &element, document, extract_mask, form, field); } diff --git a/components/autofill/content/renderer/form_autofill_util.h b/components/autofill/content/renderer/form_autofill_util.h index 1be05cd..ea725f7 100644 --- a/components/autofill/content/renderer/form_autofill_util.h +++ b/components/autofill/content/renderer/form_autofill_util.h @@ -118,7 +118,20 @@ GetUnownedAutofillableFormFieldElements( // representation for |element|. // |extract_mask| usage and the return value are the same as // WebFormElementToFormData() above. -bool UnownedFormElementsAndFieldSetsToFormData( +// This function will return false and not perform any extraction if +// |document| does not pertain to checkout. +bool UnownedCheckoutFormElementsAndFieldSetsToFormData( + const std::vector<blink::WebElement>& fieldsets, + const std::vector<blink::WebFormControlElement>& control_elements, + const blink::WebFormControlElement* element, + const blink::WebDocument& document, + ExtractMask extract_mask, + FormData* form, + FormFieldData* field); + +// Same as above, but without the requirement that the elements only be +// related to checkout. +bool UnownedPasswordFormElementsAndFieldSetsToFormData( const std::vector<blink::WebElement>& fieldsets, const std::vector<blink::WebFormControlElement>& control_elements, const blink::WebFormControlElement* element, diff --git a/components/autofill/content/renderer/form_cache.cc b/components/autofill/content/renderer/form_cache.cc index a79957b..7f37577 100644 --- a/components/autofill/content/renderer/form_cache.cc +++ b/components/autofill/content/renderer/form_cache.cc @@ -142,7 +142,7 @@ std::vector<FormData> FormCache::ExtractNewForms() { return forms; FormData synthetic_form; - if (!UnownedFormElementsAndFieldSetsToFormData( + if (!UnownedCheckoutFormElementsAndFieldSetsToFormData( fieldsets, control_elements, nullptr, document, extract_mask, &synthetic_form, nullptr)) { return forms; diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc index ea8944a..8da7475 100644 --- a/components/autofill/content/renderer/password_autofill_agent.cc +++ b/components/autofill/content/renderer/password_autofill_agent.cc @@ -61,62 +61,60 @@ typedef std::map<base::string16, blink::WebInputElement> FormInputElementMap; // already. typedef SavePasswordProgressLogger Logger; -// Utility struct for form lookup and autofill. When we parse the DOM to look up -// a form, in addition to action and origin URL's we have to compare all -// necessary form elements. To avoid having to look these up again when we want -// to fill the form, the FindFormElements function stores the pointers -// in a FormElements* result, referenced to ensure they are safe to use. -struct FormElements { - blink::WebFormElement form_element; - FormInputElementMap input_elements; -}; - -typedef std::vector<FormElements*> FormElementsList; +typedef std::vector<FormInputElementMap> FormElementsList; bool FillDataContainsUsername(const PasswordFormFillData& fill_data) { return !fill_data.username_field.name.empty(); } -// Utility function to find the unique entry of the |form_element| for the +// Returns true if |control_elements| contains an element named |name| and is +// visible. +bool IsNamedElementVisible( + const std::vector<blink::WebFormControlElement>& control_elements, + const base::string16& name) { + for (size_t i = 0; i < control_elements.size(); ++i) { + if (control_elements[i].nameForAutofill() == name) { + return IsWebNodeVisible(control_elements[i]); + } + } + return false; +} + +// Utility function to find the unique entry of |control_elements| for the // specified input |field|. On successful find, adds it to |result| and returns // |true|. Otherwise clears the references from each |HTMLInputElement| from // |result| and returns |false|. -bool FindFormInputElement(blink::WebFormElement* form_element, - const FormFieldData& field, - FormElements* result) { - blink::WebVector<blink::WebNode> temp_elements; - form_element->getNamedElements(field.name, temp_elements); - +bool FindFormInputElement( + const std::vector<blink::WebFormControlElement>& control_elements, + const FormFieldData& field, + FormInputElementMap* result) { // Match the first input element, if any. - // |getNamedElements| may return non-input elements where the names match, - // so the results are filtered for input elements. // If more than one match is made, then we have ambiguity (due to misuse // of "name" attribute) so is it considered not found. bool found_input = false; - for (size_t i = 0; i < temp_elements.size(); ++i) { - if (temp_elements[i].to<blink::WebElement>().hasHTMLTagName("input")) { - // Check for a non-unique match. - if (found_input) { - found_input = false; - break; - } + for (size_t i = 0; i < control_elements.size(); ++i) { + if (control_elements[i].nameForAutofill() != field.name) + continue; - // Only fill saved passwords into password fields and usernames into - // text fields. - blink::WebInputElement input_element = - temp_elements[i].to<blink::WebInputElement>(); - if (input_element.isPasswordField() != - (field.form_control_type == "password")) - continue; + if (!control_elements[i].hasHTMLTagName("input")) + continue; - // This element matched, add it to our temporary result. It's possible - // there are multiple matches, but for purposes of identifying the form - // one suffices and if some function needs to deal with multiple - // matching elements it can get at them through the FormElement*. - // Note: This assignment adds a reference to the InputElement. - result->input_elements[field.name] = input_element; - found_input = true; + // Check for a non-unique match. + if (found_input) { + found_input = false; + break; } + + // Only fill saved passwords into password fields and usernames into + // text fields. + const blink::WebInputElement input_element = + control_elements[i].toConst<blink::WebInputElement>(); + if (input_element.isPasswordField() != + (field.form_control_type == "password")) + continue; + + (*result)[field.name] = input_element; + found_input = true; } // A required element was not found. This is not the right form. @@ -124,7 +122,7 @@ bool FindFormInputElement(blink::WebFormElement* form_element, // iteration remain in the result set. // Note: clear will remove a reference from each InputElement. if (!found_input) { - result->input_elements.clear(); + result->clear(); return false; } @@ -172,14 +170,15 @@ bool ShouldHighlightFields() { kFillOnAccountSelectFieldTrialEnabledWithNoHighlightGroup; } -// Helper to search the given form element for the specified input elements in -// |data|, and add results to |result|. -bool FindFormInputElements(blink::WebFormElement* form_element, - const PasswordFormFillData& data, - FormElements* result) { - return FindFormInputElement(form_element, data.password_field, result) && +// Helper to search through |control_elements| for the specified input elements +// in |data|, and add results to |result|. +bool FindFormInputElements( + const std::vector<blink::WebFormControlElement>& control_elements, + const PasswordFormFillData& data, + FormInputElementMap* result) { + return FindFormInputElement(control_elements, data.password_field, result) && (!FillDataContainsUsername(data) || - FindFormInputElement(form_element, data.username_field, result)); + FindFormInputElement(control_elements, data.username_field, result)); } // Helper to locate form elements identified by |data|. @@ -205,15 +204,22 @@ void FindFormElements(content::RenderFrame* render_frame, if (data.action != GetCanonicalActionForForm(fe)) continue; - scoped_ptr<FormElements> curr_elements(new FormElements); - if (!FindFormInputElements(&fe, data, curr_elements.get())) - continue; - - // We found the right element. - // Note: this assignment adds a reference to |fe|. - curr_elements->form_element = fe; - results->push_back(curr_elements.release()); + std::vector<blink::WebFormControlElement> control_elements = + ExtractAutofillableElementsInForm(fe); + FormInputElementMap cur_map; + if (FindFormInputElements(control_elements, data, &cur_map)) + results->push_back(cur_map); } + // If the element to be filled are not in a <form> element, the "action" and + // origin should be the same. + if (data.action != data.origin) + return; + + std::vector<blink::WebFormControlElement> control_elements = + GetUnownedAutofillableFormFieldElements(doc.all(), nullptr); + FormInputElementMap unowned_elements_map; + if (FindFormInputElements(control_elements, data, &unowned_elements_map)) + results->push_back(unowned_elements_map); } bool IsElementEditable(const blink::WebInputElement& element) { @@ -233,36 +239,13 @@ bool IsElementAutocompletable(const blink::WebInputElement& element) { return IsElementEditable(element); } -// Returns true if the password specified in |form| is a default value. -bool PasswordValueIsDefault(const base::string16& password_element, - const base::string16& password_value, - blink::WebFormElement form_element) { - blink::WebVector<blink::WebNode> temp_elements; - form_element.getNamedElements(password_element, temp_elements); - - // We are loose in our definition here and will return true if any of the - // appropriately named elements match the element to be saved. Currently - // we ignore filling passwords where naming is ambigious anyway. - for (size_t i = 0; i < temp_elements.size(); ++i) { - if (temp_elements[i].to<blink::WebElement>().getAttribute("value") == - password_value) - return true; - } - return false; -} - // Return true if either password_value or new_password_value is not empty and // not default. -bool FormContainsNonDefaultPasswordValue(const PasswordForm& password_form, - blink::WebFormElement form_element) { +bool FormContainsNonDefaultPasswordValue(const PasswordForm& password_form) { return (!password_form.password_value.empty() && - !PasswordValueIsDefault(password_form.password_element, - password_form.password_value, - form_element)) || + !password_form.password_value_is_default) || (!password_form.new_password_value.empty() && - !PasswordValueIsDefault(password_form.new_password_element, - password_form.new_password_value, - form_element)); + !password_form.new_password_value_is_default); } // Log a message including the name, method and action of |form|. @@ -533,10 +516,12 @@ bool ContainsNonNullEntryForNonNullKey( // Helper function to check if there exist any form on |frame| where its action -// equals |action|. Return true if so. +// equals |action| or input elements outside a <form> tag if |action| equals +// the current url. Return true if so. bool IsFormVisible( blink::WebFrame* frame, - GURL& action) { + const GURL& action, + const FormsPredictionsMap& form_predictions) { blink::WebVector<blink::WebFormElement> forms; frame->document().forms(forms); @@ -549,6 +534,20 @@ bool IsFormVisible( return true; // Form still exists } + scoped_ptr<PasswordForm> unowned_password_form( + CreatePasswordFormFromUnownedInputElements( + *frame, nullptr, &form_predictions)); + std::vector<blink::WebFormControlElement> control_elements = + GetUnownedAutofillableFormFieldElements(frame->document().all(), nullptr); + if (unowned_password_form && + action == unowned_password_form->action && + IsNamedElementVisible(control_elements, + unowned_password_form->username_element) && + IsNamedElementVisible(control_elements, + unowned_password_form->password_element)) { + return true; + } + return false; } @@ -731,7 +730,16 @@ void PasswordAutofillAgent::UpdateStateForTextChange( // handlers run, so save away a copy of the password in case it gets lost. // To honor the user having explicitly cleared the password, even an empty // password will be saved here. - ProvisionallySavePassword(element.form(), RESTRICTION_NONE); + scoped_ptr<PasswordForm> password_form; + if (element.form().isNull()) { + password_form = CreatePasswordFormFromUnownedInputElements( + *element.document().frame(), &nonscript_modified_values_, + &form_predictions_); + } else { + password_form = CreatePasswordFormFromWebForm( + element.form(), &nonscript_modified_values_, &form_predictions_); + } + ProvisionallySavePassword(password_form.Pass(), RESTRICTION_NONE); PasswordToLoginMap::iterator iter = password_to_username_.find(element); if (iter != password_to_username_.end()) { @@ -883,11 +891,12 @@ void PasswordAutofillAgent::OnDynamicFormsSeen() { void PasswordAutofillAgent::XHRSucceeded() { if (!ProvisionallySavedPasswordIsValid()) return; - blink::WebFrame* frame = render_frame()->GetWebFrame(); // Prompt to save only if the form is now gone, either invisible or // removed from the DOM. - if (IsFormVisible(frame, provisionally_saved_form_->action)) + blink::WebFrame* frame = render_frame()->GetWebFrame(); + if (IsFormVisible(frame, provisionally_saved_form_->action, + form_predictions_)) return; Send(new AutofillHostMsg_InPageNavigation(routing_id(), @@ -951,8 +960,8 @@ void PasswordAutofillAgent::SendPasswordForms(bool only_visible) { } scoped_ptr<PasswordForm> password_form( - CreatePasswordForm(form, nullptr, &form_predictions_)); - if (password_form.get()) { + CreatePasswordFormFromWebForm(form, nullptr, &form_predictions_)); + if (password_form) { if (logger) { logger->LogPasswordForm(Logger::STRING_FORM_IS_PASSWORD, *password_form); @@ -961,6 +970,20 @@ void PasswordAutofillAgent::SendPasswordForms(bool only_visible) { } } + // See if there are any unattached input elements that could be used for + // password submission. + scoped_ptr<PasswordForm> password_form( + CreatePasswordFormFromUnownedInputElements(*frame, + nullptr, + &form_predictions_)); + if (password_form) { + if (logger) { + logger->LogPasswordForm(Logger::STRING_FORM_IS_PASSWORD, + *password_form); + } + password_forms.push_back(*password_form); + } + if (password_forms.empty() && !only_visible) { // We need to send the PasswordFormsRendered message regardless of whether // there are any forms visible, as this is also the code path that triggers @@ -1021,7 +1044,8 @@ void PasswordAutofillAgent::DidCommitProvisionalLoad( return; // Not a top-level navigation. // Prompt to save only if the form disappeared. - if (IsFormVisible(frame, provisionally_saved_form_->action)) + if (IsFormVisible(frame, provisionally_saved_form_->action, + form_predictions_)) return; if (is_same_page_navigation) { @@ -1071,7 +1095,10 @@ void PasswordAutofillAgent::WillSendSubmitEvent( // cleared by some scripts (http://crbug.com/28910, http://crbug.com/391693). // Had the user cleared the password, |provisionally_saved_form_| would // already have been updated in TextDidChangeInTextField. - ProvisionallySavePassword(form, RESTRICTION_NON_EMPTY_PASSWORD); + scoped_ptr<PasswordForm> password_form = CreatePasswordFormFromWebForm( + form, &nonscript_modified_values_, &form_predictions_); + ProvisionallySavePassword(password_form.Pass(), + RESTRICTION_NON_EMPTY_PASSWORD); } void PasswordAutofillAgent::WillSubmitForm(const blink::WebFormElement& form) { @@ -1083,7 +1110,8 @@ void PasswordAutofillAgent::WillSubmitForm(const blink::WebFormElement& form) { } scoped_ptr<PasswordForm> submitted_form = - CreatePasswordForm(form, &nonscript_modified_values_, &form_predictions_); + CreatePasswordFormFromWebForm(form, &nonscript_modified_values_, + &form_predictions_); // If there is a provisionally saved password, copy over the previous // password value so we get the user's typed password, not the value that @@ -1156,6 +1184,7 @@ void PasswordAutofillAgent::LegacyDidStartProvisionalLoad( routing_id(), *provisionally_saved_form_)); provisionally_saved_form_.reset(); } else { + ScopedVector<PasswordForm> possible_submitted_forms; // Loop through the forms on the page looking for one that has been // filled out. If one exists, try and save the credentials. blink::WebVector<blink::WebFormElement> forms; @@ -1168,10 +1197,19 @@ void PasswordAutofillAgent::LegacyDidStartProvisionalLoad( LogHTMLForm(logger.get(), Logger::STRING_FORM_FOUND_ON_PAGE, form_element); } - scoped_ptr<PasswordForm> password_form(CreatePasswordForm( + possible_submitted_forms.push_back(CreatePasswordFormFromWebForm( form_element, &nonscript_modified_values_, &form_predictions_)); - if (password_form.get() && !password_form->username_value.empty() && - FormContainsNonDefaultPasswordValue(*password_form, form_element)) { + } + + possible_submitted_forms.push_back( + CreatePasswordFormFromUnownedInputElements( + *render_frame()->GetWebFrame(), + &nonscript_modified_values_, + &form_predictions_)); + + for (const PasswordForm* password_form : possible_submitted_forms) { + if (password_form && !password_form->username_value.empty() && + FormContainsNonDefaultPasswordValue(*password_form)) { password_forms_found = true; if (logger) { logger->LogPasswordForm(Logger::STRING_PASSWORD_FORM_FOUND_ON_PAGE, @@ -1179,8 +1217,10 @@ void PasswordAutofillAgent::LegacyDidStartProvisionalLoad( } Send(new AutofillHostMsg_PasswordFormSubmitted(routing_id(), *password_form)); + break; } } + if (!password_forms_found && logger) logger->LogMessage(Logger::STRING_PASSWORD_FORM_NOT_FOUND_ON_PAGE); } @@ -1202,20 +1242,16 @@ void PasswordAutofillAgent::OnFillPasswordForm( } FormElementsList forms; - // We own the FormElements* in forms. FindFormElements(render_frame(), form_data, &forms); FormElementsList::iterator iter; for (iter = forms.begin(); iter != forms.end(); ++iter) { - scoped_ptr<FormElements> form_elements(*iter); - // Attach autocomplete listener to enable selecting alternate logins. blink::WebInputElement username_element, password_element; // Check whether the password form has a username input field. bool form_contains_username_field = FillDataContainsUsername(form_data); if (form_contains_username_field) { - username_element = - form_elements->input_elements[form_data.username_field.name]; + username_element = (*iter)[form_data.username_field.name]; } // No password field, bail out. @@ -1230,8 +1266,7 @@ void PasswordAutofillAgent::OnFillPasswordForm( // Get pointer to password element. (We currently only support single // password forms). - password_element = - form_elements->input_elements[form_data.password_field.name]; + password_element = (*iter)[form_data.password_field.name]; // If wait_for_username is true, we don't want to initially fill the form // until the user types in a valid username. @@ -1271,12 +1306,23 @@ void PasswordAutofillAgent::OnFindFocusedPasswordForm() { if (!element.isNull() && element.hasHTMLTagName("input")) { blink::WebInputElement input = element.to<blink::WebInputElement>(); if (input.isPasswordField() && !input.form().isNull()) { - password_form = CreatePasswordForm( - input.form(), &nonscript_modified_values_, &form_predictions_); + if (!input.form().isNull()) { + password_form = CreatePasswordFormFromWebForm( + input.form(), &nonscript_modified_values_, &form_predictions_); + } else { + password_form = CreatePasswordFormFromUnownedInputElements( + *render_frame()->GetWebFrame(), + &nonscript_modified_values_, &form_predictions_); + // Only try to use this form if |input| is one of the password elements + // for |password_form|. + if (password_form->password_element != input.nameForAutofill() && + password_form->new_password_element != input.nameForAutofill()) + password_form.reset(); + } } } - if (!password_form.get()) + if (!password_form) password_form.reset(new PasswordForm()); Send(new AutofillHostMsg_FocusedPasswordFormFound( @@ -1430,10 +1476,8 @@ void PasswordAutofillAgent::ClearPreview( } void PasswordAutofillAgent::ProvisionallySavePassword( - const blink::WebFormElement& form, + scoped_ptr<PasswordForm> password_form, ProvisionallySaveRestriction restriction) { - scoped_ptr<PasswordForm> password_form(CreatePasswordForm( - form, &nonscript_modified_values_, &form_predictions_)); if (!password_form || (restriction == RESTRICTION_NON_EMPTY_PASSWORD && password_form->password_value.empty() && password_form->new_password_value.empty())) { diff --git a/components/autofill/content/renderer/password_autofill_agent.h b/components/autofill/content/renderer/password_autofill_agent.h index f0f3267..8ba1ca9 100644 --- a/components/autofill/content/renderer/password_autofill_agent.h +++ b/components/autofill/content/renderer/password_autofill_agent.h @@ -11,6 +11,7 @@ #include "base/gtest_prod_util.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" +#include "components/autofill/content/renderer/password_form_conversion_utils.h" #include "components/autofill/core/common/form_data_predictions.h" #include "components/autofill/core/common/password_form_field_prediction_map.h" #include "components/autofill/core/common/password_form_fill_data.h" @@ -114,8 +115,6 @@ class PasswordAutofillAgent : public content::RenderFrameObserver { typedef std::map<blink::WebElement, int> LoginToPasswordInfoKeyMap; typedef std::map<blink::WebInputElement, blink::WebInputElement> PasswordToLoginMap; - using FormsPredictionsMap = - std::map<autofill::FormData, autofill::PasswordFormFieldPredictionMap>; // This class keeps track of autofilled password input elements and makes sure // the autofilled password value is not accessible to JavaScript code until @@ -238,9 +237,9 @@ class PasswordAutofillAgent : public content::RenderFrameObserver { void ClearPreview(blink::WebInputElement* username, blink::WebInputElement* password); - // Extracts a PasswordForm from |form| and saves it as - // |provisionally_saved_form_|, as long as it satisfies |restriction|. - void ProvisionallySavePassword(const blink::WebFormElement& form, + // Saves |password_form| in |provisionally_saved_form_|, as long as it + // satisfies |restriction|. + void ProvisionallySavePassword(scoped_ptr<PasswordForm> password_form, ProvisionallySaveRestriction restriction); // Returns true if |provisionally_saved_form_| has enough information that @@ -267,8 +266,7 @@ class PasswordAutofillAgent : public content::RenderFrameObserver { // Contains the most recent text that user typed or PasswordManager autofilled // in input elements. Used for storing username/password before JavaScript // changes them. - std::map<const blink::WebInputElement, blink::WebString> - nonscript_modified_values_; + ModifiedValues nonscript_modified_values_; PasswordValueGatekeeper gatekeeper_; diff --git a/components/autofill/content/renderer/password_form_conversion_utils.cc b/components/autofill/content/renderer/password_form_conversion_utils.cc index 24deefb..246147a 100644 --- a/components/autofill/content/renderer/password_form_conversion_utils.cc +++ b/components/autofill/content/renderer/password_form_conversion_utils.cc @@ -14,14 +14,17 @@ #include "components/autofill/core/common/password_form.h" #include "components/autofill/core/common/password_form_field_prediction_map.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/WebFormControlElement.h" +#include "third_party/WebKit/public/web/WebFrame.h" #include "third_party/WebKit/public/web/WebInputElement.h" #include "third_party/icu/source/i18n/unicode/regex.h" using blink::WebDocument; using blink::WebFormControlElement; using blink::WebFormElement; +using blink::WebFrame; using blink::WebInputElement; using blink::WebString; using blink::WebVector; @@ -29,6 +32,26 @@ using blink::WebVector; namespace autofill { namespace { +// PasswordForms can be constructed for both WebFormElements and for collections +// of WebInputElements that are not in a WebFormElement. This intermediate +// aggregating structure is provided so GetPasswordForm() only has one +// view of the underlying data, regardless of its origin. +struct SyntheticForm { + SyntheticForm(); + ~SyntheticForm(); + + std::vector<blink::WebElement> fieldsets; + std::vector<blink::WebFormControlElement> control_elements; + blink::WebDocument document; + blink::WebString action; + + private: + DISALLOW_COPY_AND_ASSIGN(SyntheticForm); +}; + +SyntheticForm::SyntheticForm() {} +SyntheticForm::~SyntheticForm() {} + // Layout classification of password forms // A layout sequence of a form is the sequence of it's non-password and password // input fields, represented by "N" and "P", respectively. A form like this @@ -96,6 +119,14 @@ PasswordForm::Layout SequenceToLayout(base::StringPiece layout_sequence) { return PasswordForm::Layout::LAYOUT_OTHER; } +void PopulateSyntheticFormFromWebForm(const WebFormElement& web_form, + SyntheticForm* synthetic_form) { + synthetic_form->control_elements = ExtractAutofillableElementsInForm( + web_form); + synthetic_form->action = web_form.action(); + synthetic_form->document = web_form.document(); +} + // Checks in a case-insensitive way if the autocomplete attribute for the given // |element| is present and has the specified |value_in_lowercase|. bool HasAutocompleteAttributeValue(const WebInputElement& element, @@ -182,18 +213,11 @@ bool LocateSpecificPasswords(std::vector<WebInputElement> passwords, } void FindPredictedElements( - const WebFormElement& form, - const std::map<autofill::FormData, - autofill::PasswordFormFieldPredictionMap>& form_predictions, - WebVector<WebFormControlElement>* control_elements, + const SyntheticForm& form, + const FormData& form_data, + const FormsPredictionsMap& form_predictions, std::map<autofill::PasswordFormFieldPredictionType, WebInputElement>* predicted_elements) { - FormData form_data; - if (!WebFormElementToFormData(form, WebFormControlElement(), EXTRACT_NONE, - &form_data, nullptr)) { - return; - } - // Matching only requires that action and name of the form match to allow // the username to be updated even if the form is changed after page load. // See https://crbug.com/476092 for more details. @@ -209,9 +233,6 @@ void FindPredictedElements( if (predictions_iterator == form_predictions.end()) return; - std::vector<blink::WebFormControlElement> autofillable_elements = - ExtractAutofillableElementsFromSet(*control_elements); - const autofill::PasswordFormFieldPredictionMap& field_predictions = predictions_iterator->second; for (autofill::PasswordFormFieldPredictionMap::const_iterator prediction = @@ -220,10 +241,10 @@ void FindPredictedElements( const autofill::PasswordFormFieldPredictionType& type = prediction->first; const autofill::FormFieldData& target_field = prediction->second; - for (size_t i = 0; i < autofillable_elements.size(); ++i) { - if (autofillable_elements[i].nameForAutofill() == target_field.name) { - WebInputElement* input_element = - toWebInputElement(&autofillable_elements[i]); + for (size_t i = 0; i < form.control_elements.size(); ++i) { + if (form.control_elements[i].nameForAutofill() == target_field.name) { + const WebInputElement* input_element = + toWebInputElement(&form.control_elements[i]); if (input_element) { (*predicted_elements)[type] = *input_element; } @@ -237,29 +258,20 @@ void FindPredictedElements( // If an element of |form| has an entry in |nonscript_modified_values|, the // associated string is used instead of the element's value to create // the PasswordForm. -void GetPasswordForm( - const WebFormElement& form, - PasswordForm* password_form, - const std::map<const blink::WebInputElement, blink::WebString>* - nonscript_modified_values, - const std::map<autofill::FormData, - autofill::PasswordFormFieldPredictionMap>* - form_predictions) { +bool GetPasswordForm(const SyntheticForm& form, + PasswordForm* password_form, + const ModifiedValues* nonscript_modified_values, + const FormsPredictionsMap* form_predictions) { WebInputElement latest_input_element; WebInputElement username_element; password_form->username_marked_by_site = false; std::vector<WebInputElement> passwords; std::vector<base::string16> other_possible_usernames; - WebVector<WebFormControlElement> control_elements; - form.getFormControlElements(control_elements); - std::string layout_sequence; - layout_sequence.reserve(control_elements.size()); - for (size_t i = 0; i < control_elements.size(); ++i) { - WebFormControlElement control_element = control_elements[i]; - if (control_element.isActivatedSubmit()) - password_form->submit_element = control_element.formControlName(); + layout_sequence.reserve(form.control_elements.size()); + for (size_t i = 0; i < form.control_elements.size(); ++i) { + WebFormControlElement control_element = form.control_elements[i]; WebInputElement* input_element = toWebInputElement(&control_element); if (!input_element || !input_element->isEnabled()) @@ -349,7 +361,7 @@ void GetPasswordForm( std::map<autofill::PasswordFormFieldPredictionType, WebInputElement> predicted_elements; if (form_predictions) { - FindPredictedElements(form, *form_predictions, &control_elements, + FindPredictedElements(form, password_form->form_data, *form_predictions, &predicted_elements); } // Let server predictions override the selection of the username field. This @@ -390,13 +402,9 @@ void GetPasswordForm( WebInputElement password; WebInputElement new_password; if (!LocateSpecificPasswords(passwords, &password, &new_password)) - return; - - password_form->action = GetCanonicalActionForForm(form); - if (!password_form->action.is_valid()) - return; + return false; - password_form->origin = GetCanonicalOriginForDocument(form.document()); + password_form->origin = GetCanonicalOriginForDocument(form.document); GURL::Replacements rep; rep.SetPathStr(""); password_form->signon_realm = @@ -413,10 +421,14 @@ void GetPasswordForm( } password_form->password_value = password_value; password_form->password_autocomplete_set = password.autoComplete(); + password_form->password_value_is_default = + password.getAttribute("value") == password_value; } if (!new_password.isNull()) { password_form->new_password_element = new_password.nameForAutofill(); password_form->new_password_value = new_password.value(); + password_form->new_password_value_is_default = + new_password.getAttribute("value") == new_password.value(); if (HasAutocompleteAttributeValue(new_password, "new-password")) password_form->new_password_marked_by_site = true; } @@ -438,6 +450,8 @@ void GetPasswordForm( password_form->preferred = false; password_form->blacklisted_by_user = false; password_form->type = PasswordForm::TYPE_MANUAL; + + return true; } GURL StripAuthAndParams(const GURL& gurl) { @@ -466,29 +480,62 @@ GURL GetCanonicalOriginForDocument(const WebDocument& document) { return StripAuthAndParams(full_origin); } -scoped_ptr<PasswordForm> CreatePasswordForm( +scoped_ptr<PasswordForm> CreatePasswordFormFromWebForm( const WebFormElement& web_form, - const std::map<const blink::WebInputElement, blink::WebString>* - nonscript_modified_values, - const std::map<autofill::FormData, - autofill::PasswordFormFieldPredictionMap>* - form_predictions) { + const ModifiedValues* nonscript_modified_values, + const FormsPredictionsMap* form_predictions) { if (web_form.isNull()) return scoped_ptr<PasswordForm>(); scoped_ptr<PasswordForm> password_form(new PasswordForm()); - GetPasswordForm(web_form, password_form.get(), nonscript_modified_values, - form_predictions); - + password_form->action = GetCanonicalActionForForm(web_form); if (!password_form->action.is_valid()) return scoped_ptr<PasswordForm>(); + SyntheticForm synthetic_form; + PopulateSyntheticFormFromWebForm(web_form, &synthetic_form); + WebFormElementToFormData(web_form, blink::WebFormControlElement(), EXTRACT_NONE, &password_form->form_data, NULL /* FormFieldData */); + if (!GetPasswordForm(synthetic_form, password_form.get(), + nonscript_modified_values, form_predictions)) + return scoped_ptr<PasswordForm>(); + + return password_form.Pass(); +} + +scoped_ptr<PasswordForm> CreatePasswordFormFromUnownedInputElements( + const WebFrame& frame, + const ModifiedValues* nonscript_modified_values, + const FormsPredictionsMap* form_predictions) { + SyntheticForm synthetic_form; + synthetic_form.control_elements = + GetUnownedAutofillableFormFieldElements(frame.document().all(), + &synthetic_form.fieldsets); + synthetic_form.document = frame.document(); + + if (synthetic_form.control_elements.empty()) + return scoped_ptr<PasswordForm>(); + + scoped_ptr<PasswordForm> password_form(new PasswordForm()); + UnownedPasswordFormElementsAndFieldSetsToFormData( + synthetic_form.fieldsets, + synthetic_form.control_elements, + nullptr, frame.document(), + EXTRACT_NONE, + &password_form->form_data, + nullptr /* FormFieldData */); + if (!GetPasswordForm(synthetic_form, password_form.get(), + nonscript_modified_values, form_predictions)) + return scoped_ptr<PasswordForm>(); + + // No actual action on the form, so use the the origin as the action. + password_form->action = password_form->origin; + return password_form.Pass(); } diff --git a/components/autofill/content/renderer/password_form_conversion_utils.h b/components/autofill/content/renderer/password_form_conversion_utils.h index 12e850c4..0e5321f 100644 --- a/components/autofill/content/renderer/password_form_conversion_utils.h +++ b/components/autofill/content/renderer/password_form_conversion_utils.h @@ -9,13 +9,14 @@ #include "base/memory/scoped_ptr.h" #include "components/autofill/core/common/password_form_field_prediction_map.h" +#include "third_party/WebKit/public/platform/WebString.h" #include "url/gurl.h" namespace blink { class WebDocument; class WebFormElement; +class WebFrame; class WebInputElement; -class WebString; } namespace autofill { @@ -30,6 +31,9 @@ struct PasswordForm; GURL GetCanonicalActionForForm(const blink::WebFormElement& form); GURL GetCanonicalOriginForDocument(const blink::WebDocument& document); +typedef std::map<const blink::WebInputElement, + blink::WebString> ModifiedValues; + // Create a PasswordForm from DOM form. Webkit doesn't allow storing // custom metadata to DOM nodes, so we have to do this every time an event // happens with a given form and compare against previously Create'd forms @@ -39,12 +43,17 @@ GURL GetCanonicalOriginForDocument(const blink::WebDocument& document); // the PasswordForm. // |form_predictions| is Autofill server response, if present it's used for // overwriting default username element selection. -scoped_ptr<PasswordForm> CreatePasswordForm( +scoped_ptr<PasswordForm> CreatePasswordFormFromWebForm( const blink::WebFormElement& form, - const std::map<const blink::WebInputElement, blink::WebString>* - nonscript_modified_values, - const std::map<autofill::FormData, - autofill::PasswordFormFieldPredictionMap>* form_predictions); + const ModifiedValues* nonscript_modified_values, + const FormsPredictionsMap* form_predictions); + +// Same as CreatePasswordFormFromWebForm() but for input elements that are not +// enclosed in <form> element. +scoped_ptr<PasswordForm> CreatePasswordFormFromUnownedInputElements( + const blink::WebFrame& frame, + const ModifiedValues* nonscript_modified_values, + const FormsPredictionsMap* form_predictions); } // namespace autofill diff --git a/components/autofill/content/renderer/password_form_conversion_utils_browsertest.cc b/components/autofill/content/renderer/password_form_conversion_utils_browsertest.cc index d733c53..71e81b0 100644 --- a/components/autofill/content/renderer/password_form_conversion_utils_browsertest.cc +++ b/components/autofill/content/renderer/password_form_conversion_utils_browsertest.cc @@ -84,13 +84,12 @@ class PasswordFormBuilder { void AddHiddenField() { html_ += "<INPUT type=\"hidden\"/>"; } // Appends a new submit-type field at the end of the form with the specified - // |name|. If |activated| is true, the test will emulate as if this button - // were used to submit the form. - void AddSubmitButton(const char* name, bool activated) { + // |name|. + void AddSubmitButton(const char* name) { base::StringAppendF( &html_, - "<INPUT type=\"submit\" name=\"%s\" value=\"Submit\" %s/>", - name, activated ? "set-activated-submit" : ""); + "<INPUT type=\"submit\" name=\"%s\" value=\"Submit\"/>", + name); } // Returns the HTML code for the form containing the fields that have been @@ -135,15 +134,7 @@ class MAYBE_PasswordFormConversionUtilsTest : public content::RenderViewTest { frame->document().forms(forms); ASSERT_EQ(1U, forms.size()); - WebVector<WebFormControlElement> control_elements; - forms[0].getFormControlElements(control_elements); - for (size_t i = 0; i < control_elements.size(); ++i) { - WebInputElement* input_element = toWebInputElement(&control_elements[i]); - if (input_element->hasAttribute("set-activated-submit")) - input_element->setActivatedSubmit(true); - } - - *password_form = CreatePasswordForm(forms[0], nullptr, nullptr); + *password_form = CreatePasswordFormFromWebForm(forms[0], nullptr, nullptr); } private: @@ -155,9 +146,9 @@ class MAYBE_PasswordFormConversionUtilsTest : public content::RenderViewTest { TEST_F(MAYBE_PasswordFormConversionUtilsTest, BasicFormAttributes) { PasswordFormBuilder builder(kTestFormActionURL); builder.AddUsernameField("username", "johnsmith", NULL); - builder.AddSubmitButton("inactive_submit", false); - builder.AddSubmitButton("active_submit", true); - builder.AddSubmitButton("inactive_submit2", false); + builder.AddSubmitButton("inactive_submit"); + builder.AddSubmitButton("active_submit"); + builder.AddSubmitButton("inactive_submit2"); builder.AddPasswordField("password", "secret", NULL); std::string html = builder.ProduceHTML(); @@ -167,7 +158,6 @@ TEST_F(MAYBE_PasswordFormConversionUtilsTest, BasicFormAttributes) { EXPECT_EQ("data:", password_form->signon_realm); EXPECT_EQ(GURL(kTestFormActionURL), password_form->action); - EXPECT_EQ(base::UTF8ToUTF16("active_submit"), password_form->submit_element); EXPECT_EQ(base::UTF8ToUTF16("username"), password_form->username_element); EXPECT_EQ(base::UTF8ToUTF16("johnsmith"), password_form->username_value); EXPECT_EQ(base::UTF8ToUTF16("password"), password_form->password_element); @@ -185,7 +175,7 @@ TEST_F(MAYBE_PasswordFormConversionUtilsTest, DisabledFieldsAreIgnored) { builder.AddDisabledUsernameField(); builder.AddDisabledPasswordField(); builder.AddPasswordField("password", "secret", NULL); - builder.AddSubmitButton("submit", true); + builder.AddSubmitButton("submit"); std::string html = builder.ProduceHTML(); scoped_ptr<PasswordForm> password_form; @@ -255,7 +245,7 @@ TEST_F(MAYBE_PasswordFormConversionUtilsTest, IdentifyingUsernameFields) { builder.AddPasswordField("password", "secret", NULL); builder.AddUsernameField("username3", names[2], cases[i].autocomplete[2]); builder.AddPasswordField("password2", "othersecret", NULL); - builder.AddSubmitButton("submit", true); + builder.AddSubmitButton("submit"); std::string html = builder.ProduceHTML(); scoped_ptr<PasswordForm> password_form; @@ -313,7 +303,7 @@ TEST_F(MAYBE_PasswordFormConversionUtilsTest, IdentifyingTwoPasswordFields) { builder.AddUsernameField("username1", "William", NULL); builder.AddPasswordField("password2", cases[i].password_values[1], NULL); builder.AddUsernameField("username2", "Smith", NULL); - builder.AddSubmitButton("submit", true); + builder.AddSubmitButton("submit"); std::string html = builder.ProduceHTML(); scoped_ptr<PasswordForm> password_form; @@ -374,7 +364,7 @@ TEST_F(MAYBE_PasswordFormConversionUtilsTest, IdentifyingThreePasswordFields) { builder.AddPasswordField("password2", cases[i].password_values[1], NULL); builder.AddUsernameField("username2", "Smith", NULL); builder.AddPasswordField("password3", cases[i].password_values[2], NULL); - builder.AddSubmitButton("submit", true); + builder.AddSubmitButton("submit"); std::string html = builder.ProduceHTML(); scoped_ptr<PasswordForm> password_form; @@ -509,7 +499,7 @@ TEST_F(MAYBE_PasswordFormConversionUtilsTest, builder.AddPasswordField("password2", "beta", cases[i].autocomplete[1]); builder.AddUsernameField("username2", "Smith", NULL); builder.AddPasswordField("password3", "gamma", cases[i].autocomplete[2]); - builder.AddSubmitButton("submit", true); + builder.AddSubmitButton("submit"); std::string html = builder.ProduceHTML(); scoped_ptr<PasswordForm> password_form; @@ -539,7 +529,7 @@ TEST_F(MAYBE_PasswordFormConversionUtilsTest, TEST_F(MAYBE_PasswordFormConversionUtilsTest, InvalidFormDueToBadActionURL) { PasswordFormBuilder builder("invalid_target"); builder.AddUsernameField("username", "JohnSmith", NULL); - builder.AddSubmitButton("submit", true); + builder.AddSubmitButton("submit"); builder.AddPasswordField("password", "secret", NULL); std::string html = builder.ProduceHTML(); @@ -553,7 +543,7 @@ TEST_F(MAYBE_PasswordFormConversionUtilsTest, PasswordFormBuilder builder(kTestFormActionURL); builder.AddUsernameField("username1", "John", NULL); builder.AddUsernameField("username2", "Smith", NULL); - builder.AddSubmitButton("submit", true); + builder.AddSubmitButton("submit"); std::string html = builder.ProduceHTML(); scoped_ptr<PasswordForm> password_form; @@ -583,7 +573,7 @@ TEST_F(MAYBE_PasswordFormConversionUtilsTest, builder.AddPasswordField("password1", cases[i][0], NULL); builder.AddPasswordField("password2", cases[i][1], NULL); builder.AddPasswordField("password3", cases[i][2], NULL); - builder.AddSubmitButton("submit", true); + builder.AddSubmitButton("submit"); std::string html = builder.ProduceHTML(); scoped_ptr<PasswordForm> password_form; @@ -600,7 +590,7 @@ TEST_F(MAYBE_PasswordFormConversionUtilsTest, builder.AddPasswordField("password2", "alpha", NULL); builder.AddPasswordField("password3", "alpha", NULL); builder.AddPasswordField("password4", "alpha", NULL); - builder.AddSubmitButton("submit", true); + builder.AddSubmitButton("submit"); std::string html = builder.ProduceHTML(); scoped_ptr<PasswordForm> password_form; @@ -613,7 +603,7 @@ TEST_F(MAYBE_PasswordFormConversionUtilsTest, LayoutClassificationLogin) { builder.AddHiddenField(); builder.AddUsernameField("username", "", nullptr); builder.AddPasswordField("password", "", nullptr); - builder.AddSubmitButton("submit", false); + builder.AddSubmitButton("submit"); std::string login_html = builder.ProduceHTML(); scoped_ptr<PasswordForm> login_form; @@ -629,7 +619,7 @@ TEST_F(MAYBE_PasswordFormConversionUtilsTest, LayoutClassificationSignup) { builder.AddPasswordField("new_password", "", nullptr); builder.AddHiddenField(); builder.AddPasswordField("new_password2", "", nullptr); - builder.AddSubmitButton("submit", false); + builder.AddSubmitButton("submit"); std::string signup_html = builder.ProduceHTML(); scoped_ptr<PasswordForm> signup_form; @@ -645,7 +635,7 @@ TEST_F(MAYBE_PasswordFormConversionUtilsTest, LayoutClassificationChange) { builder.AddHiddenField(); builder.AddPasswordField("new_password", "", nullptr); builder.AddPasswordField("new_password2", "", nullptr); - builder.AddSubmitButton("submit", false); + builder.AddSubmitButton("submit"); std::string change_html = builder.ProduceHTML(); scoped_ptr<PasswordForm> change_form; @@ -665,7 +655,7 @@ TEST_F(MAYBE_PasswordFormConversionUtilsTest, builder.AddPasswordField("new_password", "", nullptr); builder.AddPasswordField("new_password2", "", nullptr); builder.AddHiddenField(); - builder.AddSubmitButton("submit", false); + builder.AddSubmitButton("submit"); std::string login_plus_signup_html = builder.ProduceHTML(); scoped_ptr<PasswordForm> login_plus_signup_form; @@ -687,7 +677,7 @@ TEST_F(MAYBE_PasswordFormConversionUtilsTest, builder.AddPasswordField("new_password", "", nullptr); builder.AddUsernameField("someotherfield2", "", nullptr); builder.AddHiddenField(); - builder.AddSubmitButton("submit", false); + builder.AddSubmitButton("submit"); std::string login_plus_signup_html = builder.ProduceHTML(); scoped_ptr<PasswordForm> login_plus_signup_form; diff --git a/components/autofill/content/renderer/password_generation_agent.cc b/components/autofill/content/renderer/password_generation_agent.cc index 50303ae..bc796c8 100644 --- a/components/autofill/content/renderer/password_generation_agent.cc +++ b/components/autofill/content/renderer/password_generation_agent.cc @@ -195,7 +195,7 @@ void PasswordGenerationAgent::FindPossibleGenerationForm() { // If we can't get a valid PasswordForm, we skip this form because the // the password won't get saved even if we generate it. scoped_ptr<PasswordForm> password_form( - CreatePasswordForm(forms[i], nullptr, nullptr)); + CreatePasswordFormFromWebForm(forms[i], nullptr, nullptr)); if (!password_form.get()) { VLOG(2) << "Skipping form as it would not be saved"; continue; diff --git a/components/autofill/core/common/password_form.cc b/components/autofill/core/common/password_form.cc index e5edaca..1f046cb 100644 --- a/components/autofill/core/common/password_form.cc +++ b/components/autofill/core/common/password_form.cc @@ -31,8 +31,12 @@ void PasswordFormToJSON(const PasswordForm& form, target->SetString("username_value", form.username_value); target->SetString("password_elem", form.password_element); target->SetString("password_value", form.password_value); + target->SetBoolean("password_value_is_default", + form.password_value_is_default); target->SetString("new_password_element", form.new_password_element); target->SetString("new_password_value", form.new_password_value); + target->SetBoolean("new_password_value_is_default", + form.new_password_value_is_default); target->SetBoolean("new_password_marked_by_site", form.new_password_marked_by_site); target->SetString("other_possible_usernames", @@ -66,7 +70,9 @@ void PasswordFormToJSON(const PasswordForm& form, PasswordForm::PasswordForm() : scheme(SCHEME_HTML), username_marked_by_site(false), + password_value_is_default(false), password_autocomplete_set(true), + new_password_value_is_default(false), new_password_marked_by_site(false), ssl_valid(false), preferred(false), diff --git a/components/autofill/core/common/password_form.h b/components/autofill/core/common/password_form.h index 9663326..b4baa42 100644 --- a/components/autofill/core/common/password_form.h +++ b/components/autofill/core/common/password_form.h @@ -164,6 +164,10 @@ struct PasswordForm { // When parsing an HTML form, this is typically empty. base::string16 password_value; + // Whether the password value is the same as specified in the "value" + // attribute of the input element. Only used in the renderer. + bool password_value_is_default; + // False if autocomplete is set to "off" for the password input element; // True otherwise. bool password_autocomplete_set; @@ -175,6 +179,10 @@ struct PasswordForm { // The new password. Optional, and not persisted. base::string16 new_password_value; + // Whether the password value is the same as specified in the "value" + // attribute of the input element. Only used in the renderer. + bool new_password_value_is_default; + // Whether the |new_password_element| has an autocomplete=new-password // attribute. This is only used in parsed HTML forms. bool new_password_marked_by_site; diff --git a/components/autofill/core/common/password_form_field_prediction_map.h b/components/autofill/core/common/password_form_field_prediction_map.h index 9c22fbe..cac6c97 100644 --- a/components/autofill/core/common/password_form_field_prediction_map.h +++ b/components/autofill/core/common/password_form_field_prediction_map.h @@ -7,6 +7,7 @@ #include <map> +#include "components/autofill/core/common/form_data.h" #include "components/autofill/core/common/form_field_data.h" namespace autofill { @@ -24,6 +25,8 @@ enum PasswordFormFieldPredictionType { using PasswordFormFieldPredictionMap = std::map<PasswordFormFieldPredictionType, FormFieldData>; +using FormsPredictionsMap = + std::map<FormData, PasswordFormFieldPredictionMap>; } // namespace autofill |