blob: fb2fb49a898b5b6dbe0ffd55613f1b68b2d8d20a [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/password_manager/core/browser/password_manager.h"
#include <stddef.h>
#include <algorithm>
#include <map>
#include <memory>
#include <utility>
#include "base/feature_list.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/ranges/algorithm.h"
#include "base/stl_util.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/platform_thread.h"
#include "build/build_config.h"
#include "components/autofill/core/browser/autofill_field.h"
#include "components/autofill/core/browser/autofill_type.h"
#include "components/autofill/core/browser/form_structure.h"
#include "components/autofill/core/browser/logging/log_manager.h"
#include "components/autofill/core/common/form_data_predictions.h"
#include "components/autofill/core/common/password_generation_util.h"
#include "components/autofill/core/common/save_password_progress_logger.h"
#include "components/autofill/core/common/signatures.h"
#include "components/password_manager/core/browser/browser_save_password_progress_logger.h"
#include "components/password_manager/core/browser/credential_cache.h"
#include "components/password_manager/core/browser/field_info_manager.h"
#include "components/password_manager/core/browser/origin_credential_store.h"
#include "components/password_manager/core/browser/password_autofill_manager.h"
#include "components/password_manager/core/browser/password_form_manager.h"
#include "components/password_manager/core/browser/password_generation_frame_helper.h"
#include "components/password_manager/core/browser/password_manager_client.h"
#include "components/password_manager/core/browser/password_manager_driver.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "components/password_manager/core/browser/password_manager_util.h"
#include "components/password_manager/core/browser/password_reuse_manager.h"
#include "components/password_manager/core/browser/password_save_manager_impl.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#if defined(OS_WIN)
#include "components/prefs/pref_registry_simple.h"
#endif
using autofill::ACCOUNT_CREATION_PASSWORD;
using autofill::FieldDataManager;
using autofill::FieldRendererId;
using autofill::FormData;
using autofill::FormRendererId;
using autofill::FormStructure;
using autofill::NEW_PASSWORD;
using autofill::NOT_USERNAME;
using autofill::SINGLE_USERNAME;
using autofill::UNKNOWN_TYPE;
using autofill::USERNAME;
using autofill::mojom::SubmissionIndicatorEvent;
using base::NumberToString;
using BlocklistedStatus =
password_manager::OriginCredentialStore::BlocklistedStatus;
using password_manager::metrics_util::GaiaPasswordHashChange;
namespace password_manager {
namespace {
// Shorten the name to spare line breaks. The code provides enough context
// already.
using Logger = autofill::SavePasswordProgressLogger;
bool AreChangePasswordFieldsEmpty(const FormData& form_data,
const PasswordForm& parsed_form) {
const std::u16string& old_password = parsed_form.password_element;
const std::u16string& new_password = parsed_form.new_password_element;
const std::u16string& confirmation_password =
parsed_form.confirmation_password_element;
for (const auto& field : form_data.fields) {
if (!field.value.empty() &&
(field.name == new_password ||
(!old_password.empty() && field.name == old_password) ||
(!confirmation_password.empty() &&
field.name == confirmation_password))) {
return false;
}
}
return true;
}
// Returns true if the user needs to be prompted before a password can be
// saved (instead of automatically saving the password), based on inspecting
// the state of |manager|.
bool ShouldPromptUserToSavePassword(const PasswordFormManager& manager) {
if (manager.IsPasswordUpdate()) {
// Updating a credential might erase a useful stored value by accident.
// Always ask the user to confirm.
return true;
}
// User successfully signed-in with PSL match credentials. These credentials
// should be automatically saved in order to be autofilled on next login.
if (manager.IsPendingCredentialsPublicSuffixMatch())
return false;
if (manager.HasGeneratedPassword())
return false;
return manager.IsNewLogin();
}
#if !defined(OS_IOS)
// Finds the matched form manager with id |form_renderer_id| in
// |form_managers|.
PasswordFormManager* FindMatchedManagerByRendererId(
autofill::FormRendererId form_renderer_id,
const std::vector<std::unique_ptr<PasswordFormManager>>& form_managers,
const PasswordManagerDriver* driver) {
for (const auto& form_manager : form_managers) {
if (form_manager->DoesManage(form_renderer_id, driver))
return form_manager.get();
}
return nullptr;
}
#endif // !defined(OS_IOS)
bool HasSingleUsernameVote(const FormPredictions& form) {
for (const auto& field : form.fields) {
if (field.type == autofill::SINGLE_USERNAME)
return true;
}
return false;
}
// Returns true if at least one of the fields in |form| has a prediction to be a
// new-password related field.
bool HasNewPasswordVote(const FormPredictions& form) {
if (!base::FeatureList::IsEnabled(
password_manager::features::
KEnablePasswordGenerationForClearTextFields))
return false;
for (const auto& field : form.fields) {
if (field.type == ACCOUNT_CREATION_PASSWORD || field.type == NEW_PASSWORD)
return true;
}
return false;
}
// Adds predictions to |predictions->fields| if |field_info_manager| has
// predictions for corresponding fields. Predictions from |field_info_manager|
// have priority over server predictions.
void AddLocallySavedPredictions(FieldInfoManager* field_info_manager,
FormPredictions* predictions,
BrowserSavePasswordProgressLogger* logger) {
DCHECK(predictions);
if (!field_info_manager)
return;
for (PasswordFieldPrediction& field : predictions->fields) {
auto local_prediction = field_info_manager->GetFieldType(
predictions->form_signature, field.signature);
if (local_prediction == SINGLE_USERNAME) {
field.type = SINGLE_USERNAME;
} else if (local_prediction == NOT_USERNAME) {
// Now local prediction NOT_USERNAME is based on the weak signal (the user
// ignored or rejected the prompt) so use it only if the server does not
// have data.
if (field.type != SINGLE_USERNAME && field.type != USERNAME)
field.type = NOT_USERNAME;
}
if (logger && local_prediction != UNKNOWN_TYPE) {
std::string message = base::StrCat(
{"form signature=",
NumberToString(predictions->form_signature.value()),
" , field signature=", NumberToString(field.signature.value()),
", type=",
autofill::AutofillType::ServerFieldTypeToString(local_prediction)});
logger->LogString(Logger::STRING_LOCALLY_SAVED_PREDICTION, message);
}
}
}
bool HasMutedCredentials(base::span<const InsecureCredential> credentials,
const std::u16string& username) {
return base::ranges::any_of(credentials, [&username](const auto& credential) {
return credential.username == username && credential.is_muted &&
(credential.insecure_type == InsecureType::kLeaked ||
credential.insecure_type == InsecureType::kPhished);
});
}
} // namespace
// static
void PasswordManager::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterBooleanPref(
prefs::kCredentialsEnableService, true,
user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF);
registry->RegisterBooleanPref(
prefs::kCredentialsEnableAutosignin, true,
user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF);
registry->RegisterStringPref(prefs::kSyncPasswordHash, std::string(),
PrefRegistry::NO_REGISTRATION_FLAGS);
registry->RegisterStringPref(prefs::kSyncPasswordLengthAndHashSalt,
std::string(),
PrefRegistry::NO_REGISTRATION_FLAGS);
registry->RegisterBooleanPref(
prefs::kWasAutoSignInFirstRunExperienceShown, false,
user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF);
registry->RegisterDoublePref(prefs::kLastTimeObsoleteHttpCredentialsRemoved,
0.0);
registry->RegisterDoublePref(prefs::kLastTimePasswordCheckCompleted, 0.0);
registry->RegisterTimePref(
prefs::kSyncedLastTimePasswordCheckCompleted, base::Time(),
user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF);
registry->RegisterDictionaryPref(prefs::kAccountStoragePerAccountSettings);
registry->RegisterTimePref(prefs::kProfileStoreDateLastUsedForFilling,
base::Time());
registry->RegisterTimePref(prefs::kAccountStoreDateLastUsedForFilling,
base::Time());
registry->RegisterBooleanPref(prefs::kWereOldGoogleLoginsRemoved, false);
#if defined(OS_APPLE)
registry->RegisterIntegerPref(prefs::kKeychainMigrationStatus,
4 /* MIGRATED_DELETED */);
#endif
registry->RegisterListPref(prefs::kPasswordHashDataList,
PrefRegistry::NO_REGISTRATION_FLAGS);
registry->RegisterBooleanPref(
prefs::kPasswordLeakDetectionEnabled, true,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
#if defined(OS_ANDROID)
registry->RegisterIntegerPref(
prefs::kCurrentMigrationVersionToGoogleMobileServices, 0);
registry->RegisterDoublePref(prefs::kTimeOfLastMigrationAttempt, 0.0);
#endif
}
// static
void PasswordManager::RegisterLocalPrefs(PrefRegistrySimple* registry) {
#if defined(OS_WIN)
registry->RegisterInt64Pref(prefs::kOsPasswordLastChanged, 0);
registry->RegisterBooleanPref(prefs::kOsPasswordBlank, false);
#endif
}
PasswordManager::PasswordManager(PasswordManagerClient* client)
: client_(client), leak_delegate_(client) {
DCHECK(client_);
}
PasswordManager::~PasswordManager() = default;
void PasswordManager::OnGeneratedPasswordAccepted(
PasswordManagerDriver* driver,
const FormData& form_data,
autofill::FieldRendererId generation_element_id,
const std::u16string& password) {
PasswordFormManager* manager =
GetMatchedManager(driver, form_data.unique_renderer_id);
if (manager) {
manager->OnGeneratedPasswordAccepted(form_data, generation_element_id,
password);
} else {
// OnPresaveGeneratedPassword records the histogram in all other cases.
UMA_HISTOGRAM_BOOLEAN("PasswordManager.GeneratedFormHasNoFormManager",
true);
}
}
void PasswordManager::OnPresaveGeneratedPassword(
PasswordManagerDriver* driver,
const FormData& form_data,
const std::u16string& generated_password) {
DCHECK(client_->IsSavingAndFillingEnabled(form_data.url));
PasswordFormManager* form_manager =
GetMatchedManager(driver, form_data.unique_renderer_id);
UMA_HISTOGRAM_BOOLEAN("PasswordManager.GeneratedFormHasNoFormManager",
!form_manager);
if (form_manager)
form_manager->PresaveGeneratedPassword(form_data, generated_password);
}
void PasswordManager::OnPasswordNoLongerGenerated(PasswordManagerDriver* driver,
const FormData& form_data) {
DCHECK(client_->IsSavingAndFillingEnabled(form_data.url));
PasswordFormManager* form_manager =
GetMatchedManager(driver, form_data.unique_renderer_id);
if (form_manager)
form_manager->PasswordNoLongerGenerated();
}
void PasswordManager::SetGenerationElementAndTypeForForm(
password_manager::PasswordManagerDriver* driver,
FormRendererId form_id,
FieldRendererId generation_element,
autofill::password_generation::PasswordGenerationType type) {
PasswordFormManager* form_manager = GetMatchedManager(driver, form_id);
if (form_manager) {
DCHECK(client_->IsSavingAndFillingEnabled(form_manager->GetURL()));
form_manager->SetGenerationElement(generation_element);
form_manager->SetGenerationPopupWasShown(type);
}
}
void PasswordManager::MarkWasUnblocklistedInFormManagers(
CredentialCache* credential_cache) {
if (owned_submitted_form_manager_) {
const OriginCredentialStore& credential_store =
credential_cache->GetCredentialStore(
url::Origin::Create(owned_submitted_form_manager_->GetURL()));
if (credential_store.GetBlocklistedStatus() ==
BlocklistedStatus::kWasBlocklisted) {
owned_submitted_form_manager_->MarkWasUnblocklisted();
}
}
for (const auto& form_manager : form_managers_) {
const OriginCredentialStore& credential_store =
credential_cache->GetCredentialStore(
url::Origin::Create(form_manager->GetURL()));
if (credential_store.GetBlocklistedStatus() ==
BlocklistedStatus::kWasBlocklisted) {
form_manager->MarkWasUnblocklisted();
}
}
}
void PasswordManager::DidNavigateMainFrame(bool form_may_be_submitted) {
std::unique_ptr<BrowserSavePasswordProgressLogger> logger;
if (password_manager_util::IsLoggingActive(client_)) {
logger = std::make_unique<BrowserSavePasswordProgressLogger>(
client_->GetLogManager());
logger->LogBoolean(Logger::STRING_DID_NAVIGATE_MAIN_FRAME,
form_may_be_submitted);
}
if (client_->IsNewTabPage()) {
if (logger)
logger->LogMessage(Logger::STRING_NAVIGATION_NTP);
// On a successful Chrome sign-in the page navigates to the new tab page
// (ntp). OnPasswordFormsRendered is not called on ntp. That is
// why the standard flow for saving hash does not work. Save a password hash
// now since a navigation to ntp is the sign of successful sign-in.
PasswordFormManager* manager = GetSubmittedManager();
if (manager && manager->GetSubmittedForm()
->form_data.is_gaia_with_skip_save_password_form) {
MaybeSavePasswordHash(manager);
}
}
// Reset |possible_username_| if the navigation cannot be a result of form
// submission.
if (!form_may_be_submitted)
possible_username_.reset();
for (std::unique_ptr<PasswordFormManager>& manager : form_managers_) {
if (form_may_be_submitted && manager->is_submitted()) {
owned_submitted_form_manager_ = std::move(manager);
break;
}
}
form_managers_.clear();
TryToFindPredictionsToPossibleUsernameData();
predictions_.clear();
store_password_called_ = false;
}
void PasswordManager::UpdateFormManagers() {
std::vector<PasswordFormManager*> form_managers;
for (const auto& form_manager : form_managers_)
form_managers.push_back(form_manager.get());
// Get the fetchers and all the drivers.
std::vector<FormFetcher*> fetchers;
std::vector<PasswordManagerDriver*> drivers;
for (PasswordFormManager* form_manager : form_managers) {
fetchers.push_back(form_manager->GetFormFetcher());
if (form_manager->GetDriver())
drivers.push_back(form_manager->GetDriver().get());
}
// Remove the duplicates.
std::sort(fetchers.begin(), fetchers.end());
fetchers.erase(std::unique(fetchers.begin(), fetchers.end()), fetchers.end());
std::sort(drivers.begin(), drivers.end());
drivers.erase(std::unique(drivers.begin(), drivers.end()), drivers.end());
// Refetch credentials for all the forms and update the drivers.
for (FormFetcher* fetcher : fetchers)
fetcher->Fetch();
// The autofill manager will be repopulated again when the credentials
// are retrieved.
for (PasswordManagerDriver* driver : drivers) {
// GetPasswordAutofillManager() is returning nullptr in iOS Chrome, since
// PasswordAutofillManager is not instantiated on iOS Chrome.
// See //ios/chrome/browser/passwords/ios_chrome_password_manager_driver.mm
if (driver->GetPasswordAutofillManager()) {
driver->GetPasswordAutofillManager()->DeleteFillData();
}
}
}
void PasswordManager::DropFormManagers() {
form_managers_.clear();
owned_submitted_form_manager_.reset();
visible_forms_data_.clear();
TryToFindPredictionsToPossibleUsernameData();
predictions_.clear();
}
bool PasswordManager::IsPasswordFieldDetectedOnPage() {
return !form_managers_.empty();
}
void PasswordManager::OnPasswordFormSubmitted(PasswordManagerDriver* driver,
const FormData& form_data) {
base::UmaHistogramEnumeration("PasswordManager.FormSubmission.PerProfileType",
client_->GetProfileType());
ProvisionallySaveForm(form_data, driver, false);
}
void PasswordManager::OnDynamicFormSubmission(
password_manager::PasswordManagerDriver* driver,
SubmissionIndicatorEvent event) {
if (password_manager_util::IsLoggingActive(client_)) {
BrowserSavePasswordProgressLogger logger(client_->GetLogManager());
logger.LogMessage(Logger::STRING_ON_DYNAMIC_FORM_SUBMISSION);
}
PasswordFormManager* submitted_manager = GetSubmittedManager();
// TODO(crbug.com/949519): Add UMA metric for how frequently submitted_manager
// is actually null.
if (!submitted_manager || !submitted_manager->GetSubmittedForm())
return;
const PasswordForm* submitted_form = submitted_manager->GetSubmittedForm();
if (gaia::IsGaiaSignonRealm(GURL(submitted_form->signon_realm))) {
// The GAIA signon realm (i.e. https://accounts.google.com) will always
// perform a full page redirect once the user cleared the login flow. Thus
// don't respond to other JavaScript based signals that would result in
// false positives with regard to successful logins.
return;
}
submitted_manager->UpdateSubmissionIndicatorEvent(event);
if (IsAutomaticSavePromptAvailable())
OnLoginSuccessful();
}
void PasswordManager::OnPasswordFormCleared(
PasswordManagerDriver* driver,
const autofill::FormData& form_data) {
PasswordFormManager* manager =
GetMatchedManager(driver, form_data.unique_renderer_id);
if (!manager || !manager->is_submitted() ||
!manager->GetSubmittedForm()->IsLikelyChangePasswordForm()) {
return;
}
// If a password form was cleared, login is successful.
if (form_data.is_form_tag &&
base::FeatureList::IsEnabled(
password_manager::features::kDetectFormSubmissionOnFormClear)) {
manager->UpdateSubmissionIndicatorEvent(
SubmissionIndicatorEvent::CHANGE_PASSWORD_FORM_CLEARED);
OnLoginSuccessful();
return;
}
// If password fields outside the <form> tag were cleared, it should be
// verified that fields are relevant.
FieldRendererId new_password_field_id =
manager->GetSubmittedForm()->new_password_element_renderer_id;
auto it = base::ranges::find(form_data.fields, new_password_field_id,
&autofill::FormFieldData::unique_renderer_id);
if (it != form_data.fields.end() && it->value.empty() &&
base::FeatureList::IsEnabled(
features::kDetectFormSubmissionOnFormClear)) {
manager->UpdateSubmissionIndicatorEvent(
SubmissionIndicatorEvent::CHANGE_PASSWORD_FORM_CLEARED);
OnLoginSuccessful();
}
}
#if defined(OS_IOS)
void PasswordManager::OnSubframeFormSubmission(PasswordManagerDriver* driver,
const FormData& form_data) {
if (password_manager_util::IsLoggingActive(client_)) {
BrowserSavePasswordProgressLogger logger(client_->GetLogManager());
logger.LogMessage(Logger::STRING_ON_DYNAMIC_FORM_SUBMISSION);
}
ProvisionallySaveForm(form_data, driver, false);
if (IsAutomaticSavePromptAvailable())
OnLoginSuccessful();
}
#endif
void PasswordManager::OnUserModifiedNonPasswordField(
PasswordManagerDriver* driver,
autofill::FieldRendererId renderer_id,
const std::u16string& field_name,
const std::u16string& value) {
// |driver| might be empty on iOS or in tests.
int driver_id = driver ? driver->GetId() : 0;
possible_username_.emplace(GetSignonRealm(driver->GetLastCommittedURL()),
renderer_id, field_name, value, base::Time::Now(),
driver_id);
}
void PasswordManager::OnInformAboutUserInput(PasswordManagerDriver* driver,
const FormData& form_data) {
PasswordFormManager* manager = ProvisionallySaveForm(form_data, driver, true);
auto availability =
manager ? PasswordManagerMetricsRecorder::FormManagerAvailable::kSuccess
: PasswordManagerMetricsRecorder::FormManagerAvailable::
kMissingManual;
if (client_->GetMetricsRecorder())
client_->GetMetricsRecorder()->RecordFormManagerAvailable(availability);
ShowManualFallbackForSaving(manager, form_data);
}
void PasswordManager::HideManualFallbackForSaving() {
client_->HideManualFallbackForSaving();
}
void PasswordManager::OnPasswordFormsParsed(
PasswordManagerDriver* driver,
const std::vector<FormData>& form_data) {
CreatePendingLoginManagers(driver, form_data);
PasswordGenerationFrameHelper* password_generation_manager =
driver ? driver->GetPasswordGenerationHelper() : nullptr;
if (password_generation_manager) {
password_generation_manager->PrefetchSpec(
client_->GetLastCommittedOrigin().GetURL());
}
}
void PasswordManager::CreatePendingLoginManagers(
PasswordManagerDriver* driver,
const std::vector<FormData>& forms_data) {
std::unique_ptr<BrowserSavePasswordProgressLogger> logger;
if (password_manager_util::IsLoggingActive(client_)) {
logger = std::make_unique<BrowserSavePasswordProgressLogger>(
client_->GetLogManager());
logger->LogMessage(Logger::STRING_CREATE_LOGIN_MANAGERS_METHOD);
}
CreateFormManagers(driver, forms_data);
// Record whether or not this top-level URL has at least one password field.
client_->AnnotateNavigationEntry(!forms_data.empty());
// Only report SSL error status for cases where there are potentially forms to
// fill or save from.
if (!forms_data.empty()) {
metrics_util::CertificateError cert_error =
metrics_util::CertificateError::NONE;
const net::CertStatus cert_status = client_->GetMainFrameCertStatus();
// The order of the if statements matters -- if the status involves multiple
// errors, Chrome should report the one highest up in the list below.
if (cert_status & net::CERT_STATUS_AUTHORITY_INVALID)
cert_error = metrics_util::CertificateError::AUTHORITY_INVALID;
else if (cert_status & net::CERT_STATUS_COMMON_NAME_INVALID)
cert_error = metrics_util::CertificateError::COMMON_NAME_INVALID;
else if (cert_status & net::CERT_STATUS_WEAK_SIGNATURE_ALGORITHM)
cert_error = metrics_util::CertificateError::WEAK_SIGNATURE_ALGORITHM;
else if (cert_status & net::CERT_STATUS_DATE_INVALID)
cert_error = metrics_util::CertificateError::DATE_INVALID;
else if (net::IsCertStatusError(cert_status))
cert_error = metrics_util::CertificateError::OTHER;
UMA_HISTOGRAM_ENUMERATION(
"PasswordManager.CertificateErrorsWhileSeeingForms", cert_error,
metrics_util::CertificateError::COUNT);
}
}
void PasswordManager::CreateFormManagers(
PasswordManagerDriver* driver,
const std::vector<FormData>& forms_data) {
// Find new forms.
std::vector<const FormData*> new_forms_data;
for (const FormData& form_data : forms_data) {
if (!client_->IsFillingEnabled(form_data.url))
continue;
PasswordFormManager* manager =
GetMatchedManager(driver, form_data.unique_renderer_id);
if (manager) {
// This extra filling is just duplicating redundancy that was in
// PasswordFormManager, that helps to fix cases when the site overrides
// filled values.
// TODO(https://crbug.com/831123): Implement more robust filling and
// remove the next line.
manager->FillForm(form_data, predictions_);
} else {
new_forms_data.push_back(&form_data);
}
}
// Create form manager for new forms.
for (const FormData* new_form_data : new_forms_data)
CreateFormManager(driver, *new_form_data);
}
PasswordFormManager* PasswordManager::CreateFormManager(
PasswordManagerDriver* driver,
const autofill::FormData& form) {
form_managers_.push_back(std::make_unique<PasswordFormManager>(
client_,
driver ? driver->AsWeakPtr() : base::WeakPtr<PasswordManagerDriver>(),
form, nullptr, std::make_unique<PasswordSaveManagerImpl>(client_),
nullptr));
form_managers_.back()->ProcessServerPredictions(predictions_);
return form_managers_.back().get();
}
PasswordFormManager* PasswordManager::ProvisionallySaveForm(
const FormData& submitted_form,
PasswordManagerDriver* driver,
bool is_manual_fallback) {
std::unique_ptr<BrowserSavePasswordProgressLogger> logger;
if (password_manager_util::IsLoggingActive(client_)) {
logger = std::make_unique<BrowserSavePasswordProgressLogger>(
client_->GetLogManager());
logger->LogMessage(Logger::STRING_PROVISIONALLY_SAVE_FORM_METHOD);
}
if (!client_->IsSavingAndFillingEnabled(submitted_form.url)) {
RecordProvisionalSaveFailure(
PasswordManagerMetricsRecorder::SAVING_DISABLED, submitted_form.url);
return nullptr;
}
if (store_password_called_)
return nullptr;
const GURL& submitted_url = submitted_form.url;
if (ShouldBlockPasswordForSameOriginButDifferentScheme(submitted_url)) {
RecordProvisionalSaveFailure(
PasswordManagerMetricsRecorder::SAVING_ON_HTTP_AFTER_HTTPS,
submitted_url);
return nullptr;
}
PasswordFormManager* matched_manager =
GetMatchedManager(driver, submitted_form.unique_renderer_id);
auto availability =
matched_manager
? PasswordManagerMetricsRecorder::FormManagerAvailable::kSuccess
: PasswordManagerMetricsRecorder::FormManagerAvailable::
kMissingProvisionallySave;
if (client_->GetMetricsRecorder())
client_->GetMetricsRecorder()->RecordFormManagerAvailable(availability);
if (!matched_manager) {
RecordProvisionalSaveFailure(
PasswordManagerMetricsRecorder::NO_MATCHING_FORM, submitted_form.url);
matched_manager = CreateFormManager(driver, submitted_form);
}
if (is_manual_fallback && matched_manager->GetFormFetcher()->GetState() ==
FormFetcher::State::WAITING) {
// In case of manual fallback, the form manager has to be ready for saving.
return nullptr;
}
TryToFindPredictionsToPossibleUsernameData();
const PossibleUsernameData* possible_username =
possible_username_ ? &possible_username_.value() : nullptr;
if (!matched_manager->ProvisionallySave(submitted_form, driver,
possible_username)) {
return nullptr;
}
// Set all other form managers to no submission state.
for (const auto& manager : form_managers_) {
if (manager.get() != matched_manager)
manager->set_not_submitted();
}
// Cache the committed URL. Once the post-submit navigation concludes, we
// compare the landing URL against the cached and report the difference.
submitted_form_url_ = submitted_url;
ReportSubmittedFormFrameMetric(driver, *matched_manager->GetSubmittedForm());
return matched_manager;
}
#if !defined(OS_IOS)
void PasswordManager::LogFirstFillingResult(
PasswordManagerDriver* driver,
autofill::FormRendererId form_renderer_id,
int32_t result) {
PasswordFormManager* matching_manager =
FindMatchedManagerByRendererId(form_renderer_id, form_managers_, driver);
if (!matching_manager)
return;
matching_manager->GetMetricsRecorder()->RecordFirstFillingResult(result);
}
#endif // !defined(OS_IOS)
void PasswordManager::NotifyStorePasswordCalled() {
store_password_called_ = true;
DropFormManagers();
}
#if defined(OS_IOS)
void PasswordManager::PresaveGeneratedPassword(
PasswordManagerDriver* driver,
const FormData& form,
const std::u16string& generated_password,
FieldRendererId generation_element) {
PasswordFormManager* form_manager =
GetMatchedManager(driver, form.unique_renderer_id);
UMA_HISTOGRAM_BOOLEAN("PasswordManager.GeneratedFormHasNoFormManager",
!form_manager);
// TODO(https://crbug.com/886583): Create form manager if not found.
if (form_manager) {
form_manager->PresaveGeneratedPassword(driver, form, generated_password,
generation_element);
form_manager->ProvisionallySave(
form, driver, base::OptionalOrNullptr(possible_username_));
}
}
void PasswordManager::UpdateStateOnUserInput(
PasswordManagerDriver* driver,
FormRendererId form_id,
FieldRendererId field_id,
const std::u16string& field_value) {
for (std::unique_ptr<PasswordFormManager>& manager : form_managers_) {
if (manager->UpdateStateOnUserInput(form_id, field_id, field_value)) {
ProvisionallySaveForm(*manager->observed_form(), driver, true);
if (manager->is_submitted() && !manager->HasGeneratedPassword()) {
ShowManualFallbackForSaving(manager.get(), *manager->observed_form());
} else {
HideManualFallbackForSaving();
}
break;
}
}
}
void PasswordManager::OnPasswordNoLongerGenerated(
PasswordManagerDriver* driver) {
for (std::unique_ptr<PasswordFormManager>& manager : form_managers_)
manager->PasswordNoLongerGenerated();
}
void PasswordManager::OnPasswordFormRemoved(
PasswordManagerDriver* driver,
const FieldDataManager& field_data_manager,
FormRendererId form_id) {
for (auto& manager : form_managers_) {
if (driver && !manager->GetDriver())
manager->SetDriver(driver->AsWeakPtr());
// Find a form with corresponding renderer id.
if (manager->DoesManage(form_id, driver)) {
DetectPotentialSubmission(manager.get(), field_data_manager, driver);
return;
}
}
}
void PasswordManager::OnIframeDetach(
const std::string& frame_id,
PasswordManagerDriver* driver,
const FieldDataManager& field_data_manager) {
for (auto& manager : form_managers_) {
// Find a form with corresponding frame id. Stop iterating in case the
// target form manager was found to avoid crbug.com/1129758 and since only
// one password form is being submitted at a time.
if (manager->observed_form()->frame_id == frame_id &&
DetectPotentialSubmission(manager.get(), field_data_manager, driver)) {
return;
}
}
}
void PasswordManager::PropagateFieldDataManagerInfo(
const FieldDataManager& field_data_manager,
const PasswordManagerDriver* driver) {
for (auto& manager : form_managers_) {
if (!client_->IsSavingAndFillingEnabled(manager->GetURL())) {
RecordProvisionalSaveFailure(
PasswordManagerMetricsRecorder::SAVING_DISABLED, manager->GetURL());
continue;
}
manager->ProvisionallySaveFieldDataManagerInfo(field_data_manager, driver);
}
}
#endif
bool PasswordManager::IsAutomaticSavePromptAvailable() {
std::unique_ptr<BrowserSavePasswordProgressLogger> logger;
if (password_manager_util::IsLoggingActive(client_)) {
logger = std::make_unique<BrowserSavePasswordProgressLogger>(
client_->GetLogManager());
logger->LogMessage(Logger::STRING_CAN_PROVISIONAL_MANAGER_SAVE_METHOD);
}
PasswordFormManager* submitted_manager = GetSubmittedManager();
if (!submitted_manager) {
if (logger) {
logger->LogMessage(Logger::STRING_NO_PROVISIONAL_SAVE_MANAGER);
}
return false;
}
if (submitted_manager->GetFormFetcher()->GetState() ==
FormFetcher::State::WAITING) {
// We have a provisional save manager, but it didn't finish matching yet.
// We just give up.
RecordProvisionalSaveFailure(
PasswordManagerMetricsRecorder::MATCHING_NOT_COMPLETE,
submitted_manager->GetURL());
return false;
}
return !submitted_manager->GetPendingCredentials().only_for_fallback;
}
bool PasswordManager::ShouldBlockPasswordForSameOriginButDifferentScheme(
const GURL& url) const {
return submitted_form_url_.host_piece() == url.host_piece() &&
submitted_form_url_.SchemeIsCryptographic() &&
!url.SchemeIsCryptographic();
}
void PasswordManager::OnPasswordFormsRendered(
password_manager::PasswordManagerDriver* driver,
const std::vector<FormData>& visible_forms_data,
bool did_stop_loading) {
CreatePendingLoginManagers(driver, visible_forms_data);
std::unique_ptr<BrowserSavePasswordProgressLogger> logger;
if (password_manager_util::IsLoggingActive(client_)) {
logger = std::make_unique<BrowserSavePasswordProgressLogger>(
client_->GetLogManager());
logger->LogMessage(Logger::STRING_ON_PASSWORD_FORMS_RENDERED_METHOD);
}
if (!IsAutomaticSavePromptAvailable())
return;
PasswordFormManager* submitted_manager = GetSubmittedManager();
// If the server throws an internal error, access denied page, page not
// found etc. after a login attempt, we do not save the credentials.
if (client_->WasLastNavigationHTTPError()) {
if (logger)
logger->LogMessage(Logger::STRING_DECISION_DROP);
submitted_manager->GetMetricsRecorder()->LogSubmitFailed();
ResetSubmittedManager();
return;
}
if (logger) {
logger->LogNumber(Logger::STRING_NUMBER_OF_VISIBLE_FORMS,
visible_forms_data.size());
}
// Record all visible forms from the frame.
visible_forms_data_.insert(visible_forms_data_.end(),
visible_forms_data.begin(),
visible_forms_data.end());
if (!did_stop_loading &&
!submitted_manager->GetSubmittedForm()
->form_data.is_gaia_with_skip_save_password_form) {
// |form_data.is_gaia_with_skip_save_password_form| = true means that this
// is a Chrome sign-in page. Chrome sign-in pages are redirected to an empty
// pages, and for some reasons |did_stop_loading| might be false. So
// |did_stop_loading| is ignored for them.
return;
}
if (!driver->IsInPrimaryMainFrame() &&
submitted_manager->driver_id() != driver->GetId()) {
// Frames different from the main frame and the frame of the submitted form
// are unlikely relevant to success of submission.
return;
}
// If we see the login form again, then the login failed.
if (submitted_manager->GetPendingCredentials().scheme ==
PasswordForm::Scheme::kHtml) {
for (const FormData& form_data : visible_forms_data_) {
if (submitted_manager->IsEqualToSubmittedForm(form_data)) {
if (submitted_manager->HasLikelyChangePasswordFormSubmitted() &&
AreChangePasswordFieldsEmpty(
form_data, *submitted_manager->GetSubmittedForm())) {
continue;
}
submitted_manager->GetMetricsRecorder()->LogSubmitFailed();
if (logger) {
logger->LogFormData(Logger::STRING_PASSWORD_FORM_REAPPEARED,
form_data);
logger->LogMessage(Logger::STRING_DECISION_DROP);
}
ResetSubmittedManager();
// Clear visible_forms_data_ once we found the match.
visible_forms_data_.clear();
return;
}
}
} else {
if (logger)
logger->LogMessage(Logger::STRING_PROVISIONALLY_SAVED_FORM_IS_NOT_HTML);
}
// Clear visible_forms_data_ after checking all the visible forms.
visible_forms_data_.clear();
// Looks like a successful login attempt. Either show an infobar or
// automatically save the login data. We prompt when the user hasn't
// already given consent, either through previously accepting the infobar
// or by having the browser generate the password.
OnLoginSuccessful();
}
void PasswordManager::OnLoginSuccessful() {
if (client_->IsAutofillAssistantUIVisible()) {
// Suppress prompts while Autofill Assistant UI is shown.
return;
}
std::unique_ptr<BrowserSavePasswordProgressLogger> logger;
if (password_manager_util::IsLoggingActive(client_)) {
logger = std::make_unique<BrowserSavePasswordProgressLogger>(
client_->GetLogManager());
logger->LogMessage(Logger::STRING_ON_ASK_USER_OR_SAVE_PASSWORD);
}
PasswordFormManager* submitted_manager = GetSubmittedManager();
DCHECK(submitted_manager);
const PasswordForm* submitted_form = submitted_manager->GetSubmittedForm();
DCHECK(submitted_form);
client_->MaybeReportEnterpriseLoginEvent(
submitted_form->url, submitted_form->IsFederatedCredential(),
submitted_form->federation_origin,
submitted_manager->GetPendingCredentials().username_value);
if (!client_->IsSavingAndFillingEnabled(submitted_form->url))
return;
client_->GetStoreResultFilter()->ReportFormLoginSuccess(*submitted_manager);
// Check for leaks only if there are no muted credentials.
if (!HasMutedCredentials(
submitted_manager->GetInsecureCredentials(),
submitted_manager->GetSubmittedForm()->username_value)) {
leak_delegate_.StartLeakCheck(submitted_manager->GetPendingCredentials());
}
auto submission_event =
submitted_manager->GetSubmittedForm()->submission_event;
metrics_util::LogPasswordSuccessfulSubmissionIndicatorEvent(submission_event);
if (logger)
logger->LogSuccessfulSubmissionIndicatorEvent(submission_event);
bool able_to_save_passwords =
client_->GetProfilePasswordStore()->IsAbleToSavePasswords();
UMA_HISTOGRAM_BOOLEAN("PasswordManager.AbleToSavePasswordsOnSuccessfulLogin",
able_to_save_passwords);
if (!able_to_save_passwords)
return;
MaybeSavePasswordHash(submitted_manager);
// TODO(https://crbug.com/831123): Implement checking whether to save with
// PasswordFormManager.
// Check whether the filter allows saving this credential. In practice, this
// prevents saving the password of the syncing account. However, if the
// password is already saved, then *updating* it is still allowed - better
// than keeping an outdated password around.
if (!submitted_manager->IsPasswordUpdate() &&
!client_->GetStoreResultFilter()->ShouldSave(
*submitted_manager->GetSubmittedForm())) {
RecordProvisionalSaveFailure(
PasswordManagerMetricsRecorder::SYNC_CREDENTIAL,
submitted_manager->GetURL());
ResetSubmittedManager();
return;
}
submitted_manager->GetMetricsRecorder()->LogSubmitPassed();
UMA_HISTOGRAM_BOOLEAN(
"PasswordManager.SuccessfulLoginHappened",
submitted_manager->GetSubmittedForm()->url.SchemeIsCryptographic());
// If the form is eligible only for saving fallback, it shouldn't go here.
DCHECK(!submitted_manager->GetPendingCredentials().only_for_fallback);
if (ShouldPromptUserToSavePassword(*submitted_manager)) {
if (logger)
logger->LogMessage(Logger::STRING_DECISION_ASK);
submitted_manager->SaveSuggestedUsernameValueToVotesUploader();
bool update_password = submitted_manager->IsPasswordUpdate();
if (client_->PromptUserToSaveOrUpdatePassword(MoveOwnedSubmittedManager(),
update_password)) {
if (logger)
logger->LogMessage(Logger::STRING_SHOW_PASSWORD_PROMPT);
}
} else {
if (logger)
logger->LogMessage(Logger::STRING_DECISION_SAVE);
submitted_manager->Save();
if (!submitted_manager->IsNewLogin()) {
client_->NotifySuccessfulLoginWithExistingPassword(
submitted_manager->Clone());
}
if (submitted_manager->HasGeneratedPassword())
client_->AutomaticPasswordSave(MoveOwnedSubmittedManager());
}
ResetSubmittedManager();
}
void PasswordManager::MaybeSavePasswordHash(
PasswordFormManager* submitted_manager) {
if (!base::FeatureList::IsEnabled(features::kPasswordReuseDetectionEnabled)) {
return;
}
const PasswordForm* submitted_form = submitted_manager->GetSubmittedForm();
// When |username_value| is empty, it's not clear whether the submitted
// credentials are really Gaia or enterprise credentials. Don't save
// password hash in that case.
std::string username = base::UTF16ToUTF8(submitted_form->username_value);
if (username.empty())
return;
password_manager::PasswordReuseManager* reuse_manager =
client_->GetPasswordReuseManager();
// May be null in tests.
if (!reuse_manager)
return;
bool should_save_enterprise_pw =
client_->GetStoreResultFilter()->ShouldSaveEnterprisePasswordHash(
*submitted_form);
bool should_save_gaia_pw =
client_->GetStoreResultFilter()->ShouldSaveGaiaPasswordHash(
*submitted_form);
if (!should_save_enterprise_pw && !should_save_gaia_pw)
return;
if (password_manager_util::IsLoggingActive(client_)) {
BrowserSavePasswordProgressLogger logger(client_->GetLogManager());
logger.LogMessage(Logger::STRING_SAVE_PASSWORD_HASH);
}
// Canonicalizes username if it is an email.
if (username.find('@') != std::string::npos)
username = gaia::CanonicalizeEmail(username);
bool is_password_change = !submitted_form->new_password_element.empty();
const std::u16string password = is_password_change
? submitted_form->new_password_value
: submitted_form->password_value;
if (should_save_enterprise_pw) {
reuse_manager->SaveEnterprisePasswordHash(username, password);
return;
}
DCHECK(should_save_gaia_pw);
bool is_sync_account_email =
client_->GetStoreResultFilter()->IsSyncAccountEmail(username);
GaiaPasswordHashChange event =
is_sync_account_email
? (is_password_change
? GaiaPasswordHashChange::CHANGED_IN_CONTENT_AREA
: GaiaPasswordHashChange::SAVED_IN_CONTENT_AREA)
: (is_password_change
? GaiaPasswordHashChange::NOT_SYNC_PASSWORD_CHANGE
: GaiaPasswordHashChange::SAVED_IN_CONTENT_AREA);
reuse_manager->SaveGaiaPasswordHash(username, password, is_sync_account_email,
event);
}
void PasswordManager::ProcessAutofillPredictions(
PasswordManagerDriver* driver,
const std::vector<FormStructure*>& forms) {
std::unique_ptr<BrowserSavePasswordProgressLogger> logger;
if (password_manager_util::IsLoggingActive(client_)) {
logger = std::make_unique<BrowserSavePasswordProgressLogger>(
client_->GetLogManager());
}
for (const FormStructure* form : forms) {
// |driver| might be empty on iOS or in tests.
int driver_id = driver ? driver->GetId() : 0;
predictions_[form->form_signature()] =
ConvertToFormPredictions(driver_id, *form);
AddLocallySavedPredictions(client_->GetFieldInfoManager(),
&predictions_[form->form_signature()],
logger.get());
}
// Create form managers for non-password forms if |predictions_| has evidence
// that these forms are password related.
for (const FormStructure* form : forms) {
if (logger)
logger->LogFormStructure(Logger::STRING_SERVER_PREDICTIONS, *form);
if (GetMatchedManager(driver, form->global_id().renderer_id)) {
// The form manager is already created.
continue;
}
if (form->has_password_field())
continue;
const FormPredictions* form_predictions =
&predictions_[form->form_signature()];
// Do not skip the form if it either contains a field for the Username
// first flow or a clear-text password field.
if (!(HasSingleUsernameVote(*form_predictions) ||
HasNewPasswordVote(*form_predictions))) {
continue;
}
CreateFormManager(driver, form->ToFormData());
}
for (auto& manager : form_managers_)
manager->ProcessServerPredictions(predictions_);
}
PasswordFormManager* PasswordManager::GetSubmittedManager() const {
if (owned_submitted_form_manager_)
return owned_submitted_form_manager_.get();
for (const std::unique_ptr<PasswordFormManager>& manager : form_managers_) {
if (manager->is_submitted())
return manager.get();
}
return nullptr;
}
void PasswordManager::SaveSubmittedManager() {
PasswordFormManager* submitted_manager = GetSubmittedManager();
DCHECK(submitted_manager);
submitted_manager->Save();
}
void PasswordManager::ResetSubmittedManager() {
if (owned_submitted_form_manager_) {
owned_submitted_form_manager_.reset();
return;
}
auto submitted_manager =
base::ranges::find_if(form_managers_, &PasswordFormManager::is_submitted);
if (submitted_manager != form_managers_.end())
form_managers_.erase(submitted_manager);
}
std::unique_ptr<PasswordFormManagerForUI>
PasswordManager::MoveOwnedSubmittedManager() {
if (owned_submitted_form_manager_)
return std::move(owned_submitted_form_manager_);
for (auto iter = form_managers_.begin(); iter != form_managers_.end();
++iter) {
if ((*iter)->is_submitted()) {
std::unique_ptr<PasswordFormManager> submitted_manager = std::move(*iter);
form_managers_.erase(iter);
return std::move(submitted_manager);
}
}
NOTREACHED();
return nullptr;
}
void PasswordManager::RecordProvisionalSaveFailure(
PasswordManagerMetricsRecorder::ProvisionalSaveFailure failure,
const GURL& form_origin) {
std::unique_ptr<BrowserSavePasswordProgressLogger> logger;
if (password_manager_util::IsLoggingActive(client_)) {
logger = std::make_unique<BrowserSavePasswordProgressLogger>(
client_->GetLogManager());
}
if (client_->GetMetricsRecorder()) {
client_->GetMetricsRecorder()->RecordProvisionalSaveFailure(
failure, submitted_form_url_, form_origin, logger.get());
}
}
// TODO(https://crbug.com/831123): Implement creating missing
// PasswordFormManager when PasswordFormManager is gone.
PasswordFormManager* PasswordManager::GetMatchedManager(
PasswordManagerDriver* driver,
FormRendererId form_id) {
for (auto& form_manager : form_managers_) {
// Until support of cross-origin iframes is implemented, there is only one
// driver on iOS. It needs to be set in order for filling to work.
#if defined(OS_IOS)
if (driver && !form_manager->GetDriver())
form_manager->SetDriver(driver->AsWeakPtr());
#endif
if (form_manager->DoesManage(form_id, driver))
return form_manager.get();
}
return nullptr;
}
void PasswordManager::ReportSubmittedFormFrameMetric(
const PasswordManagerDriver* driver,
const PasswordForm& form) {
if (!driver)
return;
metrics_util::SubmittedFormFrame frame;
if (driver->IsInPrimaryMainFrame()) {
frame = metrics_util::SubmittedFormFrame::MAIN_FRAME;
} else if (form.url == client()->GetLastCommittedURL()) {
frame =
metrics_util::SubmittedFormFrame::IFRAME_WITH_SAME_URL_AS_MAIN_FRAME;
} else {
std::string main_frame_signon_realm =
GetSignonRealm(client()->GetLastCommittedURL());
frame = (main_frame_signon_realm == form.signon_realm)
? metrics_util::SubmittedFormFrame::
IFRAME_WITH_DIFFERENT_URL_SAME_SIGNON_REALM_AS_MAIN_FRAME
: metrics_util::SubmittedFormFrame::
IFRAME_WITH_DIFFERENT_SIGNON_REALM;
}
metrics_util::LogSubmittedFormFrame(frame);
}
void PasswordManager::TryToFindPredictionsToPossibleUsernameData() {
if (!possible_username_ || possible_username_->form_predictions)
return;
for (auto it : predictions_) {
if (it.second.driver_id != possible_username_->driver_id)
continue;
for (const PasswordFieldPrediction& field : it.second.fields) {
if (field.renderer_id == possible_username_->renderer_id) {
possible_username_->form_predictions = it.second;
return;
}
}
}
}
void PasswordManager::ShowManualFallbackForSaving(
PasswordFormManager* form_manager,
const FormData& form_data) {
// Where `form_manager` is nullptr, make sure the manual fallback isn't
// shown. One scenario where this is relevant is when the user inputs some
// password and then removes it. Upon removing the password, the
// `form_manager` will become nullptr.
if (!form_manager) {
HideManualFallbackForSaving();
return;
}
if (!form_manager->is_submitted())
return;
if (!client_->GetProfilePasswordStore()->IsAbleToSavePasswords() ||
!client_->IsSavingAndFillingEnabled(form_data.url) ||
ShouldBlockPasswordForSameOriginButDifferentScheme(form_data.url)) {
return;
}
if (!client_->GetStoreResultFilter()->ShouldSave(
*form_manager->GetSubmittedForm())) {
return;
}
// Show the fallback if a prompt or a confirmation bubble should be available.
bool has_generated_password = form_manager->HasGeneratedPassword();
if (ShouldPromptUserToSavePassword(*form_manager) || has_generated_password) {
bool is_update = form_manager->IsPasswordUpdate();
form_manager->GetMetricsRecorder()->RecordShowManualFallbackForSaving(
has_generated_password, is_update);
client_->ShowManualFallbackForSaving(form_manager->Clone(),
has_generated_password, is_update);
} else {
HideManualFallbackForSaving();
}
}
void PasswordManager::ResetPendingCredentials() {
for (auto& form_manager : form_managers_)
form_manager->ResetState();
owned_submitted_form_manager_.reset();
}
bool PasswordManager::IsFormManagerPendingPasswordUpdate() const {
for (const auto& form_manager : form_managers_) {
if (form_manager->IsPasswordUpdate())
return true;
}
return owned_submitted_form_manager_ &&
owned_submitted_form_manager_->IsPasswordUpdate();
}
#if defined(OS_IOS)
bool PasswordManager::DetectPotentialSubmission(
PasswordFormManager* form_manager,
const FieldDataManager& field_data_manager,
PasswordManagerDriver* driver) {
// Do not attempt to detect submission if saving is disabled.
if (!client_->IsSavingAndFillingEnabled(form_manager->GetURL())) {
RecordProvisionalSaveFailure(
PasswordManagerMetricsRecorder::SAVING_DISABLED,
form_manager->GetURL());
return false;
}
// If the manager is not submitted, it still can have autofilled data.
if (!form_manager->is_submitted()) {
form_manager->ProvisionallySaveFieldDataManagerInfo(field_data_manager,
driver);
}
// If the manager was set to be submitted, either prior to this function call
// or on provisional save above, consider submission successful.
if (form_manager->is_submitted()) {
OnLoginSuccessful();
return true;
}
return false;
}
#endif
} // namespace password_manager