blob: 7488e6875c04f185345a018bf4bef6f0b85d9320 [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/autofill/content/renderer/password_generation_agent.h"
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include "base/auto_reset.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "components/autofill/content/renderer/form_autofill_util.h"
#include "components/autofill/content/renderer/password_autofill_agent.h"
#include "components/autofill/content/renderer/password_form_conversion_utils.h"
#include "components/autofill/content/renderer/synchronous_form_cache.h"
#include "components/autofill/core/common/autofill_switches.h"
#include "components/autofill/core/common/form_data.h"
#include "components/autofill/core/common/password_form_generation_data.h"
#include "components/autofill/core/common/password_generation_util.h"
#include "components/autofill/core/common/signatures.h"
#include "content/public/renderer/render_frame.h"
#include "google_apis/gaia/gaia_urls.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/platform/web_security_origin.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_form_control_element.h"
#include "third_party/blink/public/web/web_form_element.h"
#include "third_party/blink/public/web/web_input_element.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_view.h"
#include "ui/gfx/geometry/rect.h"
using blink::WebAutofillState;
using blink::WebDocument;
using blink::WebFormControlElement;
using blink::WebFormElement;
using blink::WebInputElement;
using blink::WebLocalFrame;
using enum blink::mojom::FormControlType;
namespace autofill {
namespace {
using ::autofill::form_util::GetTextDirectionForElement;
using Logger = ::autofill::SavePasswordProgressLogger;
// Returns the renderer id of the next password field in |control_elements|
// after |new_password|. This field is likely to be the confirmation field.
// Returns a null renderer ID if there is no such field.
FieldRendererId FindConfirmationPasswordFieldId(
const std::vector<WebFormControlElement>& control_elements,
const WebFormControlElement& new_password) {
auto iter = std::ranges::find(control_elements, new_password);
if (iter == control_elements.end())
return FieldRendererId();
++iter;
for (; iter != control_elements.end(); ++iter) {
const WebInputElement input_element = iter->DynamicTo<WebInputElement>();
if (input_element &&
input_element.FormControlTypeForAutofill() == kInputPassword) {
return form_util::GetFieldRendererId(input_element);
}
}
return FieldRendererId();
}
void CopyElementValueToOtherInputElements(
const WebInputElement& element,
std::vector<WebInputElement>& elements) {
for (WebInputElement& it : elements) {
if (element != it) {
it.SetAutofillValue(element.Value());
}
it.SetAutofillState(WebAutofillState::kAutofilled);
}
}
void PreviewGeneratedValue(WebInputElement& input_element,
const blink::WebString& value) {
input_element.SetShouldRevealPassword(true);
input_element.SetSuggestedValue(value);
}
void ClearPreviewedValue(WebInputElement& input_element) {
input_element.SetShouldRevealPassword(false);
input_element.SetSuggestedValue(blink::WebString());
}
} // namespace
// During prerendering, we do not want the renderer to send messages to the
// corresponding driver. Since we use a channel associated interface, we still
// need to set up the mojo connection as before (i.e., we can't defer binding
// the interface). Instead, we enqueue our messages here as post-activation
// tasks. See post-prerendering activation steps here:
// https://wicg.github.io/nav-speculation/prerendering.html#prerendering-bcs-subsection
class PasswordGenerationAgent::DeferringPasswordGenerationDriver
: public mojom::PasswordGenerationDriver {
public:
explicit DeferringPasswordGenerationDriver(PasswordGenerationAgent* agent)
: agent_(agent) {}
~DeferringPasswordGenerationDriver() override = default;
private:
template <typename F, typename... Args>
void SendMsg(F fn, Args&&... args) {
DCHECK(!agent_->IsPrerendering());
mojom::PasswordGenerationDriver& password_generation_client =
agent_->GetPasswordGenerationDriver();
DCHECK_NE(&password_generation_client, this);
(password_generation_client.*fn)(std::forward<Args>(args)...);
}
template <typename F, typename... Args>
void DeferMsg(F fn, Args... args) {
DCHECK(agent_->IsPrerendering());
agent_->render_frame()
->GetWebFrame()
->GetDocument()
.AddPostPrerenderingActivationStep(base::BindOnce(
&DeferringPasswordGenerationDriver::SendMsg<F, Args...>,
weak_ptr_factory_.GetWeakPtr(), fn, std::forward<Args>(args)...));
}
void AutomaticGenerationAvailable(
const password_generation::PasswordGenerationUIData&
password_generation_ui_data) override {
DeferMsg(&mojom::PasswordGenerationDriver::AutomaticGenerationAvailable,
password_generation_ui_data);
}
void PresaveGeneratedPassword(const FormData& form_data,
const std::u16string& password_value) override {
DeferMsg(&mojom::PasswordGenerationDriver::PresaveGeneratedPassword,
form_data, password_value);
}
void PasswordNoLongerGenerated(const FormData& form_data) override {
DeferMsg(&mojom::PasswordGenerationDriver::PasswordNoLongerGenerated,
form_data);
}
#if !BUILDFLAG(IS_ANDROID)
void ShowPasswordEditingPopup(const gfx::RectF& bounds,
const FormData& form_data,
FieldRendererId field_renderer_id,
const std::u16string& password_value) override {
DeferMsg(&mojom::PasswordGenerationDriver::ShowPasswordEditingPopup, bounds,
form_data, field_renderer_id, password_value);
}
void PasswordGenerationRejectedByTyping() override {
DeferMsg(
&mojom::PasswordGenerationDriver::PasswordGenerationRejectedByTyping);
}
void FrameWasScrolled() override {
DeferMsg(&mojom::PasswordGenerationDriver::FrameWasScrolled);
}
void GenerationElementLostFocus() override {
DeferMsg(&mojom::PasswordGenerationDriver::GenerationElementLostFocus);
}
#endif // !BUILDFLAG(IS_ANDROID)
raw_ptr<PasswordGenerationAgent> agent_ = nullptr;
base::WeakPtrFactory<DeferringPasswordGenerationDriver> weak_ptr_factory_{
this};
};
// Contains information about generation status for an element for the
// lifetime of the possible interaction.
struct PasswordGenerationAgent::GenerationItemInfo {
GenerationItemInfo(WebInputElement generation_element,
FormData form_data,
std::vector<blink::WebInputElement> password_elements)
: generation_element_(std::move(generation_element)),
form_data_(std::move(form_data)),
password_elements_(std::move(password_elements)) {}
GenerationItemInfo(const GenerationItemInfo&) = delete;
GenerationItemInfo& operator=(const GenerationItemInfo&) = delete;
~GenerationItemInfo() = default;
// Element where we want to trigger password generation UI.
blink::WebInputElement generation_element_;
// FormData for the generation element.
FormData form_data_;
// Password elements (new password only or both new password and
// confirmation password) in the form.
std::vector<blink::WebInputElement> password_elements_;
// If the password field at |generation_element_| contains a generated
// password.
bool password_is_generated_ = false;
// True if the last password generation was manually triggered.
bool is_manually_triggered_ = false;
// True if a password was generated and the user edited it. Used for UMA
// stats.
bool password_edited_ = false;
// True if the user was editing a generated password, left that state by
// removing below `kMinimumLengthForEditedPassword` characters, but did not
// change focus or delete the password fully (in that case the field should
// remain revealed).
bool password_revealed_after_editing_ = false;
// True if the generation popup was shown during this navigation. Used to
// track UMA stats per page visit rather than per display, since the former
// is more interesting.
// TODO(crbug.com/40577440): Remove this or change the description of the
// logged event as calling AutomaticgenerationStatusChanged will no longer
// imply that a popup is shown. This could instead be logged with the
// metrics collected on the browser process.
bool generation_popup_shown_ = false;
// True if the editing popup was shown during this navigation. Used to track
// UMA stats per page rather than per display, since the former is more
// interesting.
bool editing_popup_shown_ = false;
// True when PasswordGenerationAgent updates other password fields on the page
// due to the generated password being edited. It's used to suppress the fake
// blur events coming from there.
bool updating_other_password_fields_ = false;
// True if the user explicitly rejected the generation by clicking the cancel
// button. In this case, the generation popup should not be shown again in
// this navigation unless explicitly triggered from the context menu.
bool generation_rejected_ = false;
};
PasswordGenerationAgent::PasswordGenerationAgent(
content::RenderFrame* render_frame,
PasswordAutofillAgent* password_agent,
blink::AssociatedInterfaceRegistry* registry)
: content::RenderFrameObserver(render_frame),
mark_generation_element_(
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kShowAutofillSignatures)),
password_agent_(password_agent) {
registry->AddInterface<mojom::PasswordGenerationAgent>(base::BindRepeating(
&PasswordGenerationAgent::BindPendingReceiver, base::Unretained(this)));
password_agent_->SetPasswordGenerationAgent(this);
}
PasswordGenerationAgent::~PasswordGenerationAgent() {
// Reset the pointer to `this` to avoid its dangling.
password_agent_->SetPasswordGenerationAgent(nullptr);
}
void PasswordGenerationAgent::BindPendingReceiver(
mojo::PendingAssociatedReceiver<mojom::PasswordGenerationAgent>
pending_receiver) {
receiver_.Bind(std::move(pending_receiver));
}
void PasswordGenerationAgent::DidCommitProvisionalLoad(
ui::PageTransition transition) {
// Update stats for primary main frame navigation.
if (render_frame()->GetWebFrame()->IsOutermostMainFrame()) {
if (current_generation_item_) {
if (current_generation_item_->password_edited_) {
password_generation::LogPasswordGenerationEvent(
password_generation::PASSWORD_EDITED);
}
if (current_generation_item_->generation_popup_shown_) {
password_generation::LogPasswordGenerationEvent(
password_generation::GENERATION_POPUP_SHOWN);
}
if (current_generation_item_->editing_popup_shown_) {
password_generation::LogPasswordGenerationEvent(
password_generation::EDITING_POPUP_SHOWN);
}
}
}
current_generation_item_.reset();
generation_enabled_fields_.clear();
}
void PasswordGenerationAgent::DidChangeScrollOffset() {
#if !BUILDFLAG(IS_ANDROID)
if (!current_generation_item_)
return;
GetPasswordGenerationDriver().FrameWasScrolled();
#endif // !BUILDFLAG(IS_ANDROID)
}
void PasswordGenerationAgent::OnDestruct() {
receiver_.reset();
}
void PasswordGenerationAgent::OnFieldAutofilled(
const WebInputElement& password_element) {
if (current_generation_item_ &&
current_generation_item_->password_is_generated_ &&
current_generation_item_->generation_element_ == password_element) {
password_generation::LogPasswordGenerationEvent(
password_generation::PASSWORD_DELETED_BY_AUTOFILLING);
PasswordNoLongerGenerated();
current_generation_item_->generation_element_.SetShouldRevealPassword(
false);
}
}
bool PasswordGenerationAgent::ShouldIgnoreBlur() const {
return current_generation_item_ &&
current_generation_item_->updating_other_password_fields_;
}
bool PasswordGenerationAgent::IsPrerendering() const {
return render_frame()->GetWebFrame()->GetDocument().IsPrerendering();
}
void PasswordGenerationAgent::PreviewGenerationSuggestion(
const std::u16string& password) {
CHECK(current_generation_item_);
for (auto& password_field : current_generation_item_->password_elements_) {
PreviewGeneratedValue(password_field,
blink::WebString::FromUTF16(password));
}
}
void PasswordGenerationAgent::ClearPreviewedForm() {
if (!current_generation_item_) {
return;
}
for (auto& password_field : current_generation_item_->password_elements_) {
if (password_field.SuggestedValue().IsEmpty())
continue;
ClearPreviewedValue(password_field);
}
}
void PasswordGenerationAgent::GeneratedPasswordAccepted(
const std::u16string& password) {
// Check that the navigation in between didn't reset the state.
if (!current_generation_item_) {
return;
}
CHECK(!password.empty());
CHECK_LE(kMinimumLengthForEditedPassword, password.size());
current_generation_item_->password_is_generated_ = true;
current_generation_item_->password_edited_ = false;
password_generation::LogPasswordGenerationEvent(
password_generation::PASSWORD_ACCEPTED);
LogMessage(Logger::STRING_GENERATION_RENDERER_GENERATED_PASSWORD_ACCEPTED);
// Preview needs to be cleared before filling to be removed correctly.
password_agent_->autofill_agent().ClearPreviewedForm();
for (auto& password_element : current_generation_item_->password_elements_) {
base::AutoReset<bool> auto_reset_update_confirmation_password(
&current_generation_item_->updating_other_password_fields_, true);
password_element.SetAutofillValue(blink::WebString::FromUTF16(password));
// setAutofillValue() above may have resulted in JavaScript closing the
// frame.
if (!render_frame()) {
return;
}
// crbug.com/1467893: JS can clear the generated password. In this case
// consider filling unsuccessful and don't presave the password.
if (password_element.Value().IsEmpty()) {
return;
}
password_agent_->TrackAutofilledElement(password_element);
}
CHECK(base::Contains(current_generation_item_->password_elements_,
current_generation_item_->generation_element_));
std::optional<FormData> presaved_form_data =
CreateFormDataToPresave(/*form_cache=*/{});
const std::u16string generated_password =
current_generation_item_->generation_element_.Value().Utf16();
if (presaved_form_data) {
CHECK(!generated_password.empty());
GetPasswordGenerationDriver().PresaveGeneratedPassword(*presaved_form_data,
generated_password);
}
// Call UpdateStateForTextChange after the corresponding PasswordFormManager
// is notified that the password was generated.
for (auto& password_element : current_generation_item_->password_elements_) {
// Needed to notify password_autofill_agent that the content of the field
// has changed. Without this we will overwrite the generated
// password with an Autofilled password when saving.
// https://crbug.com/493455
password_agent_->autofill_agent().UpdateStateForTextChange(
password_element, FieldPropertiesFlags::kUserTyped, /*form_cache=*/{});
}
}
void PasswordGenerationAgent::GeneratedPasswordRejected() {
if (current_generation_item_) {
current_generation_item_->generation_rejected_ = true;
}
}
void PasswordGenerationAgent::FocusNextFieldAfterPasswords() {
if (!current_generation_item_ || !render_frame()) {
return;
}
for (const WebInputElement& password_element :
current_generation_item_->password_elements_) {
if (password_element ==
password_agent_->last_queried_element().DynamicTo<WebInputElement>()) {
render_frame()->GetWebView()->AdvanceFocus(false);
}
}
}
std::optional<FormData> PasswordGenerationAgent::CreateFormDataToPresave(
const SynchronousFormCache& form_cache) {
DCHECK(current_generation_item_);
DCHECK(current_generation_item_->generation_element_);
// Since the form for presaving should match a form in the browser, create it
// with the same algorithm (to match html attributes, action, etc.).
std::unique_ptr<FormData> form_data;
WebFormElement form =
current_generation_item_->generation_element_.GetOwningFormForAutofill();
return form
? password_agent_->GetFormDataFromWebForm(form, form_cache)
: password_agent_->GetFormDataFromUnownedInputElements(form_cache);
}
void PasswordGenerationAgent::FoundFormEligibleForGeneration(
const PasswordFormGenerationData& form) {
generation_enabled_fields_[form.new_password_renderer_id] = form;
if (mark_generation_element_) {
WebFormControlElement new_password_input =
form_util::GetFormControlByRendererId(form.new_password_renderer_id);
if (new_password_input) {
// Mark the input element with renderer id
// |form.new_password_renderer_id|.
new_password_input.SetAttribute("password_creation_field", "1");
}
}
}
void PasswordGenerationAgent::TriggeredGeneratePassword(
TriggeredGeneratePasswordCallback callback) {
if (SetUpTriggeredGeneration()) {
LogMessage(Logger::STRING_GENERATION_RENDERER_SHOW_GENERATION_POPUP);
// If the field is not |type=password|, the list of suggestions
// should not be populated with passwords to avoid filling them in a
// clear-text field.
// `FormControlTypeForAutofill()` is deliberately not used.
bool is_generation_element_password_type =
current_generation_item_->generation_element_
.FormControlType() // nocheck
== kInputPassword;
password_generation::PasswordGenerationUIData password_generation_ui_data(
gfx::RectF(render_frame()->ConvertViewportToWindow(
current_generation_item_->generation_element_.BoundsInWidget())),
current_generation_item_->generation_element_.MaxLength(),
current_generation_item_->generation_element_.NameForAutofill().Utf16(),
form_util::GetFieldRendererId(
current_generation_item_->generation_element_),
is_generation_element_password_type,
GetTextDirectionForElement(
current_generation_item_->generation_element_),
current_generation_item_->form_data_,
current_generation_item_->generation_rejected_);
std::move(callback).Run(std::move(password_generation_ui_data));
current_generation_item_->generation_popup_shown_ = true;
} else {
std::move(callback).Run(std::nullopt);
}
}
bool PasswordGenerationAgent::SetUpTriggeredGeneration() {
if (!render_frame()) {
return false;
}
const WebInputElement last_focused_password_element =
password_agent_->last_queried_element().DynamicTo<WebInputElement>();
if (!last_focused_password_element ||
last_focused_password_element.IsReadOnly()) {
return false;
}
FieldRendererId last_focused_password_element_id =
form_util::GetFieldRendererId(last_focused_password_element);
bool is_automatic_generation_available = false;
auto it = generation_enabled_fields_.find(last_focused_password_element_id);
if (it != generation_enabled_fields_.end()) {
is_automatic_generation_available = true;
MaybeCreateCurrentGenerationItem(
last_focused_password_element,
it->second.confirmation_password_renderer_id, /*form_cache=*/{});
} else {
blink::WebDocument document =
render_frame() ? render_frame()->GetWebFrame()->GetDocument()
: WebDocument();
if (!document) {
return false;
}
WebFormElement form =
last_focused_password_element.GetOwningFormForAutofill();
std::vector<WebFormControlElement> control_elements =
form_util::GetOwnedAutofillableFormControls(document, form);
MaybeCreateCurrentGenerationItem(
last_focused_password_element,
FindConfirmationPasswordFieldId(control_elements,
last_focused_password_element),
/*form_cache=*/{});
}
if (!current_generation_item_)
return false;
if (current_generation_item_->generation_element_ !=
last_focused_password_element) {
return false;
}
current_generation_item_->is_manually_triggered_ =
!is_automatic_generation_available;
return true;
}
bool PasswordGenerationAgent::ShowPasswordGenerationSuggestions(
const WebInputElement& element,
const SynchronousFormCache& form_cache) {
CHECK(element);
auto it =
generation_enabled_fields_.find(form_util::GetFieldRendererId(element));
if (it != generation_enabled_fields_.end()) {
MaybeCreateCurrentGenerationItem(
element, it->second.confirmation_password_renderer_id, form_cache);
}
if (!current_generation_item_ ||
element != current_generation_item_->generation_element_) {
return false;
}
if (current_generation_item_->password_is_generated_) {
size_t password_length =
current_generation_item_->generation_element_.Value().length();
if (password_length < kMinimumLengthForEditedPassword) {
// Password is too short to be considered generated.
PasswordNoLongerGenerated();
if (password_length == 0) {
current_generation_item_->generation_element_.SetShouldRevealPassword(
false);
}
return MaybeOfferAutomaticGeneration();
}
current_generation_item_->generation_element_.SetShouldRevealPassword(true);
#if !BUILDFLAG(IS_ANDROID)
ShowEditingPopup(form_cache);
#endif // !BUILDFLAG(IS_ANDROID)
return true;
}
// Assume that if the password field has less than
// |kMaximumCharsForGenerationOffer| characters then the user is not finished
// typing their password and display the password suggestion.
if (!element.IsReadOnly() && element.IsEnabled() &&
element.Value().length() <= kMaximumCharsForGenerationOffer) {
return MaybeOfferAutomaticGeneration();
}
return false;
}
void PasswordGenerationAgent::DidEndTextFieldEditing(
const blink::WebInputElement& element) {
if (element && current_generation_item_ &&
element == current_generation_item_->generation_element_) {
#if !BUILDFLAG(IS_ANDROID)
GetPasswordGenerationDriver().GenerationElementLostFocus();
#endif // !BUILDFLAG(IS_ANDROID)
current_generation_item_->password_revealed_after_editing_ = false;
current_generation_item_->generation_element_.SetShouldRevealPassword(
false);
}
}
void PasswordGenerationAgent::TextFieldCleared(
const blink::WebInputElement& element) {
if (current_generation_item_ &&
current_generation_item_->generation_element_ == element) {
if (current_generation_item_->password_is_generated_) {
PasswordNoLongerGenerated();
}
current_generation_item_->password_revealed_after_editing_ = false;
current_generation_item_->generation_element_.SetShouldRevealPassword(
false);
}
}
bool PasswordGenerationAgent::TextDidChangeInTextField(
const WebInputElement& element,
const SynchronousFormCache& form_cache) {
if (!current_generation_item_ ||
current_generation_item_->generation_element_ != element) {
// Presave the username if it has been changed.
if (current_generation_item_ &&
current_generation_item_->password_is_generated_ && element &&
element.GetOwningFormForAutofill() ==
current_generation_item_->generation_element_
.GetOwningFormForAutofill()) {
const std::u16string generated_password =
current_generation_item_->generation_element_.Value().Utf16();
if (generated_password.empty()) {
// JS cleared the generated password in the meantime. Consider the user
// left the generation state.
PasswordNoLongerGenerated();
} else {
std::optional<FormData> presaved_form_data =
CreateFormDataToPresave(form_cache);
if (presaved_form_data) {
GetPasswordGenerationDriver().PresaveGeneratedPassword(
*presaved_form_data, generated_password);
}
}
}
return false;
}
if (!current_generation_item_->password_is_generated_) {
if (element.Value().length() == 0) {
MaybeOfferAutomaticGeneration();
} else {
// User has rejected the feature and has started typing a password.
#if !BUILDFLAG(IS_ANDROID)
GetPasswordGenerationDriver().PasswordGenerationRejectedByTyping();
#endif // !BUILDFLAG(IS_ANDROID)
// If the user is still modifying the field after leaving the editing
// state without fully clearing, it should remain revealed.
current_generation_item_->generation_element_.SetShouldRevealPassword(
current_generation_item_->password_revealed_after_editing_);
}
} else {
const bool leave_editing_state =
current_generation_item_->password_is_generated_ &&
element.Value().length() < kMinimumLengthForEditedPassword;
if (!current_generation_item_->password_is_generated_ ||
leave_editing_state) {
// The call may pop up a generation prompt, replacing the editing prompt
// if it was previously shown.
MaybeOfferAutomaticGeneration();
}
if (leave_editing_state) {
// Tell the browser that the state isn't "editing" anymore. The browser
// should hide the editing prompt if it wasn't replaced above.
current_generation_item_->password_revealed_after_editing_ = true;
PasswordNoLongerGenerated();
} else if (current_generation_item_->password_is_generated_) {
current_generation_item_->password_edited_ = true;
base::AutoReset<bool> auto_reset_update_confirmation_password(
&current_generation_item_->updating_other_password_fields_, true);
// Mirror edits to any confirmation password fields.
CopyElementValueToOtherInputElements(
element, current_generation_item_->password_elements_);
// Even though `form_cache` is available, we previously ran
// `CopyElementValueToOtherInputElements()` which triggers
// `WebFormControlElement::SetValue()`, dispatching events that might
// change the DOM. Therefore `form_cache` may be outdated.
std::optional<FormData> presaved_form_data =
CreateFormDataToPresave(/*form_cache=*/{});
std::u16string generated_password =
current_generation_item_->generation_element_.Value().Utf16();
if (presaved_form_data) {
CHECK(!generated_password.empty());
GetPasswordGenerationDriver().PresaveGeneratedPassword(
*presaved_form_data, generated_password);
}
}
// Notify `password_agent_` of text changes to the other confirmation
// password fields.
for (const auto& password_element :
current_generation_item_->password_elements_) {
// `PasswordNoLongerGenerated()` and
// `CopyElementValueToOtherInputElements()` both call
// `SetAutofillValue()`, dispatching events that might modify the DOM,
// making `form_cache` outdated, which is why it must not used in this
// call.
password_agent_->autofill_agent().UpdateStateForTextChange(
password_element, FieldPropertiesFlags::kUserTyped,
/*form_cache=*/{});
}
}
return true;
}
bool PasswordGenerationAgent::MaybeOfferAutomaticGeneration() {
// TODO(crbug.com/40580560): Add this check to the generation element class.
if (current_generation_item_->is_manually_triggered_) {
return false;
}
AutomaticGenerationAvailable();
return true;
}
void PasswordGenerationAgent::AutomaticGenerationAvailable() {
if (!render_frame())
return;
DCHECK(current_generation_item_);
DCHECK(current_generation_item_->generation_element_);
LogMessage(Logger::STRING_GENERATION_RENDERER_AUTOMATIC_GENERATION_AVAILABLE);
// If the field is not |type=password|, the list of suggestions
// should not be populated with passwordS to avoid filling them in a
// clear-text field.
// `FormControlTypeForAutofill()` is deliberately not used.
bool is_generation_element_password_type =
current_generation_item_->generation_element_
.FormControlType() // nocheck
== kInputPassword;
password_generation::PasswordGenerationUIData password_generation_ui_data(
gfx::RectF(render_frame()->ConvertViewportToWindow(
current_generation_item_->generation_element_.BoundsInWidget())),
current_generation_item_->generation_element_.MaxLength(),
current_generation_item_->generation_element_.NameForAutofill().Utf16(),
form_util::GetFieldRendererId(
current_generation_item_->generation_element_),
is_generation_element_password_type,
GetTextDirectionForElement(current_generation_item_->generation_element_),
current_generation_item_->form_data_,
current_generation_item_->generation_rejected_);
current_generation_item_->generation_popup_shown_ = true;
GetPasswordGenerationDriver().AutomaticGenerationAvailable(
password_generation_ui_data);
}
#if !BUILDFLAG(IS_ANDROID)
void PasswordGenerationAgent::ShowEditingPopup(
const SynchronousFormCache& form_cache) {
if (!render_frame())
return;
gfx::RectF bounding_box(render_frame()->ConvertViewportToWindow(
current_generation_item_->generation_element_.BoundsInWidget()));
std::optional<FormData> form_data = CreateFormDataToPresave(form_cache);
DCHECK(form_data);
FieldRendererId generation_element_renderer_id =
form_util::GetFieldRendererId(
current_generation_item_->generation_element_);
std::u16string password_value =
current_generation_item_->generation_element_.Value().Utf16();
GetPasswordGenerationDriver().ShowPasswordEditingPopup(
bounding_box, *form_data, generation_element_renderer_id, password_value);
current_generation_item_->editing_popup_shown_ = true;
}
#endif // !BUILDFLAG(IS_ANDROID)
void PasswordGenerationAgent::PasswordNoLongerGenerated() {
DCHECK(current_generation_item_);
DCHECK(current_generation_item_->password_is_generated_);
// Do not treat the password as generated, either here or in the browser.
current_generation_item_->password_is_generated_ = false;
current_generation_item_->password_edited_ = false;
for (WebInputElement& password :
current_generation_item_->password_elements_) {
password.SetAutofillState(WebAutofillState::kNotFilled);
}
password_generation::LogPasswordGenerationEvent(
password_generation::PASSWORD_DELETED);
// Clear all other password fields.
for (WebInputElement& element :
current_generation_item_->password_elements_) {
base::AutoReset<bool> auto_reset_update_confirmation_password(
&current_generation_item_->updating_other_password_fields_, true);
if (current_generation_item_->generation_element_ != element)
element.SetAutofillValue(blink::WebString());
}
// The above call to SetAutofillValue() dispatches events that may change the
// DOM. Therefore, any cached FormData may be outdated, and we must not use a
// form cache.
std::optional<FormData> presaved_form_data =
CreateFormDataToPresave(/*form_cache=*/{});
if (presaved_form_data)
GetPasswordGenerationDriver().PasswordNoLongerGenerated(
*presaved_form_data);
}
void PasswordGenerationAgent::MaybeCreateCurrentGenerationItem(
WebInputElement generation_element,
FieldRendererId confirmation_password_renderer_id,
const SynchronousFormCache& form_cache) {
// Do not create |current_generation_item_| if it already is created for
// |generation_element| or the user accepted generated password. So if the
// user accepted the generated password, generation is not offered on any
// other field.
if (current_generation_item_ &&
(current_generation_item_->generation_element_ == generation_element ||
current_generation_item_->password_is_generated_))
return;
WebFormElement form_element = generation_element.GetOwningFormForAutofill();
std::optional<FormData> form_data =
form_element
? password_agent_->GetFormDataFromWebForm(form_element, form_cache)
: password_agent_->GetFormDataFromUnownedInputElements(form_cache);
if (!form_data)
return;
std::vector<blink::WebInputElement> passwords = {generation_element};
WebFormControlElement confirmation_password =
form_util::GetFormControlByRendererId(confirmation_password_renderer_id);
if (confirmation_password) {
WebInputElement input = confirmation_password.DynamicTo<WebInputElement>();
if (input) {
passwords.push_back(input);
}
}
current_generation_item_ = std::make_unique<GenerationItemInfo>(
generation_element, std::move(*form_data), std::move(passwords));
generation_element.MaybeSetHasBeenPasswordField();
generation_element.SetAttribute("aria-autocomplete", "list");
}
mojom::PasswordManagerDriver&
PasswordGenerationAgent::GetPasswordManagerDriver() {
DCHECK(password_agent_);
return password_agent_->GetPasswordManagerDriver();
}
mojom::PasswordGenerationDriver&
PasswordGenerationAgent::GetPasswordGenerationDriver() {
if (IsPrerendering()) {
if (!deferring_password_generation_driver_) {
deferring_password_generation_driver_ =
std::make_unique<DeferringPasswordGenerationDriver>(this);
}
return *deferring_password_generation_driver_;
}
// Lazily bind this interface.
if (!password_generation_client_) {
render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(
&password_generation_client_);
}
return *password_generation_client_;
}
void PasswordGenerationAgent::LogMessage(Logger::StringID message_id) {
if (!password_agent_->logging_state_active())
return;
RendererSavePasswordProgressLogger logger(&GetPasswordManagerDriver());
logger.LogMessage(message_id);
}
void PasswordGenerationAgent::LogBoolean(Logger::StringID message_id,
bool truth_value) {
if (!password_agent_->logging_state_active())
return;
RendererSavePasswordProgressLogger logger(&GetPasswordManagerDriver());
logger.LogBoolean(message_id, truth_value);
}
} // namespace autofill