From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: fgei 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. --- android_webview/browser/aw_autofill_client.cc | 4 ++ 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 | 14 +++++- .../embedder_support/view/ContentView.java | 48 +++++++++++++++++++ .../chromium/ui/base/ViewAndroidDelegate.java | 8 ++++ 8 files changed, 139 insertions(+), 1 deletion(-) diff --git a/android_webview/browser/aw_autofill_client.cc b/android_webview/browser/aw_autofill_client.cc index 449fe65d0bdda..6f0cf1553b07e 100644 --- a/android_webview/browser/aw_autofill_client.cc +++ b/android_webview/browser/aw_autofill_client.cc @@ -86,6 +86,7 @@ AwAutofillClient::GetURLLoaderFactory() { } autofill::AutofillDownloadManager* AwAutofillClient::GetDownloadManager() { +#if defined(USE_BROWSER_AUTOFILL_ONLY) if (autofill::AutofillProvider::is_download_manager_disabled_for_testing()) { return nullptr; } @@ -95,6 +96,9 @@ autofill::AutofillDownloadManager* AwAutofillClient::GetDownloadManager() { this, GetChannel(), GetLogManager()); } return download_manager_.get(); +#else + return nullptr; +#endif // defined(USE_BROWSER_AUTOFILL_ONLY) } autofill::PersonalDataManager* AwAutofillClient::GetPersonalDataManager() { diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn index f56c41b506f45..06cd1e491c1d7 100644 --- a/chrome/android/BUILD.gn +++ b/chrome/android/BUILD.gn @@ -452,6 +452,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 d9f1d1008e623..18f23b198448e 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 @@ -10,10 +10,14 @@ import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.graphics.Rect; +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; @@ -52,6 +56,8 @@ import org.chromium.chrome.browser.tab.TabUtils.LoadIfNeededCaller; 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.AutofillSelectionMenuItemProvider; 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.ChildProcessImportance; 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.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; @@ -209,6 +217,7 @@ public class TabImpl implements Tab { private int mParentId = INVALID_TAB_ID; private int mRootId; private @TabUserAgent int mUserAgent = TabUserAgent.DEFAULT; + AutofillProvider mAutofillProvider; /** * Navigation state of the WebContents as returned by nativeGetContentsStateAsByteBuffer(), * stored to be inflated on demand using unfreezeContents(). If this is not null, there is no @@ -271,12 +280,18 @@ public 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); @@ -817,6 +832,11 @@ public 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); @@ -1395,6 +1415,18 @@ public 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 values) { + if (mAutofillProvider != null) { + mAutofillProvider.autofill(values); + } + } + /** * Initializes the {@link WebContents}. Completes the browser content components initialization * around a native WebContents pointer. @@ -1438,10 +1470,23 @@ public class TabImpl implements Tab { mWebContentsDelegate = createWebContentsDelegate(); 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, mIncognito, TabUtils.isDetached(this), webContents, mWebContentsDelegate, new TabContextMenuPopulatorFactory( mDelegateFactory.createContextMenuPopulatorFactory(this), this)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && selectionController != null) { + mAutofillProvider.setWebContents(webContents); + cv.setWebContents(webContents); + selectionController.setNonSelectionAdditionalMenuItemProvider( + new AutofillSelectionMenuItemProvider( + 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 16c12477bbcb2..a5c9b7fffe05e 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; @@ -86,6 +89,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 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 7cb8d3fc03da2..3a15d9e907b60 100644 --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn @@ -2651,6 +2651,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 9e4c108ec5bc5..afa1ade208467 100644 --- a/chrome/browser/ui/autofill/chrome_autofill_client.cc +++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc @@ -58,6 +58,9 @@ #include "chrome/browser/web_data_service_factory.h" #include "chrome/common/channel_info.h" #include "chrome/common/url_constants.h" +#if BUILDFLAG(IS_ANDROID) +#include "components/android_autofill/browser/android_autofill_manager.h" +#endif // BUILDFLAG(IS_ANDROID) #include "components/autofill/content/browser/autofill_log_router_factory.h" #include "components/autofill/content/browser/content_autofill_driver.h" #include "components/autofill/content/browser/content_autofill_driver_factory.h" @@ -215,12 +218,16 @@ ChromeAutofillClient::GetURLLoaderFactory() { } AutofillDownloadManager* ChromeAutofillClient::GetDownloadManager() { +#if defined(USE_BROWSER_AUTOFILL_ONLY) if (!download_manager_) { // Lazy initialization to avoid virtual function calls in the constructor. download_manager_ = std::make_unique( this, GetChannel(), GetLogManager()); } return download_manager_.get(); +#else + return nullptr; +#endif // defined(USE_BROWSER_AUTOFILL_ONLY) } AutofillOptimizationGuide* ChromeAutofillClient::GetAutofillOptimizationGuide() @@ -1323,7 +1330,12 @@ void ChromeAutofillClient::OnZoomChanged( ChromeAutofillClient::ChromeAutofillClient(content::WebContents* web_contents) : ContentAutofillClient( web_contents, - base::BindRepeating(&BrowserDriverInitHook, + base::BindRepeating( +#if BUILDFLAG(IS_ANDROID) + &AndroidAndBrowserDriverInitHook, +#else + &BrowserDriverInitHook, +#endif // BUILDFLAG(IS_ANDROID) this, g_browser_process->GetApplicationLocale())), content::WebContentsObserver(web_contents), 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 aefde08a5b224..e08ce993314e2 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; @@ -38,6 +40,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; @@ -92,6 +95,9 @@ public class ContentView extends FrameLayout */ public static ContentView createContentView(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); } @@ -638,4 +644,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 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 1e1d4ea42cf90..595e6a703fff4 100644 --- a/ui/android/java/src/org/chromium/ui/base/ViewAndroidDelegate.java +++ b/ui/android/java/src/org/chromium/ui/base/ViewAndroidDelegate.java @@ -31,6 +31,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. */ @@ -578,4 +582,8 @@ public class ViewAndroidDelegate { sDragAndDropDelegateForTesting = testDelegate; ResettersForTesting.register(() -> sDragAndDropDelegateForTesting = null); } + + public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {} + + public void autofill(final SparseArray values) {} }