| From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 |
| From: fgei <fgei@gmail.com> |
| Date: Mon, 20 Feb 2023 07:10:55 +0000 |
| Subject: [PATCH] Support native Android autofill at browser |
| |
| This enables support for Android Autofil on tabs showing fillable |
| entries, reusing the codebase used for webview's android autofill |
| support. |
| --- |
| chrome/android/BUILD.gn | 1 + |
| .../chromium/chrome/browser/tab/TabImpl.java | 45 +++++++++++++++++ |
| .../browser/tab/TabViewAndroidDelegate.java | 13 +++++ |
| chrome/browser/BUILD.gn | 7 +++ |
| .../ui/autofill/chrome_autofill_client.cc | 4 ++ |
| .../browser/android_autofill_client.cc | 4 ++ |
| .../embedder_support/view/ContentView.java | 48 +++++++++++++++++++ |
| .../chromium/ui/base/ViewAndroidDelegate.java | 8 ++++ |
| 8 files changed, 130 insertions(+) |
| |
| diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn |
| index fec47e97f8fe6..bd79fac82ab09 100644 |
| --- a/chrome/android/BUILD.gn |
| +++ b/chrome/android/BUILD.gn |
| @@ -472,6 +472,7 @@ if (current_toolchain == default_toolchain) { |
| "//chrome/browser/xsurface:java", |
| "//chrome/browser/xsurface_provider:dependency_provider_impl_java", |
| "//chrome/browser/xsurface_provider:java", |
| + "//components/android_autofill/browser:java", |
| "//components/autofill/android:autofill_java", |
| "//components/background_task_scheduler:background_task_scheduler_java", |
| "//components/background_task_scheduler:background_task_scheduler_task_ids_java", |
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java |
| index 4fca840ad3a78..88cfd11aa8045 100644 |
| --- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java |
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java |
| @@ -9,10 +9,14 @@ import android.app.Activity; |
| import android.content.Context; |
| import android.graphics.Rect; |
| import android.net.Uri; |
| +import android.os.Build; |
| import android.text.TextUtils; |
| +import android.util.SparseArray; |
| import android.view.View; |
| import android.view.View.OnAttachStateChangeListener; |
| +import android.view.ViewStructure; |
| import android.view.accessibility.AccessibilityEvent; |
| +import android.view.autofill.AutofillValue; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| @@ -54,6 +58,8 @@ import org.chromium.chrome.browser.tab.Tab.LoadUrlResult; |
| import org.chromium.chrome.browser.tab.TabUtils.UseDesktopUserAgentCaller; |
| import org.chromium.chrome.browser.ui.native_page.FrozenNativePage; |
| import org.chromium.chrome.browser.ui.native_page.NativePage; |
| +import org.chromium.components.autofill.AutofillProvider; |
| +// import org.chromium.components.autofill.AutofillSelectionMenuItemHelper; |
| import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils; |
| import org.chromium.components.embedder_support.util.UrlConstants; |
| import org.chromium.components.embedder_support.view.ContentView; |
| @@ -65,9 +71,11 @@ import org.chromium.content_public.browser.ContentFeatureList; |
| import org.chromium.content_public.browser.ContentFeatureMap; |
| import org.chromium.content_public.browser.LoadUrlParams; |
| import org.chromium.content_public.browser.NavigationHandle; |
| +import org.chromium.content_public.browser.SelectionPopupController; |
| import org.chromium.content_public.browser.WebContents; |
| import org.chromium.content_public.browser.WebContentsAccessibility; |
| import org.chromium.content_public.browser.navigation_controller.UserAgentOverrideOption; |
| +import org.chromium.ui.base.EventOffsetHandler; |
| import org.chromium.ui.base.PageTransition; |
| import org.chromium.ui.base.ViewAndroidDelegate; |
| import org.chromium.ui.base.WindowAndroid; |
| @@ -203,6 +211,7 @@ class TabImpl implements Tab { |
| private int mRootId; |
| private @Nullable Token mTabGroupId; |
| private @TabUserAgent int mUserAgent = TabUserAgent.DEFAULT; |
| + AutofillProvider mAutofillProvider; |
| |
| /** |
| * Navigation state of the WebContents as returned by nativeGetContentsStateAsByteBuffer(), |
| @@ -271,12 +280,18 @@ class TabImpl implements Tab { |
| public void onViewAttachedToWindow(View view) { |
| mIsViewAttachedToWindow = true; |
| updateInteractableState(); |
| + if (mAutofillProvider != null) { |
| + mAutofillProvider.onContainerViewChanged(mContentView); |
| + } |
| } |
| |
| @Override |
| public void onViewDetachedFromWindow(View view) { |
| mIsViewAttachedToWindow = false; |
| updateInteractableState(); |
| + if (mAutofillProvider != null) { |
| + mAutofillProvider.onContainerViewChanged(mContentView); |
| + } |
| } |
| }; |
| mTabViewManager = new TabViewManagerImpl(this); |
| @@ -857,6 +872,11 @@ class TabImpl implements Tab { |
| for (TabObserver observer : mObservers) observer.onDestroyed(this); |
| mObservers.clear(); |
| |
| + if (mAutofillProvider != null) { |
| + mAutofillProvider.destroy(); |
| + mAutofillProvider = null; |
| + } |
| + |
| mUserDataHost.destroy(); |
| mTabViewManager.destroy(); |
| hideNativePage(false, null); |
| @@ -1454,6 +1474,18 @@ class TabImpl implements Tab { |
| return mWebContentsState == null ? -1 : mWebContentsState.version(); |
| } |
| |
| + public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) { |
| + if (mAutofillProvider != null) { |
| + mAutofillProvider.onProvideAutoFillVirtualStructure(structure, flags); |
| + } |
| + } |
| + |
| + public void autofill(final SparseArray<AutofillValue> values) { |
| + if (mAutofillProvider != null) { |
| + mAutofillProvider.autofill(values); |
| + } |
| + } |
| + |
| /** |
| * Initializes the {@link WebContents}. Completes the browser content components initialization |
| * around a native WebContents pointer. |
| @@ -1509,6 +1541,12 @@ class TabImpl implements Tab { |
| boolean isBackgroundTab = isDetached(); |
| |
| assert mNativeTabAndroid != 0; |
| + SelectionPopupController selectionController = null; |
| + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
| + selectionController = SelectionPopupController.fromWebContents(mWebContents); |
| + mAutofillProvider = new AutofillProvider( |
| + getContext(), cv, webContents, "NativeAutofillRenderer"); |
| + } |
| TabImplJni.get() |
| .initWebContents( |
| mNativeTabAndroid, |
| @@ -1519,6 +1557,13 @@ class TabImpl implements Tab { |
| new TabContextMenuPopulatorFactory( |
| mDelegateFactory.createContextMenuPopulatorFactory(this), |
| this)); |
| + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && selectionController != null) { |
| + mAutofillProvider.setWebContents(webContents); |
| + cv.setWebContents(webContents); |
| + // selectionController.setNonSelectionAdditionalMenuItemHelper( |
| + // new AutofillSelectionMenuItemHelper( |
| + // mThemedApplicationContext, mAutofillProvider)); |
| + } |
| |
| mWebContents.notifyRendererPreferenceUpdate(); |
| TabHelpers.initWebContentsHelpers(this); |
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabViewAndroidDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabViewAndroidDelegate.java |
| index d5e0e3e9237ed..9af9e3dedbaf7 100644 |
| --- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabViewAndroidDelegate.java |
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabViewAndroidDelegate.java |
| @@ -4,7 +4,10 @@ |
| |
| package org.chromium.chrome.browser.tab; |
| |
| +import android.util.SparseArray; |
| import android.view.ViewGroup; |
| +import android.view.ViewStructure; |
| +import android.view.autofill.AutofillValue; |
| |
| import androidx.annotation.Nullable; |
| |
| @@ -83,6 +86,16 @@ public class TabViewAndroidDelegate extends ViewAndroidDelegate { |
| mTab.onBackgroundColorChanged(color); |
| } |
| |
| + @Override |
| + public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) { |
| + mTab.onProvideAutofillVirtualStructure(structure, flags); |
| + } |
| + |
| + @Override |
| + public void autofill(final SparseArray<AutofillValue> values) { |
| + mTab.autofill(values); |
| + } |
| + |
| @Override |
| public void onTopControlsChanged( |
| int topControlsOffsetY, int contentOffsetY, int topControlsMinHeightOffsetY) { |
| diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn |
| index e9c28ab05bd01..d8685ce0ef18c 100644 |
| --- a/chrome/browser/BUILD.gn |
| +++ b/chrome/browser/BUILD.gn |
| @@ -2660,6 +2660,13 @@ static_library("browser") { |
| deps += [ "//chrome/browser/error_reporting" ] |
| } |
| |
| + if (is_android) { |
| + deps += [ |
| + "//components/android_autofill/browser", |
| + "//components/android_autofill/browser:android" |
| + ] |
| + } |
| + |
| if (use_ozone) { |
| deps += [ |
| "//ui/events/ozone", |
| diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc |
| index c270bf816f25d..4e592b70f7ba7 100644 |
| --- a/chrome/browser/ui/autofill/chrome_autofill_client.cc |
| +++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc |
| @@ -245,12 +245,16 @@ ChromeAutofillClient::GetURLLoaderFactory() { |
| } |
| |
| AutofillCrowdsourcingManager* ChromeAutofillClient::GetCrowdsourcingManager() { |
| +#if defined(USE_BROWSER_AUTOFILL_ONLY) |
| if (!crowdsourcing_manager_) { |
| // Lazy initialization to avoid virtual function calls in the constructor. |
| crowdsourcing_manager_ = std::make_unique<AutofillCrowdsourcingManager>( |
| this, GetChannel(), GetLogManager()); |
| } |
| return crowdsourcing_manager_.get(); |
| +#else |
| + return nullptr; |
| +#endif // defined(USE_BROWSER_AUTOFILL_ONLY) |
| } |
| |
| AutofillOptimizationGuide* ChromeAutofillClient::GetAutofillOptimizationGuide() |
| diff --git a/components/android_autofill/browser/android_autofill_client.cc b/components/android_autofill/browser/android_autofill_client.cc |
| index 55ccc058fd391..8247c5b9faf3c 100644 |
| --- a/components/android_autofill/browser/android_autofill_client.cc |
| +++ b/components/android_autofill/browser/android_autofill_client.cc |
| @@ -73,6 +73,7 @@ AndroidAutofillClient::GetURLLoaderFactory() { |
| |
| autofill::AutofillCrowdsourcingManager* |
| AndroidAutofillClient::GetCrowdsourcingManager() { |
| +#if defined(USE_BROWSER_AUTOFILL_ONLY) |
| if (autofill::AutofillProvider:: |
| is_crowdsourcing_manager_disabled_for_testing()) { |
| return nullptr; |
| @@ -84,6 +85,9 @@ AndroidAutofillClient::GetCrowdsourcingManager() { |
| this, GetChannel(), GetLogManager()); |
| } |
| return crowdsourcing_manager_.get(); |
| +#else |
| + return nullptr; |
| +#endif // defined(USE_BROWSER_AUTOFILL_ONLY) |
| } |
| |
| autofill::PersonalDataManager* AndroidAutofillClient::GetPersonalDataManager() { |
| diff --git a/components/embedder_support/android/java/src/org/chromium/components/embedder_support/view/ContentView.java b/components/embedder_support/android/java/src/org/chromium/components/embedder_support/view/ContentView.java |
| index abe1ac2b7907d..9cf691982660d 100644 |
| --- a/components/embedder_support/android/java/src/org/chromium/components/embedder_support/view/ContentView.java |
| +++ b/components/embedder_support/android/java/src/org/chromium/components/embedder_support/view/ContentView.java |
| @@ -9,6 +9,7 @@ import android.content.res.Configuration; |
| import android.graphics.Rect; |
| import android.os.Build; |
| import android.os.Handler; |
| +import android.util.SparseArray; |
| import android.view.DragEvent; |
| import android.view.KeyEvent; |
| import android.view.MotionEvent; |
| @@ -19,6 +20,7 @@ import android.view.View.OnSystemUiVisibilityChangeListener; |
| import android.view.ViewGroup.OnHierarchyChangeListener; |
| import android.view.ViewStructure; |
| import android.view.accessibility.AccessibilityNodeProvider; |
| +import android.view.autofill.AutofillValue; |
| import android.view.inputmethod.EditorInfo; |
| import android.view.inputmethod.InputConnection; |
| import android.widget.FrameLayout; |
| @@ -39,6 +41,7 @@ import org.chromium.ui.accessibility.AccessibilityState; |
| import org.chromium.ui.base.EventForwarder; |
| import org.chromium.ui.base.EventOffsetHandler; |
| import org.chromium.ui.dragdrop.DragEventDispatchHelper.DragEventDispatchDestination; |
| +import org.chromium.ui.base.ViewAndroidDelegate; |
| |
| import java.util.function.Supplier; |
| |
| @@ -96,6 +99,9 @@ public class ContentView extends FrameLayout |
| Context context, |
| @Nullable EventOffsetHandler eventOffsetHandler, |
| @Nullable WebContents webContents) { |
| + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
| + return new ContentViewWithAutofill(context, eventOffsetHandler, webContents); |
| + } |
| return new ContentView(context, eventOffsetHandler, webContents); |
| } |
| |
| @@ -642,4 +648,46 @@ public class ContentView extends FrameLayout |
| mDragDropEventOffsetHandler.onPostDispatchDragEvent(event.getAction()); |
| return ret; |
| } |
| + |
| + /** |
| + * API level 26 implementation that includes autofill. |
| + */ |
| + private static class ContentViewWithAutofill extends ContentView { |
| + private ViewAndroidDelegate viewAndroidDelegate; |
| + |
| + private ContentViewWithAutofill( |
| + Context context, EventOffsetHandler eventOffsetHandler, WebContents webContents) { |
| + super(context, eventOffsetHandler, webContents); |
| + |
| + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
| + // The Autofill system-level infrastructure has heuristics for which Views it |
| + // considers important for autofill; only these Views will be queried for their |
| + // autofill structure on notifications that a new (virtual) View was entered. By |
| + // default, FrameLayout is not considered important for autofill. Thus, for |
| + // ContentView to be queried for its autofill structure, we must explicitly inform |
| + // the autofill system that this View is important for autofill. |
| + setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_YES); |
| + } |
| + } |
| + |
| + @Override |
| + public void setWebContents(WebContents webContents) { |
| + viewAndroidDelegate = webContents.getViewAndroidDelegate(); |
| + super.setWebContents(webContents); |
| + } |
| + |
| + @Override |
| + public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) { |
| + if (viewAndroidDelegate != null) { |
| + viewAndroidDelegate.onProvideAutofillVirtualStructure(structure, flags); |
| + } |
| + } |
| + |
| + @Override |
| + public void autofill(final SparseArray<AutofillValue> values) { |
| + if (viewAndroidDelegate != null) { |
| + viewAndroidDelegate.autofill(values); |
| + } |
| + } |
| + } |
| } |
| diff --git a/ui/android/java/src/org/chromium/ui/base/ViewAndroidDelegate.java b/ui/android/java/src/org/chromium/ui/base/ViewAndroidDelegate.java |
| index 99b29f07becf9..25c744ce5d74e 100644 |
| --- a/ui/android/java/src/org/chromium/ui/base/ViewAndroidDelegate.java |
| +++ b/ui/android/java/src/org/chromium/ui/base/ViewAndroidDelegate.java |
| @@ -32,6 +32,10 @@ import org.chromium.ui.dragdrop.DragStateTracker; |
| import org.chromium.ui.dragdrop.DropDataAndroid; |
| import org.chromium.ui.mojom.CursorType; |
| |
| +import android.util.SparseArray; |
| +import android.view.autofill.AutofillValue; |
| +import android.view.ViewStructure; |
| + |
| /** Class to acquire, position, and remove anchor views from the implementing View. */ |
| @JNINamespace("ui") |
| public class ViewAndroidDelegate { |
| @@ -573,4 +577,8 @@ public class ViewAndroidDelegate { |
| sDragAndDropDelegateForTesting = testDelegate; |
| ResettersForTesting.register(() -> sDragAndDropDelegateForTesting = null); |
| } |
| + |
| + public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {} |
| + |
| + public void autofill(final SparseArray<AutofillValue> values) {} |
| } |