// Copyright 2017 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/android_autofill/browser/android_autofill_provider.h"
#include <memory>
#include "base/android/build_info.h"
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/debug/crash_logging.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "components/android_autofill/browser/android_autofill_bridge_factory.h"
#include "components/android_autofill/browser/android_autofill_features.h"
#include "components/android_autofill/browser/android_autofill_manager.h"
#include "components/android_autofill/browser/android_autofill_provider_bridge.h"
#include "components/android_autofill/browser/form_data_android.h"
#include "components/autofill/android/touch_to_fill_keyboard_suppressor.h"
#include "components/autofill/content/browser/content_autofill_client.h"
#include "components/autofill/content/browser/content_autofill_driver.h"
#include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/common/autocomplete_parsing_util.h"
#include "components/autofill/core/common/autofill_constants.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill/core/common/form_data.h"
#include "components/autofill/core/common/form_field_data.h"
#include "components/autofill/core/common/signatures.h"
#include "components/autofill/core/common/unique_ids.h"
#include "components/password_manager/core/browser/form_parsing/form_data_parser.h"
#include "components/password_manager/core/browser/password_form.h"
#include "components/webauthn/android/webauthn_cred_man_delegate_factory.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "ui/android/window_android.h"
#include "ui/gfx/geometry/rect_f.h"
namespace autofill {
namespace {
using ::autofill::mojom::SubmissionSource;
using ::base::android::JavaRef;
using ::content::BrowserThread;
using ::password_manager::PasswordForm;
using ::webauthn::WebAuthnCredManDelegate;
using ::webauthn::WebAuthnCredManDelegateFactory;
using FieldInfo = ::autofill::AndroidAutofillProviderBridge::FieldInfo;
using RequestPasswords = WebAuthnCredManDelegate::RequestPasswords;
constexpr int kMinimumSdkVersionForPrefillRequests =
base::android::SdkVersion::SDK_VERSION_U;
constexpr base::TimeDelta kKeyboardSuppressionTimeout = base::Seconds(1);
std::unique_ptr<PasswordForm> ParseToPasswordForm(
const FormStructure& form_structure) {
// Transform the predictions data to a format the `FormDataParser` can handle
// and parse the form.
FormData form_data = form_structure.ToFormData();
auto autofill_predictions =
base::MakeFlatMap<FieldGlobalId, AutofillType::ServerPrediction>(
form_structure, /*comp=*/{},
/*proj=*/[](const std::unique_ptr<AutofillField>& field) {
return std::make_pair(field->global_id(),
AutofillType::ServerPrediction(*field));
});
password_manager::FormDataParser parser;
// The driver id is irrelevant here because it would only be used by password
// manager logic that handles the `PasswordForm` returned by the parser.
// Therefore we pass a dummy a value.
parser.set_predictions(password_manager::ConvertToFormPredictions(
/*driver_id=*/0, form_data, autofill_predictions));
// On Chrome, the parser can use stored usernames to identify a filled
// username field by the value it contains. Since we do not have access to
// credentials, we leave it empty.
return parser.Parse(form_data,
password_manager::FormDataParser::Mode::kFilling,
/*stored_usernames=*/{});
}
// Returns whether we should attempt to cache provider responses for this form.
// Currently, that is the case iff we diagnose it to be a login form or a change
// password form.
bool ShouldCachePasswordForm(const PasswordForm& pw_form) {
return pw_form.IsLikelyLoginForm() ||
(pw_form.IsLikelyChangePasswordForm() &&
base::FeatureList::IsEnabled(
features::kAndroidAutofillPrefillRequestsForChangePassword));
}
content::RenderFrameHost* GetRenderFrameHost(AutofillManager* manager) {
return static_cast<ContentAutofillDriver&>(manager->driver())
.render_frame_host();
}
WebAuthnCredManDelegate* GetCredManDelegate(content::RenderFrameHost* rfh) {
return WebAuthnCredManDelegateFactory::GetFactory(
content::WebContents::FromRenderFrameHost(rfh))
->GetRequestDelegate(rfh);
}
WebAuthnCredManDelegate* GetCredManDelegate(AutofillManager* manager) {
return GetCredManDelegate(GetRenderFrameHost(manager));
}
constexpr base::TimeDelta kWasBottomSheetShownFlipTimeout =
base::Milliseconds(50);
} // namespace
// static
void AndroidAutofillProvider::CreateForWebContents(
content::WebContents* web_contents) {
if (!FromWebContents(web_contents)) {
web_contents->SetUserData(
UserDataKey(),
base::WrapUnique(new AndroidAutofillProvider(web_contents)));
}
}
AndroidAutofillProvider* AndroidAutofillProvider::FromWebContents(
content::WebContents* web_contents) {
return static_cast<AndroidAutofillProvider*>(
AutofillProvider::FromWebContents(web_contents));
}
AndroidAutofillProvider::AndroidAutofillProvider(
content::WebContents* web_contents)
: AutofillProvider(web_contents),
content::WebContentsObserver(web_contents),
bridge_(AndroidAutofillBridgeFactory::GetInstance()
.CreateAndroidAutofillProviderBridge(/*delegate=*/this)) {}
AndroidAutofillProvider::~AndroidAutofillProvider() = default;
void AndroidAutofillProvider::AttachToJavaAutofillProvider(
JNIEnv* env,
const JavaRef<jobject>& jcaller) {
bridge_->AttachToJavaAutofillProvider(env, jcaller);
}
void AndroidAutofillProvider::RenderFrameDeleted(
content::RenderFrameHost* rfh) {
// If the popup menu has been triggered from within an iframe and that frame
// is deleted, hide the popup. This is necessary because the popup may
// actually be shown by the AutofillExternalDelegate of an ancestor frame,
// which is not notified about `rfh`'s destruction and therefore won't close
// the popup.
if (manager_ && last_queried_field_rfh_id_ == rfh->GetGlobalId()) {
OnHidePopup(manager_.get());
last_queried_field_rfh_id_ = {};
}
}
void AndroidAutofillProvider::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
if (manager_ &&
last_queried_field_rfh_id_ ==
navigation_handle->GetPreviousRenderFrameHostId() &&
!navigation_handle->IsSameDocument()) {
OnHidePopup(manager_.get());
last_queried_field_rfh_id_ = {};
credman_sheet_status_ = CredManBottomSheetLifecycle::kNotShown;
}
}
void AndroidAutofillProvider::OnVisibilityChanged(
content::Visibility visibility) {
if (visibility == content::Visibility::HIDDEN && manager_) {
OnHidePopup(manager_.get());
}
}
void AndroidAutofillProvider::OnAskForValuesToFill(
AndroidAutofillManager* manager,
const FormData& form,
const FormFieldData& field,
AutofillSuggestionTriggerSource /*unused_trigger_source*/) {
// The id isn't passed to Java side because Android API guarantees the
// response is always for current session, so we just use the current id
// in response, see OnAutofillAvailable.
DCHECK_CURRENTLY_ON(BrowserThread::UI);
GetRenderFrameHost(manager)->ForEachRenderFrameHost(
[this, &field](content::RenderFrameHost* rfh) {
LocalFrameToken frame_token(rfh->GetFrameToken().value());
if (frame_token == field.host_frame()) {
last_queried_field_rfh_id_ = rfh->GetGlobalId();
}
});
current_field_ = {field.global_id(),
manager->ComputeFieldTypeGroupForField(form, field),
field.origin()};
// Focus or field value change will also trigger the query, so it should be
// ignored if the form is same.
if (!IsLinkedForm(form)) {
StartNewSession(manager, form, field);
}
if (field.datalist_options().empty()) {
return;
}
bridge_->ShowDatalistPopup(
field.datalist_options(),
field.text_direction() == base::i18n::RIGHT_TO_LEFT);
}
bool AndroidAutofillProvider::IsFormSimilarToCachedForm(
const FormData& form,
const FormStructure* form_structure) const {
if (!cached_data_ || !cached_data_->cached_form) {
return false;
}
if (form_structure) {
CHECK_EQ(form.global_id(), form_structure->global_id());
std::unique_ptr<PasswordForm> pw_form =
ParseToPasswordForm(*form_structure);
if (!pw_form || !ShouldCachePasswordForm(*pw_form)) {
return false;
}
PasswordParserOverrides overrides =
PasswordParserOverrides::FromPasswordForm(*pw_form, *form_structure)
.value_or(PasswordParserOverrides());
return cached_data_->password_parser_overrides == overrides;
}
return cached_data_->cached_form->SimilarFormAs(form);
}
void AndroidAutofillProvider::StartNewSession(AndroidAutofillManager* manager,
const FormData& form,
const FormFieldData& field) {
FormStructure* form_structure = manager->FindCachedFormById(form.global_id());
FormDataAndroid* cached_form =
cached_data_ ? cached_data_->cached_form.get() : nullptr;
const bool is_similar_to_cached_form =
IsFormSimilarToCachedForm(form, form_structure);
// The form is assigned the same session id form sent to the Android framework
// in the prefill request iff all of the following conditions are met:
// - There is a cached form.
// - This is the first time we try to show the bottom sheet for the cached
// form (on their second interaction, the user should see the keyboard).
// - The cached form is similar to the current form.
const bool use_cached_form =
is_similar_to_cached_form && !has_used_cached_form_;
form_ = std::make_unique<FormDataAndroid>(
form, use_cached_form ? cached_form->session_id() : CreateSessionId());
FieldInfo field_info;
if (!form_->GetFieldIndex(field, &field_info.index)) {
Reset();
return;
}
check_submission_ = false;
manager_ = manager->GetWeakPtrToLeafClass();
// Set the field type predictions in `form_`.
if (form_structure) {
form_->UpdateFieldTypes(*form_structure);
// If there a non-trivial overrides from `FormDataParse` in the cached form,
// apply them to the new form as well.
if (use_cached_form &&
cached_data_->password_parser_overrides != PasswordParserOverrides()) {
form_->UpdateFieldTypes(
cached_data_->password_parser_overrides.ToFieldTypeMap());
}
}
field_info.bounds = ToClientAreaBound(field.bounds());
[&] {
// Metrics for prefill requests are only emitted if this is the first time
// a cached form is focused - hence the use of `is_similar_to_cached_form`.
if (!ArePrefillRequestsSupported() || is_similar_to_cached_form) {
return;
}
// We sent a cache request for this form element, but the form (or its
// members) have changed since then.
if (cached_form && cached_form->form().global_id() == form.global_id()) {
base::UmaHistogramEnumeration(
kPrefillRequestStateUma,
PrefillRequestState::kRequestSentFormChanged);
return;
}
// Prefill request state metrics are for forms that we would have cached.
if (!form_structure) {
return;
}
std::unique_ptr<PasswordForm> pw_form =
ParseToPasswordForm(*form_structure);
if (!pw_form || !ShouldCachePasswordForm(*pw_form)) {
return;
}
if (cached_form) {
// We would have cached the form, but another cache request had already
// been sent.
base::UmaHistogramEnumeration(
kPrefillRequestStateUma,
PrefillRequestState::kRequestNotSentMaxNumberReached);
return;
}
// If we reach this point, we know that a) we would have cached the form and
// b) no other cache request has been sent. That means that we did not
// receive the predictions for this form in time.
base::UmaHistogramEnumeration(kPrefillRequestStateUma,
PrefillRequestState::kRequestNotSentNoTime);
}();
has_used_cached_form_ = true;
bridge_->StartAutofillSession(
*form_, field_info, manager->has_server_prediction(form.global_id()));
}
void AndroidAutofillProvider::OnAutofillAvailable() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
was_bottom_sheet_just_shown_ = false;
if (manager_ && form_) {
form_->UpdateFromJava();
FillOrPreviewForm(manager_.get(), form_->form(), current_field_.group,
current_field_.origin);
}
}
void AndroidAutofillProvider::OnAcceptDatalistSuggestion(
const std::u16string& value) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (manager_) {
RendererShouldAcceptDataListSuggestion(manager_.get(), current_field_.id,
value);
}
}
void AndroidAutofillProvider::SetAnchorViewRect(
const base::android::JavaRef<jobject>& anchor,
const gfx::RectF& bounds) {
if (ui::ViewAndroid* view_android = web_contents()->GetNativeView()) {
view_android->SetAnchorRect(anchor, bounds);
}
}
void AndroidAutofillProvider::OnShowBottomSheetResult(
bool is_shown,
bool provided_autofill_structure) {
was_bottom_sheet_just_shown_ = is_shown;
if (is_shown) {
base::UmaHistogramEnumeration(
kPrefillRequestStateUma,
PrefillRequestState::kRequestSentStructureProvidedBottomSheetShown);
return;
}
if (keyboard_suppressor_) {
keyboard_suppressor_->Unsuppress();
}
// Note that in some cases this metric is not accurate: If, for example,
// the bottom sheet is not shown because keyboard suppression did not work, it
// might be that a later interaction triggers the bottom sheet. See
// b/310634445.
base::UmaHistogramEnumeration(
kPrefillRequestStateUma,
provided_autofill_structure
? PrefillRequestState::
kRequestSentStructureProvidedBottomSheetNotShown
: PrefillRequestState::kRequestSentStructureNotProvided);
if (!provided_autofill_structure && cached_data_.has_value()) {
base::UmaHistogramTimes(
kPrefillRequestBottomsheetNoViewStructureDelayUma,
base::TimeTicks::Now() - cached_data_->prefill_request_creation_time);
}
}
void AndroidAutofillProvider::OnTextFieldDidChange(
AndroidAutofillManager* manager,
const FormData& form,
const FormFieldData& field,
const base::TimeTicks timestamp) {
MaybeFireFormFieldDidChange(manager, form, field);
}
void AndroidAutofillProvider::OnTextFieldDidScroll(
AndroidAutofillManager* manager,
const FormData& form,
const FormFieldData& field) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
FieldInfo field_info;
if (!IsLinkedForm(form) ||
!form_->GetSimilarFieldIndex(field, &field_info.index)) {
return;
}
// TODO(crbug.com/40929724): Investigate whether the update of the value
// is needed - why would it have changed?
form_->OnFormFieldDidChange(field_info.index, field.value());
field_info.bounds = ToClientAreaBound(field.bounds());
bridge_->OnTextFieldDidScroll(field_info);
}
void AndroidAutofillProvider::OnSelectControlDidChange(
AndroidAutofillManager* manager,
const FormData& form,
const FormFieldData& field) {
if (!IsLinkedForm(form)) {
StartNewSession(manager, form, field);
// TODO(crbug.com/40929724): Return early at this point?
}
MaybeFireFormFieldDidChange(manager, form, field);
}
void AndroidAutofillProvider::FireSuccessfulSubmission(
SubmissionSource source) {
bridge_->OnFormSubmitted(source);
Reset();
}
void AndroidAutofillProvider::OnFormSubmitted(AndroidAutofillManager* manager,
const FormData& form,
bool known_success,
SubmissionSource source) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!IsLinkedManager(manager)) {
return;
}
// In the case of form submissions, we want to perform less strict form
// comparisons than for other form events (focus change, scroll change, etc.):
// Even if the page modifies the form between the user interaction and the
// form submission, we want to inform `AutofillManager` about the submission.
// Otherwise no saving prompt can be offered.
if (!IsIdOfLinkedForm(form.global_id())) {
return;
}
if (FormStructure* form_structure =
manager_->FindCachedFormById(form.global_id());
source == mojom::SubmissionSource::DOM_MUTATION_AFTER_AUTOFILL &&
(!form_structure ||
!base::FeatureList::IsEnabled(
features::kAutofillAcceptDomMutationAfterAutofillSubmission) ||
!ParseToPasswordForm(*form_structure))) {
return;
}
if (known_success || source == SubmissionSource::FORM_SUBMISSION ||
base::FeatureList::IsEnabled(
features::kAndroidAutofillDirectFormSubmission)) {
FireSuccessfulSubmission(source);
return;
}
check_submission_ = true;
pending_submission_source_ = source;
}
void AndroidAutofillProvider::OnFocusOnNonFormField(
AndroidAutofillManager* manager) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!IsLinkedManager(manager)) {
return;
}
bridge_->OnFocusChanged(std::nullopt);
}
void AndroidAutofillProvider::OnFocusOnFormField(
AndroidAutofillManager* manager,
const FormData& form,
const FormFieldData& field) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (ShouldShowCredManForField(field, GetRenderFrameHost(manager))) {
ShowCredManSheet(GetRenderFrameHost(manager));
// TODO: crbug.com/332471454 - Ensure this doesn't mess up WebView metrics.
}
FieldInfo field_info;
if (!IsLinkedForm(form) ||
!form_->GetSimilarFieldIndex(field, &field_info.index)) {
return;
}
field_info.bounds = ToClientAreaBound(field.bounds());
MaybeFireFormFieldVisibilitiesDidChange(manager, form);
bridge_->OnFocusChanged(field_info);
}
void AndroidAutofillProvider::MaybeFireFormFieldDidChange(
AndroidAutofillManager* manager,
const FormData& form,
const FormFieldData& field) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
FieldInfo field_info;
if (!IsLinkedForm(form) ||
!form_->GetSimilarFieldIndex(field, &field_info.index)) {
return;
}
// Propagate the changed values to Java.
form_->OnFormFieldDidChange(field_info.index, field.value());
field_info.bounds = ToClientAreaBound(field.bounds());
bridge_->OnFormFieldDidChange(field_info);
}
void AndroidAutofillProvider::MaybeFireFormFieldVisibilitiesDidChange(
AndroidAutofillManager* manager,
const FormData& form) {
if (!IsLinkedForm(form)) {
return;
}
std::vector<int> field_indices_with_change =
form_->UpdateFieldVisibilities(form);
if (field_indices_with_change.empty()) {
return;
}
bridge_->OnFormFieldVisibilitiesDidChange(field_indices_with_change);
}
void AndroidAutofillProvider::OnDidFillAutofillFormData(
AndroidAutofillManager* manager,
const FormData& form,
base::TimeTicks timestamp) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (manager != manager_.get() || !IsIdOfLinkedForm(form.global_id())) {
return;
}
// TODO(crbug.com/40760916): Investigate passing the actually filled fields,
// in case the passed fields to be filled are different from the fields that
// were actually filled.
bridge_->OnDidFillAutofillFormData();
}
void AndroidAutofillProvider::OnHidePopup(AndroidAutofillManager* manager) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (manager == manager_.get()) {
bridge_->HideDatalistPopup();
}
}
void AndroidAutofillProvider::OnServerPredictionsAvailable(
AndroidAutofillManager& manager,
FormGlobalId form_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
MaybeSendPrefillRequest(manager, form_id);
if (!IsIdOfLinkedForm(form_id)) {
return;
}
CHECK(manager_);
const FormStructure* form_structure = manager_->FindCachedFormById(form_id);
if (!form_structure) {
return;
}
form_->UpdateFieldTypes(*form_structure);
bridge_->OnServerPredictionsAvailable();
}
void AndroidAutofillProvider::OnManagerResetOrDestroyed(
AndroidAutofillManager* manager) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!IsLinkedManager(manager)) {
return;
}
// If we previously received a notification from the renderer that the form
// was likely submitted and no event caused a reset of state in the interim,
// we consider this navigation to be resulting from the submission.
if (check_submission_ && form_.get()) {
FireSuccessfulSubmission(pending_submission_source_);
return;
}
Reset();
}
bool AndroidAutofillProvider::GetCachedIsAutofilled(
const FormFieldData& field) const {
size_t field_index = 0u;
return form_ && form_->GetFieldIndex(field, &field_index) &&
form_->form().fields()[field_index].is_autofilled();
}
bool AndroidAutofillProvider::IntendsToShowBottomSheet(
AutofillManager& manager,
FormGlobalId form,
FieldGlobalId field,
const FormData& form_data) const {
return IntendsToShowCredMan(GetRenderFrameHost(&manager)) ||
(ArePrefillRequestsSupported() && !has_used_cached_form_ &&
cached_data_ && cached_data_->cached_form &&
form == cached_data_->cached_form->form().global_id());
}
bool AndroidAutofillProvider::WasBottomSheetJustShown(
AutofillManager& manager) {
if (base::FeatureList::IsEnabled(
features::kAutofillVirtualViewStructureAndroid) &&
credman_sheet_status_ == CredManBottomSheetLifecycle::kIsShowing) {
return true;
}
// TODO(crbug.com/40284788) Remove the timer once a fix is landed on the
// renderer side.
was_shown_bottom_sheet_timer_.Start(
FROM_HERE, kWasBottomSheetShownFlipTimeout, this,
&AndroidAutofillProvider::SetBottomSheetShownOff);
return was_bottom_sheet_just_shown_;
}
void AndroidAutofillProvider::SetBottomSheetShownOff() {
was_bottom_sheet_just_shown_ = false;
}
bool AndroidAutofillProvider::IntendsToShowCredMan(
content::RenderFrameHost* rfh) const {
if (!base::FeatureList::IsEnabled(
features::kAutofillVirtualViewStructureAndroid)) {
return false;
}
const WebAuthnCredManDelegate* delegate = GetCredManDelegate(rfh);
if (!delegate) {
return false; // No delegate available to trigger passkey requests.
}
// Don't show more than once per page.
return credman_sheet_status_ == CredManBottomSheetLifecycle::kNotShown;
}
bool AndroidAutofillProvider::ShouldShowCredManForField(
const FormFieldData& field,
content::RenderFrameHost* rfh) {
if (!base::FeatureList::IsEnabled(
features::kAutofillVirtualViewStructureAndroid)) {
return false;
}
if (!field.parsed_autocomplete() || !field.parsed_autocomplete()->webauthn) {
return false; // Only trigger conditional requests if a site prefers it.
}
// TODO: crbug.com/332471454 - Trigger Chrome no-passkey sheet?
WebAuthnCredManDelegate* delegate = GetCredManDelegate(rfh);
if (!delegate ||
delegate->HasPasskeys() == WebAuthnCredManDelegate::State::kNotReady) {
return false; // Requests not finished.
}
return credman_sheet_status_ == CredManBottomSheetLifecycle::kNotShown;
}
void AndroidAutofillProvider::ShowCredManSheet(content::RenderFrameHost* rfh) {
CHECK_EQ(credman_sheet_status_, CredManBottomSheetLifecycle::kNotShown);
if (WebAuthnCredManDelegate* delegate = GetCredManDelegate(rfh)) {
credman_sheet_status_ = CredManBottomSheetLifecycle::kIsShowing;
delegate->SetRequestCompletionCallback(
base::BindRepeating(&AndroidAutofillProvider::OnCredManUiClosed,
weak_ptr_factory_.GetWeakPtr()));
delegate->TriggerCredManUi(RequestPasswords(false));
}
}
void AndroidAutofillProvider::MaybeInitKeyboardSuppressor() {
// Return early if prefill requests are not supported.
if (!ArePrefillRequestsSupported() &&
!base::FeatureList::IsEnabled(
features::kAutofillVirtualViewStructureAndroid)) {
return;
}
keyboard_suppressor_ = std::make_unique<TouchToFillKeyboardSuppressor>(
ContentAutofillClient::FromWebContents(web_contents()),
base::BindRepeating(&AndroidAutofillProvider::WasBottomSheetJustShown,
base::Unretained(this)),
base::BindRepeating(&AndroidAutofillProvider::IntendsToShowBottomSheet,
base::Unretained(this)),
kKeyboardSuppressionTimeout);
}
bool AndroidAutofillProvider::IsLinkedManager(
AndroidAutofillManager* manager) const {
return manager == manager_.get();
}
bool AndroidAutofillProvider::IsIdOfLinkedForm(FormGlobalId form_id) const {
return form_ && form_->form().global_id() == form_id;
}
bool AndroidAutofillProvider::IsLinkedForm(const FormData& form) {
return form_ && form_->SimilarFormAs(form);
}
gfx::RectF AndroidAutofillProvider::ToClientAreaBound(
const gfx::RectF& bounding_box) {
gfx::Rect client_area = web_contents()->GetContainerBounds();
return bounding_box + client_area.OffsetFromOrigin();
}
void AndroidAutofillProvider::Reset() {
if (base::FeatureList::IsEnabled(
features::kAutofillVirtualViewStructureAndroid) &&
manager_) {
if (WebAuthnCredManDelegate* delegate =
GetCredManDelegate(manager_.get())) {
delegate->SetRequestCompletionCallback(base::DoNothing());
}
}
manager_ = nullptr;
form_.reset();
credman_sheet_status_ = CredManBottomSheetLifecycle::kNotShown;
current_field_ = {};
check_submission_ = false;
was_shown_bottom_sheet_timer_.Stop();
was_bottom_sheet_just_shown_ = false;
CancelSession();
// Resets the Java instance and hides the datalist popup if there is one.
bridge_->Reset();
// TODO(crbug.com/40283554): Also send an unfocus event to make sure that
// the Autofill session is truly terminated.
}
void AndroidAutofillProvider::CancelSession() {
cached_data_ = std::nullopt;
has_used_cached_form_ = false;
was_bottom_sheet_just_shown_ = false;
was_shown_bottom_sheet_timer_.Stop();
bridge_->CancelSession();
}
SessionId AndroidAutofillProvider::CreateSessionId() {
last_session_id_ = last_session_id_ == kMaximumSessionId
? kMinimumSessionId
: SessionId(last_session_id_.value() + 1);
return last_session_id_;
}
bool AndroidAutofillProvider::ArePrefillRequestsSupported() const {
return base::android::BuildInfo::GetInstance()->sdk_int() >=
kMinimumSdkVersionForPrefillRequests;
}
void AndroidAutofillProvider::MaybeSendPrefillRequest(
const AndroidAutofillManager& manager,
FormGlobalId form_id) {
if (!ArePrefillRequestsSupported()) {
return;
}
// Return if there has already been a cache request or if there is already
// an ongoing Autofill session.
if (cached_data_ || form_) {
return;
}
const FormStructure* const form_structure =
manager.FindCachedFormById(form_id);
if (!form_structure) {
return;
}
std::unique_ptr<PasswordForm> pw_form = ParseToPasswordForm(*form_structure);
if (!pw_form || !ShouldCachePasswordForm(*pw_form)) {
return;
}
cached_data_.emplace();
cached_data_->prefill_request_creation_time = base::TimeTicks::Now();
cached_data_->cached_form = std::make_unique<FormDataAndroid>(
form_structure->ToFormData(), CreateSessionId());
cached_data_->cached_form->UpdateFieldTypes(*form_structure);
if (std::optional<PasswordParserOverrides> overrides =
PasswordParserOverrides::FromPasswordForm(*pw_form,
*form_structure)) {
// If we manage to match the fields that the password form parser
// identified as username and password fields, override their types.
cached_data_->password_parser_overrides = *std::move(overrides);
cached_data_->cached_form->UpdateFieldTypes(
cached_data_->password_parser_overrides.ToFieldTypeMap());
}
bridge_->SendPrefillRequest(*cached_data_->cached_form);
}
base::flat_map<FieldGlobalId, AutofillType>
AndroidAutofillProvider::PasswordParserOverrides::ToFieldTypeMap() const {
base::flat_map<FieldGlobalId, AutofillType> result;
if (username_field_id) {
result.emplace(*username_field_id, FieldType::USERNAME);
}
if (password_field_id) {
result.emplace(*password_field_id, FieldType::PASSWORD);
}
if (new_password_field_id) {
result.emplace(*new_password_field_id, FieldType::NEW_PASSWORD);
}
return result;
}
// static
std::optional<AndroidAutofillProvider::PasswordParserOverrides>
AndroidAutofillProvider::PasswordParserOverrides::FromPasswordForm(
const PasswordForm& pw_form,
const FormStructure& form_structure) {
PasswordParserOverrides result;
for (const std::unique_ptr<AutofillField>& field : form_structure) {
if (field->renderer_id() == pw_form.username_element_renderer_id) {
if (result.username_field_id) {
return std::nullopt;
}
result.username_field_id = field->global_id();
} else if (field->renderer_id() == pw_form.password_element_renderer_id) {
if (result.password_field_id) {
return std::nullopt;
}
result.password_field_id = field->global_id();
}
}
// Perform consistency checks to confirm that the lifting was successful.
if (pw_form.IsLikelyLoginForm() && result.username_field_id &&
result.password_field_id) {
return result;
}
if (pw_form.IsLikelyChangePasswordForm() && result.password_field_id &&
result.new_password_field_id &&
base::FeatureList::IsEnabled(
features::kAndroidAutofillPrefillRequestsForChangePassword)) {
return result;
}
return std::nullopt;
}
AndroidAutofillProvider::CachedData::CachedData() = default;
AndroidAutofillProvider::CachedData::CachedData(CachedData&&) = default;
AndroidAutofillProvider::CachedData&
AndroidAutofillProvider::CachedData::operator=(CachedData&&) = default;
AndroidAutofillProvider::CachedData::~CachedData() = default;
void AndroidAutofillProvider::OnCredManUiClosed(bool success) {
credman_sheet_status_ = CredManBottomSheetLifecycle::kClosed;
if (keyboard_suppressor_) {
keyboard_suppressor_->Unsuppress();
}
// TODO: crbug.com/332471454 - Open the keyboard on failure.
}
} // namespace autofill