Commit f46ff482 authored by dvadym's avatar dvadym Committed by Commit bot

In some cases we encounter situation when in processing onSubmit of password...

In some cases we encounter situation when in processing onSubmit of password form we didn't receive information from password store yet. For example this can happen when JavaScript creates password form after submission and submit it (as for nytimes.com).

To process such situation this CL postpones checking if password form fetched from store until moment when we are going to decide if we should save it.

In order to process this correctly creating pending credentials in PasswordManager was moved to separate method and it is called only when both events happen - form submission and fetching from store finished.

Tests are not added yet

BUG=470322

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

Cr-Commit-Position: refs/heads/master@{#324400}
parent 96d29c0c
......@@ -200,7 +200,6 @@ class PasswordFormManager : public PasswordStoreConsumer {
}
#endif
protected:
const autofill::PasswordForm& observed_form() const { return observed_form_; }
private:
......@@ -326,6 +325,10 @@ class PasswordFormManager : public PasswordStoreConsumer {
bool UploadPasswordForm(const autofill::FormData& form_data,
const autofill::ServerFieldType& password_type);
// Create pending credentials from provisionally saved form and forms received
// from password store.
void CreatePendingCredentials();
// Set of PasswordForms from the DB that best match the form
// being managed by this. Use a map instead of vector, because we most
// frequently require lookups by username value in IsNewLogin.
......@@ -338,6 +341,12 @@ class PasswordFormManager : public PasswordStoreConsumer {
// The PasswordForm from the page or dialog managed by |this|.
const autofill::PasswordForm observed_form_;
// Stores provisionally saved form until |pending_credentials_| is created.
scoped_ptr<const autofill::PasswordForm> provisionally_saved_form_;
// Stores if for creating |pending_credentials_| other possible usernames
// option should apply.
OtherPossibleUsernamesAction other_possible_username_action_;
// The origin url path of observed_form_ tokenized, for convenience when
// scoring.
std::vector<std::string> form_path_tokens_;
......
......@@ -178,7 +178,6 @@ void PasswordManager::ProvisionallySavePassword(const PasswordForm& form) {
// Below, "matching" is in DoesManage-sense and "not ready" in
// !HasCompletedMatching sense. We keep track of such PasswordFormManager
// instances for UMA.
bool has_found_matching_managers_which_were_not_ready = false;
for (ScopedVector<PasswordFormManager>::iterator iter =
pending_login_managers_.begin();
iter != pending_login_managers_.end(); ++iter) {
......@@ -195,11 +194,6 @@ void PasswordManager::ProvisionallySavePassword(const PasswordForm& form) {
continue;
}
if (!(*iter)->HasCompletedMatching()) {
has_found_matching_managers_which_were_not_ready = true;
continue;
}
if (result == PasswordFormManager::RESULT_COMPLETE_MATCH) {
// If we find a manager that exactly matches the submitted form including
// the action URL, exit the loop.
......@@ -241,25 +235,11 @@ void PasswordManager::ProvisionallySavePassword(const PasswordForm& form) {
// |manager|.
manager.reset(*matched_manager_it);
pending_login_managers_.weak_erase(matched_manager_it);
} else if (has_found_matching_managers_which_were_not_ready) {
// We found some managers, but none finished matching yet. The user has
// tried to submit credentials before we had time to even find matching
// results for the given form and autofill. If this is the case, we just
// give up.
RecordFailure(MATCHING_NOT_COMPLETE, form.origin, logger.get());
return;
} else {
RecordFailure(NO_MATCHING_FORM, form.origin, logger.get());
return;
}
// Also get out of here if the user told us to 'never remember' passwords for
// this form.
if (manager->IsBlacklisted()) {
RecordFailure(FORM_BLACKLISTED, form.origin, logger.get());
return;
}
// Bail if we're missing any of the necessary form components.
if (!manager->HasValidPasswordForm()) {
RecordFailure(INVALID_FORM, form.origin, logger.get());
......@@ -467,6 +447,26 @@ void PasswordManager::OnPasswordFormsRendered(
return;
}
if (!provisional_save_manager_->HasCompletedMatching()) {
// We have a provisional save manager, but it didn't finish matching yet.
// We just give up.
RecordFailure(MATCHING_NOT_COMPLETE,
provisional_save_manager_->observed_form().origin,
logger.get());
provisional_save_manager_.reset();
return;
}
// Also get out of here if the user told us to 'never remember' passwords for
// this form.
if (provisional_save_manager_->IsBlacklisted()) {
RecordFailure(FORM_BLACKLISTED,
provisional_save_manager_->observed_form().origin,
logger.get());
provisional_save_manager_.reset();
return;
}
DCHECK(client_->IsSavingEnabledForCurrentPage());
// If the server throws an internal error, access denied page, page not
......
......@@ -25,6 +25,7 @@ using base::ASCIIToUTF16;
using testing::_;
using testing::AnyNumber;
using testing::Exactly;
using testing::Invoke;
using testing::Return;
using testing::WithArg;
......@@ -1028,4 +1029,52 @@ TEST_F(PasswordManagerTest, FormSubmittedChangedWithAutofillResponse) {
form_to_save->Save();
}
TEST_F(PasswordManagerTest, SubmitNotFetchedFromStoreForm) {
// Test that observing a newly submitted form that is fetched after on submit
// shows the save password bar.
EXPECT_CALL(driver_, FillPasswordForm(_)).Times(Exactly(0));
std::vector<PasswordForm> observed;
PasswordForm form(MakeSimpleForm());
observed.push_back(form);
PasswordStoreConsumer* form_manager = nullptr;
// Do not call back from store after GetLogins is called. Instead, save the
// pointer to the form manager for calling back later. This emulates that
// PasswordStore does not manage to fetch a form till moment of submission.
ON_CALL(*store_, GetLogins(_, _, _))
.WillByDefault(testing::SaveArg<2>(&form_manager));
// The initial load.
manager()->OnPasswordFormsParsed(&driver_, observed);
// The initial layout.
manager()->OnPasswordFormsRendered(&driver_, observed, true);
ASSERT_TRUE(form_manager);
// And the form submit contract is to call ProvisionallySavePassword.
manager()->ProvisionallySavePassword(form);
// Emulate fetching password form from PasswordStore after submission but
// before post-navigation load.
form_manager->OnGetPasswordStoreResults(
ScopedVector<autofill::PasswordForm>());
scoped_ptr<PasswordFormManager> form_to_save;
EXPECT_CALL(client_,
PromptUserToSavePasswordPtr(
_, CredentialSourceType::CREDENTIAL_SOURCE_PASSWORD_MANAGER))
.WillOnce(WithArg<0>(SaveToScopedPtr(&form_to_save)));
// Now the password manager waits for the navigation to complete.
observed.clear();
// The post-navigation load.
manager()->OnPasswordFormsParsed(&driver_, observed);
// The post-navigation layout.
manager()->OnPasswordFormsRendered(&driver_, observed, true);
ASSERT_TRUE(form_to_save);
EXPECT_CALL(*store_, AddLogin(FormMatches(form)));
// Simulate saving the form, as if the info bar was accepted.
form_to_save->Save();
}
} // namespace password_manager
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment