diff options
author | gcasto@chromium.org <gcasto@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-18 21:54:24 +0000 |
---|---|---|
committer | gcasto@chromium.org <gcasto@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-18 21:54:24 +0000 |
commit | 2cd51863e9b7b425f877a9d309babe652e27192a (patch) | |
tree | af768e6f2a6463c70fd04f5adc2a78461709ac53 | |
parent | 557c7bd3a12d45a4aeb1ced28876831f845ed8c0 (diff) | |
download | chromium_src-2cd51863e9b7b425f877a9d309babe652e27192a.zip chromium_src-2cd51863e9b7b425f877a9d309babe652e27192a.tar.gz chromium_src-2cd51863e9b7b425f877a9d309babe652e27192a.tar.bz2 |
Revert "Revert 223907 "[password generation] Upload possible account cre...""
Re-commit change. Update to not use <iostream> to follow.
BUG=240559
Review URL: https://codereview.chromium.org/23857010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@223948 0039d316-1c4b-4281-b951-d872f2087c98
20 files changed, 364 insertions, 74 deletions
diff --git a/chrome/browser/password_manager/login_database.cc b/chrome/browser/password_manager/login_database.cc index 906ee7b..929f7cd 100644 --- a/chrome/browser/password_manager/login_database.cc +++ b/chrome/browser/password_manager/login_database.cc @@ -22,7 +22,7 @@ using autofill::PasswordForm; -static const int kCurrentVersionNumber = 3; +static const int kCurrentVersionNumber = 4; static const int kCompatibleVersionNumber = 1; namespace { @@ -44,7 +44,8 @@ enum LoginTableColumns { COLUMN_SCHEME, COLUMN_PASSWORD_TYPE, COLUMN_POSSIBLE_USERNAMES, - COLUMN_TIMES_USED + COLUMN_TIMES_USED, + COLUMN_FORM_DATA }; // Using the public suffix list for matching the origin is only needed for @@ -184,16 +185,20 @@ bool LoginDatabase::MigrateOldVersionsAsNeeded() { "ADD COLUMN possible_usernames BLOB")) { return false; } + // Fall through. case 2: - if (!db_.Execute("ALTER TABLE logins " - "ADD COLUMN times_used INTEGER")) { + if (!db_.Execute("ALTER TABLE logins ADD COLUMN times_used INTEGER")) { + return false; + } + // Fall through. + case 3: + if (!db_.Execute("ALTER TABLE logins ADD COLUMN form_data BLOB")) { return false; } - break; + // Fall through. case kCurrentVersionNumber: // Already up to date return true; - break; default: NOTREACHED(); return false; @@ -221,6 +226,7 @@ bool LoginDatabase::InitLoginsTable() { "password_type INTEGER," "possible_usernames BLOB," "times_used INTEGER," + "form_data BLOB," "UNIQUE " "(origin_url, username_element, " "username_value, password_element, " @@ -298,9 +304,9 @@ bool LoginDatabase::AddLogin(const PasswordForm& form) { "(origin_url, action_url, username_element, username_value, " " password_element, password_value, submit_element, " " signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, " - " scheme, password_type, possible_usernames, times_used) " + " scheme, password_type, possible_usernames, times_used, form_data) " "VALUES " - "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")); + "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")); s.BindString(COLUMN_ORIGIN_URL, form.origin.spec()); s.BindString(COLUMN_ACTION_URL, form.action.spec()); s.BindString16(COLUMN_USERNAME_ELEMENT, form.username_element); @@ -316,9 +322,16 @@ bool LoginDatabase::AddLogin(const PasswordForm& form) { s.BindInt(COLUMN_BLACKLISTED_BY_USER, form.blacklisted_by_user); s.BindInt(COLUMN_SCHEME, form.scheme); s.BindInt(COLUMN_PASSWORD_TYPE, form.type); - Pickle pickle = SerializeVector(form.other_possible_usernames); - s.BindBlob(COLUMN_POSSIBLE_USERNAMES, pickle.data(), pickle.size()); + Pickle usernames_pickle = SerializeVector(form.other_possible_usernames); + s.BindBlob(COLUMN_POSSIBLE_USERNAMES, + usernames_pickle.data(), + usernames_pickle.size()); s.BindInt(COLUMN_TIMES_USED, form.times_used); + Pickle form_data_pickle; + autofill::SerializeFormData(form.form_data, &form_data_pickle); + s.BindBlob(COLUMN_FORM_DATA, + form_data_pickle.data(), + form_data_pickle.size()); return s.Run(); } @@ -431,6 +444,11 @@ bool LoginDatabase::InitPasswordFormFromStatement(PasswordForm* form, s.ColumnByteLength(COLUMN_POSSIBLE_USERNAMES)); form->other_possible_usernames = DeserializeVector(pickle); form->times_used = s.ColumnInt(COLUMN_TIMES_USED); + Pickle form_data_pickle( + static_cast<const char*>(s.ColumnBlob(COLUMN_FORM_DATA)), + s.ColumnByteLength(COLUMN_FORM_DATA)); + PickleIterator form_data_iter(form_data_pickle); + autofill::DeserializeFormData(&form_data_iter, &form->form_data); return true; } @@ -442,7 +460,7 @@ bool LoginDatabase::GetLogins(const PasswordForm& form, "username_element, username_value, " "password_element, password_value, submit_element, " "signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, " - "scheme, password_type, possible_usernames, times_used " + "scheme, password_type, possible_usernames, times_used, form_data " "FROM logins WHERE signon_realm == ? "; sql::Statement s; const GURL signon_realm(form.signon_realm); @@ -521,7 +539,7 @@ bool LoginDatabase::GetLoginsCreatedBetween( "username_element, username_value, " "password_element, password_value, submit_element, " "signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, " - "scheme, password_type, possible_usernames, times_used " + "scheme, password_type, possible_usernames, times_used, form_data " "FROM logins WHERE date_created >= ? AND date_created < ?" "ORDER BY origin_url")); s.BindInt64(0, begin.ToTimeT()); @@ -556,7 +574,7 @@ bool LoginDatabase::GetAllLoginsWithBlacklistSetting( "username_element, username_value, " "password_element, password_value, submit_element, " "signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, " - "scheme, password_type, possible_usernames, times_used " + "scheme, password_type, possible_usernames, times_used, form_data " "FROM logins WHERE blacklisted_by_user == ? " "ORDER BY origin_url")); s.BindInt(0, blacklisted ? 1 : 0); diff --git a/chrome/browser/password_manager/login_database_unittest.cc b/chrome/browser/password_manager/login_database_unittest.cc index 7b062a4..98bef92 100644 --- a/chrome/browser/password_manager/login_database_unittest.cc +++ b/chrome/browser/password_manager/login_database_unittest.cc @@ -41,6 +41,15 @@ class LoginDatabaseTest : public testing::Test { db_.public_suffix_domain_matching_ = enabled; } + void FormsAreEqual(const PasswordForm& expected, const PasswordForm& actual) { + PasswordForm expected_copy(expected); +#if defined(OS_MACOSX) + // On the Mac we should never be storing passwords in the database. + expected_copy.password_value = ASCIIToUTF16(""); +#endif + EXPECT_EQ(expected_copy, actual); + } + base::ScopedTempDir temp_dir_; base::FilePath file_; LoginDatabase db_; @@ -66,11 +75,16 @@ TEST_F(LoginDatabaseTest, Logins) { form.ssl_valid = false; form.preferred = false; form.scheme = PasswordForm::SCHEME_HTML; + form.times_used = 1; + form.form_data.name = ASCIIToUTF16("form_name"); + form.form_data.method = ASCIIToUTF16("POST"); - // Add it and make sure it is there. + // Add it and make sure it is there and that all the fields were retrieved + // correctly. EXPECT_TRUE(db_.AddLogin(form)); EXPECT_TRUE(db_.GetAutofillableLogins(&result)); EXPECT_EQ(1U, result.size()); + FormsAreEqual(form, *result[0]); delete result[0]; result.clear(); diff --git a/chrome/browser/password_manager/password_form_data.cc b/chrome/browser/password_manager/password_form_data.cc index 458de6f..14beef6 100644 --- a/chrome/browser/password_manager/password_form_data.cc +++ b/chrome/browser/password_manager/password_form_data.cc @@ -37,38 +37,6 @@ PasswordForm* CreatePasswordFormFromData( return form; } -bool operator==(const PasswordForm& lhs, const PasswordForm& rhs) { - return (lhs.scheme == rhs.scheme && - lhs.signon_realm == rhs.signon_realm && - lhs.origin == rhs.origin && - lhs.action == rhs.action && - lhs.submit_element == rhs.submit_element && - lhs.username_element == rhs.username_element && - lhs.password_element == rhs.password_element && - lhs.username_value == rhs.username_value && - lhs.password_value == rhs.password_value && - lhs.blacklisted_by_user == rhs.blacklisted_by_user && - lhs.preferred == rhs.preferred && - lhs.ssl_valid == rhs.ssl_valid && - lhs.date_created == rhs.date_created); -} - -std::ostream& operator<<(std::ostream& os, const PasswordForm& form) { - return os << "scheme: " << form.scheme << std::endl - << "signon_realm: " << form.signon_realm << std::endl - << "origin: " << form.origin << std::endl - << "action: " << form.action << std::endl - << "submit_element: " << form.submit_element << std::endl - << "username_elem: " << form.username_element << std::endl - << "password_elem: " << form.password_element << std::endl - << "username_value: " << form.username_value << std::endl - << "password_value: " << form.password_value << std::endl - << "blacklisted: " << form.blacklisted_by_user << std::endl - << "preferred: " << form.preferred << std::endl - << "ssl_valid: " << form.ssl_valid << std::endl - << "date_created: " << form.date_created.ToDoubleT(); -} - typedef std::set<const autofill::PasswordForm*> SetOfForms; bool ContainsSamePasswordFormsPtr( diff --git a/chrome/browser/password_manager/password_form_data.h b/chrome/browser/password_manager/password_form_data.h index fef54a1..0421a7d 100644 --- a/chrome/browser/password_manager/password_form_data.h +++ b/chrome/browser/password_manager/password_form_data.h @@ -10,6 +10,8 @@ #include "components/autofill/core/common/password_form.h" #include "testing/gmock/include/gmock/gmock.h" +// TODO(sync): This file must eventually be refactored away -- crbug.com/87185. + // Struct used for creation of PasswordForms from static arrays of data. // Note: This is only meant to be used in unit test. struct PasswordFormData { @@ -42,11 +44,6 @@ bool ContainsSamePasswordForms( std::vector<autofill::PasswordForm>& first, std::vector<autofill::PasswordForm>& second); -// Pretty-prints the contents of a PasswordForm. -// TODO(sync): This file must eventually be refactored away -- crbug.com/87185. -std::ostream& operator<<(std::ostream& os, - const autofill::PasswordForm& form); - // This gmock matcher is used to check that the |arg| contains exactly the same // PasswordForms as |forms|, regardless of order. MATCHER_P(ContainsAllPasswordForms, forms, "") { diff --git a/chrome/browser/password_manager/password_form_manager.cc b/chrome/browser/password_manager/password_form_manager.cc index fc3ddc2..b28ba21 100644 --- a/chrome/browser/password_manager/password_form_manager.cc +++ b/chrome/browser/password_manager/password_form_manager.cc @@ -13,12 +13,15 @@ #include "chrome/browser/password_manager/password_store.h" #include "chrome/browser/password_manager/password_store_factory.h" #include "chrome/browser/profiles/profile.h" +#include "components/autofill/content/browser/autofill_driver_impl.h" +#include "components/autofill/core/browser/form_structure.h" #include "components/autofill/core/browser/validation.h" #include "components/autofill/core/common/autofill_messages.h" #include "components/autofill/core/common/password_form.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" +using autofill::FormStructure; using autofill::PasswordForm; using autofill::PasswordFormMap; using base::Time; @@ -486,6 +489,9 @@ void PasswordFormManager::UpdateLogin() { // Update metadata. ++pending_credentials_.times_used; + // Check to see if this form is a candidate for password generation. + CheckForAccountCreationForm(pending_credentials_, observed_form_); + UpdatePreferredLoginState(password_store); // Remove alternate usernames. At this point we assume that we have found @@ -543,6 +549,31 @@ bool PasswordFormManager::UpdatePendingCredentialsIfOtherPossibleUsername( return false; } +void PasswordFormManager::CheckForAccountCreationForm( + const PasswordForm& pending, const PasswordForm& observed) { + // We check to see if the saved form_data is the same as the observed + // form_data, which should never be true for passwords saved on account + // creation forms. This check is only made the first time a password is used + // to cut down on false positives. Specifically a site may have multiple login + // forms with different markup, which might look similar to a signup form. + if (pending.times_used == 1) { + FormStructure pending_structure(pending.form_data); + FormStructure observed_structure(observed.form_data); + if (pending_structure.FormSignature() != + observed_structure.FormSignature()) { + autofill::AutofillDriverImpl* driver = + autofill::AutofillDriverImpl::FromWebContents(web_contents_); + if (driver && driver->autofill_manager()) { + // Note that this doesn't guarantee that the upload succeeded, only that + // |pending.form_data| is considered uploadable. + bool success = driver->autofill_manager()->UploadPasswordGenerationForm( + pending.form_data); + UMA_HISTOGRAM_BOOLEAN("PasswordGeneration.UploadStarted", success); + } + } + } +} + int PasswordFormManager::ScoreResult(const PasswordForm& candidate) const { DCHECK_EQ(state_, MATCHING_PHASE); // For scoring of candidate login data: diff --git a/chrome/browser/password_manager/password_form_manager.h b/chrome/browser/password_manager/password_form_manager.h index 064849f..9efc135 100644 --- a/chrome/browser/password_manager/password_form_manager.h +++ b/chrome/browser/password_manager/password_form_manager.h @@ -217,6 +217,12 @@ class PasswordFormManager : public PasswordStoreConsumer { // is now implicitly 'preferred'. void UpdateLogin(); + // Check to see if |pending| corresponds to an account creation form. If we + // think that it does, we label it as such and upload this state to the + // Autofill server, so that we will trigger password generation in the future. + void CheckForAccountCreationForm(const autofill::PasswordForm& pending, + const autofill::PasswordForm& observed); + // Update all login matches to reflect new preferred state - preferred flag // will be reset on all matched logins that different than the current // |pending_credentials_|. diff --git a/chrome/browser/password_manager/password_manager_browsertest.cc b/chrome/browser/password_manager/password_manager_browsertest.cc index 54ecfe6..be0c84c 100644 --- a/chrome/browser/password_manager/password_manager_browsertest.cc +++ b/chrome/browser/password_manager/password_manager_browsertest.cc @@ -5,6 +5,8 @@ #include <string> #include "base/command_line.h" +#include "base/metrics/histogram_samples.h" +#include "base/metrics/statistics_recorder.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/infobars/confirm_infobar_delegate.h" #include "chrome/browser/infobars/infobar_service.h" @@ -15,6 +17,7 @@ #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/test_switches.h" #include "chrome/test/base/ui_test_utils.h" +#include "components/autofill/core/browser/autofill_common_test.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_service.h" @@ -24,6 +27,7 @@ #include "content/public/test/browser_test_utils.h" #include "content/public/test/test_utils.h" #include "net/test/embedded_test_server/embedded_test_server.h" +#include "net/url_request/test_url_fetcher_factory.h" #include "testing/gmock/include/gmock/gmock.h" #include "ui/events/keycodes/keyboard_codes.h" @@ -130,7 +134,8 @@ class PasswordManagerBrowserTest : public InProcessBrowserTest { // would sometimes see the DidFinishLoad event from a previous navigation and // return immediately. void NavigateToFile(const std::string& path) { - ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); + if (!embedded_test_server()->Started()) + ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); NavigationObserver observer(WebContents()); GURL url = embedded_test_server()->GetURL(path); @@ -292,3 +297,60 @@ IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, NoPromptForOtherXHR) { observer.Wait(); EXPECT_FALSE(observer.infobar_shown()); } + +IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, + VerifyPasswordGenerationUpload) { + // Prevent Autofill requests from actually going over the wire. + net::TestURLFetcherFactory factory; + // Disable Autofill requesting access to AddressBook data. This causes + // the test to hang on Mac. + autofill::test::DisableSystemServices(browser()->profile()); + + // Visit a signup form. + NavigateToFile("/password/signup_form.html"); + + // Enter a password and save it. + NavigationObserver first_observer(WebContents()); + std::string fill_and_submit = + "document.getElementById('other_info').value = 'stuff';" + "document.getElementById('username_field').value = 'my_username';" + "document.getElementById('password_field').value = 'password';" + "document.getElementById('input_submit_button').click()"; + ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), fill_and_submit)); + + first_observer.Wait(); + ASSERT_TRUE(first_observer.infobar_shown()); + + // Now navigate to a login form that has similar HTML markup. + NavigateToFile("/password/password_form.html"); + + // The form should be filled with the previously submitted username. + std::string get_username = + "window.domAutomationController.send(" + "document.getElementById('username_field').value);"; + std::string actual_username; + ASSERT_TRUE(content::ExecuteScriptAndExtractString(RenderViewHost(), + get_username, + &actual_username)); + ASSERT_EQ("my_username", actual_username); + + // Submit the form and verify that there is no infobar (as the password + // has already been saved). + NavigationObserver second_observer(WebContents()); + std::string submit_form = + "document.getElementById('input_submit_button').click()"; + ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), submit_form)); + second_observer.Wait(); + EXPECT_FALSE(second_observer.infobar_shown()); + + // Verify that we sent a ping to Autofill saying that the original form + // was likely an account creation form since it has more than 2 text input + // fields and was used for the first time on a different form. + base::HistogramBase* upload_histogram = + base::StatisticsRecorder::FindHistogram( + "PasswordGeneration.UploadStarted"); + scoped_ptr<base::HistogramSamples> snapshot = + upload_histogram->SnapshotSamples(); + EXPECT_EQ(0, snapshot->GetCount(0 /* failure */)); + EXPECT_EQ(1, snapshot->GetCount(1 /* success */)); +} diff --git a/chrome/test/data/password/password_form.html b/chrome/test/data/password/password_form.html index ac96b5b..7c8551d 100644 --- a/chrome/test/data/password/password_form.html +++ b/chrome/test/data/password/password_form.html @@ -1,6 +1,6 @@ <html> <body> -<form action="done.html" onsubmit="return true;" id="testform"> +<form method="POST" action="done.html" onsubmit="return true;" id="testform"> <input type="text" id="username_field" name="username_field"> <input type="password" id="password_field" name="password_field"> <input type="submit" id="input_submit_button" name="input_submit_button"> diff --git a/chrome/test/data/password/signup_form.html b/chrome/test/data/password/signup_form.html new file mode 100644 index 0000000..abdac71 --- /dev/null +++ b/chrome/test/data/password/signup_form.html @@ -0,0 +1,20 @@ +<!-- + A mock signup form. Essentially the same as password_form.html, but includes + an additional text field which will cause it to be treated differently. + --> +<html> +<body> +<form method="POST" action="done.html" onsubmit="return true;" id="testform"> + <input type="text" id="other_info" name="other_info"> + <input type="text" id="username_field" name="username_field"> + <input type="password" id="password_field" name="password_field"> + <input type="submit" id="input_submit_button" name="input_submit_button"> +</form> + +<button id="submit_button" name="submit_button" + onclick="document.getElementById('testform').submit()"> + Submit! +</button> + +</body> +</html> diff --git a/components/autofill/content/renderer/password_form_conversion_utils.cc b/components/autofill/content/renderer/password_form_conversion_utils.cc index 3066373..7dfd45d 100644 --- a/components/autofill/content/renderer/password_form_conversion_utils.cc +++ b/components/autofill/content/renderer/password_form_conversion_utils.cc @@ -4,7 +4,9 @@ #include "components/autofill/content/renderer/password_form_conversion_utils.h" +#include "components/autofill/content/renderer/form_autofill_util.h" #include "components/autofill/core/common/password_form.h" +#include "third_party/WebKit/public/web/WebFormControlElement.h" #include "third_party/WebKit/public/web/WebPasswordFormData.h" using WebKit::WebFormElement; @@ -14,6 +16,7 @@ namespace autofill { namespace { scoped_ptr<PasswordForm> InitPasswordFormFromWebPasswordForm( + const WebFormElement& web_form, const WebKit::WebPasswordFormData& web_password_form) { PasswordForm* password_form = new PasswordForm(); password_form->signon_realm = web_password_form.signonRealm.utf8(); @@ -38,6 +41,12 @@ scoped_ptr<PasswordForm> InitPasswordFormFromWebPasswordForm( password_form->preferred = false; password_form->blacklisted_by_user = false; password_form->type = PasswordForm::TYPE_MANUAL; + WebFormElementToFormData(web_form, + WebKit::WebFormControlElement(), + REQUIRE_NONE, + EXTRACT_NONE, + &password_form->form_data, + NULL /* FormFieldData */); return scoped_ptr<PasswordForm>(password_form); } @@ -46,7 +55,7 @@ scoped_ptr<PasswordForm> InitPasswordFormFromWebPasswordForm( scoped_ptr<PasswordForm> CreatePasswordForm(const WebFormElement& webform) { WebPasswordFormData web_password_form(webform); if (web_password_form.isValid()) - return InitPasswordFormFromWebPasswordForm(web_password_form); + return InitPasswordFormFromWebPasswordForm(webform, web_password_form); return scoped_ptr<PasswordForm>(); } diff --git a/components/autofill/core/browser/autofill_manager.cc b/components/autofill/core/browser/autofill_manager.cc index 0e2ee3a..6f99772 100644 --- a/components/autofill/core/browser/autofill_manager.cc +++ b/components/autofill/core/browser/autofill_manager.cc @@ -253,23 +253,16 @@ bool AutofillManager::OnFormSubmitted(const FormData& form, // Let Autocomplete know as well. autocomplete_history_manager_->OnFormSubmitted(form); - if (!IsAutofillEnabled()) - return false; + // Grab a copy of the form data. + scoped_ptr<FormStructure> submitted_form(new FormStructure(form)); - if (driver_->GetWebContents()->GetBrowserContext()->IsOffTheRecord()) + if (!ShouldUploadForm(*submitted_form)) return false; // Don't save data that was submitted through JavaScript. if (!form.user_submitted) return false; - // Grab a copy of the form data. - scoped_ptr<FormStructure> submitted_form(new FormStructure(form)); - - // Disregard forms that we wouldn't ever autofill in the first place. - if (!submitted_form->ShouldBeParsed(true)) - return false; - // Ignore forms not present in our cache. These are typically forms with // wonky JavaScript that also makes them not auto-fillable. FormStructure* cached_submitted_form; @@ -779,6 +772,51 @@ void AutofillManager::UploadFormData(const FormStructure& submitted_form) { non_empty_types); } +bool AutofillManager::UploadPasswordGenerationForm(const FormData& form) { + FormStructure form_structure(form); + + if (!ShouldUploadForm(form_structure)) + return false; + + if (!form_structure.ShouldBeCrowdsourced()) + return false; + + // TODO(gcasto): Check that PasswordGeneration is enabled? + + // Find the first password field to label. We don't try to label anything + // else. + bool found_password_field = false; + for (size_t i = 0; i < form_structure.field_count(); ++i) { + AutofillField* field = form_structure.field(i); + + ServerFieldTypeSet types; + if (!found_password_field && field->form_control_type == "password") { + types.insert(ACCOUNT_CREATION_PASSWORD); + found_password_field = true; + } else { + types.insert(UNKNOWN_TYPE); + } + field->set_possible_types(types); + } + DCHECK(found_password_field); + + // Only one field type should be present. + ServerFieldTypeSet available_field_types; + available_field_types.insert(ACCOUNT_CREATION_PASSWORD); + + // Force uploading as these events are relatively rare and we want to make + // sure to receive them. It also makes testing easier if these requests + // always pass. + form_structure.set_upload_required(UPLOAD_REQUIRED); + + if (!download_manager_) + return false; + + return download_manager_->StartUploadRequest(form_structure, + false /* was_autofilled */, + available_field_types); +} + void AutofillManager::Reset() { form_structures_.clear(); has_logged_autofill_enabled_ = false; @@ -1132,4 +1170,18 @@ void AutofillManager::UpdateInitialInteractionTimestamp( } } +bool AutofillManager::ShouldUploadForm(const FormStructure& form) { + if (!IsAutofillEnabled()) + return false; + + if (driver_->GetWebContents()->GetBrowserContext()->IsOffTheRecord()) + return false; + + // Disregard forms that we wouldn't ever autofill in the first place. + if (!form.ShouldBeParsed(true)) + return false; + + return true; +} + } // namespace autofill diff --git a/components/autofill/core/browser/autofill_manager.h b/components/autofill/core/browser/autofill_manager.h index ec55abd3..a149e28 100644 --- a/components/autofill/core/browser/autofill_manager.h +++ b/components/autofill/core/browser/autofill_manager.h @@ -157,6 +157,16 @@ class AutofillManager : public AutofillDownloadManager::Observer { void OnRequestAutocomplete(const FormData& form, const GURL& frame_url); + // Try and upload |form|. This differs from OnFormSubmitted() in a few ways. + // - This function will only label the first <input type="password"> field + // as ACCOUNT_CREATION_PASSWORD. Other fields will stay unlabeled, as they + // should have been labeled during the upload for OnFormSubmitted(). + // - This function does not assume that |form| is being uploaded during + // the same browsing session as it was originally submitted (as we may + // not have the necessary information to classify the form at that time) + // so it bypasses the cache and doesn't log the same quality UMA metrics. + bool UploadPasswordGenerationForm(const FormData& form); + // Resets cache. virtual void Reset(); @@ -287,6 +297,9 @@ class AutofillManager : public AutofillDownloadManager::Observer { void UpdateInitialInteractionTimestamp( const base::TimeTicks& interaction_timestamp); + // Shared code to determine if |form| should be uploaded. + bool ShouldUploadForm(const FormStructure& form); + // Provides driver-level context to the shared code of the component. Must // outlive this object. AutofillDriver* driver_; diff --git a/components/autofill/core/browser/form_structure.h b/components/autofill/core/browser/form_structure.h index 4ec8f9c..6382c20 100644 --- a/components/autofill/core/browser/form_structure.h +++ b/components/autofill/core/browser/form_structure.h @@ -153,6 +153,9 @@ class FormStructure { const GURL& source_url() const { return source_url_; } + void set_upload_required(UploadRequired required) { + upload_required_ = required; + } UploadRequired upload_required() const { return upload_required_; } virtual std::string server_experiment_id() const; diff --git a/components/autofill/core/common/autofill_messages.h b/components/autofill/core/common/autofill_messages.h index 136329e..006744f 100644 --- a/components/autofill/core/common/autofill_messages.h +++ b/components/autofill/core/common/autofill_messages.h @@ -63,15 +63,6 @@ IPC_STRUCT_TRAITS_BEGIN(autofill::FormFieldDataPredictions) IPC_STRUCT_TRAITS_MEMBER(overall_type) IPC_STRUCT_TRAITS_END() -IPC_STRUCT_TRAITS_BEGIN(autofill::FormData) - IPC_STRUCT_TRAITS_MEMBER(name) - IPC_STRUCT_TRAITS_MEMBER(method) - IPC_STRUCT_TRAITS_MEMBER(origin) - IPC_STRUCT_TRAITS_MEMBER(action) - IPC_STRUCT_TRAITS_MEMBER(user_submitted) - IPC_STRUCT_TRAITS_MEMBER(fields) -IPC_STRUCT_TRAITS_END() - IPC_STRUCT_TRAITS_BEGIN(autofill::FormDataPredictions) IPC_STRUCT_TRAITS_MEMBER(data) IPC_STRUCT_TRAITS_MEMBER(signature) diff --git a/components/autofill/core/common/autofill_param_traits_macros.h b/components/autofill/core/common/autofill_param_traits_macros.h index b0a1ccf..91d357a 100644 --- a/components/autofill/core/common/autofill_param_traits_macros.h +++ b/components/autofill/core/common/autofill_param_traits_macros.h @@ -13,6 +13,15 @@ IPC_ENUM_TRAITS(autofill::PasswordForm::Type) +IPC_STRUCT_TRAITS_BEGIN(autofill::FormData) + IPC_STRUCT_TRAITS_MEMBER(name) + IPC_STRUCT_TRAITS_MEMBER(method) + IPC_STRUCT_TRAITS_MEMBER(origin) + IPC_STRUCT_TRAITS_MEMBER(action) + IPC_STRUCT_TRAITS_MEMBER(user_submitted) + IPC_STRUCT_TRAITS_MEMBER(fields) +IPC_STRUCT_TRAITS_END() + IPC_STRUCT_TRAITS_BEGIN(autofill::PasswordForm) IPC_STRUCT_TRAITS_MEMBER(signon_realm) IPC_STRUCT_TRAITS_MEMBER(origin) @@ -31,6 +40,7 @@ IPC_STRUCT_TRAITS_BEGIN(autofill::PasswordForm) IPC_STRUCT_TRAITS_MEMBER(blacklisted_by_user) IPC_STRUCT_TRAITS_MEMBER(type) IPC_STRUCT_TRAITS_MEMBER(times_used) + IPC_STRUCT_TRAITS_MEMBER(form_data) IPC_STRUCT_TRAITS_END() #endif // COMPONENTS_AUTOFILL_CORE_COMMON_AUTOFILL_PARAM_TRAITS_MACROS_H_ diff --git a/components/autofill/core/common/form_data.cc b/components/autofill/core/common/form_data.cc index 078a58d..050e2e5 100644 --- a/components/autofill/core/common/form_data.cc +++ b/components/autofill/core/common/form_data.cc @@ -6,6 +6,7 @@ #include "base/pickle.h" #include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" #include "components/autofill/core/common/form_field_data.h" namespace autofill { @@ -78,6 +79,19 @@ bool FormData::operator!=(const FormData& form) const { return !operator==(form); } +std::ostream& operator<<(std::ostream& os, const FormData& form) { + os << UTF16ToUTF8(form.name) << " " + << UTF16ToUTF8(form.method) << " " + << form.origin << " " + << form.action << " " + << form.user_submitted << " " + << "Fields:"; + for (size_t i = 0; i < form.fields.size(); ++i) { + os << form.fields[i] << ","; + } + return os; +} + void SerializeFormData(const FormData& form_data, Pickle* pickle) { pickle->WriteInt(kPickleVersion); pickle->WriteString16(form_data.name); diff --git a/components/autofill/core/common/form_data.h b/components/autofill/core/common/form_data.h index 1d6dffa..b41619c 100644 --- a/components/autofill/core/common/form_data.h +++ b/components/autofill/core/common/form_data.h @@ -19,7 +19,7 @@ struct FormData { FormData(const FormData& data); ~FormData(); - // Used by FormStructureTest. + // Used in testing. bool operator==(const FormData& form) const; bool operator!=(const FormData& form) const; @@ -37,6 +37,9 @@ struct FormData { std::vector<FormFieldData> fields; }; +// For testing. +std::ostream& operator<<(std::ostream& os, const FormData& form); + // Serialize FormData. Used by the PasswordManager to persist FormData // pertaining to password forms. Serialized data is appended to |pickle| void SerializeFormData(const FormData& form_data, Pickle* pickle); diff --git a/components/autofill/core/common/password_form.cc b/components/autofill/core/common/password_form.cc index 1f9c762..7bd8fe8 100644 --- a/components/autofill/core/common/password_form.cc +++ b/components/autofill/core/common/password_form.cc @@ -2,6 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <iostream> + +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" #include "components/autofill/core/common/password_form.h" namespace autofill { @@ -23,4 +27,53 @@ bool PasswordForm::IsPublicSuffixMatch() const { return !original_signon_realm.empty(); } +bool PasswordForm::operator==(const PasswordForm& form) const { + return signon_realm == form.signon_realm && + origin == form.origin && + action == form.action && + submit_element == form.submit_element && + username_element == form.username_element && + username_value == form.username_value && + other_possible_usernames == form.other_possible_usernames && + password_element == form.password_element && + password_value == form.password_value && + password_autocomplete_set == form.password_autocomplete_set && + old_password_element == form.old_password_element && + old_password_value == form.old_password_value && + ssl_valid == form.ssl_valid && + preferred == form.preferred && + date_created == form.date_created && + blacklisted_by_user == form.blacklisted_by_user && + type == form.type && + times_used == form.times_used && + form_data == form.form_data; +} + +bool PasswordForm::operator!=(const PasswordForm& form) const { + return !operator==(form); +} + +std::ostream& operator<<(std::ostream& os, const PasswordForm& form) { + return os << "scheme: " << form.scheme + << " signon_realm: " << form.signon_realm + << " origin: " << form.origin + << " action: " << form.action + << " submit_element: " << UTF16ToUTF8(form.submit_element) + << " username_elem: " << UTF16ToUTF8(form.username_element) + << " username_value: " << UTF16ToUTF8(form.username_value) + << " password_elem: " << UTF16ToUTF8(form.password_element) + << " password_value: " << UTF16ToUTF8(form.password_value) + << " old_password_element: " + << UTF16ToUTF8(form.old_password_element) + << " old_password_value: " << UTF16ToUTF8(form.old_password_value) + << " autocomplete_set:" << form.password_autocomplete_set + << " blacklisted: " << form.blacklisted_by_user + << " preferred: " << form.preferred + << " ssl_valid: " << form.ssl_valid + << " date_created: " << form.date_created.ToDoubleT() + << " type: " << form.type + << " times_used: " << form.times_used + << " form_data: " << form.form_data; +} + } // namespace autofill diff --git a/components/autofill/core/common/password_form.h b/components/autofill/core/common/password_form.h index 9ecb214..8485225 100644 --- a/components/autofill/core/common/password_form.h +++ b/components/autofill/core/common/password_form.h @@ -10,6 +10,7 @@ #include <vector> #include "base/time/time.h" +#include "components/autofill/core/common/form_data.h" #include "url/gurl.h" namespace autofill { @@ -180,9 +181,20 @@ struct PasswordForm { // When parsing an HTML form, this is not used. int times_used; + // Autofill representation of this form. Used to communicate with the + // Autofill servers if necessary. Currently this is only used to help + // determine forms where we can trigger password generation. + // + // When parsing an HTML form, this is normally set. + FormData form_data; + // Returns true if this match was found using public suffix matching. bool IsPublicSuffixMatch() const; + // Equality operators for testing. + bool operator==(const PasswordForm& form) const; + bool operator!=(const PasswordForm& form) const; + PasswordForm(); ~PasswordForm(); }; @@ -190,6 +202,10 @@ struct PasswordForm { // Map username to PasswordForm* for convenience. See password_form_manager.h. typedef std::map<string16, PasswordForm*> PasswordFormMap; +// For testing. +std::ostream& operator<<(std::ostream& os, + const autofill::PasswordForm& form); + } // namespace autofill #endif // COMPONENTS_AUTOFILL_CORE_COMMON_PASSWORD_FORM_H__ diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index c70d8f6..d8a3894 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -10554,6 +10554,16 @@ other types of suffix sets. </summary> </histogram> +<histogram name="PasswordGeneration.UploadStarted" enum="Boolean"> + <summary> + The number of times that we try to upload a form that we believe should + trigger password generation. False means that something about the form would + not allow us to try upload (not an Autofillable field, uploading disabled, + Autofill servers in backoff, etc.). True does not mean that the upload + actually completed successfully, just that it was started. + </summary> +</histogram> + <histogram name="PasswordManager.BlacklistedSites"> <summary> The total number of sites that the user has blacklisted. Recorded by |