Commit d61502a8 authored by Fabian Henneke's avatar Fabian Henneke Committed by Chromium LUCI CQ

Manage Autofill session lifetime

During an Autofill session, distinct sets of views (also called
partitions) can trigger individual fill requests. However, there is a
configurable limit on the maximum number of partitions, which in the
case of stock Android is set to 10. Since Chrome never finishes an
Autofill session and every page load with a form adds a partition, this
means that Autofill fills stop triggering after a short time.

This can be mitigated by cancelling the session when user actions
indicate that there is no longer any Autofill-related state to preserve
in the current tab:
* When the user switches away from a tab. By cancelling the session
  before the tab contents become hidden, we additionally prevent
  Autofill from initiating a save flow as all virtual views become
  invisible.
* When top-level navigation occurs that has not been initiated by the
  renderer. Renderer-initiated navigation, such as tapping a submit
  button in a form, may trigger Autofill's save functionality and must
  therefore not cancel the session.
* When the domain part of the current URL changes. This behavior is part
  of the Android Autofill service's compatibility mode, but was
  partially broken on Android Q+ due to http://crbug.com/1103555 and
  completely broken by the workaround for that bug. The current change
  restored this behavior.

These changes apply to Android P+ only as this is the first Android
version with compatibility Autofill support for Chrome.

The remaining scenarios in which the maximal partition count can be
reached and Autofill stops triggering include:
* A single document with more than ten separate forms. This should be
  rare and would equally be a problem in a native app.
* Single-page apps or those consisting of multiple linked pages with a
  high total number of fillable forms. If the user does not switch tabs,
  Autofill may stop working after some time. Further improvements might
  be possible here, but have to avoid cancelling Autofill in a possible
  save scenario.

