| // Copyright 2019 The Chromium Authors |
| // 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/android/password_generation_controller_impl.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/trace_event/trace_event.h" |
| #include "chrome/browser/autofill/manual_filling_controller.h" |
| #include "chrome/browser/password_manager/android/password_accessory_controller.h" |
| #include "chrome/browser/password_manager/android/password_generation_dialog_view_interface.h" |
| #include "chrome/browser/password_manager/chrome_password_manager_client.h" |
| #include "chrome/browser/touch_to_fill/password_generation/android/touch_to_fill_password_generation_controller.h" |
| #include "components/autofill/core/browser/ui/accessory_sheet_enums.h" |
| #include "components/autofill/core/common/autofill_features.h" |
| #include "components/autofill/core/common/password_generation_util.h" |
| #include "components/autofill/core/common/signatures.h" |
| #include "components/autofill/core/common/unique_ids.h" |
| #include "components/password_manager/content/browser/content_password_manager_driver.h" |
| #include "components/password_manager/core/browser/password_form.h" |
| #include "components/password_manager/core/browser/password_generation_frame_helper.h" |
| #include "components/password_manager/core/browser/password_manager.h" |
| #include "components/password_manager/core/browser/password_manager_client.h" |
| #include "components/password_manager/core/browser/password_manager_metrics_util.h" |
| #include "components/password_manager/core/common/password_manager_features.h" |
| |
| using autofill::mojom::FocusedFieldType; |
| using autofill::password_generation::PasswordGenerationType; |
| using password_manager::metrics_util::GenerationDialogChoice; |
| using ShouldShowAction = ManualFillingController::ShouldShowAction; |
| |
| PasswordGenerationControllerImpl::~PasswordGenerationControllerImpl() = default; |
| |
| // static |
| PasswordGenerationController* PasswordGenerationController::GetOrCreate( |
| content::WebContents* web_contents) { |
| PasswordGenerationControllerImpl::CreateForWebContents(web_contents); |
| return PasswordGenerationControllerImpl::FromWebContents(web_contents); |
| } |
| |
| // static |
| PasswordGenerationController* PasswordGenerationController::GetIfExisting( |
| content::WebContents* web_contents) { |
| return PasswordGenerationControllerImpl::FromWebContents(web_contents); |
| } |
| |
| struct PasswordGenerationControllerImpl::GenerationElementData { |
| GenerationElementData( |
| const autofill::password_generation::PasswordGenerationUIData& ui_data) { |
| const std::string kFieldType = "password"; |
| |
| form_data = ui_data.form_data; |
| form_signature = autofill::CalculateFormSignature(ui_data.form_data); |
| field_signature = autofill::CalculateFieldSignatureByNameAndType( |
| ui_data.generation_element, kFieldType); |
| generation_element_id = ui_data.generation_element_id; |
| max_password_length = ui_data.max_length; |
| } |
| |
| // Form for which password generation is triggered. |
| autofill::FormData form_data; |
| |
| // 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; |
| |
| // Renderer ID of the password field triggering generation. |
| autofill::FieldRendererId generation_element_id; |
| |
| // Maximum length of the generated password. |
| uint32_t max_password_length; |
| }; |
| |
| base::WeakPtr<password_manager::ContentPasswordManagerDriver> |
| PasswordGenerationControllerImpl::GetActiveFrameDriver() const { |
| return active_frame_driver_; |
| } |
| |
| void PasswordGenerationControllerImpl::OnAutomaticGenerationAvailable( |
| base::WeakPtr<password_manager::ContentPasswordManagerDriver> |
| target_frame_driver, |
| const autofill::password_generation::PasswordGenerationUIData& ui_data, |
| gfx::RectF element_bounds_in_screen_space) { |
| // We can't be sure that the active frame driver would be set in the |
| // FocusedInputChanged by now, because there is a race condition. The roots |
| // of the OnAutomaticGenerationAvailable and FocusedInputChanged calls are |
| // the same in the renderer. So we need to set it here too. |
| FocusedInputChanged(autofill::mojom::FocusedFieldType::kFillablePasswordField, |
| std::move(target_frame_driver)); |
| |
| active_frame_driver_->GetPasswordManager() |
| ->SetGenerationElementAndTypeForForm( |
| active_frame_driver_.get(), ui_data.form_data.unique_renderer_id, |
| ui_data.generation_element_id, PasswordGenerationType::kAutomatic); |
| |
| if (!base::FeatureList::IsEnabled( |
| autofill::features::kAutofillKeyboardAccessory)) { |
| active_frame_driver_->GetPasswordAutofillManager() |
| ->MaybeShowPasswordSuggestions(element_bounds_in_screen_space, |
| ui_data.text_direction); |
| } |
| |
| generation_element_data_ = std::make_unique<GenerationElementData>(ui_data); |
| |
| if (base::FeatureList::IsEnabled( |
| password_manager::features::kPasswordGenerationBottomSheet) && |
| touch_to_fill_generation_state_ == TouchToFillState::kNone) { |
| touch_to_fill_generation_controller_ = |
| std::make_unique<TouchToFillPasswordGenerationController>( |
| active_frame_driver_); |
| touch_to_fill_generation_controller_->ShowTouchToFill(); |
| touch_to_fill_generation_state_ = TouchToFillState::kIsShowing; |
| return; |
| } |
| if (touch_to_fill_generation_state_ == TouchToFillState::kIsShowing) { |
| return; |
| } |
| |
| if (!manual_filling_controller_) { |
| manual_filling_controller_ = |
| ManualFillingController::GetOrCreate(&GetWebContents()); |
| } |
| |
| DCHECK(manual_filling_controller_); |
| manual_filling_controller_->OnAccessoryActionAvailabilityChanged( |
| ShouldShowAction(true), |
| autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC); |
| } |
| |
| void PasswordGenerationControllerImpl::ShowManualGenerationDialog( |
| const password_manager::ContentPasswordManagerDriver* target_frame_driver, |
| const autofill::password_generation::PasswordGenerationUIData& ui_data) { |
| if (!IsActiveFrameDriver(target_frame_driver) || |
| !manual_generation_requested_) |
| return; |
| generation_element_data_ = std::make_unique<GenerationElementData>(ui_data); |
| ShowDialog(PasswordGenerationType::kManual); |
| } |
| |
| void PasswordGenerationControllerImpl::FocusedInputChanged( |
| autofill::mojom::FocusedFieldType focused_field_type, |
| base::WeakPtr<password_manager::ContentPasswordManagerDriver> driver) { |
| TRACE_EVENT0("passwords", |
| "PasswordGenerationControllerImpl::FocusedInputChanged"); |
| // It's probably a duplicate notification. |
| if (IsActiveFrameDriver(driver.get()) && |
| focused_field_type == FocusedFieldType::kFillablePasswordField) { |
| return; |
| } |
| ResetFocusState(); |
| if (focused_field_type == FocusedFieldType::kFillablePasswordField) |
| active_frame_driver_ = std::move(driver); |
| } |
| |
| void PasswordGenerationControllerImpl::OnGenerationRequested( |
| PasswordGenerationType type) { |
| if (type == PasswordGenerationType::kManual) { |
| manual_generation_requested_ = true; |
| client_->GeneratePassword(type); |
| } else { |
| ShowDialog(PasswordGenerationType::kAutomatic); |
| } |
| } |
| |
| void PasswordGenerationControllerImpl::GeneratedPasswordAccepted( |
| const std::u16string& password, |
| base::WeakPtr<password_manager::ContentPasswordManagerDriver> driver, |
| PasswordGenerationType type) { |
| if (!driver) |
| return; |
| password_manager::metrics_util::LogGenerationDialogChoice( |
| GenerationDialogChoice::kAccepted, type); |
| driver->GeneratedPasswordAccepted( |
| generation_element_data_->form_data, |
| generation_element_data_->generation_element_id, password); |
| ResetFocusState(); |
| } |
| |
| void PasswordGenerationControllerImpl::GeneratedPasswordRejected( |
| PasswordGenerationType type) { |
| ResetFocusState(); |
| password_manager::metrics_util::LogGenerationDialogChoice( |
| GenerationDialogChoice::kRejected, type); |
| } |
| |
| gfx::NativeWindow PasswordGenerationControllerImpl::top_level_native_window() { |
| return GetWebContents().GetTopLevelNativeWindow(); |
| } |
| |
| content::WebContents* PasswordGenerationControllerImpl::web_contents() { |
| return &GetWebContents(); |
| } |
| |
| autofill::FieldSignature |
| PasswordGenerationControllerImpl::get_field_signature_for_testing() { |
| return generation_element_data_->field_signature; |
| } |
| |
| autofill::FormSignature |
| PasswordGenerationControllerImpl::get_form_signature_for_testing() { |
| return generation_element_data_->form_signature; |
| } |
| |
| // static |
| void PasswordGenerationControllerImpl::CreateForWebContentsForTesting( |
| content::WebContents* web_contents, |
| password_manager::PasswordManagerClient* client, |
| base::WeakPtr<ManualFillingController> manual_filling_controller, |
| CreateDialogFactory create_dialog_factory) { |
| DCHECK(web_contents) << "Need valid WebContents to attach controller to!"; |
| DCHECK(!FromWebContents(web_contents)) << "Controller already attached!"; |
| DCHECK(manual_filling_controller); |
| |
| web_contents->SetUserData( |
| UserDataKey(), |
| base::WrapUnique(new PasswordGenerationControllerImpl( |
| web_contents, client, std::move(manual_filling_controller), |
| create_dialog_factory))); |
| } |
| |
| PasswordGenerationControllerImpl::PasswordGenerationControllerImpl( |
| content::WebContents* web_contents) |
| : content::WebContentsUserData<PasswordGenerationControllerImpl>( |
| *web_contents), |
| client_(ChromePasswordManagerClient::FromWebContents(web_contents)), |
| create_dialog_factory_( |
| base::BindRepeating(&PasswordGenerationDialogViewInterface::Create)) { |
| } |
| |
| PasswordGenerationControllerImpl::PasswordGenerationControllerImpl( |
| content::WebContents* web_contents, |
| password_manager::PasswordManagerClient* client, |
| base::WeakPtr<ManualFillingController> manual_filling_controller, |
| CreateDialogFactory create_dialog_factory) |
| : content::WebContentsUserData<PasswordGenerationControllerImpl>( |
| *web_contents), |
| client_(client), |
| manual_filling_controller_(std::move(manual_filling_controller)), |
| create_dialog_factory_(create_dialog_factory) {} |
| |
| void PasswordGenerationControllerImpl::ShowDialog(PasswordGenerationType type) { |
| if (!active_frame_driver_ || dialog_view_) { |
| return; |
| } |
| |
| // TODO(crbug.com/894756): Add a test helper that sets this up correctly. |
| if (!generation_element_data_) { |
| /* This can currently happen in integration tests that are iniated from |
| the java side. */ |
| return; |
| } |
| |
| dialog_view_ = create_dialog_factory_.Run(this); |
| |
| std::u16string password = |
| active_frame_driver_->GetPasswordGenerationHelper()->GeneratePassword( |
| GetWebContents().GetLastCommittedURL().DeprecatedGetOriginAsURL(), |
| generation_element_data_->form_signature, |
| generation_element_data_->field_signature, |
| generation_element_data_->max_password_length); |
| dialog_view_->Show(password, active_frame_driver_, type); |
| } |
| |
| bool PasswordGenerationControllerImpl::IsActiveFrameDriver( |
| const password_manager::ContentPasswordManagerDriver* driver) const { |
| if (!active_frame_driver_) |
| return false; |
| return active_frame_driver_.get() == driver; |
| } |
| |
| void PasswordGenerationControllerImpl::ResetFocusState() { |
| if (manual_filling_controller_) |
| manual_filling_controller_->OnAccessoryActionAvailabilityChanged( |
| ShouldShowAction(false), |
| autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC); |
| active_frame_driver_.reset(); |
| generation_element_data_.reset(); |
| dialog_view_.reset(); |
| manual_generation_requested_ = false; |
| } |
| |
| void PasswordGenerationControllerImpl::HideBottomSheetIfNeeded() { |
| if (touch_to_fill_generation_state_ != TouchToFillState::kNone) { |
| touch_to_fill_generation_state_ = TouchToFillState::kNone; |
| // TODO (crbug.com/1421753): Destroy the |
| // touch_to_fill_generation_controller_ when the bottom sheet is dismissed. |
| touch_to_fill_generation_controller_.reset(); |
| } |
| } |
| |
| void PasswordGenerationControllerImpl::RenderFrameDeleted( |
| content::RenderFrameHost* render_frame_host) { |
| if (active_frame_driver_ && |
| active_frame_driver_->render_frame_host() == render_frame_host) { |
| HideBottomSheetIfNeeded(); |
| } |
| } |
| |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(PasswordGenerationControllerImpl); |