blob: 1e71049d4eece1f7c2c62692c4fd8dc9151d8a9f [file] [log] [blame]
// Copyright 2018 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 "chrome/browser/password_manager/password_accessory_controller.h"
#include <vector>
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/android/preferences/preferences_launcher.h"
#include "chrome/browser/password_manager/password_accessory_view_interface.h"
#include "chrome/browser/password_manager/password_generation_dialog_view_interface.h"
#include "chrome/browser/ui/passwords/manage_passwords_view_utils.h"
#include "chrome/grit/generated_resources.h"
#include "components/autofill/content/browser/content_autofill_driver.h"
#include "components/autofill/content/browser/content_autofill_driver_factory.h"
#include "components/autofill/core/common/password_form.h"
#include "components/password_manager/content/browser/content_password_manager_driver.h"
#include "components/password_manager/content/browser/content_password_manager_driver_factory.h"
#include "components/password_manager/core/browser/password_manager_driver.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
#include "chrome/browser/android/preferences/preferences_launcher.h"
using autofill::PasswordForm;
using Item = PasswordAccessoryViewInterface::AccessoryItem;
DEFINE_WEB_CONTENTS_USER_DATA_KEY(PasswordAccessoryController);
struct PasswordAccessoryController::GenerationElementData {
GenerationElementData(autofill::PasswordForm form,
autofill::FormSignature form_signature,
autofill::FieldSignature field_signature,
uint32_t max_password_length)
: form(std::move(form)),
form_signature(form_signature),
field_signature(field_signature),
max_password_length(max_password_length) {}
// Form for which password generation is triggered.
autofill::PasswordForm form;
// Signature of the form for which password generation is triggered.
autofill::FormSignature form_signature;
// Signature of the field for which password generation is triggered.
autofill::FieldSignature field_signature;
// Maximum length of the generated password.
uint32_t max_password_length;
};
PasswordAccessoryController::PasswordAccessoryController(
content::WebContents* web_contents)
: web_contents_(web_contents),
view_(PasswordAccessoryViewInterface::Create(this)),
create_dialog_factory_(
base::BindRepeating(&PasswordGenerationDialogViewInterface::Create)) {
}
// Additional creation functions in unit tests only:
PasswordAccessoryController::PasswordAccessoryController(
content::WebContents* web_contents,
std::unique_ptr<PasswordAccessoryViewInterface> view,
CreateDialogFactory create_dialog_factory)
: web_contents_(web_contents),
view_(std::move(view)),
create_dialog_factory_(create_dialog_factory) {}
PasswordAccessoryController::~PasswordAccessoryController() = default;
// static
void PasswordAccessoryController::CreateForWebContentsForTesting(
content::WebContents* web_contents,
std::unique_ptr<PasswordAccessoryViewInterface> view,
CreateDialogFactory create_dialog_factory) {
DCHECK(web_contents) << "Need valid WebContents to attach controller to!";
DCHECK(!FromWebContents(web_contents)) << "Controller already attached!";
web_contents->SetUserData(
UserDataKey(),
base::WrapUnique(new PasswordAccessoryController(
web_contents, std::move(view), create_dialog_factory)));
}
void PasswordAccessoryController::OnPasswordsAvailable(
const std::map<base::string16, const PasswordForm*>& best_matches,
const GURL& origin) {
const url::Origin& tab_origin =
web_contents_->GetMainFrame()->GetLastCommittedOrigin();
if (!tab_origin.IsSameOriginWith(url::Origin::Create(origin))) {
// TODO(fhorschig): Support iframes: https://crbug.com/854150.
return;
}
DCHECK(view_);
std::vector<Item> items;
base::string16 passwords_title_str;
passwords_title_str = l10n_util::GetStringFUTF16(
best_matches.empty()
? IDS_PASSWORD_MANAGER_ACCESSORY_PASSWORD_LIST_EMPTY_MESSAGE
: IDS_PASSWORD_MANAGER_ACCESSORY_PASSWORD_LIST_TITLE,
base::ASCIIToUTF16(origin.host()));
items.emplace_back(passwords_title_str, passwords_title_str,
/*is_password=*/false, Item::Type::LABEL);
for (const auto& pair : best_matches) {
const PasswordForm* form = pair.second;
base::string16 username = GetDisplayUsername(*form);
items.emplace_back(username, username, /*is_password=*/false,
form->username_value.empty()
? Item::Type::NON_INTERACTIVE_SUGGESTION
: Item::Type::SUGGESTION);
items.emplace_back(
form->password_value,
l10n_util::GetStringFUTF16(
IDS_PASSWORD_MANAGER_ACCESSORY_PASSWORD_DESCRIPTION, username),
/*is_password=*/true, Item::Type::SUGGESTION);
}
items.emplace_back(base::string16(), base::string16(), false,
Item::Type::DIVIDER);
base::string16 manage_passwords_title = l10n_util::GetStringUTF16(
IDS_PASSWORD_MANAGER_ACCESSORY_ALL_PASSWORDS_LINK);
items.emplace_back(manage_passwords_title, manage_passwords_title, false,
Item::Type::OPTION);
view_->OnItemsAvailable(origin, items);
}
void PasswordAccessoryController::DidNavigateMainFrame() {
// Sending no passwords removes stale stuggestions and sends default options.
OnPasswordsAvailable(
/*best_matches=*/{},
web_contents_->GetMainFrame()->GetLastCommittedOrigin().GetURL());
}
void PasswordAccessoryController::OnAutomaticGenerationStatusChanged(
bool available,
const base::Optional<
autofill::password_generation::PasswordGenerationUIData>& ui_data,
const base::WeakPtr<password_manager::PasswordManagerDriver>& driver) {
target_frame_driver_ = driver;
if (available) {
DCHECK(ui_data.has_value());
generation_element_data_ = std::make_unique<GenerationElementData>(
ui_data.value().password_form,
autofill::CalculateFormSignature(
ui_data.value().password_form.form_data),
autofill::CalculateFieldSignatureByNameAndType(
ui_data.value().generation_element, "password"),
ui_data.value().max_length);
} else {
generation_element_data_.reset();
}
DCHECK(view_);
view_->OnAutomaticGenerationStatusChanged(available);
}
void PasswordAccessoryController::OnFillingTriggered(
bool is_password,
const base::string16& textToFill) const {
password_manager::ContentPasswordManagerDriverFactory* factory =
password_manager::ContentPasswordManagerDriverFactory::FromWebContents(
web_contents_);
DCHECK(factory);
// TODO(fhorschig): Consider allowing filling on non-main frames.
password_manager::ContentPasswordManagerDriver* driver =
factory->GetDriverForFrame(web_contents_->GetMainFrame());
if (!driver) {
return;
} // |driver| can be NULL if the tab is being closed.
driver->FillIntoFocusedField(is_password, textToFill);
}
void PasswordAccessoryController::OnOptionSelected(
const base::string16& selectedOption) const {
if (selectedOption ==
l10n_util::GetStringUTF16(
IDS_PASSWORD_MANAGER_ACCESSORY_ALL_PASSWORDS_LINK)) {
chrome::android::PreferencesLauncher::ShowPasswordSettings();
}
}
void PasswordAccessoryController::OnGenerationRequested() {
if (!target_frame_driver_)
return;
// TODO(crbug.com/835234): Take the modal dialog logic out of the accessory
// controller.
dialog_view_ = create_dialog_factory_.Run(this);
uint32_t spec_priority = 0;
base::string16 password =
target_frame_driver_->GetPasswordGenerationManager()->GeneratePassword(
web_contents_->GetLastCommittedURL().GetOrigin(),
generation_element_data_->form_signature,
generation_element_data_->field_signature,
generation_element_data_->max_password_length, &spec_priority);
if (target_frame_driver_ && target_frame_driver_->GetPasswordManager()) {
target_frame_driver_->GetPasswordManager()
->ReportSpecPriorityForGeneratedPassword(generation_element_data_->form,
spec_priority);
}
dialog_view_->Show(password);
}
void PasswordAccessoryController::GeneratedPasswordAccepted(
const base::string16& password) {
if (!target_frame_driver_)
return;
target_frame_driver_->GeneratedPasswordAccepted(password);
dialog_view_.reset();
}
void PasswordAccessoryController::GeneratedPasswordRejected() {
dialog_view_.reset();
}
void PasswordAccessoryController::OnSavedPasswordsLinkClicked() {
chrome::android::PreferencesLauncher::ShowPasswordSettings();
}
gfx::NativeView PasswordAccessoryController::container_view() const {
return web_contents_->GetNativeView();
}
gfx::NativeWindow PasswordAccessoryController::native_window() const {
return web_contents_->GetTopLevelNativeWindow();
}