summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorgcasto <gcasto@chromium.org>2015-06-11 12:39:01 -0700
committerCommit bot <commit-bot@chromium.org>2015-06-11 19:39:23 +0000
commit2f3f266efe6e18df27705fb2530489bcb42269d3 (patch)
tree70c9bfb9c5ff61bdd0a2824f2096c4d96348217a
parentfe9483ef6c14824c57ba6261d969e7cda91203d1 (diff)
downloadchromium_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}
-rw-r--r--chrome/browser/password_manager/password_manager_browsertest.cc184
-rw-r--r--chrome/renderer/autofill/form_autofill_browsertest.cc6
-rw-r--r--chrome/renderer/autofill/password_autofill_agent_browsertest.cc51
-rw-r--r--chrome/test/data/password/no_form_element.html41
-rw-r--r--components/autofill/content/renderer/form_autofill_util.cc57
-rw-r--r--components/autofill/content/renderer/form_autofill_util.h15
-rw-r--r--components/autofill/content/renderer/form_cache.cc2
-rw-r--r--components/autofill/content/renderer/password_autofill_agent.cc262
-rw-r--r--components/autofill/content/renderer/password_autofill_agent.h12
-rw-r--r--components/autofill/content/renderer/password_form_conversion_utils.cc145
-rw-r--r--components/autofill/content/renderer/password_form_conversion_utils.h21
-rw-r--r--components/autofill/content/renderer/password_form_conversion_utils_browsertest.cc54
-rw-r--r--components/autofill/content/renderer/password_generation_agent.cc2
-rw-r--r--components/autofill/core/common/password_form.cc6
-rw-r--r--components/autofill/core/common/password_form.h8
-rw-r--r--components/autofill/core/common/password_form_field_prediction_map.h3
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