From: uazo Date: Sun, 24 Oct 2021 16:54:04 +0000 Subject: Enable native Android autofill There are 2 different types of autofill: one managed via GCM and the native Android one that uses the provider assigned by the user (which can be any user installed app like Bitwarden for example). In chromium GCM is active while in the WebView the latter. This patch uses WebView code to enable native Android autofill along with browser-managed autofill. A separate toggle is introduced to enable autofill in incognito mode. See also: https://github.com/bromite/bromite/issues/547 Original License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html --- chrome/android/BUILD.gn | 1 + .../settings/PasswordSettings.java | 81 ++++++++++++++++++- .../chromium/chrome/browser/tab/TabImpl.java | 55 +++++++++++++ .../browser/tab/TabViewAndroidDelegate.java | 14 ++++ chrome/browser/BUILD.gn | 7 ++ chrome/browser/android/tab_android.cc | 15 ++++ chrome/browser/android/tab_android.h | 2 + .../strings/android_chrome_strings.grd | 6 ++ components/android_autofill/browser/BUILD.gn | 1 + .../browser/android_autofill_manager.cc | 2 + .../browser/android_autofill_manager.h | 2 + .../autofill/AutofillManagerWrapper.java | 5 +- .../components/autofill/AutofillProvider.java | 7 ++ .../BrowserSelectionActionMenuDelegate.java | 62 ++++++++++++++ .../browser/content_autofill_driver.cc | 57 +++++++++++++ .../content/browser/content_autofill_driver.h | 10 +++ .../content_autofill_driver_factory.cc | 13 +++ .../content/renderer/autofill_agent.cc | 6 ++ .../renderer/password_autofill_agent.cc | 5 +- .../autofill/core/browser/autofill_driver.h | 2 + .../autofill/core/browser/autofill_manager.h | 2 + .../core/browser/browser_autofill_manager.cc | 2 + .../core/browser/browser_autofill_manager.h | 2 + .../autofill/core/common/autofill_prefs.cc | 2 + .../autofill/core/common/autofill_prefs.h | 5 ++ .../embedder_support/view/ContentView.java | 46 +++++++++++ .../selection/FloatingPastePopupMenu.java | 3 +- .../chromium/ui/base/ViewAndroidDelegate.java | 8 ++ 28 files changed, 419 insertions(+), 4 deletions(-) create mode 100644 components/android_autofill/browser/java/src/org/chromium/components/autofill/BrowserSelectionActionMenuDelegate.java diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn --- a/chrome/android/BUILD.gn +++ b/chrome/android/BUILD.gn @@ -429,6 +429,7 @@ if (current_toolchain == default_toolchain) { "//chrome/browser/ui/android/hats:message_ui_delegate_java", "//chrome/browser/ui/android/layouts:java", "//chrome/browser/ui/android/layouts/glue:java", + "//components/android_autofill/browser:java", "//chrome/browser/ui/android/logo:java", "//chrome/browser/ui/android/management:java", "//chrome/browser/ui/android/multiwindow:java", diff --git a/chrome/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java b/chrome/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java --- a/chrome/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java @@ -59,6 +59,11 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Locale; +import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager; +import org.chromium.chrome.browser.ui.messages.snackbar.INeedSnackbarManager; +import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar; +import org.chromium.chrome.browser.ApplicationLifetime; + /** * The "Passwords" screen in Settings, which allows the user to enable or disable password saving, * to view saved passwords (just the username and URL), and to delete saved passwords. @@ -66,7 +71,7 @@ import java.util.Locale; public class PasswordSettings extends ChromeBaseSettingsFragment implements PasswordListObserver, Preference.OnPreferenceClickListener, - SyncService.SyncStateChangedListener { + SyncService.SyncStateChangedListener, INeedSnackbarManager { @IntDef({ TrustedVaultBannerState.NOT_SHOWN, TrustedVaultBannerState.OFFER_OPT_IN, @@ -96,6 +101,12 @@ public class PasswordSettings extends ChromeBaseSettingsFragment public static final String PREF_TRUSTED_VAULT_BANNER = "trusted_vault_banner"; public static final String PREF_KEY_MANAGE_ACCOUNT_LINK = "manage_account_link"; + public static final String PREF_ANDROID_AUTOFILL_SWITCH = "android_autofill_switch"; + public static final String PREF_ANDROID_AUTOFILL_INCOGNITO_SWITCH = "android_autofill_incognito_switch"; + + private SnackbarManager mSnackbarManager; + private Snackbar mSnackbar; + private static final String PREF_KEY_CATEGORY_SAVED_PASSWORDS = "saved_passwords"; private static final String PREF_KEY_CATEGORY_EXCEPTIONS = "exceptions"; private static final String PREF_KEY_SAVED_PASSWORDS_NO_TEXT = "saved_passwords_no_text"; @@ -128,6 +139,8 @@ public class PasswordSettings extends ChromeBaseSettingsFragment private Preference mLinkPref; private Menu mMenu; + private ChromeSwitchPreference mEnableAndroidAutofillSwitch; + private ChromeSwitchPreference mEnableAndroidAutofillIncognitoSwitch; private @ManagePasswordsReferrer int mManagePasswordsReferrer; private BottomSheetController mBottomSheetController; @@ -302,6 +315,7 @@ public class PasswordSettings extends ChromeBaseSettingsFragment } createSavePasswordsSwitch(); + createEnableAndroidAutofillSwitch(); if (shouldShowAutoSigninOption()) { createAutoSignInCheckbox(); } @@ -575,6 +589,71 @@ public class PasswordSettings extends ChromeBaseSettingsFragment getPrefService().getBoolean(Pref.CREDENTIALS_ENABLE_SERVICE)); } + private void createEnableAndroidAutofillSwitch() { + if (mSnackbar == null) { + mSnackbar = Snackbar.make(getActivity().getString(R.string.ui_relaunch_notice), + new SnackbarManager.SnackbarController() { + @Override + public void onDismissNoAction(Object actionData) { } + + @Override + public void onAction(Object actionData) { + ApplicationLifetime.terminate(true); + } + }, Snackbar.TYPE_NOTIFICATION, Snackbar.UMA_UNKNOWN) + .setSingleLine(false) + .setAction(getActivity().getString(R.string.relaunch), + /*actionData*/null) + .setDuration(/*durationMs*/70000); + } + + mEnableAndroidAutofillSwitch = new ChromeSwitchPreference(getStyledContext(), null); + mEnableAndroidAutofillSwitch.setKey(PREF_ANDROID_AUTOFILL_SWITCH); + mEnableAndroidAutofillSwitch.setTitle(R.string.enable_android_autofill); + mEnableAndroidAutofillSwitch.setOrder(ORDER_SWITCH); + mEnableAndroidAutofillSwitch.setSummaryOn(R.string.text_on); + mEnableAndroidAutofillSwitch.setSummaryOff(R.string.text_off); + + try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) { + getPreferenceScreen().addPreference(mEnableAndroidAutofillSwitch); + } + + mEnableAndroidAutofillSwitch.setChecked( + getPrefService().getBoolean(Pref.AUTOFILL_ANDROID_ENABLED)); + + mEnableAndroidAutofillSwitch.setOnPreferenceChangeListener((preference, newValue) -> { + getPrefService().setBoolean(Pref.AUTOFILL_ANDROID_ENABLED, (boolean) newValue); + if (!mSnackbarManager.isShowing()) + mSnackbarManager.showSnackbar(mSnackbar); + return true; + }); + + mEnableAndroidAutofillIncognitoSwitch = new ChromeSwitchPreference(getStyledContext(), null); + mEnableAndroidAutofillIncognitoSwitch.setKey(PREF_ANDROID_AUTOFILL_INCOGNITO_SWITCH); + mEnableAndroidAutofillIncognitoSwitch.setTitle(R.string.enable_android_autofill_incognito); + mEnableAndroidAutofillIncognitoSwitch.setOrder(ORDER_SWITCH); + mEnableAndroidAutofillIncognitoSwitch.setSummaryOn(R.string.text_on); + mEnableAndroidAutofillIncognitoSwitch.setSummaryOff(R.string.text_off); + + try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) { + getPreferenceScreen().addPreference(mEnableAndroidAutofillIncognitoSwitch); + } + + mEnableAndroidAutofillIncognitoSwitch.setChecked( + getPrefService().getBoolean(Pref.AUTOFILL_ANDROID_INCOGNITO_ENABLED)); + + mEnableAndroidAutofillIncognitoSwitch.setOnPreferenceChangeListener((preference, newValue) -> { + getPrefService().setBoolean(Pref.AUTOFILL_ANDROID_INCOGNITO_ENABLED, (boolean) newValue); + if (!mSnackbarManager.isShowing()) + mSnackbarManager.showSnackbar(mSnackbar); + return true; + }); + } + + public void setSnackbarManager(SnackbarManager manager) { + mSnackbarManager = manager; + } + private void createAutoSignInCheckbox() { ChromeSwitchPreference autoSignInSwitch = new ChromeSwitchPreference(getStyledContext(), null); 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 --- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java @@ -76,6 +76,18 @@ import org.chromium.url.GURL; import java.nio.ByteBuffer; import java.util.Objects; +import android.os.Build; +import android.util.SparseArray; +import org.chromium.ui.base.EventOffsetHandler; +import android.view.ViewStructure; +import android.view.autofill.AutofillValue; +import org.chromium.components.autofill.AutofillProvider; +import org.chromium.components.autofill.AutofillSelectionMenuItemHelper; +import org.chromium.components.autofill.BrowserSelectionActionMenuDelegate; +import org.chromium.content_public.browser.SelectionPopupController; +import org.chromium.chrome.browser.preferences.Pref; +import org.chromium.components.user_prefs.UserPrefs; + /** * Implementation of the interface {@link Tab}. Contains and manages a {@link ContentView}. This * class is not intended to be extended. @@ -236,6 +248,8 @@ class TabImpl implements Tab { private String mPendingNativePageHost; + AutofillProvider mAutofillProvider; + /** * Creates an instance of a {@link TabImpl}. Package-private. Use {@link TabBuilder} to create * an instance. @@ -857,6 +871,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 +1473,16 @@ 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. @@ -1521,6 +1550,30 @@ class TabImpl implements Tab { this)); mWebContents.notifyRendererPreferenceUpdate(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + boolean autofillEnabled = false; + if (isIncognito()) { + autofillEnabled = UserPrefs.get(Profile.getLastUsedRegularProfile()) + .getBoolean(Pref.AUTOFILL_ANDROID_INCOGNITO_ENABLED); + } else { + autofillEnabled = UserPrefs.get(Profile.getLastUsedRegularProfile()) + .getBoolean(Pref.AUTOFILL_ANDROID_ENABLED); + } + + if (autofillEnabled) { + SelectionPopupController selectionController = + SelectionPopupController.fromWebContents(mWebContents); + mAutofillProvider = new AutofillProvider(getContext(), cv, webContents, "bromite"); + TabImplJni.get().initializeAutofillIfNecessary(mNativeTabAndroid); + mAutofillProvider.setWebContents(webContents); + cv.setWebContents(webContents); + BrowserSelectionActionMenuDelegate selectionActionMenuDelegate = + new BrowserSelectionActionMenuDelegate(); + selectionActionMenuDelegate.setAutofillSelectionMenuItemHelper( + new AutofillSelectionMenuItemHelper(getContext(), mAutofillProvider)); + selectionController.setSelectionActionMenuDelegate(selectionActionMenuDelegate); + } + } TabHelpers.initWebContentsHelpers(this); notifyContentChanged(); } finally { @@ -1991,6 +2044,8 @@ class TabImpl implements Tab { boolean handleNonNavigationAboutURL(GURL url); + void initializeAutofillIfNecessary(long nativeTabAndroid); + void onShow(long nativeTabAndroid); } } 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 --- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabViewAndroidDelegate.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabViewAndroidDelegate.java @@ -20,6 +20,10 @@ import org.chromium.ui.base.WindowAndroid; import org.chromium.ui.dragdrop.DragAndDropBrowserDelegate; import org.chromium.ui.dragdrop.DragStateTracker; +import android.util.SparseArray; +import android.view.autofill.AutofillValue; +import android.view.ViewStructure; + /** Implementation of the abstract class {@link ViewAndroidDelegate} for Chrome. */ public class TabViewAndroidDelegate extends ViewAndroidDelegate { private final TabImpl mTab; @@ -83,6 +87,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 --- 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/android/tab_android.cc b/chrome/browser/android/tab_android.cc --- a/chrome/browser/android/tab_android.cc +++ b/chrome/browser/android/tab_android.cc @@ -52,6 +52,13 @@ #include "url/android/gurl_android.h" #include "url/gurl.h" +#include "components/android_autofill/browser/android_autofill_manager.h" +#include "components/android_autofill/browser/autofill_provider.h" +#include "components/android_autofill/browser/autofill_provider_android.h" +#include "components/autofill/content/browser/content_autofill_driver_factory.h" +#include "chrome/browser/ui/autofill/chrome_autofill_client.h" +#include "chrome/browser/browser_process.h" + using base::android::AttachCurrentThread; using base::android::ConvertUTF8ToJavaString; using base::android::JavaParamRef; @@ -507,3 +514,11 @@ static void JNI_TabImpl_Init(JNIEnv* env, // This will automatically bind to the Java object and pass ownership there. new TabAndroid(env, obj, profile, id); } + +void TabAndroid::InitializeAutofillIfNecessary(JNIEnv* env) { + if (!autofill::ContentAutofillDriverFactory::FromWebContents( + web_contents_.get())) { + content::WebContents* web_contents = web_contents_.get(); + autofill::ChromeAutofillClient::CreateForWebContents(web_contents); + } +} diff --git a/chrome/browser/android/tab_android.h b/chrome/browser/android/tab_android.h --- a/chrome/browser/android/tab_android.h +++ b/chrome/browser/android/tab_android.h @@ -172,6 +172,8 @@ class TabAndroid : public base::SupportsUserData { void SetDevToolsAgentHost(scoped_refptr host); + void InitializeAutofillIfNecessary(JNIEnv* env); + // This should never return null, unless it is called in a state where no // tabs exist (such as on FRE), which should never happen. If it is called // then, a nullptr will be returned and must be handled accordingly. diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd @@ -637,6 +637,12 @@ CHAR_LIMIT guidelines: Save passwords + + Enable native Android autofill + + + Enable native Android autofill in incognito + Auto Sign-in diff --git a/components/android_autofill/browser/BUILD.gn b/components/android_autofill/browser/BUILD.gn --- a/components/android_autofill/browser/BUILD.gn +++ b/components/android_autofill/browser/BUILD.gn @@ -41,6 +41,7 @@ android_library("java") { "java/src/org/chromium/components/autofill/AutofillProviderUMA.java", "java/src/org/chromium/components/autofill/AutofillRequest.java", "java/src/org/chromium/components/autofill/AutofillSelectionMenuItemHelper.java", + "java/src/org/chromium/components/autofill/BrowserSelectionActionMenuDelegate.java", "java/src/org/chromium/components/autofill/FormData.java", "java/src/org/chromium/components/autofill/FormFieldData.java", "java/src/org/chromium/components/autofill/PrefillRequest.java", diff --git a/components/android_autofill/browser/android_autofill_manager.cc b/components/android_autofill/browser/android_autofill_manager.cc --- a/components/android_autofill/browser/android_autofill_manager.cc +++ b/components/android_autofill/browser/android_autofill_manager.cc @@ -34,6 +34,8 @@ AndroidAutofillManager::~AndroidAutofillManager() { Reset(); } +bool AndroidAutofillManager::IsAndroidAutofill() const { return true; } + base::WeakPtr AndroidAutofillManager::GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); } diff --git a/components/android_autofill/browser/android_autofill_manager.h b/components/android_autofill/browser/android_autofill_manager.h --- a/components/android_autofill/browser/android_autofill_manager.h +++ b/components/android_autofill/browser/android_autofill_manager.h @@ -33,6 +33,8 @@ class AndroidAutofillManager : public AutofillManager, ~AndroidAutofillManager() override; + bool IsAndroidAutofill() const override; + base::WeakPtr GetWeakPtrToLeafClass() { return weak_ptr_factory_.GetWeakPtr(); } diff --git a/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java b/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java --- a/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java +++ b/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java @@ -51,6 +51,7 @@ public class AutofillManagerWrapper { AutofillManagerWrapper manager = mManager.get(); if (manager == null) return; manager.mIsAutofillInputUIShowing = (event == EVENT_INPUT_SHOWN); + if (manager.isLoggable()) manager.log("onAutofillEvent isAutofillInputUIShowing: " + manager.mIsAutofillInputUIShowing); if (event == EVENT_INPUT_SHOWN) manager.notifyInputUIChange(); } } @@ -91,6 +92,7 @@ public class AutofillManagerWrapper { // Uses Exception to catch various cases. (refer to crbug.com/1186406) Log.e(TAG, "getAutofillServiceComponentName", e); } + if (isLoggable()) log("componentName=" + componentName); if (componentName != null) { mPackageName = componentName.getPackageName(); mIsAwGCurrentAutofillService = @@ -256,7 +258,7 @@ public class AutofillManagerWrapper { /** Always check isLoggable() before call this method. */ public static void log(String log) { // Log.i() instead of Log.d() is used here because log.d() is stripped out in release build. - Log.i(TAG, log); + Log.i(TAG, "---" + log); } public static boolean isLoggable() { @@ -269,6 +271,7 @@ public class AutofillManagerWrapper { // NOTE: See the comment on TAG above for why this is still AwAutofillManager. // Check the system setting directly. sIsLoggable = android.util.Log.isLoggable(TAG, Log.DEBUG); + // sIsLoggable = true; // to force enable the log } } diff --git a/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillProvider.java b/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillProvider.java --- a/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillProvider.java +++ b/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillProvider.java @@ -192,13 +192,19 @@ public class AutofillProvider { /** @return whether query autofill suggestion. */ public boolean shouldQueryAutofillSuggestion() { + AutofillManagerWrapper.log("---shouldQueryAutofillSuggestion" + + " mRequest != null=" + (mRequest != null) + + " mRequest.getFocusField() != null=" + (mRequest != null && mRequest.getFocusField() != null) + + " !mAutofillManager.isAutofillInputUIShowing()=" + ( !mAutofillManager.isAutofillInputUIShowing())); return mRequest != null && mRequest.getFocusField() != null && !mAutofillManager.isAutofillInputUIShowing(); } public void queryAutofillSuggestion() { + AutofillManagerWrapper.log("queryAutofillSuggestion start"); if (shouldQueryAutofillSuggestion()) { + AutofillManagerWrapper.log("calling requestAutofill"); FocusField focusField = mRequest.getFocusField(); mAutofillManager.requestAutofill( mContainerView, @@ -258,6 +264,7 @@ public class AutofillProvider { float width, float height, boolean hasServerPrediction) { + AutofillManagerWrapper.log("startAutofillSession"); // Check focusField inside short value? Autofill Manager might have session that wasn't // started by AutofillProvider, we just always cancel existing session here. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { diff --git a/components/android_autofill/browser/java/src/org/chromium/components/autofill/BrowserSelectionActionMenuDelegate.java b/components/android_autofill/browser/java/src/org/chromium/components/autofill/BrowserSelectionActionMenuDelegate.java new file mode 100644 --- /dev/null +++ b/components/android_autofill/browser/java/src/org/chromium/components/autofill/BrowserSelectionActionMenuDelegate.java @@ -0,0 +1,62 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.components.autofill; + +import android.content.pm.ResolveInfo; + +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; + +import org.chromium.base.metrics.RecordHistogram; +import org.chromium.components.autofill.AutofillSelectionMenuItemHelper; +import org.chromium.content_public.browser.SelectionMenuItem; +import org.chromium.content_public.browser.SelectionPopupController; +import org.chromium.content_public.browser.selection.SelectionActionMenuDelegate; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +import org.chromium.base.Log; + +/** Interface for customizing text selection menu items in {@link SelectionPopupController} */ +public class BrowserSelectionActionMenuDelegate implements SelectionActionMenuDelegate { + private AutofillSelectionMenuItemHelper mAutofillSelectionMenuItemHelper; + + public BrowserSelectionActionMenuDelegate() { + } + + @Override + public void modifyDefaultMenuItems( + List menuItemBuilders, + boolean isSelectionPassword, + @NonNull String selectedText) { + } + + @Override + public List filterTextProcessingActivities(List activities) { + return activities; + } + + @NonNull + @Override + public List getAdditionalNonSelectionItems() { + if (mAutofillSelectionMenuItemHelper != null) { + return mAutofillSelectionMenuItemHelper.getAdditionalItems(); + } + return new ArrayList<>(); + } + + @NonNull + @Override + public List getAdditionalTextProcessingItems() { + return new ArrayList<>(); + } + + public void setAutofillSelectionMenuItemHelper(AutofillSelectionMenuItemHelper provider) { + mAutofillSelectionMenuItemHelper = provider; + } +} diff --git a/components/autofill/content/browser/content_autofill_driver.cc b/components/autofill/content/browser/content_autofill_driver.cc --- a/components/autofill/content/browser/content_autofill_driver.cc +++ b/components/autofill/content/browser/content_autofill_driver.cc @@ -423,6 +423,10 @@ void ContentAutofillDriver::FormsSeen( const std::vector& removed_forms) { target->GetAutofillManager().OnFormsSeen( WithNewVersion(updated_forms), removed_forms); + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnFormsSeen( + WithNewVersion(updated_forms), removed_forms); + } }); } @@ -440,6 +444,10 @@ void ContentAutofillDriver::FormSubmitted( bool known_success, mojom::SubmissionSource submission_source) { target->GetAutofillManager().OnFormSubmitted( WithNewVersion(form), known_success, submission_source); + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnFormSubmitted( + WithNewVersion(form), known_success, submission_source); + } }); } @@ -461,6 +469,10 @@ void ContentAutofillDriver::TextFieldDidChange(const FormData& raw_form, base::TimeTicks timestamp) { target->GetAutofillManager().OnTextFieldDidChange( WithNewVersion(form), field, bounding_box, timestamp); + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnTextFieldDidChange( + WithNewVersion(form), field, bounding_box, timestamp); + } }); } @@ -480,6 +492,10 @@ void ContentAutofillDriver::TextFieldDidScroll(const FormData& raw_form, const FormFieldData& field, const gfx::RectF& bounding_box) { target->GetAutofillManager().OnTextFieldDidScroll(WithNewVersion(form), field, bounding_box); + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnTextFieldDidScroll( + WithNewVersion(form), field, bounding_box); + } }); } @@ -500,6 +516,10 @@ void ContentAutofillDriver::SelectControlDidChange( const FormFieldData& field, const gfx::RectF& bounding_box) { target->GetAutofillManager().OnSelectControlDidChange( WithNewVersion(form), field, bounding_box); + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnSelectControlDidChange( + WithNewVersion(form), field, bounding_box); + } }); } @@ -522,6 +542,10 @@ void ContentAutofillDriver::AskForValuesToFill( AutofillSuggestionTriggerSource trigger_source) { target->GetAutofillManager().OnAskForValuesToFill( WithNewVersion(form), field, bounding_box, trigger_source); + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnAskForValuesToFill( + WithNewVersion(form), field, bounding_box, trigger_source); + } }); } @@ -533,6 +557,8 @@ void ContentAutofillDriver::HidePopup() { DCHECK(!target->IsPrerendering()) << "We should never affect UI while prerendering"; target->GetAutofillManager().OnHidePopup(); + if (target->secondary_autofill_manager()) + target->secondary_autofill_manager()->OnHidePopup(); }); } @@ -544,6 +570,8 @@ void ContentAutofillDriver::FocusNoLongerOnForm(bool had_interacted_form) { this, had_interacted_form, [](autofill::AutofillDriver* target, bool had_interacted_form) { target->GetAutofillManager().OnFocusNoLongerOnForm(had_interacted_form); + if (target->secondary_autofill_manager()) + target->secondary_autofill_manager()->OnFocusNoLongerOnForm(had_interacted_form); }); } @@ -563,9 +591,16 @@ void ContentAutofillDriver::FocusOnFormField(const FormData& raw_form, const FormFieldData& field, const gfx::RectF& bounding_box) { target->GetAutofillManager().OnFocusOnFormField(WithNewVersion(form), field, bounding_box); + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnFocusOnFormField( + WithNewVersion(form), field, bounding_box); + } }, [](autofill::AutofillDriver* target) { target->GetAutofillManager().OnFocusNoLongerOnForm(true); + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnFocusNoLongerOnForm(true); + } }); } @@ -580,6 +615,10 @@ void ContentAutofillDriver::DidFillAutofillFormData(const FormData& raw_form, base::TimeTicks timestamp) { target->GetAutofillManager().OnDidFillAutofillFormData( WithNewVersion(form), timestamp); + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnDidFillAutofillFormData( + WithNewVersion(form), timestamp); + } }); } @@ -589,6 +628,8 @@ void ContentAutofillDriver::DidEndTextFieldEditing() { } router().DidEndTextFieldEditing(this, [](autofill::AutofillDriver* target) { target->GetAutofillManager().OnDidEndTextFieldEditing(); + if (target->secondary_autofill_manager()) + target->secondary_autofill_manager()->OnDidEndTextFieldEditing(); }); } @@ -603,6 +644,10 @@ void ContentAutofillDriver::SelectOrSelectListFieldOptionsDidChange( cast(target) ->GetAutofillManager() .OnSelectOrSelectListFieldOptionsDidChange(WithNewVersion(form)); + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnSelectOrSelectListFieldOptionsDidChange( + WithNewVersion(form)); + } }); } @@ -622,6 +667,10 @@ void ContentAutofillDriver::JavaScriptChangedAutofilledValue( const FormFieldData& field, const std::u16string& old_value) { target->GetAutofillManager().OnJavaScriptChangedAutofilledValue( WithNewVersion(form), field, old_value); + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnJavaScriptChangedAutofilledValue( + WithNewVersion(form), field, old_value); + } }); } @@ -634,6 +683,8 @@ void ContentAutofillDriver::OnContextMenuShownInField( const FieldGlobalId& field_global_id) { target->GetAutofillManager().OnContextMenuShownInField(form_global_id, field_global_id); + if (target->secondary_autofill_manager()) + target->secondary_autofill_manager()->OnContextMenuShownInField(form_global_id, field_global_id); }); } @@ -641,6 +692,8 @@ void ContentAutofillDriver::Reset() { owner_->router().UnregisterDriver(this, /*driver_is_dying=*/false); autofill_manager_->Reset(); + if (secondary_autofill_manager_) + secondary_autofill_manager_->Reset(); } const mojo::AssociatedRemote& @@ -653,6 +706,10 @@ ContentAutofillDriver::GetAutofillAgent() { return autofill_agent_; } +raw_ptr ContentAutofillDriver::secondary_autofill_manager() { + return secondary_autofill_manager_.get(); +} + void ContentAutofillDriver::SetFrameAndFormMetaData( FormData& form, base::optional_ref field) const { diff --git a/components/autofill/content/browser/content_autofill_driver.h b/components/autofill/content/browser/content_autofill_driver.h --- a/components/autofill/content/browser/content_autofill_driver.h +++ b/components/autofill/content/browser/content_autofill_driver.h @@ -128,6 +128,12 @@ class ContentAutofillDriver : public AutofillDriver, ContentAutofillDriver& operator=(const ContentAutofillDriver&) = delete; ~ContentAutofillDriver() override; + void set_secondary_autofill_manager( + std::unique_ptr secondary_autofill_manager) { + secondary_autofill_manager_ = std::move(secondary_autofill_manager); + } + raw_ptr secondary_autofill_manager() override; + content::RenderFrameHost* render_frame_host() { return &*render_frame_host_; } const content::RenderFrameHost* render_frame_host() const { return &*render_frame_host_; @@ -326,6 +332,10 @@ class ContentAutofillDriver : public AutofillDriver, // The factory that created this driver. Outlives `this`. const raw_ref owner_; + // adds a reference for AndroidAutofillManager, since native autofill works in + // conjunction with browser autofill in Bromite + std::unique_ptr secondary_autofill_manager_ = nullptr; + mojo::AssociatedReceiver receiver_{this}; mojo::AssociatedRemote autofill_agent_; diff --git a/components/autofill/content/browser/content_autofill_driver_factory.cc b/components/autofill/content/browser/content_autofill_driver_factory.cc --- a/components/autofill/content/browser/content_autofill_driver_factory.cc +++ b/components/autofill/content/browser/content_autofill_driver_factory.cc @@ -21,6 +21,12 @@ #include "content/public/browser/web_contents.h" #include "third_party/blink/public/common/features.h" +#if BUILDFLAG(IS_ANDROID) +#include "components/android_autofill/browser/android_autofill_manager.h" +#include "components/android_autofill/browser/autofill_provider.h" +#include "components/android_autofill/browser/autofill_provider_android.h" +#endif + namespace autofill { class ScopedAutofillManagersObservation; @@ -95,6 +101,13 @@ ContentAutofillDriver* ContentAutofillDriverFactory::DriverForFrame( // 5. `render_frame_host->~RenderFrameHostImpl()` finishes. if (render_frame_host->IsRenderFrameLive()) { driver = std::make_unique(render_frame_host, this); +#if BUILDFLAG(IS_ANDROID) + if (!driver->GetAutofillManager().IsAndroidAutofill()) { + driver->set_secondary_autofill_manager( + base::WrapUnique(new AndroidAutofillManager(driver.get(), + &client_.get()))); + } +#endif for (Observer& observer : observers_) { observer.OnContentAutofillDriverCreated(*this, *driver); } diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc --- a/components/autofill/content/renderer/autofill_agent.cc +++ b/components/autofill/content/renderer/autofill_agent.cc @@ -942,12 +942,16 @@ void AutofillAgent::ShowSuggestions( password_generation_agent_->ShowPasswordGenerationSuggestions( input_element)) { is_popup_possibly_visible_ = true; +#if !BUILDFLAG(IS_ANDROID) return; +#endif } if (password_autofill_agent_->ShowSuggestions(input_element, trigger_source)) { is_popup_possibly_visible_ = true; +#if !BUILDFLAG(IS_ANDROID) return; +#endif } } @@ -959,11 +963,13 @@ void AutofillAgent::ShowSuggestions( // match a regex). In this specific case we are actually interested in whether // the field is currently a password field, not whether it has ever been a // password field. +#if !BUILDFLAG(IS_ANDROID) if (!input_element.IsNull() && input_element.IsPasswordField /*disable presubmit*/ () && !config_.query_password_suggestions) { return; } +#endif QueryAutofillSuggestions(element, trigger_source); } diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc --- a/components/autofill/content/renderer/password_autofill_agent.cc +++ b/components/autofill/content/renderer/password_autofill_agent.cc @@ -832,7 +832,10 @@ void PasswordAutofillAgent::UpdatePasswordStateForTextChange( void PasswordAutofillAgent::TrackAutofilledElement( const blink::WebFormControlElement& element) { - autofill_agent_->TrackAutofilledElement(element); + // fix for https://github.com/bromite/bromite/issues/1570 + AutofillAgent* agent = autofill_agent_.get(); + if (agent) + agent->TrackAutofilledElement(element); } void PasswordAutofillAgent::FillPasswordSuggestion( diff --git a/components/autofill/core/browser/autofill_driver.h b/components/autofill/core/browser/autofill_driver.h --- a/components/autofill/core/browser/autofill_driver.h +++ b/components/autofill/core/browser/autofill_driver.h @@ -70,6 +70,8 @@ class AutofillDriver { // Returns the AutofillManager owned by the AutofillDriver. virtual AutofillManager& GetAutofillManager() = 0; + virtual raw_ptr secondary_autofill_manager() = 0; + // Returns whether the AutofillDriver instance is associated with an active // frame in the MPArch sense. virtual bool IsInActiveFrame() const = 0; diff --git a/components/autofill/core/browser/autofill_manager.h b/components/autofill/core/browser/autofill_manager.h --- a/components/autofill/core/browser/autofill_manager.h +++ b/components/autofill/core/browser/autofill_manager.h @@ -183,6 +183,8 @@ class AutofillManager ~AutofillManager() override; + virtual bool IsAndroidAutofill() const = 0; + // The following will fail a DCHECK if called for a prerendered main frame. AutofillClient& client() { DCHECK(!driver().IsPrerendering()); diff --git a/components/autofill/core/browser/browser_autofill_manager.cc b/components/autofill/core/browser/browser_autofill_manager.cc --- a/components/autofill/core/browser/browser_autofill_manager.cc +++ b/components/autofill/core/browser/browser_autofill_manager.cc @@ -477,6 +477,8 @@ BrowserAutofillManager::~BrowserAutofillManager() { // (due to a navigation). } +bool BrowserAutofillManager::IsAndroidAutofill() const { return false; } + base::WeakPtr BrowserAutofillManager::GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); } diff --git a/components/autofill/core/browser/browser_autofill_manager.h b/components/autofill/core/browser/browser_autofill_manager.h --- a/components/autofill/core/browser/browser_autofill_manager.h +++ b/components/autofill/core/browser/browser_autofill_manager.h @@ -121,6 +121,8 @@ class BrowserAutofillManager : public AutofillManager { ~BrowserAutofillManager() override; + bool IsAndroidAutofill() const override; + // Whether the |field| should show an entry to scan a credit card. virtual bool ShouldShowScanCreditCard(const FormData& form, const FormFieldData& field) const; diff --git a/components/autofill/core/common/autofill_prefs.cc b/components/autofill/core/common/autofill_prefs.cc --- a/components/autofill/core/common/autofill_prefs.cc +++ b/components/autofill/core/common/autofill_prefs.cc @@ -64,6 +64,8 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) { registry->RegisterBooleanPref( prefs::kAutofillCreditCardFidoAuthOfferCheckboxState, true); #endif + registry->RegisterBooleanPref(prefs::kAutofillAndroidEnabled, true); + registry->RegisterBooleanPref(prefs::kAutofillAndroidIncognitoEnabled, false); registry->RegisterIntegerPref(prefs::kAutocompleteLastVersionRetentionPolicy, 0); registry->RegisterStringPref(prefs::kAutofillUploadEncodingSeed, ""); diff --git a/components/autofill/core/common/autofill_prefs.h b/components/autofill/core/common/autofill_prefs.h --- a/components/autofill/core/common/autofill_prefs.h +++ b/components/autofill/core/common/autofill_prefs.h @@ -87,6 +87,11 @@ inline constexpr char kAutofillUploadEventsLastResetTimestamp[] = // retention policy was run. inline constexpr char kAutocompleteLastVersionRetentionPolicy[] = "autocomplete.retention_policy_last_version"; +// Boolean that is true to enable native Android Autofill +inline constexpr char kAutofillAndroidEnabled[] = + "autofill.android_autofill_enabled"; +inline constexpr char kAutofillAndroidIncognitoEnabled[] = + "autofill.android_autofill_incognito_enabled"; #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID) || \ BUILDFLAG(IS_IOS) // Boolean that is set when payment methods mandatory re-auth is enabled by the 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 --- 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 @@ -42,6 +42,11 @@ import org.chromium.ui.dragdrop.DragEventDispatchHelper.DragEventDispatchDestina import java.util.function.Supplier; +import org.chromium.base.Log; +import android.util.SparseArray; +import android.view.autofill.AutofillValue; +import org.chromium.ui.base.ViewAndroidDelegate; + /** * The containing view for {@link WebContents} that exists in the Android UI hierarchy and exposes * the various {@link View} functionality to it. @@ -96,6 +101,8 @@ 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 +649,43 @@ public class ContentView extends FrameLayout mDragDropEventOffsetHandler.onPostDispatchDragEvent(event.getAction()); return ret; } + + /** + * API level 26 implementation that includes autofill. + */ + public 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/content/public/android/java/src/org/chromium/content/browser/selection/FloatingPastePopupMenu.java b/content/public/android/java/src/org/chromium/content/browser/selection/FloatingPastePopupMenu.java --- a/content/public/android/java/src/org/chromium/content/browser/selection/FloatingPastePopupMenu.java +++ b/content/public/android/java/src/org/chromium/content/browser/selection/FloatingPastePopupMenu.java @@ -133,13 +133,14 @@ public class FloatingPastePopupMenu implements PastePopupMenu { SortedSet nonSelectionMenuItems = SelectActionMenuHelper.getNonSelectionMenuItems( mContext, actionMenuDelegate, mSelectionActionMenuDelegate); + mCustomMenuItemClickListeners.clear(); SelectionPopupControllerImpl.initializeActionMenu( mContext, nonSelectionMenuItems, menu, mCustomMenuItemClickListeners, null); } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - mCustomMenuItemClickListeners.clear(); + // fixed a bug, moved above return false; } diff --git a/ui/android/java/src/org/chromium/ui/base/ViewAndroidDelegate.java b/ui/android/java/src/org/chromium/ui/base/ViewAndroidDelegate.java --- 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 values) {} } --