Bug: 1150732
Change-Id: I1005cbd349f3ba3ea25a9edafe80d82a21876bce
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2546900Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Reviewed-by: default avatarMichael Bai <michaelbai@chromium.org>
Reviewed-by: default avatarAndrew Grieve <agrieve@chromium.org>
Commit-Queue: Fabian Henneke <fabian.henneke@gmail.com>
Cr-Commit-Position: refs/heads/master@{#831987}
parent 37b7dc1b
......@@ -5,6 +5,7 @@
package org.chromium.base.compat;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ClipDescription;
import android.content.Context;
import android.content.pm.ApplicationInfo;
......@@ -14,6 +15,7 @@ import android.os.Build;
import android.view.Display;
import android.view.View;
import android.view.Window;
import android.view.autofill.AutofillManager;
import org.chromium.base.StrictModeContext;
import org.chromium.base.annotations.VerifiesOnO;
......@@ -70,4 +72,22 @@ public final class ApiHelperForO {
return context.createContextForSplit(name);
}
}
/** See {@link AutofillManager#cancel()}. */
public static void cancelAutofillSession(Activity activity) {
// The AutofillManager has to be retrieved from an activity context.
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/Application.java;l=624;drc=5d123b67756dffcfdebdb936ab2de2b29c799321
AutofillManager afm = activity.getSystemService(AutofillManager.class);
if (afm != null) {
afm.cancel();
}
}
/** See {@link AutofillManager#notifyValueChanged(View)}. */
public static void notifyValueChangedForAutofill(View view) {
final AutofillManager afm = view.getContext().getSystemService(AutofillManager.class);
if (afm != null) {
afm.notifyValueChanged(view);
}
}
}
......@@ -1361,6 +1361,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/sync/ui/TrustedVaultKeyRetrievalProxyActivity.java",
"java/src/org/chromium/chrome/browser/tab/AccessibilityVisibilityHandler.java",
"java/src/org/chromium/chrome/browser/tab/AuthenticatorNavigationInterceptorTabHelper.java",
"java/src/org/chromium/chrome/browser/tab/AutofillSessionLifetimeController.java",
"java/src/org/chromium/chrome/browser/tab/HistoricalTabSaver.java",
"java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateClientImpl.java",
"java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateTabHelper.java",
......
......@@ -40,6 +40,7 @@ import org.chromium.base.Log;
import org.chromium.base.SysUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.base.compat.ApiHelperForO;
import org.chromium.chrome.browser.WindowDelegate;
import org.chromium.ui.KeyboardVisibilityDelegate;
......@@ -359,6 +360,20 @@ public abstract class UrlBar extends AutocompleteEditText {
}
}
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Due to crbug.com/1103555, Autofill had to be disabled on the UrlBar to work around
// an issue on Android Q+. With Autofill disabled, the Autofill compat mode no longer
// learns of changes to the UrlBar, which prevents it from cancelling the session if
// the domain changes. We restore this behavior by mimicking the relevant part of
// TextView.notifyListeningManagersAfterTextChanged().
// https://cs.android.com/android/platform/superproject/+/5d123b67756dffcfdebdb936ab2de2b29c799321:frameworks/base/core/java/android/widget/TextView.java;l=10618;drc=master;bpv=0
ApiHelperForO.notifyValueChangedForAutofill(this);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// This method contains special logic to enable long presses to be handled correctly.
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.tab;
import android.annotation.TargetApi;
import android.app.Activity;
import android.os.Build;
import org.chromium.base.compat.ApiHelperForO;
import org.chromium.chrome.browser.ActivityTabProvider;
import org.chromium.chrome.browser.app.ChromeActivity;
import org.chromium.chrome.browser.lifecycle.Destroyable;
import org.chromium.content_public.browser.NavigationHandle;
/**
* Handles the lifetime of the current Autofill session.
*
* The Android Autofill service only tracks a limited number (default: 10) of view sets with an
* open fill request per Autofill session. In order to keep Autofill triggering, the session has to
* be finished (cancelled or committed) periodically.
*
* Additionally, Autofill sessions that clearly should not trigger a save flow can be cancelled in
* order to reduce save UI false positives. The Autofill service triggers save when all Autofill-
* relevant virtual views become invisible, so care must be taken to cancel the session beforehand.
*
* Autofill sessions are cancelled:
* 1. when the domain part of the UrlBar content changes:
* In this case the session is cancelled by the Android Autofill service's compat mode.
* 2. right before the tab is hidden:
* Ensures that no save UI is shown when the user switches to a different tab or the tab
* switcher. By cancelling the session in onInteractabilityChanged, we catch both cases at a
* point where tab contents are not yet hidden.
* 3. when browser-initiated navigation occurs:
* As opposed to renderer-initiated navigation (e.g., submitting a form), navigation initiated by
* browser controls should never trigger save UI. In order to cancel the session before web
* content views become invisible, we have to use onDidStartNavigation rather than one of the
* later events.
*/
public class AutofillSessionLifetimeController implements Destroyable {
private Activity mActivity;
private final ActivityTabProvider.ActivityTabTabObserver mActivityTabObserver;
@TargetApi(Build.VERSION_CODES.O)
public AutofillSessionLifetimeController(
ChromeActivity activity, ActivityTabProvider activityTabProvider) {
mActivity = activity;
mActivityTabObserver = new ActivityTabProvider.ActivityTabTabObserver(activityTabProvider) {
@Override
public void onDidStartNavigation(Tab tab, NavigationHandle navigationHandle) {
if (navigationHandle.isInMainFrame() && !navigationHandle.isRendererInitiated()) {
ApiHelperForO.cancelAutofillSession(mActivity);
}
}
@Override
public void onInteractabilityChanged(Tab tab, boolean isInteractable) {
if (!isInteractable) {
ApiHelperForO.cancelAutofillSession(mActivity);
}
}
};
activity.getLifecycleDispatcher().register(this);
}
// Destroyable
@Override
public void destroy() {
mActivityTabObserver.destroy();
mActivity = null;
}
}
......@@ -4,6 +4,7 @@
package org.chromium.chrome.browser.ui;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.text.TextUtils;
......@@ -66,6 +67,7 @@ import org.chromium.chrome.browser.share.ShareButtonController;
import org.chromium.chrome.browser.share.ShareDelegate;
import org.chromium.chrome.browser.share.ShareUtils;
import org.chromium.chrome.browser.tab.AccessibilityVisibilityHandler;
import org.chromium.chrome.browser.tab.AutofillSessionLifetimeController;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabLaunchType;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
......@@ -119,6 +121,7 @@ public class RootUiCoordinator
private final MenuOrKeyboardActionController mMenuOrKeyboardActionController;
private final TabObscuringHandler mTabObscuringHandler;
private final AccessibilityVisibilityHandler mAccessibilityVisibilityHandler;
private final @Nullable AutofillSessionLifetimeController mAutofillSessionLifetimeController;
private ActivityTabProvider mActivityTabProvider;
private ObservableSupplier<ShareDelegate> mShareDelegateSupplier;
......@@ -221,6 +224,14 @@ public class RootUiCoordinator
mTabObscuringHandler = new TabObscuringHandler();
mAccessibilityVisibilityHandler = new AccessibilityVisibilityHandler(
activity.getLifecycleDispatcher(), mActivityTabProvider, mTabObscuringHandler);
// While Autofill is supported on Android O, meaningful Autofill interactions in Chrome
// require the compatibility mode introduced in Android P.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
mAutofillSessionLifetimeController = new AutofillSessionLifetimeController(
activity, mActivityTabProvider);
} else {
mAutofillSessionLifetimeController = null;
}
mProfileSupplier = profileSupplier;
mBookmarkBridgeSupplier = bookmarkBridgeSupplier;
mAppMenuSupplier = new OneshotSupplierImpl<>();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment