| // 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/field_types.h" |
| #include "components/autofill/core/browser/foundations/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"; |
| |
| 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 OnTextFieldValueChanged(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 OnSelectControlSelectionChanged(AndroidAutofillManager* manager, |
| const FormData& form, |
| const FormFieldData& field) override; |
| void OnFormSubmitted(AndroidAutofillManager* manager, |
| const FormData& form, |
| mojom::SubmissionSource source) override; |
| void OnFocusOnNonFormField(AndroidAutofillManager* manager) override; |
| void OnFocusOnFormField(AndroidAutofillManager* manager, |
| const FormData& form, |
| const FormFieldData& field) override; |
| void OnDidAutofillForm(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; |
| bool HasPasskeyRequest() override; |
| void OnTriggerPasskeyRequest() 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); |
| |
| // Updates fields that changed on native class only. The Android bridge is not |
| // yet invoked to give preference to a possible CredMan flow. |
| // Using the given form and field that are newly focused, the method returns |
| // the necessary field information to continue the focus event later on. If |
| // continuing the focus event is not possible or necessary, it returns a |
| // `std::nullopt`. |
| std::optional<AndroidAutofillProviderBridge::FieldInfo> StartFocusChange( |
| const FormData& form, |
| const FormFieldData& field); |
| |
| // 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); |
| |
| 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) const; |
| |
| gfx::RectF ToClientAreaBound(const gfx::RectF& bounding_box) const; |
| |
| 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. |
| 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. 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> FromLoginForm( |
| const password_manager::PasswordForm& pw_form, |
| const FormStructure& form_structure); |
| |
| // Creates a map as expected by `FormDataAndroid::UpdateFieldTypes`. |
| base::flat_map<FieldGlobalId, FieldType> ToFieldTypeMap() const; |
| |
| std::optional<FieldGlobalId> username_field_id; |
| std::optional<FieldGlobalId> 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. |
| // If a `field_to_focus` is given and CredMan wasn't able to sign the |
| // user in, attempt to continue an earlier attempt to focus a field. |
| void OnCredManUiClosed( |
| FormGlobalId form_id, |
| std::optional<AndroidAutofillProviderBridge::FieldInfo> field_to_focus, |
| webauthn::WebAuthnCredManDelegate::State has_passkeys, |
| 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(const FormFieldData& field, |
| 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. The given |
| // `field_to_focus` is used to continue any interrupted focus event once |
| // CredMan closes. Returns true if the sheet was opened and false if that |
| // wasn't possible. |
| bool ShowCredManSheet( |
| content::RenderFrameHost* rfh, |
| FormGlobalId form_id, |
| std::optional<AndroidAutofillProviderBridge::FieldInfo> field_to_focus); |
| |
| 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. |
| }; |
| |
| // Groups all state that is specific to the current autofill session. |
| // A session starts when StartNewSession() is called and ends when Reset() is |
| // called. |
| struct SessionState { |
| SessionState(); |
| SessionState(SessionState&&); |
| SessionState& operator=(SessionState&&); |
| ~SessionState(); |
| |
| // The form of the current session (queried input or changed select box). |
| std::unique_ptr<FormDataAndroid> form; |
| |
| // The autofill manager for the current session. |
| base::WeakPtr<AndroidAutofillManager> manager; |
| |
| // Information about a field in an autofill session. |
| struct CurrentFieldInfo { |
| FieldGlobalId id; |
| FieldTypeGroup group = {FieldTypeGroup::kNoGroup}; |
| url::Origin origin; |
| }; |
| |
| // Properties of the last-focused field of the current session for `form` |
| // (queried input or changed select box). |
| CurrentFieldInfo 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; |
| }; |
| |
| // Current autofill session state. Empty when no session is active. |
| std::optional<SessionState> session_state_; |
| |
| // CredMan bottom sheet lifecycle state. This is independent of autofill |
| // sessions because passkey authentication can happen without autofill. |
| 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). |
| // This tracks bottom sheets from both autofill sessions AND prefill requests. |
| 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; |
| |
| 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_ |