| // 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_autofill_agent.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <iterator> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/check.h" |
| #include "base/check_deref.h" |
| #include "base/containers/contains.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/i18n/case_conversion.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/no_destructor.h" |
| #include "base/notreached.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/to_string.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/types/zip.h" |
| #include "build/build_config.h" |
| #include "components/autofill/content/common/mojom/autofill_driver.mojom.h" |
| #include "components/autofill/content/renderer/form_autofill_util.h" |
| #include "components/autofill/content/renderer/password_form_conversion_utils.h" |
| #include "components/autofill/content/renderer/password_generation_agent.h" |
| #include "components/autofill/content/renderer/prefilled_values_detector.h" |
| #include "components/autofill/content/renderer/renderer_save_password_progress_logger.h" |
| #include "components/autofill/content/renderer/suggestion_properties.h" |
| #include "components/autofill/content/renderer/synchronous_form_cache.h" |
| #include "components/autofill/core/common/aliases.h" |
| #include "components/autofill/core/common/autofill_constants.h" |
| #include "components/autofill/core/common/autofill_debug_features.h" |
| #include "components/autofill/core/common/autofill_features.h" |
| #include "components/autofill/core/common/autofill_regexes.h" |
| #include "components/autofill/core/common/autofill_util.h" |
| #include "components/autofill/core/common/form_data.h" |
| #include "components/autofill/core/common/form_field_data.h" |
| #include "components/autofill/core/common/mojom/autofill_types.mojom-shared.h" |
| #include "components/autofill/core/common/mojom/autofill_types.mojom.h" |
| #include "components/autofill/core/common/password_form_fill_data.h" |
| #include "components/autofill/core/common/signatures.h" |
| #include "components/autofill/core/common/unique_ids.h" |
| #include "components/password_manager/core/common/password_manager_constants.h" |
| #include "components/password_manager/core/common/password_manager_features.h" |
| #include "components/password_manager/core/common/password_manager_util.h" |
| #include "components/safe_browsing/buildflags.h" |
| #include "components/signin/public/base/signin_buildflags.h" |
| #include "content/public/renderer/render_frame.h" |
| #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| #include "services/service_manager/public/cpp/interface_provider.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/common/input/web_input_event.h" |
| #include "third_party/blink/public/platform/web_security_origin.h" |
| #include "third_party/blink/public/web/web_autofill_client.h" |
| #include "third_party/blink/public/web/web_document.h" |
| #include "third_party/blink/public/web/web_element.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_node.h" |
| #include "third_party/blink/public/web/web_view.h" |
| #include "ui/base/page_transition_types.h" |
| #include "ui/events/keycodes/keyboard_codes.h" |
| #include "url/gurl.h" |
| |
| using blink::WebAutofillState; |
| using blink::WebDocument; |
| using blink::WebDocumentLoader; |
| using blink::WebElement; |
| using blink::WebElementCollection; |
| using blink::WebFormControlElement; |
| using blink::WebFormElement; |
| using blink::WebFrame; |
| using blink::WebInputElement; |
| using blink::WebLocalFrame; |
| using blink::WebNode; |
| using blink::WebSecurityOrigin; |
| using blink::WebString; |
| using blink::WebURL; |
| using blink::WebView; |
| |
| using password_manager::util::IsRendererRecognizedCredentialForm; |
| |
| namespace autofill { |
| |
| namespace { |
| |
| using form_util::GetFieldRendererId; |
| using form_util::GetFormByRendererId; |
| using form_util::GetFormControlByRendererId; |
| using form_util::GetFormRendererId; |
| using form_util::IsElementEditable; |
| |
| using mojom::SubmissionIndicatorEvent; |
| using mojom::SubmissionSource; |
| |
| constexpr auto kInputPassword = blink::mojom::FormControlType::kInputPassword; |
| |
| // The size above which we stop triggering autocomplete. |
| const size_t kMaximumTextSizeForAutocomplete = 1000; |
| |
| constexpr char kSubmissionSourceHistogram[] = |
| "Autofill.SubmissionDetectionSource.PasswordAutofillAgent"; |
| |
| // Names of HTML attributes to show form and field signatures for debugging. |
| const char kDebugAttributeForFormSignature[] = "form_signature"; |
| const char kDebugAttributeForAlternativeFormSignature[] = |
| "alternative_form_signature"; |
| const char kDebugAttributeForFieldSignature[] = "field_signature"; |
| const char kDebugAttributeForParserAnnotations[] = "pm_parser_annotation"; |
| const char kDebugAttributeForVisibility[] = "visibility_annotation"; |
| // Name of HTML attribute that stores the copy of autofill tooltip for |
| // debugging. |
| constexpr char kDebugAttributeForAutofill[] = "autofill-information"; |
| |
| // HTML attribute that is used as a tooltip if |
| // `kAutofillShowTypePredictions` is on. |
| constexpr char kHtmlAttributeForAutofillTooltip[] = "title"; |
| |
| // Maps element names to the actual elements to simplify form filling. |
| typedef std::map<std::u16string, WebInputElement> FormInputElementMap; |
| |
| // Use the shorter name when referencing SavePasswordProgressLogger::StringID |
| // values to spare line breaks. The code provides enough context for that |
| // already. |
| typedef SavePasswordProgressLogger Logger; |
| |
| typedef std::vector<FormInputElementMap> FormElementsList; |
| |
| bool DoUsernamesMatch(const std::u16string& potential_suggestion, |
| const std::u16string& current_username, |
| bool exact_match) { |
| if (potential_suggestion == current_username) |
| return true; |
| return !exact_match && IsPrefixOfEmailEndingWithAtSign(current_username, |
| potential_suggestion); |
| } |
| |
| // Returns whether the `username_element` is allowed to be autofilled. |
| // |
| // Note that if the user interacts with the `password_field` and the |
| // `username_element` is user-defined (i.e., non-empty and non-autofilled), then |
| // this function returns false. This is a precaution, to not override the field |
| // if it has been classified as username by accident. |
| bool IsUsernameAmendable(const WebInputElement& username_element, |
| bool is_password_field_selected) { |
| return username_element && IsElementEditable(username_element) && |
| (!is_password_field_selected || username_element.IsAutofilled() || |
| username_element.IsPreviewed() || username_element.Value().IsEmpty()); |
| } |
| |
| // Log `message` if `logger` is not null. |
| void LogMessage(Logger* logger, Logger::StringID message) { |
| if (logger) |
| logger->LogMessage(message); |
| } |
| |
| // Log `message` and `value` if `logger` is not null. |
| void LogBoolean(Logger* logger, Logger::StringID message, bool value) { |
| if (logger) |
| logger->LogBoolean(message, value); |
| } |
| |
| // Log a message including the name, method and action of |form|. |
| void LogHTMLForm(Logger* logger, |
| Logger::StringID message_id, |
| const WebFormElement& form) { |
| if (logger) { |
| logger->LogHTMLForm(message_id, form.GetName().Utf8(), |
| GURL(form.Action().Utf8())); |
| } |
| } |
| |
| // Returns true if there are any suggestions to be derived from `fill_data`. |
| // Only considers suggestions with usernames having `typed_username` as prefix. |
| bool CanShowUsernameSuggestion(const PasswordFormFillData& fill_data, |
| const std::u16string& typed_username) { |
| std::u16string typed_username_lower = base::i18n::ToLower(typed_username); |
| if (base::StartsWith( |
| base::i18n::ToLower(fill_data.preferred_login.username_value), |
| typed_username_lower, base::CompareCase::SENSITIVE)) { |
| return true; |
| } |
| |
| for (const auto& login : fill_data.additional_logins) { |
| if (base::StartsWith(base::i18n::ToLower(login.username_value), |
| typed_username_lower, base::CompareCase::SENSITIVE)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| // This function attempts to find the matching credentials for the |
| // `current_username` by scanning `fill_data`. The result is written in |
| // `username` and `password` parameters. |
| void FindMatchesByUsername(const PasswordFormFillData& fill_data, |
| const std::u16string& current_username, |
| bool exact_username_match, |
| RendererSavePasswordProgressLogger* logger, |
| std::u16string* username, |
| std::u16string* password) { |
| // Look for any suitable matches to current field text. |
| if (DoUsernamesMatch(fill_data.preferred_login.username_value, |
| current_username, exact_username_match)) { |
| *username = fill_data.preferred_login.username_value; |
| *password = fill_data.preferred_login.password_value; |
| LogMessage(logger, Logger::STRING_USERNAMES_MATCH); |
| } else { |
| // Scan additional logins for a match. |
| for (const auto& it : fill_data.additional_logins) { |
| if (!it.realm.empty()) { |
| // Non-empty realm means PSL match. Do not autofill PSL matched |
| // credentials. The reason for this is that PSL matched sites are |
| // different sites, so a password for a PSL matched site should be never |
| // filled without explicit user selection. |
| continue; |
| } |
| if (DoUsernamesMatch(it.username_value, current_username, |
| exact_username_match)) { |
| *username = it.username_value; |
| *password = it.password_value; |
| break; |
| } |
| } |
| LogBoolean(logger, Logger::STRING_MATCH_IN_ADDITIONAL, |
| !(username->empty() && password->empty())); |
| } |
| } |
| |
| // TODO(crbug.com/40447274): This duplicates code from |
| // components/password_manager/core/browser/password_store/psl_matching_helper.h. |
| // The logic using this code should ultimately end up in |
| // components/password_manager/core/browser, at which point it can use the |
| // original code directly. |
| std::string GetRegistryControlledDomain(const GURL& signon_realm) { |
| return net::registry_controlled_domains::GetDomainAndRegistry( |
| signon_realm, |
| net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); |
| } |
| |
| // TODO(crbug.com/40447274): This duplicates code from |
| // components/password_manager/core/browser/password_store/psl_matching_helper.h. |
| // The logic using this code should ultimately end up in |
| // components/password_manager/core/browser, at which point it can use the |
| // original code directly. |
| bool IsPublicSuffixDomainMatch(const std::string& url1, |
| const std::string& url2) { |
| GURL gurl1(url1); |
| GURL gurl2(url2); |
| |
| if (!gurl1.is_valid() || !gurl2.is_valid()) |
| return false; |
| |
| if (gurl1 == gurl2) |
| return true; |
| |
| std::string domain1(GetRegistryControlledDomain(gurl1)); |
| std::string domain2(GetRegistryControlledDomain(gurl2)); |
| |
| if (domain1.empty() || domain2.empty()) |
| return false; |
| |
| return gurl1.GetScheme() == gurl2.GetScheme() && domain1 == domain2 && |
| gurl1.GetPort() == gurl2.GetPort(); |
| } |
| |
| // Helper function that calculates form signature for `form_data` and returns it |
| // as a string. |
| std::string GetFormSignatureAsString(const FormData& form_data) { |
| return base::NumberToString(CalculateFormSignature(form_data).value()); |
| } |
| // Similar to `GetFormSignatureAsString` but returns alternative form signature |
| // as a string. |
| std::string GetAlternativeFormSignatureAsString(const FormData& form_data) { |
| return base::NumberToString( |
| CalculateAlternativeFormSignature(form_data).value()); |
| } |
| |
| // Sets the specified attribute of `target` to the given value. This must not |
| // happen while ScriptForbiddenScope is active (e.g. during |
| // blink::FrameLoader::FinishedParsing(), see crbug.com/1219852). Therefore, |
| // this function should be called asynchronously via SetAttributeAsync. |
| void SetAttributeInternal(WebElement target, |
| const std::string& attribute_utf8, |
| const std::string& value_utf8) { |
| target.SetAttribute(WebString::FromUTF8(attribute_utf8), |
| WebString::FromUTF8(value_utf8)); |
| } |
| |
| // Posts an async task to call SetAttributeInternal. |
| void SetAttributeAsync(WebElement target, |
| const std::string& attribute_utf8, |
| const std::string& value_utf8) { |
| if (!target) { |
| return; |
| } |
| target.GetDocument() |
| .GetFrame() |
| ->GetTaskRunner(blink::TaskType::kInternalDefault) |
| ->PostTask(FROM_HERE, base::BindOnce(&SetAttributeInternal, target, |
| attribute_utf8, value_utf8)); |
| } |
| |
| // Annotate `fields` with field signatures, form signature and visibility state |
| // as HTML attributes. |
| void AnnotateFieldsWithSignatures( |
| base::span<const WebFormControlElement> fields, |
| const std::string& form_signature, |
| const std::string& alternative_form_signature) { |
| for (const WebFormControlElement& control_element : fields) { |
| std::optional<autofill::FormControlType> type = |
| form_util::GetAutofillFormControlType(control_element); |
| if (!type) { |
| continue; |
| } |
| FieldSignature field_signature = CalculateFieldSignatureByNameAndType( |
| control_element.NameForAutofill().Utf16(), *type); |
| SetAttributeAsync(control_element, kDebugAttributeForFieldSignature, |
| base::NumberToString(field_signature.value())); |
| SetAttributeAsync(control_element, kDebugAttributeForFormSignature, |
| form_signature); |
| SetAttributeAsync(control_element, |
| kDebugAttributeForAlternativeFormSignature, |
| alternative_form_signature); |
| SetAttributeAsync(control_element, kDebugAttributeForVisibility, |
| base::ToString(control_element.IsFocusable())); |
| } |
| } |
| |
| // Returns true iff there is a password field in `frame`. |
| // We don't have to iterate through the whole DOM to find password fields. |
| // Instead, we can iterate through the fields of the forms and the unowned |
| // fields, both of which are cached in the Document. |
| bool HasPasswordField(const WebLocalFrame& frame) { |
| auto ContainsPasswordField = [&](const auto& fields) { |
| return base::Contains(fields, blink::mojom::FormControlType::kInputPassword, |
| &WebFormControlElement::FormControlTypeForAutofill); |
| }; |
| |
| WebDocument doc = frame.GetDocument(); |
| return std::ranges::any_of(doc.GetTopLevelForms(), ContainsPasswordField, |
| &WebFormElement::GetFormControlElements) || |
| ContainsPasswordField(doc.UnassociatedFormControls()); |
| } |
| |
| // Returns the closest visible autocompletable non-password text element |
| // preceding the `password_element` either in a form, if it belongs to one, or |
| // in the `frame`. |
| WebInputElement FindUsernameElementPrecedingPasswordElement( |
| WebLocalFrame* frame, |
| const WebInputElement& password_element) { |
| DCHECK(password_element); |
| |
| std::vector<WebFormControlElement> elements = |
| form_util::GetOwnedAutofillableFormControls( |
| frame->GetDocument(), password_element.GetOwningFormForAutofill()); |
| |
| auto iter = std::ranges::find(elements, password_element); |
| if (iter == elements.end()) |
| return WebInputElement(); |
| |
| for (auto begin = elements.begin(); iter != begin;) { |
| --iter; |
| const WebInputElement input = iter->DynamicTo<WebInputElement>(); |
| if (input && input.IsTextField() && |
| input.FormControlTypeForAutofill() != kInputPassword && |
| IsElementEditable(input) && input.IsFocusable()) { |
| return input; |
| } |
| } |
| |
| return WebInputElement(); |
| } |
| |
| // Returns true if `element`'s frame origin is not PSL matched with the origin |
| // of any parent frame. |
| bool IsInCrossOriginIframeOrEmbeddedFrame(const WebInputElement& element) { |
| WebFrame* cur_frame = element.GetDocument().GetFrame(); |
| WebString bottom_frame_origin = cur_frame->GetSecurityOrigin().ToString(); |
| |
| DCHECK(cur_frame); |
| |
| while (cur_frame->Parent()) { |
| cur_frame = cur_frame->Parent(); |
| if (!IsPublicSuffixDomainMatch( |
| bottom_frame_origin.Utf8(), |
| cur_frame->GetSecurityOrigin().ToString().Utf8())) { |
| return true; |
| } |
| } |
| // In MPArch, if we haven't reached the primary main frame, it means |
| // we are in a nested frame tree. Fenced Frames are always considered |
| // cross origin so we should return true here. Adding NOTREACHED for now |
| // for future nested inner frame trees. |
| if (!cur_frame->IsOutermostMainFrame()) { |
| if (element.GetDocument().GetFrame()->IsInFencedFrameTree()) { |
| return true; |
| } else { |
| NOTREACHED(); |
| } |
| } |
| return false; |
| } |
| |
| void AnnotateFieldWithParsingResult( |
| FieldRendererId renderer_id, |
| const std::string& password_managers_annotation) { |
| if (renderer_id.is_null()) |
| return; |
| auto element = GetFormControlByRendererId(renderer_id); |
| if (!element) { |
| return; |
| } |
| // Calling SetAttribute synchronously here is safe because |
| // AnnotateFieldWithParsingResult is triggered via a call from the the |
| // browser. This means that we should not be in a ScriptForbiddenScope. |
| element.SetAttribute( |
| WebString::FromASCII(kDebugAttributeForParserAnnotations), |
| WebString::FromASCII(password_managers_annotation)); |
| |
| if (!base::FeatureList::IsEnabled( |
| features::debug::kAutofillShowTypePredictions)) { |
| return; |
| } |
| |
| if (!element.HasAttribute(kDebugAttributeForAutofill)) { |
| // No autofill tooltip yet, don't fill anything. |
| return; |
| } |
| |
| std::string autofill_tooltip = |
| element.GetAttribute(kDebugAttributeForAutofill).Utf8(); |
| |
| element.SetAttribute( |
| kHtmlAttributeForAutofillTooltip, |
| WebString::FromUTF8( |
| base::StrCat({element.GetAttribute(kDebugAttributeForAutofill).Utf8(), |
| "\n", kDebugAttributeForParserAnnotations, ": ", |
| password_managers_annotation}))); |
| } |
| |
| bool HasDocumentWithValidFrame(const WebInputElement& element) { |
| WebFrame* frame = element.GetDocument().GetFrame(); |
| return frame && frame->View(); |
| } |
| |
| // This method tries to fix `fields` with empty typed or filled properties by |
| // matching them against previously filled or typed in fields with the same |
| // value and copying their filled or typed mask. |
| // |
| // This helps against websites where submitted fields differ from fields that |
| // had previously been autofilled or typed into. |
| [[nodiscard]] std::vector<FormFieldData> FillNonTypedOrFilledPropertiesMasks( |
| std::vector<FormFieldData> fields, |
| const FieldDataManager& manager) { |
| static constexpr FieldPropertiesMask kFilledOrTyped = |
| FieldPropertiesFlags::kAutofilled | FieldPropertiesFlags::kUserTyped; |
| |
| for (auto& field : fields) { |
| if (field.properties_mask() & kFilledOrTyped) { |
| continue; |
| } |
| |
| for (const auto& [field_id, field_data] : manager.field_data_map()) { |
| const std::optional<std::u16string>& value = field_data.first; |
| FieldPropertiesMask properties = field_data.second; |
| if ((properties & kFilledOrTyped) && value == field.value()) { |
| field.set_properties_mask(field.properties_mask() | |
| (properties & kFilledOrTyped)); |
| break; |
| } |
| } |
| } |
| return fields; |
| } |
| |
| size_t GetIndexOfElement(const FormData& form_data, |
| const WebInputElement& element) { |
| if (!element) { |
| return form_data.fields().size(); |
| } |
| for (size_t i = 0; i < form_data.fields().size(); ++i) { |
| if (form_data.fields()[i].renderer_id() == |
| form_util::GetFieldRendererId(element)) { |
| return i; |
| } |
| } |
| return form_data.fields().size(); |
| } |
| |
| bool HasTextInputs(const FormData& form_data) { |
| return std::ranges::any_of(form_data.fields(), |
| &FormFieldData::IsTextInputElement); |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| bool IsWebAuthnForm(base::optional_ref<const FormData> form_data) { |
| auto has_webauthn_attribute = [](const FormFieldData& field) { |
| return field.parsed_autocomplete() && field.parsed_autocomplete()->webauthn; |
| }; |
| return form_data && |
| std::ranges::any_of(form_data->fields(), has_webauthn_attribute); |
| } |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| FieldPropertiesFlags GetFieldFlags(AutofillSuggestionTriggerSource source) { |
| return source == AutofillSuggestionTriggerSource::kManualFallbackPasswords |
| ? FieldPropertiesFlags:: |
| kAutofilledPasswordFormFilledViaManualFallback |
| : FieldPropertiesFlags::kAutofilledOnUserTrigger; |
| } |
| |
| } // 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 PasswordAutofillAgent::DeferringPasswordManagerDriver |
| : public mojom::PasswordManagerDriver { |
| public: |
| explicit DeferringPasswordManagerDriver(PasswordAutofillAgent* agent) |
| : agent_(agent) {} |
| ~DeferringPasswordManagerDriver() override = default; |
| |
| private: |
| template <typename F, typename... Args> |
| void SendMsg(F fn, Args&&... args) { |
| DCHECK(!agent_->IsPrerendering()); |
| mojom::PasswordManagerDriver& password_manager_driver = |
| agent_->GetPasswordManagerDriver(); |
| DCHECK_NE(&password_manager_driver, this); |
| (password_manager_driver.*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( |
| &DeferringPasswordManagerDriver::SendMsg<F, Args...>, |
| weak_ptr_factory_.GetWeakPtr(), fn, std::forward<Args>(args)...)); |
| } |
| void PasswordFormsParsed(const std::vector<FormData>& forms_data) override { |
| DeferMsg(&mojom::PasswordManagerDriver::PasswordFormsParsed, forms_data); |
| } |
| void PasswordFormsRendered( |
| const std::vector<FormData>& visible_forms_data) override { |
| DeferMsg(&mojom::PasswordManagerDriver::PasswordFormsRendered, |
| visible_forms_data); |
| } |
| void PasswordFormSubmitted(const FormData& form_data) override { |
| DeferMsg(&mojom::PasswordManagerDriver::PasswordFormSubmitted, form_data); |
| } |
| void InformAboutUserInput(const FormData& form_data) override { |
| DeferMsg(&mojom::PasswordManagerDriver::InformAboutUserInput, form_data); |
| } |
| void DynamicFormSubmission( |
| mojom::SubmissionIndicatorEvent submission_indication_event) override { |
| DeferMsg(&mojom::PasswordManagerDriver::DynamicFormSubmission, |
| submission_indication_event); |
| } |
| void PasswordFormCleared(const FormData& form_data) override { |
| DeferMsg(&mojom::PasswordManagerDriver::PasswordFormCleared, form_data); |
| } |
| void RecordSavePasswordProgress(const std::string& log) override { |
| DeferMsg(&mojom::PasswordManagerDriver::RecordSavePasswordProgress, log); |
| } |
| void UserModifiedPasswordField() override { |
| DeferMsg(&mojom::PasswordManagerDriver::UserModifiedPasswordField); |
| } |
| void UserModifiedNonPasswordField(FieldRendererId renderer_id, |
| const std::u16string& value, |
| bool autocomplete_attribute_has_username, |
| bool is_likely_otp) override { |
| DeferMsg(&mojom::PasswordManagerDriver::UserModifiedNonPasswordField, |
| renderer_id, value, autocomplete_attribute_has_username, |
| is_likely_otp); |
| } |
| void ShowPasswordSuggestions( |
| const PasswordSuggestionRequest& request) override { |
| DeferMsg(&mojom::PasswordManagerDriver::ShowPasswordSuggestions, request); |
| } |
| void CheckSafeBrowsingReputation(const GURL& form_action, |
| const GURL& frame_url) override { |
| DeferMsg(&mojom::PasswordManagerDriver::CheckSafeBrowsingReputation, |
| form_action, frame_url); |
| } |
| void FocusedInputChanged( |
| FieldRendererId focused_field_id, |
| mojom::FocusedFieldType focused_field_type) override { |
| DeferMsg(&mojom::PasswordManagerDriver::FocusedInputChanged, |
| focused_field_id, focused_field_type); |
| } |
| void LogFirstFillingResult(FormRendererId form_renderer_id, |
| int32_t result) override { |
| DeferMsg(&mojom::PasswordManagerDriver::LogFirstFillingResult, |
| form_renderer_id, result); |
| } |
| |
| raw_ptr<PasswordAutofillAgent> agent_ = nullptr; |
| base::WeakPtrFactory<DeferringPasswordManagerDriver> weak_ptr_factory_{this}; |
| }; |
| |
| PasswordAutofillAgent::FocusStateNotifier::FocusStateNotifier( |
| PasswordAutofillAgent* agent) |
| : agent_(CHECK_DEREF(agent)) {} |
| |
| PasswordAutofillAgent::FocusStateNotifier::~FocusStateNotifier() = default; |
| |
| void PasswordAutofillAgent::FocusStateNotifier::FocusedElementChanged( |
| const WebElement& element) { |
| auto field_info = GetFocusedFieldInfo(element); |
| NotifyIfChanged(field_info.first, field_info.second); |
| } |
| |
| std::pair<mojom::FocusedFieldType, FieldRendererId> |
| PasswordAutofillAgent::FocusStateNotifier::GetFocusedFieldInfo( |
| const WebElement& element) { |
| mojom::FocusedFieldType new_focused_field_type = |
| mojom::FocusedFieldType::kUnknown; |
| FieldRendererId new_focused_field_id = FieldRendererId(); |
| if (auto form_control_element = element.DynamicTo<WebFormControlElement>()) { |
| new_focused_field_type = GetFieldType(form_control_element); |
| new_focused_field_id = form_util::GetFieldRendererId(form_control_element); |
| } |
| return {new_focused_field_type, new_focused_field_id}; |
| } |
| |
| mojom::FocusedFieldType PasswordAutofillAgent::FocusStateNotifier::GetFieldType( |
| const WebFormControlElement& node) { |
| auto form_control_type = node.FormControlTypeForAutofill(); |
| if (form_control_type == blink::mojom::FormControlType::kTextArea) { |
| return mojom::FocusedFieldType::kFillableTextArea; |
| } |
| |
| WebInputElement input_element = node.DynamicTo<WebInputElement>(); |
| if (!input_element || !input_element.IsTextField() || |
| !form_util::IsElementEditable(input_element)) { |
| return mojom::FocusedFieldType::kUnfillableElement; |
| } |
| |
| if (form_control_type == blink::mojom::FormControlType::kInputSearch) { |
| return mojom::FocusedFieldType::kFillableSearchField; |
| } |
| if (form_control_type == blink::mojom::FormControlType::kInputPassword) { |
| return mojom::FocusedFieldType::kFillablePasswordField; |
| } |
| if (agent_->IsUsernameInputField(input_element)) { |
| return mojom::FocusedFieldType::kFillableUsernameField; |
| } |
| if (form_util::IsWebauthnTaggedElement(node)) { |
| return mojom::FocusedFieldType::kFillableWebauthnTaggedField; |
| } |
| return mojom::FocusedFieldType::kFillableNonSearchField; |
| } |
| |
| void PasswordAutofillAgent::FocusStateNotifier::NotifyIfChanged( |
| mojom::FocusedFieldType new_focused_field_type, |
| FieldRendererId new_focused_field_id) { |
| // Forward the request if the focused field is different from the previous |
| // one. |
| if (focused_field_id_ == new_focused_field_id && |
| focused_field_type_ == new_focused_field_type) { |
| return; |
| } |
| |
| agent_->GetPasswordManagerDriver().FocusedInputChanged( |
| new_focused_field_id, new_focused_field_type); |
| |
| focused_field_type_ = new_focused_field_type; |
| focused_field_id_ = new_focused_field_id; |
| } |
| |
| PasswordAutofillAgent::PasswordAutofillAgent( |
| content::RenderFrame* render_frame, |
| blink::AssociatedInterfaceRegistry* registry) |
| : content::RenderFrameObserver(render_frame) { |
| registry->AddInterface<mojom::PasswordAutofillAgent>(base::BindRepeating( |
| &PasswordAutofillAgent::BindPendingReceiver, base::Unretained(this))); |
| } |
| |
| PasswordAutofillAgent::~PasswordAutofillAgent() = default; |
| |
| void PasswordAutofillAgent::Init(AutofillAgent* autofill_agent) { |
| autofill_agent_ = autofill_agent; |
| } |
| |
| void PasswordAutofillAgent::BindPendingReceiver( |
| mojo::PendingAssociatedReceiver<mojom::PasswordAutofillAgent> |
| pending_receiver) { |
| receiver_.Bind(std::move(pending_receiver)); |
| } |
| |
| void PasswordAutofillAgent::SetPasswordGenerationAgent( |
| PasswordGenerationAgent* generation_agent) { |
| password_generation_agent_ = generation_agent; |
| } |
| |
| PasswordAutofillAgent::FormStructureInfo::FormStructureInfo() = default; |
| |
| PasswordAutofillAgent::FormStructureInfo::FormStructureInfo( |
| const FormStructureInfo& other) = default; |
| |
| PasswordAutofillAgent::FormStructureInfo& |
| PasswordAutofillAgent::FormStructureInfo::operator=( |
| const PasswordAutofillAgent::FormStructureInfo& other) = default; |
| |
| PasswordAutofillAgent::FormStructureInfo::FormStructureInfo( |
| FormStructureInfo&& other) = default; |
| |
| PasswordAutofillAgent::FormStructureInfo& |
| PasswordAutofillAgent::FormStructureInfo::operator=( |
| PasswordAutofillAgent::FormStructureInfo&& other) = default; |
| |
| PasswordAutofillAgent::FormStructureInfo::~FormStructureInfo() = default; |
| |
| PasswordAutofillAgent::PasswordValueGatekeeper::PasswordValueGatekeeper() |
| : was_user_gesture_seen_(false) {} |
| |
| PasswordAutofillAgent::PasswordValueGatekeeper::~PasswordValueGatekeeper() = |
| default; |
| |
| void PasswordAutofillAgent::PasswordValueGatekeeper::RegisterElement( |
| WebInputElement element) { |
| CHECK(element); |
| if (was_user_gesture_seen_) { |
| ShowValue(element); |
| } else { |
| elements_.emplace_back(element); |
| } |
| } |
| |
| void PasswordAutofillAgent::PasswordValueGatekeeper::OnUserGesture() { |
| if (was_user_gesture_seen_) { |
| return; |
| } |
| was_user_gesture_seen_ = true; |
| for (FieldRef element : elements_) { |
| if (WebInputElement input_element = |
| element.GetField().DynamicTo<WebInputElement>()) { |
| ShowValue(input_element); |
| } |
| } |
| elements_.clear(); |
| } |
| |
| void PasswordAutofillAgent::PasswordValueGatekeeper::Reset() { |
| was_user_gesture_seen_ = false; |
| elements_.clear(); |
| } |
| |
| void PasswordAutofillAgent::PasswordValueGatekeeper::ShowValue( |
| WebInputElement element) { |
| if (element && !element.SuggestedValue().IsEmpty()) { |
| element.SetAutofillValue(element.SuggestedValue()); |
| } |
| } |
| |
| std::optional<PasswordSuggestionRequest> |
| PasswordAutofillAgent::CreateRequestForChangeInTextField( |
| const WebInputElement& element, |
| const SynchronousFormCache& form_cache) { |
| CHECK(element); |
| return CreateRequestForDomain( |
| element, AutofillSuggestionTriggerSource::kTextFieldValueChanged, |
| form_cache); |
| } |
| |
| // LINT.IfChange |
| |
| void PasswordAutofillAgent::NotifyPasswordManagerAboutUserFieldModification( |
| const WebInputElement& element, |
| FieldModificationType modification_type) { |
| if (element.FormControlTypeForAutofill() == kInputPassword) { |
| auto iter = password_to_username_.find(FieldRef(element)); |
| if ((iter != password_to_username_.end()) && |
| (modification_type == FieldModificationType::kManualTyping)) { |
| // Note that the suggested value of `mutable_element` was reset when its |
| // value changed. |
| // TODO(crbug.com/41132785): Do this through const WebInputElement. |
| WebInputElement mutable_element = element; // We need a non-const. |
| mutable_element.SetAutofillState(WebAutofillState::kNotFilled); |
| } |
| GetPasswordManagerDriver().UserModifiedPasswordField(); |
| return; |
| } |
| |
| const std::u16string element_value = element.Value().Utf16(); |
| static base::NoDestructor<WebString> kAutocomplete("autocomplete"); |
| std::string autocomplete_attribute = |
| element.GetAttribute(*kAutocomplete).Utf8(); |
| static base::NoDestructor<WebString> kName("name"); |
| std::u16string name_attribute = element.GetAttribute(*kName).Utf16(); |
| std::u16string id_attribute = element.GetIdAttribute().Utf16(); |
| static base::NoDestructor<WebString> kLabel("label"); |
| std::u16string label_attribute = element.GetAttribute(*kLabel).Utf16(); |
| std::optional<mojom::FormControlType> type_attribute = |
| form_util::GetAutofillFormControlType(element); |
| |
| if (!password_manager::util::CanFieldBeConsideredAsSingleUsername( |
| name_attribute, id_attribute, label_attribute, type_attribute) || |
| !password_manager::util::CanValueBeConsideredAsSingleUsername( |
| element_value)) { |
| return; |
| } |
| |
| bool is_likely_otp = password_manager::util::IsLikelyOtp( |
| name_attribute, id_attribute, autocomplete_attribute); |
| |
| GetPasswordManagerDriver().UserModifiedNonPasswordField( |
| GetFieldRendererId(element), element_value, |
| base::Contains(autocomplete_attribute, |
| password_manager::constants::kAutocompleteUsername), |
| is_likely_otp); |
| } |
| |
| void PasswordAutofillAgent::UpdatePasswordStateForTextChange( |
| const WebInputElement& element, |
| const SynchronousFormCache& form_cache) { |
| NotifyPasswordManagerAboutUserFieldModification( |
| element, FieldModificationType::kManualTyping); |
| |
| InformBrowserAboutUserInput(element.GetOwningFormForAutofill(), element, |
| form_cache); |
| } |
| |
| // LINT.ThenChange(//components/password_manager/core/browser/password_manager.cc:update_password_state_for_text_change) |
| |
| void PasswordAutofillAgent::TrackAutofilledElement( |
| const WebFormControlElement& element) { |
| autofill_agent_->TrackAutofilledElement(element); |
| } |
| |
| void PasswordAutofillAgent::FillPasswordSuggestion( |
| const std::u16string& username, |
| const std::u16string& password, |
| base::OnceCallback<void(bool)> callback) { |
| auto focused_element = last_queried_element().DynamicTo<WebInputElement>(); |
| if (!focused_element || !IsElementEditable(focused_element)) { |
| std::move(callback).Run(/*success=*/false); |
| return; |
| } |
| WebInputElement username_element; |
| WebInputElement password_element; |
| PasswordInfo* password_info = nullptr; |
| if (!HasElementsToFill(focused_element, UseFallbackData(true), |
| &username_element, &password_element, |
| &password_info)) { |
| std::move(callback).Run(/*success=*/false); |
| return; |
| } |
| if (focused_element.FormControlTypeForAutofill() == kInputPassword) { |
| CHECK(password_element); |
| password_info->password_field_suggestion_was_accepted = true; |
| password_info->password_field = FieldRef(password_element); |
| } |
| bool success = FillUsernameAndPasswordElements( |
| username_element, password_element, username, password, |
| AutofillSuggestionTriggerSource::kFormControlElementClicked); |
| std::move(callback).Run(success); |
| } |
| |
| void PasswordAutofillAgent::FillPasswordSuggestionById( |
| FieldRendererId username_element_id, |
| FieldRendererId password_element_id, |
| const std::u16string& username, |
| const std::u16string& password, |
| AutofillSuggestionTriggerSource suggestion_source) { |
| if (!last_queried_element()) { |
| return; |
| } |
| FillUsernameAndPasswordElements( |
| GetFormControlByRendererId(username_element_id) |
| .DynamicTo<WebInputElement>(), |
| GetFormControlByRendererId(password_element_id) |
| .DynamicTo<WebInputElement>(), |
| username, password, suggestion_source); |
| } |
| |
| bool PasswordAutofillAgent::FillUsernameAndPasswordElements( |
| blink::WebInputElement username_element, |
| blink::WebInputElement password_element, |
| const std::u16string& username, |
| const std::u16string& password, |
| AutofillSuggestionTriggerSource suggestion_source) { |
| ClearPreviewedForm(); |
| WebFormControlElement focused_element = last_queried_element(); |
| // TODO(crbug.com/341995827): Remove dependency on `focused_element`. Username |
| // filling condition could be made similar to the password one and selection |
| // range setting could be skipped. |
| CHECK(focused_element); |
| // Call OnFieldAutofilled before WebInputElement::SetAutofillState which may |
| // cause frame closing. |
| if (password_element && password_generation_agent_) { |
| password_generation_agent_->OnFieldAutofilled(password_element); |
| } |
| bool is_password_field_focused = password_element == focused_element; |
| if (IsUsernameAmendable(username_element, is_password_field_focused) && |
| !(username.empty() && is_password_field_focused) && |
| username_element.Value().Utf16() != username) { |
| DoFillField(username_element, username, GetFieldFlags(suggestion_source)); |
| } |
| if (password_element && IsElementEditable(password_element)) { |
| FillPasswordFieldAndSave(password_element, password, suggestion_source); |
| // TODO(crbug.com/40223173): As Touch-To-Fill and auto-submission don't |
| // currently support filling single username fields, the code below is |
| // within `password_element`. Support such fields too and move the |
| // code out the condition. |
| // If the `username_element` is visible/focusable and the `password_element` |
| // is not, trigger submission on the former as the latter unlikely has an |
| // Enter listener. |
| if (username_element && username_element.IsFocusable() && |
| !password_element.IsFocusable()) { |
| field_renderer_id_to_submit_ = GetFieldRendererId(username_element); |
| } else { |
| field_renderer_id_to_submit_ = GetFieldRendererId(password_element); |
| } |
| } |
| auto length = base::checked_cast<unsigned>(focused_element.Value().length()); |
| focused_element.SetSelectionRange(length, length); |
| |
| // Returns whether the fields were filled with the requested values |
| // successfully. |
| return (!username_element || username_element.Value().Utf16() == username) && |
| (!password_element || password_element.Value().Utf16() == password); |
| } |
| |
| void PasswordAutofillAgent::FillIntoFocusedField( |
| bool is_password, |
| const std::u16string& credential) { |
| auto focused_input = last_queried_element().DynamicTo<WebInputElement>(); |
| if (!focused_input || focused_input.IsReadOnly()) { |
| return; |
| } |
| if (!is_password) { |
| DoFillField(focused_input, credential, |
| FieldPropertiesFlags::kAutofilledOnUserTrigger); |
| } |
| if (focused_input.FormControlTypeForAutofill() != kInputPassword) { |
| return; |
| } |
| FillPasswordFieldAndSave(focused_input, credential, |
| AutofillSuggestionTriggerSource::kUnspecified); |
| } |
| |
| void PasswordAutofillAgent::PreviewField(FieldRendererId field_id, |
| const std::u16string& value) { |
| WebInputElement input = form_util::GetFormControlByRendererId(field_id) |
| .DynamicTo<WebInputElement>(); |
| if (!input || !IsElementEditable(input)) { |
| return; |
| } |
| DoPreviewField( |
| input, value, |
| /*is_password=*/input.FormControlTypeForAutofill() == kInputPassword); |
| } |
| |
| void PasswordAutofillAgent::FillField( |
| FieldRendererId field_id, |
| const std::u16string& value, |
| FieldPropertiesMask field_properties, |
| base::OnceCallback<void(bool)> success_callback) { |
| WebFormControlElement form_control = |
| form_util::GetFormControlByRendererId(field_id); |
| WebInputElement input_element = form_control.DynamicTo<WebInputElement>(); |
| if (!input_element || input_element.IsReadOnly()) { |
| std::move(success_callback).Run(false); |
| // Early return for non-input fields such as textarea. |
| return; |
| } |
| DoFillField(input_element, value, field_properties); |
| if (base::FeatureList::IsEnabled( |
| password_manager::features::kActorLoginTreatFillingAsUserInput)) { |
| InformBrowserAboutUserInput(input_element.GetOwningFormForAutofill(), |
| input_element, /*form_cache=*/{}); |
| } |
| std::move(success_callback).Run(true); |
| } |
| |
| void PasswordAutofillAgent::FillChangePasswordForm( |
| FieldRendererId password_element_id, |
| FieldRendererId new_password_element_id, |
| FieldRendererId confirm_password_element_id, |
| const std::u16string& old_password, |
| const std::u16string& new_password, |
| FillChangePasswordFormCallback callback) { |
| WebInputElement last_element; |
| |
| auto filling_tasks = { |
| std::make_pair(password_element_id, old_password), |
| std::make_pair(new_password_element_id, new_password), |
| std::make_pair(confirm_password_element_id, new_password)}; |
| for (const auto& task : filling_tasks) { |
| WebFormControlElement form_control = |
| form_util::GetFormControlByRendererId(task.first); |
| WebInputElement input_element = form_control.DynamicTo<WebInputElement>(); |
| if (!input_element) { |
| continue; |
| } |
| |
| DoFillField(input_element, task.second, |
| FieldPropertiesFlags::kAutofilledChangePasswordFormOnPageLoad); |
| last_element = input_element; |
| } |
| |
| if (!last_element) { |
| std::move(callback).Run(std::nullopt); |
| return; |
| } |
| |
| const WebFormElement& form = last_element.GetOwningFormForAutofill(); |
| std::optional<FormData> form_data = |
| form ? GetFormDataFromWebForm(form, /*form_cache=*/{}) |
| : GetFormDataFromUnownedInputElements(/*form_cache=*/{}); |
| if (!form_data) { |
| std::move(callback).Run(std::nullopt); |
| return; |
| } |
| |
| std::move(callback).Run(*form_data); |
| } |
| |
| void PasswordAutofillAgent::DoPreviewField(WebInputElement input, |
| const std::u16string& credential, |
| bool is_password) { |
| CHECK(input); |
| previewed_elements_.emplace_back(PreviewInfo{ |
| .field_id = form_util::GetFieldRendererId(input), |
| .autofill_state = input.GetAutofillState(), |
| .is_password = is_password, |
| }); |
| input.SetSuggestedValue(WebString::FromUTF16(credential)); |
| } |
| |
| void PasswordAutofillAgent::DoFillField(WebInputElement input, |
| const std::u16string& credential, |
| FieldPropertiesMask field_properties) { |
| CHECK(input); |
| input.SetAutofillValue(WebString::FromUTF16(credential)); |
| field_data_manager().UpdateFieldDataMap(form_util::GetFieldRendererId(input), |
| credential, field_properties); |
| |
| if ((field_properties & kAutofilledOnUserTrigger) || |
| (field_properties & kAutofilledPasswordFormFilledViaManualFallback)) { |
| // Notify password manager when the user is modifying field values by |
| // manually filling the form. |
| NotifyPasswordManagerAboutUserFieldModification( |
| input, FieldModificationType::kFillingOnUserTrigger); |
| } |
| TrackAutofilledElement(input); |
| } |
| |
| void PasswordAutofillAgent::FillPasswordFieldAndSave( |
| WebInputElement password_input, |
| const std::u16string& credential, |
| AutofillSuggestionTriggerSource suggestion_source) { |
| CHECK(password_input.FormControlTypeForAutofill() == kInputPassword); |
| DoFillField(password_input, credential, GetFieldFlags(suggestion_source)); |
| InformBrowserAboutUserInput(password_input.GetOwningFormForAutofill(), |
| password_input, /*form_cache=*/{}); |
| } |
| |
| void PasswordAutofillAgent::PreviewSuggestion( |
| const WebFormControlElement& control_element, |
| const std::u16string& username, |
| const std::u16string& password) { |
| // The element in context of the suggestion popup. |
| const WebInputElement element = control_element.DynamicTo<WebInputElement>(); |
| if (!element || !IsElementEditable(element)) { |
| return; |
| } |
| WebInputElement username_element; |
| WebInputElement password_element; |
| PasswordInfo* password_info; |
| if (!HasElementsToFill(element, UseFallbackData(true), &username_element, |
| &password_element, &password_info)) { |
| return; |
| } |
| PreviewUsernameAndPasswordElements(username_element, password_element, |
| username, password); |
| } |
| |
| void PasswordAutofillAgent::PreviewPasswordSuggestionById( |
| FieldRendererId username_element_id, |
| FieldRendererId password_element_id, |
| const std::u16string& username, |
| const std::u16string& password) { |
| if (!last_queried_element()) { |
| return; |
| } |
| PreviewUsernameAndPasswordElements( |
| GetFormControlByRendererId(username_element_id) |
| .DynamicTo<WebInputElement>(), |
| GetFormControlByRendererId(password_element_id) |
| .DynamicTo<WebInputElement>(), |
| username, password); |
| } |
| |
| void PasswordAutofillAgent::PreviewUsernameAndPasswordElements( |
| blink::WebInputElement username_element, |
| blink::WebInputElement password_element, |
| const std::u16string& username, |
| const std::u16string& password) { |
| WebFormControlElement focused_element = last_queried_element(); |
| // TODO(crbug.com/341995827): Remove dependency on `focused_element` when |
| // similar dependency is removed from |
| // `PasswordAutofillAgent::FillUsernameAndPasswordElements`. |
| CHECK(focused_element); |
| if (IsUsernameAmendable(username_element, |
| password_element == focused_element)) { |
| DoPreviewField(username_element, username, /*is_password=*/false); |
| } |
| if (password_element && IsElementEditable(password_element)) { |
| DoPreviewField(password_element, password, /*is_password=*/true); |
| } |
| } |
| |
| void PasswordAutofillAgent::ClearPreviewedForm() { |
| for (const PreviewInfo& preview_info : previewed_elements_) { |
| WebInputElement element = |
| form_util::GetFormControlByRendererId(preview_info.field_id) |
| .DynamicTo<WebInputElement>(); |
| if (!element) { |
| continue; |
| } |
| element.SetSuggestedValue(WebString()); |
| element.SetAutofillState(preview_info.autofill_state); |
| } |
| previewed_elements_.clear(); |
| } |
| |
| std::optional<PasswordSuggestionRequest> |
| PasswordAutofillAgent::CreateSuggestionRequest( |
| const std::u16string& typed_username, |
| const WebInputElement& user_input, |
| AutofillSuggestionTriggerSource trigger_source, |
| const SynchronousFormCache& form_cache) { |
| base::UmaHistogramEnumeration("PasswordManager.SuggestionPopupTriggerSource", |
| trigger_source); |
| // TODO(crbug.com/408843433): Don't extract the data here but pass it in from |
| // the caller who needs it anyways for autofill requests. |
| std::optional<std::pair<FormData, raw_ref<const FormFieldData>>> |
| form_and_field = form_util::FindFormAndFieldForFormControlElement( |
| user_input, field_data_manager(), |
| autofill_agent_->GetCallTimerState( |
| CallTimerState::CallSite::kShowSuggestionPopup), |
| autofill_agent_->button_titles_cache(), form_cache); |
| if (!form_and_field) { |
| return std::nullopt; |
| } |
| |
| // TODO(crbug.com/408843433): Don't find this data again. Pass it from caller. |
| WebInputElement username_element; |
| WebInputElement password_element; |
| PasswordInfo* password_info = nullptr; |
| |
| // If false, neither info nor fields were found. Continue for fallback data. |
| FindPasswordInfoForElement(user_input, UseFallbackData(false), |
| &username_element, &password_element, |
| &password_info); |
| |
| // These could be form.field.size() when the request is for fallback data. |
| const size_t username_field_index = |
| GetIndexOfElement(form_and_field->first, username_element); |
| const size_t password_field_index = |
| GetIndexOfElement(form_and_field->first, password_element); |
| |
| return PasswordSuggestionRequest( |
| TriggeringField(*form_and_field->second, trigger_source, typed_username, |
| gfx::RectF(render_frame()->ConvertViewportToWindow( |
| user_input.BoundsInWidget()))), |
| std::move(form_and_field->first), username_field_index, |
| password_field_index); |
| } |
| |
| bool PasswordAutofillAgent::FindPasswordInfoForElement( |
| const WebInputElement& element, |
| UseFallbackData use_fallback_data, |
| WebInputElement* username_element, |
| WebInputElement* password_element, |
| PasswordInfo** password_info) { |
| DCHECK(username_element && password_element && password_info); |
| username_element->Reset(); |
| password_element->Reset(); |
| if (!element) { |
| return false; |
| } |
| if (suggestion_banned_fields_.contains(GetFieldRendererId(element))) { |
| // No suggestion for `element` since there is a reliable signal that |
| // `element` is a non-credential field. |
| return false; |
| } |
| if (element.FormControlTypeForAutofill() != kInputPassword) { |
| *username_element = element; |
| } else { |
| *password_element = element; |
| |
| // If there is a password field, but a request to the store hasn't been sent |
| // yet, then do fetch saved credentials now. |
| if (!sent_request_to_store_) { |
| SendPasswordForms(/*only_visible=*/false, /*form_cache=*/{}); |
| return false; |
| } |
| |
| auto iter = web_input_to_password_info_.find(FieldRef(element)); |
| if (iter == web_input_to_password_info_.end()) { |
| auto password_iter = password_to_username_.find(FieldRef(element)); |
| if (password_iter == password_to_username_.end()) { |
| if (!use_fallback_data || web_input_to_password_info_.empty()) { |
| return false; |
| } |
| iter = last_supplied_password_info_iter_; |
| } else { |
| *username_element = |
| password_iter->second.GetField().DynamicTo<WebInputElement>(); |
| } |
| } |
| |
| if (iter != web_input_to_password_info_.end()) { |
| // It's a password field without corresponding username field. Try to find |
| // the username field based on visibility. |
| *username_element = FindUsernameElementPrecedingPasswordElement( |
| render_frame()->GetWebFrame(), *password_element); |
| *password_info = &iter->second; |
| return true; |
| } |
| } |
| |
| if (username_element == nullptr || username_element->IsNull()) { |
| return false; |
| } |
| auto iter = web_input_to_password_info_.find(FieldRef(*username_element)); |
| if (iter == web_input_to_password_info_.end()) { |
| return false; |
| } |
| *password_info = &iter->second; |
| if (password_element->IsNull()) { |
| if (WebInputElement password_input = (*password_info) |
| ->password_field.GetField() |
| .DynamicTo<WebInputElement>()) { |
| *password_element = password_input; |
| } |
| } |
| return true; |
| } |
| |
| bool PasswordAutofillAgent::IsUsernameOrPasswordFillable( |
| const WebInputElement& username_element, |
| const WebInputElement& password_element, |
| PasswordInfo* password_info) { |
| if (username_element && IsElementEditable(username_element) && |
| !password_element) { |
| return true; |
| } |
| if (password_element && IsElementEditable(password_element)) { |
| return true; |
| } |
| // Password field might be disabled before entering valid username. |
| // For such cases, check that the password field is under <form> tag. |
| // Otherwise, username and password element might not be related. |
| return username_element && IsElementEditable(username_element) && |
| (password_info != nullptr && |
| !password_info->fill_data.form_renderer_id.is_null()); |
| } |
| |
| bool PasswordAutofillAgent::HasElementsToFill( |
| const WebInputElement& trigger_element, |
| UseFallbackData use_fallback_data, |
| WebInputElement* username_element, |
| WebInputElement* password_element, |
| PasswordInfo** password_info) { |
| if (!FindPasswordInfoForElement(trigger_element, use_fallback_data, |
| username_element, password_element, |
| password_info)) { |
| return false; |
| } |
| return IsUsernameOrPasswordFillable(*username_element, *password_element, |
| *password_info); |
| } |
| |
| void PasswordAutofillAgent::MaybeCheckSafeBrowsingReputation( |
| const WebInputElement& element) { |
| // Enabled on desktop and Android |
| #if BUILDFLAG(FULL_SAFE_BROWSING) || BUILDFLAG(SAFE_BROWSING_DB_REMOTE) |
| // Note: A site may use a Password field to collect a CVV or a Credit Card |
| // number, but showing a slightly misleading warning here is better than |
| // showing no warning at all. |
| if (element.FormControlTypeForAutofill() != kInputPassword) { |
| return; |
| } |
| if (checked_safe_browsing_reputation_) |
| return; |
| |
| checked_safe_browsing_reputation_ = true; |
| WebLocalFrame* frame = render_frame()->GetWebFrame(); |
| GURL frame_url = GURL(frame->GetDocument().Url()); |
| WebFormElement form_element = element.GetOwningFormForAutofill(); |
| GURL action_url = form_element |
| ? form_util::GetCanonicalActionForForm(form_element) |
| : GURL(); |
| GetPasswordManagerDriver().CheckSafeBrowsingReputation(action_url, frame_url); |
| #endif |
| } |
| |
| void PasswordAutofillAgent::ShowSuggestions( |
| const PasswordSuggestionRequest& password_request) { |
| GetPasswordManagerDriver().ShowPasswordSuggestions(password_request); |
| } |
| |
| bool PasswordAutofillAgent::FrameCanAccessPasswordManager() { |
| // about:blank or about:srcdoc frames should not be allowed to use password |
| // manager. See https://crbug.com/756587. |
| WebLocalFrame* frame = render_frame()->GetWebFrame(); |
| WebURL url = frame->GetDocument().Url(); |
| if (!url.ProtocolIs(url::kHttpScheme) && !url.ProtocolIs(url::kHttpsScheme)) |
| return false; |
| return frame->GetSecurityOrigin().CanAccessPasswordManager(); |
| } |
| |
| void PasswordAutofillAgent::OnDynamicFormsSeen( |
| const SynchronousFormCache& form_cache) { |
| SendPasswordForms(/*only_visible=*/false, form_cache); |
| } |
| |
| void PasswordAutofillAgent::UserGestureObserved() { |
| autofilled_elements_cache_.clear(); |
| |
| gatekeeper_.OnUserGesture(); |
| } |
| |
| void PasswordAutofillAgent::AnnotateFormsAndFieldsWithSignatures( |
| std::vector<WebFormElement>& forms, |
| const SynchronousFormCache& form_cache) { |
| if (!render_frame()) { |
| return; |
| } |
| WebDocument document = render_frame()->GetWebFrame()->GetDocument(); |
| for (const WebFormElement& form : forms) { |
| std::optional<FormData> form_data = |
| GetFormDataFromWebForm(form, form_cache); |
| std::string form_signature; |
| std::string alternative_form_signature; |
| if (form_data) { |
| // GetAlternativeFormSignatureAsString() require the FormData::url. |
| form_data->set_url(document.Url()); |
| form_signature = GetFormSignatureAsString(*form_data); |
| alternative_form_signature = |
| GetAlternativeFormSignatureAsString(*form_data); |
| SetAttributeAsync(form, kDebugAttributeForFormSignature, form_signature); |
| SetAttributeAsync(form, kDebugAttributeForAlternativeFormSignature, |
| alternative_form_signature); |
| } |
| AnnotateFieldsWithSignatures( |
| form_util::GetOwnedAutofillableFormControls(document, form), |
| form_signature, alternative_form_signature); |
| } |
| |
| std::optional<FormData> form_data = |
| GetFormDataFromUnownedInputElements(form_cache); |
| std::string form_signature; |
| std::string alternative_form_signature; |
| if (form_data) { |
| // GetFormSignatureAsString() may require the FormData::url. |
| form_data->set_url(render_frame()->GetWebFrame()->GetDocument().Url()); |
| form_signature = GetFormSignatureAsString(*form_data); |
| alternative_form_signature = |
| GetAlternativeFormSignatureAsString(*form_data); |
| } |
| AnnotateFieldsWithSignatures( |
| form_util::GetOwnedAutofillableFormControls(document, WebFormElement()), |
| form_signature, alternative_form_signature); |
| } |
| |
| void PasswordAutofillAgent::SendPasswordForms( |
| bool only_visible, |
| const SynchronousFormCache& form_cache) { |
| std::unique_ptr<RendererSavePasswordProgressLogger> logger; |
| if (logging_state_active_) { |
| logger = std::make_unique<RendererSavePasswordProgressLogger>( |
| &GetPasswordManagerDriver()); |
| logger->LogMessage(Logger::STRING_SEND_PASSWORD_FORMS_METHOD); |
| logger->LogBoolean(Logger::STRING_ONLY_VISIBLE, only_visible); |
| } |
| |
| WebLocalFrame* frame = render_frame()->GetWebFrame(); |
| |
| // Make sure that this security origin is allowed to use password manager. |
| WebDocument doc = frame->GetDocument(); |
| WebSecurityOrigin origin = doc.GetSecurityOrigin(); |
| if (logger) { |
| logger->LogURL(Logger::STRING_SECURITY_ORIGIN, |
| GURL(origin.ToString().Utf8())); |
| } |
| if (!FrameCanAccessPasswordManager()) { |
| LogMessage(logger.get(), Logger::STRING_SECURITY_ORIGIN_FAILURE); |
| return; |
| } |
| |
| // Checks whether the webpage is a redirect page or an empty page. |
| if (form_util::IsWebpageEmpty(frame)) { |
| LogMessage(logger.get(), Logger::STRING_WEBPAGE_EMPTY); |
| return; |
| } |
| |
| std::vector<WebFormElement> forms = doc.GetTopLevelForms(); |
| |
| if (IsShowAutofillSignaturesEnabled()) |
| AnnotateFormsAndFieldsWithSignatures(forms, form_cache); |
| if (logger) |
| logger->LogNumber(Logger::STRING_NUMBER_OF_ALL_FORMS, forms.size()); |
| |
| size_t num_fields_seen = 0; |
| std::vector<FormData> password_forms_data; |
| for (const WebFormElement& form_element : forms) { |
| if (only_visible) { |
| bool is_form_visible = |
| std::ranges::any_of(form_element.GetFormControlElements(), // nocheck |
| &WebElement::IsFocusable); |
| LogHTMLForm(logger.get(), Logger::STRING_FORM_FOUND_ON_PAGE, |
| form_element); |
| LogBoolean(logger.get(), Logger::STRING_FORM_IS_VISIBLE, is_form_visible); |
| |
| // If requested, ignore non-rendered forms, e.g., those styled with |
| // display:none. |
| if (!is_form_visible) |
| continue; |
| } |
| |
| std::optional<FormData> form_data( |
| GetFormDataFromWebForm(form_element, form_cache)); |
| if (!form_data || !IsRendererRecognizedCredentialForm(*form_data)) { |
| continue; |
| } |
| if (num_fields_seen + form_data->fields().size() > kMaxExtractableFields) { |
| break; |
| } |
| num_fields_seen += form_data->fields().size(); |
| |
| FormStructureInfo form_structure_info = |
| ExtractFormStructureInfo(*form_data); |
| if (only_visible || WasFormStructureChanged(form_structure_info)) { |
| forms_structure_cache_[form_structure_info.renderer_id] = |
| std::move(form_structure_info); |
| |
| password_forms_data.push_back(std::move(*form_data)); |
| continue; |
| } |
| |
| std::vector<WebFormControlElement> control_elements = |
| form_element.GetFormControlElements(); // nocheck |
| // Sometimes JS can change autofilled forms. In this case we try to restore |
| // values for the changed elements. |
| TryFixAutofilledForm(control_elements); |
| } |
| |
| // See if there are any unassociated input elements that could be used for |
| // password submission. |
| // TODO(crbug.com/41422255): Consider using TryFixAutofilledForm for the cases |
| // when there is no form tag. |
| bool add_unowned_inputs = true; |
| if (only_visible) { |
| std::vector<WebFormControlElement> control_elements = |
| form_util::GetOwnedAutofillableFormControls(doc, WebFormElement()); |
| add_unowned_inputs = |
| std::ranges::any_of(control_elements, &WebElement::IsFocusable); |
| LogBoolean(logger.get(), Logger::STRING_UNOWNED_INPUTS_VISIBLE, |
| add_unowned_inputs); |
| } |
| |
| if (add_unowned_inputs) { |
| std::optional<FormData> form_data( |
| GetFormDataFromUnownedInputElements(form_cache)); |
| if (form_data && IsRendererRecognizedCredentialForm(*form_data) && |
| num_fields_seen + form_data->fields().size() <= kMaxExtractableFields) { |
| password_forms_data.push_back(std::move(*form_data)); |
| } |
| } |
| |
| if (only_visible) { |
| // Send the PasswordFormsRendered message regardless of whether |
| // `password_forms_data` is empty. The empty `password_forms_data` are a |
| // possible signal to the browser that a pending login attempt succeeded. |
| GetPasswordManagerDriver().PasswordFormsRendered(password_forms_data); |
| } else { |
| // If there is a password field, but the list of password forms is empty for |
| // some reason, add a dummy form to the list. It will cause a request to the |
| // store. Therefore, saved passwords will be available for filling on click. |
| if (!sent_request_to_store_ && password_forms_data.empty() && |
| HasPasswordField(*frame)) { |
| // Set everything that `FormDigest` needs. |
| password_forms_data.emplace_back(); |
| } |
| if (!password_forms_data.empty()) { |
| sent_request_to_store_ = true; |
| GetPasswordManagerDriver().PasswordFormsParsed(password_forms_data); |
| } |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| // Provide warnings about the accessibility of password forms on the page. |
| if (!password_forms_data.empty() && |
| (doc.Url().ProtocolIs(url::kHttpScheme) || |
| doc.Url().ProtocolIs(url::kHttpsScheme))) { |
| page_passwords_analyser_.AnalyseDocumentDOM(frame); |
| } |
| #endif |
| } |
| |
| void PasswordAutofillAgent::DispatchedDOMContentLoadedEvent( |
| const SynchronousFormCache& form_cache) { |
| SendPasswordForms(/*only_visible=*/false, form_cache); |
| } |
| |
| void PasswordAutofillAgent::DidFinishLoad() { |
| // The `frame` contents have been rendered. Let the PasswordManager know |
| // which of the loaded frames are actually visible to the user. This also |
| // triggers the "Save password?" infobar if the user just submitted a password |
| // form. |
| SendPasswordForms(/*only_visible=*/true, /*form_cache=*/{}); |
| } |
| |
| void PasswordAutofillAgent::DidCommitProvisionalLoad( |
| ui::PageTransition transition) { |
| checked_safe_browsing_reputation_ = false; |
| recorded_first_filling_result_ = false; |
| } |
| |
| void PasswordAutofillAgent::OnDestruct() { |
| receiver_.reset(); |
| } |
| |
| bool PasswordAutofillAgent::IsPrerendering() const { |
| return render_frame()->GetWebFrame()->GetDocument().IsPrerendering(); |
| } |
| |
| bool PasswordAutofillAgent::IsUsernameInputField( |
| const WebInputElement& input_element) const { |
| return input_element && |
| input_element.FormControlTypeForAutofill() != kInputPassword && |
| base::Contains(web_input_to_password_info_, FieldRef(input_element)); |
| } |
| |
| void PasswordAutofillAgent::ReadyToCommitNavigation( |
| WebDocumentLoader* document_loader) { |
| std::unique_ptr<RendererSavePasswordProgressLogger> logger; |
| if (logging_state_active_) { |
| logger = std::make_unique<RendererSavePasswordProgressLogger>( |
| &GetPasswordManagerDriver()); |
| logger->LogMessage(Logger::STRING_DID_START_PROVISIONAL_LOAD_METHOD); |
| } |
| |
| WebLocalFrame* navigated_frame = render_frame()->GetWebFrame(); |
| if (navigated_frame->IsOutermostMainFrame()) { |
| // This is a new navigation, so require a new user gesture before filling in |
| // passwords. |
| gatekeeper_.Reset(); |
| } else { |
| LogMessage(logger.get(), Logger::STRING_FRAME_NOT_MAIN_FRAME); |
| } |
| |
| CleanupOnDocumentShutdown(); |
| } |
| |
| // mojom::PasswordAutofillAgent: |
| void PasswordAutofillAgent::ApplyFillDataOnParsingCompletion( |
| const PasswordFormFillData& form_data) { |
| std::unique_ptr<RendererSavePasswordProgressLogger> logger; |
| if (logging_state_active_) { |
| logger = std::make_unique<RendererSavePasswordProgressLogger>( |
| &GetPasswordManagerDriver()); |
| logger->LogMessage(Logger::STRING_ON_FILL_PASSWORD_FORM_METHOD); |
| } |
| suggestion_banned_fields_ = form_data.suggestion_banned_fields; |
| |
| bool username_password_fields_not_set = |
| form_data.username_element_renderer_id.is_null() && |
| form_data.password_element_renderer_id.is_null(); |
| if (username_password_fields_not_set) { |
| // No fields for filling were found during parsing, which means filling |
| // fallback case. So save data for fallback filling. |
| MaybeStoreFallbackData(form_data); |
| return; |
| } |
| |
| WebInputElement username_element, password_element; |
| std::tie(username_element, password_element) = |
| FindUsernamePasswordElements(form_data); |
| bool is_single_username_fill = |
| form_data.password_element_renderer_id.is_null(); |
| WebElement main_element = |
| is_single_username_fill ? username_element : password_element; |
| if (!main_element) { |
| MaybeStoreFallbackData(form_data); |
| // TODO(crbug.com/40626063): Fix logging for single username. |
| LogFirstFillingResult(form_data, FillingResult::kNoPasswordElement); |
| return; |
| } |
| |
| times_received_fill_data_[form_data.form_renderer_id]++; |
| StoreDataForFillOnAccountSelect(form_data, username_element, |
| password_element); |
| |
| // If wait_for_username is true, we don't want to initially fill the form |
| // until the user types in a valid username. |
| if (form_data.wait_for_username) { |
| LogFirstFillingResult(form_data, FillingResult::kWaitForUsername); |
| MaybeTriggerSuggestionsOnFocusedElement(username_element, password_element); |
| return; |
| } |
| FillCredentialsAutomatically(username_element, password_element, form_data, |
| logger.get(), |
| form_data.notify_browser_of_successful_filling); |
| } |
| |
| void PasswordAutofillAgent::SetLoggingState(bool active) { |
| logging_state_active_ = active; |
| } |
| |
| void PasswordAutofillAgent::AnnotateFieldsWithParsingResult( |
| const ParsingResult& parsing_result) { |
| AnnotateFieldWithParsingResult(parsing_result.username_renderer_id, |
| "username_element"); |
| AnnotateFieldWithParsingResult(parsing_result.password_renderer_id, |
| "password_element"); |
| AnnotateFieldWithParsingResult(parsing_result.new_password_renderer_id, |
| "new_password_element"); |
| AnnotateFieldWithParsingResult(parsing_result.confirm_password_renderer_id, |
| "confirmation_password_element"); |
| } |
| |
| void PasswordAutofillAgent::InformNoSavedCredentials( |
| bool should_show_popup_without_passwords) { |
| autofilled_elements_cache_.clear(); |
| |
| #if BUILDFLAG(ENABLE_DICE_SUPPORT) |
| should_show_popup_without_passwords_ = should_show_popup_without_passwords; |
| #endif |
| |
| // Clear the actual field values. |
| std::vector<WebFormControlElement> elements; |
| elements.reserve(all_autofilled_elements_.size()); |
| for (FieldRendererId id : all_autofilled_elements_) { |
| elements.push_back(form_util::GetFormControlByRendererId(id)); |
| } |
| |
| for (WebFormControlElement element : elements) { |
| if (!element) { |
| continue; |
| } |
| // Don't clear the actual value of fields that the user has edited manually |
| // (which changes the autofill state back to kNotFilled). |
| if (element.IsAutofilled()) { |
| element.SetValue(WebString()); |
| } |
| element.SetSuggestedValue(WebString()); |
| } |
| all_autofilled_elements_.clear(); |
| |
| field_data_manager().ClearData(); |
| } |
| |
| void PasswordAutofillAgent::CheckViewAreaVisible( |
| FieldRendererId field_id, |
| CheckViewAreaVisibleCallback callback) { |
| WebFormControlElement element = |
| form_util::GetFormControlByRendererId(field_id); |
| if (!element) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| element.ScrollIntoViewIfNeeded(); |
| |
| std::move(callback).Run(!element.VisibleBoundsInWidget().IsEmpty()); |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| void PasswordAutofillAgent::TriggerFormSubmission() { |
| // Find the last interacted element to simulate an enter keystroke at. |
| WebFormControlElement form_control = |
| GetFormControlByRendererId(field_renderer_id_to_submit_); |
| if (!form_control) { |
| // The target field doesn't exist anymore. Don't try to submit it. |
| return; |
| } |
| |
| // `form_control` can only be `WebInputElement`, not `WebSelectElement`. |
| WebInputElement input = form_control.To<WebInputElement>(); |
| input.DispatchSimulatedEnter(); |
| field_renderer_id_to_submit_ = FieldRendererId(); |
| } |
| #endif |
| |
| std::optional<FormData> PasswordAutofillAgent::GetFormDataFromWebForm( |
| const WebFormElement& web_form, |
| const SynchronousFormCache& form_cache) { |
| return CreateFormDataFromWebForm( |
| web_form, field_data_manager(), &username_detector_cache_, |
| autofill_agent_->button_titles_cache(), |
| autofill_agent_->GetCallTimerState( |
| CallTimerState::CallSite::kGetFormDataFromWebForm), |
| form_cache); |
| } |
| |
| std::optional<FormData> |
| PasswordAutofillAgent::GetFormDataFromUnownedInputElements( |
| const SynchronousFormCache& form_cache) { |
| // The element's frame might have been detached in the meantime (see |
| // http://crbug.com/585363, comments 5 and 6), in which case `frame` will |
| // be null. This was hardly caused by form submission (unless the user is |
| // supernaturally quick), so it is OK to drop the ball here. |
| content::RenderFrame* frame = render_frame(); |
| if (!frame) |
| return std::nullopt; |
| WebLocalFrame* web_frame = frame->GetWebFrame(); |
| if (!web_frame) |
| return std::nullopt; |
| return CreateFormDataFromUnownedInputElements( |
| *web_frame, field_data_manager(), &username_detector_cache_, |
| autofill_agent_->GetCallTimerState( |
| CallTimerState::CallSite::kGetFormDataFromUnownedInputElements), |
| autofill_agent_->button_titles_cache(), form_cache); |
| } |
| |
| void PasswordAutofillAgent::InformAboutFormClearing( |
| const WebFormElement& form) { |
| if (!FrameCanAccessPasswordManager()) |
| return; |
| for (const auto& element : form.GetFormControlElements()) { |
| // Notify PasswordManager if `form` has password fields that have user typed |
| // input or input autofilled on user trigger. |
| if (IsPasswordFieldFilledByUser(element)) { |
| NotifyPasswordManagerAboutClearedForm(form); |
| return; |
| } |
| } |
| } |
| |
| void PasswordAutofillAgent::InformAboutFieldClearing( |
| const WebInputElement& cleared_element) { |
| if (!FrameCanAccessPasswordManager()) |
| return; |
| DCHECK(cleared_element.Value().IsEmpty()); |
| FieldRendererId field_id = form_util::GetFieldRendererId(cleared_element); |
| // Ignore fields that had no user input or autofill on user trigger. |
| if (!field_data_manager().DidUserType(field_id) && |
| !field_data_manager().WasAutofilledOnUserTrigger(field_id)) { |
| return; |
| } |
| |
| WebFormElement form = cleared_element.GetOwningFormForAutofill(); |
| if (!form) { |
| // Process password field clearing for fields outside the <form> tag. |
| if (std::optional<FormData> unowned_form_data = |
| GetFormDataFromUnownedInputElements(/*form_cache=*/{})) { |
| GetPasswordManagerDriver().PasswordFormCleared(*unowned_form_data); |
| } |
| return; |
| } |
| // Process field clearing for a form under a <form> tag. |
| // Only notify PasswordManager in case all user filled password fields were |
| // cleared. |
| bool cleared_all_password_fields = std::ranges::all_of( |
| form.GetFormControlElements(), [this](const auto& el) { |
| return !IsPasswordFieldFilledByUser(el) || el.Value().IsEmpty(); |
| }); |
| if (cleared_all_password_fields) |
| NotifyPasswordManagerAboutClearedForm(form); |
| } |
| |
| std::optional<PasswordSuggestionRequest> |
| PasswordAutofillAgent::CreateRequestForDomain( |
| const WebInputElement& element, |
| AutofillSuggestionTriggerSource trigger_source, |
| const SynchronousFormCache& form_cache) { |
| WebInputElement username_element; |
| WebInputElement password_element; |
| PasswordInfo* password_info = nullptr; |
| FindPasswordInfoForElement(element, UseFallbackData(true), &username_element, |
| &password_element, &password_info); |
| |
| if (!password_info) { |
| MaybeCheckSafeBrowsingReputation(element); |
| if (!CanShowPopupWithoutPasswords(password_element)) { |
| return std::nullopt; |
| } |
| } |
| |
| if (!element.IsTextField() || !IsElementEditable(element)) { |
| return std::nullopt; |
| } |
| // Check that at least one fillable element is editable. |
| if (!IsUsernameOrPasswordFillable(username_element, password_element, |
| password_info)) { |
| return std::nullopt; |
| } |
| |
| // Don't attempt to autofill with values that are too large. |
| if (element.Value().length() > kMaximumTextSizeForAutocomplete) { |
| return std::nullopt; |
| } |
| |
| if (!HasDocumentWithValidFrame(element)) { |
| return std::nullopt; |
| } |
| |
| // If a username element is focused, show suggestions unless all possible |
| // usernames are filtered. |
| if (element.FormControlTypeForAutofill() != kInputPassword) { |
| std::u16string username_prefix; |
| if (!ShouldShowFullSuggestionListForPasswordManager(trigger_source, |
| element) && |
| !base::FeatureList::IsEnabled( |
| password_manager::features::kNoPasswordSuggestionFiltering)) { |
| if (!password_info || |
| !CanShowUsernameSuggestion(password_info->fill_data, |
| element.Value().Utf16())) { |
| return std::nullopt; |
| } |
| username_prefix = element.Value().Utf16(); |
| } |
| return CreateSuggestionRequest(username_prefix, element, trigger_source, |
| form_cache); |
| } |
| |
| // If the element is a password field, do not to show a popup if the user has |
| // already accepted a password suggestion on another password field. |
| if (HasAcceptedSuggestionOnOtherField(element)) { |
| return std::nullopt; |
| } |
| |
| return CreateSuggestionRequest(std::u16string(), element, trigger_source, |
| form_cache); |
| } |
| |
| std::optional<PasswordSuggestionRequest> |
| PasswordAutofillAgent::CreateManualFallbackRequest( |
| const WebInputElement& element, |
| const SynchronousFormCache& form_cache) { |
| WebInputElement username_element; |
| WebInputElement password_element; |
| PasswordInfo* password_info = nullptr; |
| if (!FindPasswordInfoForElement(element, UseFallbackData(false), |
| &username_element, &password_element, |
| &password_info)) { |
| // Perform this action only if there's no passwords saved for the triggering |
| // field. Manual fallback suggestions can be shown on any field. |
| MaybeCheckSafeBrowsingReputation(element); |
| } |
| |
| if (!FrameCanAccessPasswordManager()) { |
| return std::nullopt; |
| } |
| |
| if (!HasDocumentWithValidFrame(element)) { |
| return std::nullopt; |
| } |
| |
| return CreateSuggestionRequest( |
| std::u16string(), element, |
| AutofillSuggestionTriggerSource::kManualFallbackPasswords, form_cache); |
| } |
| |
| bool PasswordAutofillAgent::HasAcceptedSuggestionOnOtherField( |
| const WebInputElement& element) { |
| WebInputElement username_element; |
| WebInputElement password_element; |
| PasswordInfo* password_info = nullptr; |
| FindPasswordInfoForElement(element, UseFallbackData(true), &username_element, |
| &password_element, &password_info); |
| return password_info && |
| password_info->password_field_suggestion_was_accepted && |
| element != password_info->password_field.GetField(); |
| } |
| |
| void PasswordAutofillAgent::CleanupOnDocumentShutdown() { |
| web_input_to_password_info_.clear(); |
| password_to_username_.clear(); |
| last_supplied_password_info_iter_ = web_input_to_password_info_.end(); |
| field_data_manager().ClearData(); |
| previewed_elements_.clear(); |
| sent_request_to_store_ = false; |
| checked_safe_browsing_reputation_ = false; |
| username_detector_cache_.clear(); |
| forms_structure_cache_.clear(); |
| autofilled_elements_cache_.clear(); |
| all_autofilled_elements_.clear(); |
| field_renderer_id_to_submit_ = FieldRendererId(); |
| suggestion_banned_fields_.clear(); |
| #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| page_passwords_analyser_.Reset(); |
| #endif |
| |
| for (const auto& [_, times_received_data] : times_received_fill_data_) { |
| base::UmaHistogramCounts100("PasswordManager.TimesReceivedFillDataForForm", |
| times_received_data); |
| } |
| times_received_fill_data_.clear(); |
| } |
| |
| void PasswordAutofillAgent::InformBrowserAboutUserInput( |
| const WebFormElement& form, |
| const WebInputElement& element, |
| const SynchronousFormCache& form_cache) { |
| DCHECK(form || element); |
| if (!FrameCanAccessPasswordManager()) |
| return; |
| std::optional<FormData> form_data = |
| form ? GetFormDataFromWebForm(form, form_cache) |
| : GetFormDataFromUnownedInputElements(form_cache); |
| if (!form_data) { |
| return; |
| } |
| // Notify the browser about user inputs if `form` is recognized as a |
| // credential form in the renderer, or if the browser has parsed `element` as |
| // password-related and provided filling data for it. |
| if (IsRendererRecognizedCredentialForm(*form_data) || |
| (element && web_input_to_password_info_.contains(FieldRef(element)))) { |
| GetPasswordManagerDriver().InformAboutUserInput(*form_data); |
| } |
| } |
| |
| bool PasswordAutofillAgent::FillCredentialsAutomatically( |
| WebInputElement username_element, |
| WebInputElement password_element, |
| const PasswordFormFillData& fill_data, |
| RendererSavePasswordProgressLogger* logger, |
| bool notify_browser_of_successful_filling) { |
| LogMessage(logger, Logger::STRING_FILL_USERNAME_AND_PASSWORD_METHOD); |
| |
| bool is_single_username_fill = !password_element; |
| WebInputElement main_element = |
| is_single_username_fill ? username_element : password_element; |
| |
| if (IsInCrossOriginIframeOrEmbeddedFrame(main_element)) { |
| LogMessage(logger, Logger::STRING_FAILED_TO_FILL_INTO_IFRAME); |
| LogFirstFillingResult(fill_data, FillingResult::kBlockedByFrameHierarchy); |
| return false; |
| } |
| |
| // Don't fill username if password can't be set. |
| if (!IsElementEditable(main_element)) { |
| LogMessage(logger, |
| Logger::STRING_FAILED_TO_FILL_NO_AUTOCOMPLETEABLE_ELEMENT); |
| LogFirstFillingResult(fill_data, |
| FillingResult::kPasswordElementIsNotAutocompleteable); |
| return false; |
| } |
| |
| // `current_username` is the username for credentials that are going to be |
| // autofilled. It is selected according to the algorithm: |
| // 1. If the page already contains a non-empty value in `username_element` |
| // that is not found in the list of values known to be used as placeholders, |
| // this is adopted and not overridden. |
| // 2. Default username from `fill_data` if the username field is |
| // autocompletable. |
| // 3. Empty if username field doesn't exist or if username field is empty and |
| // not autocompletable (no username case). |
| std::u16string current_username; |
| |
| // Whether the username element was prefilled with content that was on a |
| // list of known placeholder texts that should be overridden (e.g. "username |
| // or email" or there is a server hint that it is just a placeholder). |
| bool prefilled_placeholder_username = false; |
| |
| if (username_element) { |
| // This is a heuristic guess. If the credential is stored for |
| // www.example.com, the username may be prefilled with "@example.com". |
| std::string possible_email_domain = |
| GetRegistryControlledDomain(fill_data.url); |
| |
| prefilled_placeholder_username = |
| !username_element.Value().IsEmpty() && |
| PossiblePrefilledUsernameValue(username_element.Value().Utf8(), |
| possible_email_domain); |
| |
| if (!username_element.Value().IsEmpty() && |
| username_element.GetAutofillState() == WebAutofillState::kNotFilled && |
| !prefilled_placeholder_username) { |
| // Username is filled with content that was not on a list of known |
| // placeholder texts (e.g. "username or email") nor there is server-side |
| // data that this value is placeholder. |
| current_username = username_element.Value().Utf16(); |
| } else if (IsElementEditable(username_element)) { |
| current_username = fill_data.preferred_login.username_value; |
| } |
| } |
| |
| // `username` and `password` will contain the match found if any. |
| std::u16string username; |
| std::u16string password; |
| |
| bool exact_username_match = |
| !username_element || IsElementEditable(username_element); |
| |
| FindMatchesByUsername(fill_data, current_username, exact_username_match, |
| logger, &username, &password); |
| |
| if (password.empty() && !is_single_username_fill) { |
| if (username_element && !username_element.Value().IsEmpty() && |
| !prefilled_placeholder_username) { |
| LogMessage(logger, Logger::STRING_FAILED_TO_FILL_PREFILLED_USERNAME); |
| LogFirstFillingResult( |
| fill_data, FillingResult::kUsernamePrefilledWithIncompatibleValue); |
| return false; |
| } |
| LogMessage(logger, |
| Logger::STRING_FAILED_TO_FILL_FOUND_NO_PASSWORD_FOR_USERNAME); |
| LogFirstFillingResult(fill_data, |
| FillingResult::kFoundNoPasswordForUsername); |
| return false; |
| } |
| |
| // Call OnFieldAutofilled before WebInputElement::SetAutofillState which may |
| // cause frame closing. |
| if (password_generation_agent_ && !is_single_username_fill) |
| password_generation_agent_->OnFieldAutofilled(password_element); |
| |
| // Input matches the username, fill in required values. |
| if (username_element && IsElementEditable(username_element)) { |
| if (!username.empty() && |
| (username_element.Value().IsEmpty() || |
| username_element.GetAutofillState() != WebAutofillState::kNotFilled || |
| prefilled_placeholder_username)) { |
| FillFieldAutomatically(username, username_element); |
| } |
| if (logger) |
| logger->LogElementName(Logger::STRING_USERNAME_FILLED, username_element); |
| } |
| |
| if (!is_single_username_fill) { |
| FillFieldAutomatically(password, password_element); |
| if (logger) |
| logger->LogElementName(Logger::STRING_PASSWORD_FILLED, password_element); |
| } |
| |
| if (notify_browser_of_successful_filling) { |
| TrackAutofilledElement(main_element); |
| // TODO(crbug.com/395080478): Rename InformBrowserAboutUserInput. |
| InformBrowserAboutUserInput(main_element.GetOwningFormForAutofill(), |
| main_element, /*form_cache=*/{}); |
| } |
| |
| LogFirstFillingResult(fill_data, FillingResult::kSuccess); |
| return true; |
| } |
| |
| void PasswordAutofillAgent::FireHostSubmitEvent( |
| FormRendererId form_id, |
| base::optional_ref<const FormData> submitted_form, |
| mojom::SubmissionSource source) { |
| switch (source) { |
| case mojom::SubmissionSource::NONE: |
| NOTREACHED(); |
| case mojom::SubmissionSource::FORM_SUBMISSION: |
| CHECK(submitted_form.has_value()); |
| OnFormSubmitted(*submitted_form); |
| return; |
| case mojom::SubmissionSource::PROBABLY_FORM_SUBMITTED: |
| return; |
| case mojom::SubmissionSource::SAME_DOCUMENT_NAVIGATION: |
| case mojom::SubmissionSource::XHR_SUCCEEDED: |
| case mojom::SubmissionSource::FRAME_DETACHED: |
| case mojom::SubmissionSource::DOM_MUTATION_AFTER_AUTOFILL: |
| if (FrameCanAccessPasswordManager()) { |
| base::UmaHistogramEnumeration(kSubmissionSourceHistogram, source); |
| GetPasswordManagerDriver().DynamicFormSubmission( |
| ToSubmissionIndicatorEvent(source)); |
| } |
| return; |
| } |
| NOTREACHED(); |
| } |
| |
| void PasswordAutofillAgent::OnFormSubmitted(const FormData& submitted_form) { |
| WebFormElement form_element = |
| GetFormByRendererId(submitted_form.renderer_id()); |
| std::unique_ptr<RendererSavePasswordProgressLogger> logger; |
| if (logging_state_active_) { |
| logger = std::make_unique<RendererSavePasswordProgressLogger>( |
| &GetPasswordManagerDriver()); |
| LogHTMLForm(logger.get(), Logger::STRING_HTML_FORM_FOR_SUBMIT, |
| form_element); |
| } |
| |
| if (!FrameCanAccessPasswordManager()) { |
| LogMessage(logger.get(), Logger::STRING_SECURITY_ORIGIN_FAILURE); |
| return; |
| } |
| |
| std::optional<FormData> processed_submitted_form = GetFormDataFromWebForm( |
| form_element, SynchronousFormCache(submitted_form)); |
| |
| if (!processed_submitted_form || !HasTextInputs(*processed_submitted_form)) { |
| return; |
| } |
| |
| processed_submitted_form->set_submission_event( |
| SubmissionIndicatorEvent::HTML_FORM_SUBMISSION); |
| |
| processed_submitted_form->set_fields(FillNonTypedOrFilledPropertiesMasks( |
| processed_submitted_form->ExtractFields(), field_data_manager())); |
| |
| base::UmaHistogramEnumeration(kSubmissionSourceHistogram, |
| mojom::SubmissionSource::FORM_SUBMISSION); |
| GetPasswordManagerDriver().PasswordFormSubmitted(*processed_submitted_form); |
| } |
| |
| void PasswordAutofillAgent::HidePopup() { |
| if (autofill_agent_->unsafe_autofill_driver()) { |
| autofill_agent_->unsafe_autofill_driver()->HidePopup(); |
| } |
| } |
| |
| bool PasswordAutofillAgent::CanShowPopupWithoutPasswords( |
| const WebInputElement& password_element) const { |
| #if BUILDFLAG(ENABLE_DICE_SUPPORT) |
| return password_element && IsElementEditable(password_element) && |
| should_show_popup_without_passwords_; |
| #else |
| return false; |
| #endif |
| } |
| |
| mojom::PasswordManagerDriver& |
| PasswordAutofillAgent::GetPasswordManagerDriver() { |
| if (IsPrerendering()) { |
| if (!deferring_password_manager_driver_) { |
| deferring_password_manager_driver_ = |
| std::make_unique<DeferringPasswordManagerDriver>(this); |
| } |
| return *deferring_password_manager_driver_; |
| } |
| |
| // Lazily bind this interface. |
| if (!password_manager_driver_) { |
| render_frame()->GetRemoteAssociatedInterfaces()->GetInterface( |
| &password_manager_driver_); |
| } |
| |
| return *password_manager_driver_; |
| } |
| |
| std::pair<WebInputElement, WebInputElement> |
| PasswordAutofillAgent::FindUsernamePasswordElements( |
| const PasswordFormFillData& form_data) { |
| const FieldRendererId username_renderer_id = |
| form_data.username_element_renderer_id; |
| const FieldRendererId password_renderer_id = |
| form_data.password_element_renderer_id; |
| const bool is_username_present = !username_renderer_id.is_null(); |
| const bool is_password_present = !password_renderer_id.is_null(); |
| |
| std::vector<FieldRendererId> element_ids; |
| if (is_password_present) |
| element_ids.push_back(password_renderer_id); |
| if (is_username_present) |
| element_ids.push_back(username_renderer_id); |
| |
| std::vector<WebFormControlElement> elements; |
| elements.reserve(element_ids.size()); |
| for (FieldRendererId id : element_ids) { |
| elements.push_back(form_util::GetFormControlByRendererId(id)); |
| } |
| |
| // Set password element. |
| WebInputElement password_field; |
| size_t current_index = 0; |
| if (is_password_present) |
| password_field = elements[current_index++].DynamicTo<WebInputElement>(); |
| |
| // Set username element. |
| WebInputElement username_field; |
| if (is_username_present) |
| username_field = elements[current_index++].DynamicTo<WebInputElement>(); |
| |
| return std::make_pair(username_field, password_field); |
| } |
| |
| void PasswordAutofillAgent::StoreDataForFillOnAccountSelect( |
| const PasswordFormFillData& form_data, |
| WebInputElement username_element, |
| WebInputElement password_element) { |
| WebInputElement main_element = |
| username_element ? username_element : password_element; |
| |
| PasswordInfo password_info; |
| password_info.fill_data = form_data; |
| if (password_element) { |
| password_info.password_field = FieldRef(password_element); |
| } |
| if (main_element) { |
| web_input_to_password_info_[FieldRef(main_element)] = password_info; |
| last_supplied_password_info_iter_ = |
| web_input_to_password_info_.find(FieldRef(main_element)); |
| if (main_element.FormControlTypeForAutofill() != kInputPassword && |
| password_element && username_element) { |
| password_to_username_[FieldRef(password_element)] = |
| FieldRef(username_element); |
| } |
| } |
| } |
| |
| void PasswordAutofillAgent::MaybeStoreFallbackData( |
| const PasswordFormFillData& form_data) { |
| if (!web_input_to_password_info_.empty()) |
| return; |
| // If for some reasons elements for filling were not found (for example |
| // because they were renamed by JavaScript) then add fill data for |
| // `web_input_to_password_info_`. When the user clicks on a password field |
| // which is not a key in `web_input_to_password_info_`, the first element from |
| // `web_input_to_password_info_` will be used in |
| // PasswordAutofillAgent::FindPasswordInfoForElement to propose to fill. |
| PasswordInfo password_info; |
| password_info.fill_data = form_data; |
| web_input_to_password_info_[FieldRef()] = password_info; |
| last_supplied_password_info_iter_ = web_input_to_password_info_.begin(); |
| } |
| |
| void PasswordAutofillAgent::LogFirstFillingResult( |
| const PasswordFormFillData& form_data, |
| FillingResult result) { |
| if (recorded_first_filling_result_) |
| return; |
| UMA_HISTOGRAM_ENUMERATION("PasswordManager.FirstRendererFillingResult", |
| result); |
| GetPasswordManagerDriver().LogFirstFillingResult( |
| form_data.form_renderer_id, base::strict_cast<int32_t>(result)); |
| recorded_first_filling_result_ = true; |
| } |
| |
| PasswordAutofillAgent::FormStructureInfo |
| PasswordAutofillAgent::ExtractFormStructureInfo(const FormData& form_data) { |
| FormStructureInfo result; |
| result.renderer_id = form_data.renderer_id(); |
| result.fields.resize(form_data.fields().size()); |
| |
| for (auto [form_field, field_info] : |
| base::zip(form_data.fields(), result.fields)) { |
| field_info.renderer_id = form_field.renderer_id(); |
| field_info.form_control_type = form_field.form_control_type(); |
| field_info.autocomplete_attribute = form_field.autocomplete_attribute(); |
| field_info.is_focusable = form_field.is_focusable(); |
| } |
| |
| return result; |
| } |
| |
| bool PasswordAutofillAgent::WasFormStructureChanged( |
| const FormStructureInfo& form_info) const { |
| if (form_info.renderer_id.is_null()) { |
| return true; |
| } |
| |
| auto cached_form = forms_structure_cache_.find(form_info.renderer_id); |
| if (cached_form == forms_structure_cache_.end()) |
| return true; |
| |
| const FormStructureInfo& cached_form_info = cached_form->second; |
| |
| if (form_info.fields.size() != cached_form_info.fields.size()) |
| return true; |
| |
| for (auto [form_field, cached_form_field] : |
| base::zip(form_info.fields, cached_form_info.fields)) { |
| if (form_field.renderer_id != cached_form_field.renderer_id) { |
| return true; |
| } |
| |
| if (form_field.form_control_type != cached_form_field.form_control_type) |
| return true; |
| |
| if (form_field.autocomplete_attribute != |
| cached_form_field.autocomplete_attribute) { |
| return true; |
| } |
| |
| if (form_field.is_focusable != cached_form_field.is_focusable) |
| return true; |
| } |
| return false; |
| } |
| |
| void PasswordAutofillAgent::TryFixAutofilledForm( |
| std::vector<WebFormControlElement>& control_elements) const { |
| for (WebFormControlElement& control_element : control_elements) { |
| auto cached_element = autofilled_elements_cache_.find( |
| form_util::GetFieldRendererId(control_element)); |
| if (cached_element == autofilled_elements_cache_.end()) { |
| continue; |
| } |
| // autofilled_elements_cache_ stores values filled at page load time and |
| // gets wiped when we observe a user gesture. During this time, the |
| // username/password fields can be in preview state and we restore this |
| // state if JavaScript modifies the field's value. |
| const WebString& cached_value = cached_element->second; |
| if (cached_value != control_element.SuggestedValue()) { |
| control_element.SetSuggestedValue(cached_value); |
| } |
| } |
| } |
| |
| void PasswordAutofillAgent::FillFieldAutomatically(const std::u16string& value, |
| WebInputElement field) { |
| CHECK(field); |
| // Do not autofill on load fields that have any user typed input. |
| const FieldRendererId field_id = form_util::GetFieldRendererId(field); |
| if (field_data_manager().DidUserType(field_id)) { |
| return; |
| } |
| if (field.Value().Utf16() == value) { |
| return; |
| } |
| field.SetSuggestedValue(WebString::FromUTF16(value)); |
| field.SetAutofillState(WebAutofillState::kAutofilled); |
| // Wait to fill until a user gesture occurs. This is to make sure that we do |
| // not fill in the DOM with a password until we believe the user is |
| // intentionally interacting with the page. |
| gatekeeper_.RegisterElement(field); |
| field_data_manager().UpdateFieldDataMap( |
| field_id, value, FieldPropertiesFlags::kAutofilledOnPageLoad); |
| autofilled_elements_cache_.emplace(field_id, WebString::FromUTF16(value)); |
| all_autofilled_elements_.insert(field_id); |
| } |
| |
| bool PasswordAutofillAgent::IsPasswordFieldFilledByUser( |
| const WebFormControlElement& element) const { |
| FieldRendererId element_id = form_util::GetFieldRendererId(element); |
| return element.FormControlTypeForAutofill() == |
| blink::mojom::FormControlType::kInputPassword && |
| (field_data_manager().DidUserType(element_id) || |
| field_data_manager().WasAutofilledOnUserTrigger(element_id)); |
| } |
| |
| void PasswordAutofillAgent::NotifyPasswordManagerAboutClearedForm( |
| const WebFormElement& cleared_form) { |
| CHECK(cleared_form); |
| if (std::optional<FormData> form_data = form_util::ExtractFormData( |
| cleared_form.GetDocument(), cleared_form, field_data_manager(), |
| autofill_agent_->GetCallTimerState( |
| CallTimerState::CallSite::kNotifyPasswordManagerAboutClearedForm), |
| autofill_agent_->button_titles_cache())) { |
| GetPasswordManagerDriver().PasswordFormCleared(*form_data); |
| } |
| } |
| |
| void PasswordAutofillAgent::MaybeTriggerSuggestionsOnFocusedElement( |
| const WebInputElement& username_element, |
| const WebInputElement& password_element) { |
| WebInputElement focused_element; |
| if (username_element && username_element.Focused()) { |
| focused_element = username_element; |
| } else if (password_element && password_element.Focused()) { |
| focused_element = password_element; |
| } else { |
| return; |
| } |
| |
| std::optional<FormData> form_data = GetFormDataFromWebForm( |
| focused_element.GetOwningFormForAutofill(), /*form_cache=*/{}); |
| if (form_data && (times_received_fill_data_[form_data->renderer_id()] == 1) && |
| #if BUILDFLAG(IS_ANDROID) |
| // Limit showing suggestions on autofocus to WebAuthn forms only, since |
| // Android suggestion UI (TTF) can be much more intrusive. |
| IsWebAuthnForm(form_data) && |
| #endif // BUILDFLAG(IS_ANDROID) |
| base::FeatureList::IsEnabled( |
| password_manager::features::kShowSuggestionsOnAutofocus)) { |
| // Updating the focused field in the `FocusStateNotifier` to the currently |
| // focused field is typically a no-op, but this isn't set for an |
| // autofocused field. Setting it here allows a notification to proceed |
| // when the initially-focused field is blurred. |
| std::tie(focus_state_notifier_.focused_field_type_, |
| focus_state_notifier_.focused_field_id_) = |
| focus_state_notifier_.GetFocusedFieldInfo(focused_element); |
| autofill_agent_->TriggerSuggestions( |
| form_util::GetFieldRendererId(focused_element), |
| AutofillSuggestionTriggerSource::kPasswordManagerProcessedFocusedField); |
| } |
| } |
| |
| } // namespace autofill |