chromium/components/android_autofill/browser/android_autofill_provider.h

// 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.

#ifndef COMPONENTS_ANDROID_AUTOFILL_BROWSER_ANDROID_AUTOFILL_PROVIDER_H_
#define COMPONENTS_ANDROID_AUTOFILL_BROWSER_ANDROID_AUTOFILL_PROVIDER_H_

#include "base/memory/weak_ptr.h"
#include "base/timer/timer.h"
#include "components/android_autofill/browser/android_autofill_provider_bridge.h"
#include "components/android_autofill/browser/autofill_provider.h"
#include "components/android_autofill/browser/form_data_android.h"
#include "components/autofill/core/browser/autofill_manager.h"
#include "components/autofill/core/common/unique_ids.h"
#include "components/webauthn/android/webauthn_cred_man_delegate.h"
#include "content/public/browser/web_contents_observer.h"

namespace content {
class WebContents;
}  // namespace content

namespace password_manager {
struct PasswordForm;
}  // namespace password_manager

namespace autofill {

class TouchToFillKeyboardSuppressor;

// Android implementation of AutofillProvider, it has one instance per
// WebContents, this class is native peer of AutofillProvider.java.
// This class is always instantialized by AutofillProvider Java object.
class AndroidAutofillProvider : public AutofillProvider,
                                public AndroidAutofillProviderBridge::Delegate,
                                public content::WebContentsObserver {
 public:
  // Used as a metric that describes the state of a prefill requests. It is
  // emitted for every form deemed suitable for prefill requests prefill
  // requests are supported on this Android version (U+).
  enum class PrefillRequestState {
    // A prefill request was sent, a view structure was requested and provided
    // and a bottom sheet was shown.
    kRequestSentStructureProvidedBottomSheetShown = 0,
    // A prefill request was sent and a view structure was requested and
    // provided, but no bottom sheet was shown. The reason for not showing the
    // bottom sheet is opaque to WebView (e.g., no suggestions available by
    // providers, keyboard suppression not working correctly, etc.)
    kRequestSentStructureProvidedBottomSheetNotShown = 1,
    // A prefill request was sent, but no view structure was requested by the
    // framework.
    kRequestSentStructureNotProvided = 2,
    // A prefill request was sent, but the form changed substantially between
    // sending the cache request and the focus event on the form.
    kRequestSentFormChanged = 3,
    // A prefill request was not sent because the maximum number of prefill
    // requests (currently 1 per session) had already been reached.
    kRequestNotSentMaxNumberReached = 4,
    // A prefill request was not sent because there was no time - the form was
    // only analyzed to be cacheable when a session had already been started.
    kRequestNotSentNoTime = 5,
    kMaxValue = kRequestNotSentNoTime
  };

  static constexpr char kPrefillRequestStateUma[] =
      "Autofill.WebView.PrefillRequestState";
  // The name of the UMA that is emitted when a form similarity check between a
  // cached form and the interacted form fails.
  static constexpr char kPrefillRequestBottomsheetNoViewStructureDelayUma[] =
      "Autofill.WebView.BottomsheetNoViewStructureDelay";

  static void CreateForWebContents(content::WebContents* web_contents);

  static AndroidAutofillProvider* FromWebContents(
      content::WebContents* web_contents);

  AndroidAutofillProvider(const AndroidAutofillProvider&) = delete;
  AndroidAutofillProvider& operator=(const AndroidAutofillProvider&) = delete;
  ~AndroidAutofillProvider() override;

  // Attach this detached object to `jcaller`.
  void AttachToJavaAutofillProvider(
      JNIEnv* env,
      const base::android::JavaRef<jobject>& jcaller);

  // AutofillProvider:
  void OnAskForValuesToFill(
      AndroidAutofillManager* manager,
      const FormData& form,
      const FormFieldData& field,
      AutofillSuggestionTriggerSource /*unused_trigger_source*/) override;
  void OnTextFieldDidChange(AndroidAutofillManager* manager,
                            const FormData& form,
                            const FormFieldData& field,
                            const base::TimeTicks timestamp) override;
  void OnTextFieldDidScroll(AndroidAutofillManager* manager,
                            const FormData& form,
                            const FormFieldData& field) override;
  void OnSelectControlDidChange(AndroidAutofillManager* manager,
                                const FormData& form,
                                const FormFieldData& field) override;
  void OnFormSubmitted(AndroidAutofillManager* manager,
                       const FormData& form,
                       bool known_success,
                       mojom::SubmissionSource source) override;
  void OnFocusOnNonFormField(AndroidAutofillManager* manager) override;
  void OnFocusOnFormField(AndroidAutofillManager* manager,
                          const FormData& form,
                          const FormFieldData& field) override;
  void OnDidFillAutofillFormData(AndroidAutofillManager* manager,
                                 const FormData& form,
                                 base::TimeTicks timestamp) override;
  void OnHidePopup(AndroidAutofillManager* manager) override;
  void OnServerPredictionsAvailable(AndroidAutofillManager& manager,
                                    FormGlobalId form_id) override;

  void OnManagerResetOrDestroyed(AndroidAutofillManager* manager) override;

  bool GetCachedIsAutofilled(const FormFieldData& field) const override;

  void MaybeInitKeyboardSuppressor() override;

 private:
  friend class AndroidAutofillProviderTestApi;

  explicit AndroidAutofillProvider(content::WebContents* web_contents);

  // AndroidAutofillProviderBridge::Delegate:
  void OnAutofillAvailable() override;
  void OnAcceptDatalistSuggestion(const std::u16string& value) override;
  void SetAnchorViewRect(const base::android::JavaRef<jobject>& anchor,
                         const gfx::RectF& bounds) override;
  void OnShowBottomSheetResult(bool is_shown,
                               bool provided_autofill_structure) override;

  // content::WebContentsObserver:
  void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
  void DidFinishNavigation(
      content::NavigationHandle* navigation_handle) override;
  void OnVisibilityChanged(content::Visibility visibility) override;

  // Returns true the first time it is called after a bottom sheet was shown.
  // This is intended to be used only by the keyboard suppressor, which calls it
  // once in OnAfterAskForValuesToFill to determine whether it should continue
  // to suppress the keyboard. Ideally, this would return whether the bottom
  // sheet is currently showing, but Android does not expose that information.
  bool WasBottomSheetJustShown(AutofillManager& manager);

  // Returns true if there's possibility for the bottom sheet to show, false
  // otherwise.
  bool IntendsToShowBottomSheet(AutofillManager& manager,
                                FormGlobalId form,
                                FieldGlobalId field,
                                const FormData& form_data) const;

  void FireSuccessfulSubmission(mojom::SubmissionSource source);

  // Calls `OnFormFieldDidChange` in the bridge if there is an ongoing Autofill
  // session for this `form`.
  void MaybeFireFormFieldDidChange(AndroidAutofillManager* manager,
                                   const FormData& form,
                                   const FormFieldData& field);

  // Propagates visibility changes for fields in `form` and notifies the bridge
  // in case any of the fields had a visibility change.
  void MaybeFireFormFieldVisibilitiesDidChange(AndroidAutofillManager* manager,
                                             const FormData& form);

  bool IsLinkedManager(AndroidAutofillManager* manager) const;

  // Checks whether a `form_` is linked and whether it's the same form as
  // `form`, having same identifier.
  bool IsIdOfLinkedForm(FormGlobalId form_id) const;

  // Same as `IsLinkedForm`, but also checks that `form` and `form_` are
  // similar, using form similarity checks.
  bool IsLinkedForm(const FormData& form);

  gfx::RectF ToClientAreaBound(const gfx::RectF& bounding_box);

  void StartNewSession(AndroidAutofillManager* manager,
                       const FormData& form,
                       const FormFieldData& field);

  void Reset();

  // Cancels the current Autofill session, resetting cached session data.
  void CancelSession();

  // Returns a new session id. Session ids are required when creating a
  // `FormDataAndroid` object and used to generate virtual ids that identify
  // form fields uniquely to the Android Autofill framework.
  SessionId CreateSessionId();

  // Returns whether prefill requests are supported. This depends on the
  // Android version.
  bool ArePrefillRequestsSupported() const;

  // Sends a prefill request to the Autofill framework if all the below
  // conditions are met:
  // 1. Prefill requests are supported (correct SDK version & feature flag).
  // 2. No prefill request has been sent so far, since the framework only
  //    supports caching a single form at a time.
  // 3. There is no ongoing Autofill session. This is to ensure that the
  //    `onProvideAutofillStructure` callback from the framework does not
  //     confuse information requests for caching and for the current Autofill
  //     session.
  // 4. The form is predicted to be a login form or a (assuming that
  //    `kAndroidAutofillPrefillRequestsForChangePassword` is enabled) a change
  //     password form.
  void MaybeSendPrefillRequest(const AndroidAutofillManager& manager,
                               FormGlobalId form_id);

  // Stores field ids for fields detected by `password_manager::FormDataParser`
  // as username or password fields. Currently used only for prefill requests.
  struct PasswordParserOverrides {
    bool operator==(const PasswordParserOverrides&) const = default;

    // Returns the `PasswordParserOverrides` obtained from matching the
    // `FieldRendererId`s of username and password fields in `pw_form` to the
    // `FieldGlobalId`s in `form_structure`. Returns `std::nullopt` if no unique
    // matching could be found or if the matching is incomplete. A unique
    // matching may not exist if the form is spread across multiple iframes. In
    // practice, this should be extremely rare for password forms.
    static std::optional<PasswordParserOverrides> FromPasswordForm(
        const password_manager::PasswordForm& pw_form,
        const FormStructure& form_structure);

    // Creates a map as expected by `FormDataAndroid::UpdateFieldTypes`.
    base::flat_map<FieldGlobalId, AutofillType> ToFieldTypeMap() const;

    std::optional<FieldGlobalId> username_field_id;
    std::optional<FieldGlobalId> password_field_id;
    std::optional<FieldGlobalId> new_password_field_id;
  };

  // Checks whether `form` is similar to the cached form. `form_structure` must
  // be the `form_structure` corresponding to `form` if it is available (i.e.
  // cached by the AutofillManager already). The check works as follows:
  // - If `form_structure` is not null and
  //   `kAndroidAutofillSignatureForPrefillRequestSimilarityCheck` is enabled,
  //   the form is parsed using `password_manager::FormDataParser`. The form
  //   is classified as similar if the fields classified as username and
  //   passwords match between the cached and the focused form.
  // - Alternatively, it returns the result of `FormDataAndroid::SimilarFormAs`.
  bool IsFormSimilarToCachedForm(const FormData& form,
                                 const FormStructure* form_structure) const;

  // In some cases we get two AskForValuesToFill events within short time frame
  // so we set timer to set the `was_bottom_sheet_just_shown_` to false after it
  // gets accessed.
  // TODO(crbug.com/40284788): Remove once a fix is landed on the renderer side.
  void SetBottomSheetShownOff();

  // Stops the keyboard suppression. Called when the CredMan UI was closed. If
  // the UI was dismissed without selecting a passkey, `success` will be false.
  void OnCredManUiClosed(bool success);

  // Returns true if CredMan *may* be shown for the given field. It only returns
  // false if the sheet was already shown or prefetching concluded and indicated
  // that no passkeys are available.
  bool IntendsToShowCredMan(content::RenderFrameHost* rfh) const;

  // Returns true if a passkey request is pending  or succeeded for the given
  // `rfh` and the CredMan UI should be shown when the given `field` is focused.
  bool ShouldShowCredManForField(const FormFieldData& field,
                                 content::RenderFrameHost* rfh);

  // Triggers a prefetched passkey request which opens a bottom sheet.
  void ShowCredManSheet(content::RenderFrameHost* rfh);

  enum class CredManBottomSheetLifecycle {
    kNotShown,   // The sheet hasn't been shown. Does not indicate it will be.
    kIsShowing,  // The sheet was triggered. Does not guarantee it's visible.
    kClosed,     // The sheet was dismissed and shouldn't be shown again.
  };

  CredManBottomSheetLifecycle credman_sheet_status_ =
      CredManBottomSheetLifecycle::kNotShown;

  // This is used by the keyboard suppressor. We update it with the result of
  // the platform method call `showAutofillDialog`. Since we are not notified
  // when the bottom sheet is dismissed, we set a timer to set it to `false`
  // shortly after `WasBottomSheetJustShown()` is called. The timer's function
  // is to handle multiple calls related to the same user event correctly, which
  // can currently happen (crbug.com/1490581).
  bool was_bottom_sheet_just_shown_ = false;

  // Sets `was_bottom_sheet_just_shown` to false after a timeout.
  base::OneShotTimer was_shown_bottom_sheet_timer_;

  // Helper struct that contains cache data used in prefill requests.
  struct CachedData {
    CachedData();
    CachedData(CachedData&&);
    CachedData& operator=(CachedData&&);
    ~CachedData();

    std::unique_ptr<FormDataAndroid> cached_form;
    PasswordParserOverrides password_parser_overrides;
    // The time when the prefill request was sent - used for metrics only.
    base::TimeTicks prefill_request_creation_time;
  };
  std::optional<CachedData> cached_data_;

  // Indicates whether we have used the cached form to show a bottom sheet. This
  // state is kept because a bottom sheet should only be shown once per cached
  // form to allow the user to access the keyboard after focusing on the
  // (cached) form a second time.
  bool has_used_cached_form_ = false;

  // The form of the current session (queried input or changed select box).
  std::unique_ptr<FormDataAndroid> form_;

  // Properties of the last-focused field of the current session for `form_`
  // (queried input or changed select box).
  struct {
    FieldGlobalId id;
    FieldTypeGroup group = {FieldTypeGroup::kNoGroup};
    url::Origin origin;
  } current_field_;

  // The frame of the field for which the last OnAskForValuesToFill() happened.
  //
  // It is not necessarily the same frame as the current session's
  // `last_focused_field_id_.host_frame` because the session may survive
  // OnAskForValuesToFill().
  //
  // It's not necessarily the same frame as `manager_`'s for the same reason as
  // `last_focused_field_id_`, and also because `manager_` may refer to an
  // ancestor frame of the queried field.
  content::GlobalRenderFrameHostId last_queried_field_rfh_id_;

  base::WeakPtr<AndroidAutofillManager> manager_;
  bool check_submission_ = false;
  // Valid only if check_submission_ is true.
  mojom::SubmissionSource pending_submission_source_;

  static constexpr SessionId kMinimumSessionId = SessionId(1);
  static constexpr SessionId kMaximumSessionId = SessionId(0xffff);
  // The last assigned session id.
  SessionId last_session_id_ = kMaximumSessionId;

  // The bridge for C++ <-> Java communication.
  std::unique_ptr<AndroidAutofillProviderBridge> bridge_;

  // Used for handling keyboard suppression in case there's a bottom sheet.
  std::unique_ptr<TouchToFillKeyboardSuppressor> keyboard_suppressor_;

  base::WeakPtrFactory<AndroidAutofillProvider> weak_ptr_factory_{this};
};
}  // namespace autofill

#endif  // COMPONENTS_ANDROID_AUTOFILL_BROWSER_ANDROID_AUTOFILL_PROVIDER_H_