blob: d687b0eeb4587c5524fca36f45040bedced5cbd5 [file] [log] [blame]
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.
---
android_webview/browser/aw_autofill_client.cc | 4 ++
chrome/android/BUILD.gn | 1 +
.../chromium/chrome/browser/tab/TabImpl.java | 44 +++++++++++++++++
.../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, 138 insertions(+), 1 deletion(-)
diff --git a/android_webview/browser/aw_autofill_client.cc b/android_webview/browser/aw_autofill_client.cc
index e78b00768655b..4a76d9c0502c8 100644
--- a/android_webview/browser/aw_autofill_client.cc
+++ b/android_webview/browser/aw_autofill_client.cc
@@ -73,6 +73,7 @@ AwAutofillClient::GetURLLoaderFactory() {
autofill::AutofillCrowdsourcingManager*
AwAutofillClient::GetCrowdsourcingManager() {
+#if defined(USE_BROWSER_AUTOFILL_ONLY)
if (autofill::AutofillProvider::
is_crowdsourcing_manager_disabled_for_testing()) {
return nullptr;
@@ -84,6 +85,9 @@ AwAutofillClient::GetCrowdsourcingManager() {
this, GetChannel(), GetLogManager());
}
return crowdsourcing_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 dac7963df6224..b85c663c9a714 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -467,6 +467,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 f5f10dd2f13a1..97c29c85df08b 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;
@@ -51,6 +55,7 @@ import org.chromium.chrome.browser.rlz.RevenueStats;
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.dom_distiller.core.DomDistillerUrlUtils;
import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.components.embedder_support.view.ContentView;
@@ -64,9 +69,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;
@@ -200,6 +207,7 @@ 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(),
@@ -259,12 +267,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);
@@ -844,6 +858,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);
@@ -1424,6 +1443,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.
@@ -1479,6 +1510,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,
@@ -1489,6 +1526,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 afdf577837a49..d18e8c3ec8661 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -2662,6 +2662,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 4707fc2d949d2..e762cddf40665 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"
@@ -218,12 +221,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()
@@ -1362,7 +1369,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 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 0bd9924087048..b2b110c91c1c3 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 {
@@ -586,4 +590,8 @@ public class ViewAndroidDelegate {
sDragAndDropDelegateForTesting = testDelegate;
ResettersForTesting.register(() -> sDragAndDropDelegateForTesting = null);
}
+
+ public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {}
+
+ public void autofill(final SparseArray<AutofillValue> values) {}
}