diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/components/autofill | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-c30a6232df03e1efbd9f3b226777b07e087a1122.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/components/autofill')
183 files changed, 5673 insertions, 2250 deletions
diff --git a/chromium/components/autofill/android/BUILD.gn b/chromium/components/autofill/android/BUILD.gn index f246baeb159..e29c613e982 100644 --- a/chromium/components/autofill/android/BUILD.gn +++ b/chromium/components/autofill/android/BUILD.gn @@ -41,6 +41,7 @@ android_library("autofill_java") { "//base:base_java", "//content/public/android:content_java", "//third_party/android_deps:android_support_v7_appcompat_java", + "//third_party/android_deps:androidx_appcompat_appcompat_resources_java", "//ui/android:ui_java", ] sources = [ @@ -54,49 +55,3 @@ android_library("autofill_java") { ] srcjar_deps = [ ":autofill_core_browser_java_enums" ] } - -android_library("provider_java") { - deps = [ - "//base:base_java", - "//base:jni_java", - "//components/autofill/core/common/mojom:mojo_types_java", - "//components/version_info/android:version_constants_java", - "//content/public/android:content_java", - "//third_party/android_deps:androidx_annotation_annotation_java", - "//ui/android:ui_java", - ] - annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ] - sources = [ - "java/src/org/chromium/components/autofill/AutofillActionModeCallback.java", - "java/src/org/chromium/components/autofill/AutofillManagerWrapper.java", - "java/src/org/chromium/components/autofill/AutofillProvider.java", - "java/src/org/chromium/components/autofill/AutofillProviderImpl.java", - "java/src/org/chromium/components/autofill/AutofillProviderUMA.java", - "java/src/org/chromium/components/autofill/FormData.java", - "java/src/org/chromium/components/autofill/FormFieldData.java", - ] -} - -generate_jni("jni_headers") { - sources = [ - "java/src/org/chromium/components/autofill/AutofillProvider.java", - "java/src/org/chromium/components/autofill/FormData.java", - "java/src/org/chromium/components/autofill/FormFieldData.java", - ] -} - -static_library("provider") { - sources = [ - "autofill_provider_android.cc", - "autofill_provider_android.h", - "form_data_android.cc", - "form_data_android.h", - "form_field_data_android.cc", - "form_field_data_android.h", - ] - deps = [ - ":jni_headers", - "//components/autofill/core/browser:browser", - "//content/public/browser", - ] -} diff --git a/chromium/components/autofill/android/OWNERS b/chromium/components/autofill/android/OWNERS index 7f956d082c5..475b1659fb0 100644 --- a/chromium/components/autofill/android/OWNERS +++ b/chromium/components/autofill/android/OWNERS @@ -1,8 +1,2 @@ file://ui/android/OWNERS -# Files related to integration with system autofill -per-file *autofill_provider*=michaelbai@chromium.org -per-file *AutofillProvider*=michaelbai@chromium.org -per-file *AutofillManagerWrapper*=michaelbai@chromium.org -per-file *form*=michaelbai@chromium.org -per-file *Form*=michaelbai@chromium.org diff --git a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProvider.java b/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProvider.java deleted file mode 100644 index a7d05d359a4..00000000000 --- a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProvider.java +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2017 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.components.autofill; - -import android.util.SparseArray; -import android.view.ViewGroup; -import android.view.ViewStructure; -import android.view.autofill.AutofillValue; - -import org.chromium.base.annotations.CalledByNative; -import org.chromium.base.annotations.JNINamespace; -import org.chromium.base.annotations.NativeMethods; -import org.chromium.content_public.browser.WebContents; - -/** - * This class defines interface of AutofillProvider, it doesn't use chrome's - * autofill service or suggestion UI, instead, uses third party autofill service - * by knowing of format structure and user's input. - * - * AutofillProvider handles one autofill session at time, each call of - * queryFormFieldAutofill cancels previous session and starts a new one, the - * calling of other methods shall associate with current session. - * - */ -@JNINamespace("autofill") -public abstract class AutofillProvider { - public AutofillProvider() {} - - /** - * Invoked when container view is changed. - * - * @param containerView new container view. - */ - public abstract void onContainerViewChanged(ViewGroup containerView); - - public abstract void setWebContents(WebContents webContents); - - /** - * Invoked when autofill value is available, AutofillProvider shall fill the - * form with the provided values. - * - * @param values the array of autofill values, the key is virtual id of form - * field. - */ - public abstract void autofill(final SparseArray<AutofillValue> values); - - /** - * Invoked when autofill service needs the form structure. - * - * @param structure see View.onProvideAutofillVirtualStructure() - * @param flags see View.onProvideAutofillVirtualStructure() - */ - public abstract void onProvideAutoFillVirtualStructure(ViewStructure structure, int flags); - - /** - * @return whether query autofill suggestion. - */ - public abstract boolean shouldQueryAutofillSuggestion(); - - public abstract void queryAutofillSuggestion(); - - /** - * Invoked when filling form is need. AutofillProvider shall ask autofill - * service for the values with which to fill the form. - * - * @param formData the form needs to fill. - * @param focus the index of focus field in formData - * @param x the boundary of focus field. - * @param y the boundary of focus field. - * @param width the boundary of focus field. - * @param height the boundary of focus field. - */ - @CalledByNative - protected abstract void startAutofillSession( - FormData formData, int focus, float x, float y, float width, float height); - - /** - * Invoked when form field's value is changed. - * - * @param index index of field in current form. - * @param x the boundary of focus field. - * @param y the boundary of focus field. - * @param width the boundary of focus field. - * @param height the boundary of focus field. - * - */ - @CalledByNative - protected abstract void onFormFieldDidChange( - int index, float x, float y, float width, float height); - - /** - * Invoked when text field is scrolled. - * - * @param index index of field in current form. - * @param x the boundary of focus field. - * @param y the boundary of focus field. - * @param width the boundary of focus field. - * @param height the boundary of focus field. - * - */ - @CalledByNative - protected abstract void onTextFieldDidScroll( - int index, float x, float y, float width, float height); - - /** - * Invoked when current form will be submitted. - * @param submissionSource the submission source, could be any member defined in - * SubmissionSource.java - */ - @CalledByNative - protected abstract void onFormSubmitted(int submissionSource); - - /** - * Invoked when focus field changed. - * - * @param focusOnForm whether focus is still on form. - * @param focusItem the index of field has focus - * @param x the boundary of focus field. - * @param y the boundary of focus field. - * @param width the boundary of focus field. - * @param height the boundary of focus field. - */ - @CalledByNative - protected abstract void onFocusChanged( - boolean focusOnForm, int focusItem, float x, float y, float width, float height); - - /** - * Send form to renderer for filling. - * - * @param nativeAutofillProvider the native autofill provider. - * @param formData the form to fill. - */ - protected void autofill(long nativeAutofillProvider, FormData formData) { - AutofillProviderJni.get().onAutofillAvailable( - nativeAutofillProvider, AutofillProvider.this, formData); - } - - /** - * Invoked when current query need to be reset. - */ - @CalledByNative - protected abstract void reset(); - - @CalledByNative - protected abstract void setNativeAutofillProvider(long nativeAutofillProvider); - - @CalledByNative - protected abstract void onDidFillAutofillFormData(); - - @NativeMethods - interface Natives { - void onAutofillAvailable( - long nativeAutofillProviderAndroid, AutofillProvider caller, FormData formData); - } -} diff --git a/chromium/components/autofill/android/provider/BUILD.gn b/chromium/components/autofill/android/provider/BUILD.gn new file mode 100644 index 00000000000..263b88afd5e --- /dev/null +++ b/chromium/components/autofill/android/provider/BUILD.gn @@ -0,0 +1,53 @@ +# 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. + +import("//build/config/android/rules.gni") +import("//build/config/locales.gni") + +android_library("java") { + deps = [ + "//base:base_java", + "//base:jni_java", + "//components/autofill/android:autofill_java", + "//components/autofill/core/common/mojom:mojo_types_java", + "//components/version_info/android:version_constants_java", + "//content/public/android:content_java", + "//third_party/android_deps:androidx_annotation_annotation_java", + "//ui/android:ui_java", + ] + annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ] + sources = [ + "java/src/org/chromium/components/autofill/AutofillActionModeCallback.java", + "java/src/org/chromium/components/autofill/AutofillManagerWrapper.java", + "java/src/org/chromium/components/autofill/AutofillProvider.java", + "java/src/org/chromium/components/autofill/AutofillProviderUMA.java", + "java/src/org/chromium/components/autofill/FormData.java", + "java/src/org/chromium/components/autofill/FormFieldData.java", + ] +} + +generate_jni("jni_headers") { + sources = [ + "java/src/org/chromium/components/autofill/AutofillProvider.java", + "java/src/org/chromium/components/autofill/FormData.java", + "java/src/org/chromium/components/autofill/FormFieldData.java", + ] +} + +static_library("provider") { + sources = [ + "autofill_provider_android.cc", + "autofill_provider_android.h", + "form_data_android.cc", + "form_data_android.h", + "form_field_data_android.cc", + "form_field_data_android.h", + ] + deps = [ + ":jni_headers", + "//components/autofill/core/browser:browser", + "//content/public/browser", + "//ui/android", + ] +} diff --git a/chromium/components/autofill/android/junit/OWNERS b/chromium/components/autofill/android/provider/OWNERS index 44a22b15980..44a22b15980 100644 --- a/chromium/components/autofill/android/junit/OWNERS +++ b/chromium/components/autofill/android/provider/OWNERS diff --git a/chromium/components/autofill/android/autofill_provider_android.cc b/chromium/components/autofill/android/provider/autofill_provider_android.cc index 76129371d95..408ff96c130 100644 --- a/chromium/components/autofill/android/autofill_provider_android.cc +++ b/chromium/components/autofill/android/provider/autofill_provider_android.cc @@ -2,27 +2,30 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "components/autofill/android/autofill_provider_android.h" +#include "components/autofill/android/provider/autofill_provider_android.h" #include <memory> #include "base/android/jni_android.h" #include "base/android/jni_array.h" #include "base/android/jni_string.h" -#include "components/autofill/android/form_data_android.h" -#include "components/autofill/android/jni_headers/AutofillProvider_jni.h" +#include "components/autofill/android/provider/form_data_android.h" +#include "components/autofill/android/provider/jni_headers/AutofillProvider_jni.h" #include "components/autofill/core/browser/autofill_driver.h" #include "components/autofill/core/browser/autofill_handler_proxy.h" #include "components/autofill/core/common/autofill_constants.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/web_contents.h" +#include "ui/android/window_android.h" #include "ui/gfx/geometry/rect_f.h" using base::android::AttachCurrentThread; +using base::android::ConvertJavaStringToUTF16; using base::android::ConvertUTF16ToJavaString; using base::android::ConvertUTF8ToJavaString; using base::android::JavaRef; using base::android::ScopedJavaLocalRef; +using base::android::ToJavaArrayOfStrings; using content::BrowserThread; using content::WebContents; using gfx::RectF; @@ -85,6 +88,21 @@ void AutofillProviderAndroid::OnQueryFormFieldAutofill( // ignored if the form is same. if (ShouldStartNewSession(handler, form)) StartNewSession(handler, form, field, bounding_box); + + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); + if (obj.is_null()) + return; + + if (!field.datalist_values.empty()) { + ScopedJavaLocalRef<jobjectArray> jdatalist_values = + ToJavaArrayOfStrings(env, field.datalist_values); + ScopedJavaLocalRef<jobjectArray> jdatalist_labels = + ToJavaArrayOfStrings(env, field.datalist_labels); + Java_AutofillProvider_showDatalistPopup( + env, obj, jdatalist_values, jdatalist_labels, + field.text_direction == base::i18n::RIGHT_TO_LEFT); + } } bool AutofillProviderAndroid::ShouldStartNewSession( @@ -141,6 +159,31 @@ void AutofillProviderAndroid::OnAutofillAvailable(JNIEnv* env, } } +void AutofillProviderAndroid::OnAcceptDataListSuggestion(JNIEnv* env, + jobject jcaller, + jstring value) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (auto* handler = handler_.get()) { + RendererShouldAcceptDataListSuggestion( + handler, ConvertJavaStringToUTF16(env, value)); + } +} + +void AutofillProviderAndroid::SetAnchorViewRect(JNIEnv* env, + jobject jcaller, + jobject anchor_view, + jfloat x, + jfloat y, + jfloat width, + jfloat height) { + ui::ViewAndroid* view_android = web_contents_->GetNativeView(); + if (!view_android) + return; + + view_android->SetAnchorRect(ScopedJavaLocalRef<jobject>(env, anchor_view), + gfx::RectF(x, y, width, height)); +} + void AutofillProviderAndroid::OnTextFieldDidChange( AutofillHandlerProxy* handler, const FormData& form, @@ -314,6 +357,18 @@ void AutofillProviderAndroid::OnFormsSeen(AutofillHandlerProxy* handler, FireSuccessfulSubmission(pending_submission_source_); } +void AutofillProviderAndroid::OnHidePopup(AutofillHandlerProxy* handler) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (handler == handler_.get()) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); + if (obj.is_null()) + return; + + Java_AutofillProvider_hidePopup(env, obj); + } +} + void AutofillProviderAndroid::Reset(AutofillHandlerProxy* handler) { DCHECK_CURRENTLY_ON(BrowserThread::UI); if (handler == handler_.get()) { diff --git a/chromium/components/autofill/android/autofill_provider_android.h b/chromium/components/autofill/android/provider/autofill_provider_android.h index 270a05eea00..b0830e2338e 100644 --- a/chromium/components/autofill/android/autofill_provider_android.h +++ b/chromium/components/autofill/android/provider/autofill_provider_android.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef COMPONENTS_AUTOFILL_ANDROID_AUTOFILL_PROVIDER_ANDROID_H_ -#define COMPONENTS_AUTOFILL_ANDROID_AUTOFILL_PROVIDER_ANDROID_H_ +#ifndef COMPONENTS_AUTOFILL_ANDROID_PROVIDER_AUTOFILL_PROVIDER_ANDROID_H_ +#define COMPONENTS_AUTOFILL_ANDROID_PROVIDER_AUTOFILL_PROVIDER_ANDROID_H_ #include "base/android/jni_weak_ref.h" #include "base/memory/weak_ptr.h" @@ -67,11 +67,21 @@ class AutofillProviderAndroid : public AutofillProvider { void OnFormsSeen(AutofillHandlerProxy* handler, const std::vector<FormData>& forms, const base::TimeTicks timestamp) override; + void OnHidePopup(AutofillHandlerProxy* handler) override; void Reset(AutofillHandlerProxy* handler) override; // Methods called by Java. void OnAutofillAvailable(JNIEnv* env, jobject jcaller, jobject form_data); + void OnAcceptDataListSuggestion(JNIEnv* env, jobject jcaller, jstring value); + + void SetAnchorViewRect(JNIEnv* env, + jobject jcaller, + jobject anchor_view, + jfloat x, + jfloat y, + jfloat width, + jfloat height); private: void FireSuccessfulSubmission(mojom::SubmissionSource source); @@ -114,4 +124,4 @@ class AutofillProviderAndroid : public AutofillProvider { }; } // namespace autofill -#endif // COMPONENTS_AUTOFILL_ANDROID_AUTOFILL_PROVIDER_ANDROID_H_ +#endif // COMPONENTS_AUTOFILL_ANDROID_PROVIDER_AUTOFILL_PROVIDER_ANDROID_H_ diff --git a/chromium/components/autofill/android/form_data_android.cc b/chromium/components/autofill/android/provider/form_data_android.cc index c91a0c62472..7f24f8967c5 100644 --- a/chromium/components/autofill/android/form_data_android.cc +++ b/chromium/components/autofill/android/provider/form_data_android.cc @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "components/autofill/android/form_data_android.h" +#include "components/autofill/android/provider/form_data_android.h" #include "base/android/jni_string.h" -#include "components/autofill/android/form_field_data_android.h" -#include "components/autofill/android/jni_headers/FormData_jni.h" +#include "components/autofill/android/provider/form_field_data_android.h" +#include "components/autofill/android/provider/jni_headers/FormData_jni.h" #include "components/autofill/core/browser/form_structure.h" using base::android::AttachCurrentThread; diff --git a/chromium/components/autofill/android/form_data_android.h b/chromium/components/autofill/android/provider/form_data_android.h index a2b33c8d917..8882a788673 100644 --- a/chromium/components/autofill/android/form_data_android.h +++ b/chromium/components/autofill/android/provider/form_data_android.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef COMPONENTS_AUTOFILL_ANDROID_FORM_DATA_ANDROID_H_ -#define COMPONENTS_AUTOFILL_ANDROID_FORM_DATA_ANDROID_H_ +#ifndef COMPONENTS_AUTOFILL_ANDROID_PROVIDER_FORM_DATA_ANDROID_H_ +#define COMPONENTS_AUTOFILL_ANDROID_PROVIDER_FORM_DATA_ANDROID_H_ #include "base/android/jni_weak_ref.h" #include "base/android/scoped_java_ref.h" @@ -70,4 +70,4 @@ class FormDataAndroid { } // namespace autofill -#endif // COMPONENTS_AUTOFILL_ANDROID_FORM_DATA_ANDROID_H_ +#endif // COMPONENTS_AUTOFILL_ANDROID_PROVIDER_FORM_DATA_ANDROID_H_ diff --git a/chromium/components/autofill/android/form_field_data_android.cc b/chromium/components/autofill/android/provider/form_field_data_android.cc index da134267220..ee5471f5239 100644 --- a/chromium/components/autofill/android/form_field_data_android.cc +++ b/chromium/components/autofill/android/provider/form_field_data_android.cc @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "components/autofill/android/form_field_data_android.h" +#include "components/autofill/android/provider/form_field_data_android.h" #include "base/android/jni_array.h" #include "base/android/jni_string.h" -#include "components/autofill/android/jni_headers/FormFieldData_jni.h" +#include "components/autofill/android/provider/jni_headers/FormFieldData_jni.h" #include "components/autofill/core/common/autofill_util.h" using base::android::AttachCurrentThread; @@ -50,6 +50,10 @@ ScopedJavaLocalRef<jobject> FormFieldDataAndroid::GetJavaPeer() { if (!heuristic_type_.IsUnknown()) jheuristic_type = ConvertUTF8ToJavaString(env, heuristic_type_.ToString()); + ScopedJavaLocalRef<jobjectArray> jdatalist_values = + ToJavaArrayOfStrings(env, field_ptr_->datalist_values); + ScopedJavaLocalRef<jobjectArray> jdatalist_labels = + ToJavaArrayOfStrings(env, field_ptr_->datalist_labels); obj = Java_FormFieldData_createFormFieldData( env, jname, jlabel, jvalue, jautocomplete_attr, @@ -57,7 +61,8 @@ ScopedJavaLocalRef<jobject> FormFieldDataAndroid::GetJavaPeer() { joption_values, joption_contents, IsCheckable(field_ptr_->check_status), IsChecked(field_ptr_->check_status), field_ptr_->max_length, jheuristic_type, field_ptr_->bounds.x(), field_ptr_->bounds.y(), - field_ptr_->bounds.right(), field_ptr_->bounds.bottom()); + field_ptr_->bounds.right(), field_ptr_->bounds.bottom(), + jdatalist_values, jdatalist_labels); java_ref_ = JavaObjectWeakGlobalRef(env, obj); } return obj; diff --git a/chromium/components/autofill/android/form_field_data_android.h b/chromium/components/autofill/android/provider/form_field_data_android.h index e882bb49704..a12ad979bfe 100644 --- a/chromium/components/autofill/android/form_field_data_android.h +++ b/chromium/components/autofill/android/provider/form_field_data_android.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef COMPONENTS_AUTOFILL_ANDROID_FORM_FIELD_DATA_ANDROID_H_ -#define COMPONENTS_AUTOFILL_ANDROID_FORM_FIELD_DATA_ANDROID_H_ +#ifndef COMPONENTS_AUTOFILL_ANDROID_PROVIDER_FORM_FIELD_DATA_ANDROID_H_ +#define COMPONENTS_AUTOFILL_ANDROID_PROVIDER_FORM_FIELD_DATA_ANDROID_H_ #include "base/android/jni_weak_ref.h" #include "base/android/scoped_java_ref.h" @@ -39,4 +39,4 @@ class FormFieldDataAndroid { } // namespace autofill -#endif // COMPONENTS_AUTOFILL_ANDROID_FORM_FIELD_DATA_ANDROID_H_ +#endif // COMPONENTS_AUTOFILL_ANDROID_PROVIDER_FORM_FIELD_DATA_ANDROID_H_ diff --git a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillActionModeCallback.java b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillActionModeCallback.java index 6921fcab52c..6921fcab52c 100644 --- a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillActionModeCallback.java +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillActionModeCallback.java diff --git a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java index 0287ac38f97..0287ac38f97 100644 --- a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillManagerWrapper.java diff --git a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProviderImpl.java b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProvider.java index e53188067c3..acbf7a61a22 100644 --- a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProviderImpl.java +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProvider.java @@ -19,18 +19,30 @@ import android.view.autofill.AutofillValue; import androidx.annotation.VisibleForTesting; +import org.chromium.base.ContextUtils; import org.chromium.base.Log; +import org.chromium.base.StrictModeContext; import org.chromium.base.ThreadUtils; -import org.chromium.base.annotations.DoNotInline; +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.base.annotations.VerifiesOnO; import org.chromium.base.metrics.ScopedSysTraceEvent; import org.chromium.components.version_info.VersionConstants; import org.chromium.content_public.browser.WebContents; +import org.chromium.content_public.browser.WebContentsAccessibility; +import org.chromium.ui.DropdownItem; +import org.chromium.ui.base.ViewAndroidDelegate; import org.chromium.ui.base.WindowAndroid; import org.chromium.ui.display.DisplayAndroid; /** - * This class uses Android autofill service to fill web form. All methods are - * supposed to be called in UI thread. + * This class works with Android autofill service to fill web form, it doesn't use chrome's + * autofill service or suggestion UI. All methods are supposed to be called in UI thread. + * + * AutofillProvider handles one autofill session at time, each call of + * queryFormFieldAutofill cancels previous session and starts a new one, the + * calling of other methods shall associate with current session. * * This class doesn't have 1:1 mapping to native AutofillProviderAndroid; the * normal ownership model is that this object is owned by the embedder-specific @@ -38,12 +50,13 @@ import org.chromium.ui.display.DisplayAndroid; * AutofillProviderAndroid is owned by the embedder-specific C++ WebContents * wrapper (e.g., native AwContents in //android_webview). * - * DoNotInline since it causes class verification errors, see crbug.com/991851. + * VerifiesOnO since it causes class verification errors, see crbug.com/991851. */ -@DoNotInline +@VerifiesOnO @TargetApi(Build.VERSION_CODES.O) -public class AutofillProviderImpl extends AutofillProvider { - private static final String TAG = "AutofillProviderImpl"; +@JNINamespace("autofill") +public class AutofillProvider { + private static final String TAG = "AutofillProvider"; private static class FocusField { public final short fieldIndex; public final Rect absBound; @@ -117,11 +130,15 @@ public class AutofillProviderImpl extends AutofillProvider { child.setAutofillValue(AutofillValue.forToggle(field.isChecked())); break; case FormFieldData.ControlType.TEXT: + case FormFieldData.ControlType.DATALIST: child.setAutofillType(View.AUTOFILL_TYPE_TEXT); child.setAutofillValue(AutofillValue.forText(field.getValue())); if (field.mMaxLength != 0) { builder.addAttribute("maxlength", String.valueOf(field.mMaxLength)); } + if (field.getControlType() == FormFieldData.ControlType.DATALIST) { + child.setAutofillOptions(field.mDatalistValues); + } break; default: break; @@ -151,6 +168,7 @@ public class AutofillProviderImpl extends AutofillProvider { field.setChecked(value.getToggleValue()); break; case FormFieldData.ControlType.TEXT: + case FormFieldData.ControlType.DATALIST: field.setAutofillValue((String) value.getTextValue()); break; default: @@ -188,6 +206,7 @@ public class AutofillProviderImpl extends AutofillProvider { case FormFieldData.ControlType.TOGGLE: return AutofillValue.forToggle(field.isChecked()); case FormFieldData.ControlType.TEXT: + case FormFieldData.ControlType.DATALIST: return AutofillValue.forText(field.getValue()); default: return null; @@ -240,17 +259,20 @@ public class AutofillProviderImpl extends AutofillProvider { private AutofillProviderUMA mAutofillUMA; private AutofillManagerWrapper.InputUIObserver mInputUIObserver; private long mAutofillTriggeredTimeMillis; + private Context mContext; + private AutofillPopup mDatalistPopup; + private WebContentsAccessibility mWebContentsAccessibility; + private View mAnchorView; - public AutofillProviderImpl(Context context, ViewGroup containerView, String providerName) { + public AutofillProvider(Context context, ViewGroup containerView, String providerName) { this(containerView, new AutofillManagerWrapper(context), context, providerName); } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - public AutofillProviderImpl(ViewGroup containerView, AutofillManagerWrapper manager, + public AutofillProvider(ViewGroup containerView, AutofillManagerWrapper manager, Context context, String providerName) { mProviderName = providerName; - try (ScopedSysTraceEvent e = - ScopedSysTraceEvent.scoped("AutofillProviderImpl.constructor")) { + try (ScopedSysTraceEvent e = ScopedSysTraceEvent.scoped("AutofillProvider.constructor")) { assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; mAutofillManager = manager; mContainerView = containerView; @@ -266,15 +288,25 @@ public class AutofillProviderImpl extends AutofillProvider { } }; mAutofillManager.addInputUIObserver(mInputUIObserver); + mContext = context; } } - @Override + /** + * Invoked when container view is changed. + * + * @param containerView new container view. + */ public void onContainerViewChanged(ViewGroup containerView) { mContainerView = containerView; } - @Override + /** + * Invoked when autofill service needs the form structure. + * + * @param structure see View.onProvideAutofillVirtualStructure() + * @param flags see View.onProvideAutofillVirtualStructure() + */ public void onProvideAutoFillVirtualStructure(ViewStructure structure, int flags) { // This method could be called for the session started by the native // control outside of the scope of autofill, e.g. the URL bar, in this case, we simply @@ -295,7 +327,13 @@ public class AutofillProviderImpl extends AutofillProvider { mAutofillUMA.onVirtualStructureProvided(); } - @Override + /** + * Invoked when autofill value is available, AutofillProvider shall fill the + * form with the provided values. + * + * @param values the array of autofill values, the key is virtual id of form + * field. + */ public void autofill(final SparseArray<AutofillValue> values) { if (mNativeAutofillProvider != 0 && mRequest != null && mRequest.autofill((values))) { autofill(mNativeAutofillProvider, mRequest.mFormData); @@ -306,13 +344,14 @@ public class AutofillProviderImpl extends AutofillProvider { } } - @Override + /** + * @return whether query autofill suggestion. + */ public boolean shouldQueryAutofillSuggestion() { return mRequest != null && mRequest.getFocusField() != null && !mAutofillManager.isAutofillInputUIShowing(); } - @Override public void queryAutofillSuggestion() { if (shouldQueryAutofillSuggestion()) { FocusField focusField = mRequest.getFocusField(); @@ -321,27 +360,50 @@ public class AutofillProviderImpl extends AutofillProvider { } } - @Override + /** + * Invoked when filling form is need. AutofillProvider shall ask autofill + * service for the values with which to fill the form. + * + * @param formData the form needs to fill. + * @param focus the index of focus field in formData + * @param x the boundary of focus field. + * @param y the boundary of focus field. + * @param width the boundary of focus field. + * @param height the boundary of focus field. + */ + @CalledByNative public void startAutofillSession( FormData formData, int focus, float x, float y, float width, float height) { // Check focusField inside short value? - // Autofill Manager might have session that wasn't started by AutofillProviderImpl, + // 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) { mAutofillManager.cancel(); } - mAutofillManager.notifyNewSessionStarted(); + Rect absBound = transformToWindowBounds(new RectF(x, y, x + width, y + height)); - if (mRequest != null) notifyViewExitBeforeDestoryRequest(); + if (mRequest != null) notifyViewExitBeforeDestroyRequest(); transformFormFieldToContainViewCoordinates(formData); mRequest = new AutofillRequest(formData, new FocusField((short) focus, absBound)); int virtualId = mRequest.getVirtualId((short) focus); - mAutofillManager.notifyVirtualViewEntered(mContainerView, virtualId, absBound); + notifyVirtualViewEntered(mContainerView, virtualId, absBound); mAutofillUMA.onSessionStarted(mAutofillManager.isDisabled()); mAutofillTriggeredTimeMillis = System.currentTimeMillis(); + + mAutofillManager.notifyNewSessionStarted(); } - @Override + /** + * Invoked when form field's value is changed. + * + * @param index index of field in current form. + * @param x the boundary of focus field. + * @param y the boundary of focus field. + * @param width the boundary of focus field. + * @param height the boundary of focus field. + * + */ + @CalledByNative public void onFormFieldDidChange(int index, float x, float y, float width, float height) { // Check index inside short value? if (mRequest == null) return; @@ -358,17 +420,27 @@ public class AutofillProviderImpl extends AutofillProvider { int virtualId = mRequest.getVirtualId(sIndex); Rect absBound = transformToWindowBounds(new RectF(x, y, x + width, y + height)); if (!focusField.absBound.equals(absBound)) { - mAutofillManager.notifyVirtualViewExited(mContainerView, virtualId); - mAutofillManager.notifyVirtualViewEntered(mContainerView, virtualId, absBound); + notifyVirtualViewExited(mContainerView, virtualId); + notifyVirtualViewEntered(mContainerView, virtualId, absBound); // Update focus field position. mRequest.setFocusField(new FocusField(focusField.fieldIndex, absBound)); } } - notifyVirtualValueChanged(index); + notifyVirtualValueChanged(index, /* forceNotify = */ false); mAutofillUMA.onUserChangeFieldValue(mRequest.getField(sIndex).hasPreviouslyAutofilled()); } - @Override + /** + * Invoked when text field is scrolled. + * + * @param index index of field in current form. + * @param x the boundary of focus field. + * @param y the boundary of focus field. + * @param width the boundary of focus field. + * @param height the boundary of focus field. + * + */ + @CalledByNative public void onTextFieldDidScroll(int index, float x, float y, float width, float height) { // crbug.com/730764 - from P and above, Android framework listens to the onScrollChanged() // and repositions the autofill UI automatically. @@ -383,42 +455,94 @@ public class AutofillProviderImpl extends AutofillProvider { Rect absBound = transformToWindowBounds(new RectF(x, y, x + width, y + height)); // Notify the new position to the Android framework. Note that we do not call // notifyVirtualViewExited() here intentionally to avoid flickering. - mAutofillManager.notifyVirtualViewEntered(mContainerView, virtualId, absBound); + notifyVirtualViewEntered(mContainerView, virtualId, absBound); // Update focus field position. mRequest.setFocusField(new FocusField(focusField.fieldIndex, absBound)); } - private void notifyVirtualValueChanged(int index) { + private boolean isDatalistField(int childId) { + FormFieldData field = mRequest.getField((short) childId); + return field.mControlType == FormFieldData.ControlType.DATALIST; + } + + private void notifyVirtualValueChanged(int index, boolean forceNotify) { + // The ValueChanged, ViewEntered and ViewExited aren't notified to the autofill service for + // the focused datalist to avoid the potential UI conflict. + // The datalist support was added later and the option list is displayed by WebView, the + // autofill service might also show its suggestions when the datalist (associated the input + // field) is focused, the two UI overlap, the solution is to completely hide the fact that + // the datalist is being focused to the autofill service to prevent it from displaying the + // suggestion. + // The ValueChange will still be sent to autofill service when the form + // submitted or autofilled. + if (!forceNotify && isDatalistField(index)) return; AutofillValue autofillValue = mRequest.getFieldNewValue(index); if (autofillValue == null) return; mAutofillManager.notifyVirtualValueChanged( mContainerView, mRequest.getVirtualId((short) index), autofillValue); } - @Override + private void notifyVirtualViewEntered(View parent, int childId, Rect absBounds) { + // Refer to notifyVirtualValueChanged() for the reason of the datalist's special handling. + if (isDatalistField(childId)) return; + mAutofillManager.notifyVirtualViewEntered(parent, childId, absBounds); + } + + private void notifyVirtualViewExited(View parent, int childId) { + // Refer to notifyVirtualValueChanged() for the reason of the datalist's special handling. + if (isDatalistField(childId)) return; + mAutofillManager.notifyVirtualViewExited(parent, childId); + } + + /** + * Invoked when current form will be submitted. + * @param submissionSource the submission source, could be any member defined in + * SubmissionSource.java + */ + @CalledByNative public void onFormSubmitted(int submissionSource) { // The changes could be missing, like those made by Javascript, we'd better to notify // AutofillManager current values. also see crbug.com/353001 and crbug.com/732856. - notifyFormValues(); + forceNotifyFormValues(); mAutofillManager.commit(submissionSource); mRequest = null; mAutofillUMA.onFormSubmitted(submissionSource); } - @Override + /** + * Invoked when focus field changed. + * + * @param focusOnForm whether focus is still on form. + * @param focusItem the index of field has focus + * @param x the boundary of focus field. + * @param y the boundary of focus field. + * @param width the boundary of focus field. + * @param height the boundary of focus field. + */ + @CalledByNative public void onFocusChanged( boolean focusOnForm, int focusField, float x, float y, float width, float height) { onFocusChangedImpl( focusOnForm, focusField, x, y, width, height, false /*causedByValueChange*/); } - private void notifyViewExitBeforeDestoryRequest() { + @CalledByNative + public void hidePopup() { + if (mDatalistPopup != null) { + mDatalistPopup.dismiss(); + mDatalistPopup = null; + } + if (mWebContentsAccessibility != null) { + mWebContentsAccessibility.onAutofillPopupDismissed(); + } + } + + private void notifyViewExitBeforeDestroyRequest() { if (mRequest == null) return; FocusField focusField = mRequest.getFocusField(); if (focusField == null) return; - mAutofillManager.notifyVirtualViewExited( - mContainerView, mRequest.getVirtualId(focusField.fieldIndex)); + notifyVirtualViewExited(mContainerView, mRequest.getVirtualId(focusField.fieldIndex)); mRequest.setFocusField(null); } @@ -436,37 +560,118 @@ public class AutofillProviderImpl extends AutofillProvider { // Notify focus changed. if (prev != null) { - mAutofillManager.notifyVirtualViewExited( - mContainerView, mRequest.getVirtualId(prev.fieldIndex)); + notifyVirtualViewExited(mContainerView, mRequest.getVirtualId(prev.fieldIndex)); } - mAutofillManager.notifyVirtualViewEntered( + notifyVirtualViewEntered( mContainerView, mRequest.getVirtualId((short) focusField), absBound); if (!causedByValueChange) { // The focus field value might not sync with platform's // AutofillManager, just notify it value changed. - notifyVirtualValueChanged(focusField); + notifyVirtualValueChanged(focusField, /* forceNotify = */ false); mAutofillTriggeredTimeMillis = System.currentTimeMillis(); } mRequest.setFocusField(new FocusField((short) focusField, absBound)); } else { if (prev == null) return; // Notify focus changed. - mAutofillManager.notifyVirtualViewExited( - mContainerView, mRequest.getVirtualId(prev.fieldIndex)); + notifyVirtualViewExited(mContainerView, mRequest.getVirtualId(prev.fieldIndex)); mRequest.setFocusField(null); } } - @Override + @CalledByNative + protected void showDatalistPopup( + String[] datalistValues, String[] datalistLabels, boolean isRtl) { + if (mRequest == null) return; + FocusField focusField = mRequest.getFocusField(); + if (focusField != null) { + showDatalistPopup(datalistValues, datalistLabels, + mRequest.getField(focusField.fieldIndex).getBounds(), isRtl); + } + } + + /** + * Display the simplest popup for the datalist. This is same as WebView's datalist popup in + * Android pre-o. No suggestion from the autofill service will be presented, No advance + * features of AutofillPopup are used. + */ + private void showDatalistPopup( + String[] datalistValues, String[] datalistLabels, RectF bounds, boolean isRtl) { + final AutofillSuggestion[] suggestions = new AutofillSuggestion[datalistValues.length]; + for (int i = 0; i < suggestions.length; i++) { + suggestions[i] = new AutofillSuggestion(datalistValues[i], datalistLabels[i], + DropdownItem.NO_ICON, false /* isIconAtLeft */, i, false /* isDeletable */, + false /* isMultilineLabel */, false /* isBoldLabel */); + } + if (mWebContentsAccessibility == null) { + mWebContentsAccessibility = WebContentsAccessibility.fromWebContents(mWebContents); + } + if (mDatalistPopup == null) { + if (ContextUtils.activityFromContext(mContext) == null) return; + ViewAndroidDelegate delegate = mWebContents.getViewAndroidDelegate(); + if (mAnchorView == null) mAnchorView = delegate.acquireView(); + setAnchorViewRect(bounds); + try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) { + mDatalistPopup = new AutofillPopup(mContext, mAnchorView, new AutofillDelegate() { + @Override + public void dismissed() { + onDatalistPopupDismissed(); + } + + @Override + public void suggestionSelected(int listIndex) { + onSuggestionSelected(suggestions[listIndex].getLabel()); + } + + @Override + public void deleteSuggestion(int listIndex) {} + + @Override + public void accessibilityFocusCleared() { + mWebContentsAccessibility.onAutofillPopupAccessibilityFocusCleared(); + } + }); + } catch (RuntimeException e) { + // Deliberately swallowing exception because bad framework implementation can + // throw exceptions in ListPopupWindow constructor. + onDatalistPopupDismissed(); + return; + } + } + mDatalistPopup.filterAndShow(suggestions, isRtl, false); + if (mWebContentsAccessibility != null) { + mWebContentsAccessibility.onAutofillPopupDisplayed(mDatalistPopup.getListView()); + } + } + + private void onDatalistPopupDismissed() { + ViewAndroidDelegate delegate = mWebContents.getViewAndroidDelegate(); + delegate.removeView(mAnchorView); + mAnchorView = null; + } + + private void onSuggestionSelected(String value) { + acceptDataListSuggestion(mNativeAutofillProvider, value); + hidePopup(); + } + + private void setAnchorViewRect(RectF rect) { + setAnchorViewRect(mNativeAutofillProvider, mAnchorView, rect); + } + + /** + * Invoked when current query need to be reset. + */ + @CalledByNative protected void reset() { // We don't need to reset anything here, it should be safe to cancel // current autofill session when new one starts in // startAutofillSession(). } - @Override + @CalledByNative protected void setNativeAutofillProvider(long nativeAutofillProvider) { if (nativeAutofillProvider == mNativeAutofillProvider) return; // Setting the mNativeAutofillProvider to 0 may occur as a @@ -482,21 +687,29 @@ public class AutofillProviderImpl extends AutofillProvider { if (nativeAutofillProvider == 0) mAutofillManager.destroy(); } - @Override public void setWebContents(WebContents webContents) { if (webContents == mWebContents) return; if (mWebContents != null) mRequest = null; mWebContents = webContents; } - @Override + @CalledByNative protected void onDidFillAutofillFormData() { - notifyFormValues(); + // The changes were caused by the autofill service autofill form, + // notified it about the result. + forceNotifyFormValues(); } - private void notifyFormValues() { + private void forceNotifyFormValues() { if (mRequest == null) return; - for (int i = 0; i < mRequest.getFieldCount(); ++i) notifyVirtualValueChanged(i); + for (int i = 0; i < mRequest.getFieldCount(); ++i) { + notifyVirtualValueChanged(i, /* forceNotify = */ true); + } + } + + @VisibleForTesting + public AutofillPopup getDatalistPopupForTesting() { + return mDatalistPopup; } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) @@ -537,4 +750,37 @@ public class AutofillProviderImpl extends AutofillProvider { field.setBoundsInContainerViewCoordinates(bounds); } } + + /** + * Send form to renderer for filling. + * + * @param nativeAutofillProvider the native autofill provider. + * @param formData the form to fill. + */ + private void autofill(long nativeAutofillProvider, FormData formData) { + AutofillProviderJni.get().onAutofillAvailable( + nativeAutofillProvider, AutofillProvider.this, formData); + } + + private void acceptDataListSuggestion(long nativeAutofillProvider, String value) { + AutofillProviderJni.get().onAcceptDataListSuggestion( + nativeAutofillProvider, AutofillProvider.this, value); + } + + private void setAnchorViewRect(long nativeAutofillProvider, View anchorView, RectF rect) { + AutofillProviderJni.get().setAnchorViewRect(nativeAutofillProvider, AutofillProvider.this, + anchorView, rect.left, rect.top, rect.width(), rect.height()); + } + + @NativeMethods + interface Natives { + void onAutofillAvailable( + long nativeAutofillProviderAndroid, AutofillProvider caller, FormData formData); + + void onAcceptDataListSuggestion( + long nativeAutofillProviderAndroid, AutofillProvider caller, String value); + + void setAnchorViewRect(long nativeAutofillProviderAndroid, AutofillProvider caller, + View anchorView, float x, float y, float width, float height); + } } diff --git a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProviderUMA.java b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProviderUMA.java index 394cd849c95..394cd849c95 100644 --- a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProviderUMA.java +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/AutofillProviderUMA.java diff --git a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/FormData.java b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/FormData.java index 8069aa887b6..8069aa887b6 100644 --- a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/FormData.java +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/FormData.java diff --git a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/FormFieldData.java b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/FormFieldData.java index 964c73206c7..eb0a94a6bce 100644 --- a/chromium/components/autofill/android/java/src/org/chromium/components/autofill/FormFieldData.java +++ b/chromium/components/autofill/android/provider/java/src/org/chromium/components/autofill/FormFieldData.java @@ -22,13 +22,17 @@ import java.lang.annotation.RetentionPolicy; public class FormFieldData { /** * Define the control types supported by android.view.autofill.AutofillValue. + * + * Android doesn't have DATALIST control, it is sent to the Autofill service as + * View.AUTOFILL_TYPE_TEXT with AutofillOptions. */ - @IntDef({ControlType.TEXT, ControlType.TOGGLE, ControlType.LIST}) + @IntDef({ControlType.TEXT, ControlType.TOGGLE, ControlType.LIST, ControlType.DATALIST}) @Retention(RetentionPolicy.SOURCE) public @interface ControlType { int TEXT = 0; int TOGGLE = 1; int LIST = 2; + int DATALIST = 3; } public final String mLabel; @@ -43,6 +47,9 @@ public class FormFieldData { public final @ControlType int mControlType; public final int mMaxLength; public final String mHeuristicType; + public final String[] mDatalistValues; + public final String[] mDatalistLabels; + // The bounds in the viewport's coordinates private final RectF mBounds; // The bounds in the container view's coordinates. @@ -58,7 +65,8 @@ public class FormFieldData { private FormFieldData(String name, String label, String value, String autocompleteAttr, boolean shouldAutocomplete, String placeholder, String type, String id, String[] optionValues, String[] optionContents, boolean isCheckField, boolean isChecked, - int maxLength, String heuristicType, float left, float top, float right, float bottom) { + int maxLength, String heuristicType, float left, float top, float right, float bottom, + String[] datalistValues, String[] datalistLabels) { mName = name; mLabel = label; mValue = value; @@ -70,8 +78,12 @@ public class FormFieldData { mOptionValues = optionValues; mOptionContents = optionContents; mIsChecked = isChecked; + mDatalistLabels = datalistLabels; + mDatalistValues = datalistValues; if (mOptionValues != null && mOptionValues.length != 0) { mControlType = ControlType.LIST; + } else if (mDatalistValues != null && mDatalistValues.length != 0) { + mControlType = ControlType.DATALIST; } else if (isCheckField) { mControlType = ControlType.TOGGLE; } else { @@ -142,9 +154,9 @@ public class FormFieldData { String autocompleteAttr, boolean shouldAutocomplete, String placeholder, String type, String id, String[] optionValues, String[] optionContents, boolean isCheckField, boolean isChecked, int maxLength, String heuristicType, float left, float top, - float right, float bottom) { + float right, float bottom, String[] datalistValues, String[] datalistLabels) { return new FormFieldData(name, label, value, autocompleteAttr, shouldAutocomplete, placeholder, type, id, optionValues, optionContents, isCheckField, isChecked, - maxLength, heuristicType, left, top, right, bottom); + maxLength, heuristicType, left, top, right, bottom, datalistValues, datalistLabels); } } diff --git a/chromium/components/autofill/android/junit/BUILD.gn b/chromium/components/autofill/android/provider/junit/BUILD.gn index 0f58b8ac6fa..95cea50bf90 100644 --- a/chromium/components/autofill/android/junit/BUILD.gn +++ b/chromium/components/autofill/android/provider/junit/BUILD.gn @@ -9,13 +9,15 @@ java_library("components_autofill_junit_tests") { # Platform checks are broken for Robolectric. See https://crbug.com/1071638. bypass_platform_checks = true testonly = true - sources = - [ "src/org/chromium/components/autofill/AutofillProviderImplTest.java" ] + sources = [ "src/org/chromium/components/autofill/AutofillProviderTest.java" ] deps = [ "//base:base_java_test_support", "//base:base_junit_test_support", - "//components/autofill/android:provider_java", + "//components/autofill/android/provider:java", "//content/public/android:content_java", + "//third_party/android_deps:robolectric_all_java", + "//third_party/junit", + "//third_party/mockito:mockito_java", "//ui/android:ui_java", ] } diff --git a/chromium/components/autofill/android/junit/src/org/chromium/components/autofill/AutofillProviderImplTest.java b/chromium/components/autofill/android/provider/junit/src/org/chromium/components/autofill/AutofillProviderTest.java index d8c8c2bfd3d..e4b12cec808 100644 --- a/chromium/components/autofill/android/junit/src/org/chromium/components/autofill/AutofillProviderImplTest.java +++ b/chromium/components/autofill/android/provider/junit/src/org/chromium/components/autofill/AutofillProviderTest.java @@ -31,11 +31,11 @@ import org.chromium.ui.display.DisplayAndroid; import java.util.ArrayList; /** - * The unit tests for AutofillProviderImpl. + * The unit tests for AutofillProvider. */ @RunWith(BaseRobolectricTestRunner.class) @Config(manifest = Config.NONE) -public class AutofillProviderImplTest { +public class AutofillProviderTest { private static final float EXPECTED_DIP_SCALE = 2; private static final int SCROLL_X = 15; private static final int SCROLL_Y = 155; @@ -46,7 +46,7 @@ public class AutofillProviderImplTest { private WindowAndroid mWindowAndroid; private WebContents mWebContents; private ViewGroup mContainerView; - private AutofillProviderImpl mAutofillProvider; + private AutofillProvider mAutofillProvider; private DisplayAndroid mDisplayAndroid; @Before @@ -57,8 +57,7 @@ public class AutofillProviderImplTest { mDisplayAndroid = Mockito.mock(DisplayAndroid.class); mWebContents = Mockito.mock(WebContents.class); mContainerView = Mockito.mock(ViewGroup.class); - mAutofillProvider = - new AutofillProviderImpl(mContext, mContainerView, "AutofillProviderImplTest"); + mAutofillProvider = new AutofillProvider(mContext, mContainerView, "AutofillProviderTest"); mAutofillProvider.setWebContents(mWebContents); when(mWebContents.getTopLevelNativeWindow()).thenReturn(mWindowAndroid); @@ -85,10 +84,10 @@ public class AutofillProviderImplTest { ArrayList<FormFieldData> fields = new ArrayList<FormFieldData>(1); fields.add(FormFieldData.createFormFieldData(null, null, null, null, false, null, null, null, null, null, false, false, 0, null, 10 /* left */, 20 /* top */, - 300 /* right */, 60 /*bottom*/)); + 300 /* right */, 60 /*bottom*/, null, null)); fields.add(FormFieldData.createFormFieldData(null, null, null, null, false, null, null, null, null, null, false, false, 0, null, 20 /* left */, 100 /* top */, - 400 /* right */, 200 /*bottom*/)); + 400 /* right */, 200 /*bottom*/, null, null)); FormData formData = new FormData(null, null, fields); mAutofillProvider.transformFormFieldToContainViewCoordinates(formData); RectF result = formData.mFields.get(0).getBoundsInContainerViewCoordinates(); diff --git a/chromium/components/autofill/content/browser/content_autofill_driver.cc b/chromium/components/autofill/content/browser/content_autofill_driver.cc index 7cf16fc27ea..ed248d98c85 100644 --- a/chromium/components/autofill/content/browser/content_autofill_driver.cc +++ b/chromium/components/autofill/content/browser/content_autofill_driver.cc @@ -288,17 +288,11 @@ void ContentAutofillDriver::DidEndTextFieldEditing() { autofill_handler_->OnDidEndTextFieldEditing(); } -void ContentAutofillDriver::SetDataList( - const std::vector<base::string16>& values, - const std::vector<base::string16>& labels) { - autofill_handler_->OnSetDataList(values, labels); -} - void ContentAutofillDriver::SelectFieldOptionsDidChange(const FormData& form) { autofill_handler_->SelectFieldOptionsDidChange(form); } -void ContentAutofillDriver::DidNavigateMainFrame( +void ContentAutofillDriver::DidNavigateFrame( content::NavigationHandle* navigation_handle) { if (navigation_handle->IsSameDocument()) return; diff --git a/chromium/components/autofill/content/browser/content_autofill_driver.h b/chromium/components/autofill/content/browser/content_autofill_driver.h index 587b09f7e65..7d201fe0deb 100644 --- a/chromium/components/autofill/content/browser/content_autofill_driver.h +++ b/chromium/components/autofill/content/browser/content_autofill_driver.h @@ -115,13 +115,11 @@ class ContentAutofillDriver : public AutofillDriver, base::TimeTicks timestamp) override; void DidPreviewAutofillFormData() override; void DidEndTextFieldEditing() override; - void SetDataList(const std::vector<base::string16>& values, - const std::vector<base::string16>& labels) override; void SelectFieldOptionsDidChange(const FormData& form) override; - // Called when the main frame has navigated. Explicitely will not trigger for - // subframe navigations. See navigation_handle.h for details. - void DidNavigateMainFrame(content::NavigationHandle* navigation_handle); + // DidNavigateFrame() is called on the frame's driver, respectively, when a + // navigation occurs in that specific frame. + void DidNavigateFrame(content::NavigationHandle* navigation_handle); AutofillManager* autofill_manager() { return autofill_manager_; } AutofillHandler* autofill_handler() { return autofill_handler_.get(); } diff --git a/chromium/components/autofill/content/browser/content_autofill_driver_factory.cc b/chromium/components/autofill/content/browser/content_autofill_driver_factory.cc index 35e691af47c..2738209f51c 100644 --- a/chromium/components/autofill/content/browser/content_autofill_driver_factory.cc +++ b/chromium/components/autofill/content/browser/content_autofill_driver_factory.cc @@ -132,18 +132,13 @@ void ContentAutofillDriverFactory::RenderFrameDeleted( void ContentAutofillDriverFactory::DidFinishNavigation( content::NavigationHandle* navigation_handle) { - // For the purposes of this code, a navigation is not important if it has not - // committed yet or if it's in a subframe. - if (!navigation_handle->HasCommitted() || - !navigation_handle->IsInMainFrame()) { - return; + if (navigation_handle->HasCommitted() && + (navigation_handle->IsInMainFrame() || + navigation_handle->HasSubframeNavigationEntryCommitted())) { + NavigationFinished(); + DriverForFrame(navigation_handle->GetRenderFrameHost()) + ->DidNavigateFrame(navigation_handle); } - - // A main frame navigation has occured. We suppress the autofill popup and - // tell the autofill driver. - NavigationFinished(); - DriverForFrame(navigation_handle->GetRenderFrameHost()) - ->DidNavigateMainFrame(navigation_handle); } void ContentAutofillDriverFactory::OnVisibilityChanged( diff --git a/chromium/components/autofill/content/browser/content_autofill_driver_unittest.cc b/chromium/components/autofill/content/browser/content_autofill_driver_unittest.cc index cc75354a844..335cde6bf11 100644 --- a/chromium/components/autofill/content/browser/content_autofill_driver_unittest.cc +++ b/chromium/components/autofill/content/browser/content_autofill_driver_unittest.cc @@ -282,7 +282,7 @@ class TestContentAutofillDriver : public ContentAutofillDriver { return static_cast<MockAutofillManager*>(autofill_manager()); } - using ContentAutofillDriver::DidNavigateMainFrame; + using ContentAutofillDriver::DidNavigateFrame; }; class ContentAutofillDriverTest : public content::RenderViewHostTestHarness { @@ -316,7 +316,7 @@ class ContentAutofillDriverTest : public content::RenderViewHostTestHarness { content::MockNavigationHandle navigation_handle(GURL(), main_rfh()); navigation_handle.set_has_committed(true); navigation_handle.set_is_same_document(same_document); - driver_->DidNavigateMainFrame(&navigation_handle); + driver_->DidNavigateFrame(&navigation_handle); } protected: diff --git a/chromium/components/autofill/content/browser/risk/fingerprint.cc b/chromium/components/autofill/content/browser/risk/fingerprint.cc index 35475b3d8a0..b7aa9f45bb9 100644 --- a/chromium/components/autofill/content/browser/risk/fingerprint.cc +++ b/chromium/components/autofill/content/browser/risk/fingerprint.cc @@ -51,6 +51,7 @@ #include "ui/display/display.h" #include "ui/display/screen.h" #include "ui/gfx/geometry/rect.h" +#include "url/gurl.h" #if BUILDFLAG(ENABLE_PLUGINS) #include "content/public/browser/plugin_service.h" @@ -316,7 +317,7 @@ FingerprintDataLoader::FingerprintDataLoader( content::GetDeviceService().BindGeolocationContext( geolocation_context_.BindNewPipeAndPassReceiver()); geolocation_context_->BindGeolocation( - geolocation_.BindNewPipeAndPassReceiver()); + geolocation_.BindNewPipeAndPassReceiver(), GURL::EmptyGURL()); geolocation_->SetHighAccuracy(false); geolocation_->QueryNextPosition( base::BindOnce(&FingerprintDataLoader::OnGotGeoposition, diff --git a/chromium/components/autofill/content/common/mojom/autofill_agent.mojom b/chromium/components/autofill/content/common/mojom/autofill_agent.mojom index 3e7e9c944a5..28b90f991f2 100644 --- a/chromium/components/autofill/content/common/mojom/autofill_agent.mojom +++ b/chromium/components/autofill/content/common/mojom/autofill_agent.mojom @@ -90,7 +90,7 @@ interface PasswordAutofillAgent { // Lets the renderer know that there are no saved credentials for filling. // This is the "no results" equivalent of FillPasswordForm. - InformNoSavedCredentials(); + InformNoSavedCredentials(bool should_show_popup_without_passwords); // Fills the given |credential| into the last focused text input. FillIntoFocusedField(bool is_password, mojo_base.mojom.String16 credential); diff --git a/chromium/components/autofill/content/common/mojom/autofill_driver.mojom b/chromium/components/autofill/content/common/mojom/autofill_driver.mojom index 96fcfbf363b..1c9d27910e0 100644 --- a/chromium/components/autofill/content/common/mojom/autofill_driver.mojom +++ b/chromium/components/autofill/content/common/mojom/autofill_driver.mojom @@ -71,10 +71,6 @@ interface AutofillDriver { // Sent when a text field is done editing. DidEndTextFieldEditing(); - - // Informs browser of data list values for the current field. - SetDataList(array<mojo_base.mojom.String16> values, - array<mojo_base.mojom.String16> labels); }; // There is one instance of this interface per web contents in the browser diff --git a/chromium/components/autofill/content/renderer/BUILD.gn b/chromium/components/autofill/content/renderer/BUILD.gn index 82ce0f34d5a..6ee2bee980b 100644 --- a/chromium/components/autofill/content/renderer/BUILD.gn +++ b/chromium/components/autofill/content/renderer/BUILD.gn @@ -80,6 +80,7 @@ jumbo_static_library("test_support") { "//components/autofill/content/renderer", "//services/service_manager/public/cpp", "//skia", + "//testing/gmock", "//testing/gtest", "//third_party/blink/public:blink", ] diff --git a/chromium/components/autofill/content/renderer/autofill_agent.cc b/chromium/components/autofill/content/renderer/autofill_agent.cc index 307fa136447..39e65c63370 100644 --- a/chromium/components/autofill/content/renderer/autofill_agent.cc +++ b/chromium/components/autofill/content/renderer/autofill_agent.cc @@ -86,6 +86,7 @@ using blink::WebVector; namespace autofill { +using form_util::ExtractMask; using form_util::FindFormAndFieldForFormControlElement; using form_util::UnownedCheckoutFormElementsAndFieldSetsToFormData; using mojom::SubmissionSource; @@ -98,32 +99,12 @@ namespace { // upon, instead of multiple in close succession (debounce time). size_t kWaitTimeForSelectOptionsChangesMs = 50; -// Gets all the data list values (with corresponding label) for the given -// element. -void GetDataListSuggestions(const WebInputElement& element, - std::vector<base::string16>* values, - std::vector<base::string16>* labels) { - for (const auto& option : element.FilteredDataListOptions()) { - values->push_back(option.Value().Utf16()); - if (option.Value() != option.Label()) - labels->push_back(option.Label().Utf16()); - else - labels->push_back(base::string16()); - } -} - -// Trim the vector before sending it to the browser process to ensure we -// don't send too much data through the IPC. -void TrimStringVectorForIPC(std::vector<base::string16>* strings) { - // Limit the size of the vector. - if (strings->size() > kMaxListSize) - strings->resize(kMaxListSize); - - // Limit the size of the strings in the vector. - for (size_t i = 0; i < strings->size(); ++i) { - if ((*strings)[i].length() > kMaxDataLength) - (*strings)[i].resize(kMaxDataLength); - } +// Helper function to return EXTRACT_DATALIST if kAutofillExtractAllDatalist is +// enabled, otherwise EXTRACT_NONE is returned. +ExtractMask GetExtractDatalistMask() { + return base::FeatureList::IsEnabled(features::kAutofillExtractAllDatalists) + ? form_util::EXTRACT_DATALIST + : form_util::EXTRACT_NONE; } } // namespace @@ -160,6 +141,9 @@ AutofillAgent::AutofillAgent(content::RenderFrame* render_frame, &AutofillAgent::BindPendingReceiver, base::Unretained(this))); } +// The destructor is not guaranteed to be called. Destruction happens (only) +// through the OnDestruct() event, which posts a task to delete this object. +// The process may be killed before this deletion can happen. AutofillAgent::~AutofillAgent() { RemoveFormObserver(this); } @@ -175,17 +159,13 @@ bool AutofillAgent::FormDataCompare::operator()(const FormData& lhs, std::tie(rhs.name, rhs.url, rhs.action, rhs.is_form_tag); } -void AutofillAgent::DidCommitProvisionalLoad(bool is_same_document_navigation, - ui::PageTransition transition) { +void AutofillAgent::DidCommitProvisionalLoad(ui::PageTransition transition) { blink::WebFrame* frame = render_frame()->GetWebFrame(); // TODO(dvadym): check if we need to check if it is main frame navigation // http://crbug.com/443155 if (frame->Parent()) return; // Not a top-level navigation. - if (is_same_document_navigation) - return; - // Navigation to a new page or a page refresh. element_.Reset(); @@ -225,9 +205,11 @@ void AutofillAgent::DidChangeScrollOffsetImpl( FormData form; FormFieldData field; - if (FindFormAndFieldForFormControlElement(element_, field_data_manager_.get(), - form_util::EXTRACT_BOUNDS, &form, - &field)) { + if (FindFormAndFieldForFormControlElement( + element_, field_data_manager_.get(), + static_cast<ExtractMask>(form_util::EXTRACT_BOUNDS | + GetExtractDatalistMask()), + &form, &field)) { GetAutofillDriver()->TextFieldDidScroll(form, field, field.bounds); } @@ -237,14 +219,6 @@ void AutofillAgent::DidChangeScrollOffsetImpl( void AutofillAgent::FocusedElementChanged(const WebElement& element) { was_focused_before_now_ = false; - - if ((IsKeyboardAccessoryEnabled() || !focus_requires_scroll_) && - !element.IsNull() && - element.GetDocument().GetFrame()->HasTransientUserActivation()) { - focused_node_was_last_clicked_ = true; - HandleFocusChangeComplete(); - } - HidePopup(); if (element.IsNull()) { @@ -258,14 +232,30 @@ void AutofillAgent::FocusedElementChanged(const WebElement& element) { const WebInputElement* input = ToWebInputElement(&element); + bool focus_moved_to_new_form = false; if (!last_interacted_form_.IsNull() && (!input || last_interacted_form_ != input->Form())) { // The focused element is not part of the last interacted form (could be // in a different form). GetAutofillDriver()->FocusNoLongerOnForm(); - return; + focus_moved_to_new_form = true; + } + + // Calls HandleFocusChangeComplete() after notifying the focus is no longer on + // the previous form, then early return. No need to notify the newly focused + // element because that will be done by HandleFocusChangeComplete() which + // triggers FormControlElementClicked(). + // Refer to http://crbug.com/1105254 + if ((IsKeyboardAccessoryEnabled() || !focus_requires_scroll_) && + !element.IsNull() && + element.GetDocument().GetFrame()->HasTransientUserActivation()) { + focused_node_was_last_clicked_ = true; + HandleFocusChangeComplete(); } + if (focus_moved_to_new_form) + return; + if (!input || !input->IsEnabled() || input->IsReadOnly() || !input->IsTextField()) return; @@ -274,9 +264,11 @@ void AutofillAgent::FocusedElementChanged(const WebElement& element) { FormData form; FormFieldData field; - if (FindFormAndFieldForFormControlElement(element_, field_data_manager_.get(), - form_util::EXTRACT_BOUNDS, &form, - &field)) { + if (FindFormAndFieldForFormControlElement( + element_, field_data_manager_.get(), + static_cast<ExtractMask>(form_util::EXTRACT_BOUNDS | + GetExtractDatalistMask()), + &form, &field)) { GetAutofillDriver()->FocusOnFormField(form, field, field.bounds); } } @@ -355,9 +347,11 @@ void AutofillAgent::OnTextFieldDidChange(const WebInputElement& element) { FormData form; FormFieldData field; - if (FindFormAndFieldForFormControlElement(element, field_data_manager_.get(), - form_util::EXTRACT_BOUNDS, &form, - &field)) { + if (FindFormAndFieldForFormControlElement( + element, field_data_manager_.get(), + static_cast<ExtractMask>(form_util::EXTRACT_BOUNDS | + GetExtractDatalistMask()), + &form, &field)) { GetAutofillDriver()->TextFieldDidChange(form, field, field.bounds, AutofillTickClock::NowTicks()); } @@ -616,9 +610,8 @@ bool AutofillAgent::CollectFormlessElements(FormData* output) { if (control_elements.size() > kMaxParseableFields) return false; - const form_util::ExtractMask extract_mask = - static_cast<form_util::ExtractMask>(form_util::EXTRACT_VALUE | - form_util::EXTRACT_OPTIONS); + const ExtractMask extract_mask = static_cast<ExtractMask>( + form_util::EXTRACT_VALUE | form_util::EXTRACT_OPTIONS); return UnownedCheckoutFormElementsAndFieldSetsToFormData( fieldsets, control_elements, nullptr, document, field_data_manager_.get(), @@ -784,15 +777,18 @@ void AutofillAgent::QueryAutofillSuggestions( FormData form; FormFieldData field; - if (!FindFormAndFieldForFormControlElement(element, field_data_manager_.get(), - form_util::EXTRACT_BOUNDS, &form, - &field)) { + if (!FindFormAndFieldForFormControlElement( + element, field_data_manager_.get(), + static_cast<ExtractMask>(form_util::EXTRACT_BOUNDS | + GetExtractDatalistMask()), + &form, &field)) { // If we didn't find the cached form, at least let autocomplete have a shot // at providing suggestions. WebFormControlElementToFormField( element, nullptr, - static_cast<form_util::ExtractMask>(form_util::EXTRACT_VALUE | - form_util::EXTRACT_BOUNDS), + static_cast<ExtractMask>(form_util::EXTRACT_VALUE | + form_util::EXTRACT_BOUNDS | + GetExtractDatalistMask()), &field); } @@ -803,20 +799,15 @@ void AutofillAgent::QueryAutofillSuggestions( return; } - std::vector<base::string16> data_list_values; - std::vector<base::string16> data_list_labels; - const WebInputElement* input_element = ToWebInputElement(&element); - if (input_element) { - // Find the datalist values and send them to the browser process. - GetDataListSuggestions(*input_element, &data_list_values, - &data_list_labels); - TrimStringVectorForIPC(&data_list_values); - TrimStringVectorForIPC(&data_list_labels); + if (!base::FeatureList::IsEnabled(features::kAutofillExtractAllDatalists)) { + if (const WebInputElement* input_element = ToWebInputElement(&element)) { + // Find the datalist values and send them to the browser process. + form_util::GetDataListSuggestions(*input_element, &field.datalist_values, + &field.datalist_labels); + } } is_popup_possibly_visible_ = true; - - GetAutofillDriver()->SetDataList(data_list_values, data_list_labels); GetAutofillDriver()->QueryFormFieldAutofill(autofill_query_id_, form, field, field.bounds, autoselect_first_suggestion); @@ -1041,7 +1032,9 @@ void AutofillAgent::OnProvisionallySaveForm( FormData form; FormFieldData field; if (FindFormAndFieldForFormControlElement( - element, field_data_manager_.get(), form_util::EXTRACT_BOUNDS, + element, field_data_manager_.get(), + static_cast<ExtractMask>(form_util::EXTRACT_BOUNDS | + GetExtractDatalistMask()), &form, &field)) { GetAutofillDriver()->SelectControlDidChange(form, field, field.bounds); } diff --git a/chromium/components/autofill/content/renderer/autofill_agent.h b/chromium/components/autofill/content/renderer/autofill_agent.h index b0e7943afb2..be2b158814f 100644 --- a/chromium/components/autofill/content/renderer/autofill_agent.h +++ b/chromium/components/autofill/content/renderer/autofill_agent.h @@ -168,8 +168,7 @@ class AutofillAgent : public content::RenderFrameObserver, }; // content::RenderFrameObserver: - void DidCommitProvisionalLoad(bool is_same_document_navigation, - ui::PageTransition transition) override; + void DidCommitProvisionalLoad(ui::PageTransition transition) override; void DidFinishDocumentLoad() override; void DidChangeScrollOffset() override; void FocusedElementChanged(const blink::WebElement& element) override; diff --git a/chromium/components/autofill/content/renderer/focus_test_utils.h b/chromium/components/autofill/content/renderer/focus_test_utils.h index a20563153b8..038e8fcedb0 100644 --- a/chromium/components/autofill/content/renderer/focus_test_utils.h +++ b/chromium/components/autofill/content/renderer/focus_test_utils.h @@ -5,9 +5,9 @@ #ifndef COMPONENTS_AUTOFILL_CONTENT_RENDERER_FOCUS_TEST_UTILS_H_ #define COMPONENTS_AUTOFILL_CONTENT_RENDERER_FOCUS_TEST_UTILS_H_ -#include "base/callback.h" -#include "string" +#include <string> +#include "base/callback.h" #include "third_party/blink/public/web/web_document.h" namespace autofill { diff --git a/chromium/components/autofill/content/renderer/form_autofill_util.cc b/chromium/components/autofill/content/renderer/form_autofill_util.cc index c1f47cb9575..35f247f215c 100644 --- a/chromium/components/autofill/content/renderer/form_autofill_util.cc +++ b/chromium/components/autofill/content/renderer/form_autofill_util.cc @@ -1470,11 +1470,6 @@ bool UnownedFormElementsAndFieldSetsToFormData( FormData* form, FormFieldData* field) { form->url = GetCanonicalOriginForDocument(document); - if (IsAutofillFieldMetadataEnabled() && !document.Body().IsNull()) { - SCOPED_UMA_HISTOGRAM_TIMER( - "PasswordManager.ButtonTitlePerformance.NoFormTag"); - form->button_titles = InferButtonTitlesForForm(document.Body()); - } if (document.GetFrame() && document.GetFrame()->Top()) { form->main_frame_origin = document.GetFrame()->Top()->GetSecurityOrigin(); } else { @@ -1514,6 +1509,20 @@ bool ScriptModifiedUsernameAcceptable( return field_data_manager->FindMachedValue(value); } +// Trim the vector before sending it to the browser process to ensure we +// don't send too much data through the IPC. +void TrimStringVectorForIPC(std::vector<base::string16>* strings) { + // Limit the size of the vector. + if (strings->size() > kMaxListSize) + strings->resize(kMaxListSize); + + // Limit the size of the strings in the vector. + for (auto& string : *strings) { + if (string.length() > kMaxDataLength) + string.resize(kMaxDataLength); + } +} + // Helper function that strips any authentication data, as well as query and // ref portions of URL. GURL StripAuthAndParams(const GURL& gurl) { @@ -1527,6 +1536,20 @@ GURL StripAuthAndParams(const GURL& gurl) { } // namespace +void GetDataListSuggestions(const WebInputElement& element, + std::vector<base::string16>* values, + std::vector<base::string16>* labels) { + for (const auto& option : element.FilteredDataListOptions()) { + values->push_back(option.Value().Utf16()); + if (option.Value() != option.Label()) + labels->push_back(option.Label().Utf16()); + else + labels->push_back(base::string16()); + } + TrimStringVectorForIPC(values); + TrimStringVectorForIPC(labels); +} + bool ExtractFormData(const WebFormElement& form_element, const FieldDataManager& field_data_manager, FormData* data) { @@ -1583,7 +1606,7 @@ GURL GetCanonicalOriginForDocument(const WebDocument& document) { return StripAuthAndParams(full_origin); } -GURL GetOriginWithoutAuthForDocument(const WebDocument& document) { +GURL GetDocumentUrlWithoutAuth(const WebDocument& document) { GURL::Replacements rep; rep.ClearUsername(); rep.ClearPassword(); @@ -1647,6 +1670,11 @@ base::string16 GetFormIdentifier(const WebFormElement& form) { return identifier; } +FormRendererId GetFormRendererId(const blink::WebFormElement& form) { + return form.IsNull() ? FormRendererId() + : FormRendererId(form.UniqueRendererFormId()); +} + base::i18n::TextDirection GetTextDirectionForElement( const blink::WebFormControlElement& element) { // Use 'text-align: left|right' if set or 'direction' otherwise. @@ -1766,6 +1794,12 @@ void WebFormControlElementToFormField( } } } + if (extract_mask & EXTRACT_DATALIST) { + if (auto* input = blink::ToWebInputElement(&element)) { + GetDataListSuggestions(*input, &field->datalist_values, + &field->datalist_labels); + } + } if (!(extract_mask & EXTRACT_VALUE)) return; @@ -1834,11 +1868,6 @@ bool WebFormElementToFormData( form->action = GetCanonicalActionForForm(form_element); form->is_action_empty = form_element.Action().IsNull() || form_element.Action().IsEmpty(); - if (IsAutofillFieldMetadataEnabled()) { - SCOPED_UMA_HISTOGRAM_TIMER( - "PasswordManager.ButtonTitlePerformance.HasFormTag"); - form->button_titles = InferButtonTitlesForForm(form_element); - } if (frame->Top()) { form->main_frame_origin = frame->Top()->GetSecurityOrigin(); } else { @@ -2179,6 +2208,41 @@ base::string16 FindChildText(const WebNode& node) { return FindChildTextWithIgnoreList(node, std::set<WebNode>()); } +ButtonTitleList GetButtonTitles(const WebFormElement& web_form, + const WebDocument& document, + ButtonTitlesCache* button_titles_cache) { + DCHECK(button_titles_cache); + if (!IsAutofillFieldMetadataEnabled() && web_form.IsNull()) + return ButtonTitleList(); + + // True if the cache has no entry for |web_form|. + bool cache_miss = true; + // Iterator pointing to the entry for |web_form| if the entry for |web_form| + // is found. + ButtonTitlesCache::iterator form_position; + std::tie(form_position, cache_miss) = button_titles_cache->emplace( + GetFormRendererId(web_form), ButtonTitleList()); + if (!cache_miss) + return form_position->second; + + ButtonTitleList button_titles; + DCHECK(!web_form.IsNull() || !document.IsNull()); + if (web_form.IsNull()) { + const WebElement& body = document.Body(); + if (!body.IsNull()) { + SCOPED_UMA_HISTOGRAM_TIMER( + "PasswordManager.ButtonTitlePerformance.NoFormTag"); + button_titles = InferButtonTitlesForForm(body); + } + } else { + SCOPED_UMA_HISTOGRAM_TIMER( + "PasswordManager.ButtonTitlePerformance.HasFormTag"); + button_titles = InferButtonTitlesForForm(web_form); + } + form_position->second = std::move(button_titles); + return form_position->second; +} + base::string16 FindChildTextWithIgnoreListForTesting( const WebNode& node, const std::set<WebNode>& divs_to_skip) { @@ -2192,10 +2256,6 @@ bool InferLabelForElementForTesting(const WebFormControlElement& element, return InferLabelForElement(element, stop_words, label, label_source); } -ButtonTitleList InferButtonTitlesForTesting(const WebElement& form_element) { - return InferButtonTitlesForForm(form_element); -} - WebFormElement FindFormByUniqueRendererId(WebDocument doc, FormRendererId form_renderer_id) { for (const auto& form : doc.Forms()) { diff --git a/chromium/components/autofill/content/renderer/form_autofill_util.h b/chromium/components/autofill/content/renderer/form_autofill_util.h index 877d8aa285b..1a33e640255 100644 --- a/chromium/components/autofill/content/renderer/form_autofill_util.h +++ b/chromium/components/autofill/content/renderer/form_autofill_util.h @@ -10,6 +10,7 @@ #include <set> #include <vector> +#include "base/containers/flat_map.h" #include "base/i18n/rtl.h" #include "base/macros.h" #include "base/strings/string16.h" @@ -45,6 +46,10 @@ class FieldDataManager; namespace form_util { +// Mapping from a form element's render id to results of button titles +// heuristics for a given form element. +using ButtonTitlesCache = base::flat_map<FormRendererId, ButtonTitleList>; + // A bit field mask to extract data from WebFormControlElement. // Copied to components/autofill/ios/browser/resources/autofill_controller.js. enum ExtractMask { @@ -59,8 +64,18 @@ enum ExtractMask { // WebFormControlElement. EXTRACT_BOUNDS = 1 << 3, // Extract bounds from WebFormControlElement, // could trigger layout if needed. + EXTRACT_DATALIST = 1 << 4, // Extract datalist from WebFormControlElement, + // the total number of options is up to + // kMaxListSize and each option has as far as + // kMaxDataLength. }; +// Gets up to kMaxListSize data list values (with corresponding label) for the +// given element, each value and label have as far as kMaxDataLength. +void GetDataListSuggestions(const blink::WebInputElement& element, + std::vector<base::string16>* values, + std::vector<base::string16>* labels); + // Extract FormData from the form element and return whether the operation was // successful. bool ExtractFormData(const blink::WebFormElement& form_element, @@ -89,7 +104,7 @@ bool AreFormContentsVisible(const blink::WebFormElement& form); // strip unnecessary data (e.g. query params and HTTP credentials). GURL GetCanonicalActionForForm(const blink::WebFormElement& form); GURL GetCanonicalOriginForDocument(const blink::WebDocument& document); -GURL GetOriginWithoutAuthForDocument(const blink::WebDocument& document); +GURL GetDocumentUrlWithoutAuth(const blink::WebDocument& document); // Returns true if |element| is a month input element. bool IsMonthInput(const blink::WebInputElement* element); @@ -123,6 +138,10 @@ bool IsWebElementVisible(const blink::WebElement& element); // attribute. base::string16 GetFormIdentifier(const blink::WebFormElement& form); +// Returns the |unique_renderer_id| of a given |WebFormElement|. If +// |WebFormElement::IsNull()|, returns a null renderer ID. +FormRendererId GetFormRendererId(const blink::WebFormElement& form); + // Returns text alignment for |element|. base::i18n::TextDirection GetTextDirectionForElement( const blink::WebFormControlElement& element); @@ -273,6 +292,15 @@ void PreviewSuggestion(const base::string16& suggestion, // Whitespace is trimmed from text accumulated at descendant nodes. base::string16 FindChildText(const blink::WebNode& node); +// Returns the button titles for |web_form| (or unowned buttons in |document| if +// |web_form| is null). |button_titles_cache| can be used to spare recomputation +// if called multiple times for the same form. Button titles computation for +// unowned buttons is enabled only in Dev and Canary (crbug.com/1086446), +// otherwise the method returns an empty list. +ButtonTitleList GetButtonTitles(const blink::WebFormElement& web_form, + const blink::WebDocument& document, + ButtonTitlesCache* button_titles_cache); + // Exposed for testing purpose base::string16 FindChildTextWithIgnoreListForTesting( const blink::WebNode& node, @@ -281,8 +309,6 @@ bool InferLabelForElementForTesting(const blink::WebFormControlElement& element, const std::vector<base::char16>& stop_words, base::string16* label, FormFieldData::LabelSource* label_source); -ButtonTitleList InferButtonTitlesForTesting( - const blink::WebElement& form_element); // Returns form by unique renderer id. Return null element if there is no form // with given form renderer id. diff --git a/chromium/components/autofill/content/renderer/form_autofill_util_browsertest.cc b/chromium/components/autofill/content/renderer/form_autofill_util_browsertest.cc index 9693e877d2f..34575e77b47 100644 --- a/chromium/components/autofill/content/renderer/form_autofill_util_browsertest.cc +++ b/chromium/components/autofill/content/renderer/form_autofill_util_browsertest.cc @@ -4,12 +4,15 @@ #include "components/autofill/content/renderer/form_autofill_util.h" +#include "base/metrics/field_trial.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" +#include "base/test/scoped_feature_list.h" #include "components/autofill/core/common/mojom/autofill_types.mojom-shared.h" #include "components/autofill/core/common/renderer_id.h" #include "content/public/test/render_view_test.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/platform/web_string.h" #include "third_party/blink/public/platform/web_vector.h" @@ -138,6 +141,14 @@ const char kDivTableExample6[] = // TODO(crbug.com/796918): Should be "label" or "label-" const char kDivTableExample6Expected[] = ""; +void VerifyButtonTitleCache(const WebFormElement& form_target, + const ButtonTitleList& expected_button_titles, + const ButtonTitlesCache& actual_cache) { + EXPECT_THAT(actual_cache, + testing::ElementsAre(testing::Pair(GetFormRendererId(form_target), + expected_button_titles))); +} + class FormAutofillUtilsTest : public content::RenderViewTest { public: FormAutofillUtilsTest() {} @@ -272,8 +283,8 @@ TEST_F(FormAutofillUtilsTest, InferLabelSourceTest) { } } -TEST_F(FormAutofillUtilsTest, InferButtonTitleForFormTest) { - const char kHtml[] = +TEST_F(FormAutofillUtilsTest, GetButtonTitles) { + constexpr char kHtml[] = "<form id='target'>" " <input type='button' value='Clear field'>" " <input type='button' value='Clear field'>" @@ -294,8 +305,11 @@ TEST_F(FormAutofillUtilsTest, InferButtonTitleForFormTest) { ASSERT_FALSE(target.IsNull()); const WebFormElement& form_target = target.ToConst<WebFormElement>(); ASSERT_FALSE(form_target.IsNull()); + ButtonTitlesCache cache; + + autofill::ButtonTitleList actual = + GetButtonTitles(form_target, web_frame->GetDocument(), &cache); - autofill::ButtonTitleList actual = InferButtonTitlesForTesting(form_target); autofill::ButtonTitleList expected = { {base::UTF8ToUTF16("Clear field"), ButtonTitleType::INPUT_ELEMENT_BUTTON_TYPE}, @@ -309,9 +323,11 @@ TEST_F(FormAutofillUtilsTest, InferButtonTitleForFormTest) { {base::UTF8ToUTF16("Join"), ButtonTitleType::DIV}, {base::UTF8ToUTF16("Start"), ButtonTitleType::SPAN}}; EXPECT_EQ(expected, actual); + + VerifyButtonTitleCache(form_target, expected, cache); } -TEST_F(FormAutofillUtilsTest, InferButtonTitleForFormTest_TooLongTitle) { +TEST_F(FormAutofillUtilsTest, GetButtonTitles_TooLongTitle) { std::string title; for (int i = 0; i < 300; ++i) title += "a"; @@ -330,8 +346,10 @@ TEST_F(FormAutofillUtilsTest, InferButtonTitleForFormTest_TooLongTitle) { ASSERT_FALSE(target.IsNull()); const WebFormElement& form_target = target.ToConst<WebFormElement>(); ASSERT_FALSE(form_target.IsNull()); + ButtonTitlesCache cache; - autofill::ButtonTitleList actual = InferButtonTitlesForTesting(form_target); + autofill::ButtonTitleList actual = + GetButtonTitles(form_target, web_frame->GetDocument(), &cache); int total_length = 0; for (auto title : actual) { @@ -341,8 +359,14 @@ TEST_F(FormAutofillUtilsTest, InferButtonTitleForFormTest_TooLongTitle) { EXPECT_EQ(200, total_length); } -TEST_F(FormAutofillUtilsTest, InferButtonTitle_Formless) { - const char kNoFormHtml[] = +TEST_F(FormAutofillUtilsTest, GetButtonTitles_Formless) { + // Button titles computation and crowdsourcing for <form>less forms are + // enabled only if |AutofillFieldMetadata| (Dev and Canary) is enabled. + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.Init(); + base::FieldTrialList::CreateFieldTrial("AutofillFieldMetadata", "Enabled"); + + constexpr char kNoFormHtml[] = "<div class='reg-form'>" " <input type='button' value='\n Show\t password '>" " <button>Sign Up</button>" @@ -358,10 +382,12 @@ TEST_F(FormAutofillUtilsTest, InferButtonTitle_Formless) { LoadHTML(kNoFormHtml); WebLocalFrame* web_frame = GetMainFrame(); ASSERT_NE(nullptr, web_frame); - const WebElement& body = web_frame->GetDocument().Body(); - ASSERT_FALSE(body.IsNull()); + WebFormElement form_target; + ASSERT_FALSE(web_frame->GetDocument().Body().IsNull()); + ButtonTitlesCache cache; - autofill::ButtonTitleList actual = InferButtonTitlesForTesting(body); + autofill::ButtonTitleList actual = + GetButtonTitles(form_target, web_frame->GetDocument(), &cache); autofill::ButtonTitleList expected = { {base::UTF8ToUTF16("Show password"), ButtonTitleType::INPUT_ELEMENT_BUTTON_TYPE}, @@ -370,6 +396,42 @@ TEST_F(FormAutofillUtilsTest, InferButtonTitle_Formless) { {base::UTF8ToUTF16("Register"), ButtonTitleType::BUTTON_ELEMENT_BUTTON_TYPE}}; EXPECT_EQ(expected, actual); + + VerifyButtonTitleCache(form_target, expected, cache); +} + +TEST_F(FormAutofillUtilsTest, GetButtonTitles_Formless_DisabledByDefault) { + // Button titles computation and crowdsourcing for <form>less forms should be + // disabled if |AutofillFieldMetadata| is disabled. + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.Init(); + base::FieldTrialList::CreateFieldTrial("AutofillFieldMetadata", "Disabled"); + + constexpr char kNoFormHtml[] = + "<div class='reg-form'>" + " <input type='button' value='\n Show\t password '>" + " <button>Sign Up</button>" + " <button type='button'>Register</button>" + "</div>" + "<form id='ignored-form'>" + " <input type='button' value='Ignore this'>" + " <button>Ignore this</button>" + " <a id='Submit' value='Ignore this'>" + " <div name='BTN'>Ignore this</div>" + "</form>"; + + LoadHTML(kNoFormHtml); + WebLocalFrame* web_frame = GetMainFrame(); + ASSERT_NE(nullptr, web_frame); + WebFormElement form_target; + ASSERT_FALSE(web_frame->GetDocument().Body().IsNull()); + ButtonTitlesCache cache; + + autofill::ButtonTitleList actual = + GetButtonTitles(form_target, web_frame->GetDocument(), &cache); + + EXPECT_TRUE(actual.empty()); + EXPECT_TRUE(cache.empty()); } TEST_F(FormAutofillUtilsTest, IsEnabled) { @@ -788,6 +850,84 @@ TEST_F(FormAutofillUtilsTest, ExtractUnownedBounds) { EXPECT_FALSE(form_data.fields.back().bounds.IsEmpty()); } +TEST_F(FormAutofillUtilsTest, GetDataListSuggestions) { + LoadHTML( + "<body><input list='datalist_id' name='count' id='i1'><datalist " + "id='datalist_id'><option value='1'><option " + "value='2'></datalist></body>"); + WebDocument doc = GetMainFrame()->GetDocument(); + auto web_control = doc.GetElementById("i1").To<WebInputElement>(); + std::vector<base::string16> values; + std::vector<base::string16> labels; + GetDataListSuggestions(web_control, &values, &labels); + ASSERT_EQ(values.size(), 2u); + ASSERT_EQ(labels.size(), 2u); + EXPECT_EQ(values[0], base::UTF8ToUTF16("1")); + EXPECT_EQ(values[1], base::UTF8ToUTF16("2")); + EXPECT_EQ(labels[0], base::UTF8ToUTF16("")); + EXPECT_EQ(labels[1], base::UTF8ToUTF16("")); +} + +TEST_F(FormAutofillUtilsTest, GetDataListSuggestionsWithLabels) { + LoadHTML( + "<body><input list='datalist_id' name='count' id='i1'><datalist " + "id='datalist_id'><option value='1'>one</option><option " + "value='2'>two</option></datalist></body>"); + WebDocument doc = GetMainFrame()->GetDocument(); + auto web_control = doc.GetElementById("i1").To<WebInputElement>(); + std::vector<base::string16> values; + std::vector<base::string16> labels; + GetDataListSuggestions(web_control, &values, &labels); + ASSERT_EQ(values.size(), 2u); + ASSERT_EQ(labels.size(), 2u); + EXPECT_EQ(values[0], base::UTF8ToUTF16("1")); + EXPECT_EQ(values[1], base::UTF8ToUTF16("2")); + EXPECT_EQ(labels[0], base::UTF8ToUTF16("one")); + EXPECT_EQ(labels[1], base::UTF8ToUTF16("two")); +} + +TEST_F(FormAutofillUtilsTest, ExtractDataList) { + LoadHTML( + "<body><input list='datalist_id' name='count' id='i1'><datalist " + "id='datalist_id'><option value='1'>one</option><option " + "value='2'>two</option></datalist></body>"); + WebDocument doc = GetMainFrame()->GetDocument(); + auto web_control = doc.GetElementById("i1").To<WebInputElement>(); + FormData form_data; + FormFieldData form_field_data; + ASSERT_TRUE(FindFormAndFieldForFormControlElement( + web_control, nullptr /*field_data_manager*/, EXTRACT_DATALIST, &form_data, + &form_field_data)); + + auto& values = form_data.fields.back().datalist_values; + auto& labels = form_data.fields.back().datalist_labels; + ASSERT_EQ(values.size(), 2u); + ASSERT_EQ(labels.size(), 2u); + EXPECT_EQ(values[0], base::UTF8ToUTF16("1")); + EXPECT_EQ(values[1], base::UTF8ToUTF16("2")); + EXPECT_EQ(labels[0], base::UTF8ToUTF16("one")); + EXPECT_EQ(labels[1], base::UTF8ToUTF16("two")); + EXPECT_EQ(form_field_data.datalist_values, values); + EXPECT_EQ(form_field_data.datalist_labels, labels); +} + +TEST_F(FormAutofillUtilsTest, NotExtractDataList) { + LoadHTML( + "<body><input list='datalist_id' name='count' id='i1'><datalist " + "id='datalist_id'><option value='1'>one</option><option " + "value='2'>two</option></datalist></body>"); + WebDocument doc = GetMainFrame()->GetDocument(); + auto web_control = doc.GetElementById("i1").To<WebInputElement>(); + FormData form_data; + FormFieldData form_field_data; + ASSERT_TRUE(FindFormAndFieldForFormControlElement( + web_control, nullptr /*field_data_manager*/, &form_data, + &form_field_data)); + + EXPECT_TRUE(form_data.fields.back().datalist_values.empty()); + EXPECT_TRUE(form_data.fields.back().datalist_labels.empty()); +} + } // namespace } // namespace form_util } // namespace autofill diff --git a/chromium/components/autofill/content/renderer/form_cache.cc b/chromium/components/autofill/content/renderer/form_cache.cc index 1bde0197a15..fe638091f41 100644 --- a/chromium/components/autofill/content/renderer/form_cache.cc +++ b/chromium/components/autofill/content/renderer/form_cache.cc @@ -18,6 +18,7 @@ #include "base/strings/string_split.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" +#include "base/strings/string_number_conversions.h" #include "components/autofill/content/renderer/form_autofill_util.h" #include "components/autofill/content/renderer/page_form_analyser_logger.h" #include "components/autofill/core/common/autofill_constants.h" @@ -420,37 +421,19 @@ bool FormCache::ShowPredictions(const FormDataPredictions& form, std::vector<WebFormControlElement> control_elements; - // First check the synthetic form. - bool found_synthetic_form = false; - if (form.data.SameFormAs(synthetic_form_)) { - found_synthetic_form = true; + if (form.data.unique_renderer_id.is_null()) { // Form is synthetic. WebDocument document = frame_->GetDocument(); control_elements = form_util::GetUnownedAutofillableFormFieldElements( document.All(), nullptr); - } - - if (!found_synthetic_form) { - // Find the real form by searching through the WebDocuments. - bool found_form = false; - + } else { for (const WebFormElement& form_element : frame_->GetDocument().Forms()) { - // To match two forms, we look for the form's name and the number of - // fields on that form. (Form names may not be unique.) - // Note: WebString() == WebString(string16()) does not evaluate to |true| - // -- WebKit distinguishes between a "null" string (lhs) and an "empty" - // string (rhs). We don't want that distinction, so forcing to string16. - base::string16 element_name = form_util::GetFormIdentifier(form_element); - if (element_name == form.data.name) { - found_form = true; + FormRendererId form_id(form_element.UniqueRendererFormId()); + if (form_id == form.data.unique_renderer_id) { control_elements = form_util::ExtractAutofillableElementsInForm(form_element); - if (control_elements.size() == form.fields.size()) - break; + break; } } - - if (!found_form) - return false; } if (control_elements.size() != form.fields.size()) { @@ -464,11 +447,9 @@ bool FormCache::ShowPredictions(const FormDataPredictions& form, WebFormControlElement& element = control_elements[i]; const FormFieldData& field_data = form.data.fields[i]; - if (element.NameForAutofill().Utf16() != field_data.name) { - // Keep things simple. Don't show predictions for elements whose names - // were modified between page load and the server's response to our query. + FieldRendererId field_id(element.UniqueRendererFormControlId()); + if (field_id != field_data.unique_renderer_id) continue; - } const FormFieldDataPredictions& field = form.fields[i]; // Possibly add a console warning for this field regarding the usage of @@ -492,6 +473,11 @@ bool FormCache::ShowPredictions(const FormDataPredictions& form, const base::string16 truncated_label = field_data.label.substr( 0, std::min(field_data.label.length(), kMaxLabelSize)); + std::string form_id = + base::NumberToString(form.data.unique_renderer_id.value()); + std::string field_id = + base::NumberToString(field.field.unique_renderer_id.value()); + std::string title = base::StrCat({"overall type: ", field.overall_type, // "\nserver type: ", field.server_type, // @@ -500,7 +486,9 @@ bool FormCache::ShowPredictions(const FormDataPredictions& form, "\nparseable name: ", field.parseable_name, // "\nsection: ", field.section, // "\nfield signature: ", field.signature, // - "\nform signature: ", form.signature}); + "\nform signature: ", form.signature, // + "\nform renderer id: ", form_id, // + "\nfield renderer id: ", field_id}); // Set this debug string to the title so that a developer can easily debug // by hovering the mouse over the input field. diff --git a/chromium/components/autofill/content/renderer/form_tracker.cc b/chromium/components/autofill/content/renderer/form_tracker.cc index e077f40a8b7..8613e8d3938 100644 --- a/chromium/components/autofill/content/renderer/form_tracker.cc +++ b/chromium/components/autofill/content/renderer/form_tracker.cc @@ -126,14 +126,13 @@ void FormTracker::FormControlDidChangeImpl( } } -void FormTracker::DidCommitProvisionalLoad(bool is_same_document_navigation, - ui::PageTransition transition) { +void FormTracker::DidCommitProvisionalLoad(ui::PageTransition transition) { DCHECK_CALLED_ON_VALID_SEQUENCE(form_tracker_sequence_checker_); - if (!is_same_document_navigation) { - ResetLastInteractedElements(); - return; - } + ResetLastInteractedElements(); +} +void FormTracker::DidFinishSameDocumentNavigation() { + DCHECK_CALLED_ON_VALID_SEQUENCE(form_tracker_sequence_checker_); FireSubmissionIfFormDisappear(SubmissionSource::SAME_DOCUMENT_NAVIGATION); } diff --git a/chromium/components/autofill/content/renderer/form_tracker.h b/chromium/components/autofill/content/renderer/form_tracker.h index ed4e403ff26..1ac3a8a1272 100644 --- a/chromium/components/autofill/content/renderer/form_tracker.h +++ b/chromium/components/autofill/content/renderer/form_tracker.h @@ -87,8 +87,8 @@ class FormTracker : public content::RenderFrameObserver { FormSubmittedBySameDocumentNavigation); // content::RenderFrameObserver: - void DidCommitProvisionalLoad(bool is_same_document_navigation, - ui::PageTransition transition) override; + void DidCommitProvisionalLoad(ui::PageTransition transition) override; + void DidFinishSameDocumentNavigation() override; void DidStartNavigation( const GURL& url, base::Optional<blink::WebNavigationType> navigation_type) override; diff --git a/chromium/components/autofill/content/renderer/html_based_username_detector.cc b/chromium/components/autofill/content/renderer/html_based_username_detector.cc index 9929ea02fd1..1006ccd24ab 100644 --- a/chromium/components/autofill/content/renderer/html_based_username_detector.cc +++ b/chromium/components/autofill/content/renderer/html_based_username_detector.cc @@ -284,13 +284,6 @@ void FindUsernameFieldInternal( } } -// Returns the |unique_renderer_id| of a given |WebFormElement|. If -// |WebFormElement::IsNull()| return a null renderer ID. -FormRendererId GetFormRendererId(WebFormElement form) { - return form.IsNull() ? FormRendererId() - : FormRendererId(form.UniqueRendererFormId()); -} - } // namespace const std::vector<FieldRendererId>& GetPredictionsFieldBasedOnHtmlAttributes( @@ -311,8 +304,8 @@ const std::vector<FieldRendererId>& GetPredictionsFieldBasedOnHtmlAttributes( bool cache_miss = true; // Iterator pointing to the entry for |form| if the entry for |form| is found. UsernameDetectorCache::iterator form_position; - std::tie(form_position, cache_miss) = username_detector_cache->insert( - std::make_pair(GetFormRendererId(form), std::vector<FieldRendererId>())); + std::tie(form_position, cache_miss) = username_detector_cache->emplace( + form_util::GetFormRendererId(form), std::vector<FieldRendererId>()); if (cache_miss) { std::vector<FieldRendererId> username_predictions; diff --git a/chromium/components/autofill/content/renderer/html_based_username_detector_browsertest.cc b/chromium/components/autofill/content/renderer/html_based_username_detector_browsertest.cc new file mode 100644 index 00000000000..d39bca5d8cd --- /dev/null +++ b/chromium/components/autofill/content/renderer/html_based_username_detector_browsertest.cc @@ -0,0 +1,345 @@ +// 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. + +#include "base/strings/stringprintf.h" +#include "components/autofill/content/renderer/form_autofill_util.h" +#include "components/autofill/content/renderer/html_based_username_detector.h" +#include "content/public/test/render_view_test.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/web/web_document.h" +#include "third_party/blink/public/web/web_local_frame.h" + +using blink::WebElement; +using blink::WebFormControlElement; +using blink::WebFormElement; +using blink::WebLocalFrame; +using blink::WebString; + +namespace autofill { + +namespace { + +struct TextField { + const char* name; + const char* id; + const char* value; + const char* label; +}; + +constexpr char kTestForm[] = R"( + <FORM name="Test" id="userform"> + <label for="%s">%s</label> + <input type="text" name="%s" id="%s" value="%s" /> + + <label for="%s">%s</label> + <input type="text" name="%s" id="%s" value="%s" /> + + <input type="password" id="password" value="v" /> + <input type="submit" value="submit" /> + </FORM> +)"; + +std::string GetFormHTML(const TextField& first_field, + const TextField& second_field) { + return base::StringPrintf( + kTestForm, first_field.id, first_field.label, first_field.name, + first_field.id, first_field.value, second_field.id, second_field.label, + second_field.name, second_field.id, second_field.value); +} + +class HtmlBasedUsernameDetectorTest : public content::RenderViewTest { + protected: + struct TestCase { + const TextField first_text_field_parameter; + const TextField second_text_field_parameter; + const WebString expected_username_id; + }; + + FormData LoadFormDataFromHtml(const std::string& html) { + LoadHTML(html.data()); + const WebFormElement& form = GetFormElement(); + return GetFormDataFromForm(form); + } + + FormData GetFormDataFromForm(const WebFormElement& form) { + FormData form_data; + EXPECT_TRUE(form_util::WebFormElementToFormData( + form, WebFormControlElement(), nullptr, form_util::EXTRACT_NONE, + &form_data, nullptr)); + + return form_data; + } + + FieldRendererId GetRendererIdFromWebElementId(const WebString& id) { + const WebLocalFrame* frame = GetMainFrame(); + const WebElement& element = frame->GetDocument().GetElementById(id); + EXPECT_FALSE(element.IsNull()); + return FieldRendererId(element.ToConst<blink::WebInputElement>() + .UniqueRendererFormControlId()); + } + + WebFormElement GetFormElement() { + const WebLocalFrame* frame = GetMainFrame(); + const blink::WebVector<WebFormElement>& forms = + frame->GetDocument().Forms(); + EXPECT_EQ(1U, forms.size()); + EXPECT_FALSE(forms[0].IsNull()); + + return forms[0]; + } + + std::vector<WebFormControlElement> GetFormControlElements() { + const WebFormElement& form = GetFormElement(); + blink::WebVector<WebFormControlElement> control_elements = + form.GetFormControlElements(); + return control_elements.ReleaseVector(); + } + + void PredictAndCheckUsernameId(const std::string& html, + const WebString& expected_username_id) { + const FormData& form_data = LoadFormDataFromHtml(html); + const std::vector<WebFormControlElement>& control_elements = + GetFormControlElements(); + + // Get the expected renderer id from the expected username id. + const FieldRendererId expected_renderer_id = + GetRendererIdFromWebElementId(expected_username_id); + + // Run predictions and test the result. + UsernameDetectorCache cache; + const std::vector<FieldRendererId>& renderer_ids = + GetPredictionsFieldBasedOnHtmlAttributes(control_elements, form_data, + &cache); + + ASSERT_EQ(1u, cache.size()); + ASSERT_FALSE(cache.begin()->second.empty()); + EXPECT_EQ(expected_renderer_id, cache.begin()->second[0]); + ASSERT_FALSE(renderer_ids.empty()); + EXPECT_EQ(expected_renderer_id, renderer_ids[0]); + } +}; + +} // namespace + +TEST_F(HtmlBasedUsernameDetectorTest, DeveloperGroupAttributes) { + // Each test case consists of a set of parameters to be plugged into + // the TestCase struct, plus the corresponding expectations. The test data + // contains cases that are identified by HTML detector, and not by + // base heuristic. Thus, username field does not necessarely have to + // be right before password field. These tests basically check + // searching in developer group (i.e. name and id attribute, + // concatenated, with "$" guard in between). + const TestCase test_cases[] = { + // There are both field name and id. + {{"username", "x1d", "johnsmith"}, + {"email", "y1d", "js@google.com"}, + "x1d"}, + // there is no field id. + {{"username", "x1d", "johnsmith"}, + {"email", "y1d", "js@google.com"}, + "x1d"}, + // Upper or mixed case shouldn't matter. + {{"uSeRnAmE", "x1d", "johnsmith"}, + {"email", "y1d", "js@google.com"}, + "x1d"}, + // Check removal of special characters. + {{"u1_s2-e3~r4/n5(a)6m#e", "x1d", "johnsmith"}, + {"email", "y1d", "js@google.com"}, + "x1d"}, + // Check guard between field name and field id. + {{"us", "ername", "johnsmith"}, {"email", "id", "js@google.com"}, "id"}, + // Check removal of fields with latin negative words in developer group. + {{"email", "x", "js@google.com"}, + {"fake_username", "y", "johnsmith"}, + "x"}, + {{"email", "mail", "js@google.com"}, + {"user_name", "fullname", "johnsmith"}, + "mail"}, + // Identify latin translations of "username". + {{"benutzername", "x", "johnsmith"}, + {"email", "y", "js@google.com"}, + "x"}, + // Identify latin translations of "user". + {{"utilizator", "x1d", "johnsmith"}, + {"email", "y1d", "js@google.com"}, + "x1d"}, + // Identify technical words. + {{"loginid", "x1d", "johnsmith"}, + {"email", "y1d", "js@google.com"}, + "x1d"}, + // Identify weak words. + {{"usrname", "x1d", "johnsmith"}, + {"email", "y1d", "js@google.com"}, + "y1d"}, + // If a word matches in maximum 2 fields, it is accepted. + // First encounter is selected as username. + {{"username", "x1d", "johnsmith"}, + {"repeat_username", "y1d", "johnsmith"}, + "x1d"}, + // A short word should be enclosed between delimiters. Otherwise, an + // Occurrence doesn't count. + {{"identity_name", "idn", "johnsmith"}, {"id", "xid", "123"}, "xid"}}; + + for (size_t i = 0; i < base::size(test_cases); ++i) { + SCOPED_TRACE(testing::Message() << "Iteration " << i); + + const std::string& form_html = + GetFormHTML(test_cases[i].first_text_field_parameter, + test_cases[i].second_text_field_parameter); + + PredictAndCheckUsernameId(form_html, test_cases[i].expected_username_id); + } +} + +TEST_F(HtmlBasedUsernameDetectorTest, UserGroupAttributes) { + // Each test case consists of a set of parameters to be plugged into + // the TestCase struct, plus the corresponding expectations. The test data + // contains cases that are identified by HTML detector, and not by + // base heuristic. Thus, username field does not necessarely have to + // be right before password field. These tests basically check + // searching in user group + const TestCase test_cases[] = { + // Label information will decide username. + {{"name1", "id1", "johnsmith", "Username:"}, + {"name2", "id2", "js@google.com", "Email:"}, + "id1"}, + // Placeholder information will decide username. + {{"name1", "id1", "js@google.com", "Email:"}, + {"name2", "id2", "johnsmith", "Username:"}, + "id2"}, + // Check removal of special characters. + {{"name1", "id1", "johnsmith", "U s er n a m e:"}, + {"name2", "id2", "js@google.com", "Email:"}, + "id1"}, + // Check removal of fields with latin negative words in user group. + {{"name1", "id1", "johnsmith", "Username password:"}, + {"name2", "id2", "js@google.com", "Email:"}, + "id2"}, + // Check removal of fields with non-latin negative words in user group. + {{"name1", "id1", "js@google.com", "Email:"}, + {"name2", "id2", "johnsmith", "የይለፍቃልየይለፍቃል:"}, + "id1"}, + // Identify latin translations of "username". + {{"name1", "id1", "johnsmith", "Username:"}, + {"name2", "id2", "js@google.com", "Email:"}, + "id1"}, + // Identify non-latin translations of "username". + {{"name1", "id1", "johnsmith", "用户名:"}, + {"name2", "id2", "js@google.com", "Email:"}, + "id1"}, + // Identify latin translations of "user". + {{"name1", "id1", "johnsmith", "Wosuta:"}, + {"name2", "id2", "js@google.com", "Email:"}, + "id1"}, + // Identify non-latin translations of "user". + {{"name1", "id1", "johnsmith", "истифода:"}, + {"name2", "id2", "js@google.com", "Email:"}, + "id1"}, + // Identify weak words. + {{"name1", "id1", "johnsmith", "Insert your login details:"}, + {"name2", "id2", "js@google.com", "Insert your email:"}, + "id1"}, + // Check user group priority, compared to developer group. + // User group should have higher priority than developer group. + {{"email", "id1", "js@google.com", "Username:"}, + {"username", "id2", "johnsmith", "Email:"}, + "id1"}, + // Check treatment for short dictionary words. "uid" has higher priority, + // but its occurrence is ignored because it is a part of another word. + { + {"name1", "noword", "johnsmith", "Insert your id:"}, + {"name2", "uidentical", "js@google.com", "Insert something:"}, + "noword", + }}; + + for (size_t i = 0; i < base::size(test_cases); ++i) { + SCOPED_TRACE(testing::Message() << "Iteration " << i); + + const std::string& form_html = + GetFormHTML(test_cases[i].first_text_field_parameter, + test_cases[i].second_text_field_parameter); + + PredictAndCheckUsernameId(form_html, test_cases[i].expected_username_id); + } +} + +TEST_F(HtmlBasedUsernameDetectorTest, SeveralDetections) { + // If word matches in more than 2 fields, we don't match on it. + // We search for match with another word. + const std::string& test_form = R"( + <form> + <input type="text" name="address" id="xuser" value="addr" /> + <input type="text" name="loginid" id="yuser" value="johnsmith" /> + <input type="text" name="tel" id="zuser" value="sometel" /> + <input type="password" id="password" value="v" /> + <input type="submit" value="submit" /> + </form> + )"; + PredictAndCheckUsernameId(test_form, "yuser"); +} + +TEST_F(HtmlBasedUsernameDetectorTest, HTMLDetectorCache) { + const TextField text_fields[] = { + {"unknown", "12345"}, + {"something", "smith"}, + }; + + const std::string& form_html = GetFormHTML(text_fields[0], text_fields[1]); + + FormData form_data = LoadFormDataFromHtml(form_html); + std::vector<WebFormControlElement> control_elements = + GetFormControlElements(); + + UsernameDetectorCache cache; + std::vector<FieldRendererId> field_ids = + GetPredictionsFieldBasedOnHtmlAttributes(control_elements, form_data, + &cache); + + // No signals from HTML attributes. The classifier found nothing and cached + // it. + ASSERT_EQ(1u, cache.size()); + EXPECT_TRUE(field_ids.empty()); + const WebFormElement& form = GetFormElement(); + EXPECT_EQ(FormRendererId(form.UniqueRendererFormId()), cache.begin()->first); + EXPECT_TRUE(cache.begin()->second.empty()); + + // Changing attributes would change the classifier's output. But the output + // will be the same because it was cached in |username_detector_cache|. + control_elements[0].SetAttribute("name", "id"); + form_data = GetFormDataFromForm(GetFormElement()); + field_ids = GetPredictionsFieldBasedOnHtmlAttributes(control_elements, + form_data, &cache); + ASSERT_EQ(1u, cache.size()); + EXPECT_TRUE(field_ids.empty()); + EXPECT_EQ(FormRendererId(form.UniqueRendererFormId()), cache.begin()->first); + EXPECT_TRUE(cache.begin()->second.empty()); + + // Clear the cache. The classifier will find username field and cache it. + cache.clear(); + ASSERT_EQ(4u, control_elements.size()); + field_ids = GetPredictionsFieldBasedOnHtmlAttributes(control_elements, + form_data, &cache); + ASSERT_EQ(1u, cache.size()); + EXPECT_EQ(1u, field_ids.size()); + EXPECT_EQ(FormRendererId(form.UniqueRendererFormId()), cache.begin()->first); + ASSERT_EQ(1u, cache.begin()->second.size()); + EXPECT_EQ(FieldRendererId(control_elements[0].UniqueRendererFormControlId()), + cache.begin()->second[0]); + + // Change the attributes again ("username" is stronger signal than "id"), + // but keep the cache. The classifier's output should be the same. + control_elements[1].SetAttribute("name", "username"); + form_data = GetFormDataFromForm(GetFormElement()); + field_ids = GetPredictionsFieldBasedOnHtmlAttributes(control_elements, + form_data, &cache); + + ASSERT_EQ(1u, cache.size()); + EXPECT_EQ(1u, field_ids.size()); + EXPECT_EQ(FormRendererId(form.UniqueRendererFormId()), cache.begin()->first); + ASSERT_EQ(1u, cache.begin()->second.size()); + EXPECT_EQ(FieldRendererId(control_elements[0].UniqueRendererFormControlId()), + cache.begin()->second[0]); +} + +} // namespace autofill diff --git a/chromium/components/autofill/content/renderer/password_autofill_agent.cc b/chromium/components/autofill/content/renderer/password_autofill_agent.cc index d85705681a9..4d283f72a0f 100644 --- a/chromium/components/autofill/content/renderer/password_autofill_agent.cc +++ b/chromium/components/autofill/content/renderer/password_autofill_agent.cc @@ -24,7 +24,6 @@ #include "base/strings/utf_string_conversions.h" #include "base/threading/thread_task_runner_handle.h" #include "build/build_config.h" -#include "components/autofill/content/renderer/form_autofill_util.h" #include "components/autofill/content/renderer/password_form_conversion_utils.h" #include "components/autofill/content/renderer/password_generation_agent.h" #include "components/autofill/content/renderer/prefilled_values_detector.h" @@ -36,7 +35,6 @@ #include "components/autofill/core/common/mojom/autofill_types.mojom.h" #include "components/autofill/core/common/password_form_fill_data.h" #include "components/autofill/core/common/signatures.h" -#include "components/password_manager/core/common/password_manager_features.h" #include "components/safe_browsing/buildflags.h" #include "content/public/renderer/document_state.h" #include "content/public/renderer/render_frame.h" @@ -257,9 +255,9 @@ WebString GetFormSignatureAsWebString(const FormData& form_data) { // Annotate |fields| with field signatures and form signature as HTML // attributes. void AnnotateFieldsWithSignatures( - std::vector<blink::WebFormControlElement>* fields, + std::vector<blink::WebFormControlElement>& fields, const blink::WebString& form_signature) { - for (blink::WebFormControlElement& control_element : *fields) { + for (blink::WebFormControlElement& control_element : fields) { FieldSignature field_signature = CalculateFieldSignatureByNameAndType( control_element.NameForAutofill().Utf16(), control_element.FormControlTypeForAutofill().Utf8()); @@ -272,37 +270,6 @@ void AnnotateFieldsWithSignatures( } } -// Annotate |forms| and all fields in the |frame| with form and field signatures -// as HTML attributes. -void AnnotateFormsAndFieldsWithSignatures(WebLocalFrame* frame, - WebVector<WebFormElement>* forms) { - for (WebFormElement& form : *forms) { - std::unique_ptr<FormData> form_data( - CreateFormDataFromWebForm(form, /*field_data_manager=*/nullptr, - /*username_detector_cache=*/nullptr)); - WebString form_signature; - if (form_data) { - form_signature = GetFormSignatureAsWebString(*form_data); - form.SetAttribute(WebString::FromASCII(kDebugAttributeForFormSignature), - form_signature); - } - std::vector<WebFormControlElement> form_fields = - form_util::ExtractAutofillableElementsInForm(form); - AnnotateFieldsWithSignatures(&form_fields, form_signature); - } - - std::vector<WebFormControlElement> unowned_elements = - form_util::GetUnownedAutofillableFormFieldElements( - frame->GetDocument().All(), nullptr); - std::unique_ptr<FormData> form_data(CreateFormDataFromUnownedInputElements( - *frame, /*field_data_manager=*/nullptr, - /*username_detector_cache=*/nullptr)); - WebString form_signature; - if (form_data) - form_signature = GetFormSignatureAsWebString(*form_data); - AnnotateFieldsWithSignatures(&unowned_elements, form_signature); -} - // Returns true iff there is a password field in |frame|. bool HasPasswordField(const WebLocalFrame& frame) { static base::NoDestructor<WebString> kPassword("password"); @@ -416,14 +383,6 @@ bool HasDocumentWithValidFrame(const WebInputElement& element) { return frame && frame->View(); } -bool ShowPopupWithoutPasswords(const WebInputElement& password_element) { - if (!base::FeatureList::IsEnabled( - password_manager::features::kEnablePasswordsAccountStorage)) { - return false; - } - return !password_element.IsNull() && IsElementEditable(password_element); -} - // This method tries to fix `fields` with empty typed or filled properties by // matching them against previously filled or typed in fields with the same // value and copying their filled or typed mask. @@ -905,9 +864,10 @@ bool PasswordAutofillAgent::ShowSuggestions( FindPasswordInfoForElement(element, UseFallbackData(true), &username_element, &password_element, &password_info); - if (!password_info && !ShowPopupWithoutPasswords(password_element)) { + if (!password_info) { MaybeCheckSafeBrowsingReputation(element); - return false; + if (!CanShowPopupWithoutPasswords(password_element)) + return false; } // Check that all fillable elements are editable. @@ -1015,6 +975,31 @@ void PasswordAutofillAgent::UserGestureObserved() { gatekeeper_.OnUserGesture(); } +void PasswordAutofillAgent::AnnotateFormsAndFieldsWithSignatures( + WebVector<WebFormElement>& forms) { + for (WebFormElement& form : forms) { + std::unique_ptr<FormData> form_data = GetFormDataFromWebForm(form); + WebString form_signature; + if (form_data) { + form_signature = GetFormSignatureAsWebString(*form_data); + form.SetAttribute(WebString::FromASCII(kDebugAttributeForFormSignature), + form_signature); + } + std::vector<WebFormControlElement> form_fields = + form_util::ExtractAutofillableElementsInForm(form); + AnnotateFieldsWithSignatures(form_fields, form_signature); + } + + std::vector<WebFormControlElement> unowned_elements = + form_util::GetUnownedAutofillableFormFieldElements( + render_frame()->GetWebFrame()->GetDocument().All(), nullptr); + std::unique_ptr<FormData> form_data = GetFormDataFromUnownedInputElements(); + WebString form_signature; + if (form_data) + form_signature = GetFormSignatureAsWebString(*form_data); + AnnotateFieldsWithSignatures(unowned_elements, form_signature); +} + void PasswordAutofillAgent::SendPasswordForms(bool only_visible) { std::unique_ptr<RendererSavePasswordProgressLogger> logger; if (logging_state_active_) { @@ -1046,7 +1031,7 @@ void PasswordAutofillAgent::SendPasswordForms(bool only_visible) { WebVector<WebFormElement> forms = frame->GetDocument().Forms(); if (IsShowAutofillSignaturesEnabled()) - AnnotateFormsAndFieldsWithSignatures(frame, &forms); + AnnotateFormsAndFieldsWithSignatures(forms); if (logger) logger->LogNumber(Logger::STRING_NUMBER_OF_ALL_FORMS, forms.size()); @@ -1131,7 +1116,7 @@ void PasswordAutofillAgent::SendPasswordForms(bool only_visible) { password_forms_data.back().url = form_util::GetCanonicalOriginForDocument(frame->GetDocument()); password_forms_data.back().full_url = - form_util::GetOriginWithoutAuthForDocument(frame->GetDocument()); + form_util::GetDocumentUrlWithoutAuth(frame->GetDocument()); } if (!password_forms_data.empty()) { sent_request_to_store_ = true; @@ -1161,12 +1146,9 @@ void PasswordAutofillAgent::DidFinishLoad() { } void PasswordAutofillAgent::DidCommitProvisionalLoad( - bool is_same_document_navigation, ui::PageTransition transition) { - if (!is_same_document_navigation) { - checked_safe_browsing_reputation_ = false; - recorded_first_filling_result_ = false; - } + checked_safe_browsing_reputation_ = false; + recorded_first_filling_result_ = false; } void PasswordAutofillAgent::OnFrameDetached() { @@ -1312,7 +1294,10 @@ void PasswordAutofillAgent::AnnotateFieldsWithParsingResult( "confirmation_password_element"); } -void PasswordAutofillAgent::InformNoSavedCredentials() { +void PasswordAutofillAgent::InformNoSavedCredentials( + bool should_show_popup_without_passwords) { + should_show_popup_without_passwords_ = should_show_popup_without_passwords; + autofilled_elements_cache_.clear(); // Clear the actual field values. @@ -1382,7 +1367,8 @@ void PasswordAutofillAgent::FocusedNodeHasChanged(const blink::WebNode& node) { std::unique_ptr<FormData> PasswordAutofillAgent::GetFormDataFromWebForm( const WebFormElement& web_form) { return CreateFormDataFromWebForm(web_form, field_data_manager_.get(), - &username_detector_cache_); + &username_detector_cache_, + &button_titles_cache_); } std::unique_ptr<FormData> @@ -1398,7 +1384,8 @@ PasswordAutofillAgent::GetFormDataFromUnownedInputElements() { if (!web_frame) return nullptr; return CreateFormDataFromUnownedInputElements( - *web_frame, field_data_manager_.get(), &username_detector_cache_); + *web_frame, field_data_manager_.get(), &username_detector_cache_, + &button_titles_cache_); } //////////////////////////////////////////////////////////////////////////////// @@ -1430,6 +1417,7 @@ void PasswordAutofillAgent::CleanupOnDocumentShutdown() { web_input_to_password_info_.clear(); password_to_username_.clear(); last_supplied_password_info_iter_ = web_input_to_password_info_.end(); + should_show_popup_without_passwords_ = false; browser_has_form_to_process_ = false; field_data_manager_.get()->ClearData(); username_autofill_state_ = WebAutofillState::kNotFilled; @@ -1535,7 +1523,7 @@ bool PasswordAutofillAgent::FillUserNameAndPassword( // This is a heuristic guess. If the credential is stored for // www.example.com, the username may be prefilled with "@example.com". std::string possible_email_domain = - GetRegistryControlledDomain(fill_data.origin); + GetRegistryControlledDomain(fill_data.url); prefilled_placeholder_username = !username_element.Value().IsEmpty() && @@ -1886,4 +1874,10 @@ void PasswordAutofillAgent::SetLastUpdatedFormAndField( : FieldRendererId(input.UniqueRendererFormControlId()); } +bool PasswordAutofillAgent::CanShowPopupWithoutPasswords( + const WebInputElement& password_element) const { + return should_show_popup_without_passwords_ && !password_element.IsNull() && + IsElementEditable(password_element); +} + } // namespace autofill diff --git a/chromium/components/autofill/content/renderer/password_autofill_agent.h b/chromium/components/autofill/content/renderer/password_autofill_agent.h index 011425f9186..4852ec2a3b8 100644 --- a/chromium/components/autofill/content/renderer/password_autofill_agent.h +++ b/chromium/components/autofill/content/renderer/password_autofill_agent.h @@ -20,6 +20,7 @@ #include "components/autofill/content/common/mojom/autofill_driver.mojom.h" #include "components/autofill/content/renderer/autofill_agent.h" #include "components/autofill/content/renderer/field_data_manager.h" +#include "components/autofill/content/renderer/form_autofill_util.h" #include "components/autofill/content/renderer/form_tracker.h" #include "components/autofill/content/renderer/html_based_username_detector.h" #include "components/autofill/core/common/mojom/autofill_types.mojom.h" @@ -99,10 +100,6 @@ enum class FillingResult { kMaxValue = kNoFillableElementsFound, }; -// Names of HTML attributes to show form and field signatures for debugging. -extern const char kDebugAttributeForFormSignature[]; -extern const char kDebugAttributeForFieldSignature[]; - class FieldDataManager; class RendererSavePasswordProgressLogger; class PasswordGenerationAgent; @@ -133,7 +130,8 @@ class PasswordAutofillAgent : public content::RenderFrameObserver, // mojom::PasswordAutofillAgent: void FillPasswordForm(const PasswordFormFillData& form_data) override; - void InformNoSavedCredentials() override; + void InformNoSavedCredentials( + bool should_show_popup_without_passwords) override; void FillIntoFocusedField(bool is_password, const base::string16& credential) override; void SetLoggingState(bool active) override; @@ -235,8 +233,7 @@ class PasswordAutofillAgent : public content::RenderFrameObserver, void DidFinishLoad() override; void ReadyToCommitNavigation( blink::WebDocumentLoader* document_loader) override; - void DidCommitProvisionalLoad(bool is_same_document_navigation, - ui::PageTransition transition) override; + void DidCommitProvisionalLoad(ui::PageTransition transition) override; void OnDestruct() override; const scoped_refptr<FieldDataManager> GetFieldDataManager() { @@ -345,6 +342,12 @@ class PasswordAutofillAgent : public content::RenderFrameObserver, DISALLOW_COPY_AND_ASSIGN(PasswordValueGatekeeper); }; + // Annotate |forms| and all fields in the current frame with form and field + // signatures as HTML attributes. Used by + // chrome://flags/#enable-show-autofill-signatures only. + void AnnotateFormsAndFieldsWithSignatures( + blink::WebVector<blink::WebFormElement>& forms); + // Scans the given frame for password forms and sends them up to the browser. // If |only_visible| is true, only forms visible in the layout are sent. void SendPasswordForms(bool only_visible); @@ -467,6 +470,9 @@ class PasswordAutofillAgent : public content::RenderFrameObserver, void SetLastUpdatedFormAndField(const blink::WebFormElement& form, const blink::WebFormControlElement& input); + bool CanShowPopupWithoutPasswords( + const blink::WebInputElement& password_element) const; + // The logins we have filled so far with their associated info. WebInputToPasswordInfoMap web_input_to_password_info_; // A (sort-of) reverse map to |web_input_to_password_info_|. @@ -474,6 +480,8 @@ class PasswordAutofillAgent : public content::RenderFrameObserver, // The chronologically last insertion into |web_input_to_password_info_|. WebInputToPasswordInfoMap::iterator last_supplied_password_info_iter_; + bool should_show_popup_without_passwords_ = false; + // Map WebFormControlElement to the pair of: // 1) The most recent text that user typed or PasswordManager autofilled in // input elements. Used for storing username/password before JavaScript @@ -513,10 +521,6 @@ class PasswordAutofillAgent : public content::RenderFrameObserver, // Records the username typed before suggestions preview. base::string16 username_query_prefix_; - // The HTML based username detector's cache which maps form elements to - // username predictions. - UsernameDetectorCache username_detector_cache_; - // This notifier is used to avoid sending redundant messages to the password // manager driver mojo interface. FocusStateNotifier focus_state_notifier_; @@ -543,6 +547,14 @@ class PasswordAutofillAgent : public content::RenderFrameObserver, // structure. Replace FormData with a smaller structure. std::map<FormRendererId, FormStructureInfo> forms_structure_cache_; + // The HTML based username detector's cache which maps form elements to + // username predictions. + UsernameDetectorCache username_detector_cache_; + + // Stores the mapping from a form element's ID to results of button titles + // heuristics for that form. + form_util::ButtonTitlesCache button_titles_cache_; + // Flag to prevent that multiple PasswordManager.FirstRendererFillingResult // UMA metrics are recorded per page load. This is reset on // DidCommitProvisionalLoad() but only for non-same-document-navigations. diff --git a/chromium/components/autofill/content/renderer/password_form_conversion_utils.cc b/chromium/components/autofill/content/renderer/password_form_conversion_utils.cc index a216162bcfd..93824cae0bd 100644 --- a/chromium/components/autofill/content/renderer/password_form_conversion_utils.cc +++ b/chromium/components/autofill/content/renderer/password_form_conversion_utils.cc @@ -9,7 +9,6 @@ #include "base/no_destructor.h" #include "base/strings/string_piece.h" #include "base/strings/string_split.h" -#include "components/autofill/content/renderer/form_autofill_util.h" #include "components/autofill/content/renderer/html_based_username_detector.h" #include "components/autofill/core/common/password_form.h" #include "components/autofill/core/common/renderer_id.h" @@ -128,7 +127,8 @@ bool IsGaiaWithSkipSavePasswordForm(const blink::WebFormElement& form) { std::unique_ptr<FormData> CreateFormDataFromWebForm( const WebFormElement& web_form, const FieldDataManager* field_data_manager, - UsernameDetectorCache* username_detector_cache) { + UsernameDetectorCache* username_detector_cache, + form_util::ButtonTitlesCache* button_titles_cache) { if (web_form.IsNull()) return nullptr; @@ -136,7 +136,7 @@ std::unique_ptr<FormData> CreateFormDataFromWebForm( form_data->url = form_util::GetCanonicalOriginForDocument(web_form.GetDocument()); form_data->full_url = - form_util::GetOriginWithoutAuthForDocument(web_form.GetDocument()); + form_util::GetDocumentUrlWithoutAuth(web_form.GetDocument()); form_data->is_gaia_with_skip_save_password_form = IsGaiaWithSkipSavePasswordForm(web_form) || IsGaiaReauthenticationForm(web_form); @@ -153,6 +153,8 @@ std::unique_ptr<FormData> CreateFormDataFromWebForm( } form_data->username_predictions = GetUsernamePredictions( control_elements.ReleaseVector(), *form_data, username_detector_cache); + form_data->button_titles = form_util::GetButtonTitles( + web_form, web_form.GetDocument(), button_titles_cache); return form_data; } @@ -160,7 +162,8 @@ std::unique_ptr<FormData> CreateFormDataFromWebForm( std::unique_ptr<FormData> CreateFormDataFromUnownedInputElements( const WebLocalFrame& frame, const FieldDataManager* field_data_manager, - UsernameDetectorCache* username_detector_cache) { + UsernameDetectorCache* username_detector_cache, + form_util::ButtonTitlesCache* button_titles_cache) { std::vector<WebElement> fieldsets; std::vector<WebFormControlElement> control_elements = form_util::GetUnownedFormFieldElements(frame.GetDocument().All(), @@ -179,9 +182,12 @@ std::unique_ptr<FormData> CreateFormDataFromUnownedInputElements( form_data->url = form_util::GetCanonicalOriginForDocument(frame.GetDocument()); form_data->full_url = - form_util::GetOriginWithoutAuthForDocument(frame.GetDocument()); + form_util::GetDocumentUrlWithoutAuth(frame.GetDocument()); form_data->username_predictions = GetUsernamePredictions( control_elements, *form_data, username_detector_cache); + form_data->button_titles = form_util::GetButtonTitles( + WebFormElement(), frame.GetDocument(), button_titles_cache); + return form_data; } diff --git a/chromium/components/autofill/content/renderer/password_form_conversion_utils.h b/chromium/components/autofill/content/renderer/password_form_conversion_utils.h index 8701d51e57e..07a2ad05095 100644 --- a/chromium/components/autofill/content/renderer/password_form_conversion_utils.h +++ b/chromium/components/autofill/content/renderer/password_form_conversion_utils.h @@ -12,6 +12,7 @@ #include <vector> #include "base/strings/string_piece.h" +#include "components/autofill/content/renderer/form_autofill_util.h" #include "components/autofill/content/renderer/html_based_username_detector.h" #include "components/autofill/core/common/password_form.h" #include "third_party/blink/public/platform/web_string.h" @@ -42,14 +43,16 @@ bool IsGaiaWithSkipSavePasswordForm(const blink::WebFormElement& form); std::unique_ptr<FormData> CreateFormDataFromWebForm( const blink::WebFormElement& web_form, const FieldDataManager* field_data_manager, - UsernameDetectorCache* username_detector_cache); + UsernameDetectorCache* username_detector_cache, + form_util::ButtonTitlesCache* button_titles_cache); // Same as CreateFormDataFromWebForm() but for input elements that are // not enclosed in <form> element. std::unique_ptr<FormData> CreateFormDataFromUnownedInputElements( const blink::WebLocalFrame& frame, const FieldDataManager* field_data_manager, - UsernameDetectorCache* username_detector_cache); + UsernameDetectorCache* username_detector_cache, + form_util::ButtonTitlesCache* button_titles_cache); // The "Realm" for the sign-on. This is scheme, host, port. std::string GetSignOnRealm(const GURL& origin); diff --git a/chromium/components/autofill/content/renderer/password_generation_agent.cc b/chromium/components/autofill/content/renderer/password_generation_agent.cc index 959fa2d7388..a87d298ca38 100644 --- a/chromium/components/autofill/content/renderer/password_generation_agent.cc +++ b/chromium/components/autofill/content/renderer/password_generation_agent.cc @@ -160,10 +160,7 @@ void PasswordGenerationAgent::BindPendingReceiver( } void PasswordGenerationAgent::DidCommitProvisionalLoad( - bool is_same_document_navigation, ui::PageTransition transition) { - if (is_same_document_navigation) - return; // Update stats for main frame navigation. if (!render_frame()->GetWebFrame()->Parent()) { if (current_generation_item_) { diff --git a/chromium/components/autofill/content/renderer/password_generation_agent.h b/chromium/components/autofill/content/renderer/password_generation_agent.h index 6baf4393658..2eeb0e3044e 100644 --- a/chromium/components/autofill/content/renderer/password_generation_agent.h +++ b/chromium/components/autofill/content/renderer/password_generation_agent.h @@ -97,8 +97,7 @@ class PasswordGenerationAgent : public content::RenderFrameObserver, struct GenerationItemInfo; // RenderFrameObserver: - void DidCommitProvisionalLoad(bool is_same_document_navigation, - ui::PageTransition transition) override; + void DidCommitProvisionalLoad(ui::PageTransition transition) override; void DidChangeScrollOffset() override; void OnDestruct() override; diff --git a/chromium/components/autofill/core/browser/BUILD.gn b/chromium/components/autofill/core/browser/BUILD.gn index d8de972ccdb..3696ebea775 100644 --- a/chromium/components/autofill/core/browser/BUILD.gn +++ b/chromium/components/autofill/core/browser/BUILD.gn @@ -169,6 +169,8 @@ jumbo_static_library("browser") { "metrics/form_event_logger_base.h", "metrics/form_events.h", "payments/account_info_getter.h", + "payments/autofill_offer_manager.cc", + "payments/autofill_offer_manager.h", "payments/autofill_wallet_model_type_controller.cc", "payments/autofill_wallet_model_type_controller.h", "payments/card_unmask_delegate.cc", @@ -501,6 +503,7 @@ jumbo_static_library("test_support") { sources += [ "payments/test_credit_card_fido_authenticator.cc", "payments/test_credit_card_fido_authenticator.h", + "payments/test_internal_authenticator.cc", "payments/test_internal_authenticator.h", ] @@ -718,6 +721,8 @@ if (use_libfuzzer) { # TODO(crbug.com/896313): Reduce the dependency on "browser". ":browser", "//base:base", + "//components/autofill/core/browser", + "//components/autofill/core/browser:test_support", "//components/autofill/core/browser/proto", "//components/autofill/core/common", "//third_party/libprotobuf-mutator", diff --git a/chromium/components/autofill/core/browser/autofill_and_password_manager_internals/autofill_and_password_manager_internals.css b/chromium/components/autofill/core/browser/autofill_and_password_manager_internals/autofill_and_password_manager_internals.css index 4d4b7509401..36b5c68b20a 100644 --- a/chromium/components/autofill/core/browser/autofill_and_password_manager_internals/autofill_and_password_manager_internals.css +++ b/chromium/components/autofill/core/browser/autofill_and_password_manager_internals/autofill_and_password_manager_internals.css @@ -175,8 +175,8 @@ html { display: none; } -.hide-AddressProfileFormImport.log-entry[scope='AddressProfileFormImport'], -.hide-AddressProfileFormImport.log-entry[scope='AddressProfileFormImport'] + hr { +.hide-AddressProfileFormImport .log-entry[scope='AddressProfileFormImport'], +.hide-AddressProfileFormImport .log-entry[scope='AddressProfileFormImport'] + hr { display: none; } diff --git a/chromium/components/autofill/core/browser/autofill_assistant_unittest.cc b/chromium/components/autofill/core/browser/autofill_assistant_unittest.cc index a47451ca2e5..a5b7a87d286 100644 --- a/chromium/components/autofill/core/browser/autofill_assistant_unittest.cc +++ b/chromium/components/autofill/core/browser/autofill_assistant_unittest.cc @@ -168,8 +168,8 @@ TEST_F(AutofillAssistantTest, CanShowCreditCardAssist_FeatureOff) { std::unique_ptr<FormStructure> form_structure = CreateValidCreditCardForm(); auto& form_structures = *autofill_manager_->mutable_form_structures(); - auto signature = form_structure->form_signature(); - form_structures[signature] = std::move(form_structure); + auto renderer_id = form_structure->unique_renderer_id(); + form_structures[renderer_id] = std::move(form_structure); EXPECT_FALSE(autofill_assistant_->CanShowCreditCardAssist()); } @@ -183,8 +183,8 @@ TEST_F(AutofillAssistantTest, CanShowCreditCardAssist_FeatureOn) { // With valid input, the function extracts the credit card form properly. std::unique_ptr<FormStructure> form_structure = CreateValidCreditCardForm(); auto& form_structures = *autofill_manager_->mutable_form_structures(); - auto signature = form_structure->form_signature(); - form_structures[signature] = std::move(form_structure); + auto renderer_id = form_structure->unique_renderer_id(); + form_structures[renderer_id] = std::move(form_structure); EXPECT_TRUE(autofill_assistant_->CanShowCreditCardAssist()); } @@ -199,8 +199,8 @@ TEST_F(AutofillAssistantTest, CanShowCreditCardAssist_FeatureOn_Secure) { form_structure->DetermineHeuristicTypes(); auto& form_structures = *autofill_manager_->mutable_form_structures(); - auto signature = form_structure->form_signature(); - form_structures[signature] = std::move(form_structure); + auto renderer_id = form_structure->unique_renderer_id(); + form_structures[renderer_id] = std::move(form_structure); EXPECT_TRUE(autofill_assistant_->CanShowCreditCardAssist()); } @@ -217,8 +217,8 @@ TEST_F(AutofillAssistantTest, CanShowCreditCardAssist_FeatureOn_NotSecure) { form_structure->DetermineHeuristicTypes(); auto& form_structures = *autofill_manager_->mutable_form_structures(); - auto signature = form_structure->form_signature(); - form_structures[signature] = std::move(form_structure); + auto renderer_id = form_structure->unique_renderer_id(); + form_structures[renderer_id] = std::move(form_structure); EXPECT_FALSE(autofill_assistant_->CanShowCreditCardAssist()); } @@ -233,8 +233,8 @@ TEST_F(AutofillAssistantTest, CanShowCreditCardAssist_FeatureOn_Javascript) { form_structure->DetermineHeuristicTypes(); auto& form_structures = *autofill_manager_->mutable_form_structures(); - auto signature = form_structure->form_signature(); - form_structures[signature] = std::move(form_structure); + auto renderer_id = form_structure->unique_renderer_id(); + form_structures[renderer_id] = std::move(form_structure); EXPECT_TRUE(autofill_assistant_->CanShowCreditCardAssist()); } @@ -249,8 +249,8 @@ TEST_F(AutofillAssistantTest, CanShowCreditCardAssist_FeatureOn_WeirdJs) { form_structure->DetermineHeuristicTypes(); auto& form_structures = *autofill_manager_->mutable_form_structures(); - auto signature = form_structure->form_signature(); - form_structures[signature] = std::move(form_structure); + auto renderer_id = form_structure->unique_renderer_id(); + form_structures[renderer_id] = std::move(form_structure); EXPECT_TRUE(autofill_assistant_->CanShowCreditCardAssist()); } @@ -264,8 +264,8 @@ TEST_F(AutofillAssistantTest, CanShowCreditCardAssist_FeatureOn_EmptyAction) { form_structure->DetermineHeuristicTypes(); auto& form_structures = *autofill_manager_->mutable_form_structures(); - auto signature = form_structure->form_signature(); - form_structures[signature] = std::move(form_structure); + auto renderer_id = form_structure->unique_renderer_id(); + form_structures[renderer_id] = std::move(form_structure); EXPECT_TRUE(autofill_assistant_->CanShowCreditCardAssist()); } @@ -275,8 +275,8 @@ TEST_F(AutofillAssistantTest, ShowAssistForCreditCard_ValidCard_CancelCvc) { // Will extract the credit card form data. auto& form_structures = *autofill_manager_->mutable_form_structures(); - auto signature = form_structure->form_signature(); - form_structures[signature] = std::move(form_structure); + auto renderer_id = form_structure->unique_renderer_id(); + form_structures[renderer_id] = std::move(form_structure); EXPECT_TRUE(autofill_assistant_->CanShowCreditCardAssist()); // Create a valid card for the assist. @@ -297,8 +297,8 @@ TEST_F(AutofillAssistantTest, ShowAssistForCreditCard_ValidCard_SubmitCvc) { // Will extract the credit card form data. auto& form_structures = *autofill_manager_->mutable_form_structures(); - auto signature = form_structure->form_signature(); - form_structures[signature] = std::move(form_structure); + auto renderer_id = form_structure->unique_renderer_id(); + form_structures[renderer_id] = std::move(form_structure); EXPECT_TRUE(autofill_assistant_->CanShowCreditCardAssist()); // Create a valid card for the assist. diff --git a/chromium/components/autofill/core/browser/autofill_client.cc b/chromium/components/autofill/core/browser/autofill_client.cc index 3e56a59c33e..ac6e1eae560 100644 --- a/chromium/components/autofill/core/browser/autofill_client.cc +++ b/chromium/components/autofill/core/browser/autofill_client.cc @@ -4,10 +4,33 @@ #include "components/autofill/core/browser/autofill_client.h" +#include "components/autofill/core/browser/ui/suggestion.h" #include "components/version_info/channel.h" namespace autofill { +AutofillClient::PopupOpenArgs::PopupOpenArgs() = default; +AutofillClient::PopupOpenArgs::PopupOpenArgs( + const gfx::RectF& element_bounds, + base::i18n::TextDirection text_direction, + std::vector<autofill::Suggestion> suggestions, + AutoselectFirstSuggestion autoselect_first_suggestion, + PopupType popup_type) + : element_bounds(element_bounds), + text_direction(text_direction), + suggestions(std::move(suggestions)), + autoselect_first_suggestion(autoselect_first_suggestion), + popup_type(popup_type) {} +AutofillClient::PopupOpenArgs::PopupOpenArgs( + const AutofillClient::PopupOpenArgs&) = default; +AutofillClient::PopupOpenArgs::PopupOpenArgs(AutofillClient::PopupOpenArgs&&) = + default; +AutofillClient::PopupOpenArgs::~PopupOpenArgs() = default; +AutofillClient::PopupOpenArgs& AutofillClient::PopupOpenArgs::operator=( + const AutofillClient::PopupOpenArgs&) = default; +AutofillClient::PopupOpenArgs& AutofillClient::PopupOpenArgs::operator=( + AutofillClient::PopupOpenArgs&&) = default; + version_info::Channel AutofillClient::GetChannel() const { return version_info::Channel::UNKNOWN; } diff --git a/chromium/components/autofill/core/browser/autofill_client.h b/chromium/components/autofill/core/browser/autofill_client.h index b1cd8283f70..be85907d853 100644 --- a/chromium/components/autofill/core/browser/autofill_client.h +++ b/chromium/components/autofill/core/browser/autofill_client.h @@ -15,6 +15,7 @@ #include "base/i18n/rtl.h" #include "base/memory/weak_ptr.h" #include "base/strings/string16.h" +#include "base/util/type_safety/strong_alias.h" #include "base/values.h" #include "build/build_config.h" #include "components/autofill/core/browser/payments/legal_message_line.h" @@ -24,6 +25,7 @@ #include "components/security_state/core/security_state.h" #include "services/metrics/public/cpp/ukm_source_id.h" #include "ui/base/window_open_disposition.h" +#include "ui/gfx/geometry/rect_f.h" #include "url/gurl.h" #if !defined(OS_IOS) @@ -36,10 +38,6 @@ namespace content { class RenderFrameHost; } -namespace gfx { -class RectF; -} - namespace signin { class IdentityManager; } @@ -177,6 +175,31 @@ class AutofillClient : public RiskDataLoader { bool show_prompt = false; }; + // Required arguments to create a dropdown showing autofill suggestions. + struct PopupOpenArgs { + using AutoselectFirstSuggestion = + ::util::StrongAlias<class AutoSelectFirstSuggestionTag, bool>; + + PopupOpenArgs(); + PopupOpenArgs(const gfx::RectF& element_bounds, + base::i18n::TextDirection text_direction, + std::vector<autofill::Suggestion> suggestions, + AutoselectFirstSuggestion autoselect_first_suggestion, + PopupType popup_type); + PopupOpenArgs(const PopupOpenArgs&); + PopupOpenArgs(PopupOpenArgs&&); + ~PopupOpenArgs(); + PopupOpenArgs& operator=(const PopupOpenArgs&); + PopupOpenArgs& operator=(PopupOpenArgs&&); + + gfx::RectF element_bounds; + base::i18n::TextDirection text_direction = + base::i18n::TextDirection::UNKNOWN_DIRECTION; + std::vector<autofill::Suggestion> suggestions; + AutoselectFirstSuggestion autoselect_first_suggestion{false}; + PopupType popup_type = PopupType::kUnspecified; + }; + // Callback to run after local credit card save is offered. Sends whether the // prompt was accepted, declined, or ignored in |user_decision|. typedef base::OnceCallback<void(SaveCardOfferUserDecision user_decision)> @@ -210,7 +233,7 @@ class AutofillClient : public RiskDataLoader { typedef base::RepeatingCallback<void(WebauthnDialogCallbackType)> WebauthnDialogCallback; - ~AutofillClient() override {} + ~AutofillClient() override = default; // Returns the channel for the installation. In branded builds, this will be // version_info::Channel::{STABLE,BETA,DEV,CANARY}. In unbranded builds, or @@ -414,11 +437,7 @@ class AutofillClient : public RiskDataLoader { // |identifiers| for the element at |element_bounds|. |delegate| will be // notified of popup events. virtual void ShowAutofillPopup( - const gfx::RectF& element_bounds, - base::i18n::TextDirection text_direction, - const std::vector<Suggestion>& suggestions, - bool autoselect_first_suggestion, - PopupType popup_type, + const PopupOpenArgs& open_args, base::WeakPtr<AutofillPopupDelegate> delegate) = 0; // Update the data list values shown by the Autofill popup, if visible. @@ -430,6 +449,12 @@ class AutofillClient : public RiskDataLoader { // |UpdatePopup| to update the open popup in-place. virtual void PinPopupView() = 0; + // The returned arguments allow to reopen the dropdown with + // |ShowAutofillPopup| even if the controller is destroyed temporarily. + // This function ensures that the element's bounds are transformed back to the + // screen space-independent bounds. + virtual PopupOpenArgs GetReopenPopupArgs() const = 0; + // Returns (not elided) suggestions currently held by the UI. virtual base::span<const Suggestion> GetPopupSuggestions() const = 0; diff --git a/chromium/components/autofill/core/browser/autofill_download_manager.cc b/chromium/components/autofill/core/browser/autofill_download_manager.cc index 92847181730..1fe15d7faeb 100644 --- a/chromium/components/autofill/core/browser/autofill_download_manager.cc +++ b/chromium/components/autofill/core/browser/autofill_download_manager.cc @@ -27,7 +27,6 @@ #include "build/build_config.h" #include "components/autofill/core/browser/autofill_driver.h" #include "components/autofill/core/browser/autofill_metrics.h" -#include "components/autofill/core/browser/form_structure.h" #include "components/autofill/core/browser/logging/log_manager.h" #include "components/autofill/core/browser/logging/log_protobufs.h" #include "components/autofill/core/browser/proto/legacy_proto_bridge.h" @@ -587,7 +586,7 @@ bool GetAPIQueryPayload(const AutofillQueryContents& query, } // namespace struct AutofillDownloadManager::FormRequestData { - std::vector<std::string> form_signatures; + FormAndFieldSignatures signatures; RequestType request_type; std::string payload; int num_attempts = 0; @@ -640,9 +639,8 @@ bool AutofillDownloadManager::StartQueryRequest( // Encode the query for the requested forms. AutofillQueryContents query; - FormRequestData request_data; - if (!FormStructure::EncodeQueryRequest(forms, &request_data.form_signatures, - &query)) { + FormAndFieldSignatures signatures; + if (!FormStructure::EncodeQueryRequest(forms, &query, &signatures)) { return false; } @@ -665,17 +663,27 @@ bool AutofillDownloadManager::StartQueryRequest( return false; } + FormRequestData request_data; + request_data.signatures = std::move(signatures); request_data.request_type = AutofillDownloadManager::REQUEST_QUERY; request_data.payload = std::move(payload); AutofillMetrics::LogServerQueryMetric(AutofillMetrics::QUERY_SENT); std::string query_data; - if (CheckCacheForQueryRequest(request_data.form_signatures, &query_data)) { + if (CheckCacheForQueryRequest(request_data.signatures, &query_data)) { DVLOG(1) << "AutofillDownloadManager: query request has been retrieved " - << "from the cache, form signatures: " - << GetCombinedSignature(request_data.form_signatures); + << "from the cache, form signatures:" << + [&request_data] { + std::string form_sigs; + for (const auto& form_and_fields : request_data.signatures) { + base::StrAppend( + &form_sigs, + {" ", base::NumberToString(form_and_fields.first.value())}); + } + return form_sigs; + }(); observer_->OnLoadedServerPredictions(std::move(query_data), - request_data.form_signatures); + request_data.signatures); return true; } @@ -710,9 +718,10 @@ bool AutofillDownloadManager::StartUploadRequest( return false; AutofillUploadContents upload; + FormAndFieldSignatures signatures; if (!form.EncodeUploadRequest(available_field_types, form_was_autofilled, login_form_signature, observed_submission, - &upload)) { + &upload, &signatures)) { return false; } @@ -743,7 +752,7 @@ bool AutofillDownloadManager::StartUploadRequest( } FormRequestData request_data; - request_data.form_signatures.push_back(form.FormSignatureAsStr()); + request_data.signatures = std::move(signatures); request_data.request_type = AutofillDownloadManager::REQUEST_UPLOAD; request_data.payload = std::move(payload); @@ -925,32 +934,27 @@ bool AutofillDownloadManager::StartRequest(FormRequestData request_data) { } void AutofillDownloadManager::CacheQueryRequest( - const std::vector<std::string>& forms_in_query, + const FormAndFieldSignatures& forms_in_query, const std::string& query_data) { - std::string signature = GetCombinedSignature(forms_in_query); for (auto it = cached_forms_.begin(); it != cached_forms_.end(); ++it) { - if (it->first == signature) { + if (it->first == forms_in_query) { // We hit the cache, move to the first position and return. - std::pair<std::string, std::string> data = *it; + auto data = *it; cached_forms_.erase(it); cached_forms_.push_front(data); return; } } - std::pair<std::string, std::string> data; - data.first = signature; - data.second = query_data; - cached_forms_.push_front(data); + cached_forms_.emplace_front(forms_in_query, query_data); while (cached_forms_.size() > max_form_cache_size_) cached_forms_.pop_back(); } bool AutofillDownloadManager::CheckCacheForQueryRequest( - const std::vector<std::string>& forms_in_query, + const FormAndFieldSignatures& forms_in_query, std::string* query_data) const { - std::string signature = GetCombinedSignature(forms_in_query); for (const auto& it : cached_forms_) { - if (it.first == signature) { + if (it.first == forms_in_query) { // We hit the cache, fill the data and return. *query_data = it.second; return true; @@ -959,23 +963,6 @@ bool AutofillDownloadManager::CheckCacheForQueryRequest( return false; } -std::string AutofillDownloadManager::GetCombinedSignature( - const std::vector<std::string>& forms_in_query) const { - size_t total_size = forms_in_query.size(); - for (size_t i = 0; i < forms_in_query.size(); ++i) - total_size += forms_in_query[i].length(); - std::string signature; - - signature.reserve(total_size); - - for (size_t i = 0; i < forms_in_query.size(); ++i) { - if (i) - signature.append(","); - signature.append(forms_in_query[i]); - } - return signature; -} - // static int AutofillDownloadManager::GetMaxServerAttempts() { // This value is constant for the life of the browser, so we cache it @@ -995,7 +982,7 @@ void AutofillDownloadManager::OnSimpleLoaderComplete( std::unique_ptr<network::SimpleURLLoader> simple_loader = std::move(*it); url_loaders_.erase(it); - CHECK(request_data.form_signatures.size()); + CHECK(request_data.signatures.size() > 0); int response_code = -1; // Invalid response code. if (simple_loader->ResponseInfo() && simple_loader->ResponseInfo()->headers) { response_code = simple_loader->ResponseInfo()->headers->response_code(); @@ -1024,7 +1011,7 @@ void AutofillDownloadManager::OnSimpleLoaderComplete( << response_code << " and error message from the server " << error_message; - observer_->OnServerRequestError(request_data.form_signatures[0], + observer_->OnServerRequestError(request_data.signatures.front().first, request_data.request_type, response_code); LogFailingPayloadSize(request_data.request_type, @@ -1053,11 +1040,11 @@ void AutofillDownloadManager::OnSimpleLoaderComplete( } if (request_data.request_type == AutofillDownloadManager::REQUEST_QUERY) { - CacheQueryRequest(request_data.form_signatures, *response_body); + CacheQueryRequest(request_data.signatures, *response_body); UMA_HISTOGRAM_BOOLEAN("Autofill.Query.WasInCache", simple_loader->LoadedFromCache()); observer_->OnLoadedServerPredictions(std::move(*response_body), - request_data.form_signatures); + request_data.signatures); return; } diff --git a/chromium/components/autofill/core/browser/autofill_download_manager.h b/chromium/components/autofill/core/browser/autofill_download_manager.h index 88ce53237a5..4531f498907 100644 --- a/chromium/components/autofill/core/browser/autofill_download_manager.h +++ b/chromium/components/autofill/core/browser/autofill_download_manager.h @@ -20,6 +20,8 @@ #include "base/strings/string_piece.h" #include "base/time/time.h" #include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/form_structure.h" +#include "components/autofill/core/common/signatures.h" #include "components/variations/variations_http_header_provider.h" #include "net/base/backoff_entry.h" #include "services/network/public/cpp/simple_url_loader.h" @@ -30,7 +32,6 @@ class PrefService; namespace autofill { class AutofillDriver; -class FormStructure; class LogManager; const size_t kMaxAPIQueryGetSize = 10240; // 10 KiB @@ -52,10 +53,10 @@ class AutofillDownloadManager { public: // Called when field type predictions are successfully received from the // server. |response| contains the server response for the forms - // represented by |form_signatures|. + // represented by |signatures|. virtual void OnLoadedServerPredictions( std::string response, - const std::vector<std::string>& form_signatures) = 0; + const FormAndFieldSignatures& signatures) = 0; // These notifications are used to help with testing. // Called when heuristic either successfully considered for upload and @@ -65,7 +66,7 @@ class AutofillDownloadManager { // |form_signature| - the signature of the requesting form. // |request_type| - type of request that failed. // |http_error| - HTTP error code. - virtual void OnServerRequestError(const std::string& form_signature, + virtual void OnServerRequestError(FormSignature form_signature, RequestType request_type, int http_error) {} @@ -135,7 +136,8 @@ class AutofillDownloadManager { FRIEND_TEST_ALL_PREFIXES(AutofillDownloadManagerTest, RetryLimit_Query); struct FormRequestData; - typedef std::list<std::pair<std::string, std::string> > QueryRequestCache; + typedef std::list<std::pair<FormAndFieldSignatures, std::string>> + QueryRequestCache; // Returns the URL and request method to use when issuing the request // described by |request_data|. If the returned method is GET, the URL @@ -163,11 +165,11 @@ class AutofillDownloadManager { // Caches query request. |forms_in_query| is a vector of form signatures in // the query. |query_data| is the successful data returned over the wire. - void CacheQueryRequest(const std::vector<std::string>& forms_in_query, + void CacheQueryRequest(const FormAndFieldSignatures& forms_in_query, const std::string& query_data); // Returns true if query is in the cache, while filling |query_data|, false // otherwise. |forms_in_query| is a vector of form signatures in the query. - bool CheckCacheForQueryRequest(const std::vector<std::string>& forms_in_query, + bool CheckCacheForQueryRequest(const FormAndFieldSignatures& forms_in_query, std::string* query_data) const; // Concatenates |forms_in_query| into one signature. std::string GetCombinedSignature( diff --git a/chromium/components/autofill/core/browser/autofill_download_manager_unittest.cc b/chromium/components/autofill/core/browser/autofill_download_manager_unittest.cc index 6b31dc6ef00..22fd6ac32b1 100644 --- a/chromium/components/autofill/core/browser/autofill_download_manager_unittest.cc +++ b/chromium/components/autofill/core/browser/autofill_download_manager_unittest.cc @@ -211,7 +211,7 @@ class AutofillDownloadManagerTest : public AutofillDownloadManager::Observer, // AutofillDownloadManager::Observer implementation. void OnLoadedServerPredictions( std::string response_xml, - const std::vector<std::string>& form_signatures) override { + const FormAndFieldSignatures& form_signatures) override { ResponseData response; response.response = std::move(response_xml); response.type_of_response = QUERY_SUCCESSFULL; @@ -224,11 +224,11 @@ class AutofillDownloadManagerTest : public AutofillDownloadManager::Observer, responses_.push_back(response); } - void OnServerRequestError(const std::string& form_signature, + void OnServerRequestError(FormSignature form_signature, AutofillDownloadManager::RequestType request_type, int http_error) override { ResponseData response; - response.signature = form_signature; + response.signature = base::NumberToString(form_signature.value()); response.error = http_error; response.type_of_response = request_type == AutofillDownloadManager::REQUEST_QUERY @@ -1401,7 +1401,7 @@ class AutofillServerCommunicationTest // AutofillDownloadManager::Observer implementation. void OnLoadedServerPredictions( std::string /* response_xml */, - const std::vector<std::string>& /*form_signatures */) override { + const FormAndFieldSignatures& /*form_signatures */) override { ASSERT_TRUE(run_loop_); run_loop_->QuitWhenIdle(); } @@ -2007,6 +2007,8 @@ TEST_P(AutofillUploadTest, RichMetadata) { EXPECT_TRUE(upload.randomized_form_metadata().has_id()); EXPECT_TRUE(upload.randomized_form_metadata().has_name()); EXPECT_TRUE(upload.randomized_form_metadata().has_url()); + ASSERT_TRUE(upload.randomized_form_metadata().has_checksum_for_url()); + EXPECT_EQ(upload.randomized_form_metadata().checksum_for_url(), 3608731642); EXPECT_EQ(3, upload.field_size()); for (const auto& f : upload.field()) { ASSERT_TRUE(f.has_randomized_field_metadata()); diff --git a/chromium/components/autofill/core/browser/autofill_experiments.cc b/chromium/components/autofill/core/browser/autofill_experiments.cc index ef21685dbc3..86f1fbca419 100644 --- a/chromium/components/autofill/core/browser/autofill_experiments.cc +++ b/chromium/components/autofill/core/browser/autofill_experiments.cc @@ -225,34 +225,4 @@ bool IsInAutofillSuggestionsDisabledExperiment() { return group_name == "Disabled"; } -bool OfferStoreUnmaskedCards(bool is_off_the_record) { -#if defined(OS_LINUX) && !defined(OS_CHROMEOS) - // The checkbox can be forced on with a flag, but by default we don't store - // on Linux due to lack of system keychain integration. See crbug.com/162735 - return base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kEnableOfferStoreUnmaskedWalletCards); -#else - // Never offer to store unmasked cards when off the record. - if (is_off_the_record) { - return false; - } - - // Query the field trial before checking command line flags to ensure UMA - // reports the correct group. - std::string group_name = - base::FieldTrialList::FindFullName("OfferStoreUnmaskedWalletCards"); - - // The checkbox can be forced on or off with flags. - if (base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kEnableOfferStoreUnmaskedWalletCards)) - return true; - if (base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kDisableOfferStoreUnmaskedWalletCards)) - return false; - - // Otherwise use the field trial to show the checkbox or not. - return group_name != "Disabled"; -#endif -} - } // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_experiments.h b/chromium/components/autofill/core/browser/autofill_experiments.h index d291ce0af08..d5b92be5822 100644 --- a/chromium/components/autofill/core/browser/autofill_experiments.h +++ b/chromium/components/autofill/core/browser/autofill_experiments.h @@ -44,11 +44,6 @@ bool IsCreditCardMigrationEnabled(PersonalDataManager* personal_data_manager, // disables providing suggestions. bool IsInAutofillSuggestionsDisabledExperiment(); -// Returns true if the user should be offered to locally store unmasked cards. -// This controls whether the option is presented at all rather than the default -// response of the option. -bool OfferStoreUnmaskedCards(bool is_off_the_record); - } // namespace autofill #endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_EXPERIMENTS_H_ diff --git a/chromium/components/autofill/core/browser/autofill_external_delegate.cc b/chromium/components/autofill/core/browser/autofill_external_delegate.cc index 6cc56740ad1..ca64c2ca325 100644 --- a/chromium/components/autofill/core/browser/autofill_external_delegate.cc +++ b/chromium/components/autofill/core/browser/autofill_external_delegate.cc @@ -39,6 +39,9 @@ namespace autofill { namespace { +using AutoselectFirstSuggestion = + AutofillClient::PopupOpenArgs::AutoselectFirstSuggestion; + // Returns true if the suggestion entry is an Autofill warning message. // Warning messages should display on top of suggestion list. bool IsAutofillWarningEntry(int frontend_id) { @@ -162,9 +165,10 @@ void AutofillExternalDelegate::OnSuggestionsReturned( // Send to display. if (query_field_.is_focusable && GetAutofillDriver()->CanShowAutofillUi()) { - manager_->client()->ShowAutofillPopup( + autofill::AutofillClient::PopupOpenArgs open_args( element_bounds_, query_field_.text_direction, suggestions, - autoselect_first_suggestion, popup_type_, GetWeakPtr()); + AutoselectFirstSuggestion(autoselect_first_suggestion), popup_type_); + manager_->client()->ShowAutofillPopup(open_args, GetWeakPtr()); } } diff --git a/chromium/components/autofill/core/browser/autofill_external_delegate_unittest.cc b/chromium/components/autofill/core/browser/autofill_external_delegate_unittest.cc index b70b30a792f..fd2f179f617 100644 --- a/chromium/components/autofill/core/browser/autofill_external_delegate_unittest.cc +++ b/chromium/components/autofill/core/browser/autofill_external_delegate_unittest.cc @@ -7,6 +7,7 @@ #include "base/command_line.h" #include "base/compiler_specific.h" +#include "base/i18n/rtl.h" #include "base/macros.h" #include "base/strings/string16.h" #include "base/strings/utf_string_conversions.h" @@ -35,6 +36,7 @@ #include "testing/gtest/include/gtest/gtest.h" #include "ui/base/l10n/l10n_util.h" #include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rect_f.h" using base::ASCIIToUTF16; using testing::_; @@ -51,15 +53,22 @@ const int kAutofillProfileId = 1; class MockAutofillDriver : public TestAutofillDriver { public: - MockAutofillDriver() {} + MockAutofillDriver() = default; // Mock methods to enable testability. - MOCK_METHOD1(RendererShouldAcceptDataListSuggestion, - void(const base::string16&)); - MOCK_METHOD0(RendererShouldClearFilledSection, void()); - MOCK_METHOD0(RendererShouldClearPreviewedForm, void()); - MOCK_METHOD1(RendererShouldFillFieldWithValue, void(const base::string16&)); - MOCK_METHOD1(RendererShouldPreviewFieldWithValue, - void(const base::string16&)); + MOCK_METHOD(void, + RendererShouldAcceptDataListSuggestion, + (const base::string16&), + (override)); + MOCK_METHOD(void, RendererShouldClearFilledSection, (), (override)); + MOCK_METHOD(void, RendererShouldClearPreviewedForm, (), (override)); + MOCK_METHOD(void, + RendererShouldFillFieldWithValue, + (const base::string16&), + (override)); + MOCK_METHOD(void, + RendererShouldPreviewFieldWithValue, + (const base::string16&), + (override)); private: DISALLOW_COPY_AND_ASSIGN(MockAutofillDriver); @@ -67,25 +76,23 @@ class MockAutofillDriver : public TestAutofillDriver { class MockAutofillClient : public TestAutofillClient { public: - MockAutofillClient() {} - - MOCK_METHOD1(ScanCreditCard, void(CreditCardScanCallback callbacK)); - - MOCK_METHOD6(ShowAutofillPopup, - void(const gfx::RectF& element_bounds, - base::i18n::TextDirection text_direction, - const std::vector<Suggestion>& suggestions, - bool autoselect_first_suggestion, - PopupType popup_type, - base::WeakPtr<AutofillPopupDelegate> delegate)); - - MOCK_METHOD2(UpdateAutofillPopupDataListValues, - void(const std::vector<base::string16>& values, - const std::vector<base::string16>& lables)); - - MOCK_METHOD1(HideAutofillPopup, void(PopupHidingReason)); - - MOCK_METHOD1(ExecuteCommand, void(int)); + MockAutofillClient() = default; + MOCK_METHOD(void, + ScanCreditCard, + (CreditCardScanCallback callbacK), + (override)); + MOCK_METHOD(void, + ShowAutofillPopup, + (const autofill::AutofillClient::PopupOpenArgs& open_args, + base::WeakPtr<AutofillPopupDelegate> delegate), + (override)); + MOCK_METHOD(void, + UpdateAutofillPopupDataListValues, + (const std::vector<base::string16>& values, + const std::vector<base::string16>& lables), + (override)); + MOCK_METHOD(void, HideAutofillPopup, (PopupHidingReason), (override)); + MOCK_METHOD(void, ExecuteCommand, (int), (override)); private: DISALLOW_COPY_AND_ASSIGN(MockAutofillClient); @@ -99,21 +106,24 @@ class MockAutofillManager : public AutofillManager { client, client->GetPersonalDataManager(), client->GetAutocompleteHistoryManager()) {} - ~MockAutofillManager() override {} PopupType GetPopupType(const FormData& form, const FormFieldData& field) override { return PopupType::kPersonalInformation; } - MOCK_METHOD2(ShouldShowScanCreditCard, - bool(const FormData& form, const FormFieldData& field)); - - MOCK_METHOD2(ShouldShowCreditCardSigninPromo, - bool(const FormData& form, const FormFieldData& field)); - - MOCK_METHOD2(OnUserHideSuggestions, - void(const FormData& form, const FormFieldData& field)); + MOCK_METHOD(bool, + ShouldShowScanCreditCard, + (const FormData& form, const FormFieldData& field), + (override)); + MOCK_METHOD(bool, + ShouldShowCreditCardSigninPromo, + (const FormData& form, const FormFieldData& field), + (override)); + MOCK_METHOD(void, + OnUserHideSuggestions, + (const FormData& form, const FormFieldData& field), + (override)); bool ShouldShowCardsFromAccountOption(const FormData& form, const FormFieldData& field) { @@ -124,19 +134,22 @@ class MockAutofillManager : public AutofillManager { should_show_cards_from_account_option_ = true; } - MOCK_METHOD5(FillOrPreviewForm, - void(AutofillDriver::RendererFormDataAction action, - int query_id, - const FormData& form, - const FormFieldData& field, - int unique_id)); - - MOCK_METHOD5(FillCreditCardForm, - void(int query_id, - const FormData& form, - const FormFieldData& field, - const CreditCard& credit_card, - const base::string16& cvc)); + MOCK_METHOD(void, + FillOrPreviewForm, + (AutofillDriver::RendererFormDataAction action, + int query_id, + const FormData& form, + const FormFieldData& field, + int unique_id), + (override)); + MOCK_METHOD(void, + FillCreditCardForm, + (int query_id, + const FormData& form, + const FormFieldData& field, + const CreditCard& credit_card, + const base::string16& cvc), + (override)); private: bool should_show_cards_from_account_option_ = false; @@ -208,9 +221,9 @@ TEST_F(AutofillExternalDelegateUnitTest, TestExternalDelegateVirtualCalls) { // The enums must be cast to ints to prevent compile errors on linux_rel. auto element_ids = testing::ElementsAre( kAutofillProfileId, static_cast<int>(POPUP_ITEM_ID_AUTOFILL_OPTIONS)); - EXPECT_CALL(autofill_client_, - ShowAutofillPopup(_, _, SuggestionVectorIdsAre(element_ids), - false, PopupType::kPersonalInformation, _)); + AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client_, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); // This should call ShowAutofillPopup. std::vector<Suggestion> autofill_item; @@ -218,6 +231,9 @@ TEST_F(AutofillExternalDelegateUnitTest, TestExternalDelegateVirtualCalls) { autofill_item[0].frontend_id = kAutofillProfileId; external_delegate_->OnSuggestionsReturned( kQueryId, autofill_item, /*autoselect_first_suggestion=*/false); + EXPECT_THAT(open_args.suggestions, SuggestionVectorIdsAre(element_ids)); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPersonalInformation); EXPECT_CALL( *autofill_manager_, @@ -243,10 +259,9 @@ TEST_F(AutofillExternalDelegateUnitTest, // The enums must be cast to ints to prevent compile errors on linux_rel. auto element_ids = testing::ElementsAre( kAutofillProfileId, static_cast<int>(POPUP_ITEM_ID_AUTOFILL_OPTIONS)); - - EXPECT_CALL(autofill_client_, - ShowAutofillPopup(_, _, SuggestionVectorIdsAre(element_ids), - false, PopupType::kPersonalInformation, _)); + AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client_, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); base::UserActionTester user_action_tester; @@ -258,6 +273,9 @@ TEST_F(AutofillExternalDelegateUnitTest, kQueryId, autofill_item, /*autoselect_first_suggestion=*/false); EXPECT_EQ(0, user_action_tester.GetActionCount( "Signin_Impression_FromAutofillDropdown")); + EXPECT_THAT(open_args.suggestions, SuggestionVectorIdsAre(element_ids)); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPersonalInformation); EXPECT_CALL( *autofill_manager_, @@ -284,9 +302,9 @@ TEST_F(AutofillExternalDelegateUnitTest, auto element_ids = testing::ElementsAre( static_cast<int>(POPUP_ITEM_ID_CREDIT_CARD_SIGNIN_PROMO)); - EXPECT_CALL(autofill_client_, - ShowAutofillPopup(_, _, SuggestionVectorIdsAre(element_ids), - false, PopupType::kPersonalInformation, _)); + AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client_, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); base::UserActionTester user_action_tester; @@ -296,6 +314,9 @@ TEST_F(AutofillExternalDelegateUnitTest, kQueryId, items, /*autoselect_first_suggestion=*/false); EXPECT_EQ(1, user_action_tester.GetActionCount( "Signin_Impression_FromAutofillDropdown")); + EXPECT_THAT(open_args.suggestions, SuggestionVectorIdsAre(element_ids)); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPersonalInformation); EXPECT_CALL(autofill_client_, ExecuteCommand(autofill::POPUP_ITEM_ID_CREDIT_CARD_SIGNIN_PROMO)); @@ -328,9 +349,9 @@ TEST_F(AutofillExternalDelegateUnitTest, ExternalDelegateDataList) { static_cast<int>(POPUP_ITEM_ID_SEPARATOR), #endif kAutofillProfileId, static_cast<int>(POPUP_ITEM_ID_AUTOFILL_OPTIONS)); - EXPECT_CALL(autofill_client_, - ShowAutofillPopup(_, _, SuggestionVectorIdsAre(element_ids), - false, PopupType::kPersonalInformation, _)); + AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client_, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); // This should call ShowAutofillPopup. std::vector<Suggestion> autofill_item; @@ -338,20 +359,25 @@ TEST_F(AutofillExternalDelegateUnitTest, ExternalDelegateDataList) { autofill_item[0].frontend_id = kAutofillProfileId; external_delegate_->OnSuggestionsReturned( kQueryId, autofill_item, /*autoselect_first_suggestion=*/false); + EXPECT_THAT(open_args.suggestions, SuggestionVectorIdsAre(element_ids)); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPersonalInformation); // Try calling OnSuggestionsReturned with no Autofill values and ensure // the datalist items are still shown. // The enum must be cast to an int to prevent compile errors on linux_rel. - EXPECT_CALL( - autofill_client_, - ShowAutofillPopup(_, _, - SuggestionVectorIdsAre(testing::ElementsAre( - static_cast<int>(POPUP_ITEM_ID_DATALIST_ENTRY))), - false, PopupType::kPersonalInformation, _)); + + EXPECT_CALL(autofill_client_, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); autofill_item.clear(); external_delegate_->OnSuggestionsReturned( kQueryId, autofill_item, /*autoselect_first_suggestion=*/false); + EXPECT_THAT(open_args.suggestions, + SuggestionVectorIdsAre(testing::ElementsAre( + static_cast<int>(POPUP_ITEM_ID_DATALIST_ENTRY)))); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPersonalInformation); } // Test that datalist values can get updated while a popup is showing. @@ -378,9 +404,9 @@ TEST_F(AutofillExternalDelegateUnitTest, UpdateDataListWhileShowingPopup) { static_cast<int>(POPUP_ITEM_ID_SEPARATOR), #endif kAutofillProfileId, static_cast<int>(POPUP_ITEM_ID_AUTOFILL_OPTIONS)); - EXPECT_CALL(autofill_client_, - ShowAutofillPopup(_, _, SuggestionVectorIdsAre(element_ids), - false, PopupType::kPersonalInformation, _)); + AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client_, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); // Ensure the popup is displayed. std::vector<Suggestion> autofill_item; @@ -388,6 +414,9 @@ TEST_F(AutofillExternalDelegateUnitTest, UpdateDataListWhileShowingPopup) { autofill_item[0].frontend_id = kAutofillProfileId; external_delegate_->OnSuggestionsReturned( kQueryId, autofill_item, /*autoselect_first_suggestion=*/false); + EXPECT_THAT(open_args.suggestions, SuggestionVectorIdsAre(element_ids)); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPersonalInformation); // This would normally get called from ShowAutofillPopup, but it is mocked so // we need to call OnPopupShown ourselves. @@ -428,9 +457,9 @@ TEST_F(AutofillExternalDelegateUnitTest, DuplicateAutofillDatalistValues) { static_cast<int>(POPUP_ITEM_ID_SEPARATOR), #endif kAutofillProfileId, static_cast<int>(POPUP_ITEM_ID_AUTOFILL_OPTIONS)); - EXPECT_CALL(autofill_client_, - ShowAutofillPopup(_, _, SuggestionVectorIdsAre(element_ids), - false, PopupType::kPersonalInformation, _)); + AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client_, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); // Have an Autofill item that is identical to one of the datalist entries. std::vector<Suggestion> autofill_item; @@ -440,6 +469,9 @@ TEST_F(AutofillExternalDelegateUnitTest, DuplicateAutofillDatalistValues) { autofill_item[0].frontend_id = kAutofillProfileId; external_delegate_->OnSuggestionsReturned( kQueryId, autofill_item, /*autoselect_first_suggestion=*/false); + EXPECT_THAT(open_args.suggestions, SuggestionVectorIdsAre(element_ids)); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPersonalInformation); } // Test that we de-dupe autocomplete values against datalist values, keeping the @@ -467,9 +499,9 @@ TEST_F(AutofillExternalDelegateUnitTest, DuplicateAutocompleteDatalistValues) { static_cast<int>(POPUP_ITEM_ID_SEPARATOR), #endif static_cast<int>(POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY)); - EXPECT_CALL(autofill_client_, - ShowAutofillPopup(_, _, SuggestionVectorIdsAre(element_ids), - false, PopupType::kPersonalInformation, _)); + AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client_, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); // Have an Autocomplete item that is identical to one of the datalist entries // and one that is distinct. @@ -482,6 +514,9 @@ TEST_F(AutofillExternalDelegateUnitTest, DuplicateAutocompleteDatalistValues) { autocomplete_items[1].frontend_id = POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY; external_delegate_->OnSuggestionsReturned( kQueryId, autocomplete_items, /*autoselect_first_suggestion=*/false); + EXPECT_THAT(open_args.suggestions, SuggestionVectorIdsAre(element_ids)); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPersonalInformation); } // Test that the Autofill popup is able to display warnings explaining why @@ -490,14 +525,9 @@ TEST_F(AutofillExternalDelegateUnitTest, DuplicateAutocompleteDatalistValues) { TEST_F(AutofillExternalDelegateUnitTest, AutofillWarnings) { IssueOnQuery(kQueryId); - // The enums must be cast to ints to prevent compile errors on linux_rel. - EXPECT_CALL( - autofill_client_, - ShowAutofillPopup( - _, _, - SuggestionVectorIdsAre(testing::ElementsAre(static_cast<int>( - POPUP_ITEM_ID_INSECURE_CONTEXT_PAYMENT_DISABLED_MESSAGE))), - false, PopupType::kPersonalInformation, _)); + AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client_, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); // This should call ShowAutofillPopup. std::vector<Suggestion> autofill_item; @@ -506,6 +536,15 @@ TEST_F(AutofillExternalDelegateUnitTest, AutofillWarnings) { POPUP_ITEM_ID_INSECURE_CONTEXT_PAYMENT_DISABLED_MESSAGE; external_delegate_->OnSuggestionsReturned( kQueryId, autofill_item, /*autoselect_first_suggestion=*/false); + + // The enums must be cast to ints to prevent compile errors on linux_rel. + EXPECT_THAT(open_args.suggestions, + SuggestionVectorIdsAre(testing::ElementsAre(static_cast<int>( + POPUP_ITEM_ID_INSECURE_CONTEXT_PAYMENT_DISABLED_MESSAGE)))); + EXPECT_EQ(open_args.element_bounds, gfx::RectF()); + EXPECT_EQ(open_args.text_direction, base::i18n::UNKNOWN_DIRECTION); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPersonalInformation); } // Test that Autofill warnings are removed if there are also autocomplete @@ -514,13 +553,9 @@ TEST_F(AutofillExternalDelegateUnitTest, AutofillWarningsNotShown_WithSuggestions) { IssueOnQuery(kQueryId); - // The enums must be cast to ints to prevent compile errors on linux_rel. - EXPECT_CALL(autofill_client_, - ShowAutofillPopup( - _, _, - SuggestionVectorIdsAre(testing::ElementsAre( - static_cast<int>(POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY))), - false, PopupType::kPersonalInformation, _)); + AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client_, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); // This should call ShowAutofillPopup. std::vector<Suggestion> suggestions; @@ -532,6 +567,13 @@ TEST_F(AutofillExternalDelegateUnitTest, suggestions[1].frontend_id = POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY; external_delegate_->OnSuggestionsReturned( kQueryId, suggestions, /*autoselect_first_suggestion=*/false); + + // The enums must be cast to ints to prevent compile errors on linux_rel. + EXPECT_THAT(open_args.suggestions, + SuggestionVectorIdsAre(testing::ElementsAre( + static_cast<int>(POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY)))); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPersonalInformation); } // Test that the Autofill delegate doesn't try and fill a form with a @@ -768,9 +810,9 @@ TEST_F(AutofillExternalDelegateUnitTest, ShouldShowGooglePayIcon) { auto element_icons = testing::ElementsAre(std::string(), testing::StartsWith("googlePay")); - EXPECT_CALL(autofill_client_, - ShowAutofillPopup(_, _, SuggestionVectorIconsAre(element_icons), - false, PopupType::kPersonalInformation, _)); + AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client_, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); std::vector<Suggestion> autofill_item; autofill_item.push_back(Suggestion()); @@ -779,6 +821,9 @@ TEST_F(AutofillExternalDelegateUnitTest, ShouldShowGooglePayIcon) { // This should call ShowAutofillPopup. external_delegate_->OnSuggestionsReturned( kQueryId, autofill_item, /*autoselect_first_suggestion=*/false, true); + EXPECT_THAT(open_args.suggestions, SuggestionVectorIconsAre(element_icons)); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPersonalInformation); } TEST_F(AutofillExternalDelegateUnitTest, @@ -788,9 +833,9 @@ TEST_F(AutofillExternalDelegateUnitTest, auto element_icons = testing::ElementsAre( std::string(), std::string() /* Autofill setting item does not have icon. */); - EXPECT_CALL(autofill_client_, - ShowAutofillPopup(_, _, SuggestionVectorIconsAre(element_icons), - false, PopupType::kPersonalInformation, _)); + AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client_, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); std::vector<Suggestion> autofill_item; autofill_item.push_back(Suggestion()); @@ -799,6 +844,9 @@ TEST_F(AutofillExternalDelegateUnitTest, // This should call ShowAutofillPopup. external_delegate_->OnSuggestionsReturned( kQueryId, autofill_item, /*autoselect_first_suggestion=*/false, false); + EXPECT_THAT(open_args.suggestions, SuggestionVectorIconsAre(element_icons)); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPersonalInformation); } TEST_F(AutofillExternalDelegateUnitTest, ShouldUseNewSettingName) { @@ -806,9 +854,9 @@ TEST_F(AutofillExternalDelegateUnitTest, ShouldUseNewSettingName) { auto element_values = testing::ElementsAre( base::string16(), l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE)); - EXPECT_CALL(autofill_client_, - ShowAutofillPopup(_, _, SuggestionVectorValuesAre(element_values), - false, PopupType::kPersonalInformation, _)); + AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client_, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); std::vector<Suggestion> autofill_item; autofill_item.push_back(Suggestion()); @@ -817,6 +865,9 @@ TEST_F(AutofillExternalDelegateUnitTest, ShouldUseNewSettingName) { // This should call ShowAutofillPopup. external_delegate_->OnSuggestionsReturned( kQueryId, autofill_item, /*autoselect_first_suggestion=*/false); + EXPECT_THAT(open_args.suggestions, SuggestionVectorValuesAre(element_values)); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPersonalInformation); } // Tests that the prompt to show account cards shows up when the corresponding @@ -830,9 +881,9 @@ TEST_F(AutofillExternalDelegateCardsFromAccountTest, base::string16(), l10n_util::GetStringUTF16(IDS_AUTOFILL_SHOW_ACCOUNT_CARDS), l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE)); - EXPECT_CALL(autofill_client_, - ShowAutofillPopup(_, _, SuggestionVectorValuesAre(element_values), - false, PopupType::kPersonalInformation, _)); + AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client_, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); std::vector<Suggestion> autofill_item; autofill_item.push_back(Suggestion()); @@ -840,6 +891,9 @@ TEST_F(AutofillExternalDelegateCardsFromAccountTest, external_delegate_->OnSuggestionsReturned( kQueryId, autofill_item, /*autoselect_first_suggestion=*/false); + EXPECT_THAT(open_args.suggestions, SuggestionVectorValuesAre(element_values)); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPersonalInformation); } // Tests that the prompt to show account cards shows up when the corresponding @@ -851,13 +905,16 @@ TEST_F(AutofillExternalDelegateCardsFromAccountTest, auto element_values = testing::ElementsAre( l10n_util::GetStringUTF16(IDS_AUTOFILL_SHOW_ACCOUNT_CARDS)); - EXPECT_CALL(autofill_client_, - ShowAutofillPopup(_, _, SuggestionVectorValuesAre(element_values), - false, PopupType::kPersonalInformation, _)); + AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client_, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); external_delegate_->OnSuggestionsReturned( kQueryId, std::vector<Suggestion>(), /*autoselect_first_suggestion=*/false); + EXPECT_THAT(open_args.suggestions, SuggestionVectorValuesAre(element_values)); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPersonalInformation); } } // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_handler.cc b/chromium/components/autofill/core/browser/autofill_handler.cc index 60c09c74bf0..ddb4f6816d3 100644 --- a/chromium/components/autofill/core/browser/autofill_handler.cc +++ b/chromium/components/autofill/core/browser/autofill_handler.cc @@ -5,13 +5,16 @@ #include "components/autofill/core/browser/autofill_handler.h" #include "base/containers/adapters.h" +#include "base/feature_list.h" #include "components/autofill/core/browser/form_structure.h" #include "components/autofill/core/browser/logging/log_manager.h" #include "components/autofill/core/common/autofill_data_validation.h" +#include "components/autofill/core/common/autofill_features.h" #include "components/autofill/core/common/autofill_internals/log_message.h" #include "components/autofill/core/common/autofill_internals/logging_scope.h" #include "components/autofill/core/common/autofill_payments_features.h" #include "components/autofill/core/common/autofill_tick_clock.h" +#include "components/autofill/core/common/renderer_id.h" #include "components/autofill/core/common/signatures.h" #include "ui/gfx/geometry/rect_f.h" @@ -79,46 +82,62 @@ void AutofillHandler::OnFormsSeen(const std::vector<FormData>& forms, if (forms.empty()) return; - // Parse each of the forms. Because parsing a given FormData may invalidate - // and replace a form parsed before it (invalidating any pointers we might - // hold) we track the newly created form signatures instead of remembering - // the pointer values. - std::set<FormSignature> new_form_signatures; + std::set<FormRendererId> new_form_renderer_ids; for (const FormData& form : forms) { const auto parse_form_start_time = AutofillTickClock::NowTicks(); - FormStructure* cached_form_structure = nullptr; - FormStructure* form_structure = nullptr; - // Try to find the FormStructure that corresponds to |form| if the form - // contains credit card fields only. - // |cached_form_structure| may still be nullptr after this call. - ignore_result(FindCachedForm(form, &cached_form_structure)); + FormStructure* cached_form_structure = + FindCachedFormByRendererId(form.unique_renderer_id); + // Autofill used to ignore cache hits for non-credit-card forms. The + // motivation behind this is probably to have credit-card forms preserve + // their original signature, whereas non-credit-card forms would use the + // most recent form signature. Ignoring cache hits however appears to be + // part of breaking profile imports and voting for dynamic forms. See + // crbug/1091401#c15 for details. + // + // Therefore, if the kAutofillKeepInitialFormValuesInCache experiment is + // enabled, we do not ignore cache hits, but in those cases where the old + // code would have ignored the cache hit we update the FormStructure's + // FormSignature. + // Otherwise, if the experiment disabled, we just ignore the cache hit. + // + // TODO(crbug.com/1100231) Clean up when experiment is complete. + const bool kOldBehavior = !base::FeatureList::IsEnabled( + features::kAutofillKeepInitialFormValuesInCache); + bool update_form_signature = false; if (cached_form_structure) { for (const FormType& form_type : cached_form_structure->GetFormTypes()) { if (form_type != CREDIT_CARD_FORM) { - cached_form_structure = nullptr; + update_form_signature = true; + if (kOldBehavior) + cached_form_structure = nullptr; break; } } } - if (!ParseForm(form, cached_form_structure, &form_structure)) + FormStructure* form_structure = ParseForm(form, cached_form_structure); + if (!form_structure) continue; DCHECK(form_structure); - new_form_signatures.insert(form_structure->form_signature()); + + if (update_form_signature && !kOldBehavior) + form_structure->set_form_signature(CalculateFormSignature(form)); + + new_form_renderer_ids.insert(form_structure->unique_renderer_id()); AutofillMetrics::LogParseFormTiming(AutofillTickClock::NowTicks() - parse_form_start_time); } - if (new_form_signatures.empty()) + if (new_form_renderer_ids.empty()) return; // Populate the set of newly created form structures and call the // OnFormsParsed handler. std::vector<FormStructure*> new_form_structures; - new_form_structures.reserve(new_form_signatures.size()); - for (auto signature : new_form_signatures) { - FormStructure* form_structure = nullptr; - if (FindCachedForm(signature, &form_structure) && form_structure) { + new_form_structures.reserve(new_form_renderer_ids.size()); + for (auto renderer_id : new_form_renderer_ids) { + FormStructure* form_structure = FindCachedFormByRendererId(renderer_id); + if (form_structure) { new_form_structures.push_back(form_structure); } else { NOTREACHED(); @@ -204,8 +223,9 @@ bool AutofillHandler::GetCachedFormAndField(const FormData& form, FormStructure** form_structure, AutofillField** autofill_field) { // Maybe find an existing FormStructure that corresponds to |form|. - FormStructure* cached_form = nullptr; - if (FindCachedForm(form, &cached_form)) { + FormStructure* cached_form = + FindCachedFormByRendererId(form.unique_renderer_id); + if (cached_form) { DCHECK(cached_form); if (!CachedFormNeedsUpdate(form, *cached_form)) { // There is no data to return if there are no auto-fillable fields. @@ -221,7 +241,8 @@ bool AutofillHandler::GetCachedFormAndField(const FormData& form, // The form is new or updated, parse it and discard |cached_form|. // i.e., |cached_form| is no longer valid after this call. - if (!ParseForm(form, std::move(cached_form), form_structure)) + *form_structure = ParseForm(form, cached_form); + if (!*form_structure) return false; // Annotate the updated form with its predicted types. @@ -236,52 +257,40 @@ bool AutofillHandler::GetCachedFormAndField(const FormData& form, return *autofill_field != nullptr; } -bool AutofillHandler::FindCachedForm(FormSignature form_signature, - FormStructure** form_structure) const { - auto it = form_structures_.find(form_signature); - if (it != form_structures_.end()) { - *form_structure = it->second.get(); - return true; - } - return false; -} - -bool AutofillHandler::FindCachedForm(const FormData& form, - FormStructure** form_structure) const { - // Find the FormStructure that corresponds to |form|. - if (FindCachedForm(autofill::CalculateFormSignature(form), form_structure)) - return true; - - // The form might have been modified by JavaScript which resulted in a change - // of form signature. Compare it to all the forms in the cache to look for a - // match. - for (const auto& it : form_structures_) { - if (*it.second == form) { - *form_structure = it.second.get(); - return true; +size_t AutofillHandler::FindCachedFormsBySignature( + FormSignature form_signature, + std::vector<FormStructure*>* form_structures) const { + size_t hits_num = 0; + for (const auto& p : form_structures_) { + if (p.second->form_signature() == form_signature) { + ++hits_num; + if (form_structures) + form_structures->push_back(p.second.get()); } } + return hits_num; +} - *form_structure = nullptr; - return false; +FormStructure* AutofillHandler::FindCachedFormByRendererId( + FormRendererId form_renderer_id) const { + auto it = form_structures_.find(form_renderer_id); + return it != form_structures_.end() ? it->second.get() : nullptr; } -bool AutofillHandler::ParseForm(const FormData& form, - const FormStructure* cached_form, - FormStructure** parsed_form_structure) { - DCHECK(parsed_form_structure); +FormStructure* AutofillHandler::ParseForm(const FormData& form, + const FormStructure* cached_form) { if (form_structures_.size() >= kAutofillHandlerMaxFormCacheSize) { if (log_manager_) { log_manager_->Log() << LoggingScope::kAbortParsing << LogMessage::kAbortParsingTooManyForms << form; } - return false; + return nullptr; } auto form_structure = std::make_unique<FormStructure>(form); form_structure->ParseFieldTypesFromAutocompleteAttributes(); if (!form_structure->ShouldBeParsed(log_manager_)) - return false; + return nullptr; if (cached_form) { // We need to keep the server data if available. We need to use them while @@ -300,17 +309,17 @@ bool AutofillHandler::ParseForm(const FormData& form, // Hold the parsed_form_structure we intend to return. We can use this to // reference the form_signature when transferring ownership below. - *parsed_form_structure = form_structure.get(); + FormStructure* parsed_form_structure = form_structure.get(); // Ownership is transferred to |form_structures_| which maintains it until // the form is parsed again or the AutofillHandler is destroyed. // // Note that this insert/update takes ownership of the new form structure // and also destroys the previously cached form structure. - form_structures_[(*parsed_form_structure)->form_signature()] = + form_structures_[parsed_form_structure->unique_renderer_id()] = std::move(form_structure); - return true; + return parsed_form_structure; } void AutofillHandler::Reset() { diff --git a/chromium/components/autofill/core/browser/autofill_handler.h b/chromium/components/autofill/core/browser/autofill_handler.h index 942d74de177..a7965f77308 100644 --- a/chromium/components/autofill/core/browser/autofill_handler.h +++ b/chromium/components/autofill/core/browser/autofill_handler.h @@ -16,6 +16,7 @@ #include "components/autofill/core/browser/autofill_driver.h" #include "components/autofill/core/common/form_data.h" #include "components/autofill/core/common/mojom/autofill_types.mojom.h" +#include "components/autofill/core/common/renderer_id.h" #include "components/autofill/core/common/signatures.h" namespace gfx { @@ -46,9 +47,6 @@ class AutofillHandler { virtual void OnFormParsed() = 0; }; - using FormStructureMap = - std::map<FormSignature, std::unique_ptr<FormStructure>>; - virtual ~AutofillHandler(); // Invoked when the value of textfield is changed. @@ -108,10 +106,6 @@ class AutofillHandler { // Invoked when popup window should be hidden. virtual void OnHidePopup() = 0; - // Invoked when data list need to be set. - virtual void OnSetDataList(const std::vector<base::string16>& values, - const std::vector<base::string16>& labels) = 0; - // Invoked when the options of a select element in the |form| changed. virtual void SelectFieldOptionsDidChange(const FormData& form) = 0; @@ -140,14 +134,18 @@ class AutofillHandler { } // Returns the present form structures seen by Autofill handler. - const FormStructureMap& form_structures() const { return form_structures_; } + const std::map<FormRendererId, std::unique_ptr<FormStructure>>& + form_structures() const { + return form_structures_; + } AutofillDriver* driver() { return driver_; } #if defined(UNIT_TEST) // A public wrapper that calls |mutable_form_structures| for testing purposes // only. - FormStructureMap* mutable_form_structures_for_test() { + std::map<FormRendererId, std::unique_ptr<FormStructure>>* + mutable_form_structures_for_test() { return mutable_form_structures(); } #endif @@ -192,30 +190,28 @@ class AutofillHandler { virtual void OnFormsParsed(const std::vector<FormStructure*>& form_structures, const base::TimeTicks timestamp) = 0; - // Fills |form_structure| with a pointer to the cached form structure - // corresponding to |form_signature|. Returns false if no cached form - // structure is found with a matching signature. - bool FindCachedForm(FormSignature form_signature, - FormStructure** form_structure) const WARN_UNUSED_RESULT; + // Returns the number of FormStructures with the given |form_signature| and + // appends them to |form_structures|. Runs in linear time. + size_t FindCachedFormsBySignature( + FormSignature form_signature, + std::vector<FormStructure*>* form_structures) const; - // Fills |form_structure| with a pointer to the cached form structure - // corresponding to |form|. This will do a direct match of the form's - // signature as well as fuzzy match of the forms structure if no directly - // matching form signature is found. Returns false if no match is found. - bool FindCachedForm(const FormData& form, - FormStructure** form_structure) const WARN_UNUSED_RESULT; + // Returns nullptr if no cached form structure is found with a matching + // |renderer_id|. Runs in logarithmic time. + FormStructure* FindCachedFormByRendererId(FormRendererId renderer_id) const; // Parses the |form| with the server data retrieved from the |cached_form| - // (if any), and writes it to the |parse_form_structure|. Adds the - // |parse_form_structure| to the |form_structures_|. Returns true if the form - // is parsed. - bool ParseForm(const FormData& form, - const FormStructure* cached_form, - FormStructure** parsed_form_structure); + // (if any). Returns nullptr if the form should not be parsed. Otherwise, adds + // the returned form structure to the |form_structures_|. + FormStructure* ParseForm(const FormData& form, + const FormStructure* cached_form); bool value_from_dynamic_change_form_ = false; - FormStructureMap* mutable_form_structures() { return &form_structures_; } + std::map<FormRendererId, std::unique_ptr<FormStructure>>* + mutable_form_structures() { + return &form_structures_; + } private: // Provides driver-level context to the shared code of the component. Must @@ -225,7 +221,7 @@ class AutofillHandler { LogManager* const log_manager_; // Our copy of the form data. - FormStructureMap form_structures_; + std::map<FormRendererId, std::unique_ptr<FormStructure>> form_structures_; // Will be not null only for |SaveCardBubbleViewsFullFormBrowserTest|. ObserverForTest* observer_for_testing_ = nullptr; diff --git a/chromium/components/autofill/core/browser/autofill_handler_proxy.cc b/chromium/components/autofill/core/browser/autofill_handler_proxy.cc index 8a9fcaf4558..a246c8c8d72 100644 --- a/chromium/components/autofill/core/browser/autofill_handler_proxy.cc +++ b/chromium/components/autofill/core/browser/autofill_handler_proxy.cc @@ -88,11 +88,9 @@ void AutofillHandlerProxy::OnDidPreviewAutofillFormData() {} void AutofillHandlerProxy::OnDidEndTextFieldEditing() {} -void AutofillHandlerProxy::OnHidePopup() {} - -void AutofillHandlerProxy::OnSetDataList( - const std::vector<base::string16>& values, - const std::vector<base::string16>& labels) {} +void AutofillHandlerProxy::OnHidePopup() { + provider_->OnHidePopup(this); +} void AutofillHandlerProxy::SelectFieldOptionsDidChange(const FormData& form) {} diff --git a/chromium/components/autofill/core/browser/autofill_handler_proxy.h b/chromium/components/autofill/core/browser/autofill_handler_proxy.h index 152292c21ab..2abce2fdb02 100644 --- a/chromium/components/autofill/core/browser/autofill_handler_proxy.h +++ b/chromium/components/autofill/core/browser/autofill_handler_proxy.h @@ -28,8 +28,6 @@ class AutofillHandlerProxy : public AutofillHandler { void OnDidPreviewAutofillFormData() override; void OnDidEndTextFieldEditing() override; void OnHidePopup() override; - void OnSetDataList(const std::vector<base::string16>& values, - const std::vector<base::string16>& labels) override; void SelectFieldOptionsDidChange(const FormData& form) override; void Reset() override; diff --git a/chromium/components/autofill/core/browser/autofill_manager.cc b/chromium/components/autofill/core/browser/autofill_manager.cc index 1513e981190..4e8321b85e3 100644 --- a/chromium/components/autofill/core/browser/autofill_manager.cc +++ b/chromium/components/autofill/core/browser/autofill_manager.cc @@ -248,6 +248,12 @@ bool IsAddressForm(FieldTypeGroup field_type_group) { void LogAutofillTypePredictionsAvailable( LogManager* log_manager, const std::vector<FormStructure*>& forms) { + if (VLOG_IS_ON(1)) { + VLOG(1) << "Parsed forms:"; + for (FormStructure* form : forms) + VLOG(1) << *form; + } + if (!log_manager || !log_manager->IsLoggingActive()) return; @@ -425,7 +431,24 @@ int TypeValueFormFillingLimit(ServerFieldType field_type) { } // namespace -AutofillManager::FillingContext::FillingContext() = default; +AutofillManager::FillingContext::FillingContext( + const AutofillField& field, + const AutofillProfile* optional_profile, + const CreditCard* optional_credit_card, + const base::string16* optional_cvc) + : profile(optional_profile ? base::make_optional(*optional_profile) + : base::nullopt), + credit_card(optional_credit_card + ? base::make_optional(std::make_pair( + *optional_credit_card, + optional_cvc ? *optional_cvc : base::string16())) + : base::nullopt), + filled_field_name(field.unique_name()), + original_fill_time(AutofillTickClock::NowTicks()) { + DCHECK(optional_profile || optional_credit_card); + DCHECK(optional_credit_card || !optional_cvc); + DCHECK(profile || credit_card); +} AutofillManager::FillingContext::~FillingContext() = default; @@ -892,6 +915,7 @@ void AutofillManager::OnQueryFormFieldAutofillImpl( const FormFieldData& field, const gfx::RectF& transformed_box, bool autoselect_first_suggestion) { + SetDataList(field.datalist_values, field.datalist_labels); external_delegate_->OnQuery(query_id, form, field, transformed_box); std::vector<Suggestion> suggestions; @@ -1022,9 +1046,9 @@ void AutofillManager::FillOrPreviewCreditCardForm( credit_card_, *form_structure, *autofill_field, sync_state_); } - FillOrPreviewDataModelForm( - action, query_id, form, field, credit_card_, /*is_credit_card=*/true, - /*cvc=*/base::string16(), form_structure, autofill_field); + FillOrPreviewDataModelForm(action, query_id, form, field, /*profile=*/nullptr, + &credit_card_, /*cvc=*/nullptr, form_structure, + autofill_field); } void AutofillManager::FillOrPreviewProfileForm( @@ -1040,22 +1064,11 @@ void AutofillManager::FillOrPreviewProfileForm( if (action == AutofillDriver::FORM_DATA_ACTION_FILL) { address_form_event_logger_->OnDidFillSuggestion( profile, *form_structure, *autofill_field, sync_state_); - - // Set up the information needed for an eventual refill of this form. - if (!form_structure->GetIdentifierForRefill().empty()) { - auto& entry = - filling_contexts_map_[form_structure->GetIdentifierForRefill()]; - auto filling_context = std::make_unique<FillingContext>(); - filling_context->temp_data_model = profile; - filling_context->filled_field_name = autofill_field->unique_name(); - filling_context->original_fill_time = AutofillTickClock::NowTicks(); - entry = std::move(filling_context); - } } - FillOrPreviewDataModelForm( - action, query_id, form, field, profile, /*is_credit_card=*/false, - /*cvc=*/base::string16(), form_structure, autofill_field); + FillOrPreviewDataModelForm(action, query_id, form, field, &profile, + /*credit_card=*/nullptr, + /*cvc=*/nullptr, form_structure, autofill_field); } void AutofillManager::FillOrPreviewForm( @@ -1096,9 +1109,9 @@ void AutofillManager::FillCreditCardForm(int query_id, if (!GetCachedFormAndField(form, field, &form_structure, &autofill_field)) return; - FillOrPreviewDataModelForm( - AutofillDriver::FORM_DATA_ACTION_FILL, query_id, form, field, credit_card, - /*is_credit_card=*/true, cvc, form_structure, autofill_field); + FillOrPreviewDataModelForm(AutofillDriver::FORM_DATA_ACTION_FILL, query_id, + form, field, /*profile=*/nullptr, &credit_card, + &cvc, form_structure, autofill_field); } void AutofillManager::FillProfileForm(const autofill::AutofillProfile& profile, @@ -1167,11 +1180,13 @@ void AutofillManager::OnDidFillAutofillFormData(const FormData& form, UpdatePendingForm(form); - FormStructure* form_structure = nullptr; std::set<FormType> form_types; // Find the FormStructure that corresponds to |form|. Use default form type if // form is not present in our cache, which will happen rarely. - if (FindCachedForm(form, &form_structure)) { + + FormStructure* form_structure = + FindCachedFormByRendererId(form.unique_renderer_id); + if (form_structure) { form_types = form_structure->GetFormTypes(); } @@ -1354,8 +1369,8 @@ void AutofillManager::SetTestDelegate(AutofillManagerTestDelegate* delegate) { test_delegate_ = delegate; } -void AutofillManager::OnSetDataList(const std::vector<base::string16>& values, - const std::vector<base::string16>& labels) { +void AutofillManager::SetDataList(const std::vector<base::string16>& values, + const std::vector<base::string16>& labels) { if (!IsValidString16Vector(values) || !IsValidString16Vector(labels) || values.size() != labels.size()) return; @@ -1364,14 +1379,13 @@ void AutofillManager::OnSetDataList(const std::vector<base::string16>& values, } void AutofillManager::SelectFieldOptionsDidChange(const FormData& form) { - FormStructure* form_structure = nullptr; - // Look for a cached version of the form. It will be a null pointer if none is // found, which is fine. - FormStructure* cached_form = nullptr; - ignore_result(FindCachedForm(form, &cached_form)); + FormStructure* cached_form = + FindCachedFormByRendererId(form.unique_renderer_id); - if (!ParseForm(form, cached_form, &form_structure)) + FormStructure* form_structure = ParseForm(form, cached_form); + if (!form_structure) return; if (ShouldTriggerRefill(*form_structure)) @@ -1380,21 +1394,23 @@ void AutofillManager::SelectFieldOptionsDidChange(const FormData& form) { void AutofillManager::OnLoadedServerPredictions( std::string response, - const std::vector<std::string>& form_signatures) { - // Get the current valid FormStructures represented by |form_signatures|. + const FormAndFieldSignatures& signatures) { + // Get the current valid FormStructures represented by |signatures|. std::vector<FormStructure*> queried_forms; - queried_forms.reserve(form_signatures.size()); - for (const std::string& signature : form_signatures) { - // The |signature| is the direct string representation of the FormSignature. - // Convert it to a uint64_t to do the lookup. - FormSignature form_signature; - FormStructure* form_structure; - if (base::StringToUint64(signature, &form_signature.value()) && - FindCachedForm(form_signature, &form_structure)) { - queried_forms.push_back(form_structure); - } + queried_forms.reserve(signatures.size()); + for (const auto& p : signatures) { + FindCachedFormsBySignature(p.first, &queried_forms); } + // Each form signature in |signatures| is supposed to be unique, and therefore + // appear only once. This ensures that FindCachedFormsBySignature() produces + // an output without duplicates in the forms. + // TODO(crbug/1064709): |queried_forms| could be a set data structure; their + // order should be irrelevant. + DCHECK_EQ(queried_forms.size(), + std::set<FormStructure*>(queried_forms.begin(), queried_forms.end()) + .size()); + // If there are no current forms corresponding to the queried signatures, drop // the query response. if (queried_forms.empty()) @@ -1404,10 +1420,12 @@ void AutofillManager::OnLoadedServerPredictions( if (base::FeatureList::IsEnabled(features::kAutofillUseApi)) { // Parse response from API. FormStructure::ParseApiQueryResponse(std::move(response), queried_forms, + signatures, form_interactions_ukm_logger_.get()); } else { // Parse response from legacy server. FormStructure::ParseQueryResponse(std::move(response), queried_forms, + signatures, form_interactions_ukm_logger_.get()); } @@ -1687,15 +1705,25 @@ void AutofillManager::FillOrPreviewDataModelForm( int query_id, const FormData& form, const FormFieldData& field, - const AutofillDataModel& data_model, - bool is_credit_card, - const base::string16& cvc, + const AutofillProfile* optional_profile, + const CreditCard* optional_credit_card, + const base::string16* optional_cvc, FormStructure* form_structure, AutofillField* autofill_field, bool is_refill) { + DCHECK(optional_profile || optional_credit_card); + DCHECK(optional_credit_card || !optional_cvc); DCHECK(form_structure); DCHECK(autofill_field); + const AutofillDataModel& data_model = [&]() -> const AutofillDataModel& { + if (optional_profile) + return *optional_profile; + else + return *optional_credit_card; + }(); + bool is_credit_card = !!optional_credit_card; + LogBuffer buffer; buffer << "is credit card section: " << is_credit_card << Br{}; buffer << "is refill: " << is_refill << Br{}; @@ -1707,21 +1735,25 @@ void AutofillManager::FillOrPreviewDataModelForm( FormData result = form; DCHECK_EQ(form_structure->field_count(), form.fields.size()); + if (action == AutofillDriver::FORM_DATA_ACTION_FILL && !is_refill) { + filling_contexts_map_[form_structure->GetIdentifierForRefill()] = + std::make_unique<FillingContext>(*autofill_field, optional_profile, + optional_credit_card, optional_cvc); + } + // Only record the types that are filled for an eventual refill if all the // following are satisfied: // The refilling feature is enabled. // A form with the given name is already filled. // A refill has not been attempted for that form yet. // This fill is not a refill attempt. - // This is not a credit card fill. FillingContext* filling_context = nullptr; auto itr = filling_contexts_map_.find(form_structure->GetIdentifierForRefill()); if (itr != filling_contexts_map_.end()) filling_context = itr->second.get(); bool could_attempt_refill = filling_context != nullptr && - !filling_context->attempted_refill && - !is_refill && !is_credit_card; + !filling_context->attempted_refill && !is_refill; // Count the number of times the value of a specific type was filled into the // form. @@ -1796,11 +1828,12 @@ void AutofillManager::FillOrPreviewDataModelForm( continue; } + ServerFieldType field_type = cached_field->Type().GetStorableType(); + // Don't fill expired cards expiration date. - if (data_util::IsCreditCardExpirationType( - cached_field->Type().GetStorableType()) && - static_cast<const CreditCard*>(&data_model) - ->IsExpired(AutofillClock::Now())) { + if (data_util::IsCreditCardExpirationType(field_type) && + (!optional_credit_card || + optional_credit_card->IsExpired(AutofillClock::Now()))) { buffer << Tr{} << field_number << "Skipped: don't fill expiration date of expired cards"; continue; @@ -1808,7 +1841,6 @@ void AutofillManager::FillOrPreviewDataModelForm( // A field with a specific type is only allowed to be filled a limited // number of times given by |TypeValueFormFillingLimit(field_type)|. - const auto field_type = cached_field->Type().GetStorableType(); if (type_filling_count[field_type] >= TypeValueFormFillingLimit(field_type)) { buffer << Tr{} << field_number @@ -1831,12 +1863,14 @@ void AutofillManager::FillOrPreviewDataModelForm( bool has_value_before = !result.fields[i].value.empty(); bool is_autofilled_before = result.fields[i].is_autofilled; + const base::string16 kEmptyCvc{}; std::string failure_to_fill; // Reason for failing to fill. // Fill the non-empty value from |data_model| into the result vector, which // will be sent to the renderer. FillFieldWithValue( - cached_field, data_model, &result.fields[i], should_notify, cvc, + cached_field, data_model, &result.fields[i], should_notify, + optional_cvc ? *optional_cvc : kEmptyCvc, data_util::DetermineGroups(form_structure->GetServerFieldTypes()), &failure_to_fill); @@ -1880,9 +1914,9 @@ std::unique_ptr<FormStructure> AutofillManager::ValidateSubmittedForm( const FormData& form) { // Ignore forms not present in our cache. These are typically forms with // wonky JavaScript that also makes them not auto-fillable. - FormStructure* cached_submitted_form; - if (!FindCachedForm(form, &cached_submitted_form) || - !ShouldUploadForm(*cached_submitted_form)) { + FormStructure* cached_submitted_form = + FindCachedFormByRendererId(form.unique_renderer_id); + if (!cached_submitted_form || !ShouldUploadForm(*cached_submitted_form)) { return nullptr; } @@ -2425,8 +2459,9 @@ bool AutofillManager::ShouldTriggerRefill(const FormStructure& form_structure) { } void AutofillManager::TriggerRefill(const FormData& form) { - FormStructure* form_structure = nullptr; - if (!FindCachedForm(form, &form_structure)) + FormStructure* form_structure = + FindCachedFormByRendererId(form.unique_renderer_id); + if (!form_structure) return; DCHECK(form_structure); @@ -2460,12 +2495,23 @@ void AutofillManager::TriggerRefill(const FormData& form) { return; FormFieldData field = *autofill_field; - base::string16 cvc; - FillOrPreviewDataModelForm( - AutofillDriver::RendererFormDataAction::FORM_DATA_ACTION_FILL, - /*query_id=*/-1, form, field, filling_context->temp_data_model, - /*is_credit_card=*/false, cvc, form_structure, autofill_field, - /*is_refill=*/true); + if (filling_context->credit_card) { + FillOrPreviewDataModelForm( + AutofillDriver::RendererFormDataAction::FORM_DATA_ACTION_FILL, + /*query_id=*/-1, form, field, + /*profile=*/nullptr, &filling_context->credit_card.value().first, + &filling_context->credit_card.value().second, form_structure, + autofill_field, + /*is_refill=*/true); + } + if (filling_context->profile) { + FillOrPreviewDataModelForm( + AutofillDriver::RendererFormDataAction::FORM_DATA_ACTION_FILL, + /*query_id=*/-1, form, field, &filling_context->profile.value(), + /*credic_card=*/nullptr, /*cvc=*/nullptr, form_structure, + autofill_field, + /*is_refill=*/true); + } } void AutofillManager::GetAvailableSuggestions( diff --git a/chromium/components/autofill/core/browser/autofill_manager.h b/chromium/components/autofill/core/browser/autofill_manager.h index ec2e22519c7..71fb7e85857 100644 --- a/chromium/components/autofill/core/browser/autofill_manager.h +++ b/chromium/components/autofill/core/browser/autofill_manager.h @@ -17,6 +17,7 @@ #include "base/gtest_prod_util.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" +#include "base/optional.h" #include "base/strings/string16.h" #include "base/time/time.h" #include "base/timer/timer.h" @@ -228,8 +229,6 @@ class AutofillManager : public AutofillHandler, void OnDidPreviewAutofillFormData() override; void OnDidEndTextFieldEditing() override; void OnHidePopup() override; - void OnSetDataList(const std::vector<base::string16>& values, - const std::vector<base::string16>& labels) override; void SelectFieldOptionsDidChange(const FormData& form) override; void Reset() override; @@ -280,8 +279,8 @@ class AutofillManager : public AutofillHandler, // purposes only. void OnLoadedServerPredictionsForTest( std::string response, - const std::vector<std::string>& form_signatures) { - OnLoadedServerPredictions(response, form_signatures); + const FormAndFieldSignatures& signatures) { + OnLoadedServerPredictions(response, signatures); } // A public wrapper that calls |MakeFrontendID| for testing purposes only. @@ -386,17 +385,25 @@ class AutofillManager : public AutofillHandler, private: // Keeps track of the filling context for a form, used to make refill attemps. struct FillingContext { - FillingContext(); + // |optional_profile| or |optional_credit_card| must be non-null. + // If |optional_credit_card| is non-null, |optional_cvc| may be non-null. + FillingContext(const AutofillField& field, + const AutofillProfile* optional_profile, + const CreditCard* optional_credit_card, + const base::string16* optional_cvc); ~FillingContext(); // Whether a refill attempt was made. bool attempted_refill = false; - // The profile that was used for the initial fill. - AutofillProfile temp_data_model; + // The profile or credit card that was used for the initial fill. + // The std::string associated with the credit card is the CVC, which may be + // empty. + const base::Optional<AutofillProfile> profile; + const base::Optional<std::pair<CreditCard, base::string16>> credit_card; // The name of the field that was initially filled. - base::string16 filled_field_name; - // The time at which the initial fill occured. - base::TimeTicks original_fill_time; + const base::string16 filled_field_name; + // The time at which the initial fill occurred. + const base::TimeTicks original_fill_time; // The timer used to trigger a refill. base::OneShotTimer on_refill_timer; // The field type groups that were initially filled. @@ -430,7 +437,7 @@ class AutofillManager : public AutofillHandler, // AutofillDownloadManager::Observer: void OnLoadedServerPredictions( std::string response, - const std::vector<std::string>& form_signatures) override; + const FormAndFieldSignatures& signatures) override; // CreditCardAccessManager::Accessor void OnCreditCardFetched( @@ -477,9 +484,9 @@ class AutofillManager : public AutofillHandler, int query_id, const FormData& form, const FormFieldData& field, - const AutofillDataModel& data_model, - bool is_credit_card, - const base::string16& cvc, + const AutofillProfile* optional_profile, + const CreditCard* optional_credit_card, + const base::string16* optional_cvc, FormStructure* form_structure, AutofillField* autofill_field, bool is_refill = false); @@ -596,6 +603,8 @@ class AutofillManager : public AutofillHandler, FormEventLoggerBase* GetEventFormLogger( FieldTypeGroup field_type_group) const; + void SetDataList(const std::vector<base::string16>& values, + const std::vector<base::string16>& labels); AutofillClient* const client_; LogManager* log_manager_; diff --git a/chromium/components/autofill/core/browser/autofill_manager_unittest.cc b/chromium/components/autofill/core/browser/autofill_manager_unittest.cc index ec5ccdf8e0e..79f827e5c8e 100644 --- a/chromium/components/autofill/core/browser/autofill_manager_unittest.cc +++ b/chromium/components/autofill/core/browser/autofill_manager_unittest.cc @@ -96,7 +96,6 @@ using features::kAutofillEnforceMinRequiredFieldsForHeuristics; using features::kAutofillEnforceMinRequiredFieldsForQuery; using features::kAutofillEnforceMinRequiredFieldsForUpload; using features::kAutofillRestrictUnownedFieldsToFormlessCheckout; -using mojom::ButtonTitleType; using mojom::SubmissionIndicatorEvent; using mojom::SubmissionSource; @@ -832,6 +831,7 @@ TEST_F(AutofillManagerTest, OnFormsSeen_DifferentFormStructures) { // Different form structure. FormData form2; + form2.unique_renderer_id.value() = 2; form2.name = ASCIIToUTF16("MyForm"); form2.url = GURL("https://myform.com/form.html"); form2.action = GURL("https://myform.com/submit.html"); @@ -862,6 +862,7 @@ TEST_F(AutofillManagerTest, OnFormsSeen_SendAutofillTypePredictionsToRenderer) { FormData form2; FormFieldData field; test::CreateTestFormField("Querty", "qwerty", "", "text", &field); + form2.unique_renderer_id.value() = 2; form2.name = ASCIIToUTF16("NonQueryable"); form2.url = form1.url; form2.action = GURL("https://myform.com/submit.html"); @@ -4716,6 +4717,9 @@ TEST_F(AutofillManagerTest, AutocompleteSuggestions_SomeWhenAutofillDisabled) { autocomplete_history_manager_.get())); autofill_manager_->SetAutofillProfileEnabled(false); autofill_manager_->SetAutofillCreditCardEnabled(false); + external_delegate_ = std::make_unique<TestAutofillExternalDelegate>( + autofill_manager_.get(), autofill_driver_.get(), + /*call_parent_methods=*/false); autofill_manager_->SetExternalDelegate(external_delegate_.get()); // Set up our form data. @@ -4742,6 +4746,9 @@ TEST_F(AutofillManagerTest, autocomplete_history_manager_.get())); autofill_manager_->SetAutofillProfileEnabled(false); autofill_manager_->SetAutofillCreditCardEnabled(false); + external_delegate_ = std::make_unique<TestAutofillExternalDelegate>( + autofill_manager_.get(), autofill_driver_.get(), + /*call_parent_methods=*/false); autofill_manager_->SetExternalDelegate(external_delegate_.get()); // Set up our form data. @@ -4812,6 +4819,9 @@ TEST_F(AutofillManagerTest, autocomplete_history_manager_.get())); autofill_manager_->SetAutofillProfileEnabled(false); autofill_manager_->SetAutofillCreditCardEnabled(false); + external_delegate_ = std::make_unique<TestAutofillExternalDelegate>( + autofill_manager_.get(), autofill_driver_.get(), + /*call_parent_methods=*/false); autofill_manager_->SetExternalDelegate(external_delegate_.get()); // Set up our form data. @@ -4840,6 +4850,9 @@ TEST_F(AutofillManagerTest, autocomplete_history_manager_.get())); autofill_manager_->SetAutofillProfileEnabled(false); autofill_manager_->SetAutofillCreditCardEnabled(false); + external_delegate_ = std::make_unique<TestAutofillExternalDelegate>( + autofill_manager_.get(), autofill_driver_.get(), + /*call_parent_methods=*/false); autofill_manager_->SetExternalDelegate(external_delegate_.get()); // Set up our form data. @@ -4890,6 +4903,9 @@ TEST_F(AutofillManagerTest, AutocompleteOffRespectedForAutocomplete) { autocomplete_history_manager_.get())); autofill_manager_->SetAutofillProfileEnabled(false); autofill_manager_->SetAutofillCreditCardEnabled(false); + external_delegate_ = std::make_unique<TestAutofillExternalDelegate>( + autofill_manager_.get(), autofill_driver_.get(), + /*call_parent_methods=*/false); autofill_manager_->SetExternalDelegate(external_delegate_.get()); EXPECT_CALL(*(autocomplete_history_manager_.get()), @@ -4947,6 +4963,7 @@ TEST_F(AutofillManagerTest, OnLoadedServerPredictionsFromLegacyServer) { // Similarly, a second form. FormData form2; + form2.unique_renderer_id.value() = 2; form2.name = ASCIIToUTF16("MyForm"); form2.url = GURL("http://myform.com/form.html"); form2.action = GURL("http://myform.com/submit.html"); @@ -4981,9 +4998,8 @@ TEST_F(AutofillManagerTest, OnLoadedServerPredictionsFromLegacyServer) { std::string response_string; ASSERT_TRUE(response.SerializeToString(&response_string)); - std::vector<std::string> signatures; - signatures.push_back(form_structure->FormSignatureAsStr()); - signatures.push_back(form_structure2->FormSignatureAsStr()); + FormAndFieldSignatures signatures = + test::GetEncodedSignatures({form_structure, form_structure2}); base::HistogramTester histogram_tester; autofill_manager_->OnLoadedServerPredictionsForTest(response_string, @@ -5022,6 +5038,7 @@ TEST_F(AutofillManagerTest, OnLoadedServerPredictionsFromApi) { // First form on the page. FormData form; + form.unique_renderer_id.value() = 1; form.name = ASCIIToUTF16("MyForm"); form.url = GURL("http://myform.com/form.html"); form.action = GURL("http://myform.com/submit.html"); @@ -5045,6 +5062,7 @@ TEST_F(AutofillManagerTest, OnLoadedServerPredictionsFromApi) { // Second form on the page. FormData form2; + form2.unique_renderer_id.value() = 2; form2.name = ASCIIToUTF16("MyForm2"); form2.url = GURL("http://myform.com/form.html"); form2.action = GURL("http://myform.com/submit.html"); @@ -5084,8 +5102,8 @@ TEST_F(AutofillManagerTest, OnLoadedServerPredictionsFromApi) { std::string encoded_response_string; base::Base64Encode(response_string, &encoded_response_string); - std::vector<std::string> signatures = {form_structure->FormSignatureAsStr(), - form_structure2->FormSignatureAsStr()}; + FormAndFieldSignatures signatures = + test::GetEncodedSignatures({form_structure, form_structure2}); // Run method under test. base::HistogramTester histogram_tester; @@ -5127,6 +5145,8 @@ TEST_F(AutofillManagerTest, OnLoadedServerPredictions_ResetManager) { // |form_structure| will be owned by |autofill_manager_|. TestFormStructure* form_structure = new TestFormStructure(form); form_structure->DetermineHeuristicTypes(); + FormAndFieldSignatures signatures = + test::GetEncodedSignatures(*form_structure); autofill_manager_->AddSeenFormStructure( std::unique_ptr<TestFormStructure>(form_structure)); @@ -5146,9 +5166,6 @@ TEST_F(AutofillManagerTest, OnLoadedServerPredictions_ResetManager) { std::string response_string_base64; base::Base64Encode(response_string, &response_string_base64); - std::vector<std::string> signatures; - signatures.push_back(form_structure->FormSignatureAsStr()); - // Reset the manager (such as during a navigation). autofill_manager_->Reset(); @@ -5209,12 +5226,9 @@ TEST_F(AutofillManagerTest, DetermineHeuristicsWithOverallPrediction) { std::string response_string_base64; base::Base64Encode(response_string, &response_string_base64); - std::vector<std::string> signatures; - signatures.push_back(form_structure->FormSignatureAsStr()); - base::HistogramTester histogram_tester; - autofill_manager_->OnLoadedServerPredictionsForTest(response_string_base64, - signatures); + autofill_manager_->OnLoadedServerPredictionsForTest( + response_string_base64, test::GetEncodedSignatures(*form_structure)); // Verify that FormStructure::ParseQueryResponse was called (here and below). histogram_tester.ExpectBucketCount("Autofill.ServerQueryResponse", AutofillMetrics::QUERY_RESPONSE_RECEIVED, @@ -7641,8 +7655,9 @@ TEST_F(AutofillManagerTest, DidShowSuggestions_LogByType_AddressOnly) { // Create a form with name and address fields. FormData form; form.name = ASCIIToUTF16("MyForm"); - form.button_titles = {std::make_pair( - ASCIIToUTF16("Submit"), ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; + form.button_titles = { + std::make_pair(ASCIIToUTF16("Submit"), + mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; form.url = GURL("http://myform.com/form.html"); form.action = GURL("http://myform.com/submit.html"); form.main_frame_origin = @@ -7684,8 +7699,9 @@ TEST_F(AutofillManagerTest, // Create a form with address fields. FormData form; form.name = ASCIIToUTF16("MyForm"); - form.button_titles = {std::make_pair( - ASCIIToUTF16("Submit"), ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; + form.button_titles = { + std::make_pair(ASCIIToUTF16("Submit"), + mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; form.url = GURL("http://myform.com/form.html"); form.action = GURL("http://myform.com/submit.html"); form.main_frame_origin = @@ -7726,8 +7742,9 @@ TEST_F(AutofillManagerTest, DidShowSuggestions_LogByType_ContactOnly) { // Create a form with name and contact fields. FormData form; form.name = ASCIIToUTF16("MyForm"); - form.button_titles = {std::make_pair( - ASCIIToUTF16("Submit"), ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; + form.button_titles = { + std::make_pair(ASCIIToUTF16("Submit"), + mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; form.url = GURL("http://myform.com/form.html"); form.action = GURL("http://myform.com/submit.html"); form.main_frame_origin = @@ -7768,8 +7785,9 @@ TEST_F(AutofillManagerTest, // Create a form with contact fields. FormData form; form.name = ASCIIToUTF16("MyForm"); - form.button_titles = {std::make_pair( - ASCIIToUTF16("Submit"), ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; + form.button_titles = { + std::make_pair(ASCIIToUTF16("Submit"), + mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; form.url = GURL("http://myform.com/form.html"); form.action = GURL("http://myform.com/submit.html"); form.main_frame_origin = @@ -7809,8 +7827,9 @@ TEST_F(AutofillManagerTest, DidShowSuggestions_LogByType_Other) { // Create a form with name fields. FormData form; form.name = ASCIIToUTF16("MyForm"); - form.button_titles = {std::make_pair( - ASCIIToUTF16("Submit"), ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; + form.button_titles = { + std::make_pair(ASCIIToUTF16("Submit"), + mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; form.url = GURL("http://myform.com/form.html"); form.action = GURL("http://myform.com/submit.html"); form.main_frame_origin = @@ -7850,8 +7869,9 @@ TEST_F(AutofillManagerTest, DidShowSuggestions_LogByType_AddressPlusEmail) { // Create a form with name, address, and email fields. FormData form; form.name = ASCIIToUTF16("MyForm"); - form.button_titles = {std::make_pair( - ASCIIToUTF16("Submit"), ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; + form.button_titles = { + std::make_pair(ASCIIToUTF16("Submit"), + mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; form.url = GURL("http://myform.com/form.html"); form.action = GURL("http://myform.com/submit.html"); form.main_frame_origin = @@ -7901,8 +7921,9 @@ TEST_F(AutofillManagerTest, // Create a form with address and email fields. FormData form; form.name = ASCIIToUTF16("MyForm"); - form.button_titles = {std::make_pair( - ASCIIToUTF16("Submit"), ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; + form.button_titles = { + std::make_pair(ASCIIToUTF16("Submit"), + mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; form.url = GURL("http://myform.com/form.html"); form.action = GURL("http://myform.com/submit.html"); form.main_frame_origin = @@ -7949,8 +7970,9 @@ TEST_F(AutofillManagerTest, DidShowSuggestions_LogByType_AddressPlusPhone) { // Create a form with name fields. FormData form; form.name = ASCIIToUTF16("MyForm"); - form.button_titles = {std::make_pair( - ASCIIToUTF16("Submit"), ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; + form.button_titles = { + std::make_pair(ASCIIToUTF16("Submit"), + mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; form.url = GURL("http://myform.com/form.html"); form.action = GURL("http://myform.com/submit.html"); form.main_frame_origin = @@ -8000,8 +8022,9 @@ TEST_F(AutofillManagerTest, // Create a form with name, address, and phone fields. FormData form; form.name = ASCIIToUTF16("MyForm"); - form.button_titles = {std::make_pair( - ASCIIToUTF16("Submit"), ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; + form.button_titles = { + std::make_pair(ASCIIToUTF16("Submit"), + mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; form.url = GURL("http://myform.com/form.html"); form.action = GURL("http://myform.com/submit.html"); form.main_frame_origin = @@ -8049,8 +8072,9 @@ TEST_F(AutofillManagerTest, // Create a form with name, address, phone, and email fields. FormData form; form.name = ASCIIToUTF16("MyForm"); - form.button_titles = {std::make_pair( - ASCIIToUTF16("Submit"), ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; + form.button_titles = { + std::make_pair(ASCIIToUTF16("Submit"), + mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; form.url = GURL("http://myform.com/form.html"); form.action = GURL("http://myform.com/submit.html"); form.main_frame_origin = @@ -8101,8 +8125,9 @@ TEST_F(AutofillManagerTest, // Create a form with address, phone, and email fields. FormData form; form.name = ASCIIToUTF16("MyForm"); - form.button_titles = {std::make_pair( - ASCIIToUTF16("Submit"), ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; + form.button_titles = { + std::make_pair(ASCIIToUTF16("Submit"), + mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; form.url = GURL("http://myform.com/form.html"); form.action = GURL("http://myform.com/submit.html"); form.main_frame_origin = diff --git a/chromium/components/autofill/core/browser/autofill_metrics.cc b/chromium/components/autofill/core/browser/autofill_metrics.cc index ee304fed0c1..9cdccda5142 100644 --- a/chromium/components/autofill/core/browser/autofill_metrics.cc +++ b/chromium/components/autofill/core/browser/autofill_metrics.cc @@ -20,6 +20,7 @@ #include "components/autofill/core/browser/autofill_type.h" #include "components/autofill/core/browser/data_model/credit_card.h" #include "components/autofill/core/browser/form_structure.h" +#include "components/autofill/core/browser/form_types.h" #include "components/autofill/core/common/autofill_clock.h" #include "components/autofill/core/common/autofill_prefs.h" #include "components/autofill/core/common/autofill_tick_clock.h" @@ -110,8 +111,9 @@ std::string PreviousSaveCreditCardPromptUserDecisionToString( // accessed from the unit test file. It is not exposed in the header file, // however, because it is not intended for consumption outside of the metrics // implementation. -int GetFieldTypeGroupMetric(ServerFieldType field_type, - AutofillMetrics::FieldTypeQualityMetric metric) { +int GetFieldTypeGroupPredictionQualityMetric( + ServerFieldType field_type, + AutofillMetrics::FieldTypeQualityMetric metric) { DCHECK_LT(metric, AutofillMetrics::NUM_FIELD_TYPE_QUALITY_METRICS); FieldTypeGroupForMetrics group = GROUP_AMBIGUOUS; @@ -227,6 +229,26 @@ int GetFieldTypeGroupMetric(ServerFieldType field_type, return (group << 8) | metric; } +// This function encodes the integer value of a |ServerFieldType| and the +// metric value of an |AutofilledFieldUserEdtingStatus| into a 16 bit integer. +// The lower four bits are used to encode the editing status and the higher +// 12 bits are used to encode the field type. +int GetFieldTypeUserEditStatusMetric( + ServerFieldType server_type, + AutofillMetrics::AutofilledFieldUserEditingStatusMetric metric) { + static_assert(ServerFieldType::MAX_VALID_FIELD_TYPE <= (UINT16_MAX >> 4), + "Autofill::ServerTypes value needs more than 12 bits."); + + static_assert( + static_cast<int>( + AutofillMetrics::AutofilledFieldUserEditingStatusMetric::kMaxValue) <= + (UINT16_MAX >> 12), + "AutofillMetrics::AutofilledFieldUserEditingStatusMetric value needs " + "more than 4 bits"); + + return (server_type << 4) | static_cast<int>(metric); +} + namespace { const char* GetQualityMetricPredictionSource( @@ -355,9 +377,9 @@ void LogPredictionQualityMetricsForFieldsOnlyFilledWhenFocused( DVLOG(2) << "TRUE POSITIVE"; base::UmaHistogramSparse(aggregate_histogram, AutofillMetrics::TRUE_POSITIVE); - base::UmaHistogramSparse( - type_specific_histogram, - GetFieldTypeGroupMetric(actual_type, AutofillMetrics::TRUE_POSITIVE)); + base::UmaHistogramSparse(type_specific_histogram, + GetFieldTypeGroupPredictionQualityMetric( + actual_type, AutofillMetrics::TRUE_POSITIVE)); if (log_rationalization_metrics) { bool duplicated_filling = DuplicatedFilling(form, field); base::UmaHistogramSparse( @@ -378,8 +400,8 @@ void LogPredictionQualityMetricsForFieldsOnlyFilledWhenFocused( if (predicted_type != UNKNOWN_TYPE) base::UmaHistogramSparse( type_specific_histogram, - GetFieldTypeGroupMetric(predicted_type, - AutofillMetrics::FALSE_NEGATIVE_MISMATCH)); + GetFieldTypeGroupPredictionQualityMetric( + predicted_type, AutofillMetrics::FALSE_NEGATIVE_MISMATCH)); if (log_rationalization_metrics) { // Logging RATIONALIZATION_OK despite of type mismatch here because autofill // would have got it wrong with or without rationalization. Rationalization @@ -418,9 +440,9 @@ void LogPredictionQualityMetricsForCommonFields( // predict that type with which the field was filled. base::UmaHistogramSparse(aggregate_histogram, AutofillMetrics::TRUE_POSITIVE); - base::UmaHistogramSparse( - type_specific_histogram, - GetFieldTypeGroupMetric(actual_type, AutofillMetrics::TRUE_POSITIVE)); + base::UmaHistogramSparse(type_specific_histogram, + GetFieldTypeGroupPredictionQualityMetric( + actual_type, AutofillMetrics::TRUE_POSITIVE)); return; } @@ -435,8 +457,9 @@ void LogPredictionQualityMetricsForCommonFields( : (is_ambiguous ? AutofillMetrics::FALSE_POSITIVE_AMBIGUOUS : AutofillMetrics::FALSE_POSITIVE_UNKNOWN)); base::UmaHistogramSparse(aggregate_histogram, metric); - base::UmaHistogramSparse(type_specific_histogram, - GetFieldTypeGroupMetric(predicted_type, metric)); + base::UmaHistogramSparse( + type_specific_histogram, + GetFieldTypeGroupPredictionQualityMetric(predicted_type, metric)); return; } @@ -449,8 +472,8 @@ void LogPredictionQualityMetricsForCommonFields( AutofillMetrics::FALSE_NEGATIVE_UNKNOWN); base::UmaHistogramSparse( type_specific_histogram, - GetFieldTypeGroupMetric(actual_type, - AutofillMetrics::FALSE_NEGATIVE_UNKNOWN)); + GetFieldTypeGroupPredictionQualityMetric( + actual_type, AutofillMetrics::FALSE_NEGATIVE_UNKNOWN)); return; } @@ -465,12 +488,12 @@ void LogPredictionQualityMetricsForCommonFields( AutofillMetrics::FALSE_NEGATIVE_MISMATCH); base::UmaHistogramSparse( type_specific_histogram, - GetFieldTypeGroupMetric(actual_type, - AutofillMetrics::FALSE_NEGATIVE_MISMATCH)); + GetFieldTypeGroupPredictionQualityMetric( + actual_type, AutofillMetrics::FALSE_NEGATIVE_MISMATCH)); base::UmaHistogramSparse( type_specific_histogram, - GetFieldTypeGroupMetric(predicted_type, - AutofillMetrics::FALSE_POSITIVE_MISMATCH)); + GetFieldTypeGroupPredictionQualityMetric( + predicted_type, AutofillMetrics::FALSE_POSITIVE_MISMATCH)); } // Logs field type prediction quality metrics. The primary histogram name is @@ -690,6 +713,121 @@ void AutofillMetrics::LogSaveCardRequestExpirationDateReasonMetric( } // static +void AutofillMetrics::LogSaveCardPromptOfferMetric( + SaveCardPromptOfferMetric metric, + bool is_uploading, + bool is_reshow, + AutofillClient::SaveCreditCardOptions options, + int previous_save_credit_card_prompt_user_decision, + security_state::SecurityLevel security_level, + AutofillSyncSigninState sync_state) { + DCHECK_LT(metric, NUM_SAVE_CARD_PROMPT_OFFER_METRICS); + std::string base_histogram_name = "Autofill.SaveCreditCardPromptOffer"; + std::string destination = is_uploading ? ".Upload" : ".Local"; + std::string show = is_reshow ? ".Reshows" : ".FirstShow"; + std::string metric_with_destination_and_show = + base_histogram_name + destination + show; + base::UmaHistogramEnumeration(metric_with_destination_and_show, metric, + NUM_SAVE_CARD_PROMPT_OFFER_METRICS); + + base::UmaHistogramEnumeration( + metric_with_destination_and_show + GetMetricsSyncStateSuffix(sync_state), + metric, NUM_SAVE_CARD_PROMPT_OFFER_METRICS); + + if (options.should_request_name_from_user) { + base::UmaHistogramEnumeration( + metric_with_destination_and_show + ".RequestingCardholderName", metric, + NUM_SAVE_CARD_PROMPT_OFFER_METRICS); + } + if (options.should_request_expiration_date_from_user) { + base::UmaHistogramEnumeration( + metric_with_destination_and_show + ".RequestingExpirationDate", metric, + NUM_SAVE_CARD_PROMPT_OFFER_METRICS); + } + if (options.has_non_focusable_field) { + base::UmaHistogramEnumeration( + metric_with_destination_and_show + ".FromNonFocusableForm", metric, + NUM_SAVE_CARD_PROMPT_OFFER_METRICS); + } + if (options.from_dynamic_change_form) { + base::UmaHistogramEnumeration( + metric_with_destination_and_show + ".FromDynamicChangeForm", metric, + NUM_SAVE_CARD_PROMPT_OFFER_METRICS); + } + + base::UmaHistogramEnumeration( + metric_with_destination_and_show + + PreviousSaveCreditCardPromptUserDecisionToString( + previous_save_credit_card_prompt_user_decision), + metric, NUM_SAVE_CARD_PROMPT_OFFER_METRICS); + + if (security_level != security_state::SecurityLevel::SECURITY_LEVEL_COUNT) { + base::UmaHistogramEnumeration( + security_state::GetSecurityLevelHistogramName( + base_histogram_name + destination, security_level), + metric, NUM_SAVE_CARD_PROMPT_OFFER_METRICS); + } +} + +// static +void AutofillMetrics::LogSaveCardPromptResultMetric( + SaveCardPromptResultMetric metric, + bool is_uploading, + bool is_reshow, + AutofillClient::SaveCreditCardOptions options, + int previous_save_credit_card_prompt_user_decision, + security_state::SecurityLevel security_level, + AutofillSyncSigninState sync_state) { + DCHECK_LT(metric, NUM_SAVE_CARD_PROMPT_RESULT_METRICS); + std::string base_histogram_name = "Autofill.SaveCreditCardPromptResult"; + std::string destination = is_uploading ? ".Upload" : ".Local"; + std::string show = is_reshow ? ".Reshows" : ".FirstShow"; + std::string metric_with_destination_and_show = + base_histogram_name + destination + show; + + base::UmaHistogramEnumeration(metric_with_destination_and_show, metric, + NUM_SAVE_CARD_PROMPT_RESULT_METRICS); + + base::UmaHistogramEnumeration( + metric_with_destination_and_show + GetMetricsSyncStateSuffix(sync_state), + metric, NUM_SAVE_CARD_PROMPT_RESULT_METRICS); + + if (options.should_request_name_from_user) { + base::UmaHistogramEnumeration( + metric_with_destination_and_show + ".RequestingCardholderName", metric, + NUM_SAVE_CARD_PROMPT_RESULT_METRICS); + } + if (options.should_request_expiration_date_from_user) { + base::UmaHistogramEnumeration( + metric_with_destination_and_show + ".RequestingExpirationDate", metric, + NUM_SAVE_CARD_PROMPT_RESULT_METRICS); + } + if (options.has_non_focusable_field) { + base::UmaHistogramEnumeration( + metric_with_destination_and_show + ".FromNonFocusableForm", metric, + NUM_SAVE_CARD_PROMPT_RESULT_METRICS); + } + if (options.from_dynamic_change_form) { + base::UmaHistogramEnumeration( + metric_with_destination_and_show + ".FromDynamicChangeForm", metric, + NUM_SAVE_CARD_PROMPT_RESULT_METRICS); + } + + base::UmaHistogramEnumeration( + metric_with_destination_and_show + + PreviousSaveCreditCardPromptUserDecisionToString( + previous_save_credit_card_prompt_user_decision), + metric, NUM_SAVE_CARD_PROMPT_RESULT_METRICS); + + if (security_level != security_state::SecurityLevel::SECURITY_LEVEL_COUNT) { + base::UmaHistogramEnumeration( + security_state::GetSecurityLevelHistogramName( + base_histogram_name + destination, security_level), + metric, NUM_SAVE_CARD_PROMPT_RESULT_METRICS); + } +} + +// static void AutofillMetrics::LogSaveCardPromptMetric( SaveCardPromptMetric metric, bool is_uploading, @@ -761,6 +899,12 @@ void AutofillMetrics::LogSaveCardPromptMetricBySecurityLevel( } // static +void AutofillMetrics::LogCreditCardUploadLegalMessageLinkClicked() { + base::RecordAction(base::UserMetricsAction( + "Autofill_CreditCardUpload_LegalMessageLinkClicked")); +} + +// static void AutofillMetrics::LogCreditCardUploadFeedbackMetric( CreditCardUploadFeedbackMetric metric) { DCHECK_LT(metric, NUM_CREDIT_CARD_UPLOAD_FEEDBACK_METRICS); @@ -1262,6 +1406,49 @@ void AutofillMetrics::LogOverallPredictionQualityMetrics( true /*log_rationalization_metrics*/); } +void AutofillMetrics::LogEditedAutofilledFieldAtSubmission( + FormInteractionsUkmLogger* form_interactions_ukm_logger, + const FormStructure& form, + const AutofillField& field) { + const std::string aggregate_histogram = + "Autofill.EditedAutofilledFieldAtSubmission.Aggregate"; + const std::string type_specific_histogram = + "Autofill.EditedAutofilledFieldAtSubmission.ByFieldType"; + + AutofilledFieldUserEditingStatusMetric editing_metric = + field.previously_autofilled() + ? AutofilledFieldUserEditingStatusMetric::AUTOFILLED_FIELD_WAS_EDITED + : AutofilledFieldUserEditingStatusMetric:: + AUTOFILLED_FIELD_WAS_NOT_EDITED; + + // Record the aggregated UMA statistics. + base::UmaHistogramEnumeration(aggregate_histogram, editing_metric); + + // Record the type specific UMA statistics. + base::UmaHistogramSparse(type_specific_histogram, + GetFieldTypeUserEditStatusMetric( + field.Type().GetStorableType(), editing_metric)); + + // Record the UMA statistics spliced by the autocomplete attribute value. + FormType form_type = + FormTypes::FieldTypeGroupToFormType(field.Type().group()); + if (form_type == ADDRESS_FORM || form_type == CREDIT_CARD_FORM) { + bool autocomplete_off = field.autocomplete_attribute == "off"; + const std::string autocomplete_histogram = base::StrCat( + {"Autofill.Autocomplete.", autocomplete_off ? "Off" : "NotOff", + ".EditedAutofilledFieldAtSubmission.", + form_type == ADDRESS_FORM ? "Address" : "CreditCard"}); + base::UmaHistogramEnumeration(autocomplete_histogram, editing_metric); + } + + // If the field was edited, record the event to UKM. + if (editing_metric == + AutofilledFieldUserEditingStatusMetric::AUTOFILLED_FIELD_WAS_EDITED) { + form_interactions_ukm_logger->LogEditedAutofilledFieldAtSubmission(form, + field); + } +} + // static void AutofillMetrics::LogServerQueryMetric(ServerQueryMetric metric) { DCHECK_LT(metric, NUM_SERVER_QUERY_METRICS); @@ -1481,7 +1668,9 @@ void AutofillMetrics::LogStoredCreditCardMetrics( const std::vector<std::unique_ptr<CreditCard>>& server_cards, base::TimeDelta disused_data_threshold) { size_t num_local_cards = 0; + size_t num_local_cards_with_nickname = 0; size_t num_masked_cards = 0; + size_t num_masked_cards_with_nickname = 0; size_t num_unmasked_cards = 0; size_t num_disused_local_cards = 0; size_t num_disused_masked_cards = 0; @@ -1513,6 +1702,8 @@ void AutofillMetrics::LogStoredCreditCardMetrics( days_since_last_use); num_local_cards += 1; num_disused_local_cards += disused_delta; + if (card->HasValidNickname()) + num_local_cards_with_nickname += 1; break; case CreditCard::MASKED_SERVER_CARD: UMA_HISTOGRAM_COUNTS_1000( @@ -1523,6 +1714,8 @@ void AutofillMetrics::LogStoredCreditCardMetrics( days_since_last_use); num_masked_cards += 1; num_disused_masked_cards += disused_delta; + if (card->HasValidNickname()) + num_masked_cards_with_nickname += 1; break; case CreditCard::FULL_SERVER_CARD: UMA_HISTOGRAM_COUNTS_1000( @@ -1549,10 +1742,15 @@ void AutofillMetrics::LogStoredCreditCardMetrics( UMA_HISTOGRAM_COUNTS_1000("Autofill.StoredCreditCardCount", num_cards); UMA_HISTOGRAM_COUNTS_1000("Autofill.StoredCreditCardCount.Local", num_local_cards); + UMA_HISTOGRAM_COUNTS_1000("Autofill.StoredCreditCardCount.Local.WithNickname", + num_local_cards_with_nickname); UMA_HISTOGRAM_COUNTS_1000("Autofill.StoredCreditCardCount.Server", num_server_cards); UMA_HISTOGRAM_COUNTS_1000("Autofill.StoredCreditCardCount.Server.Masked", num_masked_cards); + UMA_HISTOGRAM_COUNTS_1000( + "Autofill.StoredCreditCardCount.Server.Masked.WithNickname", + num_masked_cards_with_nickname); UMA_HISTOGRAM_COUNTS_1000("Autofill.StoredCreditCardCount.Server.Unmasked", num_unmasked_cards); @@ -1953,6 +2151,19 @@ void AutofillMetrics::FormInteractionsUkmLogger::LogDidFillSuggestion( .Record(ukm_recorder_); } +void AutofillMetrics::FormInteractionsUkmLogger:: + LogEditedAutofilledFieldAtSubmission(const FormStructure& form, + const AutofillField& field) { + if (!CanLog()) + return; + + ukm::builders::Autofill_EditedAutofilledFieldAtSubmission(source_id_) + .SetFieldSignature(HashFieldSignature(field.GetFieldSignature())) + .SetFormSignature(HashFormSignature(form.form_signature())) + .SetOverallType(static_cast<int64_t>(field.Type().GetStorableType())) + .Record(ukm_recorder_); +} + void AutofillMetrics::FormInteractionsUkmLogger::LogTextFieldDidChange( const FormStructure& form, const AutofillField& field) { @@ -2211,4 +2422,10 @@ void AutofillMetrics:: "Autofill.AddressProfileImportCountrySpecificFieldRequirements", metric); } +// static +void AutofillMetrics::LogAddressFormImportStatustMetric( + AutofillMetrics::AddressProfileImportStatusMetric metric) { + base::UmaHistogramEnumeration("Autofill.AddressProfileImportStatus", metric); +} + } // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_metrics.h b/chromium/components/autofill/core/browser/autofill_metrics.h index 124cac7c810..68dcd8aebd3 100644 --- a/chromium/components/autofill/core/browser/autofill_metrics.h +++ b/chromium/components/autofill/core/browser/autofill_metrics.h @@ -135,14 +135,14 @@ class AutofillMetrics { enum InfoBarMetric { INFOBAR_SHOWN = 0, // We showed an infobar, e.g. prompting to save credit - // card info. - INFOBAR_ACCEPTED, // The user explicitly accepted the infobar. - INFOBAR_DENIED, // The user explicitly denied the infobar. - INFOBAR_IGNORED, // The user completely ignored the infobar (logged on - // tab close). + // card info. + INFOBAR_ACCEPTED, // The user explicitly accepted the infobar. + INFOBAR_DENIED, // The user explicitly denied the infobar. + INFOBAR_IGNORED, // The user completely ignored the infobar (logged on + // tab close). INFOBAR_NOT_SHOWN_INVALID_LEGAL_MESSAGE, // We didn't show the infobar - // because the provided legal - // message was invalid. + // because the provided legal + // message was invalid. NUM_INFO_BAR_METRICS, }; @@ -233,6 +233,30 @@ class AutofillMetrics { kMaxValue = kExpirationDatePresentButExpired, }; + // Metrics to track event when the save card prompt is offered. + enum SaveCardPromptOfferMetric { + // The prompt is actually shown. + SAVE_CARD_PROMPT_SHOWN, + // The prompt is not shown because the prompt has been declined by the user + // too many times. + SAVE_CARD_PROMPT_NOT_SHOWN_MAX_STRIKES_REACHED, + NUM_SAVE_CARD_PROMPT_OFFER_METRICS, + }; + + enum SaveCardPromptResultMetric { + // The user explicitly accepted the prompt by clicking the ok button. + SAVE_CARD_PROMPT_ACCEPTED, + // The user explicitly cancelled the prompt by clicking the cancel button. + SAVE_CARD_PROMPT_CANCELLED, + // The user explicitly closed the prompt with the close button or ESC. + SAVE_CARD_PROMPT_CLOSED, + // The user did not interact with the prompt. + SAVE_CARD_PROMPT_NOT_INTERACTED, + // The prompt lost focus and was deactivated. + SAVE_CARD_PROMPT_LOST_FOCUS, + NUM_SAVE_CARD_PROMPT_RESULT_METRICS, + }; + // Metrics to measure user interaction with the save credit card prompt. // // SAVE_CARD_PROMPT_DISMISS_FOCUS is not stored explicitly, but can be @@ -244,7 +268,7 @@ class AutofillMetrics { // location bar icon being clicked while bubble is hidden (reshows). SAVE_CARD_PROMPT_SHOW_REQUESTED, // The prompt was shown successfully. - SAVE_CARD_PROMPT_SHOWN, + SAVE_CARD_PROMPT_SHOWN_DEPRECATED, // The prompt was not shown because the legal message was invalid. SAVE_CARD_PROMPT_END_INVALID_LEGAL_MESSAGE, // The user explicitly accepted the prompt. @@ -966,6 +990,21 @@ class AutofillMetrics { kMaxValue = LINE1_ZIP_STATE_CITY_REQUIREMENT_VIOLATED, }; + // To record if the value in an autofilled field was edited by the user. + enum class AutofilledFieldUserEditingStatusMetric { + AUTOFILLED_FIELD_WAS_EDITED = 0, + AUTOFILLED_FIELD_WAS_NOT_EDITED = 1, + kMaxValue = AUTOFILLED_FIELD_WAS_NOT_EDITED, + }; + + // Represent the overall status of a profile import. + enum class AddressProfileImportStatusMetric { + NO_IMPORT = 0, + REGULAR_IMPORT = 1, + SECTION_UNION_IMPORT = 2, + kMaxValue = SECTION_UNION_IMPORT, + }; + // Utility to log URL keyed form interaction events. class FormInteractionsUkmLogger { public: @@ -999,6 +1038,8 @@ class AutofillMetrics { const AutofillField& field); void LogTextFieldDidChange(const FormStructure& form, const AutofillField& field); + void LogEditedAutofilledFieldAtSubmission(const FormStructure& form, + const AutofillField& field); void LogFieldFillStatus(const FormStructure& form, const AutofillField& field, QualityMetricType metric_type); @@ -1107,6 +1148,22 @@ class AutofillMetrics { static void LogCreditCardFillingInfoBarMetric(InfoBarMetric metric); static void LogSaveCardRequestExpirationDateReasonMetric( SaveCardRequestExpirationDateReasonMetric metric); + static void LogSaveCardPromptOfferMetric( + SaveCardPromptOfferMetric metric, + bool is_uploading, + bool is_reshow, + AutofillClient::SaveCreditCardOptions options, + int previous_save_credit_card_prompt_user_decision, + security_state::SecurityLevel security_level, + AutofillSyncSigninState sync_state); + static void LogSaveCardPromptResultMetric( + SaveCardPromptResultMetric metric, + bool is_uploading, + bool is_reshow, + AutofillClient::SaveCreditCardOptions options, + int previous_save_credit_card_prompt_user_decision, + security_state::SecurityLevel security_level, + AutofillSyncSigninState sync_state); static void LogSaveCardPromptMetric( SaveCardPromptMetric metric, bool is_uploading, @@ -1119,6 +1176,7 @@ class AutofillMetrics { SaveCardPromptMetric metric, bool is_uploading, security_state::SecurityLevel security_level); + static void LogCreditCardUploadLegalMessageLinkClicked(); static void LogCreditCardUploadFeedbackMetric( CreditCardUploadFeedbackMetric metric); static void LogManageCardsPromptMetric(ManageCardsPromptMetric metric, @@ -1534,6 +1592,15 @@ class AutofillMetrics { bool is_city_missing, bool is_line1_missing); + // Records if an autofilled field of a specific type was edited by the user. + static void LogEditedAutofilledFieldAtSubmission( + FormInteractionsUkmLogger* form_interactions_ukm_logger, + const FormStructure& form, + const AutofillField& field); + + static void LogAddressFormImportStatustMetric( + AddressProfileImportStatusMetric metric); + static const char* GetMetricsSyncStateSuffix( AutofillSyncSigninState sync_state); @@ -1545,6 +1612,11 @@ class AutofillMetrics { DISALLOW_IMPLICIT_CONSTRUCTORS(AutofillMetrics); }; -} // namespace autofill +#if defined(UNIT_TEST) +int GetFieldTypeUserEditStatusMetric( + ServerFieldType server_type, + AutofillMetrics::AutofilledFieldUserEditingStatusMetric metric); +#endif +} // namespace autofill #endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_METRICS_H_ diff --git a/chromium/components/autofill/core/browser/autofill_metrics_unittest.cc b/chromium/components/autofill/core/browser/autofill_metrics_unittest.cc index 5fad6416589..f5c0ffec08d 100644 --- a/chromium/components/autofill/core/browser/autofill_metrics_unittest.cc +++ b/chromium/components/autofill/core/browser/autofill_metrics_unittest.cc @@ -51,6 +51,7 @@ #include "components/autofill/core/common/autofill_tick_clock.h" #include "components/autofill/core/common/form_data.h" #include "components/autofill/core/common/form_field_data.h" +#include "components/autofill/core/common/renderer_id.h" #include "components/autofill/core/common/signatures.h" #include "components/prefs/pref_service.h" #include "components/sync/driver/test_sync_service.h" @@ -100,6 +101,8 @@ using UkmFormSubmittedType = ukm::builders::Autofill_FormSubmitted; using UkmFieldTypeValidationType = ukm::builders::Autofill_FieldTypeValidation; using UkmFieldFillStatusType = ukm::builders::Autofill_FieldFillStatus; using UkmFormEventType = ukm::builders::Autofill_FormEvent; +using UkmEditedAutofilledFieldAtSubmission = + ukm::builders::Autofill_EditedAutofilledFieldAtSubmission; using ExpectedUkmMetricsRecord = std::vector<std::pair<const char*, int64_t>>; using ExpectedUkmMetrics = std::vector<ExpectedUkmMetricsRecord>; @@ -117,6 +120,13 @@ FieldSignature Collapse(FieldSignature sig) { return FieldSignature(sig.value() % 1021); } +// Returns numbers which are distinct from each other within the scope of one +// test. +FormRendererId MakeFormRendererId() { + static uint32_t counter = 10; + return FormRendererId(counter++); +} + struct AddressProfileImportRequirementExpectations { AddressImportRequirements requirement; bool fulfilled; @@ -295,8 +305,9 @@ class MockAutofillClient : public TestAutofillClient { } // namespace // This is defined in the autofill_metrics.cc implementation file. -int GetFieldTypeGroupMetric(ServerFieldType field_type, - AutofillMetrics::FieldTypeQualityMetric metric); +int GetFieldTypeGroupPredictionQualityMetric( + ServerFieldType field_type, + AutofillMetrics::FieldTypeQualityMetric metric); class AutofillMetricsTest : public testing::Test { public: @@ -615,6 +626,7 @@ INSTANTIATE_TEST_SUITE_P(AutofillMetricsTest, TEST_F(AutofillMetricsTest, QualityMetrics) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -681,48 +693,50 @@ TEST_F(AutofillMetricsTest, QualityMetrics) { aggregate_histogram, AutofillMetrics::FALSE_NEGATIVE_UNKNOWN, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(ADDRESS_HOME_COUNTRY, - AutofillMetrics::FALSE_NEGATIVE_UNKNOWN), + GetFieldTypeGroupPredictionQualityMetric( + ADDRESS_HOME_COUNTRY, AutofillMetrics::FALSE_NEGATIVE_UNKNOWN), 1); // Match: histogram_tester.ExpectBucketCount(aggregate_histogram, AutofillMetrics::TRUE_POSITIVE, 2); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(NAME_FULL, AutofillMetrics::TRUE_POSITIVE), 1); + GetFieldTypeGroupPredictionQualityMetric( + NAME_FULL, AutofillMetrics::TRUE_POSITIVE), + 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(PHONE_HOME_CITY_AND_NUMBER, - AutofillMetrics::TRUE_POSITIVE), + GetFieldTypeGroupPredictionQualityMetric( + PHONE_HOME_CITY_AND_NUMBER, AutofillMetrics::TRUE_POSITIVE), 1); // Mismatch: histogram_tester.ExpectBucketCount( aggregate_histogram, AutofillMetrics::FALSE_NEGATIVE_MISMATCH, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(EMAIL_ADDRESS, - AutofillMetrics::FALSE_NEGATIVE_MISMATCH), + GetFieldTypeGroupPredictionQualityMetric( + EMAIL_ADDRESS, AutofillMetrics::FALSE_NEGATIVE_MISMATCH), 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(PHONE_HOME_NUMBER, - AutofillMetrics::FALSE_POSITIVE_MISMATCH), + GetFieldTypeGroupPredictionQualityMetric( + PHONE_HOME_NUMBER, AutofillMetrics::FALSE_POSITIVE_MISMATCH), 1); // False Positive Unknown: histogram_tester.ExpectBucketCount( aggregate_histogram, AutofillMetrics::FALSE_POSITIVE_UNKNOWN, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(PHONE_HOME_NUMBER, - AutofillMetrics::FALSE_POSITIVE_UNKNOWN), + GetFieldTypeGroupPredictionQualityMetric( + PHONE_HOME_NUMBER, AutofillMetrics::FALSE_POSITIVE_UNKNOWN), 1); // False Positive Empty: histogram_tester.ExpectBucketCount( aggregate_histogram, AutofillMetrics::FALSE_POSITIVE_EMPTY, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(NAME_FULL, - AutofillMetrics::FALSE_POSITIVE_EMPTY), + GetFieldTypeGroupPredictionQualityMetric( + NAME_FULL, AutofillMetrics::FALSE_POSITIVE_EMPTY), 1); // Sanity Check: @@ -744,33 +758,34 @@ TEST_F(AutofillMetricsTest, QualityMetrics) { aggregate_histogram, AutofillMetrics::FALSE_NEGATIVE_UNKNOWN, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(ADDRESS_HOME_COUNTRY, - AutofillMetrics::FALSE_NEGATIVE_UNKNOWN), + GetFieldTypeGroupPredictionQualityMetric( + ADDRESS_HOME_COUNTRY, AutofillMetrics::FALSE_NEGATIVE_UNKNOWN), 1); // Match: histogram_tester.ExpectBucketCount(aggregate_histogram, AutofillMetrics::TRUE_POSITIVE, 2); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(EMAIL_ADDRESS, AutofillMetrics::TRUE_POSITIVE), + GetFieldTypeGroupPredictionQualityMetric( + EMAIL_ADDRESS, AutofillMetrics::TRUE_POSITIVE), 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(PHONE_HOME_WHOLE_NUMBER, - AutofillMetrics::TRUE_POSITIVE), + GetFieldTypeGroupPredictionQualityMetric( + PHONE_HOME_WHOLE_NUMBER, AutofillMetrics::TRUE_POSITIVE), 1); // Mismatch: histogram_tester.ExpectBucketCount( aggregate_histogram, AutofillMetrics::FALSE_NEGATIVE_MISMATCH, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(NAME_FULL, - AutofillMetrics::FALSE_NEGATIVE_MISMATCH), + GetFieldTypeGroupPredictionQualityMetric( + NAME_FULL, AutofillMetrics::FALSE_NEGATIVE_MISMATCH), 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(NAME_FIRST, - AutofillMetrics::FALSE_POSITIVE_MISMATCH), + GetFieldTypeGroupPredictionQualityMetric( + NAME_FIRST, AutofillMetrics::FALSE_POSITIVE_MISMATCH), 1); // False Positive Unknown: @@ -778,16 +793,16 @@ TEST_F(AutofillMetricsTest, QualityMetrics) { aggregate_histogram, AutofillMetrics::FALSE_POSITIVE_UNKNOWN, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(EMAIL_ADDRESS, - AutofillMetrics::FALSE_POSITIVE_UNKNOWN), + GetFieldTypeGroupPredictionQualityMetric( + EMAIL_ADDRESS, AutofillMetrics::FALSE_POSITIVE_UNKNOWN), 1); // False Positive Empty: histogram_tester.ExpectBucketCount( aggregate_histogram, AutofillMetrics::FALSE_POSITIVE_EMPTY, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(NAME_FIRST, - AutofillMetrics::FALSE_POSITIVE_EMPTY), + GetFieldTypeGroupPredictionQualityMetric( + NAME_FIRST, AutofillMetrics::FALSE_POSITIVE_EMPTY), 1); // Sanity Check: @@ -796,6 +811,253 @@ TEST_F(AutofillMetricsTest, QualityMetrics) { } } +// Test that the ProfileImportStatus logs a no import. +TEST_F(AutofillMetricsTest, ProfileImportStatus_NoImport) { + // Set up our form data. + FormData form; + form.name = ASCIIToUTF16("TestForm"); + form.url = GURL("http://example.com/form.html"); + form.action = GURL("http://example.com/submit.html"); + + std::vector<ServerFieldType> heuristic_types, server_types; + FormFieldData field; + + test::CreateTestFormField("Name", "name", "Elvis Aaron Presley", "text", + &field); + form.fields.push_back(field); + heuristic_types.push_back(NAME_FULL); + server_types.push_back(NAME_FULL); + + test::CreateTestFormField("Address", "home_line_one", + "3734 Elvis Presley Blvd.", "text", &field); + form.fields.push_back(field); + heuristic_types.push_back(ADDRESS_HOME_LINE1); + server_types.push_back(ADDRESS_HOME_LINE1); + + test::CreateTestFormField("City", "city", "New York", "text", &field); + form.fields.push_back(field); + heuristic_types.push_back(ADDRESS_HOME_CITY); + server_types.push_back(ADDRESS_HOME_CITY); + + test::CreateTestFormField("Phone", "phone", "2345678901", "text", &field); + form.fields.push_back(field); + heuristic_types.push_back(PHONE_HOME_CITY_AND_NUMBER); + server_types.push_back(PHONE_HOME_CITY_AND_NUMBER); + + test::CreateTestFormField("State", "state", "InvalidState", "text", &field); + form.fields.push_back(field); + heuristic_types.push_back(ADDRESS_HOME_STATE); + server_types.push_back(ADDRESS_HOME_STATE); + + test::CreateTestFormField("ZIP", "zip", "00000000000000000", "text", &field); + form.fields.push_back(field); + heuristic_types.push_back(ADDRESS_HOME_ZIP); + server_types.push_back(ADDRESS_HOME_ZIP); + + test::CreateTestFormField("Country", "country", "NoACountry", "text", &field); + form.fields.push_back(field); + heuristic_types.push_back(ADDRESS_HOME_COUNTRY); + server_types.push_back(ADDRESS_HOME_COUNTRY); + + // Simulate having seen this form on page load. + autofill_manager_->AddSeenForm(form, heuristic_types, server_types); + std::string guid(kTestGuid); + autofill_manager_->FillOrPreviewForm( + AutofillDriver::FORM_DATA_ACTION_FILL, 0, form, form.fields.front(), + autofill_manager_->MakeFrontendIDForTest(std::string(), guid)); + + // Simulate form submission. + base::HistogramTester histogram_tester; + autofill_manager_->OnFormSubmitted(form, false, + SubmissionSource::FORM_SUBMISSION); + + std::string histogram = "Autofill.AddressProfileImportStatus"; + histogram_tester.ExpectBucketCount( + histogram, + AutofillMetrics::AddressProfileImportStatusMetric::REGULAR_IMPORT, 0); + histogram_tester.ExpectBucketCount( + histogram, AutofillMetrics::AddressProfileImportStatusMetric::NO_IMPORT, + 1); + histogram_tester.ExpectBucketCount( + histogram, + AutofillMetrics::AddressProfileImportStatusMetric::SECTION_UNION_IMPORT, + 0); +} + +// Test that the ProfileImportStatus logs a regular import. +TEST_F(AutofillMetricsTest, ProfileImportStatus_RegularImport) { + // Set up our form data. + FormData form; + form.name = ASCIIToUTF16("TestForm"); + form.url = GURL("http://example.com/form.html"); + form.action = GURL("http://example.com/submit.html"); + + std::vector<ServerFieldType> heuristic_types, server_types; + FormFieldData field; + + test::CreateTestFormField("Name", "name", "Elvis Aaron Presley", "text", + &field); + form.fields.push_back(field); + heuristic_types.push_back(NAME_FULL); + server_types.push_back(NAME_FULL); + + test::CreateTestFormField("Address", "home_line_one", + "3734 Elvis Presley Blvd.", "text", &field); + form.fields.push_back(field); + heuristic_types.push_back(ADDRESS_HOME_LINE1); + server_types.push_back(ADDRESS_HOME_LINE1); + + test::CreateTestFormField("City", "city", "New York", "text", &field); + form.fields.push_back(field); + heuristic_types.push_back(ADDRESS_HOME_CITY); + server_types.push_back(ADDRESS_HOME_CITY); + + test::CreateTestFormField("Phone", "phone", "2345678901", "text", &field); + form.fields.push_back(field); + heuristic_types.push_back(PHONE_HOME_CITY_AND_NUMBER); + server_types.push_back(PHONE_HOME_CITY_AND_NUMBER); + + test::CreateTestFormField("State", "state", "CA", "text", &field); + form.fields.push_back(field); + heuristic_types.push_back(ADDRESS_HOME_STATE); + server_types.push_back(ADDRESS_HOME_STATE); + + test::CreateTestFormField("ZIP", "zip", "37373", "text", &field); + form.fields.push_back(field); + heuristic_types.push_back(ADDRESS_HOME_ZIP); + server_types.push_back(ADDRESS_HOME_ZIP); + + test::CreateTestFormField("Country", "country", "USA", "text", &field); + form.fields.push_back(field); + heuristic_types.push_back(ADDRESS_HOME_COUNTRY); + server_types.push_back(ADDRESS_HOME_COUNTRY); + + // Simulate having seen this form on page load. + autofill_manager_->AddSeenForm(form, heuristic_types, server_types); + std::string guid(kTestGuid); + autofill_manager_->FillOrPreviewForm( + AutofillDriver::FORM_DATA_ACTION_FILL, 0, form, form.fields.front(), + autofill_manager_->MakeFrontendIDForTest(std::string(), guid)); + + // Simulate form submission. + base::HistogramTester histogram_tester; + autofill_manager_->OnFormSubmitted(form, false, + SubmissionSource::FORM_SUBMISSION); + + std::string histogram = "Autofill.AddressProfileImportStatus"; + histogram_tester.ExpectBucketCount( + histogram, + AutofillMetrics::AddressProfileImportStatusMetric::REGULAR_IMPORT, 1); + histogram_tester.ExpectBucketCount( + histogram, AutofillMetrics::AddressProfileImportStatusMetric::NO_IMPORT, + 0); + histogram_tester.ExpectBucketCount( + histogram, + AutofillMetrics::AddressProfileImportStatusMetric::SECTION_UNION_IMPORT, + 0); +} + +// Test that the ProfileImportStatus logs a section union mport. +TEST_F(AutofillMetricsTest, ProfileImportStatus_UnionImport) { + // Set up our form data. + FormData form; + form.name = ASCIIToUTF16("TestForm"); + form.url = GURL("http://example.com/form.html"); + form.action = GURL("http://example.com/submit.html"); + + std::vector<ServerFieldType> heuristic_types, server_types; + FormFieldData field; + + test::CreateTestFormField("Name", "name", "Elvis Aaron Presley", "text", + &field); + form.fields.push_back(field); + heuristic_types.push_back(NAME_FULL); + server_types.push_back(NAME_FULL); + + test::CreateTestFormField("Address", "home_line_one", + "3734 Elvis Presley Blvd.", "text", &field); + form.fields.push_back(field); + heuristic_types.push_back(ADDRESS_HOME_LINE1); + server_types.push_back(ADDRESS_HOME_LINE1); + + test::CreateTestFormField("ZIP", "zip", "37373", "text", &field); + form.fields.push_back(field); + heuristic_types.push_back(ADDRESS_HOME_ZIP); + server_types.push_back(ADDRESS_HOME_ZIP); + + test::CreateTestFormField("Country", "country", "USA", "text", &field); + form.fields.push_back(field); + heuristic_types.push_back(ADDRESS_HOME_COUNTRY); + server_types.push_back(ADDRESS_HOME_COUNTRY); + + test::CreateTestFormField("Phone", "phone", "2345678901", "text", &field); + form.fields.push_back(field); + heuristic_types.push_back(PHONE_HOME_CITY_AND_NUMBER); + server_types.push_back(PHONE_HOME_CITY_AND_NUMBER); + + test::CreateTestFormField("City", "city", "New York", "text", &field); + // Assign a specific section. + field.autocomplete_attribute = "section-billing locality"; + form.fields.push_back(field); + heuristic_types.push_back(ADDRESS_HOME_CITY); + server_types.push_back(ADDRESS_HOME_CITY); + + test::CreateTestFormField("State", "state", "CA", "text", &field); + // Make the state a different section than the city. + field.autocomplete_attribute = "section-shipping address-level1"; + form.fields.push_back(field); + heuristic_types.push_back(ADDRESS_HOME_STATE); + server_types.push_back(ADDRESS_HOME_STATE); + + // Simulate having seen this form on page load. + autofill_manager_->AddSeenForm(form, heuristic_types, server_types); + std::string guid(kTestGuid); + autofill_manager_->FillOrPreviewForm( + AutofillDriver::FORM_DATA_ACTION_FILL, 0, form, form.fields.front(), + autofill_manager_->MakeFrontendIDForTest(std::string(), guid)); + + base::HistogramTester histogram_tester; + std::string histogram = "Autofill.AddressProfileImportStatus"; + + // Disable the union import feature. + scoped_feature_list_.InitAndDisableFeature( + features::kAutofillProfileImportFromUnifiedSection); + + // Simulate form submission. + autofill_manager_->OnFormSubmitted(form, false, + SubmissionSource::FORM_SUBMISSION); + + histogram_tester.ExpectBucketCount( + histogram, + AutofillMetrics::AddressProfileImportStatusMetric::REGULAR_IMPORT, 0); + histogram_tester.ExpectBucketCount( + histogram, AutofillMetrics::AddressProfileImportStatusMetric::NO_IMPORT, + 1); + histogram_tester.ExpectBucketCount( + histogram, + AutofillMetrics::AddressProfileImportStatusMetric::SECTION_UNION_IMPORT, + 0); + + // Enable the union import feature. + scoped_feature_list_.Reset(); + scoped_feature_list_.InitAndEnableFeature( + features::kAutofillProfileImportFromUnifiedSection); + // Simulate form submission. + autofill_manager_->OnFormSubmitted(form, false, + SubmissionSource::FORM_SUBMISSION); + + histogram_tester.ExpectBucketCount( + histogram, + AutofillMetrics::AddressProfileImportStatusMetric::REGULAR_IMPORT, 0); + histogram_tester.ExpectBucketCount( + histogram, AutofillMetrics::AddressProfileImportStatusMetric::NO_IMPORT, + 1); + histogram_tester.ExpectBucketCount( + histogram, + AutofillMetrics::AddressProfileImportStatusMetric::SECTION_UNION_IMPORT, + 1); +} + // Test that the ProfileImportRequirements are all counted as fulfilled for a // 'perfect' profile import. TEST_F(AutofillMetricsTest, ProfileImportRequirements_AllFulfilled) { @@ -1427,6 +1689,7 @@ TEST_F(AutofillMetricsTest, QualityMetrics_LoggedCorrecltyForRationalizationOk) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -1507,6 +1770,7 @@ TEST_F(AutofillMetricsTest, QualityMetrics_LoggedCorrecltyForRationalizationGood) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -1574,6 +1838,7 @@ TEST_F(AutofillMetricsTest, LogHiddenRepresentationalFieldSkipDecision) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -1716,6 +1981,7 @@ TEST_F(AutofillMetricsTest, LogHiddenRepresentationalFieldSkipDecision) { TEST_F(AutofillMetricsTest, LogRepeatedAddressTypeRationalized) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -1765,7 +2031,7 @@ TEST_F(AutofillMetricsTest, LogRepeatedAddressTypeRationalized) { ASSERT_TRUE(response.SerializeToString(&response_string)); FormStructure::ParseQueryResponse( - response_string, forms, + response_string, forms, test::GetEncodedSignatures(forms), autofill_manager_->form_interactions_ukm_logger_for_test()); ASSERT_EQ(test_ukm_recorder_ @@ -1823,6 +2089,7 @@ TEST_F(AutofillMetricsTest, LogRepeatedAddressTypeRationalized) { TEST_F(AutofillMetricsTest, LogRepeatedStateCountryTypeRationalized) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -1880,7 +2147,7 @@ TEST_F(AutofillMetricsTest, LogRepeatedStateCountryTypeRationalized) { ASSERT_TRUE(response.SerializeToString(&response_string)); FormStructure::ParseQueryResponse( - response_string, forms, + response_string, forms, test::GetEncodedSignatures(forms), autofill_manager_->form_interactions_ukm_logger_for_test()); ASSERT_EQ(test_ukm_recorder_ @@ -1960,6 +2227,7 @@ TEST_F(AutofillMetricsTest, QualityMetrics_LoggedCorrecltyForRationalizationBad) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -2026,6 +2294,7 @@ TEST_F(AutofillMetricsTest, QualityMetrics_LoggedCorrecltyForOnlyFillWhenFocusedField) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -2115,16 +2384,18 @@ TEST_F(AutofillMetricsTest, AutofillMetrics::TRUE_POSITIVE, 4); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(NAME_FULL, AutofillMetrics::TRUE_POSITIVE), 1); + GetFieldTypeGroupPredictionQualityMetric( + NAME_FULL, AutofillMetrics::TRUE_POSITIVE), + 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(ADDRESS_HOME_LINE1, - AutofillMetrics::TRUE_POSITIVE), + GetFieldTypeGroupPredictionQualityMetric( + ADDRESS_HOME_LINE1, AutofillMetrics::TRUE_POSITIVE), 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(PHONE_HOME_CITY_AND_NUMBER, - AutofillMetrics::TRUE_POSITIVE), + GetFieldTypeGroupPredictionQualityMetric( + PHONE_HOME_CITY_AND_NUMBER, AutofillMetrics::TRUE_POSITIVE), 2); // TRUE_NEGATIVE_EMPTY histogram_tester.ExpectBucketCount(aggregate_histogram, @@ -2134,8 +2405,8 @@ TEST_F(AutofillMetricsTest, aggregate_histogram, AutofillMetrics::FALSE_NEGATIVE_MISMATCH, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(PHONE_HOME_WHOLE_NUMBER, - AutofillMetrics::FALSE_NEGATIVE_MISMATCH), + GetFieldTypeGroupPredictionQualityMetric( + PHONE_HOME_WHOLE_NUMBER, AutofillMetrics::FALSE_NEGATIVE_MISMATCH), 1); // Sanity Check: histogram_tester.ExpectTotalCount(aggregate_histogram, 6); @@ -2156,16 +2427,18 @@ TEST_F(AutofillMetricsTest, AutofillMetrics::TRUE_POSITIVE, 4); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(NAME_FULL, AutofillMetrics::TRUE_POSITIVE), 1); + GetFieldTypeGroupPredictionQualityMetric( + NAME_FULL, AutofillMetrics::TRUE_POSITIVE), + 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(ADDRESS_HOME_LINE1, - AutofillMetrics::TRUE_POSITIVE), + GetFieldTypeGroupPredictionQualityMetric( + ADDRESS_HOME_LINE1, AutofillMetrics::TRUE_POSITIVE), 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(PHONE_HOME_CITY_AND_NUMBER, - AutofillMetrics::TRUE_POSITIVE), + GetFieldTypeGroupPredictionQualityMetric( + PHONE_HOME_CITY_AND_NUMBER, AutofillMetrics::TRUE_POSITIVE), 2); // TRUE_NEGATIVE_EMPTY histogram_tester.ExpectBucketCount(aggregate_histogram, @@ -2175,8 +2448,9 @@ TEST_F(AutofillMetricsTest, aggregate_histogram, AutofillMetrics::FALSE_NEGATIVE_MISMATCH, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(PHONE_HOME_CITY_AND_NUMBER, - AutofillMetrics::FALSE_NEGATIVE_MISMATCH), + GetFieldTypeGroupPredictionQualityMetric( + PHONE_HOME_CITY_AND_NUMBER, + AutofillMetrics::FALSE_NEGATIVE_MISMATCH), 1); // Sanity Check: histogram_tester.ExpectTotalCount(aggregate_histogram, 6); @@ -2327,6 +2601,7 @@ TEST_P(QualityMetricsTest, Classification) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -2466,11 +2741,11 @@ TEST_P(QualityMetricsTest, Classification) { aggregate_expected_count); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(actual_field_type, metric), + GetFieldTypeGroupPredictionQualityMetric(actual_field_type, metric), expected_count_for_actual_type); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(predicted_type, metric), + GetFieldTypeGroupPredictionQualityMetric(predicted_type, metric), expected_count_for_predicted_type); } } @@ -2502,6 +2777,7 @@ TEST_F(AutofillMetricsTest, TimingMetrics) { base::HistogramTester histogram_tester; FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -2543,6 +2819,7 @@ TEST_F(AutofillMetricsTest, TimingMetrics) { TEST_F(AutofillMetricsTest, QualityMetrics_NoSubmission) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -2612,47 +2889,49 @@ TEST_F(AutofillMetricsTest, QualityMetrics_NoSubmission) { aggregate_histogram, AutofillMetrics::FALSE_NEGATIVE_UNKNOWN, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(ADDRESS_HOME_COUNTRY, - AutofillMetrics::FALSE_NEGATIVE_UNKNOWN), + GetFieldTypeGroupPredictionQualityMetric( + ADDRESS_HOME_COUNTRY, AutofillMetrics::FALSE_NEGATIVE_UNKNOWN), 1); // Match: histogram_tester.ExpectBucketCount(aggregate_histogram, AutofillMetrics::TRUE_POSITIVE, 2); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(NAME_FULL, AutofillMetrics::TRUE_POSITIVE), 1); + GetFieldTypeGroupPredictionQualityMetric( + NAME_FULL, AutofillMetrics::TRUE_POSITIVE), + 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(PHONE_HOME_WHOLE_NUMBER, - AutofillMetrics::TRUE_POSITIVE), + GetFieldTypeGroupPredictionQualityMetric( + PHONE_HOME_WHOLE_NUMBER, AutofillMetrics::TRUE_POSITIVE), 1); // Mismatch: histogram_tester.ExpectBucketCount( aggregate_histogram, AutofillMetrics::FALSE_NEGATIVE_MISMATCH, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(EMAIL_ADDRESS, - AutofillMetrics::FALSE_NEGATIVE_MISMATCH), + GetFieldTypeGroupPredictionQualityMetric( + EMAIL_ADDRESS, AutofillMetrics::FALSE_NEGATIVE_MISMATCH), 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(PHONE_HOME_NUMBER, - AutofillMetrics::FALSE_POSITIVE_MISMATCH), + GetFieldTypeGroupPredictionQualityMetric( + PHONE_HOME_NUMBER, AutofillMetrics::FALSE_POSITIVE_MISMATCH), 1); // False Positives: histogram_tester.ExpectBucketCount( aggregate_histogram, AutofillMetrics::FALSE_POSITIVE_EMPTY, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(NAME_FULL, - AutofillMetrics::FALSE_POSITIVE_EMPTY), + GetFieldTypeGroupPredictionQualityMetric( + NAME_FULL, AutofillMetrics::FALSE_POSITIVE_EMPTY), 1); histogram_tester.ExpectBucketCount( aggregate_histogram, AutofillMetrics::FALSE_POSITIVE_UNKNOWN, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(PHONE_HOME_NUMBER, - AutofillMetrics::FALSE_POSITIVE_UNKNOWN), + GetFieldTypeGroupPredictionQualityMetric( + PHONE_HOME_NUMBER, AutofillMetrics::FALSE_POSITIVE_UNKNOWN), 1); // Sanity Check: @@ -2674,33 +2953,34 @@ TEST_F(AutofillMetricsTest, QualityMetrics_NoSubmission) { aggregate_histogram, AutofillMetrics::FALSE_NEGATIVE_UNKNOWN, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(ADDRESS_HOME_COUNTRY, - AutofillMetrics::FALSE_NEGATIVE_UNKNOWN), + GetFieldTypeGroupPredictionQualityMetric( + ADDRESS_HOME_COUNTRY, AutofillMetrics::FALSE_NEGATIVE_UNKNOWN), 1); // Match: histogram_tester.ExpectBucketCount(aggregate_histogram, AutofillMetrics::TRUE_POSITIVE, 2); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(EMAIL_ADDRESS, AutofillMetrics::TRUE_POSITIVE), + GetFieldTypeGroupPredictionQualityMetric( + EMAIL_ADDRESS, AutofillMetrics::TRUE_POSITIVE), 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(PHONE_HOME_WHOLE_NUMBER, - AutofillMetrics::TRUE_POSITIVE), + GetFieldTypeGroupPredictionQualityMetric( + PHONE_HOME_WHOLE_NUMBER, AutofillMetrics::TRUE_POSITIVE), 1); // Mismatch: histogram_tester.ExpectBucketCount( aggregate_histogram, AutofillMetrics::FALSE_NEGATIVE_MISMATCH, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(NAME_FULL, - AutofillMetrics::FALSE_NEGATIVE_MISMATCH), + GetFieldTypeGroupPredictionQualityMetric( + NAME_FULL, AutofillMetrics::FALSE_NEGATIVE_MISMATCH), 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(NAME_FIRST, - AutofillMetrics::FALSE_POSITIVE_MISMATCH), + GetFieldTypeGroupPredictionQualityMetric( + NAME_FIRST, AutofillMetrics::FALSE_POSITIVE_MISMATCH), 1); // False Positives: @@ -2708,15 +2988,15 @@ TEST_F(AutofillMetricsTest, QualityMetrics_NoSubmission) { aggregate_histogram, AutofillMetrics::FALSE_POSITIVE_EMPTY, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(NAME_FIRST, - AutofillMetrics::FALSE_POSITIVE_EMPTY), + GetFieldTypeGroupPredictionQualityMetric( + NAME_FIRST, AutofillMetrics::FALSE_POSITIVE_EMPTY), 1); histogram_tester.ExpectBucketCount( aggregate_histogram, AutofillMetrics::FALSE_POSITIVE_UNKNOWN, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(EMAIL_ADDRESS, - AutofillMetrics::FALSE_POSITIVE_UNKNOWN), + GetFieldTypeGroupPredictionQualityMetric( + EMAIL_ADDRESS, AutofillMetrics::FALSE_POSITIVE_UNKNOWN), 1); // Sanity Check: @@ -2729,6 +3009,7 @@ TEST_F(AutofillMetricsTest, QualityMetrics_NoSubmission) { // on autocomplete attributes present on the fields. TEST_F(AutofillMetricsTest, QualityMetrics_BasedOnAutocomplete) { FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("MyForm"); form.url = GURL("http://myform.com/form.html"); form.action = GURL("http://myform.com/submit.html"); @@ -2761,7 +3042,7 @@ TEST_F(AutofillMetricsTest, QualityMetrics_BasedOnAutocomplete) { TestFormStructure* form_structure_ptr = form_structure.get(); form_structure->DetermineHeuristicTypes(); ASSERT_TRUE(autofill_manager_->mutable_form_structures_for_test() - ->emplace(form_structure_ptr->form_signature(), + ->emplace(form_structure_ptr->unique_renderer_id(), std::move(form_structure)) .second); @@ -2785,12 +3066,9 @@ TEST_F(AutofillMetricsTest, QualityMetrics_BasedOnAutocomplete) { std::string encoded_response_string; base::Base64Encode(response_string, &encoded_response_string); - std::vector<std::string> signatures; - signatures.push_back(form_structure_ptr->FormSignatureAsStr()); - base::HistogramTester histogram_tester; - autofill_manager_->OnLoadedServerPredictionsForTest(encoded_response_string, - signatures); + autofill_manager_->OnLoadedServerPredictionsForTest( + encoded_response_string, test::GetEncodedSignatures(*form_structure_ptr)); // Verify that FormStructure::ParseQueryResponse was called (here and below). histogram_tester.ExpectBucketCount("Autofill.ServerQueryResponse", @@ -2819,27 +3097,29 @@ TEST_F(AutofillMetricsTest, QualityMetrics_BasedOnAutocomplete) { aggregate_histogram, AutofillMetrics::FALSE_NEGATIVE_UNKNOWN, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(ADDRESS_HOME_ZIP, - AutofillMetrics::FALSE_NEGATIVE_UNKNOWN), + GetFieldTypeGroupPredictionQualityMetric( + ADDRESS_HOME_ZIP, AutofillMetrics::FALSE_NEGATIVE_UNKNOWN), 1); // Match: histogram_tester.ExpectBucketCount(aggregate_histogram, AutofillMetrics::TRUE_POSITIVE, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(NAME_LAST, AutofillMetrics::TRUE_POSITIVE), 1); + GetFieldTypeGroupPredictionQualityMetric( + NAME_LAST, AutofillMetrics::TRUE_POSITIVE), + 1); // Mismatch: histogram_tester.ExpectBucketCount( aggregate_histogram, AutofillMetrics::FALSE_NEGATIVE_MISMATCH, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(NAME_FIRST, - AutofillMetrics::FALSE_POSITIVE_MISMATCH), + GetFieldTypeGroupPredictionQualityMetric( + NAME_FIRST, AutofillMetrics::FALSE_POSITIVE_MISMATCH), 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(NAME_MIDDLE, - AutofillMetrics::FALSE_POSITIVE_MISMATCH), + GetFieldTypeGroupPredictionQualityMetric( + NAME_MIDDLE, AutofillMetrics::FALSE_POSITIVE_MISMATCH), 1); // Sanity check. @@ -2852,6 +3132,7 @@ TEST_F(AutofillMetricsTest, QualityMetrics_BasedOnAutocomplete) { TEST_F(AutofillMetricsTest, UpiVirtualPaymentAddress) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -2911,6 +3192,7 @@ TEST_F(AutofillMetricsTest, PredictedMetricsWithAutocomplete) { // seen/parsed, and the time it is submitted. FormData form; FormFieldData field; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -2948,7 +3230,7 @@ TEST_F(AutofillMetricsTest, PredictedMetricsWithAutocomplete) { SCOPED_TRACE("ADDRESS_HOME_COUNTRY"); histogram_tester.ExpectBucketCount( histogram_name, - GetFieldTypeGroupMetric( + GetFieldTypeGroupPredictionQualityMetric( ADDRESS_HOME_COUNTRY, source == "Overall" ? AutofillMetrics::TRUE_POSITIVE : AutofillMetrics::FALSE_NEGATIVE_UNKNOWN), @@ -2961,8 +3243,8 @@ TEST_F(AutofillMetricsTest, PredictedMetricsWithAutocomplete) { SCOPED_TRACE("ADDRESS_HOME_ZIP"); histogram_tester.ExpectBucketCount( histogram_name, - GetFieldTypeGroupMetric(ADDRESS_HOME_ZIP, - AutofillMetrics::FALSE_NEGATIVE_UNKNOWN), + GetFieldTypeGroupPredictionQualityMetric( + ADDRESS_HOME_ZIP, AutofillMetrics::FALSE_NEGATIVE_UNKNOWN), 1); } @@ -2971,10 +3253,10 @@ TEST_F(AutofillMetricsTest, PredictedMetricsWithAutocomplete) { SCOPED_TRACE("PHONE_HOME_WHOLE_NUMBER"); histogram_tester.ExpectBucketCount( histogram_name, - GetFieldTypeGroupMetric(PHONE_HOME_WHOLE_NUMBER, - source == "Server" - ? AutofillMetrics::FALSE_NEGATIVE_UNKNOWN - : AutofillMetrics::TRUE_POSITIVE), + GetFieldTypeGroupPredictionQualityMetric( + PHONE_HOME_WHOLE_NUMBER, + source == "Server" ? AutofillMetrics::FALSE_NEGATIVE_UNKNOWN + : AutofillMetrics::TRUE_POSITIVE), 1); } @@ -2988,6 +3270,7 @@ TEST_F(AutofillMetricsTest, PredictedMetricsWithAutocomplete) { TEST_F(AutofillMetricsTest, SaneMetricsWithCacheMismatch) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -3052,8 +3335,8 @@ TEST_F(AutofillMetricsTest, SaneMetricsWithCacheMismatch) { aggregate_histogram, AutofillMetrics::FALSE_NEGATIVE_UNKNOWN, 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(ADDRESS_HOME_STATE, - AutofillMetrics::FALSE_NEGATIVE_UNKNOWN), + GetFieldTypeGroupPredictionQualityMetric( + ADDRESS_HOME_STATE, AutofillMetrics::FALSE_NEGATIVE_UNKNOWN), 1); // Match: histogram_tester.ExpectBucketCount(aggregate_histogram, @@ -3061,23 +3344,25 @@ TEST_F(AutofillMetricsTest, SaneMetricsWithCacheMismatch) { source == "Heuristic" ? 2 : 1); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(NAME_FULL, AutofillMetrics::TRUE_POSITIVE), 1); + GetFieldTypeGroupPredictionQualityMetric( + NAME_FULL, AutofillMetrics::TRUE_POSITIVE), + 1); // Mismatch: histogram_tester.ExpectBucketCount(aggregate_histogram, AutofillMetrics::FALSE_NEGATIVE_MISMATCH, source == "Heuristic" ? 1 : 2); histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(EMAIL_ADDRESS, - AutofillMetrics::FALSE_NEGATIVE_MISMATCH), + GetFieldTypeGroupPredictionQualityMetric( + EMAIL_ADDRESS, AutofillMetrics::FALSE_NEGATIVE_MISMATCH), 1); // Source dependent: histogram_tester.ExpectBucketCount( by_field_type_histogram, - GetFieldTypeGroupMetric(ADDRESS_HOME_CITY, - source == "Heuristic" - ? AutofillMetrics::TRUE_POSITIVE - : AutofillMetrics::FALSE_NEGATIVE_MISMATCH), + GetFieldTypeGroupPredictionQualityMetric( + ADDRESS_HOME_CITY, source == "Heuristic" + ? AutofillMetrics::TRUE_POSITIVE + : AutofillMetrics::FALSE_NEGATIVE_MISMATCH), 1); } } @@ -3087,6 +3372,7 @@ TEST_F(AutofillMetricsTest, SaneMetricsWithCacheMismatch) { TEST_F(AutofillMetricsTest, StoredProfileCountAutofillableFormSubmission) { // Construct a fillable form. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -3123,6 +3409,7 @@ TEST_F(AutofillMetricsTest, StoredProfileCountNonAutofillableFormSubmission) { features.InitAndEnableFeature(kAutofillEnforceMinRequiredFieldsForHeuristics); // Construct a non-fillable form. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -3150,11 +3437,150 @@ TEST_F(AutofillMetricsTest, StoredProfileCountNonAutofillableFormSubmission) { "Autofill.StoredProfileCountAtAutofillableFormSubmission", 0); } +// Verify that when submitting an autofillable form, the proper tppe of +// the edited fields is correctly logged to UKM. +TEST_F(AutofillMetricsTest, TypeOfEditedAutofilledFieldsUkmLogging) { + // Construct a fillable form. + FormData form; + form.name = ASCIIToUTF16("TestForm"); + form.url = GURL("https://example.test/form.html"); + form.action = GURL("https://example.test/submit.html"); + form.main_frame_origin = + url::Origin::Create(GURL("https://example.test/form.html")); + + std::vector<ServerFieldType> heuristic_types, server_types; + + FormFieldData field; + test::CreateTestFormField("Autofilled", "autofilled", "Elvis Aaron Presley", + "text", &field); + field.is_autofilled = true; + form.fields.push_back(field); + heuristic_types.push_back(NAME_FULL); + server_types.push_back(NAME_FULL); + + test::CreateTestFormField("Autofill Failed", "autofillfailed", + "buddy@gmail.com", "text", &field); + field.is_autofilled = true; + form.fields.push_back(field); + heuristic_types.push_back(EMAIL_ADDRESS); + server_types.push_back(EMAIL_ADDRESS); + + test::CreateTestFormField("Phone", "phone", "2345678901", "tel", &field); + field.is_autofilled = true; + form.fields.push_back(field); + heuristic_types.push_back(PHONE_HOME_CITY_AND_NUMBER); + server_types.push_back(PHONE_HOME_CITY_AND_NUMBER); + + autofill_manager_->AddSeenForm(form, heuristic_types, server_types); + + // Verify that there are no counts before form submission. + + EXPECT_EQ(0U, test_ukm_recorder_->entries_count()); + + base::HistogramTester histogram_tester; + // Simulate text input in the first and second fields. + autofill_manager_->OnTextFieldDidChange(form, form.fields[0], gfx::RectF(), + TimeTicks()); + + // Simulate form submission. + autofill_manager_->OnFormSubmitted(form, false, + SubmissionSource::FORM_SUBMISSION); + ExpectedUkmMetricsRecord name_field_ukm_record{ + {UkmEditedAutofilledFieldAtSubmission::kFieldSignatureName, + Collapse(CalculateFieldSignatureForField(form.fields[0])).value()}, + {UkmEditedAutofilledFieldAtSubmission::kFormSignatureName, + Collapse(CalculateFormSignature(form)).value()}, + {UkmEditedAutofilledFieldAtSubmission::kOverallTypeName, + static_cast<int64_t>(NAME_FULL)}}; + + VerifyUkm(test_ukm_recorder_, form, + UkmEditedAutofilledFieldAtSubmission::kEntryName, + {name_field_ukm_record}); +} + +// Verify that when submitting an autofillable form, the proper tppe of +// the edited fields is correctly logged to UMA. +TEST_F(AutofillMetricsTest, TypeOfEditedAutofilledFieldsUmaLogging) { + // Construct a fillable form. + FormData form; + form.name = ASCIIToUTF16("TestForm"); + form.url = GURL("http://example.com/form.html"); + form.action = GURL("http://example.com/submit.html"); + form.main_frame_origin = + url::Origin::Create(GURL("http://example_root.com/form.html")); + + std::vector<ServerFieldType> heuristic_types, server_types; + + FormFieldData field; + test::CreateTestFormField("Autofilled", "autofilled", "Elvis Aaron Presley", + "text", &field); + field.is_autofilled = true; + form.fields.push_back(field); + heuristic_types.push_back(NAME_FULL); + server_types.push_back(NAME_FULL); + + test::CreateTestFormField("Autofill Failed", "autofillfailed", + "buddy@gmail.com", "text", &field); + field.is_autofilled = true; + form.fields.push_back(field); + heuristic_types.push_back(EMAIL_ADDRESS); + server_types.push_back(EMAIL_ADDRESS); + + test::CreateTestFormField("Phone", "phone", "2345678901", "tel", &field); + field.is_autofilled = true; + form.fields.push_back(field); + heuristic_types.push_back(PHONE_HOME_CITY_AND_NUMBER); + server_types.push_back(PHONE_HOME_CITY_AND_NUMBER); + + autofill_manager_->AddSeenForm(form, heuristic_types, server_types); + + base::HistogramTester histogram_tester; + // Simulate text input in the first and second fields. + autofill_manager_->OnTextFieldDidChange(form, form.fields[0], gfx::RectF(), + TimeTicks()); + autofill_manager_->OnTextFieldDidChange(form, form.fields[1], gfx::RectF(), + TimeTicks()); + + // Simulate form submission. + autofill_manager_->OnFormSubmitted(form, false, + SubmissionSource::FORM_SUBMISSION); + + // The |NAME_FULL| field was edited (bucket 112). + histogram_tester.ExpectBucketCount( + "Autofill.EditedAutofilledFieldAtSubmission.ByFieldType", 112, 1); + + // The |NAME_FULL| field was edited (bucket 144). + histogram_tester.ExpectBucketCount( + "Autofill.EditedAutofilledFieldAtSubmission.ByFieldType", 144, 1); + + // The |PHONE_HOME_CITY_AND_NUMBER| field was not edited (bucket 209). + histogram_tester.ExpectBucketCount( + "Autofill.EditedAutofilledFieldAtSubmission.ByFieldType", 209, 1); + + // The aggregated histogram should have two counts on edited fields. + histogram_tester.ExpectBucketCount( + "Autofill.EditedAutofilledFieldAtSubmission.Aggregate", 0, 2); + + // The aggregated histogram should have one count on accepted fields. + histogram_tester.ExpectBucketCount( + "Autofill.EditedAutofilledFieldAtSubmission.Aggregate", 1, 1); + + // The autocomplete!=off histogram should have one count on accepted fields. + histogram_tester.ExpectBucketCount( + "Autofill.Autocomplete.NotOff.EditedAutofilledFieldAtSubmission.Address", + 1, 1); + + // The autocomplete!=off histogram should have no count on accepted fields. + histogram_tester.ExpectTotalCount( + "Autofill.Autocomplete.Off.EditedAutofilledFieldAtSubmission.Address", 0); +} + // Verify that when submitting an autofillable form, the proper number of edited // fields is logged. TEST_F(AutofillMetricsTest, NumberOfEditedAutofilledFields) { // Construct a fillable form. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -3209,6 +3635,7 @@ TEST_F(AutofillMetricsTest, NumberOfEditedAutofilledFields) { TEST_F(AutofillMetricsTest, NumberOfEditedAutofilledFields_NoSubmission) { // Construct a fillable form. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -3258,6 +3685,7 @@ TEST_F(AutofillMetricsTest, NumberOfEditedAutofilledFields_NoSubmission) { TEST_F(AutofillMetricsTest, DeveloperEngagement) { // Start with a non-fillable form. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -3362,6 +3790,7 @@ TEST_F(AutofillMetricsTest, UkmDeveloperEngagement_LogFillableFormParsedWithoutTypeHints) { // Start with a non-fillable form. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -3408,6 +3837,7 @@ TEST_F(AutofillMetricsTest, TEST_F(AutofillMetricsTest, UkmDeveloperEngagement_LogFillableFormParsedWithTypeHints) { FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -3465,6 +3895,7 @@ TEST_F(AutofillMetricsTest, UkmDeveloperEngagement_LogUpiVpaTypeHint) { // Disabled. {}); FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -3634,6 +4065,67 @@ TEST_F(AutofillMetricsTest, LogStoredCreditCardMetrics) { "Autofill.DaysSinceLastUse.StoredCreditCard.Server.Unmasked", 200, 3); } +TEST_F(AutofillMetricsTest, LogStoredCreditCardWithNicknameMetrics) { + std::vector<std::unique_ptr<CreditCard>> local_cards; + std::vector<std::unique_ptr<CreditCard>> server_cards; + local_cards.reserve(2); + server_cards.reserve(4); + + // Create cards with and without nickname of each record type: 1 of each for + // local, 2 of each for masked. + const std::vector<CreditCard::RecordType> record_types{ + CreditCard::LOCAL_CARD, CreditCard::MASKED_SERVER_CARD}; + int num_cards_of_type = 0; + for (auto record_type : record_types) { + num_cards_of_type += 1; + for (int i = 0; i < num_cards_of_type; ++i) { + // Create a card with a nickname. + CreditCard card_with_nickname = test::GetRandomCreditCard(record_type); + card_with_nickname.SetNickname(ASCIIToUTF16("Valid nickname")); + + // Create a card that doesn't have a nickname. + CreditCard card_without_nickname = test::GetRandomCreditCard(record_type); + // Set nickname to empty. + card_without_nickname.SetNickname(ASCIIToUTF16("")); + + // Add the cards to the personal data manager in the appropriate way. + auto& repo = + (record_type == CreditCard::LOCAL_CARD) ? local_cards : server_cards; + repo.push_back( + std::make_unique<CreditCard>(std::move(card_with_nickname))); + repo.push_back( + std::make_unique<CreditCard>(std::move(card_without_nickname))); + } + } + + // Log the stored credit card metrics for the cards configured above. + base::HistogramTester histogram_tester; + AutofillMetrics::LogStoredCreditCardMetrics(local_cards, server_cards, + base::TimeDelta::FromDays(180)); + + // Validate the count metrics. + histogram_tester.ExpectTotalCount("Autofill.StoredCreditCardCount", 1); + histogram_tester.ExpectTotalCount("Autofill.StoredCreditCardCount.Local", 1); + histogram_tester.ExpectTotalCount( + "Autofill.StoredCreditCardCount.Local.WithNickname", 1); + histogram_tester.ExpectTotalCount("Autofill.StoredCreditCardCount.Server", 1); + histogram_tester.ExpectTotalCount( + "Autofill.StoredCreditCardCount.Server.Masked", 1); + histogram_tester.ExpectTotalCount( + "Autofill.StoredCreditCardCount.Server.Masked.WithNickname", 1); + histogram_tester.ExpectBucketCount("Autofill.StoredCreditCardCount", 6, 1); + histogram_tester.ExpectBucketCount("Autofill.StoredCreditCardCount.Local", 2, + 1); + histogram_tester.ExpectBucketCount( + "Autofill.StoredCreditCardCount.Local.WithNickname", 1, 1); + histogram_tester.ExpectBucketCount("Autofill.StoredCreditCardCount.Server", 4, + 1); + histogram_tester.ExpectBucketCount( + "Autofill.StoredCreditCardCount.Server.Masked", 4, 1); + histogram_tester.ExpectBucketCount( + "Autofill.StoredCreditCardCount.Server.Masked.WithNickname", 2, 1); +} + // Test that we correctly log when Profile Autofill is enabled at startup. TEST_F(AutofillMetricsTest, AutofillProfileIsEnabledAtStartup) { base::HistogramTester histogram_tester; @@ -3698,6 +4190,7 @@ TEST_F(AutofillMetricsTest, AutofillCreditCardIsDisabledAtStartup) { TEST_F(AutofillMetricsTest, AddressSuggestionsCount) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -3770,6 +4263,7 @@ TEST_F(AutofillMetricsTest, AddressSuggestionsCount) { TEST_P(AutofillMetricsCompanyTest, CompanyNameSuggestions) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -3814,6 +4308,7 @@ TEST_F(AutofillMetricsTest, CreditCardCheckoutFlowUserActions) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -3999,6 +4494,7 @@ TEST_F(AutofillMetricsTest, CreditCardCheckoutFlowUserActions) { // Test that the UPI Checkout flow form submit is correctly logged TEST_F(AutofillMetricsTest, UpiVpaUkmTest) { FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -4030,6 +4526,7 @@ TEST_F(AutofillMetricsTest, ProfileCheckoutFlowUserActions) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -4171,6 +4668,7 @@ TEST_F(AutofillMetricsTest, PolledCreditCardSuggestions_DebounceLogs) { // Set up the form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://www.foo.com/"); @@ -4231,6 +4729,7 @@ TEST_F(AutofillMetricsTest, QueriedCreditCardFormIsSecure) { // Set up the form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -4250,6 +4749,7 @@ TEST_F(AutofillMetricsTest, QueriedCreditCardFormIsSecure) { { // Simulate having seen this insecure form on page load. + form.unique_renderer_id = MakeFormRendererId(); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); form.main_frame_origin = @@ -4269,6 +4769,7 @@ TEST_F(AutofillMetricsTest, QueriedCreditCardFormIsSecure) { { // Simulate having seen this secure form on page load. autofill_manager_->Reset(); + form.unique_renderer_id = MakeFormRendererId(); form.url = GURL("https://example.com/form.html"); form.action = GURL("https://example.com/submit.html"); form.main_frame_origin = @@ -4292,6 +4793,7 @@ TEST_F(AutofillMetricsTest, PolledProfileSuggestions_DebounceLogs) { // Set up the form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -4349,6 +4851,7 @@ TEST_F(AutofillMetricsTest, PolledProfileSuggestions_DebounceLogs) { TEST_P(AutofillMetricsIFrameTest, CreditCardParsedFormEvents) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -4381,6 +4884,7 @@ TEST_P(AutofillMetricsIFrameTest, CreditCardParsedFormEvents) { TEST_P(AutofillMetricsIFrameTest, CreditCardInteractedFormEvents) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -4437,6 +4941,7 @@ TEST_P(AutofillMetricsIFrameTest, CreditCardInteractedFormEvents) { TEST_P(AutofillMetricsIFrameTest, CreditCardPopupSuppressedFormEvents) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -4495,6 +5000,7 @@ TEST_P(AutofillMetricsIFrameTest, CreditCardPopupSuppressedFormEvents) { TEST_P(AutofillMetricsIFrameTest, CreditCardShownFormEvents) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -4577,6 +5083,7 @@ TEST_P(AutofillMetricsIFrameTest, CreditCardSelectedFormEvents) { true /* include_full_server_credit_card */); // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -4658,6 +5165,7 @@ TEST_P(AutofillMetricsIFrameTest, CreditCardFilledFormEvents) { true /* include_full_server_credit_card */); // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -4910,6 +5418,7 @@ TEST_F(AutofillMetricsTest, CreditCardGetRealPanDuration) { false /* include_full_server_credit_card */); // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -4979,6 +5488,7 @@ TEST_F(AutofillMetricsTest, // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -5021,6 +5531,7 @@ TEST_P(AutofillMetricsIFrameTest, // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -5065,6 +5576,7 @@ TEST_P(AutofillMetricsIFrameTest, // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -5110,6 +5622,7 @@ TEST_P(AutofillMetricsIFrameTest, // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -5156,6 +5669,7 @@ TEST_P(AutofillMetricsIFrameTest, // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -5202,6 +5716,7 @@ TEST_P(AutofillMetricsIFrameTest, // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -5261,6 +5776,7 @@ TEST_F(AutofillMetricsTest, ShouldNotLogFormEventNoCardForAddressForm) { RecreateProfile(/*is_server=*/false); // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -5302,6 +5818,7 @@ TEST_P(AutofillMetricsIFrameTest, CreditCardSubmittedFormEvents) { true /* include_full_server_credit_card */); // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -5796,6 +6313,7 @@ TEST_P(AutofillMetricsIFrameTest, CreditCardWillSubmitFormEvents) { true /* include_full_server_credit_card */); // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -6456,6 +6974,7 @@ TEST_P(AutofillMetricsServerNicknameTest, LogServerNicknameSelectionDuration) { TEST_F(AutofillMetricsTest, MixedParsedFormEvents) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -6499,6 +7018,7 @@ TEST_F(AutofillMetricsTest, MixedParsedFormEvents) { TEST_F(AutofillMetricsTest, AddressParsedFormEvents) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -6541,6 +7061,7 @@ TEST_F(AutofillMetricsTest, AddressParsedFormEvents) { TEST_F(AutofillMetricsTest, AddressInteractedFormEvents) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -6617,6 +7138,7 @@ TEST_F(AutofillMetricsTest, AddressSuppressedFormEvents) { RecreateProfile(/*is_server=*/false); // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -6710,6 +7232,7 @@ TEST_F(AutofillMetricsTest, AddressShownFormEvents) { RecreateProfile(/*is_server=*/false); // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -6821,6 +7344,7 @@ TEST_F(AutofillMetricsTest, AddressFilledFormEvents) { RecreateProfile(/*is_server=*/false); // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -6940,6 +7464,7 @@ TEST_F(AutofillMetricsTest, AddressSubmittedFormEvents) { RecreateProfile(/*is_server=*/false); // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -7152,6 +7677,7 @@ TEST_F(AutofillMetricsTest, AddressWillSubmitFormEvents) { RecreateProfile(/*is_server=*/false); // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -7336,6 +7862,7 @@ TEST_F(AutofillMetricsTest, AddressWillSubmitFormEvents) { TEST_F(AutofillMetricsTest, CreditCardFormEventsAreSegmented) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -7447,6 +7974,7 @@ TEST_F(AutofillMetricsTest, CreditCardFormEventsAreSegmented) { TEST_F(AutofillMetricsTest, AddressFormEventsAreSegmented) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -7556,6 +8084,7 @@ TEST_F(AutofillMetricsTest, DaysSinceLastUse_Profile) { TEST_F(AutofillMetricsTest, AutofillFormSubmittedState) { // Start with a form with insufficiently many fields. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -7859,6 +8388,7 @@ TEST_F( AutofillMetricsTest, AutofillFormSubmittedState_DontCountUnfilledFieldsWithOnlyFillWhenFocused) { FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -7994,6 +8524,7 @@ TEST_F(AutofillMetricsTest, LogUserHappinessMetric_UnknownForm) { TEST_F(AutofillMetricsTest, UserHappinessFormInteraction_EmptyForm) { // Load a fillable form. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -8020,6 +8551,7 @@ TEST_F(AutofillMetricsTest, UserHappinessFormInteraction_CreditCardForm) { // Load a fillable form. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("https://example.com/form.html"); form.action = GURL("https://example.com/submit.html"); @@ -8189,6 +8721,7 @@ TEST_F(AutofillMetricsTest, UserHappinessFormInteraction_CreditCardForm) { TEST_F(AutofillMetricsTest, UserHappinessFormInteraction_AddressForm) { // Load a fillable form. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -8443,6 +8976,7 @@ TEST_F(AutofillMetricsTest, FormFillDuration) { // Load a fillable form. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -8460,6 +8994,7 @@ TEST_F(AutofillMetricsTest, FormFillDuration) { // Fill additional form. FormData second_form = form; + second_form.unique_renderer_id = MakeFormRendererId(); test::CreateTestFormField("Second Phone", "second_phone", "", "text", &field); second_form.fields.push_back(field); @@ -8864,6 +9399,7 @@ TEST_F(AutofillMetricsTest, ProfileActionOnFormSubmitted) { // Load a fillable form. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -8990,6 +9526,7 @@ class AutofillMetricsParseQueryResponseTest : public testing::Test { public: void SetUp() override { FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.url = GURL("http://foo.com"); form.main_frame_origin = url::Origin::Create(GURL("http://foo_root.com")); FormFieldData field; @@ -9043,7 +9580,8 @@ TEST_F(AutofillMetricsParseQueryResponseTest, ServerHasData) { ASSERT_TRUE(response.SerializeToString(&response_string)); base::HistogramTester histogram_tester; - FormStructure::ParseQueryResponse(response_string, forms_, nullptr); + FormStructure::ParseQueryResponse( + response_string, forms_, test::GetEncodedSignatures(forms_), nullptr); EXPECT_THAT( histogram_tester.GetAllSamples("Autofill.ServerResponseHasDataForForm"), ElementsAre(Bucket(true, 2))); @@ -9062,7 +9600,8 @@ TEST_F(AutofillMetricsParseQueryResponseTest, OneFormNoServerData) { ASSERT_TRUE(response.SerializeToString(&response_string)); base::HistogramTester histogram_tester; - FormStructure::ParseQueryResponse(response_string, forms_, nullptr); + FormStructure::ParseQueryResponse( + response_string, forms_, test::GetEncodedSignatures(forms_), nullptr); EXPECT_THAT( histogram_tester.GetAllSamples("Autofill.ServerResponseHasDataForForm"), ElementsAre(Bucket(false, 1), Bucket(true, 1))); @@ -9080,7 +9619,8 @@ TEST_F(AutofillMetricsParseQueryResponseTest, AllFormsNoServerData) { ASSERT_TRUE(response.SerializeToString(&response_string)); base::HistogramTester histogram_tester; - FormStructure::ParseQueryResponse(response_string, forms_, nullptr); + FormStructure::ParseQueryResponse( + response_string, forms_, test::GetEncodedSignatures(forms_), nullptr); EXPECT_THAT( histogram_tester.GetAllSamples("Autofill.ServerResponseHasDataForForm"), ElementsAre(Bucket(false, 2))); @@ -9099,7 +9639,8 @@ TEST_F(AutofillMetricsParseQueryResponseTest, PartialNoServerData) { ASSERT_TRUE(response.SerializeToString(&response_string)); base::HistogramTester histogram_tester; - FormStructure::ParseQueryResponse(response_string, forms_, nullptr); + FormStructure::ParseQueryResponse( + response_string, forms_, test::GetEncodedSignatures(forms_), nullptr); EXPECT_THAT( histogram_tester.GetAllSamples("Autofill.ServerResponseHasDataForForm"), ElementsAre(Bucket(true, 2))); @@ -9114,6 +9655,7 @@ TEST_F(AutofillMetricsTest, NonsecureCreditCardForm) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -9172,6 +9714,7 @@ TEST_F(AutofillMetricsTest, // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("https://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -9296,6 +9839,7 @@ TEST_F(AutofillMetricsTest, DISABLED_AutofillSuggestionShownTest) { false /* include_masked_server_credit_card */, false /* include_full_server_credit_card */); FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example_cc.com/form.html"); form.action = GURL("http://example_cc.com/submit.html"); @@ -9335,6 +9879,7 @@ TEST_F(AutofillMetricsTest, DynamicFormMetrics) { // Set up our form data. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -9434,10 +9979,10 @@ TEST_F(AutofillMetricsTest, LogUserHappinessBySecurityLevel) { base::HistogramTester histogram_tester; AutofillMetrics::LogUserHappinessBySecurityLevel( AutofillMetrics::USER_DID_AUTOFILL_ONCE, UNKNOWN_FORM_TYPE, - security_state::SecurityLevel::EV_SECURE); - histogram_tester.ExpectBucketCount( - "Autofill.UserHappiness.Unknown.EV_SECURE", - AutofillMetrics::USER_DID_AUTOFILL_ONCE, 1); + security_state::SecurityLevel::SECURE); + histogram_tester.ExpectBucketCount("Autofill.UserHappiness.Unknown.SECURE", + AutofillMetrics::USER_DID_AUTOFILL_ONCE, + 1); } { @@ -9457,6 +10002,7 @@ TEST_F(AutofillMetricsTest, LogUserHappinessBySecurityLevel) { TEST_F(AutofillMetricsTest, LogUserHappinessBySecurityLevel_FromFormEvents) { // Load a fillable form. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -9718,11 +10264,11 @@ TEST_F(AutofillMetricsTest, LogSaveCardPromptMetricBySecurityLevel) { { base::HistogramTester histogram_tester; AutofillMetrics::LogSaveCardPromptMetricBySecurityLevel( - AutofillMetrics::SAVE_CARD_PROMPT_SHOWN, /*is_uploading=*/true, - security_state::SecurityLevel::SECURE); + AutofillMetrics::SAVE_CARD_PROMPT_SHOWN_DEPRECATED, + /*is_uploading=*/true, security_state::SecurityLevel::SECURE); histogram_tester.ExpectBucketCount( "Autofill.SaveCreditCardPrompt.Upload.SECURE", - AutofillMetrics::SAVE_CARD_PROMPT_SHOWN, 1); + AutofillMetrics::SAVE_CARD_PROMPT_SHOWN_DEPRECATED, 1); } { @@ -9749,9 +10295,9 @@ TEST_F(AutofillMetricsTest, LogSaveCardPromptMetricBySecurityLevel) { base::HistogramTester histogram_tester; AutofillMetrics::LogSaveCardPromptMetricBySecurityLevel( AutofillMetrics::SAVE_CARD_PROMPT_END_NAVIGATION_SHOWING, - /*is_uploading=*/false, security_state::SecurityLevel::EV_SECURE); + /*is_uploading=*/false, security_state::SecurityLevel::SECURE); histogram_tester.ExpectBucketCount( - "Autofill.SaveCreditCardPrompt.Local.EV_SECURE", + "Autofill.SaveCreditCardPrompt.Local.SECURE", AutofillMetrics::SAVE_CARD_PROMPT_END_NAVIGATION_SHOWING, 1); } @@ -9779,22 +10325,23 @@ TEST_F(AutofillMetricsTest, /*is_uploading=*/true, /*is_reshow=*/false, AutofillClient::SaveCreditCardOptions(), /*previous_save_credit_card_prompt_user_decision=*/1, - security_state::SecurityLevel::EV_SECURE, SyncSigninState::kSignedOut); + security_state::SecurityLevel::SECURE, SyncSigninState::kSignedOut); histogram_tester.ExpectBucketCount( - "Autofill.SaveCreditCardPrompt.Upload.EV_SECURE", + "Autofill.SaveCreditCardPrompt.Upload.SECURE", AutofillMetrics::SAVE_CARD_PROMPT_END_NAVIGATION_SHOWING, 1); } { base::HistogramTester histogram_tester; AutofillMetrics::LogSaveCardPromptMetric( - AutofillMetrics::SAVE_CARD_PROMPT_SHOWN, /*is_uploading=*/false, + AutofillMetrics::SAVE_CARD_PROMPT_SHOWN_DEPRECATED, + /*is_uploading=*/false, /*is_reshow=*/true, AutofillClient::SaveCreditCardOptions(), /*previous_save_credit_card_prompt_user_decision=*/0, security_state::SecurityLevel::SECURE, SyncSigninState::kSignedOut); histogram_tester.ExpectBucketCount( "Autofill.SaveCreditCardPrompt.Local.SECURE", - AutofillMetrics::SAVE_CARD_PROMPT_SHOWN, 1); + AutofillMetrics::SAVE_CARD_PROMPT_SHOWN_DEPRECATED, 1); } } @@ -9963,15 +10510,17 @@ TEST_F(AutofillMetricsTest, LogSaveCardPromptMetric_BySyncState) { /*is_uploading=*/true, /*is_reshow=*/false, AutofillClient::SaveCreditCardOptions(), /*previous_save_credit_card_prompt_user_decision=*/1, - security_state::SecurityLevel::EV_SECURE, SyncSigninState::kSignedIn); + security_state::SecurityLevel::SECURE, SyncSigninState::kSignedIn); histogram_tester.ExpectBucketCount( "Autofill.SaveCreditCardPrompt.Upload.FirstShow.SignedIn", AutofillMetrics::SAVE_CARD_PROMPT_END_NAVIGATION_SHOWING, 1); } + { base::HistogramTester histogram_tester; AutofillMetrics::LogSaveCardPromptMetric( - AutofillMetrics::SAVE_CARD_PROMPT_SHOWN, /*is_uploading=*/false, + AutofillMetrics::SAVE_CARD_PROMPT_SHOWN_DEPRECATED, + /*is_uploading=*/false, /*is_reshow=*/true, AutofillClient::SaveCreditCardOptions(), /*previous_save_credit_card_prompt_user_decision=*/0, security_state::SecurityLevel::SECURE, @@ -9979,7 +10528,7 @@ TEST_F(AutofillMetricsTest, LogSaveCardPromptMetric_BySyncState) { histogram_tester.ExpectBucketCount( "Autofill.SaveCreditCardPrompt.Local.Reshows." "SignedInAndSyncFeatureEnabled", - AutofillMetrics::SAVE_CARD_PROMPT_SHOWN, 1); + AutofillMetrics::SAVE_CARD_PROMPT_SHOWN_DEPRECATED, 1); } } @@ -10026,6 +10575,7 @@ TEST_P(AutofillMetricsFunnelTest, LogFunnelMetrics) { // Load a fillable form. FormData form; + form.unique_renderer_id = MakeFormRendererId(); form.name = ASCIIToUTF16("TestForm"); form.url = GURL("http://example.com/form.html"); form.action = GURL("http://example.com/submit.html"); @@ -10136,6 +10686,10 @@ TEST_P(AutofillMetricsFunnelTest, LogFunnelMetrics) { "Autofill.KeyMetrics.FillingCorrectness.Address", 1, 1); histogram_tester.ExpectBucketCount( "Autofill.KeyMetrics.FillingAssistance.Address", 1, 1); + histogram_tester.ExpectBucketCount( + "Autofill.Autocomplete.NotOff.FillingAcceptance.Address", 1, 1); + histogram_tester.ExpectTotalCount( + "Autofill.Autocomplete.Off.FillingAcceptance.Address", 0); } else { histogram_tester.ExpectTotalCount( "Autofill.KeyMetrics.FillingReadiness.Address", 0); @@ -10145,6 +10699,10 @@ TEST_P(AutofillMetricsFunnelTest, LogFunnelMetrics) { "Autofill.KeyMetrics.FillingCorrectness.Address", 0); histogram_tester.ExpectTotalCount( "Autofill.KeyMetrics.FillingAssistance.Address", 0); + histogram_tester.ExpectTotalCount( + "Autofill.Autocomplete.NotOff.FillingAcceptance.Address", 0); + histogram_tester.ExpectTotalCount( + "Autofill.Autocomplete.Off.FillingAcceptance.Address", 0); } if (user_accepted_suggestion) { histogram_tester.ExpectBucketCount( @@ -10172,6 +10730,7 @@ void AutofillMetricsKeyMetricsTest::SetUp() { RecreateProfile(/*is_server=*/false); // Load a fillable form. + form_.unique_renderer_id = MakeFormRendererId(); form_.name = ASCIIToUTF16("TestForm"); form_.url = GURL("http://example.com/form.html"); form_.action = GURL("http://example.com/submit.html"); @@ -10373,4 +10932,18 @@ TEST_F(AutofillMetricsKeyMetricsTest, LogUserFixesFilledDataButDoesNotSubmit) { "Autofill.KeyMetrics.FormSubmission.Autofilled.Address", 0, 1); } +TEST_F(AutofillMetricsTest, GetFieldTypeUserEditStatusMetric) { + // The id of ADDRESS_HOME_COUNTRY is 36 = 0b10'0100. + ServerFieldType server_type = ADDRESS_HOME_COUNTRY; + // The id of AUTOFILL_FIELD_WAS_NOT_EDITED is 1. + AutofillMetrics::AutofilledFieldUserEditingStatusMetric metric = + AutofillMetrics::AutofilledFieldUserEditingStatusMetric:: + AUTOFILLED_FIELD_WAS_NOT_EDITED; + + int expected_result = 0b10'0100'0001; + int actual_result = + autofill::GetFieldTypeUserEditStatusMetric(server_type, metric); + EXPECT_EQ(expected_result, actual_result); +} + } // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_provider.cc b/chromium/components/autofill/core/browser/autofill_provider.cc index 9fc3b8c0f43..8cea5630a05 100644 --- a/chromium/components/autofill/core/browser/autofill_provider.cc +++ b/chromium/components/autofill/core/browser/autofill_provider.cc @@ -19,4 +19,10 @@ void AutofillProvider::SendFormDataToRenderer(AutofillHandlerProxy* handler, requestId, AutofillDriver::FORM_DATA_ACTION_FILL, formData); } -} // namespace autofil +void AutofillProvider::RendererShouldAcceptDataListSuggestion( + AutofillHandlerProxy* handler, + const base::string16& value) { + handler->driver()->RendererShouldAcceptDataListSuggestion(value); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_provider.h b/chromium/components/autofill/core/browser/autofill_provider.h index 09f87a90ab9..6eae4082e4b 100644 --- a/chromium/components/autofill/core/browser/autofill_provider.h +++ b/chromium/components/autofill/core/browser/autofill_provider.h @@ -67,11 +67,18 @@ class AutofillProvider { const std::vector<FormData>& forms, const base::TimeTicks timestamp) = 0; + virtual void OnHidePopup(AutofillHandlerProxy* handler) = 0; + virtual void Reset(AutofillHandlerProxy* handler) = 0; void SendFormDataToRenderer(AutofillHandlerProxy* handler, int requestId, const FormData& formData); + + // Notifies the renderer should accept the datalist suggestion given by + // |value| and fill the associated input field. + void RendererShouldAcceptDataListSuggestion(AutofillHandlerProxy* handler, + const base::string16& value); }; } // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_test_utils.cc b/chromium/components/autofill/core/browser/autofill_test_utils.cc index bf871432b31..82b3b06f3b4 100644 --- a/chromium/components/autofill/core/browser/autofill_test_utils.cc +++ b/chromium/components/autofill/core/browser/autofill_test_utils.cc @@ -22,8 +22,10 @@ #include "components/autofill/core/common/autofill_clock.h" #include "components/autofill/core/common/autofill_constants.h" #include "components/autofill/core/common/autofill_prefs.h" +#include "components/autofill/core/common/autofill_util.h" #include "components/autofill/core/common/form_data.h" #include "components/autofill/core/common/form_field_data.h" +#include "components/autofill/core/common/renderer_id.h" #include "components/os_crypt/os_crypt_mocker.h" #include "components/pref_registry/pref_registry_syncable.h" #include "components/prefs/pref_service.h" @@ -50,6 +52,16 @@ std::string GetRandomCardNumber() { return value; } +FormRendererId MakeFormRendererId() { + static uint32_t counter = 10; + return FormRendererId(counter++); +} + +FieldRendererId MakeFieldRendererId() { + static uint32_t counter = 10; + return FieldRendererId(counter++); +} + } // namespace std::unique_ptr<PrefService> PrefServiceForTesting() { @@ -74,6 +86,7 @@ void CreateTestFormField(const char* label, const char* value, const char* type, FormFieldData* field) { + field->unique_renderer_id = MakeFieldRendererId(); field->label = ASCIIToUTF16(label); field->name = ASCIIToUTF16(name); field->value = ASCIIToUTF16(value); @@ -108,6 +121,27 @@ void CreateTestSelectField(const std::vector<const char*>& values, CreateTestSelectField("", "", "", values, values, values.size(), field); } +void CreateTestDatalistField(const char* label, + const char* name, + const char* value, + const std::vector<const char*>& values, + const std::vector<const char*>& labels, + FormFieldData* field) { + // Fill the base attributes. + CreateTestFormField(label, name, value, "text", field); + + std::vector<base::string16> values16(values.size()); + for (size_t i = 0; i < values.size(); ++i) + values16[i] = base::UTF8ToUTF16(values[i]); + + std::vector<base::string16> label16(labels.size()); + for (size_t i = 0; i < labels.size(); ++i) + label16[i] = base::UTF8ToUTF16(labels[i]); + + field->datalist_values = values16; + field->datalist_labels = label16; +} + void CreateTestAddressFormData(FormData* form, const char* unique_id) { std::vector<ServerFieldTypeSet> types; CreateTestAddressFormData(form, &types, unique_id); @@ -116,6 +150,7 @@ void CreateTestAddressFormData(FormData* form, const char* unique_id) { void CreateTestAddressFormData(FormData* form, std::vector<ServerFieldTypeSet>* types, const char* unique_id) { + form->unique_renderer_id = MakeFormRendererId(); form->name = ASCIIToUTF16("MyForm") + ASCIIToUTF16(unique_id ? unique_id : ""); form->button_titles = { @@ -192,6 +227,7 @@ void CreateTestAddressFormData(FormData* form, void CreateTestPersonalInformationFormData(FormData* form, const char* unique_id) { + form->unique_renderer_id = MakeFormRendererId(); form->name = ASCIIToUTF16("MyForm") + ASCIIToUTF16(unique_id ? unique_id : ""); form->url = GURL("http://myform.com/form.html"); @@ -216,6 +252,7 @@ void CreateTestCreditCardFormData(FormData* form, bool use_month_type, bool split_names, const char* unique_id) { + form->unique_renderer_id = MakeFormRendererId(); form->name = ASCIIToUTF16("MyForm") + ASCIIToUTF16(unique_id ? unique_id : ""); if (is_https) { @@ -796,5 +833,28 @@ std::string TenYearsFromNow() { return base::NumberToString(now.year + 10); } +FormAndFieldSignatures GetEncodedSignatures(const FormStructure& form) { + FormAndFieldSignatures signatures; + signatures.emplace_back(form.form_signature(), + std::vector<autofill::FieldSignature>{}); + for (const auto& field : form) { + if (form.ShouldSkipFieldVisibleForTesting(*field)) + continue; + signatures.back().second.push_back(field->GetFieldSignature()); + } + return signatures; +} + +FormAndFieldSignatures GetEncodedSignatures( + const std::vector<FormStructure*>& forms) { + FormAndFieldSignatures all_signatures; + for (const FormStructure* form : forms) { + FormAndFieldSignatures form_signatures = GetEncodedSignatures(*form); + std::move(form_signatures.begin(), form_signatures.end(), + std::back_inserter(all_signatures)); + } + return all_signatures; +} + } // namespace test } // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_test_utils.h b/chromium/components/autofill/core/browser/autofill_test_utils.h index 900e51fcdb7..f51ea475c9f 100644 --- a/chromium/components/autofill/core/browser/autofill_test_utils.h +++ b/chromium/components/autofill/core/browser/autofill_test_utils.h @@ -14,6 +14,7 @@ #include "components/autofill/core/browser/data_model/credit_card.h" #include "components/autofill/core/browser/data_model/credit_card_cloud_token_data.h" #include "components/autofill/core/browser/field_types.h" +#include "components/autofill/core/browser/form_structure.h" #include "components/autofill/core/browser/proto/api_v1.pb.h" #include "components/autofill/core/browser/proto/server.pb.h" @@ -64,6 +65,14 @@ void CreateTestSelectField(const char* label, void CreateTestSelectField(const std::vector<const char*>& values, FormFieldData* field); +// Provides a quick way to populate a datalist field. +void CreateTestDatalistField(const char* label, + const char* name, + const char* value, + const std::vector<const char*>& values, + const std::vector<const char*>& labels, + FormFieldData* field); + // Populates |form| with data corresponding to a simple address form. // Note that this actually appends fields to the form data, which can be useful // for building up more complex test forms. Another version of the function is @@ -274,6 +283,16 @@ void FillQueryField(AutofillPageQueryRequest_Form_Field* field, const char* name, const char* control_type); +// Creates the structure of signatures that would be encoded by +// FormStructure::EncodeUploadRequest() and FormStructure::EncodeQueryRequest() +// and consumed by FormStructure::ParseQueryResponse() and +// FormStructure::ParseApiQueryResponse(). +// +// Perhaps a neater way would be to move this to TestFormStructure. +FormAndFieldSignatures GetEncodedSignatures(const FormStructure& form); +FormAndFieldSignatures GetEncodedSignatures( + const std::vector<FormStructure*>& forms); + // Calls the required functions on the given external delegate to cause the // delegate to display a popup. void GenerateTestAutofillPopup( diff --git a/chromium/components/autofill/core/browser/autofill_type.cc b/chromium/components/autofill/core/browser/autofill_type.cc index 19e9cbf009f..31428cacab9 100644 --- a/chromium/components/autofill/core/browser/autofill_type.cc +++ b/chromium/components/autofill/core/browser/autofill_type.cc @@ -10,9 +10,13 @@ namespace autofill { FieldTypeGroup GroupTypeOfServerFieldType(ServerFieldType field_type) { switch (field_type) { + case NAME_HONORIFIC_PREFIX: case NAME_FIRST: case NAME_MIDDLE: case NAME_LAST: + case NAME_LAST_FIRST: + case NAME_LAST_SECOND: + case NAME_LAST_CONJUNCTION: case NAME_MIDDLE_INITIAL: case NAME_FULL: case NAME_SUFFIX: @@ -139,6 +143,7 @@ FieldTypeGroup GroupTypeOfHtmlFieldType(HtmlFieldType field_type, HtmlFieldMode field_mode) { switch (field_type) { case HTML_TYPE_NAME: + case HTML_TYPE_HONORIFIC_PREFIX: case HTML_TYPE_GIVEN_NAME: case HTML_TYPE_ADDITIONAL_NAME: case HTML_TYPE_ADDITIONAL_NAME_INITIAL: @@ -318,6 +323,9 @@ ServerFieldType AutofillType::GetStorableType() const { case HTML_TYPE_NAME: return NAME_FULL; + case HTML_TYPE_HONORIFIC_PREFIX: + return NAME_HONORIFIC_PREFIX; + case HTML_TYPE_GIVEN_NAME: return NAME_FIRST; @@ -456,6 +464,8 @@ std::string AutofillType::ToString() const { break; case HTML_TYPE_NAME: return "HTML_TYPE_NAME"; + case HTML_TYPE_HONORIFIC_PREFIX: + return "HTML_TYPE_HONORIFIC_PREFIX"; case HTML_TYPE_GIVEN_NAME: return "HTML_TYPE_GIVEN_NAME"; case HTML_TYPE_ADDITIONAL_NAME: @@ -558,12 +568,20 @@ std::string AutofillType::ServerFieldTypeToString(ServerFieldType type) { return "UNKNOWN_TYPE"; case EMPTY_TYPE: return "EMPTY_TYPE"; + case NAME_HONORIFIC_PREFIX: + return "NAME_HONORIFIC_PREFIX"; case NAME_FIRST: return "NAME_FIRST"; case NAME_MIDDLE: return "NAME_MIDDLE"; case NAME_LAST: return "NAME_LAST"; + case NAME_LAST_FIRST: + return "NAME_LAST_FIRST"; + case NAME_LAST_CONJUNCTION: + return "NAME_LAST_CONJUNCTION"; + case NAME_LAST_SECOND: + return "NAME_LAST_SECOND"; case NAME_MIDDLE_INITIAL: return "NAME_MIDDLE_INITIAL"; case NAME_FULL: diff --git a/chromium/components/autofill/core/browser/data_model/credit_card.cc b/chromium/components/autofill/core/browser/data_model/credit_card.cc index 4b42be5216b..8e2fffe9ddb 100644 --- a/chromium/components/autofill/core/browser/data_model/credit_card.cc +++ b/chromium/components/autofill/core/browser/data_model/credit_card.cc @@ -760,9 +760,16 @@ void CreditCard::SetExpirationDateFromString(const base::string16& text) { const std::pair<base::string16, base::string16> CreditCard::LabelPieces() const { base::string16 label; - // No CC number, return name only. - if (number().empty()) + if (number().empty()) { + // No CC number, if valid nickname is present, return nickname only. + // Otherwise, return cardholder name only. + if (base::FeatureList::IsEnabled( + features::kAutofillEnableCardNicknameManagement) && + HasValidNickname()) { + return std::make_pair(nickname_, base::string16()); + } return std::make_pair(name_on_card_, base::string16()); + } base::string16 obfuscated_cc_number = CardIdentifierStringForAutofillDisplay(); @@ -817,11 +824,12 @@ base::string16 CreditCard::NetworkAndLastFourDigits() const { : network + ASCIIToUTF16(" ") + obfuscated_string; } -base::string16 CreditCard::CardIdentifierStringForAutofillDisplay() const { +base::string16 CreditCard::CardIdentifierStringForAutofillDisplay( + base::string16 customized_nickname) const { if (base::FeatureList::IsEnabled( features::kAutofillEnableSurfacingServerCardNickname) && - HasValidNickname()) { - return NicknameAndLastFourDigits(); + (HasValidNickname() || !customized_nickname.empty())) { + return NicknameAndLastFourDigits(customized_nickname); } // Return a Google-specific string for Google-issued cards. if (base::FeatureList::IsEnabled(features::kAutofillEnableGoogleIssuedCard) && @@ -832,10 +840,11 @@ base::string16 CreditCard::CardIdentifierStringForAutofillDisplay() const { } base::string16 CreditCard::CardIdentifierStringAndDescriptiveExpiration( - const std::string& app_locale) const { + const std::string& app_locale, + base::string16 customized_nickname) const { return l10n_util::GetStringFUTF16( IDS_AUTOFILL_CREDIT_CARD_TWO_LINE_LABEL_FROM_NAME, - CardIdentifierStringForAutofillDisplay(), + CardIdentifierStringForAutofillDisplay(customized_nickname), GetInfo(AutofillType(CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR), app_locale)); } @@ -897,13 +906,17 @@ bool CreditCard::HasValidNickname() const { return false; } // Must not contain digits. - for (char c : nickname_) { + for (base::char16 c : nickname_) { if (base::IsAsciiDigit(c)) return false; } return true; } +base::string16 CreditCard::NicknameAndLastFourDigitsForTesting() const { + return NicknameAndLastFourDigits(); +} + base::string16 CreditCard::Expiration2DigitYearAsString() const { return data_util::Expiration2DigitYearAsString(expiration_year_); } @@ -954,15 +967,17 @@ base::string16 CreditCard::NetworkForFill() const { return ::autofill::NetworkForFill(network_); } -base::string16 CreditCard::NicknameAndLastFourDigits() const { +base::string16 CreditCard::NicknameAndLastFourDigits( + base::string16 customized_nickname) const { // Should call HasValidNickname() to check valid nickname before calling this. - DCHECK(HasValidNickname()); + DCHECK(HasValidNickname() || !customized_nickname.empty()); const base::string16 digits = LastFourDigits(); // If digits are empty, return nickname. if (digits.empty()) - return nickname_; + return customized_nickname.empty() ? nickname_ : customized_nickname; - return nickname_ + ASCIIToUTF16(" ") + + return (customized_nickname.empty() ? nickname_ : customized_nickname) + + ASCIIToUTF16(" ") + internal::GetObfuscatedStringForCardDigits(digits); } diff --git a/chromium/components/autofill/core/browser/data_model/credit_card.h b/chromium/components/autofill/core/browser/data_model/credit_card.h index 555dc938a99..e74ced42b14 100644 --- a/chromium/components/autofill/core/browser/data_model/credit_card.h +++ b/chromium/components/autofill/core/browser/data_model/credit_card.h @@ -260,14 +260,16 @@ class CreditCard : public AutofillDataModel { // available and valid; otherwise, formatted as 'IssuerNetwork - ****2345'. // Google-issued cards have their own specific identifier, instead of // displaying the issuer network name. - base::string16 CardIdentifierStringForAutofillDisplay() const; + base::string16 CardIdentifierStringForAutofillDisplay( + base::string16 customized_nickname = base::string16()) const; // A label for this card formatted as 'Nickname - ****2345, expires on MM/YY' // if nickname experiment is turned on and nickname is available; otherwise, // formatted as 'IssuerNetwork - ****2345, expires on MM/YY'. // This label is used as a second line label when the cardholder // name/expiration date field is selected. base::string16 CardIdentifierStringAndDescriptiveExpiration( - const std::string& app_locale) const; + const std::string& app_locale, + base::string16 customized_nickname = base::string16()) const; // A label for this card formatted as 'Expires on MM/YY'. // This label is used as a second line label when the autofill dropdown // uses a two line layout and the credit card number is selected. @@ -292,10 +294,12 @@ class CreditCard : public AutofillDataModel { // Returns whether the card has a valid nickname. bool HasValidNickname() const; + // Should be used ONLY by tests. + base::string16 NicknameAndLastFourDigitsForTesting() const; + private: FRIEND_TEST_ALL_PREFIXES(CreditCardTest, SetExpirationDateFromString); FRIEND_TEST_ALL_PREFIXES(CreditCardTest, SetExpirationYearFromString); - FRIEND_TEST_ALL_PREFIXES(CreditCardTest, NicknameAndLastFourDigitsStrings); base::string16 Expiration2DigitYearAsString() const; @@ -312,7 +316,8 @@ class CreditCard : public AutofillDataModel { // A label for this card formatted as 'Nickname - ****2345'. Always call // HasValidNickname() before calling this. - base::string16 NicknameAndLastFourDigits() const; + base::string16 NicknameAndLastFourDigits( + base::string16 customized_nickname = base::string16()) const; // Sets the name_on_card_ value based on the saved name parts. void SetNameOnCardFromSeparateParts(); diff --git a/chromium/components/autofill/core/browser/data_model/credit_card_unittest.cc b/chromium/components/autofill/core/browser/data_model/credit_card_unittest.cc index 01c06a494c8..56a5c11ec45 100644 --- a/chromium/components/autofill/core/browser/data_model/credit_card_unittest.cc +++ b/chromium/components/autofill/core/browser/data_model/credit_card_unittest.cc @@ -61,8 +61,11 @@ const char* const kInvalidNumbers[] = { }; const char* const kValidNicknames[] = { - "Grocery Card", "Two percent Cashback", + "Grocery Card", + "Two percent Cashback", "Mastercard \xF0\x9F\x92\xB3", /* Nickname with UTF-8 hex encoded emoji */ + "\u0634\u063a\u0645\u0688", /* arbitrary Arabic script in unicode */ + "\u0434\u0444\u0431\u044A", /* arbitrary Cyrillic script in unicode */ }; const char* const kInvalidNicknames[] = { @@ -98,9 +101,11 @@ TEST(CreditCardTest, GetObfuscatedStringForCardDigits) { TEST(CreditCardTest, PreviewSummaryAndNetworkAndLastFourDigitsStrings) { base::test::ScopedFeatureList scoped_feature_list; base::string16 valid_nickname = ASCIIToUTF16("My Visa Card"); - // Enable the flag. - scoped_feature_list.InitAndEnableFeature( - features::kAutofillEnableSurfacingServerCardNickname); + // Enable the flags. + scoped_feature_list.InitWithFeatures( + /*enable_features=*/{features::kAutofillEnableSurfacingServerCardNickname, + features::kAutofillEnableCardNicknameManagement}, + /*disable_features=*/{}); // Case 0: empty credit card. CreditCard credit_card0(base::GenerateGUID(), "https://www.example.com/"); @@ -126,6 +131,16 @@ TEST(CreditCardTest, PreviewSummaryAndNetworkAndLastFourDigitsStrings) { base::string16 obfuscated1 = credit_card1.NetworkAndLastFourDigits(); EXPECT_EQ(ASCIIToUTF16(std::string("Card")), obfuscated1); + // Case 1.1: No credit card number, but has nickname. + CreditCard credit_card11(base::GenerateGUID(), "https://www.example.com/"); + test::SetCreditCardInfo(&credit_card11, "John Dillinger", "", "01", "2010", + "1"); + credit_card11.SetNickname(valid_nickname); + base::string16 summary11 = credit_card11.Label(); + EXPECT_EQ(valid_nickname, summary11); + base::string16 obfuscated11 = credit_card11.NetworkAndLastFourDigits(); + EXPECT_EQ(ASCIIToUTF16(std::string("Card")), obfuscated11); + // Case 2: No month. CreditCard credit_card2(base::GenerateGUID(), "https://www.example.com/"); test::SetCreditCardInfo(&credit_card2, "John Dillinger", @@ -193,10 +208,12 @@ TEST(CreditCardTest, PreviewSummaryAndNetworkAndLastFourDigitsStrings) { summary6); // Case 7: Have everything including nickname but flag is off. - // Reset and disable the feature flag. + // Reset and disable the nickname feature flags. scoped_feature_list.Reset(); - scoped_feature_list.InitAndDisableFeature( - features::kAutofillEnableSurfacingServerCardNickname); + scoped_feature_list.InitWithFeatures( + /*enable_features=*/{}, /*disable_features=*/{ + features::kAutofillEnableSurfacingServerCardNickname, + features::kAutofillEnableCardNicknameManagement}); CreditCard credit_card7(base::GenerateGUID(), "https://www.example.com/"); test::SetCreditCardInfo(&credit_card7, "John Dillinger", "5105 1051 0510 5100" /* Mastercard */, "01", "2010", @@ -206,6 +223,16 @@ TEST(CreditCardTest, PreviewSummaryAndNetworkAndLastFourDigitsStrings) { EXPECT_EQ(UTF8ToUTF16(std::string("Mastercard ") + test::ObfuscatedCardDigitsAsUTF8("5100") + ", 01/2010"), summary7); + + // Case 8: No credit card number, has valid nickname, but flag is off. + CreditCard credit_card8(base::GenerateGUID(), "https://www.example.com/"); + test::SetCreditCardInfo(&credit_card8, "John Dillinger", "", "01", "2010", + "1"); + credit_card8.SetNickname(valid_nickname); + base::string16 summary8 = credit_card11.Label(); + EXPECT_EQ(base::string16(ASCIIToUTF16("John Dillinger")), summary8); + base::string16 obfuscated8 = credit_card8.NetworkAndLastFourDigits(); + EXPECT_EQ(ASCIIToUTF16(std::string("Card")), obfuscated8); } TEST(CreditCardTest, NicknameAndLastFourDigitsStrings) { @@ -216,7 +243,7 @@ TEST(CreditCardTest, NicknameAndLastFourDigitsStrings) { test::SetCreditCardInfo(&credit_card1, "John Dillinger", "", "01", "2020", "1"); credit_card1.SetNickname(valid_nickname); - EXPECT_EQ(valid_nickname, credit_card1.NicknameAndLastFourDigits()); + EXPECT_EQ(valid_nickname, credit_card1.NicknameAndLastFourDigitsForTesting()); // Case 2: Have everything. CreditCard credit_card2(base::GenerateGUID(), "https://www.example.com/"); @@ -226,7 +253,7 @@ TEST(CreditCardTest, NicknameAndLastFourDigitsStrings) { EXPECT_EQ( valid_nickname + UTF8ToUTF16(std::string(" ") + test::ObfuscatedCardDigitsAsUTF8("5100")), - credit_card2.NicknameAndLastFourDigits()); + credit_card2.NicknameAndLastFourDigitsForTesting()); } TEST(CreditCardTest, CardIdentifierStringsForAutofillDisplay) { diff --git a/chromium/components/autofill/core/browser/data_model/data_model_utils.cc b/chromium/components/autofill/core/browser/data_model/data_model_utils.cc index 67391ee4b1f..518d6a7949d 100644 --- a/chromium/components/autofill/core/browser/data_model/data_model_utils.cc +++ b/chromium/components/autofill/core/browser/data_model/data_model_utils.cc @@ -12,6 +12,8 @@ #include "base/strings/utf_string_conversions.h" #include "base/time/time.h" #include "components/autofill/core/common/autofill_clock.h" +#include "components/autofill/core/common/autofill_regex_constants.h" +#include "components/autofill/core/common/autofill_regexes.h" #include "third_party/icu/source/common/unicode/uloc.h" #include "third_party/icu/source/i18n/unicode/dtfmtsym.h" @@ -141,6 +143,18 @@ bool SetExpirationYear(int value, int* expiration_year) { return true; } +base::string16 FindPossiblePhoneCountryCode(const base::string16& text) { + base::string16 candidate; + if (text.find(base::ASCIIToUTF16("00")) != base::string16::npos || + text.find('+') != base::string16::npos) { + if (MatchesPattern(text, base::ASCIIToUTF16(kAugmentedPhoneCountryCodeRe), + &candidate, 1)) + return candidate; + } + + return base::string16(); +} + } // namespace data_util } // namespace autofill diff --git a/chromium/components/autofill/core/browser/data_model/data_model_utils.h b/chromium/components/autofill/core/browser/data_model/data_model_utils.h index 59a75c3ce54..f1324537936 100644 --- a/chromium/components/autofill/core/browser/data_model/data_model_utils.h +++ b/chromium/components/autofill/core/browser/data_model/data_model_utils.h @@ -47,6 +47,10 @@ bool SetExpirationMonth(int value, int* expiration_month); // made to |*expiration_year|. bool SetExpirationYear(int value, int* expiration_year); +// Finds possible country code in |text| by fetching the first sub-group when +// matched with |kAugmentedPhoneCountryCodeRe| regex. +base::string16 FindPossiblePhoneCountryCode(const base::string16& text); + } // namespace data_util } // namespace autofill diff --git a/chromium/components/autofill/core/browser/data_model/phone_number.cc b/chromium/components/autofill/core/browser/data_model/phone_number.cc index 4327531281e..96a2b0b62f1 100644 --- a/chromium/components/autofill/core/browser/data_model/phone_number.cc +++ b/chromium/components/autofill/core/browser/data_model/phone_number.cc @@ -13,9 +13,11 @@ #include "base/strings/utf_string_conversions.h" #include "components/autofill/core/browser/autofill_type.h" #include "components/autofill/core/browser/data_model/autofill_profile.h" +#include "components/autofill/core/browser/data_model/data_model_utils.h" #include "components/autofill/core/browser/field_types.h" #include "components/autofill/core/browser/geo/autofill_country.h" #include "components/autofill/core/browser/geo/phone_number_i18n.h" +#include "components/autofill/core/common/autofill_features.h" namespace autofill { namespace { @@ -134,6 +136,18 @@ void PhoneNumber::GetMatchingTypes(const base::string16& text, matching_types->insert(PHONE_HOME_WHOLE_NUMBER); } } + + // |PHONE_HOME_COUNTRY_CODE| is added to the set of the |matching_types| when + // the digits extracted from the |stripped_text| match the |country_code|. + if (base::FeatureList::IsEnabled( + features::kAutofillEnableAugmentedPhoneCountryCode)) { + base::string16 candidate = + data_util::FindPossiblePhoneCountryCode(stripped_text); + base::string16 country_code = + GetInfo(AutofillType(PHONE_HOME_COUNTRY_CODE), app_locale); + if (candidate.size() > 0 && candidate == country_code) + matching_types->insert(PHONE_HOME_COUNTRY_CODE); + } } // Normalize phones if |type| is a whole number: diff --git a/chromium/components/autofill/core/browser/data_model/phone_number_unittest.cc b/chromium/components/autofill/core/browser/data_model/phone_number_unittest.cc index 29456dcf536..2a28625d1d9 100644 --- a/chromium/components/autofill/core/browser/data_model/phone_number_unittest.cc +++ b/chromium/components/autofill/core/browser/data_model/phone_number_unittest.cc @@ -6,10 +6,12 @@ #include "base/strings/string16.h" #include "base/strings/utf_string_conversions.h" +#include "base/test/scoped_feature_list.h" #include "components/autofill/core/browser/autofill_type.h" #include "components/autofill/core/browser/data_model/autofill_profile.h" #include "components/autofill/core/browser/field_types.h" #include "components/autofill/core/browser/geo/phone_number_i18n.h" +#include "components/autofill/core/common/autofill_features.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -289,4 +291,78 @@ TEST(PhoneNumberTest, InternationalPhoneHomeCityAndNumber_DE) { phone_number.GetInfo(PHONE_HOME_CITY_AND_NUMBER, "en-US")); } +// Tests whether the |PHONE_HOME_COUNTRY_CODE| is added to the set of matching +// types. +TEST(PhoneNumberTest, CountryCodeInMatchingTypes) { + base::test::ScopedFeatureList enabled; + enabled.InitAndEnableFeature( + features::kAutofillEnableAugmentedPhoneCountryCode); + + AutofillProfile profile; + profile.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("US")); + // Set the phone number such that country_code == 1, city_code = 650, + // number = 2345678. + base::string16 phone(ASCIIToUTF16("1 [650] 234-5678")); + PhoneNumber phone_number(&profile); + phone_number.SetInfo(AutofillType(PHONE_HOME_WHOLE_NUMBER), phone, "US"); + + std::vector<const char*> test_cases = {"+1", "1", "(+1) United States", + "US (+1)"}; + + for (size_t i = 0; i < test_cases.size(); i++) { + SCOPED_TRACE(testing::Message() << "i(US) = " << i); + + ServerFieldTypeSet matching_types; + phone_number.GetMatchingTypes(ASCIIToUTF16(test_cases[i]), "US", + &matching_types); + + EXPECT_THAT(matching_types, testing::ElementsAre(PHONE_HOME_COUNTRY_CODE)); + } + + profile.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("DE")); + base::string16 de_phone(ASCIIToUTF16("+49 0151 6679586")); + PhoneNumber phone_number_de(&profile); + phone_number_de.SetInfo(AutofillType(PHONE_HOME_WHOLE_NUMBER), de_phone, + "DE"); + + test_cases = {"49", "+49", "(+49) DE", "(0049) DE", "0049"}; + for (size_t i = 0; i < test_cases.size(); i++) { + SCOPED_TRACE(testing::Message() << "i(DE) = " << i); + + ServerFieldTypeSet matching_types; + phone_number_de.GetMatchingTypes(ASCIIToUTF16(test_cases[i]), "DE", + &matching_types); + + EXPECT_THAT(matching_types, testing::ElementsAre(PHONE_HOME_COUNTRY_CODE)); + } +} + +// Tests that the |PHONE_HOME_COUNTRY_CODE| should not be added to the set of +// matching types. +TEST(PhoneNumberTest, CountryCodeNotInMatchingTypes) { + base::test::ScopedFeatureList enabled; + enabled.InitAndEnableFeature( + features::kAutofillEnableAugmentedPhoneCountryCode); + + AutofillProfile profile; + profile.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("US")); + // Set phone number so country_code == 1, city_code = 650, number = 2345678. + base::string16 phone(ASCIIToUTF16("1 [650] 234-5678")); + PhoneNumber phone_number(&profile); + phone_number.SetInfo(AutofillType(PHONE_HOME_WHOLE_NUMBER), phone, "US"); + + std::vector<const char*> test_cases = { + "01", "+16502", "11", "211", "0001", "++1", "+1abc2", "001abc2", "01"}; + + for (size_t i = 0; i < test_cases.size(); i++) { + SCOPED_TRACE(testing::Message() << "i = " << i); + + ServerFieldTypeSet matching_types; + phone_number.GetMatchingTypes(ASCIIToUTF16(test_cases[i]), "US", + &matching_types); + + EXPECT_THAT(matching_types, testing::IsEmpty()); + } +} + } // namespace autofill diff --git a/chromium/components/autofill/core/browser/data_model/test_data_creator.cc b/chromium/components/autofill/core/browser/data_model/test_data_creator.cc index 41bdcd75669..edb610199dc 100644 --- a/chromium/components/autofill/core/browser/data_model/test_data_creator.cc +++ b/chromium/components/autofill/core/browser/data_model/test_data_creator.cc @@ -6,6 +6,7 @@ #include <inttypes.h> +#include "base/logging.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "components/autofill/core/browser/data_model/autofill_profile.h" diff --git a/chromium/components/autofill/core/browser/field_filler.cc b/chromium/components/autofill/core/browser/field_filler.cc index 34766959c5e..16ada9dbb6c 100644 --- a/chromium/components/autofill/core/browser/field_filler.cc +++ b/chromium/components/autofill/core/browser/field_filler.cc @@ -705,6 +705,36 @@ base::string16 RemoveWhitespace(const base::string16& value) { return stripped_value; } +// Finds the best suitable option in the |field| that corresponds to the +// |country_code|. +// If the exact match is not found, extracts the digits (ignoring leading '00' +// or '+') from each option and compares them with the |country_code|. +void FillPhoneCountryCodeSelectControl(const base::string16& country_code, + FormFieldData* field, + std::string* failure_to_fill) { + if (country_code.empty()) + return; + + // Find the option that exactly matches the |country_code|. + if (SetSelectControlValue(country_code, field, /*best_match_index=*/nullptr, + failure_to_fill)) + return; + + for (size_t i = 0; i < field->option_contents.size(); i++) { + base::string16 cc_candidate_in_value = + data_util::FindPossiblePhoneCountryCode( + RemoveWhitespace(field->option_values[i])); + base::string16 cc_candidate_in_content = + data_util::FindPossiblePhoneCountryCode( + RemoveWhitespace(field->option_contents[i])); + if (cc_candidate_in_value == country_code || + cc_candidate_in_content == country_code) { + field->value = field->option_values[i]; + return; + } + } +} + } // namespace FieldFiller::FieldFiller(const std::string& app_locale, @@ -748,7 +778,16 @@ bool FieldFiller::FillFormField(const AutofillField& field, } if (type.group() == PHONE_HOME) { - FillPhoneNumberField(field, value, field_data); + // If the |field_data| is a selection box and having the type + // |PHONE_HOME_COUNTRY_CODE|, call |FillPhoneCountryCodeSelectControl|. + if (base::FeatureList::IsEnabled( + features::kAutofillEnableAugmentedPhoneCountryCode) && + field_data->form_control_type == "select-one" && + type.GetStorableType() == PHONE_HOME_COUNTRY_CODE) { + FillPhoneCountryCodeSelectControl(value, field_data, failure_to_fill); + } else { + FillPhoneNumberField(field, value, field_data); + } return true; } if (field_data->form_control_type == "select-one") { diff --git a/chromium/components/autofill/core/browser/field_filler_unittest.cc b/chromium/components/autofill/core/browser/field_filler_unittest.cc index f5c2e8f6b08..02f1c95c11f 100644 --- a/chromium/components/autofill/core/browser/field_filler_unittest.cc +++ b/chromium/components/autofill/core/browser/field_filler_unittest.cc @@ -1495,4 +1495,132 @@ INSTANTIATE_TEST_SUITE_P( FillStateTextTestCase{HTML_TYPE_ADDRESS_LEVEL1, 3, "Quebec", "", false})); +// Tests that the correct option is chosen in the selection box when one of the +// options exactly matches the phone country code. +TEST_F(AutofillFieldFillerTest, + FillSelectControlPhoneCountryCodeWithExactMatch) { + base::test::ScopedFeatureList enabled; + enabled.InitAndEnableFeature( + features::kAutofillEnableAugmentedPhoneCountryCode); + + std::vector<const char*> kPhoneCountryCode = {"91", "1", "20", "49"}; + AutofillField field; + test::CreateTestSelectField(kPhoneCountryCode, &field); + field.set_heuristic_type(PHONE_HOME_COUNTRY_CODE); + + AutofillProfile address; + address.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("+15145554578")); + FieldFiller filler(/*app_locale=*/"en-US", /*address_normalizer=*/nullptr); + filler.FillFormField(field, address, &field, /*cvc=*/base::string16()); + EXPECT_EQ(ASCIIToUTF16("1"), field.value); +} + +// Tests that the correct option is chosen in the selection box when the options +// are preceded by a plus sign and the field is of |PHONE_HOME_COUNTRY_CODE| +// type. +TEST_F(AutofillFieldFillerTest, + FillSelectControlPhoneCountryCodePrecededByPlus) { + base::test::ScopedFeatureList enabled; + enabled.InitAndEnableFeature( + features::kAutofillEnableAugmentedPhoneCountryCode); + + std::vector<const char*> kPhoneCountryCode = {"+91", "+1", "+20", "+49"}; + AutofillField field; + test::CreateTestSelectField(kPhoneCountryCode, &field); + field.set_heuristic_type(PHONE_HOME_COUNTRY_CODE); + + AutofillProfile address; + address.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("+918890888888")); + FieldFiller filler(/*app_locale=*/"en-US", /*address_normalizer=*/nullptr); + filler.FillFormField(field, address, &field, /*cvc=*/base::string16()); + EXPECT_EQ(ASCIIToUTF16("+91"), field.value); +} + +// Tests that the correct option is chosen in the selection box when the options +// are preceded by a '00' and the field is of |PHONE_HOME_COUNTRY_CODE| +// type. +TEST_F(AutofillFieldFillerTest, + FillSelectControlPhoneCountryCodePrecededByDoubleZeros) { + base::test::ScopedFeatureList enabled; + enabled.InitAndEnableFeature( + features::kAutofillEnableAugmentedPhoneCountryCode); + + std::vector<const char*> kPhoneCountryCode = {"0091", "001", "0020", "0049"}; + AutofillField field; + test::CreateTestSelectField(kPhoneCountryCode, &field); + field.set_heuristic_type(PHONE_HOME_COUNTRY_CODE); + + AutofillProfile address; + address.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("+918890888888")); + FieldFiller filler(/*app_locale=*/"en-US", /*address_normalizer=*/nullptr); + filler.FillFormField(field, address, &field, /*cvc=*/base::string16()); + EXPECT_EQ(ASCIIToUTF16("0091"), field.value); +} + +// Tests that the correct option is chosen in the selection box when the options +// are composed of the country code and the country name. +TEST_F(AutofillFieldFillerTest, FillSelectControlAugmentedPhoneCountryCode) { + base::test::ScopedFeatureList enabled; + enabled.InitAndEnableFeature( + features::kAutofillEnableAugmentedPhoneCountryCode); + + std::vector<const char*> kPhoneCountryCode = { + "Please select an option", "+91 (India)", "+1 (United States)", + "+20 (Egypt)", "+49 (Germany)"}; + AutofillField field; + test::CreateTestSelectField(kPhoneCountryCode, &field); + field.set_heuristic_type(PHONE_HOME_COUNTRY_CODE); + + AutofillProfile address; + address.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("+49151669087345")); + FieldFiller filler(/*app_locale=*/"en-US", /*address_normalizer=*/nullptr); + filler.FillFormField(field, address, &field, /*cvc=*/base::string16()); + EXPECT_EQ(ASCIIToUTF16("+49 (Germany)"), field.value); +} + +// Tests that the correct option is chosen in the selection box when the options +// are composed of the country code having whitespace and the country name. +TEST_F(AutofillFieldFillerTest, + FillSelectControlAugmentedPhoneCountryCodeWithWhiteSpaces) { + base::test::ScopedFeatureList enabled; + enabled.InitAndEnableFeature( + features::kAutofillEnableAugmentedPhoneCountryCode); + + std::vector<const char*> kPhoneCountryCode = { + "Please select an option", "(00 91) India", "(00 1) United States", + "(00 20) Egypt", "(00 49) Germany"}; + AutofillField field; + test::CreateTestSelectField(kPhoneCountryCode, &field); + field.set_heuristic_type(PHONE_HOME_COUNTRY_CODE); + + AutofillProfile address; + address.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("+49151669087345")); + FieldFiller filler(/*app_locale=*/"en-US", /*address_normalizer=*/nullptr); + filler.FillFormField(field, address, &field, /*cvc=*/base::string16()); + EXPECT_EQ(ASCIIToUTF16("(00 49) Germany"), field.value); +} + +// Tests that the correct option is chosen in the selection box when the options +// are composed of the country code that is preceded by '00' and the country +// name. +TEST_F(AutofillFieldFillerTest, + FillSelectControlAugmentedPhoneCountryCodeHavingDoubleZeros) { + base::test::ScopedFeatureList enabled; + enabled.InitAndEnableFeature( + features::kAutofillEnableAugmentedPhoneCountryCode); + + std::vector<const char*> kPhoneCountryCode = { + "Please select an option", "(0091) India", "(001) United States", + "(0020) Egypt", "(0049) Germany"}; + AutofillField field; + test::CreateTestSelectField(kPhoneCountryCode, &field); + field.set_heuristic_type(PHONE_HOME_COUNTRY_CODE); + + AutofillProfile address; + address.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("+49151669087345")); + FieldFiller filler(/*app_locale=*/"en-US", /*address_normalizer=*/nullptr); + filler.FillFormField(field, address, &field, /*cvc=*/base::string16()); + EXPECT_EQ(ASCIIToUTF16("(0049) Germany"), field.value); +} + } // namespace autofill diff --git a/chromium/components/autofill/core/browser/field_types.cc b/chromium/components/autofill/core/browser/field_types.cc index d734b327ab2..d5f7ede4a7d 100644 --- a/chromium/components/autofill/core/browser/field_types.cc +++ b/chromium/components/autofill/core/browser/field_types.cc @@ -11,9 +11,13 @@ namespace autofill { bool IsFillableFieldType(ServerFieldType field_type) { switch (field_type) { + case NAME_HONORIFIC_PREFIX: case NAME_FIRST: case NAME_MIDDLE: case NAME_LAST: + case NAME_LAST_FIRST: + case NAME_LAST_CONJUNCTION: + case NAME_LAST_SECOND: case NAME_MIDDLE_INITIAL: case NAME_FULL: case NAME_SUFFIX: diff --git a/chromium/components/autofill/core/browser/field_types.h b/chromium/components/autofill/core/browser/field_types.h index b8964f24f76..24e11f2b469 100644 --- a/chromium/components/autofill/core/browser/field_types.h +++ b/chromium/components/autofill/core/browser/field_types.h @@ -206,9 +206,17 @@ enum ServerFieldType { // Currently not used by Chrome. ADDRESS_HOME_OTHER_SUBUNIT = 106, + // Types to represent the structure of a Hispanic/Latinx last name. + NAME_LAST_FIRST = 107, + NAME_LAST_CONJUNCTION = 108, + NAME_LAST_SECOND = 109, + + // Type to catch name additions like "Mr.", "Ms." or "Dr.". + NAME_HONORIFIC_PREFIX = 110, + // No new types can be added without a corresponding change to the Autofill // server. - MAX_VALID_FIELD_TYPE = 107, + MAX_VALID_FIELD_TYPE = 111, }; // The list of all HTML autocomplete field type hints supported by Chrome. @@ -219,6 +227,7 @@ enum HtmlFieldType { // Name types. HTML_TYPE_NAME, + HTML_TYPE_HONORIFIC_PREFIX, HTML_TYPE_GIVEN_NAME, HTML_TYPE_ADDITIONAL_NAME, HTML_TYPE_FAMILY_NAME, diff --git a/chromium/components/autofill/core/browser/form_data_importer.cc b/chromium/components/autofill/core/browser/form_data_importer.cc index b243b7aa61f..1009591614b 100644 --- a/chromium/components/autofill/core/browser/form_data_importer.cc +++ b/chromium/components/autofill/core/browser/form_data_importer.cc @@ -466,6 +466,27 @@ bool FormDataImporter::ImportAddressProfiles(const FormStructure& form) { // And close the div of the section import log. import_log_buffer << CTag{"div"}; } + // TODO(crbug.com/1097125): Remove feature test. + // Run the import on the union of the section if the import was not + // successful and if there is more than one section. + if (num_saved_profiles > 0) { + AutofillMetrics::LogAddressFormImportStatustMetric( + AutofillMetrics::AddressProfileImportStatusMetric::REGULAR_IMPORT); + } else if (base::FeatureList::IsEnabled( + features::kAutofillProfileImportFromUnifiedSection) && + sections.size() > 1) { + // Try to import by combining all sections. + if (ImportAddressProfileForSection(form, "", &import_log_buffer)) { + num_saved_profiles++; + AutofillMetrics::LogAddressFormImportStatustMetric( + AutofillMetrics::AddressProfileImportStatusMetric:: + SECTION_UNION_IMPORT); + } + } + if (num_saved_profiles == 0) { + AutofillMetrics::LogAddressFormImportStatustMetric( + AutofillMetrics::AddressProfileImportStatusMetric::NO_IMPORT); + } } import_log_buffer << LogMessage::kImportAddressProfileFromFormNumberOfImports << num_saved_profiles << CTag{}; @@ -509,7 +530,8 @@ bool FormDataImporter::ImportAddressProfileForSection( // Go through each |form| field and attempt to constitute a valid profile. for (const auto& field : form) { // Reject fields that are not within the specified |section|. - if (field->section != section) + // If section is empty, use all fields. + if (field->section != section && !section.empty()) continue; base::string16 value; @@ -518,7 +540,12 @@ bool FormDataImporter::ImportAddressProfileForSection( // If we don't know the type of the field, or the user hasn't entered any // information into the field, or the field is non-focusable (hidden), then // skip it. - if (!field->IsFieldFillable() || !field->is_focusable || value.empty()) + // TODO(crbug.com/1101280): Remove |skip_unfocussable_field| + bool skip_unfocussable_field = + !field->is_focusable && + !base::FeatureList::IsEnabled( + features::kAutofillProfileImportFromUnfocusableFields); + if (!field->IsFieldFillable() || skip_unfocussable_field || value.empty()) continue; AutofillType field_type = field->Type(); @@ -679,17 +706,12 @@ bool FormDataImporter::ImportCreditCard( } } - // If editable expiration date experiment is enabled, the card with invalid - // expiration date can be uploaded. However, the card with invalid card number - // must be ignored. + // Cards with invalid expiration dates can be uploaded due to the existence of + // the expiration date fix flow. However, cards with invalid card numbers must + // still be ignored. if (!candidate_credit_card.HasValidCardNumber()) { return false; } - if (!candidate_credit_card.HasValidExpirationDate() && - !base::FeatureList::IsEnabled( - features::kAutofillUpstreamEditableExpirationDate)) { - return false; - } // Can import one valid card per form. Start by treating it as NEW_CARD, but // overwrite this type if we discover it is already a local or server card. @@ -711,6 +733,15 @@ bool FormDataImporter::ImportCreditCard( // already a local card. imported_credit_card_record_type_ = ImportedCreditCardRecordType::LOCAL_CARD; + + // If the card is a local card and it has a nickname stored in the local + // database, copy the nickname to the |candidate_credit_card| so that the + // nickname also shows in the Upstream bubble. + if (base::FeatureList::IsEnabled( + features::kAutofillEnableSurfacingServerCardNickname)) { + candidate_credit_card.SetNickname(card_copy.nickname()); + } + // If we should not return the local card, return that we merged it, // without setting |imported_credit_card|. if (!should_return_local_card) diff --git a/chromium/components/autofill/core/browser/form_data_importer.h b/chromium/components/autofill/core/browser/form_data_importer.h index d255cbd8625..38d0b12fd9a 100644 --- a/chromium/components/autofill/core/browser/form_data_importer.h +++ b/chromium/components/autofill/core/browser/form_data_importer.h @@ -114,7 +114,8 @@ class FormDataImporter { bool ImportAddressProfiles(const FormStructure& form); // Helper method for ImportAddressProfiles which only considers the fields for - // a specified |section|. + // a specified |section|. If |section| is the empty string, the import is + // performed on the union of all sections. bool ImportAddressProfileForSection(const FormStructure& form, const std::string& section, LogBuffer* import_log_buffer); diff --git a/chromium/components/autofill/core/browser/form_data_importer_unittest.cc b/chromium/components/autofill/core/browser/form_data_importer_unittest.cc index ca166dfaea8..1487f0ff2e2 100644 --- a/chromium/components/autofill/core/browser/form_data_importer_unittest.cc +++ b/chromium/components/autofill/core/browser/form_data_importer_unittest.cc @@ -306,7 +306,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles) { form.fields.push_back(field); FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/true, form_structure); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure); AutofillProfile expected(base::GenerateGUID(), test::kEmptyOrigin); test::SetProfileInfo(&expected, "George", nullptr, "Washington", @@ -319,6 +319,60 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles) { EXPECT_EQ(0, expected.Compare(*results[0])); } +TEST_F(FormDataImporterTest, ImportAddressProfileFromUnifiedSection) { + FormData form; + form.url = GURL("https://wwww.foo.com"); + + FormFieldData field; + test::CreateTestFormField("First name:", "first_name", "George", "text", + &field); + form.fields.push_back(field); + test::CreateTestFormField("Last name:", "last_name", "Washington", "text", + &field); + form.fields.push_back(field); + test::CreateTestFormField("Email:", "email", "theprez@gmail.com", "text", + &field); + form.fields.push_back(field); + test::CreateTestFormField("Address:", "address1", "21 Laussat St", "text", + &field); + form.fields.push_back(field); + test::CreateTestFormField("City:", "city", "San Francisco", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("State:", "state", "California", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("Zip:", "zip", "94102", "text", &field); + form.fields.push_back(field); + FormStructure form_structure(form); + form_structure.DetermineHeuristicTypes(); + + // Assign the address field another section than the other fields. + form_structure.field(3)->section = "another_section"; + + base::test::ScopedFeatureList scoped_feature; + scoped_feature.InitAndDisableFeature( + features::kAutofillProfileImportFromUnifiedSection); + + // Without the feature, the import is expected to fail. + ImportAddressProfiles(/*extraction_successful=*/false, form_structure); + + // After enabled the feature, the import is expected to succeed. + scoped_feature.Reset(); + scoped_feature.InitAndEnableFeature( + features::kAutofillProfileImportFromUnifiedSection); + + ImportAddressProfiles(/*extraction_successful=*/true, form_structure); + + AutofillProfile expected(base::GenerateGUID(), test::kEmptyOrigin); + test::SetProfileInfo(&expected, "George", nullptr, "Washington", + "theprez@gmail.com", nullptr, "21 Laussat St", nullptr, + "San Francisco", "California", "94102", nullptr, + nullptr); + const std::vector<AutofillProfile*>& results2 = + personal_data_manager_->GetProfiles(); + ASSERT_EQ(1U, results2.size()); + EXPECT_EQ(0, expected.Compare(*results2[0])); +} + TEST_F(FormDataImporterTest, ImportAddressProfiles_BadEmail) { FormData form; form.url = GURL("https://wwww.foo.com"); @@ -343,7 +397,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles_BadEmail) { form.fields.push_back(field); FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/false, form_structure); + ImportAddressProfiles(/*extraction_successful=*/false, form_structure); ASSERT_EQ(0U, personal_data_manager_->GetProfiles().size()); } @@ -374,7 +428,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles_TwoEmails) { form.fields.push_back(field); FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/true, form_structure); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure); ASSERT_EQ(1U, personal_data_manager_->GetProfiles().size()); } @@ -405,7 +459,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles_TwoDifferentEmails) { form.fields.push_back(field); FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/false, form_structure); + ImportAddressProfiles(/*extraction_successful=*/false, form_structure); ASSERT_EQ(0U, personal_data_manager_->GetProfiles().size()); } @@ -427,7 +481,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles_NotEnoughFilledFields) { form.fields.push_back(field); FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/false, form_structure); + ImportAddressProfiles(/*extraction_successful=*/false, form_structure); ASSERT_EQ(0U, personal_data_manager_->GetProfiles().size()); ASSERT_EQ(0U, personal_data_manager_->GetCreditCards().size()); @@ -455,7 +509,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles_MinimumAddressUSA) { form.fields.push_back(field); FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/true, form_structure); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure); ASSERT_EQ(1U, personal_data_manager_->GetProfiles().size()); } @@ -482,7 +536,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles_MinimumAddressGB) { form.fields.push_back(field); FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/true, form_structure); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure); ASSERT_EQ(1U, personal_data_manager_->GetProfiles().size()); } @@ -504,7 +558,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles_MinimumAddressGI) { form.fields.push_back(field); FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/true, form_structure); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure); ASSERT_EQ(1U, personal_data_manager_->GetProfiles().size()); } @@ -544,7 +598,7 @@ TEST_F(FormDataImporterTest, form.fields.push_back(field); FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/true, form_structure); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure); AutofillProfile expected(base::GenerateGUID(), test::kEmptyOrigin); test::SetProfileInfo(&expected, "George", nullptr, "Washington", nullptr, @@ -556,6 +610,64 @@ TEST_F(FormDataImporterTest, EXPECT_EQ(0, expected.Compare(*results[0])); } +TEST_F(FormDataImporterTest, ImportAddressProfiles_UnFocussableFields) { + FormData form; + form.url = GURL("https://wwww.foo.com"); + + FormFieldData field; + + test::CreateTestFormField("First name:", "first_name", "George", "text", + &field); + form.fields.push_back(field); + + test::CreateTestFormField("Last name:", "last_name", "Washington", "text", + &field); + form.fields.push_back(field); + + test::CreateTestFormField("Phone:", "phone", "1234554321", "text", &field); + form.fields.push_back(field); + + test::CreateTestFormField("Address:", "address1", "21 Laussat St", "text", + &field); + form.fields.push_back(field); + + test::CreateTestFormField("City:", "city", "San Francisco", "text", &field); + // Set this field to be unfocusable. + field.is_focusable = false; + form.fields.push_back(field); + + test::CreateTestFormField("State:", "state", "California", "text", &field); + form.fields.push_back(field); + + test::CreateTestFormField("Zip:", "zip", "94102", "text", &field); + form.fields.push_back(field); + + FormStructure form_structure(form); + form_structure.DetermineHeuristicTypes(); + + // Verify the status quo that the form is not imported with the unfocusable + // fields. + // TODO(crbug.com/1101280): Remove once feature is launched. + scoped_feature_list_.InitAndDisableFeature( + features::kAutofillProfileImportFromUnfocusableFields); + ImportAddressProfiles(/*extraction_successful=*/false, form_structure); + + // Activate the feature and test again. + scoped_feature_list_.Reset(); + scoped_feature_list_.InitAndEnableFeature( + features::kAutofillProfileImportFromUnfocusableFields); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure); + + AutofillProfile expected(base::GenerateGUID(), test::kEmptyOrigin); + test::SetProfileInfo(&expected, "George", nullptr, "Washington", nullptr, + nullptr, "21 Laussat St", nullptr, "San Francisco", + "California", "94102", nullptr, "1234554321"); + const std::vector<AutofillProfile*>& results = + personal_data_manager_->GetProfiles(); + ASSERT_EQ(1U, results.size()); + EXPECT_EQ(0, expected.Compare(*results[0])); +} + TEST_F(FormDataImporterTest, ImportAddressProfiles_MultilineAddress) { FormData form; form.url = GURL("https://wwww.foo.com"); @@ -583,7 +695,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles_MultilineAddress) { form.fields.push_back(field); FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/true, form_structure); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure); AutofillProfile expected(base::GenerateGUID(), test::kEmptyOrigin); test::SetProfileInfo(&expected, "George", nullptr, "Washington", @@ -623,7 +735,7 @@ TEST_F(FormDataImporterTest, FormStructure form_structure1(form1); form_structure1.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/true, form_structure1); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure1); AutofillProfile expected(base::GenerateGUID(), test::kEmptyOrigin); test::SetProfileInfo(&expected, "George", nullptr, "Washington", @@ -659,7 +771,7 @@ TEST_F(FormDataImporterTest, FormStructure form_structure2(form2); form_structure2.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/true, form_structure2); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure2); AutofillProfile expected2(base::GenerateGUID(), test::kEmptyOrigin); test::SetProfileInfo(&expected2, "John", nullptr, "Adams", "second@gmail.com", @@ -716,7 +828,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles_TwoValidProfilesSameForm) { FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/true, form_structure); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure); AutofillProfile expected(base::GenerateGUID(), test::kEmptyOrigin); test::SetProfileInfo(&expected, "George", nullptr, "Washington", @@ -790,7 +902,7 @@ TEST_F(FormDataImporterTest, // Still able to do the import. FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/true, form_structure); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure); AutofillProfile expected(base::GenerateGUID(), test::kEmptyOrigin); test::SetProfileInfo(&expected, "George", nullptr, "Washington", @@ -872,7 +984,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles_ThreeValidProfilesSameForm) { FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/true, form_structure); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure); // Only two are saved. AutofillProfile expected(base::GenerateGUID(), test::kEmptyOrigin); @@ -926,7 +1038,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles_SameProfileWithConflict) { FormStructure form_structure1(form1); form_structure1.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/true, form_structure1); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure1); AutofillProfile expected(base::GenerateGUID(), test::kEmptyOrigin); test::SetProfileInfo(&expected, "George", nullptr, "Washington", @@ -972,7 +1084,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles_SameProfileWithConflict) { FormStructure form_structure2(form2); form_structure2.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/true, form_structure2); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure2); const std::vector<AutofillProfile*>& results2 = personal_data_manager_->GetProfiles(); @@ -1009,7 +1121,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles_MissingInfoInOld) { FormStructure form_structure1(form1); form_structure1.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/true, form_structure1); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure1); AutofillProfile expected(base::GenerateGUID(), test::kEmptyOrigin); test::SetProfileInfo(&expected, "George", nullptr, "Washington", nullptr, @@ -1045,7 +1157,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles_MissingInfoInOld) { FormStructure form_structure2(form2); form_structure2.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/true, form_structure2); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure2); const std::vector<AutofillProfile*>& results2 = personal_data_manager_->GetProfiles(); @@ -1089,7 +1201,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles_MissingInfoInNew) { FormStructure form_structure1(form1); form_structure1.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/true, form_structure1); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure1); AutofillProfile expected(base::GenerateGUID(), test::kEmptyOrigin); test::SetProfileInfo(&expected, "George", nullptr, "Washington", @@ -1127,7 +1239,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles_MissingInfoInNew) { FormStructure form_structure2(form2); form_structure2.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/true, form_structure2); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure2); const std::vector<AutofillProfile*>& results2 = personal_data_manager_->GetProfiles(); @@ -1163,7 +1275,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles_InsufficientAddress) { FormStructure form_structure1(form1); form_structure1.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/false, form_structure1); + ImportAddressProfiles(/*extraction_successful=*/false, form_structure1); // Since no refresh is expected, reload the data from the database to make // sure no changes were written out. @@ -1219,7 +1331,7 @@ TEST_F(FormDataImporterTest, FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/true, form_structure); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure); // Expect that no new profile is saved. const std::vector<AutofillProfile*>& results = @@ -1237,7 +1349,7 @@ TEST_F(FormDataImporterTest, FormStructure form_structure2(form); form_structure2.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/true, form_structure2); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure2); // Expect that no new profile is saved. const std::vector<AutofillProfile*>& results2 = @@ -1276,7 +1388,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles_UnrecognizedCountry) { FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/false, form_structure); + ImportAddressProfiles(/*extraction_successful=*/false, form_structure); // Since no refresh is expected, reload the data from the database to make // sure no changes were written out. @@ -1320,7 +1432,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles_LocalizedCountryName) { // the page language is not set. FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/false, form_structure); + ImportAddressProfiles(/*extraction_successful=*/false, form_structure); ASSERT_EQ(0U, personal_data_manager_->GetProfiles().size()); ASSERT_EQ(0U, personal_data_manager_->GetCreditCards().size()); @@ -1334,7 +1446,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles_LocalizedCountryName) { // enabled. scoped_feature_list_.InitAndDisableFeature( features::kAutofillUsePageLanguageToTranslateCountryNames); - ImportAddressProfiles(/*extraction_success=*/false, form_structure); + ImportAddressProfiles(/*extraction_successful=*/false, form_structure); // There should be no imported address profile. ASSERT_EQ(0U, personal_data_manager_->GetProfiles().size()); @@ -1345,7 +1457,7 @@ TEST_F(FormDataImporterTest, ImportAddressProfiles_LocalizedCountryName) { scoped_feature_list_.Reset(); scoped_feature_list_.InitAndEnableFeature( features::kAutofillUsePageLanguageToTranslateCountryNames); - ImportAddressProfiles(/*extraction_success=*/true, form_structure); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure); // There should be one imported address profile. ASSERT_EQ(1U, personal_data_manager_->GetProfiles().size()); @@ -1392,7 +1504,7 @@ TEST_F(FormDataImporterTest, FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/true, form_structure); + ImportAddressProfiles(/*extraction_successful=*/true, form_structure); AutofillProfile expected(base::GenerateGUID(), test::kEmptyOrigin); test::SetProfileInfo(&expected, "George", nullptr, "Washington", @@ -1439,7 +1551,7 @@ TEST_F(FormDataImporterTest, FormStructure form_structure(form); form_structure.DetermineHeuristicTypes(); - ImportAddressProfiles(/*extraction_success=*/false, form_structure); + ImportAddressProfiles(/*extraction_successful=*/false, form_structure); // Since no refresh is expected, reload the data from the database to make // sure no changes were written out. @@ -1507,39 +1619,10 @@ TEST_F(FormDataImporterTest, ImportCreditCard_InvalidCardNumber) { ASSERT_EQ(0U, personal_data_manager_->GetCreditCards().size()); } -// Tests that an invalid credit card expiration is not extracted when the -// expiration date fix flow experiment is disabled. -TEST_F(FormDataImporterTest, ImportCreditCard_InvalidExpiryDate) { - scoped_feature_list_.InitAndDisableFeature( - features::kAutofillUpstreamEditableExpirationDate); - FormData form; - form.url = GURL("https://wwww.foo.com"); - - AddFullCreditCardForm(&form, "Smalls Biggie", "4111-1111-1111-1111", "0", - "2999"); - - FormStructure form_structure(form); - form_structure.DetermineHeuristicTypes(); - std::unique_ptr<CreditCard> imported_credit_card; - base::HistogramTester histogram_tester; - EXPECT_FALSE(ImportCreditCard(form_structure, false, &imported_credit_card)); - ASSERT_FALSE(imported_credit_card); - histogram_tester.ExpectUniqueSample("Autofill.SubmittedCardState", - AutofillMetrics::HAS_CARD_NUMBER_ONLY, 1); - - // Since no refresh is expected, reload the data from the database to make - // sure no changes were written out. - ResetPersonalDataManager(USER_MODE_NORMAL); - - ASSERT_EQ(0U, personal_data_manager_->GetCreditCards().size()); -} - -// Tests that an empty credit card expiration is extracted when editable -// expiration date experiment on. +// Tests that a credit card with an empty expiration can be extracted due to the +// expiration date fix flow. TEST_F(FormDataImporterTest, ImportCreditCard_InvalidExpiryDate_EditableExpirationExpOn) { - scoped_feature_list_.InitAndEnableFeature( - features::kAutofillUpstreamEditableExpirationDate); FormData form; form.url = GURL("https://wwww.foo.com"); @@ -1555,12 +1638,10 @@ TEST_F(FormDataImporterTest, AutofillMetrics::HAS_CARD_NUMBER_ONLY, 1); } -// Tests that an expired credit card is extracted when editable expiration date -// experiment on. +// Tests that an expired credit card can be extracted due to the expiration date +// fix flow. TEST_F(FormDataImporterTest, ImportCreditCard_ExpiredExpiryDate_EditableExpirationExpOn) { - scoped_feature_list_.InitAndEnableFeature( - features::kAutofillUpstreamEditableExpirationDate); FormData form; form.url = GURL("https://wwww.foo.com"); @@ -2458,38 +2539,7 @@ TEST_F(FormDataImporterTest, // Ensures that |imported_credit_card_record_type_| is set correctly. TEST_F( FormDataImporterTest, - ImportFormData_ImportCreditCardRecordType_NoCard_ExpiredCard_EditableExpDateOff) { - scoped_feature_list_.InitAndDisableFeature( - features::kAutofillUpstreamEditableExpirationDate); - // Simulate a form submission with an expired credit card. - FormData form; - form.url = GURL("https://wwww.foo.com"); - - AddFullCreditCardForm(&form, "Biggie Smalls", "4111 1111 1111 1111", "01", - "1999"); - - FormStructure form_structure(form); - form_structure.DetermineHeuristicTypes(); - std::unique_ptr<CreditCard> imported_credit_card; - base::Optional<std::string> imported_upi_id; - EXPECT_FALSE(form_data_importer_->ImportFormData( - form_structure, /*profile_autofill_enabled=*/true, - /*credit_card_autofill_enabled=*/true, - /*should_return_local_card=*/true, &imported_credit_card, - &imported_upi_id)); - ASSERT_FALSE(imported_credit_card); - // |imported_credit_card_record_type_| should be NO_CARD because no valid card - // was successfully imported from the form. - ASSERT_TRUE(form_data_importer_->imported_credit_card_record_type_ == - FormDataImporter::ImportedCreditCardRecordType::NO_CARD); -} - -// Ensures that |imported_credit_card_record_type_| is set correctly. -TEST_F( - FormDataImporterTest, ImportFormData_ImportCreditCardRecordType_NewCard_ExpiredCard_WithExpDateFixFlow) { - scoped_feature_list_.InitAndEnableFeature( - features::kAutofillUpstreamEditableExpirationDate); // Simulate a form submission with an expired credit card. FormData form; form.url = GURL("https://wwww.foo.com"); @@ -3110,8 +3160,6 @@ TEST_F(FormDataImporterTest, // server card and user submitted an invalid expiration date month. TEST_F(FormDataImporterTest, Metrics_SubmittedServerCardExpirationStatus_EmptyExpirationMonth) { - scoped_feature_list_.InitAndEnableFeature( - features::kAutofillUpstreamEditableExpirationDate); EnableWalletCardImport(); std::vector<CreditCard> server_cards; @@ -3160,8 +3208,6 @@ TEST_F(FormDataImporterTest, // server card and user submitted an invalid expiration date year. TEST_F(FormDataImporterTest, Metrics_SubmittedServerCardExpirationStatus_EmptyExpirationYear) { - scoped_feature_list_.InitAndEnableFeature( - features::kAutofillUpstreamEditableExpirationDate); EnableWalletCardImport(); std::vector<CreditCard> server_cards; @@ -3211,8 +3257,6 @@ TEST_F(FormDataImporterTest, TEST_F( FormDataImporterTest, Metrics_SubmittedDifferentServerCardExpirationStatus_EmptyExpirationYear) { - scoped_feature_list_.InitAndEnableFeature( - features::kAutofillUpstreamEditableExpirationDate); EnableWalletCardImport(); std::vector<CreditCard> server_cards; diff --git a/chromium/components/autofill/core/browser/form_parsing/credit_card_field.cc b/chromium/components/autofill/core/browser/form_parsing/credit_card_field.cc index 36e09498599..3495f488b98 100644 --- a/chromium/components/autofill/core/browser/form_parsing/credit_card_field.cc +++ b/chromium/components/autofill/core/browser/form_parsing/credit_card_field.cc @@ -20,6 +20,7 @@ #include "components/autofill/core/browser/field_filler.h" #include "components/autofill/core/browser/field_types.h" #include "components/autofill/core/browser/form_parsing/autofill_scanner.h" +#include "components/autofill/core/browser/form_parsing/form_field.h" #include "components/autofill/core/common/autofill_clock.h" #include "components/autofill/core/common/autofill_regex_constants.h" #include "components/autofill/core/common/autofill_regexes.h" @@ -212,7 +213,7 @@ std::unique_ptr<FormField> CreditCardField::Parse(AutofillScanner* scanner, continue; } - if (credit_card_field->ParseExpirationDate(scanner)) { + if (credit_card_field->ParseExpirationDate(scanner, log_manager)) { nb_unknown_fields = 0; continue; } @@ -308,7 +309,8 @@ bool CreditCardField::LikelyCardMonthSelectField(AutofillScanner* scanner) { } // static -bool CreditCardField::LikelyCardYearSelectField(AutofillScanner* scanner) { +bool CreditCardField::LikelyCardYearSelectField(AutofillScanner* scanner, + LogManager* log_manager) { if (scanner->IsEnd()) return false; @@ -317,18 +319,48 @@ bool CreditCardField::LikelyCardYearSelectField(AutofillScanner* scanner) { MATCH_SELECT | MATCH_SEARCH)) return false; + // Filter out days - elements for date entries would have + // numbers 1 to 9 as well in them, which we can filter on. + const base::string16 kSingleDigitDateRe = base::ASCIIToUTF16("\\b[1-9]\\b"); + for (const auto& value : field->option_contents) { + if (MatchesPattern(value, kSingleDigitDateRe)) { + return false; + } + } + + // Another way to eliminate days - filter out 'day' fields. + if (FormField::ParseFieldSpecifics(scanner, base::UTF8ToUTF16(kDayRe), + MATCH_DEFAULT | MATCH_SELECT, nullptr, + {log_manager, "kDayRe"})) { + return false; + } + + // Filter out birth years - a website would not offer 1999 as a credit card + // expiration year, but show it in the context of a birth year selector. + const base::string16 kBirthYearRe = base::ASCIIToUTF16("(1999|99)"); + for (const auto& value : field->option_contents) { + if (MatchesPattern(value, kBirthYearRe)) { + return false; + } + } + const base::Time time_now = AutofillClock::Now(); base::Time::Exploded time_exploded; time_now.UTCExplode(&time_exploded); const int kYearsToMatch = 3; - std::vector<base::string16> years_to_check; + std::vector<base::string16> years_to_check_4_digit; + std::vector<base::string16> years_to_check_2_digit; for (int year = time_exploded.year; year < time_exploded.year + kYearsToMatch; ++year) { - years_to_check.push_back(base::NumberToString16(year)); + years_to_check_4_digit.push_back(base::NumberToString16(year)); + years_to_check_2_digit.push_back(base::NumberToString16(year).substr(2)); } - return (FindConsecutiveStrings(years_to_check, field->option_values) || - FindConsecutiveStrings(years_to_check, field->option_contents)); + return ( + FindConsecutiveStrings(years_to_check_4_digit, field->option_values) || + FindConsecutiveStrings(years_to_check_4_digit, field->option_contents) || + FindConsecutiveStrings(years_to_check_2_digit, field->option_values) || + FindConsecutiveStrings(years_to_check_2_digit, field->option_contents)); } // static @@ -359,20 +391,25 @@ bool CreditCardField::IsGiftCardField(AutofillScanner* scanner, if (scanner->IsEnd()) return false; + const int kMatchFieldTypes = + MATCH_DEFAULT | MATCH_NUMBER | MATCH_TELEPHONE | MATCH_SEARCH; size_t saved_cursor = scanner->SaveCursor(); - if (ParseField(scanner, base::UTF8ToUTF16(kDebitCardRe), nullptr, - {log_manager, "kDebitCardRe"})) { + if (ParseFieldSpecifics(scanner, base::UTF8ToUTF16(kDebitCardRe), + kMatchFieldTypes, nullptr, + {log_manager, "kDebitCardRe"})) { scanner->RewindTo(saved_cursor); return false; } - if (ParseField(scanner, base::UTF8ToUTF16(kDebitGiftCardRe), nullptr, - {log_manager, "kDebitGiftCardRe"})) { + if (ParseFieldSpecifics(scanner, base::UTF8ToUTF16(kDebitGiftCardRe), + kMatchFieldTypes, nullptr, + {log_manager, "kDebitGiftCardRe"})) { scanner->RewindTo(saved_cursor); return false; } - return ParseField(scanner, base::UTF8ToUTF16(kGiftCardRe), nullptr, - {log_manager, "kGiftCardRe"}); + return ParseFieldSpecifics(scanner, base::UTF8ToUTF16(kGiftCardRe), + kMatchFieldTypes, nullptr, + {log_manager, "kGiftCardRe"}); } CreditCardField::CreditCardField(LogManager* log_manager) @@ -427,7 +464,8 @@ void CreditCardField::AddClassifications( } } -bool CreditCardField::ParseExpirationDate(AutofillScanner* scanner) { +bool CreditCardField::ParseExpirationDate(AutofillScanner* scanner, + LogManager* log_manager) { if (!expiration_date_ && base::LowerCaseEqualsASCII( scanner->Cursor()->form_control_type, "month")) { expiration_date_ = scanner->Cursor(); @@ -447,7 +485,7 @@ bool CreditCardField::ParseExpirationDate(AutofillScanner* scanner) { if (LikelyCardMonthSelectField(scanner)) { expiration_month_ = scanner->Cursor(); scanner->Advance(); - if (LikelyCardYearSelectField(scanner)) { + if (LikelyCardYearSelectField(scanner, log_manager)) { expiration_year_ = scanner->Cursor(); scanner->Advance(); return true; diff --git a/chromium/components/autofill/core/browser/form_parsing/credit_card_field.h b/chromium/components/autofill/core/browser/form_parsing/credit_card_field.h index 9db7aec100f..abd4eb9a961 100644 --- a/chromium/components/autofill/core/browser/form_parsing/credit_card_field.h +++ b/chromium/components/autofill/core/browser/form_parsing/credit_card_field.h @@ -38,8 +38,10 @@ class CreditCardField : public FormField { // Returns true if |scanner| points to a field that looks like a year // <select> for a credit card. i.e. it contains the current year and - // the next few years. - static bool LikelyCardYearSelectField(AutofillScanner* scanner); + // the next few years. |log_manager| is used to log any parsing details + // to chrome://autofill-internals + static bool LikelyCardYearSelectField(AutofillScanner* scanner, + LogManager* log_manager); // Returns true if |scanner| points to a <select> field that contains credit // card type options. @@ -54,7 +56,7 @@ class CreditCardField : public FormField { // Parses the expiration month/year/date fields. Returns true if it finds // something new. - bool ParseExpirationDate(AutofillScanner* scanner); + bool ParseExpirationDate(AutofillScanner* scanner, LogManager* log_manager); // For the combined expiration field we return |exp_year_type_|; otherwise if // |expiration_year_| is having year with |max_length| of 2-digits we return diff --git a/chromium/components/autofill/core/browser/form_parsing/credit_card_field_unittest.cc b/chromium/components/autofill/core/browser/form_parsing/credit_card_field_unittest.cc index 865e1893f21..e876c72ec64 100644 --- a/chromium/components/autofill/core/browser/form_parsing/credit_card_field_unittest.cc +++ b/chromium/components/autofill/core/browser/form_parsing/credit_card_field_unittest.cc @@ -9,9 +9,11 @@ #include "base/macros.h" #include "base/memory/ptr_util.h" +#include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" #include "components/autofill/core/browser/autofill_field.h" #include "components/autofill/core/browser/form_parsing/autofill_scanner.h" +#include "components/autofill/core/common/autofill_clock.h" #include "components/autofill/core/common/form_field_data.h" #include "testing/gtest/include/gtest/gtest.h" @@ -152,6 +154,72 @@ TEST_F(CreditCardFieldTest, ParseMiniumCreditCard) { field_candidates_map_[ASCIIToUTF16("year3")].BestHeuristicType()); } +TEST_F(CreditCardFieldTest, ParseMinimumCreditCardWithExpiryDateOptions) { + FormFieldData cc_number_field; + FormFieldData month_field; + FormFieldData year_field; + + cc_number_field.form_control_type = "text"; + cc_number_field.label = ASCIIToUTF16("Card Number"); + cc_number_field.name = ASCIIToUTF16("card_number"); + list_.push_back( + std::make_unique<AutofillField>(cc_number_field, ASCIIToUTF16("number"))); + + // For month field, set the label and name to something which won't match + // any regex, so we can test matching of the options themselves. + month_field.form_control_type = "select-one"; + month_field.label = ASCIIToUTF16("Random label"); + month_field.name = ASCIIToUTF16("Random name"); + const std::vector<std::string> kMonths{"MM", "01", "02", "03", "04", + "05", "06", "07", "08", "09", + "10", "11", "12"}; + for (auto month : kMonths) { + month_field.option_contents.push_back(base::UTF8ToUTF16(month)); + month_field.option_values.push_back(base::UTF8ToUTF16(month)); + } + list_.push_back( + std::make_unique<AutofillField>(month_field, ASCIIToUTF16("month"))); + + // For year, keep the label and name to something which doesn't match regex + // so we can test matching of the options themselves. + year_field.form_control_type = "select-one"; + year_field.label = ASCIIToUTF16("Random label"); + year_field.name = ASCIIToUTF16("Random name"); + year_field.max_length = 2; + year_field.option_contents.push_back(base::ASCIIToUTF16("YY")); + year_field.option_values.push_back(base::ASCIIToUTF16("YY")); + + const base::Time time_now = AutofillClock::Now(); + base::Time::Exploded time_exploded; + time_now.UTCExplode(&time_exploded); + const int kYearsToAdd = 10; + + for (auto year = time_exploded.year; year < time_exploded.year + kYearsToAdd; + year++) { + year_field.option_contents.push_back( + base::NumberToString16(year).substr(2)); + year_field.option_values.push_back(base::NumberToString16(year).substr(2)); + } + list_.push_back( + std::make_unique<AutofillField>(year_field, ASCIIToUTF16("year"))); + + Parse(); + ASSERT_NE(nullptr, field_.get()); + AddClassifications(); + ASSERT_TRUE(field_candidates_map_.find(ASCIIToUTF16("number")) != + field_candidates_map_.end()); + EXPECT_EQ(CREDIT_CARD_NUMBER, + field_candidates_map_[ASCIIToUTF16("number")].BestHeuristicType()); + ASSERT_TRUE(field_candidates_map_.find(ASCIIToUTF16("month")) != + field_candidates_map_.end()); + EXPECT_EQ(CREDIT_CARD_EXP_MONTH, + field_candidates_map_[ASCIIToUTF16("month")].BestHeuristicType()); + ASSERT_TRUE(field_candidates_map_.find(ASCIIToUTF16("year")) != + field_candidates_map_.end()); + EXPECT_EQ(CREDIT_CARD_EXP_2_DIGIT_YEAR, + field_candidates_map_[ASCIIToUTF16("year")].BestHeuristicType()); +} + TEST_F(CreditCardFieldTest, ParseFullCreditCard) { FormFieldData field; field.form_control_type = "text"; diff --git a/chromium/components/autofill/core/browser/form_parsing/phone_field.cc b/chromium/components/autofill/core/browser/form_parsing/phone_field.cc index a067e9ca0ba..d7079bfd751 100644 --- a/chromium/components/autofill/core/browser/form_parsing/phone_field.cc +++ b/chromium/components/autofill/core/browser/form_parsing/phone_field.cc @@ -17,11 +17,33 @@ #include "base/strings/utf_string_conversions.h" #include "components/autofill/core/browser/autofill_field.h" #include "components/autofill/core/browser/form_parsing/autofill_scanner.h" +#include "components/autofill/core/common/autofill_features.h" #include "components/autofill/core/common/autofill_regex_constants.h" +#include "components/autofill/core/common/autofill_regexes.h" namespace autofill { namespace { +// Minimum limit on the number of the options of the select field for +// determining the field to be of |PHONE_HOME_COUNTRY_CODE| type. +constexpr int kMinSelectOptionsForCountryCode = 5; + +// Maximum limit on the number of the options of the select field for +// determining the field to be of |PHONE_HOME_COUNTRY_CODE| type. +// Currently, there are approximately 250 countries that have been assigned a +// phone country code, therefore, 275 is taken as the upper bound. +constexpr int kMaxSelectOptionsForCountryCode = 275; + +// Minimum percentage of options in select field that should look like a +// country code in order to classify the field as a |PHONE_HOME_COUNTRY_CODE|. +constexpr int kMinCandidatePercentageForCountryCode = 90; + +// If a <select> element has <= |kHeuristicThresholdForCountryCode| options, +// all or all-but-one need to look like country code options. Otherwise, +// |kMinCandidatePercentageForCountryCode| is used to check for a fraction +// of country code like options. +constexpr int kHeuristicThresholdForCountryCode = 10; + // This string includes all area code separators, including NoText. std::string GetAreaRegex() { std::string area_code = kAreaCodeRe; @@ -131,6 +153,65 @@ const PhoneField::Parser PhoneField::kPhoneFieldGrammars[] = { }; // static +bool PhoneField::LikelyAugmentedPhoneCountryCode( + AutofillScanner* scanner, + AutofillField** matched_field) { + // If the experiment |kAutofillEnableAugmentedPhoneCountryCode| is not + // enabled, return false. + if (!base::FeatureList::IsEnabled( + features::kAutofillEnableAugmentedPhoneCountryCode)) + return false; + + AutofillField* field = scanner->Cursor(); + + // Return false if the field is not a selection box. + if (!MatchesFormControlType(field->form_control_type, MATCH_SELECT)) + return false; + + // If the number of the options is less than the minimum limit or more than + // the maximum limit, return false. + if (field->option_contents.size() < kMinSelectOptionsForCountryCode || + field->option_contents.size() >= kMaxSelectOptionsForCountryCode) + return false; + + // |total_covered_options| stores the count of the options that are + // compared with the regex. + int total_num_options = static_cast<int>(field->option_contents.size()); + + // |total_positive_options| stores the count of the options that match the + // regex. + int total_positive_options = 0; + + for (const auto& option : field->option_contents) { + if (MatchesPattern(option, + base::ASCIIToUTF16(kAugmentedPhoneCountryCodeRe))) + total_positive_options++; + } + + // If the number of the options compared is less or equal to + // |kHeuristicThresholdForCountryCode|, then either all the options or all + // options but one should match the regex. + if (total_num_options <= kHeuristicThresholdForCountryCode && + total_positive_options + 1 < total_num_options) + return false; + + // If the number of the options compared is more than + // |kHeuristicThresholdForCountryCode|, + // |kMinCandidatePercentageForCountryCode|% of the options should match the + // regex. + if (total_num_options > kHeuristicThresholdForCountryCode && + total_positive_options * 100 < + total_num_options * kMinCandidatePercentageForCountryCode) + return false; + + // Assign the |matched_field| and advance the cursor. + if (matched_field) + *matched_field = field; + scanner->Advance(); + return true; +} + +// static std::unique_ptr<FormField> PhoneField::Parse(AutofillScanner* scanner, LogManager* log_manager) { if (scanner->IsEnd()) @@ -149,11 +230,22 @@ std::unique_ptr<FormField> PhoneField::Parse(AutofillScanner* scanner, for (; i < base::size(kPhoneFieldGrammars) && kPhoneFieldGrammars[i].regex != REGEX_SEPARATOR; ++i) { + const bool is_country_code_field = + kPhoneFieldGrammars[i].phone_part == FIELD_COUNTRY_CODE; + + // The field length comparison with |kPhoneFieldGrammars[i].max_size| is + // not required in case of the selection boxes that are of phone country + // code type. + if (is_country_code_field && + LikelyAugmentedPhoneCountryCode(scanner, + &parsed_fields[FIELD_COUNTRY_CODE])) + continue; + if (!ParsePhoneField( scanner, GetRegExp(kPhoneFieldGrammars[i].regex), &parsed_fields[kPhoneFieldGrammars[i].phone_part], {log_manager, GetRegExpName(kPhoneFieldGrammars[i].regex)}, - (kPhoneFieldGrammars[i].phone_part == FIELD_COUNTRY_CODE))) + is_country_code_field)) break; if (kPhoneFieldGrammars[i].max_size && (!parsed_fields[kPhoneFieldGrammars[i].phone_part]->max_length || diff --git a/chromium/components/autofill/core/browser/form_parsing/phone_field.h b/chromium/components/autofill/core/browser/form_parsing/phone_field.h index b9067d7a250..96c38133568 100644 --- a/chromium/components/autofill/core/browser/form_parsing/phone_field.h +++ b/chromium/components/autofill/core/browser/form_parsing/phone_field.h @@ -102,6 +102,13 @@ class PhoneField : public FormField { const RegExLogging& logging, const bool is_country_code_field); + // Returns true if |scanner| points to a <select> field that appears to be the + // phone country code by looking at its option contents. + // "Augmented" refers to the fact that we are looking for select options that + // contain not only a country code but also further text like "Germany (+49)". + static bool LikelyAugmentedPhoneCountryCode(AutofillScanner* scanner, + AutofillField** match); + // FIELD_PHONE is always present; holds suffix if prefix is present. // The rest could be NULL. AutofillField* parsed_phone_fields_[FIELD_MAX]; diff --git a/chromium/components/autofill/core/browser/form_parsing/phone_field_unittest.cc b/chromium/components/autofill/core/browser/form_parsing/phone_field_unittest.cc index 22754b699a2..f5a2d7cb76e 100644 --- a/chromium/components/autofill/core/browser/form_parsing/phone_field_unittest.cc +++ b/chromium/components/autofill/core/browser/form_parsing/phone_field_unittest.cc @@ -13,8 +13,10 @@ #include "base/memory/ptr_util.h" #include "base/stl_util.h" #include "base/strings/utf_string_conversions.h" +#include "base/test/scoped_feature_list.h" #include "components/autofill/core/browser/autofill_field.h" #include "components/autofill/core/browser/form_parsing/autofill_scanner.h" +#include "components/autofill/core/common/autofill_features.h" #include "components/autofill/core/common/form_field_data.h" #include "testing/gtest/include/gtest/gtest.h" @@ -59,6 +61,22 @@ class PhoneFieldTest : public testing::Test { EXPECT_EQ(expected_type, it->second.BestHeuristicType()) << name; } + // Populates a select |field| with the |label|, the |name| and the |contents|. + void CreateTestSelectField(const char* label, + const char* name, + const std::vector<const char*>& contents, + FormFieldData* field) { + field->label = ASCIIToUTF16(label); + field->name = ASCIIToUTF16(name); + field->form_control_type = "select-one"; + + std::vector<base::string16> contents16; + for (auto* const element : contents) + contents16.push_back(base::UTF8ToUTF16(element)); + + field->option_contents = contents16; + } + std::vector<std::unique_ptr<AutofillField>> list_; std::unique_ptr<PhoneField> field_; FieldCandidatesMap field_candidates_map_; @@ -335,4 +353,317 @@ TEST_F(PhoneFieldTest, CountryCodeIsSelectElement) { CheckField("phoneNumber", PHONE_HOME_NUMBER); } +// Tests if the country code, city code and phone number fields are correctly +// classified by the heuristic when the phone code field is a select element +// consisting of valid options. +TEST_F(PhoneFieldTest, CountryCodeWithOptions) { + base::test::ScopedFeatureList enabled; + enabled.InitAndEnableFeature( + features::kAutofillEnableAugmentedPhoneCountryCode); + + FormFieldData field; + + // Options consisting of the country code followed by the country names. + std::vector<const char*> augmented_field_options_list = { + "(+91) India", "(+49) Germany", "(+1) United States", "(+20) Egypt", + "(+1242) Bahamas", "(+593) Ecuador", "(+7) Russia"}; + CreateTestSelectField("PC", "PC", augmented_field_options_list, &field); + list_.push_back( + std::make_unique<AutofillField>(field, ASCIIToUTF16("countryCode"))); + + field.label = ASCIIToUTF16("Phone City Code"); + field.name = ASCIIToUTF16("areacode"); + field.form_control_type = "text"; + field.max_length = 3; + list_.push_back( + std::make_unique<AutofillField>(field, ASCIIToUTF16("cityCode"))); + + field.label = ASCIIToUTF16("Phone Number"); + field.name = ASCIIToUTF16("phonenumber"); + field.max_length = 0; + list_.push_back( + std::make_unique<AutofillField>(field, ASCIIToUTF16("phoneNumber"))); + + AutofillScanner scanner(list_); + field_ = Parse(&scanner); + ASSERT_NE(nullptr, field_.get()); + field_->AddClassificationsForTesting(&field_candidates_map_); + CheckField("countryCode", PHONE_HOME_COUNTRY_CODE); + CheckField("cityCode", PHONE_HOME_CITY_CODE); + CheckField("phoneNumber", PHONE_HOME_NUMBER); +} + +// Tests if the country code field is correctly classified by the heuristic when +// the phone code is a select element and consists of valid options. +TEST_F(PhoneFieldTest, IsPhoneCountryCodeField) { + base::test::ScopedFeatureList enabled; + enabled.InitAndEnableFeature( + features::kAutofillEnableAugmentedPhoneCountryCode); + + FormFieldData field; + std::vector<std::vector<const char*>> augmented_field_options_list = { + // Options with the country name followed by the country code in brackets. + {"India(+91) ", "Germany(+49)", "United States(+1)", "Egypt(+20)", + "Bahamas(+1242)", "Ecuador(+593)", "Russia(+7)"}, + + // Options consisting of the country code totaling more than 20. + {"+91", "+49", "+1", "+20", "+1242", "+593", "+7", + "+1441", "+211", "+212", "+30", "+31", "+32", "+33", + "+34", "+51", "52", "+673", "+674", "+81", "+82"}, + + // Options consisting of the country code totaling more than 20 with an + // additional placeholder option. + {"+91", "+49", + "+1", "+20", + "+1242", "+593", + "+7", "+1441", + "+211", "+212", + "+30", "+31", + "+32", "+33", + "+34", "+51", + "52", "+673", + "+674", "+81", + "+82", "Please select an option"}, + + // Options with the country name followed by the country code in brackets + // along with a placeholder option. + {"Please select an option", "(+91) India", "(+49) Germany", + "(+1) United States", "(+20) Egypt", "(+1242) Bahamas", "(+593) Ecuador", + "(+7) Russia"}, + + // Options with the phone country code followed by the country + // abbreviation. + {"91 IN", "49 DE", "1 US", "20 E", "1242 B", "593 EQ", "7 R"}, + + // Options with the phone country code that are preceded by '00' and + // followed by the country abbreviation. + {"(0091) IN", "(0049) DE", "(001) US", "(0020) E", "(001242) B", + "(00593) EQ", "(007) R"}, + + // Options with the phone country code that are preceded by '00' and + // followed by the country abbreviation with single space in between. + {"(00 91) IN", "(00 49) DE", "(00 1) US", "(00 20) E", "(00 1242) B", + "(00 593) EQ", "(00 7) R"}, + + // Options with the phone country code preceded by '00' with multiple + // spaces in between to align them. + {"00 91", "00 49", "00 1", "00 20", "001242", "00 593", "00 7"}, + + // Options with the phone country code preceded by '00'. + {"0091", "0049", "001", "0020", "001242", "00593", "007"}}; + + for (size_t i = 0; i < augmented_field_options_list.size(); ++i) { + SCOPED_TRACE(testing::Message() << "i = " << i); + const auto& options_list = augmented_field_options_list[i]; + CreateTestSelectField("PC", "PC", options_list, &field); + list_.push_back( + std::make_unique<AutofillField>(field, ASCIIToUTF16("countryCode"))); + + field.label = ASCIIToUTF16("Phone Number"); + field.name = ASCIIToUTF16("phonenumber"); + field.max_length = 14; + field.form_control_type = "text"; + list_.push_back( + std::make_unique<AutofillField>(field, ASCIIToUTF16("phoneNumber"))); + + AutofillScanner scanner(list_); + field_ = Parse(&scanner); + ASSERT_NE(nullptr, field_.get()); + field_->AddClassificationsForTesting(&field_candidates_map_); + CheckField("countryCode", PHONE_HOME_COUNTRY_CODE); + } +} // namespace autofill + +// Tests that the month field is not classified as |PHONE_HOME_COUNTRY_CODE|. +TEST_F(PhoneFieldTest, IsMonthField) { + base::test::ScopedFeatureList enabled; + enabled.InitAndEnableFeature( + features::kAutofillEnableAugmentedPhoneCountryCode); + + FormFieldData field; + std::vector<std::vector<const char*>> augmented_field_options_list = { + // Month options in numeric. + {"01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"}, + + // Month options in numeric followed by the respective text. + {"(01) Jan", "(02) Feb", "(03) March", "(04) April", "(05) May", + "(06) June", "(07) July", "(08) August", "(09) Sept", "(10) Oct", + "(11) Nov", "(12) Dec"}}; + + for (size_t i = 0; i < augmented_field_options_list.size(); ++i) { + SCOPED_TRACE(testing::Message() << "i = " << i); + const auto& options_list = augmented_field_options_list[i]; + CreateTestSelectField("Month", "Month", options_list, &field); + list_.push_back( + std::make_unique<AutofillField>(field, ASCIIToUTF16("months"))); + + field.label = ASCIIToUTF16("Phone Number"); + field.name = ASCIIToUTF16("phonenumber"); + field.max_length = 14; + field.form_control_type = "text"; + list_.push_back( + std::make_unique<AutofillField>(field, ASCIIToUTF16("phoneNumber"))); + + AutofillScanner scanner(list_); + field_ = Parse(&scanner); + ASSERT_EQ(nullptr, field_.get()); + } +} + +// Tests that the day field is not classified as |PHONE_HOME_COUNTRY_CODE|. +TEST_F(PhoneFieldTest, IsDayField) { + base::test::ScopedFeatureList enabled; + enabled.InitAndEnableFeature( + features::kAutofillEnableAugmentedPhoneCountryCode); + + FormFieldData field; + std::vector<std::vector<const char*>> augmented_field_options_list = { + // Numeric day options. + {"01", "02", "03", "04", "05", "06", "07", "08", "09", "10", + "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", + "21", "22", "23", "24", "25", "26", "27", "28", "29", "30"}, + + // Numeric day options with a select option placeholder. + {"Please select an option", + "01", + "02", + "03", + "04", + "05", + "06", + "07", + "08", + "09", + "10", + "11", + "12", + "13", + "14", + "15", + "16", + "17", + "18", + "19", + "20", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "28", + "29", + "30"}}; + + for (size_t i = 0; i < augmented_field_options_list.size(); ++i) { + SCOPED_TRACE(testing::Message() << "i = " << i); + const auto& options_list = augmented_field_options_list[i]; + CreateTestSelectField("Field", "Field", options_list, &field); + list_.push_back( + std::make_unique<AutofillField>(field, ASCIIToUTF16("day"))); + + field.label = ASCIIToUTF16("Phone Number"); + field.name = ASCIIToUTF16("phonenumber"); + field.max_length = 14; + field.form_control_type = "text"; + list_.push_back( + std::make_unique<AutofillField>(field, ASCIIToUTF16("phoneNumber"))); + + AutofillScanner scanner(list_); + field_ = Parse(&scanner); + ASSERT_EQ(nullptr, field_.get()); + } +} + +// Tests that the field is not classified as |PHONE_HOME_COUNTRY_CODE|. +TEST_F(PhoneFieldTest, IsYearField) { + base::test::ScopedFeatureList enabled; + enabled.InitAndEnableFeature( + features::kAutofillEnableAugmentedPhoneCountryCode); + + FormFieldData field; + std::vector<std::vector<const char*>> augmented_field_options_list = { + // Numeric four digit year options. + {"1990", "1991", "1992", "1993", "1994", "1995", "1996", + "1997", "1998", "1999", "2000", "2001", "2002", "2003", + "2004", "2005", "2006", "2007", "2008", "2009", "2010"}, + + // Numeric four digit year options less than 10 in total. + {"1990", "1991", "1992", "1993", "1994"}, + + // Numeric four digit year options in decreasing order. + {"2025", "2024", "2023", "2022", "2021", "2020"}, + + // Numeric two digit year options. + {"90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "00", "01", + "02", "03", "04", "05", "06"}, + + // Numeric two digit year options along with an additional placeholder + // option. + {"Please select an option", "90", "91", "92", "93", "94", "95", "96", + "97", "98", "99", "00", "01", "02", "03", "04", "05", "06"}, + + // Numeric two digit year options along with an additional placeholder + // option less than 10 in total. + {"Please select an option", "90", "91", "92", "93", "94"}}; + + for (size_t i = 0; i < augmented_field_options_list.size(); ++i) { + SCOPED_TRACE(testing::Message() << "i = " << i); + const auto& options_list = augmented_field_options_list[i]; + CreateTestSelectField("Field", "Field", options_list, &field); + list_.push_back( + std::make_unique<AutofillField>(field, ASCIIToUTF16("year"))); + + field.label = ASCIIToUTF16("Phone Number"); + field.name = ASCIIToUTF16("phonenumber"); + field.max_length = 14; + field.form_control_type = "text"; + list_.push_back( + std::make_unique<AutofillField>(field, ASCIIToUTF16("phoneNumber"))); + + AutofillScanner scanner(list_); + field_ = Parse(&scanner); + ASSERT_EQ(nullptr, field_.get()); + } +} + +// Tests that the timezone field is not classified as |PHONE_HOME_COUNTRY_CODE|. +TEST_F(PhoneFieldTest, IsTimeZoneField) { + base::test::ScopedFeatureList enabled; + enabled.InitAndEnableFeature( + features::kAutofillEnableAugmentedPhoneCountryCode); + + FormFieldData field; + std::vector<std::vector<const char*>> augmented_field_options_list = { + // Time Zone options. + {"Yemen (UTC+03:00)", "Uruguay (UTC−03:00)", "UAE (UTC+04:00)", + "Uganda (UTC+03:00)", "Turkey (UTC+03:00)", "Taiwan (UTC+08:00)", + "Sweden (UTC+01:00)"}, + + // Time Zone options with a placeholder select element. + {"Please select an option", "Yemen (UTC+03:00)", "Uruguay (UTC−03:00)", + "UAE (UTC+04:00)", "Uganda (UTC+03:00)", "Turkey (UTC+03:00)", + "Taiwan (UTC+08:00)", "Sweden (UTC+01:00)"}}; + + for (size_t i = 0; i < augmented_field_options_list.size(); ++i) { + SCOPED_TRACE(testing::Message() << "i = " << i); + const auto& options_list = augmented_field_options_list[i]; + CreateTestSelectField("Time Zone", "TimeZone", options_list, &field); + list_.push_back( + std::make_unique<AutofillField>(field, ASCIIToUTF16("timeZone"))); + + field.label = ASCIIToUTF16("Phone Number"); + field.name = ASCIIToUTF16("phonenumber"); + field.max_length = 14; + field.form_control_type = "text"; + list_.push_back( + std::make_unique<AutofillField>(field, ASCIIToUTF16("phoneNumber"))); + + AutofillScanner scanner(list_); + field_ = Parse(&scanner); + ASSERT_EQ(nullptr, field_.get()); + } +} + } // namespace autofill diff --git a/chromium/components/autofill/core/browser/form_structure.cc b/chromium/components/autofill/core/browser/form_structure.cc index 8001381efa8..66e954488ec 100644 --- a/chromium/components/autofill/core/browser/form_structure.cc +++ b/chromium/components/autofill/core/browser/form_structure.cc @@ -61,7 +61,6 @@ namespace autofill { -using mojom::ButtonTitleType; using mojom::SubmissionIndicatorEvent; namespace { @@ -169,6 +168,9 @@ HtmlFieldType FieldTypeFromAutocompleteAttributeValue( if (autocomplete_attribute_value == "name") return HTML_TYPE_NAME; + if (autocomplete_attribute_value == "honorific-prefix") + return HTML_TYPE_HONORIFIC_PREFIX; + if (autocomplete_attribute_value == "given-name" || autocomplete_attribute_value == "given_name" || autocomplete_attribute_value == "first-name" || @@ -359,32 +361,6 @@ HtmlFieldType FieldTypeFromAutocompleteAttributeValue( return HTML_TYPE_UNRECOGNIZED; } -// Helper function for explicit conversion between |ButtonTitleType| defined in -// "autofill_types.mojom.h" and "server.proto". -AutofillUploadContents_ButtonTitle_ButtonTitleType ToServerButtonTitleType( - ButtonTitleType input) { - switch (input) { - case ButtonTitleType::NONE: - return AutofillUploadContents::ButtonTitle::NONE; - case ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE: - return AutofillUploadContents::ButtonTitle::BUTTON_ELEMENT_SUBMIT_TYPE; - case ButtonTitleType::BUTTON_ELEMENT_BUTTON_TYPE: - return AutofillUploadContents::ButtonTitle::BUTTON_ELEMENT_BUTTON_TYPE; - case ButtonTitleType::INPUT_ELEMENT_SUBMIT_TYPE: - return AutofillUploadContents::ButtonTitle::INPUT_ELEMENT_SUBMIT_TYPE; - case ButtonTitleType::INPUT_ELEMENT_BUTTON_TYPE: - return AutofillUploadContents::ButtonTitle::INPUT_ELEMENT_BUTTON_TYPE; - case ButtonTitleType::HYPERLINK: - return AutofillUploadContents::ButtonTitle::HYPERLINK; - case ButtonTitleType::DIV: - return AutofillUploadContents::ButtonTitle::DIV; - case ButtonTitleType::SPAN: - return AutofillUploadContents::ButtonTitle::SPAN; - } - NOTREACHED(); - return AutofillUploadContents::ButtonTitle::NONE; -} - std::ostream& operator<<( std::ostream& out, const autofill::AutofillQueryResponseContents& response) { @@ -394,6 +370,22 @@ std::ostream& operator<<( return out; } +std::ostream& operator<<(std::ostream& out, + const autofill::AutofillQueryResponse& response) { + for (const auto& form : response.form_suggestions()) { + out << "\nForm"; + for (const auto& field : form.field_suggestions()) { + out << "\n Field\n signature: " << field.field_signature(); + if (field.has_primary_type_prediction()) + out << "\n primary_type_prediction: " + << field.primary_type_prediction(); + for (const auto& prediction : field.predictions()) + out << "\n prediction: " << prediction.type(); + } + } + return out; +} + // Returns true iff all form fields autofill types are in |contained_types|. bool AllTypesCaptured(const FormStructure& form, const ServerFieldTypeSet& contained_types) { @@ -467,11 +459,21 @@ void PopulateRandomizedFormMetadata(const RandomizedEncoder& encoder, RandomizedEncoder::FORM_NAME, form.name_attribute(), metadata->mutable_name()); } + + for (const ButtonTitleInfo& e : form.button_titles()) { + auto* button_title = metadata->add_button_title(); + DCHECK(!e.first.empty()); + EncodeRandomizedValue(encoder, form_signature, kNullFieldSignature, + RandomizedEncoder::FORM_BUTTON_TITLES, e.first, + button_title->mutable_title()); + button_title->set_type(static_cast<ButtonTitleType>(e.second)); + } auto full_source_url = form.full_source_url().spec(); if (encoder.AnonymousUrlCollectionIsEnabled() && !full_source_url.empty()) { EncodeRandomizedValue(encoder, form_signature, kNullFieldSignature, RandomizedEncoder::FORM_URL, full_source_url, metadata->mutable_url()); + metadata->set_checksum_for_url(StrToHash32Bit(full_source_url)); } } @@ -677,8 +679,11 @@ bool FormStructure::EncodeUploadRequest( bool form_was_autofilled, const std::string& login_form_signature, bool observed_submission, - AutofillUploadContents* upload) const { + AutofillUploadContents* upload, + FormAndFieldSignatures* encoded_signatures) const { DCHECK(AllTypesCaptured(*this, available_field_types)); + encoded_signatures->clear(); + upload->set_submission(observed_submission); upload->set_client_version(kClientVersion); upload->set_form_signature(form_signature().value()); @@ -712,7 +717,7 @@ bool FormStructure::EncodeUploadRequest( for (const ButtonTitleInfo& e : button_titles_) { auto* button_title = upload->add_button_title(); button_title->set_title(base::UTF16ToUTF8(e.first)); - button_title->set_type(ToServerButtonTitleType(e.second)); + button_title->set_type(static_cast<ButtonTitleType>(e.second)); } } @@ -725,36 +730,38 @@ bool FormStructure::EncodeUploadRequest( if (IsMalformed()) return false; // Malformed form, skip it. - EncodeFormForUpload(upload); + EncodeFormForUpload(upload, encoded_signatures); return true; } // static bool FormStructure::EncodeQueryRequest( const std::vector<FormStructure*>& forms, - std::vector<std::string>* encoded_signatures, - AutofillQueryContents* query) { + AutofillQueryContents* query, + FormAndFieldSignatures* encoded_signatures) { DCHECK(encoded_signatures); encoded_signatures->clear(); encoded_signatures->reserve(forms.size()); query->set_client_version(kClientVersion); - // Some badly formatted web sites repeat forms - detect that and encode only - // one form as returned data would be the same for all the repeated forms. - std::set<std::string> processed_forms; + // If a page contains repeated forms, detect that and encode only one form as + // the returned data would be the same for all the repeated forms. + // TODO(crbug/1064709#c11): the statement is not entirely correct because + // (1) distinct forms can have identical form signatures because we truncate + // (large) numbers in the form signature calculation while these are + // considered for field signatures; (2) for dynamic forms we will hold on to + // the original form signature. + std::set<FormSignature> processed_forms; for (const auto* form : forms) { - std::string signature(form->FormSignatureAsStr()); - if (processed_forms.find(signature) != processed_forms.end()) + if (processed_forms.find(form->form_signature()) != processed_forms.end()) continue; - processed_forms.insert(signature); + processed_forms.insert(form->form_signature()); UMA_HISTOGRAM_COUNTS_1000("Autofill.FieldCount", form->field_count()); if (form->IsMalformed()) continue; - form->EncodeFormForQuery(query->add_form()); - - encoded_signatures->push_back(signature); + form->EncodeFormForQuery(query->add_form(), encoded_signatures); } return !encoded_signatures->empty(); @@ -764,6 +771,7 @@ bool FormStructure::EncodeQueryRequest( void FormStructure::ParseQueryResponse( std::string payload, const std::vector<FormStructure*>& forms, + const FormAndFieldSignatures& encoded_signatures, AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger) { AutofillMetrics::LogServerQueryMetric( AutofillMetrics::QUERY_RESPONSE_RECEIVED); @@ -775,12 +783,14 @@ void FormStructure::ParseQueryResponse( VLOG(1) << "Autofill query response was successfully parsed:\n" << response; - ProcessQueryResponse(response, forms, form_interactions_ukm_logger); + ProcessQueryResponse(response, forms, encoded_signatures, + form_interactions_ukm_logger); } void FormStructure::ParseApiQueryResponse( base::StringPiece payload, const std::vector<FormStructure*>& forms, + const FormAndFieldSignatures& encoded_signatures, AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger) { AutofillMetrics::LogServerQueryMetric( AutofillMetrics::QUERY_RESPONSE_RECEIVED); @@ -796,38 +806,56 @@ void FormStructure::ParseApiQueryResponse( if (!response.ParseFromString(decoded_payload)) return; - // TODO(vincb): Make an ostream overloaded function for this. - VLOG(1) << "Autofill query response from API was successfully parsed"; + VLOG(1) << "Autofill query response from API was successfully parsed: " + << response; ProcessQueryResponse(CreateLegacyResponseFromApiResponse(response), forms, - form_interactions_ukm_logger); + encoded_signatures, form_interactions_ukm_logger); } // static void FormStructure::ProcessQueryResponse( const AutofillQueryResponseContents& response, const std::vector<FormStructure*>& forms, + const FormAndFieldSignatures& encoded_signatures, AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger) { AutofillMetrics::LogServerQueryMetric(AutofillMetrics::QUERY_RESPONSE_PARSED); bool heuristics_detected_fillable_field = false; bool query_response_overrode_heuristics = false; + // Align the server response to the |encoded_signatures|. + auto field_types = [&response, &encoded_signatures] { + std::map<std::pair<FormSignature, FieldSignature>, + ::autofill::AutofillQueryResponseContents_Field> + field_types; + auto current_field = response.field().begin(); + for (const auto& form_and_fields : encoded_signatures) { + FormSignature form = form_and_fields.first; + for (const auto& field : form_and_fields.second) { + // In some cases *successful* response does not return all the + // fields. + if (current_field == response.field().end()) + return field_types; + field_types[std::make_pair(form, field)] = *current_field++; + } + } + return field_types; + }(); + // Copy the field types into the actual form. - auto current_field = response.field().begin(); for (FormStructure* form : forms) { bool query_response_has_no_server_data = true; for (auto& field : form->fields_) { - if (form->ShouldSkipField(*field)) + auto it = field_types.find( + std::make_pair(form->form_signature(), field->GetFieldSignature())); + if (it == field_types.end()) continue; - // In some cases *successful* response does not return all the fields. - // Quit the update of the types then. - if (current_field == response.field().end()) - break; + const auto& current_field = it->second; - ServerFieldType field_type = static_cast<ServerFieldType>( - current_field->overall_type_prediction()); + ServerFieldType field_type = + static_cast<ServerFieldType>(current_field.overall_type_prediction()); query_response_has_no_server_data &= field_type == NO_SERVER_DATA; ServerFieldType heuristic_type = field->heuristic_type(); @@ -837,23 +865,21 @@ void FormStructure::ProcessQueryResponse( field->set_server_type(field_type); std::vector<AutofillQueryResponseContents::Field::FieldPrediction> server_predictions; - if (current_field->predictions_size() == 0) { + if (current_field.predictions_size() == 0) { AutofillQueryResponseContents::Field::FieldPrediction field_prediction; field_prediction.set_type(field_type); server_predictions.push_back(field_prediction); } else { - server_predictions.assign(current_field->predictions().begin(), - current_field->predictions().end()); + server_predictions.assign(current_field.predictions().begin(), + current_field.predictions().end()); } field->set_server_predictions(std::move(server_predictions)); if (heuristic_type != field->Type().GetStorableType()) query_response_overrode_heuristics = true; - if (current_field->has_password_requirements()) - field->SetPasswordRequirements(current_field->password_requirements()); - - ++current_field; + if (current_field.has_password_requirements()) + field->SetPasswordRequirements(current_field.password_requirements()); } AutofillMetrics::LogServerResponseHasDataForForm( @@ -885,18 +911,12 @@ std::vector<FormDataPredictions> FormStructure::GetFieldTypePredictions( forms.reserve(form_structures.size()); for (const FormStructure* form_structure : form_structures) { FormDataPredictions form; - form.data.name = form_structure->form_name_; - form.data.url = form_structure->source_url_; - form.data.action = form_structure->target_url_; - form.data.main_frame_origin = form_structure->main_frame_origin_; - form.data.is_form_tag = form_structure->is_form_tag_; - form.data.is_formless_checkout = form_structure->is_formless_checkout_; + form.data = form_structure->ToFormData(); form.signature = form_structure->FormSignatureAsStr(); for (const auto& field : form_structure->fields_) { - form.data.fields.push_back(FormFieldData(*field)); - FormFieldDataPredictions annotated_field; + annotated_field.field = *field; annotated_field.signature = field->FieldSignatureAsStr(); annotated_field.heuristic_type = AutofillType(field->heuristic_type()).ToString(); @@ -1041,44 +1061,68 @@ void FormStructure::RetrieveFromCache( const FormStructure& cached_form, const bool should_keep_cached_value, const bool only_server_and_autofill_state) { - // Map from field signatures to cached fields. - std::map<base::string16, const AutofillField*> cached_fields; + // TODO(crbug/1101631) Clean up once the experiment is over. + const bool kUseRendererIds = base::FeatureList::IsEnabled( + features::kAutofillRetrieveFromCacheWithRendererIds); + std::map<base::string16, const AutofillField*> cached_fields_by_name; + std::map<FieldRendererId, const AutofillField*> cached_fields_by_id; for (size_t i = 0; i < cached_form.field_count(); ++i) { auto* const field = cached_form.field(i); - cached_fields[field->unique_name()] = field; + if (kUseRendererIds) + cached_fields_by_id[field->unique_renderer_id] = field; + else + cached_fields_by_name[field->unique_name()] = field; } for (auto& field : *this) { - const auto& cached_field = cached_fields.find(field->unique_name()); - if (cached_field != cached_fields.end()) { + const AutofillField* cached_field = nullptr; + if (kUseRendererIds) { + const auto& it = cached_fields_by_id.find(field->unique_renderer_id); + if (it != cached_fields_by_id.end()) + cached_field = it->second; + } else { + const auto& it = cached_fields_by_name.find(field->unique_name()); + if (it != cached_fields_by_name.end()) + cached_field = it->second; + } + + if (cached_field) { if (!only_server_and_autofill_state) { // Transfer attributes of the cached AutofillField to the newly created // AutofillField. - field->set_heuristic_type(cached_field->second->heuristic_type()); - field->SetHtmlType(cached_field->second->html_type(), - cached_field->second->html_mode()); - field->section = cached_field->second->section; + field->set_heuristic_type(cached_field->heuristic_type()); + field->SetHtmlType(cached_field->html_type(), + cached_field->html_mode()); + field->section = cached_field->section; field->set_only_fill_when_focused( - cached_field->second->only_fill_when_focused()); + cached_field->only_fill_when_focused()); } if (should_keep_cached_value) { - field->is_autofilled = cached_field->second->is_autofilled; + field->is_autofilled = cached_field->is_autofilled; } if (field->form_control_type != "select-one") { bool is_credit_card_field = - AutofillType(cached_field->second->Type().GetStorableType()) - .group() == CREDIT_CARD; - if (should_keep_cached_value && is_credit_card_field) { - field->value = cached_field->second->value; + AutofillType(cached_field->Type().GetStorableType()).group() == + CREDIT_CARD; + if (should_keep_cached_value && + (is_credit_card_field || + base::FeatureList::IsEnabled( + features::kAutofillKeepInitialFormValuesInCache))) { + field->value = cached_field->value; value_from_dynamic_change_form_ = true; - } else if (field->value == cached_field->second->value) { + } else if (field->value == cached_field->value && + (!base::FeatureList::IsEnabled( + features:: + kAutofillImportPrefilledCountryAndStateValues) || + (field->server_type() != ADDRESS_HOME_COUNTRY && + field->server_type() != ADDRESS_HOME_STATE))) { + // TODO(crbug.com/1100231): Remove feature check once launched. // From the perspective of learning user data, text fields containing // default values are equivalent to empty fields. field->value = base::string16(); } } - field->set_server_type(cached_field->second->server_type()); - field->set_previously_autofilled( - cached_field->second->previously_autofilled()); + field->set_server_type(cached_field->server_type()); + field->set_previously_autofilled(cached_field->previously_autofilled()); } } @@ -1155,6 +1199,15 @@ void FormStructure::LogQualityMetrics( did_autofill_some_possible_fields = true; else if (!field->only_fill_when_focused()) did_autofill_all_possible_fields = false; + + // If the form was submitted, record if field types have been filled and + // subsequently edited by the user. + if (observed_submission) { + if (field->is_autofilled || field->previously_autofilled()) { + AutofillMetrics::LogEditedAutofilledFieldAtSubmission( + form_interactions_ukm_logger, *this, *field); + } + } } AutofillMetrics::LogNumberOfEditedAutofilledFields( @@ -1390,11 +1443,16 @@ size_t FormStructure::active_field_count() const { FormData FormStructure::ToFormData() const { FormData data; + data.id_attribute = id_attribute_; + data.name_attribute = name_attribute_; data.name = form_name_; + data.button_titles = button_titles_; data.url = source_url_; data.full_url = full_source_url_; data.action = target_url_; data.main_frame_origin = main_frame_origin_; + data.is_form_tag = is_form_tag_; + data.is_formless_checkout = is_formless_checkout_; data.unique_renderer_id = unique_renderer_id_; for (size_t i = 0; i < fields_.size(); ++i) { @@ -1404,23 +1462,6 @@ FormData FormStructure::ToFormData() const { return data; } -bool FormStructure::operator==(const FormData& form) const { - // TODO(jhawkins): Is this enough to differentiate a form? - if (form_name_ == form.name && source_url_ == form.url && - target_url_ == form.action) { - return true; - } - - // TODO(jhawkins): Compare field names, IDs and labels once we have labels - // set up. - - return false; -} - -bool FormStructure::operator!=(const FormData& form) const { - return !operator==(form); -} - FormStructure::SectionedFieldsIndexes::SectionedFieldsIndexes() {} FormStructure::SectionedFieldsIndexes::~SectionedFieldsIndexes() {} @@ -1866,10 +1907,13 @@ void FormStructure::RationalizeFieldTypePredictions() { } void FormStructure::EncodeFormForQuery( - AutofillQueryContents::Form* query_form) const { + AutofillQueryContents::Form* query_form, + FormAndFieldSignatures* encoded_signatures) const { DCHECK(!IsMalformed()); query_form->set_signature(form_signature().value()); + encoded_signatures->emplace_back(); + encoded_signatures->back().first = form_signature(); if (is_rich_query_enabled_) { EncodeFormMetadataForQuery(*this, query_form->mutable_form_metadata()); @@ -1880,8 +1924,8 @@ void FormStructure::EncodeFormForQuery( continue; AutofillQueryContents::Form::Field* added_field = query_form->add_field(); - added_field->set_signature(field->GetFieldSignature().value()); + encoded_signatures->back().second.push_back(field->GetFieldSignature()); if (is_rich_query_enabled_) { EncodeFieldMetadataForQuery(*field, @@ -1897,9 +1941,14 @@ void FormStructure::EncodeFormForQuery( } } -void FormStructure::EncodeFormForUpload(AutofillUploadContents* upload) const { +void FormStructure::EncodeFormForUpload( + AutofillUploadContents* upload, + FormAndFieldSignatures* encoded_signatures) const { DCHECK(!IsMalformed()); + encoded_signatures->emplace_back(); + encoded_signatures->back().first = form_signature(); + if (randomized_encoder_) { PopulateRandomizedFormMetadata(*randomized_encoder_, *this, upload->mutable_randomized_form_metadata()); @@ -1915,6 +1964,7 @@ void FormStructure::EncodeFormForUpload(AutofillUploadContents* upload) const { continue; auto* added_field = upload->add_field(); + encoded_signatures->back().second.push_back(field->GetFieldSignature()); for (const auto& field_type : field->possible_types()) { added_field->add_autofill_type(field_type); @@ -2280,6 +2330,45 @@ void FormStructure::RationalizeTypeRelationships() { } } +std::ostream& operator<<(std::ostream& buffer, const FormStructure& form) { + buffer << "\nForm signature: " + << base::StrCat({base::NumberToString(form.form_signature().value()), + " - ", + base::NumberToString( + HashFormSignature(form.form_signature()))}); + buffer << "\n Form name: " << form.form_name(); + buffer << "\n Unique renderer Id: " << form.unique_renderer_id().value(); + buffer << "\n Target URL:" << form.target_url(); + for (size_t i = 0; i < form.field_count(); ++i) { + buffer << "\n Field " << i << ": "; + const AutofillField* field = form.field(i); + buffer << "\n Signature: " + << base::StrCat( + {base::NumberToString(field->GetFieldSignature().value()), + " - ", + base::NumberToString( + HashFieldSignature(field->GetFieldSignature()))}); + buffer << "\n Name: " << field->parseable_name(); + + auto type = field->Type().ToString(); + auto heuristic_type = AutofillType(field->heuristic_type()).ToString(); + auto server_type = AutofillType(field->server_type()).ToString(); + + buffer << "\n Type: " + << base::StrCat({type, " (heuristic: ", heuristic_type, + ", server: ", server_type, ")"}); + buffer << "\n Section: " << field->section; + + constexpr size_t kMaxLabelSize = 100; + const base::string16 truncated_label = + field->label.substr(0, std::min(field->label.length(), kMaxLabelSize)); + buffer << "\n Label: " << truncated_label; + + buffer << "\n Is empty: " << (field->IsEmpty() ? "Yes" : "No"); + } + return buffer; +} + LogBuffer& operator<<(LogBuffer& buffer, const FormStructure& form) { buffer << Tag{"div"} << Attrib{"class", "form"}; buffer << Tag{"table"}; @@ -2320,6 +2409,8 @@ LogBuffer& operator<<(LogBuffer& buffer, const FormStructure& form) { const base::string16 truncated_label = field->label.substr(0, std::min(field->label.length(), kMaxLabelSize)); buffer << Tr{} << "Label:" << truncated_label; + + buffer << Tr{} << "Is empty:" << (field->IsEmpty() ? "Yes" : "No"); buffer << CTag{"table"}; buffer << CTag{"td"}; buffer << CTag{"tr"}; diff --git a/chromium/components/autofill/core/browser/form_structure.h b/chromium/components/autofill/core/browser/form_structure.h index 9890750a9f8..7e0ceef0fe8 100644 --- a/chromium/components/autofill/core/browser/form_structure.h +++ b/chromium/components/autofill/core/browser/form_structure.h @@ -49,6 +49,11 @@ enum class PasswordAttribute { kPasswordAttributesCount }; +// The structure of forms and fields, represented by their signatures, on a +// page. These are sequence containers to reflect their order in the DOM. +using FormAndFieldSignatures = + std::vector<std::pair<FormSignature, std::vector<FieldSignature>>>; + struct FormData; struct FormDataPredictions; @@ -65,43 +70,41 @@ class FormStructure { // types. void DetermineHeuristicTypes(LogManager* log_manager = nullptr); - // Encodes the proto |upload| request from this FormStructure. + // Encodes the proto |upload| request from this FormStructure, and stores + // the (single) FormSignature and the signatures of the fields to be uploaded + // in |encoded_signatures|. // In some cases, a |login_form_signature| is included as part of the upload. // This field is empty when sending upload requests for non-login forms. bool EncodeUploadRequest(const ServerFieldTypeSet& available_field_types, bool form_was_autofilled, const std::string& login_form_signature, bool observed_submission, - autofill::AutofillUploadContents* upload) const; - - // Encodes the proto |query| request for the set of |forms| that are valid - // (see implementation for details on which forms are not included in the - // query). The form signatures used in the Query request are output in - // |encoded_signatures|. All valid fields are encoded in |query|. + autofill::AutofillUploadContents* upload, + FormAndFieldSignatures* encoded_signatures) const; + + // Encodes the proto |query| request for the list of |forms| and their fields + // that are valid. The queried FormSignatures and FieldSignatures are stored + // in |encoded_signatures| in the same order as in |query|. In case multiple + // FormStructures have the same FormSignature, only the first one is included + // in |query| and |encoded_signatures|. static bool EncodeQueryRequest(const std::vector<FormStructure*>& forms, - std::vector<std::string>* encoded_signatures, - autofill::AutofillQueryContents* query); + autofill::AutofillQueryContents* query, + FormAndFieldSignatures* encoded_signatures); // Parses response as AutofillQueryResponseContents proto and calls // ProcessQueryResponse. - static void ParseQueryResponse(std::string response, - const std::vector<FormStructure*>& forms, - AutofillMetrics::FormInteractionsUkmLogger*); + static void ParseQueryResponse( + std::string response, + const std::vector<FormStructure*>& forms, + const FormAndFieldSignatures& encoded_signatures, + AutofillMetrics::FormInteractionsUkmLogger*); static void ParseApiQueryResponse( base::StringPiece payload, const std::vector<FormStructure*>& forms, + const FormAndFieldSignatures& encoded_signatures, AutofillMetrics::FormInteractionsUkmLogger*); - // Parses the field types from the server query response. |forms| must be the - // same as the one passed to EncodeQueryRequest when constructing the query. - // |form_interactions_ukm_logger| is used to provide logs to UKM and can be - // null in tests. - static void ProcessQueryResponse( - const AutofillQueryResponseContents& response, - const std::vector<FormStructure*>& forms, - AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger); - // Returns predictions using the details from the given |form_structures| and // their fields' predicted types. static std::vector<FormDataPredictions> GetFieldTypePredictions( @@ -241,6 +244,8 @@ class FormStructure { const url::Origin& main_frame_origin() const { return main_frame_origin_; } + const ButtonTitleList& button_titles() const { return button_titles_; } + bool has_author_specified_types() const { return has_author_specified_types_; } @@ -268,7 +273,11 @@ class FormStructure { bool all_fields_are_passwords() const { return all_fields_are_passwords_; } - FormSignature form_signature() const { return form_signature_; } + const FormSignature form_signature() const { return form_signature_; } + + void set_form_signature(FormSignature signature) { + form_signature_ = signature; + } // Returns a FormData containing the data this form structure knows about. FormData ToFormData() const; @@ -349,8 +358,6 @@ class FormStructure { void set_submission_source(mojom::SubmissionSource submission_source) { submission_source_ = submission_source; } - bool operator==(const FormData& form) const; - bool operator!=(const FormData& form) const; // Returns an identifier that is used by the refill logic. Takes the first non // empty of these or returns an empty string: @@ -380,6 +387,20 @@ class FormStructure { FormRendererId unique_renderer_id() const { return unique_renderer_id_; } + bool ShouldSkipFieldVisibleForTesting(const FormFieldData& field) const { + return ShouldSkipField(field); + } + + static void ProcessQueryResponseForTesting( + const AutofillQueryResponseContents& response, + const std::vector<FormStructure*>& forms, + const FormAndFieldSignatures& encoded_signatures, + AutofillMetrics::FormInteractionsUkmLogger* + form_interactions_ukm_logger) { + ProcessQueryResponse(response, forms, encoded_signatures, + form_interactions_ukm_logger); + } + private: friend class AutofillMergeTest; friend class FormStructureTest; @@ -434,6 +455,16 @@ class FormStructure { size_t current_section_ptr = 0; }; + // Parses the field types from the server query response. |forms| must be the + // same as the one passed to EncodeQueryRequest when constructing the query. + // |form_interactions_ukm_logger| is used to provide logs to UKM and can be + // null in tests. + static void ProcessQueryResponse( + const AutofillQueryResponseContents& response, + const std::vector<FormStructure*>& forms, + const FormAndFieldSignatures& encoded_signatures, + AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger); + FormStructure(FormSignature form_signature, const std::vector<FieldSignature>& field_signatures); @@ -498,12 +529,11 @@ class FormStructure { // when it considers necessary. void RationalizeFieldTypePredictions(); - // Encodes information about this form and its fields into |query_form|. - void EncodeFormForQuery( - autofill::AutofillQueryContents::Form* query_form) const; + void EncodeFormForQuery(autofill::AutofillQueryContents::Form* query_form, + FormAndFieldSignatures* encoded_signatures) const; - // Encodes information about this form and its fields into |upload|. - void EncodeFormForUpload(autofill::AutofillUploadContents* upload) const; + void EncodeFormForUpload(autofill::AutofillUploadContents* upload, + FormAndFieldSignatures* encoded_signatures) const; // Returns true if the form has no fields, or too many. bool IsMalformed() const; @@ -663,6 +693,8 @@ class FormStructure { }; LogBuffer& operator<<(LogBuffer& buffer, const FormStructure& form); +std::ostream& operator<<(std::ostream& buffer, const FormStructure& form); + } // namespace autofill #endif // COMPONENTS_AUTOFILL_CORE_BROWSER_FORM_STRUCTURE_H_ diff --git a/chromium/components/autofill/core/browser/form_structure_process_query_response_fuzzer.cc b/chromium/components/autofill/core/browser/form_structure_process_query_response_fuzzer.cc index b684f2f6ed2..8e3870a008c 100644 --- a/chromium/components/autofill/core/browser/form_structure_process_query_response_fuzzer.cc +++ b/chromium/components/autofill/core/browser/form_structure_process_query_response_fuzzer.cc @@ -7,6 +7,7 @@ #include <iostream> #include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_test_utils.h" #include "components/autofill/core/browser/form_structure.h" #include "components/autofill/core/common/form_data.h" #include "components/autofill/core/common/form_field_data.h" @@ -35,7 +36,8 @@ void AddField(const std::string& label, // forms vectors, so it can be changed if needed. DEFINE_BINARY_PROTO_FUZZER(const AutofillQueryResponseContents& response) { std::vector<FormStructure*> forms; - FormStructure::ProcessQueryResponse(response, forms, nullptr); + FormStructure::ProcessQueryResponseForTesting( + response, forms, test::GetEncodedSignatures(forms), nullptr); FormData form_data; AddField("username", "username", "text", &form_data); @@ -43,7 +45,8 @@ DEFINE_BINARY_PROTO_FUZZER(const AutofillQueryResponseContents& response) { FormStructure form(form_data); forms.push_back(&form); - FormStructure::ProcessQueryResponse(response, forms, nullptr); + FormStructure::ProcessQueryResponseForTesting( + response, forms, test::GetEncodedSignatures(forms), nullptr); } } // namespace diff --git a/chromium/components/autofill/core/browser/form_structure_unittest.cc b/chromium/components/autofill/core/browser/form_structure_unittest.cc index 0e23ab24aea..f5e7a3ae4bb 100644 --- a/chromium/components/autofill/core/browser/form_structure_unittest.cc +++ b/chromium/components/autofill/core/browser/form_structure_unittest.cc @@ -39,7 +39,6 @@ namespace autofill { using features::kAutofillEnforceMinRequiredFieldsForHeuristics; using features::kAutofillEnforceMinRequiredFieldsForQuery; using features::kAutofillEnforceMinRequiredFieldsForUpload; -using mojom::ButtonTitleType; using mojom::SubmissionIndicatorEvent; using mojom::SubmissionSource; @@ -2349,7 +2348,18 @@ TEST_F(FormStructureTest, EncodeQueryRequest) { std::vector<FormStructure*> forms; forms.push_back(&form_structure); - std::vector<std::string> encoded_signatures; + + FormAndFieldSignatures expected_signatures; + expected_signatures.push_back( + {form_structure.form_signature(), + { + form_structure.field(0)->GetFieldSignature(), + form_structure.field(1)->GetFieldSignature(), + form_structure.field(2)->GetFieldSignature(), + form_structure.field(3)->GetFieldSignature(), + form_structure.field(4)->GetFieldSignature() + // field 5 is checkable, and hence skipped. + }}); // Prepare the expected proto string. AutofillQueryContents query; @@ -2371,13 +2381,11 @@ TEST_F(FormStructureTest, EncodeQueryRequest) { std::string expected_query_string; ASSERT_TRUE(query.SerializeToString(&expected_query_string)); - const std::string kSignature1 = form_structure.FormSignatureAsStr(); - AutofillQueryContents encoded_query; - ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms, &encoded_signatures, - &encoded_query)); - ASSERT_EQ(1U, encoded_signatures.size()); - EXPECT_EQ(kSignature1, encoded_signatures[0]); + FormAndFieldSignatures encoded_signatures; + ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms, &encoded_query, + &encoded_signatures)); + EXPECT_EQ(encoded_signatures, expected_signatures); std::string encoded_query_string; encoded_query.SerializeToString(&encoded_query_string); @@ -2388,11 +2396,13 @@ TEST_F(FormStructureTest, EncodeQueryRequest) { FormStructure form_structure2(form); forms.push_back(&form_structure2); + FormAndFieldSignatures expected_signatures2 = expected_signatures; + AutofillQueryContents encoded_query2; - ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms, &encoded_signatures, - &encoded_query2)); - ASSERT_EQ(1U, encoded_signatures.size()); - EXPECT_EQ(kSignature1, encoded_signatures[0]); + FormAndFieldSignatures encoded_signatures2; + ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms, &encoded_query2, + &encoded_signatures2)); + EXPECT_EQ(encoded_signatures2, expected_signatures2); encoded_query2.SerializeToString(&encoded_query_string); EXPECT_EQ(expected_query_string, encoded_query_string); @@ -2407,6 +2417,21 @@ TEST_F(FormStructureTest, EncodeQueryRequest) { FormStructure form_structure3(form); forms.push_back(&form_structure3); + FormAndFieldSignatures expected_signatures3 = expected_signatures2; + expected_signatures3.push_back( + {form_structure3.form_signature(), + {form_structure3.field(0)->GetFieldSignature(), + form_structure3.field(1)->GetFieldSignature(), + form_structure3.field(2)->GetFieldSignature(), + form_structure3.field(3)->GetFieldSignature(), + form_structure3.field(4)->GetFieldSignature(), + // field 5 is checkable, and hence skipped. + form_structure3.field(6)->GetFieldSignature(), + form_structure3.field(7)->GetFieldSignature(), + form_structure3.field(8)->GetFieldSignature(), + form_structure3.field(9)->GetFieldSignature(), + form_structure3.field(10)->GetFieldSignature()}}); + // Add the second form to the expected proto. query_form = query.add_form(); query_form->set_signature(form_structure3.form_signature().value()); @@ -2429,16 +2454,31 @@ TEST_F(FormStructureTest, EncodeQueryRequest) { ASSERT_TRUE(query.SerializeToString(&expected_query_string)); AutofillQueryContents encoded_query3; - ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms, &encoded_signatures, - &encoded_query3)); - ASSERT_EQ(2U, encoded_signatures.size()); - EXPECT_EQ(kSignature1, encoded_signatures[0]); - const std::string kSignature2 = form_structure3.FormSignatureAsStr(); - EXPECT_EQ(kSignature2, encoded_signatures[1]); + FormAndFieldSignatures encoded_signatures3; + ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms, &encoded_query3, + &encoded_signatures3)); + EXPECT_EQ(encoded_signatures3, expected_signatures3); encoded_query3.SerializeToString(&encoded_query_string); EXPECT_EQ(expected_query_string, encoded_query_string); + // |form_structures4| will have the same signature as |form_structure3|. + form.fields.back().name = ASCIIToUTF16("address123456789"); + + FormStructure form_structure4(form); + forms.push_back(&form_structure4); + + FormAndFieldSignatures expected_signatures4 = expected_signatures3; + + AutofillQueryContents encoded_query4; + FormAndFieldSignatures encoded_signatures4; + ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms, &encoded_query4, + &encoded_signatures4)); + EXPECT_EQ(encoded_signatures4, expected_signatures4); + + encoded_query4.SerializeToString(&encoded_query_string); + EXPECT_EQ(expected_query_string, encoded_query_string); + FormData malformed_form(form); // Add 300 address fields - the form is not valid anymore, but previous ones // are. The result should be the same as in previous test. @@ -2450,22 +2490,25 @@ TEST_F(FormStructureTest, EncodeQueryRequest) { FormStructure malformed_form_structure(malformed_form); forms.push_back(&malformed_form_structure); - AutofillQueryContents encoded_query4; - ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms, &encoded_signatures, - &encoded_query4)); - ASSERT_EQ(2U, encoded_signatures.size()); - EXPECT_EQ(kSignature1, encoded_signatures[0]); - EXPECT_EQ(kSignature2, encoded_signatures[1]); - encoded_query4.SerializeToString(&encoded_query_string); + FormAndFieldSignatures expected_signatures5 = expected_signatures4; + + AutofillQueryContents encoded_query5; + FormAndFieldSignatures encoded_signatures5; + ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms, &encoded_query5, + &encoded_signatures5)); + EXPECT_EQ(encoded_signatures5, expected_signatures5); + + encoded_query5.SerializeToString(&encoded_query_string); EXPECT_EQ(expected_query_string, encoded_query_string); // Check that we fail if there are only bad form(s). std::vector<FormStructure*> bad_forms; bad_forms.push_back(&malformed_form_structure); - AutofillQueryContents encoded_query5; - EXPECT_FALSE(FormStructure::EncodeQueryRequest(bad_forms, &encoded_signatures, - &encoded_query5)); + AutofillQueryContents encoded_query6; + FormAndFieldSignatures encoded_signatures6; + EXPECT_FALSE(FormStructure::EncodeQueryRequest(bad_forms, &encoded_query6, + &encoded_signatures6)); } TEST_F(FormStructureTest, EncodeUploadRequest_SubmissionIndicatorEvents_Match) { @@ -2507,44 +2550,43 @@ TEST_F(FormStructureTest, EncodeUploadRequest_SubmissionIndicatorEvents_Match) { } TEST_F(FormStructureTest, ButtonTitleType_Match) { - // Statically assert that the mojo ButtonTitleType enum matches the - // corresponding entries the in proto AutofillUploadContents::ButtonTitle - // ButtonTitleType enum. - static_assert(AutofillUploadContents::ButtonTitle::NONE == - static_cast<int>(ButtonTitleType::NONE), - "NONE enumerator does not match!"); + // Statically assert that the mojom::ButtonTitleType enum matches the + // corresponding entries in the proto - ButtonTitleType enum. + static_assert( + ButtonTitleType::NONE == static_cast<int>(mojom::ButtonTitleType::NONE), + "NONE enumerator does not match!"); static_assert( - AutofillUploadContents::ButtonTitle::BUTTON_ELEMENT_SUBMIT_TYPE == - static_cast<int>(ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE), + ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE == + static_cast<int>(mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE), "BUTTON_ELEMENT_SUBMIT_TYPE enumerator does not match!"); static_assert( - AutofillUploadContents::ButtonTitle::BUTTON_ELEMENT_BUTTON_TYPE == - static_cast<int>(ButtonTitleType::BUTTON_ELEMENT_BUTTON_TYPE), + ButtonTitleType::BUTTON_ELEMENT_BUTTON_TYPE == + static_cast<int>(mojom::ButtonTitleType::BUTTON_ELEMENT_BUTTON_TYPE), "BUTTON_ELEMENT_BUTTON_TYPE enumerator does not match!"); static_assert( - AutofillUploadContents::ButtonTitle::INPUT_ELEMENT_SUBMIT_TYPE == - static_cast<int>(ButtonTitleType::INPUT_ELEMENT_SUBMIT_TYPE), + ButtonTitleType::INPUT_ELEMENT_SUBMIT_TYPE == + static_cast<int>(mojom::ButtonTitleType::INPUT_ELEMENT_SUBMIT_TYPE), "INPUT_ELEMENT_SUBMIT_TYPE enumerator does not match!"); static_assert( - AutofillUploadContents::ButtonTitle::INPUT_ELEMENT_BUTTON_TYPE == - static_cast<int>(ButtonTitleType::INPUT_ELEMENT_BUTTON_TYPE), + ButtonTitleType::INPUT_ELEMENT_BUTTON_TYPE == + static_cast<int>(mojom::ButtonTitleType::INPUT_ELEMENT_BUTTON_TYPE), "INPUT_ELEMENT_BUTTON_TYPE enumerator does not match!"); - static_assert(AutofillUploadContents::ButtonTitle::HYPERLINK == - static_cast<int>(ButtonTitleType::HYPERLINK), + static_assert(ButtonTitleType::HYPERLINK == + static_cast<int>(mojom::ButtonTitleType::HYPERLINK), "HYPERLINK enumerator does not match!"); - static_assert(AutofillUploadContents::ButtonTitle::DIV == - static_cast<int>(ButtonTitleType::DIV), - "DIV enumerator does not match!"); + static_assert( + ButtonTitleType::DIV == static_cast<int>(mojom::ButtonTitleType::DIV), + "DIV enumerator does not match!"); - static_assert(AutofillUploadContents::ButtonTitle::SPAN == - static_cast<int>(ButtonTitleType::SPAN), - "SPAN enumerator does not match!"); + static_assert( + ButtonTitleType::SPAN == static_cast<int>(mojom::ButtonTitleType::SPAN), + "SPAN enumerator does not match!"); } TEST_F(FormStructureTest, EncodeUploadRequest_WithMatchingValidities) { @@ -2669,10 +2711,12 @@ TEST_F(FormStructureTest, EncodeUploadRequest_WithMatchingValidities) { //////////////// std::string expected_upload_string; ASSERT_TRUE(upload.SerializeToString(&expected_upload_string)); + FormAndFieldSignatures signatures; AutofillUploadContents encoded_upload; EXPECT_TRUE(form_structure->EncodeUploadRequest( - available_field_types, false, std::string(), true, &encoded_upload)); + available_field_types, false, std::string(), true, &encoded_upload, + &signatures)); std::string encoded_upload_string; encoded_upload.SerializeToString(&encoded_upload_string); @@ -2684,7 +2728,8 @@ TEST_F(FormStructureTest, EncodeUploadRequest_WithMatchingValidities) { AutofillUploadContents encoded_upload2; EXPECT_TRUE(form_structure->EncodeUploadRequest( - available_field_types, true, std::string(), true, &encoded_upload2)); + available_field_types, true, std::string(), true, &encoded_upload2, + &signatures)); encoded_upload2.SerializeToString(&encoded_upload_string); EXPECT_EQ(expected_upload_string, encoded_upload_string); @@ -2737,7 +2782,8 @@ TEST_F(FormStructureTest, EncodeUploadRequest_WithMatchingValidities) { AutofillUploadContents encoded_upload3; EXPECT_TRUE(form_structure->EncodeUploadRequest( - available_field_types, false, std::string(), true, &encoded_upload3)); + available_field_types, false, std::string(), true, &encoded_upload3, + &signatures)); encoded_upload3.SerializeToString(&encoded_upload_string); EXPECT_EQ(expected_upload_string, encoded_upload_string); @@ -2862,10 +2908,12 @@ TEST_F(FormStructureTest, EncodeUploadRequest_WithNonMatchingValidities) { //////////////// std::string expected_upload_string; ASSERT_TRUE(upload.SerializeToString(&expected_upload_string)); + FormAndFieldSignatures signatures; AutofillUploadContents encoded_upload; EXPECT_TRUE(form_structure->EncodeUploadRequest( - available_field_types, false, std::string(), true, &encoded_upload)); + available_field_types, false, std::string(), true, &encoded_upload, + &signatures)); std::string encoded_upload_string; encoded_upload.SerializeToString(&encoded_upload_string); @@ -2995,10 +3043,12 @@ TEST_F(FormStructureTest, EncodeUploadRequest_WithMultipleValidities) { //////////////// std::string expected_upload_string; ASSERT_TRUE(upload.SerializeToString(&expected_upload_string)); + FormAndFieldSignatures signatures; AutofillUploadContents encoded_upload; EXPECT_TRUE(form_structure->EncodeUploadRequest( - available_field_types, false, std::string(), true, &encoded_upload)); + available_field_types, false, std::string(), true, &encoded_upload, + &signatures)); std::string encoded_upload_string; encoded_upload.SerializeToString(&encoded_upload_string); @@ -3081,6 +3131,18 @@ TEST_F(FormStructureTest, EncodeUploadRequest) { possible_field_types_validities[i]); } + FormAndFieldSignatures expected_signatures; + expected_signatures.push_back( + {form_structure->form_signature(), + { + form_structure->field(0)->GetFieldSignature(), + form_structure->field(1)->GetFieldSignature(), + form_structure->field(2)->GetFieldSignature(), + form_structure->field(3)->GetFieldSignature(), + form_structure->field(4)->GetFieldSignature() + // Field 5 is checkable and hence skipped. + }}); + ServerFieldTypeSet available_field_types; available_field_types.insert(NAME_FIRST); available_field_types.insert(NAME_LAST); @@ -3119,10 +3181,13 @@ TEST_F(FormStructureTest, EncodeUploadRequest) { std::string expected_upload_string; ASSERT_TRUE(upload.SerializeToString(&expected_upload_string)); + FormAndFieldSignatures signatures; AutofillUploadContents encoded_upload; EXPECT_TRUE(form_structure->EncodeUploadRequest( - available_field_types, false, std::string(), true, &encoded_upload)); + available_field_types, false, std::string(), true, &encoded_upload, + &signatures)); + EXPECT_EQ(signatures, expected_signatures); std::string encoded_upload_string; encoded_upload.SerializeToString(&encoded_upload_string); @@ -3134,7 +3199,9 @@ TEST_F(FormStructureTest, EncodeUploadRequest) { AutofillUploadContents encoded_upload2; EXPECT_TRUE(form_structure->EncodeUploadRequest( - available_field_types, true, std::string(), true, &encoded_upload2)); + available_field_types, true, std::string(), true, &encoded_upload2, + &signatures)); + EXPECT_EQ(signatures, expected_signatures); encoded_upload2.SerializeToString(&encoded_upload_string); EXPECT_EQ(expected_upload_string, encoded_upload_string); @@ -3166,6 +3233,13 @@ TEST_F(FormStructureTest, EncodeUploadRequest) { possible_field_types_validities[i]); } + expected_signatures[0].first = form_structure->form_signature(); + // Field 5 is checkable and hence skipped. + expected_signatures[0].second.push_back( + form_structure->field(6)->GetFieldSignature()); + expected_signatures[0].second.push_back( + form_structure->field(7)->GetFieldSignature()); + // Adjust the expected proto string. upload.set_form_signature(form_structure->form_signature().value()); upload.set_autofill_used(false); @@ -3187,7 +3261,9 @@ TEST_F(FormStructureTest, EncodeUploadRequest) { AutofillUploadContents encoded_upload3; EXPECT_TRUE(form_structure->EncodeUploadRequest( - available_field_types, false, std::string(), true, &encoded_upload3)); + available_field_types, false, std::string(), true, &encoded_upload3, + &signatures)); + EXPECT_EQ(signatures, expected_signatures); encoded_upload3.SerializeToString(&encoded_upload_string); EXPECT_EQ(expected_upload_string, encoded_upload_string); @@ -3215,7 +3291,8 @@ TEST_F(FormStructureTest, EncodeUploadRequest) { AutofillUploadContents encoded_upload4; EXPECT_FALSE(form_structure->EncodeUploadRequest( - available_field_types, false, std::string(), true, &encoded_upload4)); + available_field_types, false, std::string(), true, &encoded_upload4, + &signatures)); } TEST_F(FormStructureTest, @@ -3335,10 +3412,11 @@ TEST_F(FormStructureTest, std::string expected_upload_string; ASSERT_TRUE(upload.SerializeToString(&expected_upload_string)); + FormAndFieldSignatures signatures; AutofillUploadContents encoded_upload; - EXPECT_TRUE(form_structure->EncodeUploadRequest(available_field_types, true, - "42", true, &encoded_upload)); + EXPECT_TRUE(form_structure->EncodeUploadRequest( + available_field_types, true, "42", true, &encoded_upload, &signatures)); std::string encoded_upload_string; encoded_upload.SerializeToString(&encoded_upload_string); @@ -3419,8 +3497,10 @@ TEST_F(FormStructureTest, EncodeUploadRequest_WithAutocomplete) { ASSERT_TRUE(upload.SerializeToString(&expected_upload_string)); AutofillUploadContents encoded_upload; + FormAndFieldSignatures signatures; EXPECT_TRUE(form_structure->EncodeUploadRequest( - available_field_types, true, std::string(), true, &encoded_upload)); + available_field_types, true, std::string(), true, &encoded_upload, + &signatures)); std::string encoded_upload_string; encoded_upload.SerializeToString(&encoded_upload_string); @@ -3519,10 +3599,12 @@ TEST_F(FormStructureTest, EncodeUploadRequestWithPropertiesMask) { std::string expected_upload_string; ASSERT_TRUE(upload.SerializeToString(&expected_upload_string)); + FormAndFieldSignatures signatures; AutofillUploadContents encoded_upload; EXPECT_TRUE(form_structure->EncodeUploadRequest( - available_field_types, true, std::string(), true, &encoded_upload)); + available_field_types, true, std::string(), true, &encoded_upload, + &signatures)); std::string encoded_upload_string; encoded_upload.SerializeToString(&encoded_upload_string); @@ -3601,11 +3683,12 @@ TEST_F(FormStructureTest, EncodeUploadRequest_ObservedSubmissionFalse) { std::string expected_upload_string; ASSERT_TRUE(upload.SerializeToString(&expected_upload_string)); + FormAndFieldSignatures signatures; AutofillUploadContents encoded_upload; EXPECT_TRUE(form_structure->EncodeUploadRequest( available_field_types, true, std::string(), - /* observed_submission= */ false, &encoded_upload)); + /* observed_submission= */ false, &encoded_upload, &signatures)); std::string encoded_upload_string; encoded_upload.SerializeToString(&encoded_upload_string); @@ -3677,10 +3760,12 @@ TEST_F(FormStructureTest, EncodeUploadRequest_WithLabels) { std::string expected_upload_string; ASSERT_TRUE(upload.SerializeToString(&expected_upload_string)); + FormAndFieldSignatures signatures; AutofillUploadContents encoded_upload; EXPECT_TRUE(form_structure->EncodeUploadRequest( - available_field_types, true, std::string(), true, &encoded_upload)); + available_field_types, true, std::string(), true, &encoded_upload, + &signatures)); std::string encoded_upload_string; encoded_upload.SerializeToString(&encoded_upload_string); @@ -3757,10 +3842,12 @@ TEST_F(FormStructureTest, EncodeUploadRequest_WithCssClassesAndIds) { std::string expected_upload_string; ASSERT_TRUE(upload.SerializeToString(&expected_upload_string)); + FormAndFieldSignatures signatures; AutofillUploadContents encoded_upload; EXPECT_TRUE(form_structure->EncodeUploadRequest( - available_field_types, true, std::string(), true, &encoded_upload)); + available_field_types, true, std::string(), true, &encoded_upload, + &signatures)); std::string encoded_upload_string; encoded_upload.SerializeToString(&encoded_upload_string); @@ -3835,10 +3922,12 @@ TEST_F(FormStructureTest, EncodeUploadRequest_WithFormName) { std::string expected_upload_string; ASSERT_TRUE(upload.SerializeToString(&expected_upload_string)); + FormAndFieldSignatures signatures; AutofillUploadContents encoded_upload; EXPECT_TRUE(form_structure->EncodeUploadRequest( - available_field_types, true, std::string(), true, &encoded_upload)); + available_field_types, true, std::string(), true, &encoded_upload, + &signatures)); std::string encoded_upload_string; encoded_upload.SerializeToString(&encoded_upload_string); @@ -3917,10 +4006,12 @@ TEST_F(FormStructureTest, EncodeUploadRequestPartialMetadata) { std::string expected_upload_string; ASSERT_TRUE(upload.SerializeToString(&expected_upload_string)); + FormAndFieldSignatures signatures; AutofillUploadContents encoded_upload; EXPECT_TRUE(form_structure->EncodeUploadRequest( - available_field_types, true, std::string(), true, &encoded_upload)); + available_field_types, true, std::string(), true, &encoded_upload, + &signatures)); std::string encoded_upload_string; encoded_upload.SerializeToString(&encoded_upload_string); @@ -4010,10 +4101,12 @@ TEST_F(FormStructureTest, EncodeUploadRequest_DisabledMetadataTrial) { std::string expected_upload_string; ASSERT_TRUE(upload.SerializeToString(&expected_upload_string)); + FormAndFieldSignatures signatures; AutofillUploadContents encoded_upload; EXPECT_TRUE(form_structure->EncodeUploadRequest( - available_field_types, true, std::string(), true, &encoded_upload)); + available_field_types, true, std::string(), true, &encoded_upload, + &signatures)); std::string encoded_upload_string; encoded_upload.SerializeToString(&encoded_upload_string); @@ -4086,10 +4179,12 @@ TEST_F(FormStructureTest, CheckDataPresence) { std::string expected_upload_string; ASSERT_TRUE(upload.SerializeToString(&expected_upload_string)); + FormAndFieldSignatures signatures; AutofillUploadContents encoded_upload; - EXPECT_TRUE(form_structure.EncodeUploadRequest( - available_field_types, false, std::string(), true, &encoded_upload)); + EXPECT_TRUE(form_structure.EncodeUploadRequest(available_field_types, false, + std::string(), true, + &encoded_upload, &signatures)); std::string encoded_upload_string; encoded_upload.SerializeToString(&encoded_upload_string); @@ -4119,7 +4214,8 @@ TEST_F(FormStructureTest, CheckDataPresence) { AutofillUploadContents encoded_upload2; EXPECT_TRUE(form_structure.EncodeUploadRequest( - available_field_types, false, std::string(), true, &encoded_upload2)); + available_field_types, false, std::string(), true, &encoded_upload2, + &signatures)); encoded_upload2.SerializeToString(&encoded_upload_string); EXPECT_EQ(expected_upload_string, encoded_upload_string); @@ -4172,7 +4268,8 @@ TEST_F(FormStructureTest, CheckDataPresence) { AutofillUploadContents encoded_upload3; EXPECT_TRUE(form_structure.EncodeUploadRequest( - available_field_types, false, std::string(), true, &encoded_upload3)); + available_field_types, false, std::string(), true, &encoded_upload3, + &signatures)); encoded_upload3.SerializeToString(&encoded_upload_string); EXPECT_EQ(expected_upload_string, encoded_upload_string); @@ -4203,7 +4300,8 @@ TEST_F(FormStructureTest, CheckDataPresence) { AutofillUploadContents encoded_upload4; EXPECT_TRUE(form_structure.EncodeUploadRequest( - available_field_types, false, std::string(), true, &encoded_upload4)); + available_field_types, false, std::string(), true, &encoded_upload4, + &signatures)); encoded_upload4.SerializeToString(&encoded_upload_string); EXPECT_EQ(expected_upload_string, encoded_upload_string); @@ -4270,7 +4368,8 @@ TEST_F(FormStructureTest, CheckDataPresence) { AutofillUploadContents encoded_upload5; EXPECT_TRUE(form_structure.EncodeUploadRequest( - available_field_types, false, std::string(), true, &encoded_upload5)); + available_field_types, false, std::string(), true, &encoded_upload5, + &signatures)); encoded_upload5.SerializeToString(&encoded_upload_string); EXPECT_EQ(expected_upload_string, encoded_upload_string); @@ -4371,10 +4470,12 @@ TEST_F(FormStructureTest, CheckMultipleTypes) { std::string expected_upload_string; ASSERT_TRUE(upload.SerializeToString(&expected_upload_string)); + FormAndFieldSignatures signatures; AutofillUploadContents encoded_upload; EXPECT_TRUE(form_structure->EncodeUploadRequest( - available_field_types, false, std::string(), true, &encoded_upload)); + available_field_types, false, std::string(), true, &encoded_upload, + &signatures)); std::string encoded_upload_string; encoded_upload.SerializeToString(&encoded_upload_string); @@ -4397,7 +4498,8 @@ TEST_F(FormStructureTest, CheckMultipleTypes) { AutofillUploadContents encoded_upload2; EXPECT_TRUE(form_structure->EncodeUploadRequest( - available_field_types, false, std::string(), true, &encoded_upload2)); + available_field_types, false, std::string(), true, &encoded_upload2, + &signatures)); encoded_upload2.SerializeToString(&encoded_upload_string); EXPECT_EQ(expected_upload_string, encoded_upload_string); @@ -4414,7 +4516,8 @@ TEST_F(FormStructureTest, CheckMultipleTypes) { AutofillUploadContents encoded_upload3; EXPECT_TRUE(form_structure->EncodeUploadRequest( - available_field_types, false, std::string(), true, &encoded_upload3)); + available_field_types, false, std::string(), true, &encoded_upload3, + &signatures)); encoded_upload3.SerializeToString(&encoded_upload_string); EXPECT_EQ(expected_upload_string, encoded_upload_string); @@ -4439,7 +4542,8 @@ TEST_F(FormStructureTest, CheckMultipleTypes) { AutofillUploadContents encoded_upload4; EXPECT_TRUE(form_structure->EncodeUploadRequest( - available_field_types, false, std::string(), true, &encoded_upload4)); + available_field_types, false, std::string(), true, &encoded_upload4, + &signatures)); encoded_upload4.SerializeToString(&encoded_upload_string); EXPECT_EQ(expected_upload_string, encoded_upload_string); @@ -4466,10 +4570,11 @@ TEST_F(FormStructureTest, EncodeUploadRequest_PasswordsRevealed) { FormStructure form_structure(form); form_structure.set_passwords_were_revealed(true); AutofillUploadContents upload; + FormAndFieldSignatures signatures; EXPECT_TRUE(form_structure.EncodeUploadRequest( {{}} /* available_field_types */, false /* form_was_autofilled */, std::string() /* login_form_signature */, true /* observed_submission */, - &upload)); + &upload, &signatures)); EXPECT_EQ(true, upload.passwords_revealed()); } @@ -4488,10 +4593,11 @@ TEST_F(FormStructureTest, EncodeUploadRequest_IsFormTag) { FormStructure form_structure(form); form_structure.set_passwords_were_revealed(true); AutofillUploadContents upload; + FormAndFieldSignatures signatures; EXPECT_TRUE(form_structure.EncodeUploadRequest( {{}} /* available_field_types */, false /* form_was_autofilled */, std::string() /* login_form_signature */, - true /* observed_submission */, &upload)); + true /* observed_submission */, &upload, &signatures)); EXPECT_EQ(is_form_tag, upload.has_form_tag()); } } @@ -4518,6 +4624,9 @@ TEST_F(FormStructureTest, EncodeUploadRequest_RichMetadata) { FormData form; form.id_attribute = ASCIIToUTF16("form-id"); form.url = GURL("http://www.foo.com/"); + form.button_titles = { + std::make_pair(ASCIIToUTF16("Submit"), + mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)}; form.full_url = GURL("http://www.foo.com/?foo=bar"); for (const auto& f : kFieldMetadata) { FormFieldData field; @@ -4540,10 +4649,11 @@ TEST_F(FormStructureTest, EncodeUploadRequest_RichMetadata) { std::make_unique<RandomizedEncoder>(encoder)); AutofillUploadContents upload; + FormAndFieldSignatures signatures; ASSERT_TRUE(form_structure.EncodeUploadRequest( {{}} /* available_field_types */, false /* form_was_autofilled */, std::string() /* login_form_signature */, true /* observed_submission */, - &upload)); + &upload, &signatures)); const auto form_signature = form_structure.form_signature(); @@ -4571,6 +4681,18 @@ TEST_F(FormStructureTest, EncodeUploadRequest_RichMetadata) { RandomizedEncoder::FORM_URL, full_url)); ASSERT_EQ(static_cast<size_t>(upload.field_size()), base::size(kFieldMetadata)); + + ASSERT_EQ(1, upload.randomized_form_metadata().button_title().size()); + EXPECT_EQ(upload.randomized_form_metadata() + .button_title()[0] + .title() + .encoded_bits(), + encoder.EncodeForTesting(form_signature, FieldSignature(), + RandomizedEncoder::FORM_BUTTON_TITLES, + form.button_titles[0].first)); + EXPECT_EQ(ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE, + upload.randomized_form_metadata().button_title()[0].type()); + for (int i = 0; i < upload.field_size(); ++i) { const auto& metadata = upload.field(i).randomized_field_metadata(); const auto& field = *form_structure.field(i); @@ -4672,7 +4794,9 @@ TEST_F(FormStructureTest, Metadata_OnlySendFullUrlWithUserConsent) { FormStructure form_structure(form); form_structure.set_randomized_encoder(RandomizedEncoder::Create(&prefs)); AutofillUploadContents upload = AutofillUploadContents(); - form_structure.EncodeUploadRequest({}, true, "", true, &upload); + FormAndFieldSignatures signatures; + form_structure.EncodeUploadRequest({}, true, "", true, &upload, + &signatures); EXPECT_EQ(has_consent, upload.randomized_form_metadata().has_url()); } @@ -4798,7 +4922,7 @@ TEST_F(FormStructureTest, SkipFieldTest) { FormStructure form_structure(form); std::vector<FormStructure*> forms; forms.push_back(&form_structure); - std::vector<std::string> encoded_signatures; + FormAndFieldSignatures encoded_signatures; AutofillQueryContents encoded_query; // Create the expected query and serialize it to a string. @@ -4813,12 +4937,12 @@ TEST_F(FormStructureTest, SkipFieldTest) { std::string expected_query_string; ASSERT_TRUE(query.SerializeToString(&expected_query_string)); - const char kExpectedSignature[] = "18006745212084723782"; + const FormSignature kExpectedSignature(18006745212084723782UL); - ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms, &encoded_signatures, - &encoded_query)); + ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms, &encoded_query, + &encoded_signatures)); ASSERT_EQ(1U, encoded_signatures.size()); - EXPECT_EQ(kExpectedSignature, encoded_signatures[0]); + EXPECT_EQ(kExpectedSignature, encoded_signatures.front().first); std::string encoded_query_string; encoded_query.SerializeToString(&encoded_query_string); @@ -4850,7 +4974,7 @@ TEST_F(FormStructureTest, EncodeQueryRequest_WithLabels) { std::vector<FormStructure*> forms; FormStructure form_structure(form); forms.push_back(&form_structure); - std::vector<std::string> encoded_signatures; + FormAndFieldSignatures encoded_signatures; AutofillQueryContents encoded_query; // Create the expected query and serialize it to a string. @@ -4867,8 +4991,8 @@ TEST_F(FormStructureTest, EncodeQueryRequest_WithLabels) { std::string expected_query_string; ASSERT_TRUE(query.SerializeToString(&expected_query_string)); - EXPECT_TRUE(FormStructure::EncodeQueryRequest(forms, &encoded_signatures, - &encoded_query)); + EXPECT_TRUE(FormStructure::EncodeQueryRequest(forms, &encoded_query, + &encoded_signatures)); std::string encoded_query_string; encoded_query.SerializeToString(&encoded_query_string); @@ -4905,7 +5029,7 @@ TEST_F(FormStructureTest, EncodeQueryRequest_WithLongLabels) { FormStructure form_structure(form); std::vector<FormStructure*> forms; forms.push_back(&form_structure); - std::vector<std::string> encoded_signatures; + FormAndFieldSignatures encoded_signatures; AutofillQueryContents encoded_query; // Create the expected query and serialize it to a string. @@ -4922,8 +5046,8 @@ TEST_F(FormStructureTest, EncodeQueryRequest_WithLongLabels) { std::string expected_query_string; ASSERT_TRUE(query.SerializeToString(&expected_query_string)); - EXPECT_TRUE(FormStructure::EncodeQueryRequest(forms, &encoded_signatures, - &encoded_query)); + EXPECT_TRUE(FormStructure::EncodeQueryRequest(forms, &encoded_query, + &encoded_signatures)); std::string encoded_query_string; encoded_query.SerializeToString(&encoded_query_string); @@ -4954,7 +5078,7 @@ TEST_F(FormStructureTest, EncodeQueryRequest_MissingNames) { std::vector<FormStructure*> forms; forms.push_back(&form_structure); - std::vector<std::string> encoded_signatures; + FormAndFieldSignatures encoded_signatures; AutofillQueryContents encoded_query; // Create the expected query and serialize it to a string. @@ -4969,12 +5093,12 @@ TEST_F(FormStructureTest, EncodeQueryRequest_MissingNames) { std::string expected_query_string; ASSERT_TRUE(query.SerializeToString(&expected_query_string)); - const char kExpectedSignature[] = "16416961345885087496"; + const FormSignature kExpectedSignature(16416961345885087496UL); - ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms, &encoded_signatures, - &encoded_query)); + ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms, &encoded_query, + &encoded_signatures)); ASSERT_EQ(1U, encoded_signatures.size()); - EXPECT_EQ(kExpectedSignature, encoded_signatures[0]); + EXPECT_EQ(kExpectedSignature, encoded_signatures.front().first); std::string encoded_query_string; encoded_query.SerializeToString(&encoded_query_string); @@ -5005,7 +5129,7 @@ TEST_F(FormStructureTest, EncodeQueryRequest_DisabledMetadataTrial) { FormStructure form_structure(form); std::vector<FormStructure*> forms; forms.push_back(&form_structure); - std::vector<std::string> encoded_signatures; + FormAndFieldSignatures encoded_signatures; AutofillQueryContents encoded_query; // Create the expected query and serialize it to a string. @@ -5020,12 +5144,12 @@ TEST_F(FormStructureTest, EncodeQueryRequest_DisabledMetadataTrial) { std::string expected_query_string; ASSERT_TRUE(query.SerializeToString(&expected_query_string)); - const char kExpectedSignature[] = "7635954436925888745"; + const FormSignature kExpectedSignature(7635954436925888745UL); - ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms, &encoded_signatures, - &encoded_query)); + ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms, &encoded_query, + &encoded_signatures)); ASSERT_EQ(1U, encoded_signatures.size()); - EXPECT_EQ(kExpectedSignature, encoded_signatures[0]); + EXPECT_EQ(kExpectedSignature, encoded_signatures.front().first); std::string encoded_query_string; encoded_query.SerializeToString(&encoded_query_string); @@ -5075,6 +5199,73 @@ TEST_F(FormStructureTest, PossibleValues) { // Tests proper resolution heuristic, server and html field types when the // server returns NO_SERVER_DATA, UNKNOWN_TYPE, and a valid type. +TEST_F(FormStructureTest, ParseQueryResponse_TooManyTypes) { + FormData form_data; + FormFieldData field; + form_data.url = GURL("http://foo.com"); + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("First Name"); + field.name = ASCIIToUTF16("fname"); + form_data.fields.push_back(field); + + field.label = ASCIIToUTF16("Last Name"); + field.name = ASCIIToUTF16("lname"); + form_data.fields.push_back(field); + + field.label = ASCIIToUTF16("email"); + field.name = ASCIIToUTF16("email"); + field.autocomplete_attribute = "address-level2"; + form_data.fields.push_back(field); + + FormStructure form(form_data); + form.DetermineHeuristicTypes(); + + // Setup the query response. + AutofillQueryResponseContents response; + std::string response_string; + response.add_field()->set_overall_type_prediction(NAME_FIRST); + response.add_field()->set_overall_type_prediction(NAME_LAST); + response.add_field()->set_overall_type_prediction(ADDRESS_HOME_LINE1); + response.add_field()->set_overall_type_prediction(EMAIL_ADDRESS); + response.add_field()->set_overall_type_prediction(UNKNOWN_TYPE); + ASSERT_TRUE(response.SerializeToString(&response_string)); + + // Parse the response and update the field type predictions. + std::vector<FormStructure*> forms{&form}; + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); + ASSERT_EQ(form.field_count(), 3U); + + // Validate field 0. + EXPECT_EQ(NAME_FIRST, form.field(0)->heuristic_type()); + EXPECT_EQ(NAME_FIRST, form.field(0)->server_type()); + EXPECT_EQ(HTML_TYPE_UNSPECIFIED, form.field(0)->html_type()); + EXPECT_EQ(NAME_FIRST, form.field(0)->Type().GetStorableType()); + + // Validate field 1. + EXPECT_EQ(NAME_LAST, form.field(1)->heuristic_type()); + EXPECT_EQ(NAME_LAST, form.field(1)->server_type()); + EXPECT_EQ(HTML_TYPE_UNSPECIFIED, form.field(1)->html_type()); + EXPECT_EQ(NAME_LAST, form.field(1)->Type().GetStorableType()); + + // Validate field 2. Note: HTML_TYPE_ADDRESS_LEVEL2 -> City + EXPECT_EQ(EMAIL_ADDRESS, form.field(2)->heuristic_type()); + EXPECT_EQ(ADDRESS_HOME_LINE1, form.field(2)->server_type()); + EXPECT_EQ(HTML_TYPE_ADDRESS_LEVEL2, form.field(2)->html_type()); + EXPECT_EQ(ADDRESS_HOME_CITY, form.field(2)->Type().GetStorableType()); + + // Also check the extreme case of an empty form. + FormStructure empty_form{FormData()}; + std::vector<FormStructure*> empty_forms{&empty_form}; + FormStructure::ParseQueryResponse(response_string, empty_forms, + test::GetEncodedSignatures(empty_forms), + nullptr); + ASSERT_EQ(empty_form.field_count(), 0U); +} + +// Tests proper resolution heuristic, server and html field types when the +// server returns NO_SERVER_DATA, UNKNOWN_TYPE, and a valid type. TEST_F(FormStructureTest, ParseQueryResponse_UnknownType) { FormData form_data; FormFieldData field; @@ -5107,7 +5298,8 @@ TEST_F(FormStructureTest, ParseQueryResponse_UnknownType) { // Parse the response and update the field type predictions. std::vector<FormStructure*> forms{&form}; - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_EQ(form.field_count(), 3U); // Validate field 0. @@ -5184,7 +5376,8 @@ TEST_F(FormStructureTest, ParseQueryResponse) { std::string response_string; ASSERT_TRUE(response.SerializeToString(&response_string)); - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_GE(forms[0]->field_count(), 2U); ASSERT_GE(forms[1]->field_count(), 2U); @@ -5268,7 +5461,8 @@ TEST_F(FormStructureTest, ParseApiQueryResponse) { base::Base64Encode(response_string, &encoded_response_string); FormStructure::ParseApiQueryResponse(std::move(encoded_response_string), - forms, nullptr); + forms, test::GetEncodedSignatures(forms), + nullptr); // Verify that the form fields are properly filled with data retrieved from // the query. @@ -5311,6 +5505,7 @@ TEST_F(FormStructureTest, ParseApiQueryResponseWhenCannotParseProtoFromString) { std::string response_string = "invalid string that cannot be parsed"; FormStructure::ParseApiQueryResponse(std::move(response_string), forms, + test::GetEncodedSignatures(forms), nullptr); // Verify that the form fields remain intact because ParseApiQueryResponse @@ -5353,7 +5548,8 @@ TEST_F(FormStructureTest, ParseApiQueryResponseWhenPayloadNotBase64) { std::string response_string; ASSERT_TRUE(api_response.SerializeToString(&response_string)); - FormStructure::ParseApiQueryResponse(response_string, forms, nullptr); + FormStructure::ParseApiQueryResponse( + response_string, forms, test::GetEncodedSignatures(forms), nullptr); // Verify that the form fields remain intact because ParseApiQueryResponse // could not parse the server's response that was badly encoded. @@ -5389,7 +5585,8 @@ TEST_F(FormStructureTest, ParseQueryResponse_AuthorDefinedTypes) { std::string response_string; ASSERT_TRUE(response.SerializeToString(&response_string)); - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_GE(forms[0]->field_count(), 2U); // Server type is parsed from the response and is the end result type. @@ -5438,7 +5635,8 @@ TEST_F(FormStructureTest, ParseQueryResponse_RationalizeLoneField) { ASSERT_TRUE(response.SerializeToString(&response_string)); // Test that the expiry month field is rationalized away. - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_EQ(1U, forms.size()); ASSERT_EQ(4U, forms[0]->field_count()); EXPECT_EQ(NAME_FULL, forms[0]->field(0)->Type().GetStorableType()); @@ -5478,7 +5676,8 @@ TEST_F(FormStructureTest, ParseQueryResponse_RationalizeCCName) { ASSERT_TRUE(response.SerializeToString(&response_string)); // Test that the name fields are rationalized. - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_EQ(1U, forms.size()); ASSERT_EQ(3U, forms[0]->field_count()); EXPECT_EQ(NAME_FIRST, forms[0]->field(0)->Type().GetStorableType()); @@ -5529,7 +5728,8 @@ TEST_F(FormStructureTest, ParseQueryResponse_RationalizeMultiMonth_1) { ASSERT_TRUE(response.SerializeToString(&response_string)); // Test that the extra month field is rationalized away. - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_EQ(1U, forms.size()); ASSERT_EQ(5U, forms[0]->field_count()); EXPECT_EQ(CREDIT_CARD_NAME_FULL, @@ -5580,7 +5780,8 @@ TEST_F(FormStructureTest, ParseQueryResponse_RationalizeMultiMonth_2) { ASSERT_TRUE(response.SerializeToString(&response_string)); // Test that the extra month field is rationalized away. - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_EQ(1U, forms.size()); ASSERT_EQ(4U, forms[0]->field_count()); EXPECT_EQ(CREDIT_CARD_NAME_FULL, @@ -5667,7 +5868,8 @@ TEST_F(FormStructureTest, RationalizePhoneNumber_RunsOncePerSection) { FormStructure form_structure(form); std::vector<FormStructure*> forms; forms.push_back(&form_structure); - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); EXPECT_FALSE(form_structure.phone_rationalized_["fullName_1-default"]); form_structure.RationalizePhoneNumbersInSection("fullName_1-default"); @@ -5719,7 +5921,8 @@ TEST_F(FormStructureTest, RationalizeRepeatedFields_OneAddress) { forms.push_back(&form_structure); // Will call RationalizeFieldTypePredictions - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_EQ(1U, forms.size()); ASSERT_EQ(3U, forms[0]->field_count()); @@ -5771,7 +5974,8 @@ TEST_F(FormStructureTest, RationalizeRepreatedFields_TwoAddresses) { forms.push_back(&form_structure); // Will call RationalizeFieldTypePredictions - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_EQ(1U, forms.size()); ASSERT_EQ(4U, forms[0]->field_count()); @@ -5829,7 +6033,8 @@ TEST_F(FormStructureTest, RationalizeRepreatedFields_ThreeAddresses) { forms.push_back(&form_structure); // Will call RationalizeFieldTypePredictions - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_EQ(1U, forms.size()); ASSERT_EQ(5U, forms[0]->field_count()); @@ -5895,7 +6100,8 @@ TEST_F(FormStructureTest, RationalizeRepreatedFields_FourAddresses) { forms.push_back(&form_structure); // Will call RationalizeFieldTypePredictions - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_EQ(1U, forms.size()); ASSERT_EQ(6U, forms[0]->field_count()); @@ -5970,7 +6176,8 @@ TEST_F(FormStructureTest, RationalizeRepreatedFields_OneAddressEachSection) { forms.push_back(&form_structure); // Will call RationalizeFieldTypePredictions - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); // Billing ASSERT_EQ(1U, forms.size()); ASSERT_EQ(6U, forms[0]->field_count()); @@ -6112,7 +6319,8 @@ TEST_F( forms.push_back(&form_structure); // Will call RationalizeFieldTypePredictions - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_EQ(1U, forms.size()); ASSERT_EQ(15U, forms[0]->field_count()); @@ -6197,7 +6405,8 @@ TEST_F(FormStructureTest, ASSERT_TRUE(response.SerializeToString(&response_string)); // Will call RationalizeFieldTypePredictions - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); // Billing ASSERT_EQ(1U, forms.size()); ASSERT_EQ(6U, forms[0]->field_count()); @@ -6288,7 +6497,8 @@ TEST_F( std::string response_string; ASSERT_TRUE(response.SerializeToString(&response_string)); // Will call RationalizeFieldTypePredictions - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_EQ(1U, forms.size()); ASSERT_EQ(9U, forms[0]->field_count()); @@ -6382,7 +6592,8 @@ TEST_F(FormStructureTest, ASSERT_TRUE(response.SerializeToString(&response_string)); // Will call RationalizeFieldTypePredictions - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_EQ(1U, forms.size()); ASSERT_EQ(10U, forms[0]->field_count()); @@ -6508,7 +6719,8 @@ TEST_F(FormStructureTest, RationalizeRepreatedFields_CountryStateNoHeuristics) { ASSERT_TRUE(response.SerializeToString(&response_string)); // Will call RationalizeFieldTypePredictions - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_EQ(1U, forms.size()); ASSERT_EQ(14U, forms[0]->field_count()); @@ -6639,7 +6851,8 @@ TEST_F(FormStructureTest, ASSERT_TRUE(response.SerializeToString(&response_string)); // Will call RationalizeFieldTypePredictions - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_EQ(1U, forms.size()); ASSERT_EQ(14U, forms[0]->field_count()); @@ -6713,7 +6926,8 @@ TEST_F(FormStructureTest, RationalizeRepreatedFields_FirstFieldRationalized) { forms.push_back(&form_structure); // Will call RationalizeFieldTypePredictions - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_EQ(1U, forms.size()); ASSERT_EQ(5U, forms[0]->field_count()); @@ -6781,7 +6995,8 @@ TEST_F(FormStructureTest, RationalizeRepreatedFields_LastFieldRationalized) { forms.push_back(&form_structure); // Will call RationalizeFieldTypePredictions - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_EQ(1U, forms.size()); ASSERT_EQ(6U, forms[0]->field_count()); @@ -6852,7 +7067,8 @@ TEST_P(ParameterizedFormStructureTest, forms.push_back(&form_structure); // Will call RationalizeFieldTypePredictions - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_EQ(1U, forms.size()); ASSERT_EQ(4U, forms[0]->field_count()); @@ -6917,7 +7133,8 @@ TEST_P(ParameterizedFormStructureTest, NoServerDataCCFields_CVC_NoOverwrite) { forms.push_back(&form_structure); // Will call RationalizeFieldTypePredictions - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_EQ(1U, forms.size()); ASSERT_EQ(4U, forms[0]->field_count()); @@ -6992,7 +7209,8 @@ TEST_P(ParameterizedFormStructureTest, WithServerDataCCFields_CVC_NoOverwrite) { forms.push_back(&form_structure); // Will call RationalizeFieldTypePredictions - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_EQ(1U, forms.size()); ASSERT_EQ(4U, forms[0]->field_count()); @@ -7078,7 +7296,8 @@ TEST_P(RationalizationFieldTypeFilterTest, Rationalization_Rules_Filter_Out) { forms.push_back(&form_structure); // Will call RationalizeFieldTypePredictions - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_EQ(1U, forms.size()); ASSERT_EQ(4U, forms[0]->field_count()); @@ -7138,7 +7357,8 @@ TEST_P(RationalizationFieldTypeRelationshipsTest, forms.push_back(&form_structure); // Will call RationalizeFieldTypePredictions - FormStructure::ParseQueryResponse(response_string, forms, nullptr); + FormStructure::ParseQueryResponse(response_string, forms, + test::GetEncodedSignatures(forms), nullptr); ASSERT_EQ(1U, forms.size()); ASSERT_EQ(4U, forms[0]->field_count()); @@ -7168,11 +7388,11 @@ TEST_F(FormStructureTest, AllowBigForms) { std::vector<FormStructure*> forms; forms.push_back(&form_structure); - std::vector<std::string> encoded_signatures; + FormAndFieldSignatures encoded_signatures; AutofillQueryContents encoded_query; - ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms, &encoded_signatures, - &encoded_query)); + ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms, &encoded_query, + &encoded_signatures)); EXPECT_EQ(1u, encoded_signatures.size()); } @@ -7200,12 +7420,14 @@ TEST_F(FormStructureTest, CreateForPasswordManagerUpload) { FormSignature(1234), {FieldSignature(1), FieldSignature(10), FieldSignature(100)}); AutofillUploadContents upload; + FormAndFieldSignatures signatures; EXPECT_EQ(FormSignature(1234u), form->form_signature()); ASSERT_EQ(3u, form->field_count()); ASSERT_EQ(FieldSignature(100u), form->field(2)->GetFieldSignature()); EXPECT_TRUE(form->EncodeUploadRequest( {} /* available_field_types */, false /* form_was_autofilled */, - "" /*login_form_signature*/, true /*observed_submission*/, &upload)); + "" /*login_form_signature*/, true /*observed_submission*/, &upload, + &signatures)); } // Tests if a new logical form is started with the second appearance of a field diff --git a/chromium/components/autofill/core/browser/geo/country_names.cc b/chromium/components/autofill/core/browser/geo/country_names.cc index f632ff8f6ac..a5db1d0573d 100644 --- a/chromium/components/autofill/core/browser/geo/country_names.cc +++ b/chromium/components/autofill/core/browser/geo/country_names.cc @@ -130,7 +130,8 @@ const std::string CountryNames::GetCountryCodeForLocalizedCountryName( result = country_names_for_locale.GetCountryCode(country); // Put the country names for the locale into the cache. - localized_country_names_cache_.Put(locale_name, std::move(result)); + localized_country_names_cache_.Put(locale_name, + std::move(country_names_for_locale)); return result; } diff --git a/chromium/components/autofill/core/browser/geo/country_names_unittest.cc b/chromium/components/autofill/core/browser/geo/country_names_unittest.cc index 46481c05e4b..2f971cb599c 100644 --- a/chromium/components/autofill/core/browser/geo/country_names_unittest.cc +++ b/chromium/components/autofill/core/browser/geo/country_names_unittest.cc @@ -74,6 +74,9 @@ TEST(CountryNamesTest, GetCountryCodeForLocalizedCountryName) { TestCountryNames names("en_US"); EXPECT_EQ("AM", names.GetCountryCodeForLocalizedCountryName( ASCIIToUTF16("Armenien"), "de")); + // Check that there is no cache by requesting the same result twice. + EXPECT_EQ("AM", names.GetCountryCodeForLocalizedCountryName( + ASCIIToUTF16("Armenien"), "de")); EXPECT_EQ("AZ", names.GetCountryCodeForLocalizedCountryName( ASCIIToUTF16("Azerbeidzjan"), "nl")); } diff --git a/chromium/components/autofill/core/browser/metrics/form_event_logger_base.cc b/chromium/components/autofill/core/browser/metrics/form_event_logger_base.cc index 3aa2b9d378a..4b42968d295 100644 --- a/chromium/components/autofill/core/browser/metrics/form_event_logger_base.cc +++ b/chromium/components/autofill/core/browser/metrics/form_event_logger_base.cc @@ -97,6 +97,8 @@ void FormEventLoggerBase::OnDidShowSuggestions( OnSuggestionsShownOnce(); } + has_logged_autocomplete_off_ |= field.autocomplete_attribute == "off"; + RecordShowSuggestions(); } @@ -272,6 +274,11 @@ void FormEventLoggerBase::RecordFunnelAndKeyMetrics() { has_logged_suggestion_filled_); key_metrics_rows << Tr{} << "FillingAcceptance" << has_logged_suggestion_filled_; + UmaHistogramBoolean( + base::StrCat({"Autofill.Autocomplete.", + (has_logged_autocomplete_off_ ? "Off" : "NotOff"), + ".FillingAcceptance.", form_type_name_.c_str()}), + has_logged_suggestion_filled_); } if (has_logged_suggestion_filled_) { // Whether a filled form and submitted form required no fixes to filled diff --git a/chromium/components/autofill/core/browser/metrics/form_event_logger_base.h b/chromium/components/autofill/core/browser/metrics/form_event_logger_base.h index 5adfd9446d3..130c04e0e90 100644 --- a/chromium/components/autofill/core/browser/metrics/form_event_logger_base.h +++ b/chromium/components/autofill/core/browser/metrics/form_event_logger_base.h @@ -13,7 +13,6 @@ #include "components/autofill/core/browser/metrics/form_events.h" #include "components/autofill/core/browser/sync_utils.h" #include "components/autofill/core/common/form_field_data.h" -#include "components/autofill/core/common/signatures.h" namespace autofill { @@ -111,6 +110,7 @@ class FormEventLoggerBase { bool has_logged_user_hide_suggestions_ = false; bool has_logged_suggestions_shown_ = false; bool has_logged_suggestion_filled_ = false; + bool has_logged_autocomplete_off_ = false; bool has_logged_will_submit_ = false; bool has_logged_submitted_ = false; bool logged_suggestion_filled_was_server_data_ = false; diff --git a/chromium/components/autofill/core/browser/payments/autofill_offer_manager.cc b/chromium/components/autofill/core/browser/payments/autofill_offer_manager.cc new file mode 100644 index 00000000000..127700ebd5c --- /dev/null +++ b/chromium/components/autofill/core/browser/payments/autofill_offer_manager.cc @@ -0,0 +1,13 @@ +// 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. + +#include "components/autofill/core/browser/payments/autofill_offer_manager.h" + +namespace autofill { + +AutofillOfferManager::AutofillOfferManager() = default; + +AutofillOfferManager::~AutofillOfferManager() = default; + +} // namespace autofill
\ No newline at end of file diff --git a/chromium/components/autofill/core/browser/payments/autofill_offer_manager.h b/chromium/components/autofill/core/browser/payments/autofill_offer_manager.h new file mode 100644 index 00000000000..ce93163cb9f --- /dev/null +++ b/chromium/components/autofill/core/browser/payments/autofill_offer_manager.h @@ -0,0 +1,42 @@ +// 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. + +#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_AUTOFILL_OFFER_MANAGER_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_AUTOFILL_OFFER_MANAGER_H_ + +#include <string> +#include <vector> + +#include "base/strings/string16.h" + +namespace autofill { + +struct AutofillOfferData { + AutofillOfferData(); + ~AutofillOfferData(); + // The description of this offer. + base::string16 description; + // The name of this offer. + base::string16 name; + // The unique server id of this offer. + std::string offer_id; + // The ids of the cards this offer can be applied to. + std::vector<std::string> eligible_card_id; + // The merchant URL where this offer can be redeemed. + std::vector<std::string> merchant_domain; +}; + +// Manages all Autofill related offers. One per frame; owned by the +// AutofillManager. +class AutofillOfferManager { + public: + AutofillOfferManager(); + virtual ~AutofillOfferManager(); + AutofillOfferManager(const AutofillOfferManager&) = delete; + AutofillOfferManager& operator=(const AutofillOfferManager&) = delete; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_AUTOFILL_OFFER_MANAGER_H_
\ No newline at end of file diff --git a/chromium/components/autofill/core/browser/payments/autofill_save_card_infobar_delegate_mobile.cc b/chromium/components/autofill/core/browser/payments/autofill_save_card_infobar_delegate_mobile.cc index 762d557835b..8fdc3912d20 100644 --- a/chromium/components/autofill/core/browser/payments/autofill_save_card_infobar_delegate_mobile.cc +++ b/chromium/components/autofill/core/browser/payments/autofill_save_card_infobar_delegate_mobile.cc @@ -47,7 +47,7 @@ AutofillSaveCardInfoBarDelegateMobile::AutofillSaveCardInfoBarDelegateMobile( pref_service_(pref_service), had_user_interaction_(false), issuer_icon_id_(CreditCard::IconResourceId(card.network())), - card_label_(card.NetworkAndLastFourDigits()), + card_label_(card.CardIdentifierStringForAutofillDisplay()), card_sub_label_(card.AbbreviatedExpirationDateForDisplay(false)), card_last_four_digits_(card.LastFourDigits()), cardholder_name_(card.GetRawInfo(CREDIT_CARD_NAME_FULL)), @@ -72,6 +72,15 @@ AutofillSaveCardInfoBarDelegateMobile:: } } +// static +AutofillSaveCardInfoBarDelegateMobile* +AutofillSaveCardInfoBarDelegateMobile::FromInfobarDelegate( + infobars::InfoBarDelegate* delegate) { + return delegate->GetIdentifier() == AUTOFILL_CC_INFOBAR_DELEGATE_MOBILE + ? static_cast<AutofillSaveCardInfoBarDelegateMobile*>(delegate) + : nullptr; +} + void AutofillSaveCardInfoBarDelegateMobile::OnLegalMessageLinkClicked( GURL url) { infobar()->owner()->OpenURL(url, WindowOpenDisposition::NEW_FOREGROUND_TAB); diff --git a/chromium/components/autofill/core/browser/payments/autofill_save_card_infobar_delegate_mobile.h b/chromium/components/autofill/core/browser/payments/autofill_save_card_infobar_delegate_mobile.h index 64721013e55..f1f9011b1e2 100644 --- a/chromium/components/autofill/core/browser/payments/autofill_save_card_infobar_delegate_mobile.h +++ b/chromium/components/autofill/core/browser/payments/autofill_save_card_infobar_delegate_mobile.h @@ -39,6 +39,11 @@ class AutofillSaveCardInfoBarDelegateMobile : public ConfirmInfoBarDelegate { ~AutofillSaveCardInfoBarDelegateMobile() override; + // Returns |delegate| as an AutofillSaveCardInfoBarDelegateMobile, or nullptr + // if it is of another type. + static AutofillSaveCardInfoBarDelegateMobile* FromInfobarDelegate( + infobars::InfoBarDelegate* delegate); + bool upload() const { return upload_; } int issuer_icon_id() const { return issuer_icon_id_; } const base::string16& card_label() const { return card_label_; } @@ -58,7 +63,7 @@ class AutofillSaveCardInfoBarDelegateMobile : public ConfirmInfoBarDelegate { } // Called when a link in the legal message text was clicked. - void OnLegalMessageLinkClicked(GURL url); + virtual void OnLegalMessageLinkClicked(GURL url); // Google Pay branding is enabled with a flag and only for cards upstreamed // to Google. @@ -82,9 +87,9 @@ class AutofillSaveCardInfoBarDelegateMobile : public ConfirmInfoBarDelegate { // Updates and then saves the card using |cardholder_name|, // |expiration_date_month| and |expiration_date_year|, which were provided // as part of the iOS save card Infobar dialog. - bool UpdateAndAccept(base::string16 cardholder_name, - base::string16 expiration_date_month, - base::string16 expiration_date_year); + virtual bool UpdateAndAccept(base::string16 cardholder_name, + base::string16 expiration_date_month, + base::string16 expiration_date_year); #endif // defined(OS_IOS) private: diff --git a/chromium/components/autofill/core/browser/payments/credit_card_access_manager.cc b/chromium/components/autofill/core/browser/payments/credit_card_access_manager.cc index 1d306acd09c..3a1c0a8f5bf 100644 --- a/chromium/components/autofill/core/browser/payments/credit_card_access_manager.cc +++ b/chromium/components/autofill/core/browser/payments/credit_card_access_manager.cc @@ -70,11 +70,6 @@ CreditCardAccessManager::CreditCardAccessManager( base::WaitableEvent::InitialState::NOT_SIGNALED), can_fetch_unmask_details_(base::WaitableEvent::ResetPolicy::AUTOMATIC, base::WaitableEvent::InitialState::SIGNALED) { -#if !defined(OS_IOS) - // This is to initialize StrikeDatabase is if it hasn't been already, so that - // its cache would be loaded and ready to use when the first CCAM is created. - client_->GetStrikeDatabase(); -#endif } CreditCardAccessManager::~CreditCardAccessManager() {} @@ -154,7 +149,7 @@ bool CreditCardAccessManager::GetDeletionConfirmationText( return false; if (title) - title->assign(card->NetworkAndLastFourDigits()); + title->assign(card->CardIdentifierStringForAutofillDisplay()); if (body) { body->assign(l10n_util::GetStringUTF16( IDS_AUTOFILL_DELETE_CREDIT_CARD_SUGGESTION_CONFIRMATION_BODY)); diff --git a/chromium/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc b/chromium/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc index b507de35144..3c13e1d3de6 100644 --- a/chromium/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc +++ b/chromium/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc @@ -460,7 +460,7 @@ TEST_F(CreditCardAccessManagerTest, LocalCardGetDeletionConfirmationText) { card, &title, &body)); // |title| and |body| should be updated appropriately. - EXPECT_EQ(title, card->NetworkAndLastFourDigits()); + EXPECT_EQ(title, card->CardIdentifierStringForAutofillDisplay()); EXPECT_EQ(body, l10n_util::GetStringUTF16( IDS_AUTOFILL_DELETE_CREDIT_CARD_SUGGESTION_CONFIRMATION_BODY)); diff --git a/chromium/components/autofill/core/browser/payments/credit_card_fido_authenticator.cc b/chromium/components/autofill/core/browser/payments/credit_card_fido_authenticator.cc index 1260d1caf9a..6400469d8c9 100644 --- a/chromium/components/autofill/core/browser/payments/credit_card_fido_authenticator.cc +++ b/chromium/components/autofill/core/browser/payments/credit_card_fido_authenticator.cc @@ -75,6 +75,9 @@ void CreditCardFIDOAuthenticator::Authenticate( requester_ = requester; form_parsed_timestamp_ = form_parsed_timestamp; + // Cancel any previous pending WebAuthn requests. + authenticator()->Cancel(); + if (card_ && IsValidRequestOptions(request_options.Clone())) { current_flow_ = AUTHENTICATION_FLOW; GetAssertion(ParseRequestOptions(std::move(request_options))); @@ -85,6 +88,9 @@ void CreditCardFIDOAuthenticator::Authenticate( void CreditCardFIDOAuthenticator::Register(std::string card_authorization_token, base::Value creation_options) { + // Cancel any previous pending WebAuthn requests. + authenticator()->Cancel(); + // If |creation_options| is set, then must enroll a new credential. Otherwise // directly send request to payments for opting in. card_authorization_token_ = card_authorization_token; @@ -105,6 +111,10 @@ void CreditCardFIDOAuthenticator::Authorize( base::Value request_options) { requester_ = requester; card_authorization_token_ = card_authorization_token; + + // Cancel any previous pending WebAuthn requests. + authenticator()->Cancel(); + if (IsValidRequestOptions(request_options)) { // If user is already opted-in, then a new card is trying to be // authorized. Otherwise, a user with a credential on file is trying to @@ -116,6 +126,9 @@ void CreditCardFIDOAuthenticator::Authorize( } void CreditCardFIDOAuthenticator::OptOut() { + // Cancel any previous pending WebAuthn requests. + authenticator()->Cancel(); + current_flow_ = OPT_OUT_FLOW; card_authorization_token_ = std::string(); OptChange(); @@ -181,6 +194,8 @@ UserOptInIntention CreditCardFIDOAuthenticator::GetUserOptInIntention( } void CreditCardFIDOAuthenticator::CancelVerification() { + authenticator()->Cancel(); + current_flow_ = NONE_FLOW; // Full card request may not exist when this function is called. The full card // request is created in OnDidGetAssertion() but the flow can be cancelled @@ -193,6 +208,10 @@ void CreditCardFIDOAuthenticator::CancelVerification() { void CreditCardFIDOAuthenticator::OnWebauthnOfferDialogRequested( std::string card_authorization_token) { card_authorization_token_ = card_authorization_token; + + // Cancel any previous pending WebAuthn requests. + authenticator()->Cancel(); + AutofillMetrics::LogWebauthnOptInPromoShown( /*is_checkout_flow=*/!card_authorization_token_.empty()); @@ -651,7 +670,7 @@ base::Value CreditCardFIDOAuthenticator::ParseAssertionResponse( response.SetKey("credential_id", BytesToBase64(assertion_response->info->raw_id)); response.SetKey("authenticator_data", - BytesToBase64(assertion_response->authenticator_data)); + BytesToBase64(assertion_response->info->authenticator_data)); response.SetKey("client_data", BytesToBase64(assertion_response->info->client_data_json)); response.SetKey("signature", BytesToBase64(assertion_response->signature)); @@ -733,6 +752,7 @@ void CreditCardFIDOAuthenticator::LogWebauthnResult( return; } + // TODO(crbug.com/949269): Add metrics for revoked pending WebAuthn requests. AutofillMetrics::WebauthnResultMetric metric; switch (status) { case AuthenticatorStatus::SUCCESS: diff --git a/chromium/components/autofill/core/browser/payments/credit_card_save_manager.cc b/chromium/components/autofill/core/browser/payments/credit_card_save_manager.cc index 617c2f22a46..e9e8c203a20 100644 --- a/chromium/components/autofill/core/browser/payments/credit_card_save_manager.cc +++ b/chromium/components/autofill/core/browser/payments/credit_card_save_manager.cc @@ -86,9 +86,6 @@ CreditCardSaveManager::CreditCardSaveManager( payments_client_(payments_client), app_locale_(app_locale), personal_data_manager_(personal_data_manager) { - // This is to initialize StrikeDatabase is if it hasn't been already, so that - // its cache would be loaded and ready to use when the first CCSM is created. - client_->GetStrikeDatabase(); } CreditCardSaveManager::~CreditCardSaveManager() {} @@ -255,8 +252,6 @@ void CreditCardSaveManager::AttemptToOfferCardUploadSave( (should_request_expiration_date_from_user_ && personal_data_manager_->GetSyncSigninState() == AutofillSyncSigninState::kSignedInAndWalletSyncTransportEnabled)) { - DCHECK(base::FeatureList::IsEnabled( - features::kAutofillUpstreamEditableExpirationDate)); LogCardUploadDecisions(upload_decision_metrics_); pending_upload_request_origin_ = url::Origin(); return; @@ -731,43 +726,39 @@ int CreditCardSaveManager::GetDetectedValues() const { detected_values |= DetectedValue::HAS_GOOGLE_PAYMENTS_ACCOUNT; } - if (base::FeatureList::IsEnabled( - features::kAutofillUpstreamEditableExpirationDate)) { - // If expiration date month or expiration year are missing, signal that - // expiration date will be explicitly requested in the offer-to-save bubble. - if (!upload_request_.card - .GetInfo(AutofillType(CREDIT_CARD_EXP_MONTH), app_locale_) - .empty()) { - detected_values |= DetectedValue::CARD_EXPIRATION_MONTH; - } - if (!(upload_request_.card - .GetInfo(AutofillType(CREDIT_CARD_EXP_4_DIGIT_YEAR), app_locale_) - .empty())) { - detected_values |= DetectedValue::CARD_EXPIRATION_YEAR; - } + // If expiration date month or expiration year are missing, signal that + // expiration date will be explicitly requested in the offer-to-save bubble. + if (!upload_request_.card + .GetInfo(AutofillType(CREDIT_CARD_EXP_MONTH), app_locale_) + .empty()) { + detected_values |= DetectedValue::CARD_EXPIRATION_MONTH; + } + if (!(upload_request_.card + .GetInfo(AutofillType(CREDIT_CARD_EXP_4_DIGIT_YEAR), app_locale_) + .empty())) { + detected_values |= DetectedValue::CARD_EXPIRATION_YEAR; + } - // Set |USER_PROVIDED_EXPIRATION_DATE| if expiration date is detected as - // expired or missing. - if (detected_values & DetectedValue::CARD_EXPIRATION_MONTH && - detected_values & DetectedValue::CARD_EXPIRATION_YEAR) { - int month_value = 0, year_value = 0; - bool parsable = - base::StringToInt( - upload_request_.card.GetInfo(AutofillType(CREDIT_CARD_EXP_MONTH), - app_locale_), - &month_value) && - base::StringToInt( - upload_request_.card.GetInfo( - AutofillType(CREDIT_CARD_EXP_4_DIGIT_YEAR), app_locale_), - &year_value); - DCHECK(parsable); - if (!IsValidCreditCardExpirationDate(year_value, month_value, - AutofillClock::Now())) { - detected_values |= DetectedValue::USER_PROVIDED_EXPIRATION_DATE; - } - } else { + // Set |USER_PROVIDED_EXPIRATION_DATE| if expiration date is detected as + // expired or missing. + if (detected_values & DetectedValue::CARD_EXPIRATION_MONTH && + detected_values & DetectedValue::CARD_EXPIRATION_YEAR) { + int month_value = 0, year_value = 0; + bool parsable = + base::StringToInt(upload_request_.card.GetInfo( + AutofillType(CREDIT_CARD_EXP_MONTH), app_locale_), + &month_value) && + base::StringToInt( + upload_request_.card.GetInfo( + AutofillType(CREDIT_CARD_EXP_4_DIGIT_YEAR), app_locale_), + &year_value); + DCHECK(parsable); + if (!IsValidCreditCardExpirationDate(year_value, month_value, + AutofillClock::Now())) { detected_values |= DetectedValue::USER_PROVIDED_EXPIRATION_DATE; } + } else { + detected_values |= DetectedValue::USER_PROVIDED_EXPIRATION_DATE; } // If cardholder name is conflicting/missing and the user does NOT have a diff --git a/chromium/components/autofill/core/browser/payments/credit_card_save_manager_unittest.cc b/chromium/components/autofill/core/browser/payments/credit_card_save_manager_unittest.cc index 184326bc942..8798b034fb0 100644 --- a/chromium/components/autofill/core/browser/payments/credit_card_save_manager_unittest.cc +++ b/chromium/components/autofill/core/browser/payments/credit_card_save_manager_unittest.cc @@ -2619,9 +2619,6 @@ TEST_F( TEST_F( CreditCardSaveManagerTest, UploadCreditCard_ShouldRequestExpirationDate_ResetBetweenConsecutiveSaves) { - scoped_feature_list_.InitAndEnableFeature( - features::kAutofillUpstreamEditableExpirationDate); - // Create, fill and submit an address form in order to establish a recent // profile which can be selected for the upload request. FormData address_form; @@ -2670,9 +2667,6 @@ TEST_F( TEST_F( CreditCardSaveManagerTest, UploadCreditCard_WalletSyncTransportEnabled_ShouldNotRequestExpirationDate) { - scoped_feature_list_.InitAndEnableFeature( - features::kAutofillUpstreamEditableExpirationDate); - // Wallet Sync Transport is enabled. personal_data_.SetSyncAndSignInState( AutofillSyncSigninState::kSignedInAndWalletSyncTransportEnabled); @@ -2709,9 +2703,6 @@ TEST_F( TEST_F( CreditCardSaveManagerTest, UploadCreditCard_WalletSyncTransportNotEnabled_ShouldRequestExpirationDate) { - scoped_feature_list_.InitAndEnableFeature( - features::kAutofillUpstreamEditableExpirationDate); - // Wallet Sync Transport is not enabled. personal_data_.SetSyncAndSignInState( AutofillSyncSigninState::kSignedInAndSyncFeatureEnabled); @@ -2750,9 +2741,6 @@ TEST_F( TEST_F( CreditCardSaveManagerTest, UploadCreditCard_DoNotRequestExpirationDateIfMissingNameAndExpirationDate) { - scoped_feature_list_.InitAndEnableFeature( - features::kAutofillUpstreamEditableExpirationDate); - // Create, fill and submit an address form in order to establish a recent // profile which can be selected for the upload request. FormData address_form; @@ -2782,36 +2770,6 @@ TEST_F( } TEST_F(CreditCardSaveManagerTest, - UploadCreditCard_DoNotRequestExpirationDate_EditableExpDateOff) { - scoped_feature_list_.InitAndDisableFeature( - features::kAutofillUpstreamEditableExpirationDate); - // Create, fill and submit an address form in order to establish a recent - // profile which can be selected for the upload request. - FormData address_form; - test::CreateTestAddressFormData(&address_form); - FormsSeen(std::vector<FormData>(1, address_form)); - ManuallyFillAddressForm("John", "Smith", "77401", "US", &address_form); - FormSubmitted(address_form); - - // Set up our credit card form data. - FormData credit_card_form; - CreateTestCreditCardFormData(&credit_card_form, CreditCardFormOptions()); - FormsSeen(std::vector<FormData>(1, credit_card_form)); - - // Edit the data, and submit. - credit_card_form.fields[0].value = ASCIIToUTF16("John Smith"); - credit_card_form.fields[1].value = ASCIIToUTF16("4111111111111111"); - credit_card_form.fields[2].value = ASCIIToUTF16(""); - credit_card_form.fields[3].value = ASCIIToUTF16(""); - credit_card_form.fields[4].value = ASCIIToUTF16("123"); - - base::HistogramTester histogram_tester; - FormSubmitted(credit_card_form); - EXPECT_FALSE(autofill_client_.ConfirmSaveCardLocallyWasCalled()); - EXPECT_FALSE(credit_card_save_manager_->CreditCardWasUploaded()); -} - -TEST_F(CreditCardSaveManagerTest, UploadCreditCard_RequestExpirationDateViaExpDateFixFlow) { #if defined(OS_IOS) // iOS should always provide a valid expiration date when attempting to @@ -2824,8 +2782,6 @@ TEST_F(CreditCardSaveManagerTest, } #endif - scoped_feature_list_.InitAndEnableFeature( - features::kAutofillUpstreamEditableExpirationDate); // Create, fill and submit an address form in order to establish a recent // profile which can be selected for the upload request. FormData address_form; @@ -2878,8 +2834,6 @@ TEST_F(CreditCardSaveManagerTest, } #endif - scoped_feature_list_.InitAndEnableFeature( - features::kAutofillUpstreamEditableExpirationDate); // Create, fill and submit an address form in order to establish a recent // profile which can be selected for the upload request. FormData address_form; @@ -2932,8 +2886,6 @@ TEST_F(CreditCardSaveManagerTest, } #endif - scoped_feature_list_.InitAndEnableFeature( - features::kAutofillUpstreamEditableExpirationDate); // Create, fill and submit an address form in order to establish a recent // profile which can be selected for the upload request. FormData address_form; @@ -2986,8 +2938,6 @@ TEST_F(CreditCardSaveManagerTest, } #endif - scoped_feature_list_.InitAndEnableFeature( - features::kAutofillUpstreamEditableExpirationDate); // Create, fill and submit an address form in order to establish a recent // profile which can be selected for the upload request. FormData address_form; @@ -3041,8 +2991,6 @@ TEST_F( } #endif - scoped_feature_list_.InitAndEnableFeature( - features::kAutofillUpstreamEditableExpirationDate); // Create, fill and submit an address form in order to establish a recent // profile which can be selected for the upload request. FormData address_form; diff --git a/chromium/components/autofill/core/browser/payments/legal_message_line_unittest.cc b/chromium/components/autofill/core/browser/payments/legal_message_line_unittest.cc index 257e22c22b2..1234667a475 100644 --- a/chromium/components/autofill/core/browser/payments/legal_message_line_unittest.cc +++ b/chromium/components/autofill/core/browser/payments/legal_message_line_unittest.cc @@ -10,6 +10,7 @@ #include "base/check_op.h" #include "base/json/json_reader.h" +#include "base/logging.h" #include "base/no_destructor.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" diff --git a/chromium/components/autofill/core/browser/payments/local_card_migration_manager.cc b/chromium/components/autofill/core/browser/payments/local_card_migration_manager.cc index 742c4b8c80a..ec61627e313 100644 --- a/chromium/components/autofill/core/browser/payments/local_card_migration_manager.cc +++ b/chromium/components/autofill/core/browser/payments/local_card_migration_manager.cc @@ -41,9 +41,6 @@ LocalCardMigrationManager::LocalCardMigrationManager( payments_client_(payments_client), app_locale_(app_locale), personal_data_manager_(personal_data_manager) { - // This is to initialize StrikeDatabase is if it hasn't been already, so that - // its cache would be loaded and ready to use when the first LCMM is created. - client_->GetStrikeDatabase(); } LocalCardMigrationManager::~LocalCardMigrationManager() {} diff --git a/chromium/components/autofill/core/browser/payments/payments_client.cc b/chromium/components/autofill/core/browser/payments/payments_client.cc index 10db6a376e2..f340bc58f33 100644 --- a/chromium/components/autofill/core/browser/payments/payments_client.cc +++ b/chromium/components/autofill/core/browser/payments/payments_client.cc @@ -218,6 +218,12 @@ base::Value BuildCreditCardDictionary(const CreditCard& credit_card, SetStringIfNotEmpty(credit_card, CREDIT_CARD_NAME_FULL, app_locale, "cardholder_name", card); + if (base::FeatureList::IsEnabled( + features::kAutofillEnableCardNicknameUpstream) && + credit_card.HasValidNickname()) { + card.SetKey("nickname", base::Value(credit_card.nickname())); + } + card.SetKey("encrypted_pan", base::Value("__param:" + pan_field_name)); return card; } @@ -819,6 +825,13 @@ class UploadCardRequest : public PaymentsRequest { if (base::StringToInt(exp_year, &value)) request_dict.SetKey("expiration_year", base::Value(value)); + if (base::FeatureList::IsEnabled( + features::kAutofillEnableCardNicknameUpstream) && + request_details_.card.HasValidNickname()) { + request_dict.SetKey("nickname", + base::Value(request_details_.card.nickname())); + } + SetActiveExperiments(request_details_.active_experiments, request_dict); const base::string16 pan = request_details_.card.GetInfo( diff --git a/chromium/components/autofill/core/browser/payments/payments_client_unittest.cc b/chromium/components/autofill/core/browser/payments/payments_client_unittest.cc index ac12ca2ff90..821f70b48fc 100644 --- a/chromium/components/autofill/core/browser/payments/payments_client_unittest.cc +++ b/chromium/components/autofill/core/browser/payments/payments_client_unittest.cc @@ -235,12 +235,16 @@ class PaymentsClientTest : public testing::Test { // Issue an UploadCard request. This requires an OAuth token before starting // the request. - void StartUploading(bool include_cvc) { + void StartUploading(bool include_cvc, bool include_nickname = false) { PaymentsClient::UploadRequestDetails request_details; request_details.billing_customer_number = 111222333444; request_details.card = test::GetCreditCard(); if (include_cvc) request_details.cvc = base::ASCIIToUTF16("123"); + if (include_nickname) { + upstream_nickname_ = base::ASCIIToUTF16("grocery"); + request_details.card.SetNickname(upstream_nickname_); + } request_details.context_token = base::ASCIIToUTF16("context token"); request_details.risk_data = "some risk data"; request_details.app_locale = "language-LOCALE"; @@ -251,7 +255,8 @@ class PaymentsClientTest : public testing::Test { } #if !defined(OS_ANDROID) && !defined(OS_IOS) - void StartMigrating(bool has_cardholder_name) { + void StartMigrating(bool has_cardholder_name, + bool set_nickname_for_first_card = false) { PaymentsClient::MigrationRequestDetails request_details; request_details.context_token = base::ASCIIToUTF16("context token"); request_details.risk_data = "some risk data"; @@ -259,6 +264,8 @@ class PaymentsClientTest : public testing::Test { migratable_credit_cards_.clear(); CreditCard card1 = test::GetCreditCard(); + if (set_nickname_for_first_card) + card1.SetNickname(base::ASCIIToUTF16("grocery")); CreditCard card2 = test::GetCreditCard2(); if (!has_cardholder_name) { card1.SetRawInfo(CREDIT_CARD_NAME_FULL, base::UTF8ToUTF16("")); @@ -313,6 +320,8 @@ class PaymentsClientTest : public testing::Test { // A list of card BIN ranges supported by Google Payments, returned from a // GetDetails upload save preflight call. std::vector<std::pair<int, int>> supported_card_bin_ranges_; + // The nickname name in the UploadRequest that was supposed to be saved. + base::string16 upstream_nickname_; #if !defined(OS_ANDROID) && !defined(OS_IOS) // Credit cards to be upload saved during a local credit card migration call. @@ -895,6 +904,41 @@ TEST_F(PaymentsClientTest, UploadDoesNotIncludeCvcInRequestIfNotProvided) { EXPECT_TRUE(GetUploadData().find("&s7e_13_cvc=") == std::string::npos); } +TEST_F(PaymentsClientTest, UploadIncludesCardNickname) { + scoped_feature_list_.InitAndEnableFeature( + features::kAutofillEnableCardNicknameUpstream); + + StartUploading(/*include_cvc=*/true, /*include_nickname=*/true); + IssueOAuthToken(); + + // Card nickname was set. + EXPECT_TRUE(GetUploadData().find("nickname") != std::string::npos); + EXPECT_TRUE(GetUploadData().find(base::UTF16ToUTF8(upstream_nickname_)) != + std::string::npos); +} + +TEST_F(PaymentsClientTest, UploadDoesNotIncludeCardNicknameFlagDisabled) { + scoped_feature_list_.InitAndDisableFeature( + features::kAutofillEnableCardNicknameUpstream); + + StartUploading(/*include_cvc=*/true, /*include_nickname=*/true); + IssueOAuthToken(); + + // Card nickname was not set. + EXPECT_FALSE(GetUploadData().find("nickname") != std::string::npos); +} + +TEST_F(PaymentsClientTest, UploadDoesNotIncludeCardNicknameEmptyNickname) { + scoped_feature_list_.InitAndEnableFeature( + features::kAutofillEnableCardNicknameUpstream); + + StartUploading(/*include_cvc=*/true, /*include_nickname=*/false); + IssueOAuthToken(); + + // Card nickname was not set. + EXPECT_FALSE(GetUploadData().find("nickname") != std::string::npos); +} + TEST_F(PaymentsClientTest, UnmaskMissingPan) { StartUnmasking(CardUnmaskOptions()); ReturnResponse(net::HTTP_OK, "{}"); @@ -1087,6 +1131,37 @@ TEST_F(PaymentsClientTest, EXPECT_TRUE(GetUploadData().find("full_sync_enabled") != std::string::npos); } +TEST_F(PaymentsClientTest, MigrationRequestIncludesCardNickname) { + scoped_feature_list_.InitAndEnableFeature( + features::kAutofillEnableCardNicknameUpstream); + + StartMigrating(/*has_cardholder_name=*/true, + /*set_nickname_to_first_card=*/true); + IssueOAuthToken(); + + // Nickname was set for the first card. + std::size_t pos = GetUploadData().find("nickname"); + EXPECT_TRUE(pos != std::string::npos); + EXPECT_TRUE(GetUploadData().find(base::UTF16ToUTF8( + migratable_credit_cards_[0].credit_card().nickname())) != + std::string::npos); + + // Nickname was not set for the second card. + EXPECT_FALSE(GetUploadData().find("nickname", pos + 1) != std::string::npos); +} + +TEST_F(PaymentsClientTest, MigrationRequestExcludesCardNicknameIfFlagDisabled) { + scoped_feature_list_.InitAndDisableFeature( + features::kAutofillEnableCardNicknameUpstream); + + StartMigrating(/*has_cardholder_name=*/true, + /*set_nickname_for_first_card=*/true); + IssueOAuthToken(); + + // Nickname was not set. + EXPECT_FALSE(GetUploadData().find("nickname") != std::string::npos); +} + TEST_F(PaymentsClientTest, MigrationSuccessWithSaveResult) { StartMigrating(/*has_cardholder_name=*/true); IssueOAuthToken(); diff --git a/chromium/components/autofill/core/browser/payments/test_internal_authenticator.cc b/chromium/components/autofill/core/browser/payments/test_internal_authenticator.cc new file mode 100644 index 00000000000..68a073b8114 --- /dev/null +++ b/chromium/components/autofill/core/browser/payments/test_internal_authenticator.cc @@ -0,0 +1,15 @@ +// 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. + +#include "components/autofill/core/browser/payments/test_internal_authenticator.h" + +namespace autofill { + +void TestInternalAuthenticator::IsUserVerifyingPlatformAuthenticatorAvailable( + blink::mojom::Authenticator:: + IsUserVerifyingPlatformAuthenticatorAvailableCallback callback) { + std::move(callback).Run(false); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/payments/test_internal_authenticator.h b/chromium/components/autofill/core/browser/payments/test_internal_authenticator.h index ec6ec8902ba..aecafe23605 100644 --- a/chromium/components/autofill/core/browser/payments/test_internal_authenticator.h +++ b/chromium/components/autofill/core/browser/payments/test_internal_authenticator.h @@ -28,7 +28,7 @@ class TestInternalAuthenticator : public InternalAuthenticator { void IsUserVerifyingPlatformAuthenticatorAvailable( blink::mojom::Authenticator:: IsUserVerifyingPlatformAuthenticatorAvailableCallback callback) - override {} + override; void Cancel() override {} }; diff --git a/chromium/components/autofill/core/browser/personal_data_manager.cc b/chromium/components/autofill/core/browser/personal_data_manager.cc index e45d579f73f..aa552996904 100644 --- a/chromium/components/autofill/core/browser/personal_data_manager.cc +++ b/chromium/components/autofill/core/browser/personal_data_manager.cc @@ -445,11 +445,6 @@ void PersonalDataManager::OnWebDataServiceRequestDone( ReceiveLoadedDbValues(h, result.get(), &pending_server_creditcards_query_, &server_credit_cards_); - - // If the user has a saved unmasked server card and the experiment is - // disabled, force mask all cards back to the unsaved state. - if (!OfferStoreUnmaskedCards(is_off_the_record_)) - ResetFullServerCards(); } break; case AUTOFILL_CLOUDTOKEN_RESULT: @@ -2067,12 +2062,17 @@ std::vector<Suggestion> PersonalDataManager::GetSuggestionsForCards( ? Suggestion::PREFIX_MATCH : Suggestion::SUBSTRING_MATCH; + // Get the nickname for the card suggestion, which may not be the same as + // the card's nickname if there are duplicates of the card on file. + base::string16 suggestion_nickname = + GetDisplayNicknameForCreditCard(*credit_card); + // If the value is the card number, the label is the expiration date. // Otherwise the label is the card number, or if that is empty the // cardholder name. The label should never repeat the value. if (type.GetStorableType() == CREDIT_CARD_NUMBER) { - suggestion->value = - credit_card->CardIdentifierStringForAutofillDisplay(); + suggestion->value = credit_card->CardIdentifierStringForAutofillDisplay( + suggestion_nickname); #if defined(OS_ANDROID) || defined(OS_IOS) suggestion->label = credit_card->GetInfo( @@ -2082,9 +2082,10 @@ std::vector<Suggestion> PersonalDataManager::GetSuggestionsForCards( #endif // defined(OS_ANDROID) || defined(OS_IOS) } else if (credit_card->number().empty()) { - // TODO(crbug/1059087): Update suggestion label with nickname for - // empty-number local cards when nickname is supported for local card. - if (type.GetStorableType() != CREDIT_CARD_NAME_FULL) { + DCHECK_EQ(credit_card->record_type(), CreditCard::LOCAL_CARD); + if (credit_card->HasValidNickname()) { + suggestion->label = credit_card->nickname(); + } else if (type.GetStorableType() != CREDIT_CARD_NAME_FULL) { suggestion->label = credit_card->GetInfo( AutofillType(CREDIT_CARD_NAME_FULL), app_locale_); } @@ -2096,7 +2097,8 @@ std::vector<Suggestion> PersonalDataManager::GetSuggestionsForCards( suggestion->label = base::FeatureList::IsEnabled(features::kAutofillKeyboardAccessory) ? credit_card->ObfuscatedLastFourDigits() - : credit_card->CardIdentifierStringForAutofillDisplay(); + : credit_card->CardIdentifierStringForAutofillDisplay( + suggestion_nickname); #elif defined(OS_IOS) // E.g. "••••1234"". suggestion->label = credit_card->ObfuscatedLastFourDigits(); @@ -2651,4 +2653,27 @@ void PersonalDataManager::MigrateUserOptedInWalletSyncTransportIfNeeded() { /*opted_in=*/true); } +base::string16 PersonalDataManager::GetDisplayNicknameForCreditCard( + const CreditCard& card) const { + if (!base::FeatureList::IsEnabled( + features::kAutofillEnableCardNicknameManagement)) { + return base::string16(); + } + + // Always prefer a local nickname if available. + if (card.HasValidNickname() && card.record_type() == CreditCard::LOCAL_CARD) + return card.nickname(); + // Either the card a) has no nickname or b) is a server card and we would + // prefer to use the nickname of a local card. + std::vector<CreditCard*> candidates = GetCreditCards(); + for (CreditCard* candidate : candidates) { + if (candidate->guid() != card.guid() && candidate->HasSameNumberAs(card) && + candidate->HasValidNickname()) { + return candidate->nickname(); + } + } + // Fall back to nickname of |card|, which may be empty. + return card.nickname(); +} + } // namespace autofill diff --git a/chromium/components/autofill/core/browser/personal_data_manager.h b/chromium/components/autofill/core/browser/personal_data_manager.h index 1e787a4d3c0..42d68aa0a67 100644 --- a/chromium/components/autofill/core/browser/personal_data_manager.h +++ b/chromium/components/autofill/core/browser/personal_data_manager.h @@ -752,6 +752,12 @@ class PersonalDataManager : public KeyedService, // migrating from using email to Gaia ID as th account identifier. void MigrateUserOptedInWalletSyncTransportIfNeeded(); + // Return a nickname for the |card| to display. This is generally the nickname + // stored in |card|, unless |card| exists as a local and a server copy. In + // this case, we prefer the nickname of the local if it is defined. If only + // one copy has a nickname, take that. + base::string16 GetDisplayNicknameForCreditCard(const CreditCard& card) const; + // Stores the |app_locale| supplied on construction. const std::string app_locale_; diff --git a/chromium/components/autofill/core/browser/personal_data_manager_unittest.cc b/chromium/components/autofill/core/browser/personal_data_manager_unittest.cc index 766044927d5..10d3b58f676 100644 --- a/chromium/components/autofill/core/browser/personal_data_manager_unittest.cc +++ b/chromium/components/autofill/core/browser/personal_data_manager_unittest.cc @@ -1058,6 +1058,7 @@ TEST_F(PersonalDataManagerTest, AddUpdateRemoveCreditCards) { CreditCard credit_card0(base::GenerateGUID(), test::kEmptyOrigin); test::SetCreditCardInfo(&credit_card0, "John Dillinger", "4234567890123456" /* Visa */, "01", "2999", "1"); + credit_card0.SetNickname(base::ASCIIToUTF16("card zero")); CreditCard credit_card1(base::GenerateGUID(), test::kEmptyOrigin); test::SetCreditCardInfo(&credit_card1, "Bonnie Parker", @@ -1068,6 +1069,7 @@ TEST_F(PersonalDataManagerTest, AddUpdateRemoveCreditCards) { test::SetCreditCardInfo(&credit_card2, "Clyde Barrow", "378282246310005" /* American Express */, "04", "2999", "1"); + credit_card2.SetNickname(base::ASCIIToUTF16("card two")); // Add two test credit cards to the database. personal_data_->AddCreditCard(credit_card0); @@ -1082,6 +1084,7 @@ TEST_F(PersonalDataManagerTest, AddUpdateRemoveCreditCards) { // Update, remove, and add. credit_card0.SetRawInfo(CREDIT_CARD_NAME_FULL, base::ASCIIToUTF16("Joe")); + credit_card0.SetNickname(base::ASCIIToUTF16("new card zero")); personal_data_->UpdateCreditCard(credit_card0); RemoveByGUIDFromPersonalDataManager(credit_card1.guid()); personal_data_->AddCreditCard(credit_card2); @@ -1433,136 +1436,6 @@ TEST_F(PersonalDataManagerTest, KeepExistingLocalDataOnSignIn) { EXPECT_EQ(0, local_card.Compare(*personal_data_->GetCreditCards()[0])); } -// Makes sure that full cards are re-masked when full PAN storage is off. -TEST_F(PersonalDataManagerTest, RefuseToStoreFullCard) { -// On Linux this should be disabled automatically. Elsewhere, only if the -// flag is passed. -#if defined(OS_LINUX) && !defined(OS_CHROMEOS) - EXPECT_FALSE(base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kDisableOfferStoreUnmaskedWalletCards)); -#else - base::CommandLine::ForCurrentProcess()->AppendSwitch( - switches::kDisableOfferStoreUnmaskedWalletCards); -#endif - - std::vector<CreditCard> server_cards; - server_cards.push_back(CreditCard(CreditCard::FULL_SERVER_CARD, "c789")); - test::SetCreditCardInfo(&server_cards.back(), "Clyde Barrow", - "378282246310005" /* American Express */, "04", - "2999", "1"); - SetServerCards(server_cards); - personal_data_->Refresh(); - - WaitForOnPersonalDataChanged(); - - ASSERT_EQ(1U, personal_data_->GetCreditCards().size()); - EXPECT_EQ(CreditCard::MASKED_SERVER_CARD, - personal_data_->GetCreditCards()[0]->record_type()); -} - -// Makes sure that full cards are only added as masked card when full PAN -// storage is disabled. -TEST_F(PersonalDataManagerTest, AddFullCardAsMaskedCard) { -// On Linux this should be disabled automatically. Elsewhere, only if the -// flag is passed. -#if defined(OS_LINUX) && !defined(OS_CHROMEOS) - EXPECT_FALSE(base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kDisableOfferStoreUnmaskedWalletCards)); -#else - base::CommandLine::ForCurrentProcess()->AppendSwitch( - switches::kDisableOfferStoreUnmaskedWalletCards); -#endif - - CreditCard server_card(CreditCard::FULL_SERVER_CARD, "c789"); - test::SetCreditCardInfo(&server_card, "Clyde Barrow", - "378282246310005" /* American Express */, "04", - "2999", "1"); - - personal_data_->AddFullServerCreditCard(server_card); - - WaitForOnPersonalDataChanged(); - - ASSERT_EQ(1U, personal_data_->GetCreditCards().size()); - EXPECT_EQ(CreditCard::MASKED_SERVER_CARD, - personal_data_->GetCreditCards()[0]->record_type()); -} - -TEST_F(PersonalDataManagerTest, OfferStoreUnmaskedCards) { -#if defined(OS_CHROMEOS) || defined(OS_WIN) || defined(OS_MACOSX) || \ - defined(OS_IOS) || defined(OS_ANDROID) || defined(OS_FUCHSIA) - bool should_offer = true; -#elif defined(OS_LINUX) - bool should_offer = false; -#endif - EXPECT_EQ(should_offer, OfferStoreUnmaskedCards(/*is_off_the_record=*/false)); -} - -// Tests that OfferStoreUnmaskedCards always returns false if the user is off -// the record. -TEST_F(PersonalDataManagerTest, OfferStoreUnmaskedCards_OffTheRecord) { - EXPECT_EQ(false, OfferStoreUnmaskedCards(/*is_off_the_record=*/true)); -} - -// Tests that UpdateServerCreditCard can be used to mask or unmask server cards. -TEST_F(PersonalDataManagerTest, UpdateServerCreditCards) { - EnableWalletCardImport(); - - std::vector<CreditCard> server_cards; - server_cards.push_back(CreditCard(CreditCard::MASKED_SERVER_CARD, "a123")); - test::SetCreditCardInfo(&server_cards.back(), "John Dillinger", - "3456" /* Visa */, "01", "2999", "1"); - server_cards.back().SetNetworkForMaskedCard(kVisaCard); - - server_cards.push_back(CreditCard(CreditCard::MASKED_SERVER_CARD, "b456")); - test::SetCreditCardInfo(&server_cards.back(), "Bonnie Parker", - "5100" /* Mastercard */, "12", "2999", "1"); - server_cards.back().SetNetworkForMaskedCard(kMasterCard); - - server_cards.push_back(CreditCard(CreditCard::FULL_SERVER_CARD, "c789")); - test::SetCreditCardInfo(&server_cards.back(), "Clyde Barrow", - "378282246310005" /* American Express */, "04", - "2999", "1"); - - SetServerCards(server_cards); - personal_data_->Refresh(); - - WaitForOnPersonalDataChanged(); - - ASSERT_EQ(3U, personal_data_->GetCreditCards().size()); - if (!OfferStoreUnmaskedCards(/*is_off_the_record=*/false)) { - for (CreditCard* card : personal_data_->GetCreditCards()) { - EXPECT_EQ(CreditCard::MASKED_SERVER_CARD, card->record_type()); - } - // The rest of this test doesn't work if we're force-masking all unmasked - // cards. - return; - } - - // The GUIDs will be different, so just compare the data. - for (size_t i = 0; i < 3; ++i) - EXPECT_EQ(0, server_cards[i].Compare(*personal_data_->GetCreditCards()[i])); - - CreditCard* unmasked_card = &server_cards.front(); - unmasked_card->set_record_type(CreditCard::FULL_SERVER_CARD); - unmasked_card->SetNumber(base::ASCIIToUTF16("4234567890123456")); - personal_data_->UpdateServerCreditCard(*unmasked_card); - - WaitForOnPersonalDataChanged(); - - for (size_t i = 0; i < 3; ++i) - EXPECT_EQ(0, server_cards[i].Compare(*personal_data_->GetCreditCards()[i])); - - CreditCard* remasked_card = &server_cards.back(); - remasked_card->set_record_type(CreditCard::MASKED_SERVER_CARD); - remasked_card->SetNumber(base::ASCIIToUTF16("0005")); - personal_data_->UpdateServerCreditCard(*remasked_card); - - WaitForOnPersonalDataChanged(); - - for (size_t i = 0; i < 3; ++i) - EXPECT_EQ(0, server_cards[i].Compare(*personal_data_->GetCreditCards()[i])); -} - TEST_F(PersonalDataManagerTest, AddProfilesAndCreditCards) { AutofillProfile profile0(base::GenerateGUID(), test::kEmptyOrigin); test::SetProfileInfo(&profile0, "Marion", "Mitchell", "Morrison", @@ -3345,7 +3218,7 @@ TEST_F(PersonalDataManagerTest, GetCreditCardSuggestions_LocalCardsRanking) { std::vector<Suggestion> suggestions = personal_data_->GetCreditCardSuggestions( AutofillType(CREDIT_CARD_NAME_FULL), - /* field_contents= */ base::string16(), + /*field_contents=*/base::string16(), /*include_server_cards=*/true); ASSERT_EQ(3U, suggestions.size()); @@ -3394,7 +3267,7 @@ TEST_F(PersonalDataManagerTest, std::vector<Suggestion> suggestions = personal_data_->GetCreditCardSuggestions( AutofillType(CREDIT_CARD_NAME_FULL), - /* field_contents= */ base::string16(), + /*field_contents=*/base::string16(), /*include_server_cards=*/true); ASSERT_EQ(5U, suggestions.size()); @@ -3448,7 +3321,7 @@ TEST_F(PersonalDataManagerTest, std::vector<Suggestion> suggestions = personal_data_->GetCreditCardSuggestions( AutofillType(CREDIT_CARD_NAME_FULL), - /* field_contents= */ base::string16(), + /*field_contents=*/base::string16(), /*include_server_cards=*/true); ASSERT_EQ(0U, suggestions.size()); } @@ -3498,7 +3371,7 @@ TEST_F(PersonalDataManagerTest, std::vector<Suggestion> suggestions = personal_data_->GetCreditCardSuggestions( AutofillType(CREDIT_CARD_NAME_FULL), - /* field_contents= */ base::string16(), + /*field_contents=*/base::string16(), /*include_server_cards=*/true); ASSERT_EQ(0U, suggestions.size()); } @@ -3563,7 +3436,7 @@ TEST_F(PersonalDataManagerTest, GetCreditCardSuggestions_ExpiredCards) { std::vector<Suggestion> suggestions = personal_data_->GetCreditCardSuggestions( AutofillType(CREDIT_CARD_NAME_FULL), - /* field_contents= */ base::string16(), + /*field_contents=*/base::string16(), /* include_server_cards= */ true); ASSERT_EQ(3U, suggestions.size()); @@ -3691,7 +3564,8 @@ TEST_F(PersonalDataManagerTest, // Test that a card that doesn't have a number is not shown in the suggestions // when querying credit cards by their number. -TEST_F(PersonalDataManagerTest, GetCreditCardSuggestions_NumberMissing) { +TEST_F(PersonalDataManagerTest, + GetCreditCardSuggestions_NumberMissing_QueryNumberField) { // Create one normal credit card and one credit card with the number missing. ASSERT_EQ(0U, personal_data_->GetCreditCards().size()); @@ -3723,7 +3597,7 @@ TEST_F(PersonalDataManagerTest, GetCreditCardSuggestions_NumberMissing) { std::vector<Suggestion> suggestions = personal_data_->GetCreditCardSuggestions( AutofillType(CREDIT_CARD_NUMBER), - /* field_contents= */ base::string16(), + /*field_contents=*/base::string16(), /*include_server_cards=*/true); ASSERT_EQ(1U, suggestions.size()); EXPECT_EQ(base::UTF8ToUTF16(std::string("Amex ") + @@ -3737,6 +3611,36 @@ TEST_F(PersonalDataManagerTest, GetCreditCardSuggestions_NumberMissing) { #endif // defined (OS_ANDROID) || defined(OS_IOS) } +// Test that a card that doesn't have a number is shown in the suggestion list +// with nickname if a non-number field is queried. +TEST_F(PersonalDataManagerTest, + GetCreditCardSuggestions_NumberMissing_QueryNonNumberField) { + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitAndEnableFeature( + features::kAutofillEnableSurfacingServerCardNickname); + ASSERT_EQ(0U, personal_data_->GetCreditCards().size()); + + CreditCard credit_card("1141084B-72D7-4B73-90CF-3D6AC154673B", + test::kEmptyOrigin); + test::SetCreditCardInfo(&credit_card, "John Dillinger", "", "01", "2999", + "1"); + credit_card.SetNickname(base::UTF8ToUTF16("nickname")); + personal_data_->AddCreditCard(credit_card); + + // Make sure everything is set up correctly. + WaitForOnPersonalDataChanged(); + ASSERT_EQ(1U, personal_data_->GetCreditCards().size()); + + // Ensures the suggestion label is the card's nickname. + std::vector<Suggestion> suggestions = + personal_data_->GetCreditCardSuggestions( + AutofillType(CREDIT_CARD_NAME_FULL), + /*field_contents=*/base::string16(), + /*include_server_cards=*/true); + ASSERT_EQ(1U, suggestions.size()); + EXPECT_EQ(base::UTF8ToUTF16("nickname"), suggestions[0].label); +} + // Tests the suggestions of duplicate local and server credit cards. TEST_F(PersonalDataManagerTest, GetCreditCardSuggestions_ServerDuplicates) { EnableWalletCardImport(); @@ -3777,7 +3681,7 @@ TEST_F(PersonalDataManagerTest, GetCreditCardSuggestions_ServerDuplicates) { std::vector<Suggestion> suggestions = personal_data_->GetCreditCardSuggestions( AutofillType(CREDIT_CARD_NAME_FULL), - /* field_contents= */ base::string16(), + /*field_contents=*/base::string16(), /*include_server_cards=*/true); ASSERT_EQ(3U, suggestions.size()); EXPECT_EQ(base::ASCIIToUTF16("John Dillinger"), suggestions[0].value); @@ -3785,7 +3689,7 @@ TEST_F(PersonalDataManagerTest, GetCreditCardSuggestions_ServerDuplicates) { EXPECT_EQ(base::ASCIIToUTF16("Bonnie Parker"), suggestions[2].value); suggestions = personal_data_->GetCreditCardSuggestions( - AutofillType(CREDIT_CARD_NUMBER), /* field_contents= */ base::string16(), + AutofillType(CREDIT_CARD_NUMBER), /*field_contents=*/base::string16(), /*include_server_cards=*/true); ASSERT_EQ(3U, suggestions.size()); EXPECT_EQ(base::UTF8ToUTF16(std::string("Visa ") + @@ -3824,7 +3728,7 @@ TEST_F(PersonalDataManagerTest, std::vector<Suggestion> suggestions = personal_data_->GetCreditCardSuggestions( AutofillType(CREDIT_CARD_NAME_FULL), - /* field_contents= */ base::string16(), + /*field_contents=*/base::string16(), /*include_server_cards=*/true); ASSERT_EQ(3U, suggestions.size()); @@ -3839,7 +3743,7 @@ TEST_F(PersonalDataManagerTest, suggestions = personal_data_->GetCreditCardSuggestions( AutofillType(CREDIT_CARD_NAME_FULL), - /* field_contents= */ base::string16(), /*include_server_cards=*/true); + /*field_contents=*/base::string16(), /*include_server_cards=*/true); ASSERT_EQ(3U, suggestions.size()); } @@ -4036,120 +3940,6 @@ TEST_F(PersonalDataManagerTest, RecordUseOf) { EXPECT_EQ(kArbitraryTime, added_card->modification_date()); } -TEST_F(PersonalDataManagerTest, UpdateServerCreditCardUsageStats) { - EnableWalletCardImport(); - - std::vector<CreditCard> server_cards; - server_cards.push_back(CreditCard(CreditCard::MASKED_SERVER_CARD, "a123")); - test::SetCreditCardInfo(&server_cards.back(), "John Dillinger", - "3456" /* Visa */, "01", "2999", "1"); - server_cards.back().SetNetworkForMaskedCard(kVisaCard); - - server_cards.push_back(CreditCard(CreditCard::MASKED_SERVER_CARD, "b456")); - test::SetCreditCardInfo(&server_cards.back(), "Bonnie Parker", - "4444" /* Mastercard */, "12", "2999", "1"); - server_cards.back().SetNetworkForMaskedCard(kMasterCard); - - server_cards.push_back(CreditCard(CreditCard::FULL_SERVER_CARD, "c789")); - test::SetCreditCardInfo(&server_cards.back(), "Clyde Barrow", - "378282246310005" /* American Express */, "04", - "2999", "1"); - - // Create the test clock and set the time to a specific value. - TestAutofillClock test_clock; - test_clock.SetNow(kArbitraryTime); - - SetServerCards(server_cards); - - // Make sure everything is set up correctly. - personal_data_->Refresh(); - WaitForOnPersonalDataChanged(); - EXPECT_EQ(3U, personal_data_->GetCreditCards().size()); - - if (!OfferStoreUnmaskedCards(/*is_off_the_record=*/false)) { - for (CreditCard* card : personal_data_->GetCreditCards()) { - EXPECT_EQ(CreditCard::MASKED_SERVER_CARD, card->record_type()); - } - // The rest of this test doesn't work if we're force-masking all unmasked - // cards. - return; - } - - // The GUIDs will be different, so just compare the data. - for (size_t i = 0; i < 3; ++i) - EXPECT_EQ(0, server_cards[i].Compare(*personal_data_->GetCreditCards()[i])); - - CreditCard* unmasked_card = &server_cards.front(); - unmasked_card->set_record_type(CreditCard::FULL_SERVER_CARD); - unmasked_card->SetNumber(base::ASCIIToUTF16("4234567890123456")); - personal_data_->UpdateServerCreditCard(*unmasked_card); - - WaitForOnPersonalDataChanged(); - ASSERT_EQ(3U, personal_data_->GetCreditCards().size()); - - for (size_t i = 0; i < 3; ++i) - EXPECT_EQ(0, server_cards[i].Compare(*personal_data_->GetCreditCards()[i])); - - // For an unmasked card, usage data starts out as 2 because of the unmasking - // which is considered a use. The use date should now be the specified Now() - // time kArbitraryTime. - EXPECT_EQ(2U, personal_data_->GetCreditCards()[0]->use_count()); - EXPECT_EQ(kArbitraryTime, personal_data_->GetCreditCards()[0]->use_date()); - - EXPECT_EQ(1U, personal_data_->GetCreditCards()[1]->use_count()); - EXPECT_NE(kArbitraryTime, personal_data_->GetCreditCards()[1]->use_date()); - - // Having unmasked this card, usage stats should be 2 and - // kArbitraryTime. - EXPECT_EQ(2U, personal_data_->GetCreditCards()[2]->use_count()); - EXPECT_EQ(kArbitraryTime, personal_data_->GetCreditCards()[2]->use_date()); - - // Change the Now() value for a second time. - test_clock.SetNow(kSomeLaterTime); - - server_cards.back().set_guid(personal_data_->GetCreditCards()[2]->guid()); - personal_data_->RecordUseOf(server_cards.back()); - - WaitForOnPersonalDataChanged(); - ASSERT_EQ(3U, personal_data_->GetCreditCards().size()); - EXPECT_EQ(2U, personal_data_->GetCreditCards()[0]->use_count()); - EXPECT_EQ(kArbitraryTime, personal_data_->GetCreditCards()[0]->use_date()); - - EXPECT_EQ(1U, personal_data_->GetCreditCards()[1]->use_count()); - EXPECT_NE(kArbitraryTime, personal_data_->GetCreditCards()[1]->use_date()); - - // The RecordUseOf call should have incremented the use_count to 3 and set the - // use_date to kSomeLaterTime. - EXPECT_EQ(3U, personal_data_->GetCreditCards()[2]->use_count()); - EXPECT_EQ(kSomeLaterTime, personal_data_->GetCreditCards()[2]->use_date()); - - // Can record usage stats on masked cards. - server_cards[1].set_guid(personal_data_->GetCreditCards()[1]->guid()); - personal_data_->RecordUseOf(server_cards[1]); - - WaitForOnPersonalDataChanged(); - ASSERT_EQ(3U, personal_data_->GetCreditCards().size()); - EXPECT_EQ(2U, personal_data_->GetCreditCards()[1]->use_count()); - EXPECT_EQ(kSomeLaterTime, personal_data_->GetCreditCards()[1]->use_date()); - - // Change Now()'s return value for a third time. - test_clock.SetNow(kMuchLaterTime); - - // Upgrading to unmasked retains the usage stats (and increments them). - CreditCard* unmasked_card2 = &server_cards[1]; - unmasked_card2->set_record_type(CreditCard::FULL_SERVER_CARD); - unmasked_card2->SetNumber(base::ASCIIToUTF16("5555555555554444")); - personal_data_->UpdateServerCreditCard(*unmasked_card2); - - server_cards[1].set_guid(personal_data_->GetCreditCards()[1]->guid()); - personal_data_->RecordUseOf(server_cards[1]); - - WaitForOnPersonalDataChanged(); - ASSERT_EQ(3U, personal_data_->GetCreditCards().size()); - EXPECT_EQ(3U, personal_data_->GetCreditCards()[1]->use_count()); - EXPECT_EQ(kMuchLaterTime, personal_data_->GetCreditCards()[1]->use_date()); -} - TEST_F(PersonalDataManagerTest, ClearAllServerData) { // Add a server card. std::vector<CreditCard> server_cards; @@ -6746,21 +6536,6 @@ TEST_F(PersonalDataManagerTest, CreateDataForTest) { } } -#if defined(OS_LINUX) && !defined(OS_CHROMEOS) -// Make sure that it's not possible to add full server cards on Linux. -TEST_F(PersonalDataManagerTest, CannotAddFullServerCardOnLinux) { - SetUpThreeCardTypes(); - - // Check that cards were masked and other were untouched. - EXPECT_EQ(3U, personal_data_->GetCreditCards().size()); - std::vector<CreditCard*> server_cards = - personal_data_->GetServerCreditCards(); - EXPECT_EQ(2U, server_cards.size()); - for (CreditCard* card : server_cards) - EXPECT_TRUE(card->record_type() == CreditCard::MASKED_SERVER_CARD); -} -#endif // #if defined(OS_LINUX) && !defined(OS_CHROMEOS) - // These tests are not applicable on Linux since it does not support full server // cards. #if !defined(OS_LINUX) || defined(OS_CHROMEOS) @@ -7884,11 +7659,11 @@ TEST_F(PersonalDataManagerTest, OnUserAcceptedUpstreamOffer) { /////////////////////////////////////////////////////////// // kSignedInAndWalletSyncTransportEnabled /////////////////////////////////////////////////////////// - // Make a non-primary account available with both a refresh token and cookie - // to be in Sync Transport for Wallet mode. + // Make a primary account with no sync consent available to be in Sync + // Transport for Wallet mode. CoreAccountInfo active_info = - identity_test_env_.MakeAccountAvailable(kSyncTransportAccountEmail); - identity_test_env_.SetCookieAccounts({{active_info.email, active_info.gaia}}); + identity_test_env_.MakeUnconsentedPrimaryAccountAvailable( + kSyncTransportAccountEmail); sync_service_.SetAuthenticatedAccountInfo(active_info); sync_service_.SetIsAuthenticatedAccountPrimary(false); sync_service_.SetActiveDataTypes( @@ -7943,12 +7718,11 @@ TEST_F(PersonalDataManagerTest, OnUserAcceptedUpstreamOffer) { prefs::ClearSyncTransportOptIns(prefs_.get()); ASSERT_FALSE(prefs::IsUserOptedInWalletSyncTransport(prefs_.get(), active_info.account_id)); -#endif // !defined(OS_CHROMEOS) /////////////////////////////////////////////////////////// // kSignedOut /////////////////////////////////////////////////////////// - identity_test_env_.RemoveRefreshTokenForAccount(active_info.account_id); + identity_test_env_.ClearPrimaryAccount(); { EXPECT_EQ(AutofillSyncSigninState::kSignedOut, personal_data_->GetSyncSigninState()); @@ -7959,6 +7733,7 @@ TEST_F(PersonalDataManagerTest, OnUserAcceptedUpstreamOffer) { EXPECT_FALSE(prefs::IsUserOptedInWalletSyncTransport( prefs_.get(), active_info.account_id)); } +#endif // !defined(OS_CHROMEOS) /////////////////////////////////////////////////////////// // kSignedInAndSyncFeature @@ -8028,4 +7803,132 @@ TEST_F(PersonalDataManagerTest, AddAndGetUpiId) { EXPECT_THAT(all_upi_ids, testing::ElementsAre(upi_id)); } +struct ShareNicknameTestParam { + std::string local_nickname; + std::string server_nickname; + std::string expected_nickname; +}; + +const ShareNicknameTestParam kShareNicknameTestParam[] = { + {"", "", ""}, + {"", "server nickname", "server nickname"}, + {"local nickname", "", "local nickname"}, + {"local nickname", "server nickname", "local nickname"}, +}; + +class PersonalDataManagerTestForSharingNickname + : public PersonalDataManagerTest, + public testing::WithParamInterface<ShareNicknameTestParam> { + public: + PersonalDataManagerTestForSharingNickname() + : local_nickname_(base::UTF8ToUTF16(GetParam().local_nickname)), + server_nickname_(base::UTF8ToUTF16(GetParam().server_nickname)), + expected_nickname_(base::UTF8ToUTF16(GetParam().expected_nickname)) {} + + CreditCard GetLocalCard() { + CreditCard local_card("287151C8-6AB1-487C-9095-28E80BE5DA15", + test::kEmptyOrigin); + test::SetCreditCardInfo(&local_card, "Clyde Barrow", + "378282246310005" /* American Express */, "04", + "2999", "1"); + local_card.set_use_count(3); + local_card.set_use_date(AutofillClock::Now() - + base::TimeDelta::FromDays(1)); + local_card.SetNickname(local_nickname_); + return local_card; + } + + CreditCard GetServerCard() { + CreditCard full_server_card(CreditCard::FULL_SERVER_CARD, "c789"); + test::SetCreditCardInfo(&full_server_card, "Clyde Barrow", + "378282246310005" /* American Express */, "04", + "2999", "1"); + full_server_card.SetNickname(server_nickname_); + return full_server_card; + } + + base::string16 local_nickname_; + base::string16 server_nickname_; + base::string16 expected_nickname_; + + protected: + void SetUp() override { + PersonalDataManagerTest::SetUp(); + EnableWalletCardImport(); + scoped_feature_list_.InitWithFeatures( + /*enabled_features=*/{features:: + kAutofillEnableSurfacingServerCardNickname, + features::kAutofillEnableCardNicknameManagement}, + /*disabled_features=*/{}); + } + + private: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +INSTANTIATE_TEST_SUITE_P(, + PersonalDataManagerTestForSharingNickname, + testing::ValuesIn(kShareNicknameTestParam)); + +TEST_P(PersonalDataManagerTestForSharingNickname, + VerifySuggestion_DuplicateCards) { + ASSERT_EQ(0U, personal_data_->GetCreditCards().size()); + CreditCard local_card = GetLocalCard(); + personal_data_->AddCreditCard(local_card); + + SetServerCards({GetServerCard()}); + + personal_data_->Refresh(); + WaitForOnPersonalDataChanged(); + ASSERT_EQ(2U, personal_data_->GetCreditCards().size()); + + // Verifies the suggestion shows the right text. + std::vector<Suggestion> suggestions = + personal_data_->GetCreditCardSuggestions( + AutofillType(CREDIT_CARD_NUMBER), + /*field_contents=*/base::string16(), + /*include_server_cards=*/true); + ASSERT_EQ(1U, suggestions.size()); + EXPECT_EQ(suggestions[0].value, + (expected_nickname_.empty() ? base::ASCIIToUTF16("Amex") + : expected_nickname_) + + base::UTF8ToUTF16(" ") + + local_card.ObfuscatedLastFourDigits()); +} + +TEST_P(PersonalDataManagerTestForSharingNickname, + VerifySuggestion_UnrelatedCards) { + ASSERT_EQ(0U, personal_data_->GetCreditCards().size()); + CreditCard local_card = GetLocalCard(); + personal_data_->AddCreditCard(local_card); + + std::vector<CreditCard> server_cards; + CreditCard server_card = GetServerCard(); + // Make sure the cards are different by giving a different card number. + server_card.SetNumber(base::ASCIIToUTF16("371449635398431")); + server_cards.emplace_back(server_card); + SetServerCards(server_cards); + + personal_data_->Refresh(); + WaitForOnPersonalDataChanged(); + ASSERT_EQ(2U, personal_data_->GetCreditCards().size()); + + // Verifies the suggestion shows the right text. + std::vector<Suggestion> suggestions = + personal_data_->GetCreditCardSuggestions( + AutofillType(CREDIT_CARD_NUMBER), + /*field_contents=*/base::string16(), + /*include_server_cards=*/true); + ASSERT_EQ(2U, suggestions.size()); + EXPECT_THAT( + std::vector<base::string16>({suggestions[0].value, suggestions[1].value}), + testing::UnorderedElementsAre( + (server_nickname_.empty() ? base::ASCIIToUTF16("Amex") + : server_nickname_) + + base::UTF8ToUTF16(" ") + server_card.ObfuscatedLastFourDigits(), + (local_nickname_.empty() ? base::ASCIIToUTF16("Amex") + : local_nickname_) + + base::UTF8ToUTF16(" ") + local_card.ObfuscatedLastFourDigits())); +} + } // namespace autofill diff --git a/chromium/components/autofill/core/browser/proto/server.proto b/chromium/components/autofill/core/browser/proto/server.proto index a36839bde15..3eb58500da2 100644 --- a/chromium/components/autofill/core/browser/proto/server.proto +++ b/chromium/components/autofill/core/browser/proto/server.proto @@ -117,6 +117,20 @@ message AutofillRandomizedValue { optional bytes encoded_bits = 2; } +// Describes how the button is implemented in HTML source. Corresponds to +// the mojo ButtonTitleType enum defined in +// components/autofill/core/common/mojom/autofill_types.mojom.h +enum ButtonTitleType { + NONE = 0; + BUTTON_ELEMENT_SUBMIT_TYPE = 1; // <button type='submit'> + BUTTON_ELEMENT_BUTTON_TYPE = 2; // <button type='button'> + INPUT_ELEMENT_SUBMIT_TYPE = 3; // <input type='submit'> + INPUT_ELEMENT_BUTTON_TYPE = 4; // <input type='button'> + HYPERLINK = 5; // e.g. <a class='button'> + DIV = 6; // e.g. <div id='submit'> + SPAN = 7; // e.g. <span name='btn'> +} + // The collection of autofill field metadata to be sent using randomization. message AutofillRandomizedFormMetadata { // Form element id. Example: <form id="XXXXXXXX"> @@ -130,6 +144,21 @@ message AutofillRandomizedFormMetadata { // Location of form as URL. optional AutofillRandomizedValue url = 4; + + // Information about a button's title (sync with another ButtonTitle in this + // proto). + message ButtonTitle { + // Text showed on the button. + optional AutofillRandomizedValue title = 1; + + // Describes how the button is implemented in HTML source. + optional ButtonTitleType type = 2; + } + // Titles of form's buttons. Example: <input type="submit" value="XXXXX"> + repeated ButtonTitle button_title = 5; + + // Hash of the clear form URL, not randomized. + optional fixed32 checksum_for_url = 6; } // The collection of autofill field metadata to be sent using randomization. @@ -386,24 +415,13 @@ message AutofillUploadContents { // Form-level metadata observed by the client, randomized. optional AutofillRandomizedFormMetadata randomized_form_metadata = 32; - // Information about a button's title. + // Information about a button's title (sync with another ButtonTitle in this + // proto). message ButtonTitle { // Text showed on the button. optional string title = 1; - // Describes how the button is implemented in HTML source. Corresponds to - // the mojo ButtonTitleType enum defined in - // components/autofill/core/common/mojom/autofill_types.mojom.h - enum ButtonTitleType { - NONE = 0; - BUTTON_ELEMENT_SUBMIT_TYPE = 1; // <button type='submit'> - BUTTON_ELEMENT_BUTTON_TYPE = 2; // <button type='button'> - INPUT_ELEMENT_SUBMIT_TYPE = 3; // <input type='submit'> - INPUT_ELEMENT_BUTTON_TYPE = 4; // <input type='button'> - HYPERLINK = 5; // e.g. <a class='button'> - DIV = 6; // e.g. <div id='submit'> - SPAN = 7; // e.g. <span name='btn'> - } + // Describes how the button is implemented in HTML source. optional ButtonTitleType type = 2; } // Titles of form's buttons. diff --git a/chromium/components/autofill/core/browser/randomized_encoder.cc b/chromium/components/autofill/core/browser/randomized_encoder.cc index 116e169b127..4a3a467f87d 100644 --- a/chromium/components/autofill/core/browser/randomized_encoder.cc +++ b/chromium/components/autofill/core/browser/randomized_encoder.cc @@ -204,6 +204,7 @@ const char RandomizedEncoder::FORM_NAME[] = "form-name"; const char RandomizedEncoder::FORM_ACTION[] = "form-action"; const char RandomizedEncoder::FORM_URL[] = "form-url"; const char RandomizedEncoder::FORM_CSS_CLASS[] = "form-css-class"; +const char RandomizedEncoder::FORM_BUTTON_TITLES[] = "button-titles"; const char RandomizedEncoder::FIELD_ID[] = "field-id"; const char RandomizedEncoder::FIELD_NAME[] = "field-name"; diff --git a/chromium/components/autofill/core/browser/randomized_encoder.h b/chromium/components/autofill/core/browser/randomized_encoder.h index 70d7b429be6..aefd5e66630 100644 --- a/chromium/components/autofill/core/browser/randomized_encoder.h +++ b/chromium/components/autofill/core/browser/randomized_encoder.h @@ -33,6 +33,7 @@ class RandomizedEncoder { static const char FORM_ACTION[]; static const char FORM_URL[]; static const char FORM_CSS_CLASS[]; + static const char FORM_BUTTON_TITLES[]; // Field-level data-type identifiers. static const char FIELD_ID[]; diff --git a/chromium/components/autofill/core/browser/test_autofill_client.cc b/chromium/components/autofill/core/browser/test_autofill_client.cc index 550946efd03..b40c031464b 100644 --- a/chromium/components/autofill/core/browser/test_autofill_client.cc +++ b/chromium/components/autofill/core/browser/test_autofill_client.cc @@ -204,11 +204,7 @@ bool TestAutofillClient::HasCreditCardScanFeature() { void TestAutofillClient::ScanCreditCard(CreditCardScanCallback callback) {} void TestAutofillClient::ShowAutofillPopup( - const gfx::RectF& element_bounds, - base::i18n::TextDirection text_direction, - const std::vector<Suggestion>& suggestions, - bool autoselect_first_suggestion, - PopupType popup_type, + const AutofillClient::PopupOpenArgs& open_args, base::WeakPtr<AutofillPopupDelegate> delegate) {} void TestAutofillClient::UpdateAutofillPopupDataListValues( @@ -221,6 +217,10 @@ base::span<const Suggestion> TestAutofillClient::GetPopupSuggestions() const { void TestAutofillClient::PinPopupView() {} +AutofillClient::PopupOpenArgs TestAutofillClient::GetReopenPopupArgs() const { + return {}; +} + void TestAutofillClient::UpdatePopup(const std::vector<Suggestion>& suggestions, PopupType popup_type) {} diff --git a/chromium/components/autofill/core/browser/test_autofill_client.h b/chromium/components/autofill/core/browser/test_autofill_client.h index 608f1f4ca5d..11abe949a24 100644 --- a/chromium/components/autofill/core/browser/test_autofill_client.h +++ b/chromium/components/autofill/core/browser/test_autofill_client.h @@ -116,17 +116,14 @@ class TestAutofillClient : public AutofillClient { bool HasCreditCardScanFeature() override; void ScanCreditCard(CreditCardScanCallback callback) override; void ShowAutofillPopup( - const gfx::RectF& element_bounds, - base::i18n::TextDirection text_direction, - const std::vector<Suggestion>& suggestions, - bool autoselect_first_suggestion, - PopupType popup_type, + const AutofillClient::PopupOpenArgs& open_args, base::WeakPtr<AutofillPopupDelegate> delegate) override; void UpdateAutofillPopupDataListValues( const std::vector<base::string16>& values, const std::vector<base::string16>& labels) override; base::span<const Suggestion> GetPopupSuggestions() const override; void PinPopupView() override; + AutofillClient::PopupOpenArgs GetReopenPopupArgs() const override; void UpdatePopup(const std::vector<Suggestion>& suggestions, PopupType popup_type) override; void HideAutofillPopup(PopupHidingReason reason) override; diff --git a/chromium/components/autofill/core/browser/test_autofill_download_manager.cc b/chromium/components/autofill/core/browser/test_autofill_download_manager.cc index ea4795edfb5..31261f58e9a 100644 --- a/chromium/components/autofill/core/browser/test_autofill_download_manager.cc +++ b/chromium/components/autofill/core/browser/test_autofill_download_manager.cc @@ -26,7 +26,8 @@ void TestAutofillDownloadManager::VerifyLastQueriedForms( const std::vector<FormData>& expected_forms) { ASSERT_EQ(expected_forms.size(), last_queried_forms_.size()); for (size_t i = 0; i < expected_forms.size(); ++i) { - EXPECT_EQ(*last_queried_forms_[i], expected_forms[i]); + EXPECT_EQ(last_queried_forms_[i]->unique_renderer_id(), + expected_forms[i].unique_renderer_id); } } diff --git a/chromium/components/autofill/core/browser/test_autofill_manager.cc b/chromium/components/autofill/core/browser/test_autofill_manager.cc index 6d402345ade..ca610fc8d93 100644 --- a/chromium/components/autofill/core/browser/test_autofill_manager.cc +++ b/chromium/components/autofill/core/browser/test_autofill_manager.cc @@ -109,6 +109,7 @@ void TestAutofillManager::AddSeenForm( std::unique_ptr<TestFormStructure> form_structure = std::make_unique<TestFormStructure>(empty_form); form_structure->SetFieldTypes(heuristic_types, server_types); + form_structure->identify_sections_for_testing(); AddSeenFormStructure(std::move(form_structure)); form_interactions_ukm_logger()->OnFormsParsed(client()->GetUkmSourceId()); @@ -116,8 +117,8 @@ void TestAutofillManager::AddSeenForm( void TestAutofillManager::AddSeenFormStructure( std::unique_ptr<FormStructure> form_structure) { - const auto signature = form_structure->form_signature(); - (*mutable_form_structures())[signature] = std::move(form_structure); + const auto renderer_id = form_structure->unique_renderer_id(); + (*mutable_form_structures())[renderer_id] = std::move(form_structure); } void TestAutofillManager::ClearFormStructures() { diff --git a/chromium/components/autofill/core/browser/test_autofill_provider.cc b/chromium/components/autofill/core/browser/test_autofill_provider.cc index ef5137b008c..2102a1974f9 100644 --- a/chromium/components/autofill/core/browser/test_autofill_provider.cc +++ b/chromium/components/autofill/core/browser/test_autofill_provider.cc @@ -50,6 +50,8 @@ void TestAutofillProvider::OnFormsSeen(AutofillHandlerProxy* handler, const std::vector<FormData>& forms, const base::TimeTicks timestamp) {} +void TestAutofillProvider::OnHidePopup(AutofillHandlerProxy* handler) {} + void TestAutofillProvider::Reset(AutofillHandlerProxy* handler) {} } // namespace autofill diff --git a/chromium/components/autofill/core/browser/test_autofill_provider.h b/chromium/components/autofill/core/browser/test_autofill_provider.h index a7fc221cf57..7a21e59c6bb 100644 --- a/chromium/components/autofill/core/browser/test_autofill_provider.h +++ b/chromium/components/autofill/core/browser/test_autofill_provider.h @@ -48,6 +48,7 @@ class TestAutofillProvider : public AutofillProvider { void OnFormsSeen(AutofillHandlerProxy* handler, const std::vector<FormData>& forms, const base::TimeTicks timestamp) override; + void OnHidePopup(AutofillHandlerProxy* handler) override; void Reset(AutofillHandlerProxy* handler) override; }; diff --git a/chromium/components/autofill/core/browser/ui/accessory_sheet_data.cc b/chromium/components/autofill/core/browser/ui/accessory_sheet_data.cc index fd1a0d98e22..1728083a998 100644 --- a/chromium/components/autofill/core/browser/ui/accessory_sheet_data.cc +++ b/chromium/components/autofill/core/browser/ui/accessory_sheet_data.cc @@ -4,6 +4,7 @@ #include "components/autofill/core/browser/ui/accessory_sheet_data.h" +#include "base/logging.h" #include "base/strings/string_piece.h" #include "components/autofill/core/browser/ui/accessory_sheet_enums.h" diff --git a/chromium/components/autofill/core/browser/ui/accessory_sheet_enums.h b/chromium/components/autofill/core/browser/ui/accessory_sheet_enums.h index e2086f5c1bd..97a2cbf5eb4 100644 --- a/chromium/components/autofill/core/browser/ui/accessory_sheet_enums.h +++ b/chromium/components/autofill/core/browser/ui/accessory_sheet_enums.h @@ -40,6 +40,16 @@ enum class AccessoryAction { COUNT, }; +// Used to record metrics for accessory toggles. Entries should not be +// renumbered and numeric values should never be reused. Must be kept in sync +// with the enum in enums.xml. A java IntDef@ is generated from this. +// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.keyboard_accessory +enum class AccessoryToggleType { + SAVE_PASSWORDS_TOGGLE_ON = 0, + SAVE_PASSWORDS_TOGGLE_OFF = 1, + COUNT, +}; + } // namespace autofill #endif // COMPONENTS_AUTOFILL_CORE_BROWSER_UI_ACCESSORY_SHEET_ENUMS_H_ diff --git a/chromium/components/autofill/core/browser/ui/address_combobox_model.cc b/chromium/components/autofill/core/browser/ui/address_combobox_model.cc index eecc105f85b..89c03f9c433 100644 --- a/chromium/components/autofill/core/browser/ui/address_combobox_model.cc +++ b/chromium/components/autofill/core/browser/ui/address_combobox_model.cc @@ -46,7 +46,7 @@ int AddressComboboxModel::GetItemCount() const { return addresses_.size() + kNbHeaderEntries; } -base::string16 AddressComboboxModel::GetItemAt(int index) { +base::string16 AddressComboboxModel::GetItemAt(int index) const { DCHECK_GE(index, 0); // A special entry is always added at index 0 and a separator at index 1. DCHECK_LT(static_cast<size_t>(index), addresses_.size() + kNbHeaderEntries); @@ -66,7 +66,7 @@ base::string16 AddressComboboxModel::GetItemAt(int index) { return addresses_[index - kNbHeaderEntries].second; } -bool AddressComboboxModel::IsItemSeparatorAt(int index) { +bool AddressComboboxModel::IsItemSeparatorAt(int index) const { // The only separator is between the "Select" entry at 0 and the first address // at index 2. So there must be at least one address for a separator to be // shown. diff --git a/chromium/components/autofill/core/browser/ui/address_combobox_model.h b/chromium/components/autofill/core/browser/ui/address_combobox_model.h index dda2c0fcd41..deb54889c04 100644 --- a/chromium/components/autofill/core/browser/ui/address_combobox_model.h +++ b/chromium/components/autofill/core/browser/ui/address_combobox_model.h @@ -35,8 +35,8 @@ class AddressComboboxModel : public ui::ComboboxModel { // ui::ComboboxModel implementation: int GetItemCount() const override; - base::string16 GetItemAt(int index) override; - bool IsItemSeparatorAt(int index) override; + base::string16 GetItemAt(int index) const override; + bool IsItemSeparatorAt(int index) const override; int GetDefaultIndex() const override; void AddObserver(ui::ComboboxModelObserver* observer) override; void RemoveObserver(ui::ComboboxModelObserver* observer) override; diff --git a/chromium/components/autofill/core/browser/ui/country_combobox_model.cc b/chromium/components/autofill/core/browser/ui/country_combobox_model.cc index b15cc2bacec..11bc1b1b670 100644 --- a/chromium/components/autofill/core/browser/ui/country_combobox_model.cc +++ b/chromium/components/autofill/core/browser/ui/country_combobox_model.cc @@ -79,7 +79,7 @@ int CountryComboboxModel::GetItemCount() const { return countries_.size(); } -base::string16 CountryComboboxModel::GetItemAt(int index) { +base::string16 CountryComboboxModel::GetItemAt(int index) const { AutofillCountry* country = countries_[index].get(); if (country) return countries_[index]->name(); @@ -89,7 +89,7 @@ base::string16 CountryComboboxModel::GetItemAt(int index) { return base::ASCIIToUTF16("---"); } -bool CountryComboboxModel::IsItemSeparatorAt(int index) { +bool CountryComboboxModel::IsItemSeparatorAt(int index) const { return !countries_[index]; } diff --git a/chromium/components/autofill/core/browser/ui/country_combobox_model.h b/chromium/components/autofill/core/browser/ui/country_combobox_model.h index fb00d162795..712cab208f5 100644 --- a/chromium/components/autofill/core/browser/ui/country_combobox_model.h +++ b/chromium/components/autofill/core/browser/ui/country_combobox_model.h @@ -36,8 +36,8 @@ class CountryComboboxModel : public ui::ComboboxModel { // ui::ComboboxModel implementation: int GetItemCount() const override; - base::string16 GetItemAt(int index) override; - bool IsItemSeparatorAt(int index) override; + base::string16 GetItemAt(int index) const override; + bool IsItemSeparatorAt(int index) const override; // The list of countries always has the default country at the top as well as // within the sorted vector. diff --git a/chromium/components/autofill/core/browser/ui/payments/card_expiration_date_fix_flow_controller_impl.cc b/chromium/components/autofill/core/browser/ui/payments/card_expiration_date_fix_flow_controller_impl.cc index a5a81c68130..84de5925de8 100644 --- a/chromium/components/autofill/core/browser/ui/payments/card_expiration_date_fix_flow_controller_impl.cc +++ b/chromium/components/autofill/core/browser/ui/payments/card_expiration_date_fix_flow_controller_impl.cc @@ -41,7 +41,7 @@ void CardExpirationDateFixFlowControllerImpl::Show( DCHECK(!callback.is_null()); DCHECK(card_expiration_date_fix_flow_view); - card_label_ = card.NetworkAndLastFourDigits(); + card_label_ = card.CardIdentifierStringForAutofillDisplay(); if (card_expiration_date_fix_flow_view_) card_expiration_date_fix_flow_view_->ControllerGone(); diff --git a/chromium/components/autofill/core/browser/ui/payments/card_expiration_date_fix_flow_controller_impl_unittest.cc b/chromium/components/autofill/core/browser/ui/payments/card_expiration_date_fix_flow_controller_impl_unittest.cc index b3834c9347d..b4fdd61137e 100644 --- a/chromium/components/autofill/core/browser/ui/payments/card_expiration_date_fix_flow_controller_impl_unittest.cc +++ b/chromium/components/autofill/core/browser/ui/payments/card_expiration_date_fix_flow_controller_impl_unittest.cc @@ -11,10 +11,12 @@ #include "base/macros.h" #include "base/strings/utf_string_conversions.h" #include "base/test/metrics/histogram_tester.h" +#include "base/test/scoped_feature_list.h" #include "components/autofill/core/browser/autofill_metrics.h" #include "components/autofill/core/browser/autofill_test_utils.h" #include "components/autofill/core/browser/data_model/credit_card.h" #include "components/autofill/core/browser/ui/payments/card_expiration_date_fix_flow_view.h" +#include "components/autofill/core/common/autofill_payments_features.h" #include "testing/gtest/include/gtest/gtest.h" namespace autofill { @@ -29,9 +31,9 @@ class CardExpirationDateFixFlowControllerImplGenericTest { public: CardExpirationDateFixFlowControllerImplGenericTest() {} - void ShowPrompt() { + void ShowPrompt(CreditCard credit_card = CreditCard()) { controller_->Show( - test_card_expiration_date_fix_flow_view_.get(), autofill::CreditCard(), + test_card_expiration_date_fix_flow_view_.get(), credit_card, base::BindOnce( &CardExpirationDateFixFlowControllerImplGenericTest::OnAccepted, weak_ptr_factory_.GetWeakPtr())); @@ -106,4 +108,41 @@ TEST_F(CardExpirationDateFixFlowControllerImplTest, LogDismissed) { 1); } +// Tests to ensure the card nickname is shown correctly in the expiration fix +// flow prompt. The param indicates whether the nickname experiment is enabled. +class CardExpirationDateFixFlowControllerImplTestForNickname + : public CardExpirationDateFixFlowControllerImplTest, + public ::testing::WithParamInterface<bool> { + public: + CardExpirationDateFixFlowControllerImpl* GetController() { + return controller_.get(); + } + + protected: + CardExpirationDateFixFlowControllerImplTestForNickname() { + scoped_feature_list_.InitWithFeatureState( + features::kAutofillEnableSurfacingServerCardNickname, GetParam()); + } + + ~CardExpirationDateFixFlowControllerImplTestForNickname() override = default; + + private: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +INSTANTIATE_TEST_SUITE_P(, + CardExpirationDateFixFlowControllerImplTestForNickname, + testing::Bool()); + +TEST_P(CardExpirationDateFixFlowControllerImplTestForNickname, + CardIdentifierString) { + CreditCard card = test::GetCreditCard(); + card.SetNickname(base::ASCIIToUTF16("nickname")); + ShowPrompt(card); + + EXPECT_EQ(GetController()->GetCardLabel(), + GetParam() ? card.NicknameAndLastFourDigitsForTesting() + : card.NetworkAndLastFourDigits()); +} + } // namespace autofill diff --git a/chromium/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller.h b/chromium/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller.h index d2a1cb31699..d980b3621e8 100644 --- a/chromium/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller.h +++ b/chromium/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller.h @@ -32,7 +32,6 @@ class CardUnmaskPromptController { virtual base::string16 GetOkButtonLabel() const = 0; virtual int GetCvcImageRid() const = 0; virtual bool ShouldRequestExpirationDate() const = 0; - virtual bool CanStoreLocally() const = 0; virtual bool GetStoreLocallyStartState() const = 0; #if defined(OS_ANDROID) virtual bool ShouldOfferWebauthn() const = 0; diff --git a/chromium/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.cc b/chromium/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.cc index d3ac2eae1f9..f693be09c4f 100644 --- a/chromium/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.cc +++ b/chromium/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.cc @@ -27,9 +27,8 @@ namespace autofill { CardUnmaskPromptControllerImpl::CardUnmaskPromptControllerImpl( - PrefService* pref_service, - bool is_off_the_record) - : pref_service_(pref_service), is_off_the_record_(is_off_the_record) {} + PrefService* pref_service) + : pref_service_(pref_service) {} CardUnmaskPromptControllerImpl::~CardUnmaskPromptControllerImpl() { if (card_unmask_view_) @@ -125,23 +124,15 @@ void CardUnmaskPromptControllerImpl::OnUnmaskPromptAccepted( pending_details_.exp_month = exp_month; pending_details_.exp_year = exp_year; } - if (CanStoreLocally()) { - pending_details_.should_store_pan = should_store_pan; - // Remember the last choice the user made (on this device). - pref_service_->SetBoolean(prefs::kAutofillWalletImportStorageCheckboxState, - should_store_pan); - } else { - DCHECK(!should_store_pan); - pending_details_.should_store_pan = false; - } + DCHECK(!should_store_pan); + pending_details_.should_store_pan = false; - // On Android, the FIDO authentication checkbox is only shown when the local - // storage checkbox is not shown and the flag is turned on. If it is shown, - // then remember the last choice the user made on this device. + // On Android, the FIDO authentication checkbox is only shown when the flag is + // turned on. If it is shown, then remember the last choice the user made on + // this device. #if defined(OS_ANDROID) if (base::FeatureList::IsEnabled( - features::kAutofillCreditCardAuthentication) && - !CanStoreLocally()) { + features::kAutofillCreditCardAuthentication)) { pending_details_.enable_fido_auth = enable_fido_auth; pref_service_->SetBoolean( prefs::kAutofillCreditCardFidoAuthOfferCheckboxState, enable_fido_auth); @@ -212,22 +203,6 @@ bool CardUnmaskPromptControllerImpl::ShouldRequestExpirationDate() const { new_card_link_clicked_; } -bool CardUnmaskPromptControllerImpl::CanStoreLocally() const { - if (base::FeatureList::IsEnabled( - features::kAutofillNoLocalSaveOnUnmaskSuccess)) { - return false; - } - // Never offer to save for incognito. - if (is_off_the_record_) - return false; - if (reason_ == AutofillClient::UNMASK_FOR_PAYMENT_REQUEST) - return false; - if (card_.record_type() == CreditCard::LOCAL_CARD) - return false; - - return OfferStoreUnmaskedCards(is_off_the_record_); -} - bool CardUnmaskPromptControllerImpl::GetStoreLocallyStartState() const { return pref_service_->GetBoolean( prefs::kAutofillWalletImportStorageCheckboxState); @@ -326,22 +301,6 @@ void CardUnmaskPromptControllerImpl::LogOnCloseEvents() { AutofillMetrics::UNMASK_PROMPT_SAVED_CARD_LOCALLY, card_.HasValidNickname()); } - - if (CanStoreLocally()) { - // Tracking changes in local save preference. - AutofillMetrics::UnmaskPromptEvent event; - if (unmasking_initial_should_store_pan_ && final_should_store_pan) { - event = AutofillMetrics::UNMASK_PROMPT_LOCAL_SAVE_DID_NOT_OPT_OUT; - } else if (!unmasking_initial_should_store_pan_ && - !final_should_store_pan) { - event = AutofillMetrics::UNMASK_PROMPT_LOCAL_SAVE_DID_NOT_OPT_IN; - } else if (unmasking_initial_should_store_pan_ && !final_should_store_pan) { - event = AutofillMetrics::UNMASK_PROMPT_LOCAL_SAVE_DID_OPT_OUT; - } else { - event = AutofillMetrics::UNMASK_PROMPT_LOCAL_SAVE_DID_OPT_IN; - } - AutofillMetrics::LogUnmaskPromptEvent(event, card_.HasValidNickname()); - } } AutofillMetrics::UnmaskPromptEvent diff --git a/chromium/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.h b/chromium/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.h index 67e6622b06f..d5c31d30500 100644 --- a/chromium/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.h +++ b/chromium/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.h @@ -22,8 +22,7 @@ class CardUnmaskPromptView; class CardUnmaskPromptControllerImpl : public CardUnmaskPromptController { public: - CardUnmaskPromptControllerImpl(PrefService* pref_service, - bool is_off_the_record); + explicit CardUnmaskPromptControllerImpl(PrefService* pref_service); virtual ~CardUnmaskPromptControllerImpl(); // This should be OnceCallback<unique_ptr<CardUnmaskPromptView>> but there are @@ -54,7 +53,6 @@ class CardUnmaskPromptControllerImpl : public CardUnmaskPromptController { base::string16 GetOkButtonLabel() const override; int GetCvcImageRid() const override; bool ShouldRequestExpirationDate() const override; - bool CanStoreLocally() const override; bool GetStoreLocallyStartState() const override; #if defined(OS_ANDROID) bool ShouldOfferWebauthn() const override; @@ -78,7 +76,6 @@ class CardUnmaskPromptControllerImpl : public CardUnmaskPromptController { PrefService* pref_service_; bool new_card_link_clicked_ = false; - bool is_off_the_record_; CreditCard card_; AutofillClient::UnmaskCardReason reason_; base::WeakPtr<CardUnmaskDelegate> delegate_; diff --git a/chromium/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl_unittest.cc b/chromium/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl_unittest.cc index fba820d9cc9..27ee2995f52 100644 --- a/chromium/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl_unittest.cc +++ b/chromium/components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl_unittest.cc @@ -69,15 +69,11 @@ class TestCardUnmaskPromptController : public CardUnmaskPromptControllerImpl { public: explicit TestCardUnmaskPromptController( TestingPrefServiceSimple* pref_service) - : CardUnmaskPromptControllerImpl(pref_service, false), - can_store_locally_(!base::FeatureList::IsEnabled( - features::kAutofillNoLocalSaveOnUnmaskSuccess)) {} + : CardUnmaskPromptControllerImpl(pref_service) {} - bool CanStoreLocally() const override { return can_store_locally_; } #if defined(OS_ANDROID) bool ShouldOfferWebauthn() const override { return should_offer_webauthn_; } #endif - void set_can_store_locally(bool can) { can_store_locally_ = can; } void set_should_offer_webauthn(bool should) { should_offer_webauthn_ = should; } @@ -87,7 +83,6 @@ class TestCardUnmaskPromptController : public CardUnmaskPromptControllerImpl { } private: - bool can_store_locally_; bool should_offer_webauthn_; base::WeakPtrFactory<TestCardUnmaskPromptController> weak_factory_{this}; @@ -173,7 +168,6 @@ TEST_F(CardUnmaskPromptControllerImplTest, FidoAuthOfferCheckboxStatePersistent) { scoped_feature_list_.InitAndEnableFeature( features::kAutofillCreditCardAuthentication); - controller_->set_can_store_locally(false); ShowPromptAndSimulateResponse(/*should_store_pan=*/false, /*enable_fido_auth=*/true); EXPECT_TRUE(pref_service_->GetBoolean( @@ -189,7 +183,6 @@ TEST_F(CardUnmaskPromptControllerImplTest, PopulateCheckboxToUserProvidedUnmaskDetails) { scoped_feature_list_.InitAndEnableFeature( features::kAutofillCreditCardAuthentication); - controller_->set_can_store_locally(false); ShowPromptAndSimulateResponse(/*should_store_pan=*/false, /*enable_fido_auth=*/true); @@ -486,7 +479,6 @@ TEST_P(LoggingValidationTestForNickname, LogUnmaskedCardAfterFailure) { } TEST_P(LoggingValidationTestForNickname, DontLogForHiddenCheckbox) { - controller_->set_can_store_locally(false); ShowPromptAndSimulateResponse(/*should_store_pan=*/false, /*enable_fido_auth=*/false); base::HistogramTester histogram_tester; diff --git a/chromium/components/autofill/core/browser/ui/popup_types.h b/chromium/components/autofill/core/browser/ui/popup_types.h index 1656116a9b2..21918b9bc20 100644 --- a/chromium/components/autofill/core/browser/ui/popup_types.h +++ b/chromium/components/autofill/core/browser/ui/popup_types.h @@ -34,6 +34,9 @@ enum class PopupHidingReason { kUserAborted, // The user explicitly dismissed the popup (e.g. ESC key). kViewDestroyed, // The popup view (or its controller) goes out of scope. kWidgetChanged, // The platform-native UI changed (e.g. window resize). + kInsufficientSpace, // Not enough space in content area to display an display + // at least one row of the popup within the bounds of the + // content area. }; } // namespace autofill diff --git a/chromium/components/autofill/core/browser/ui/region_combobox_model.cc b/chromium/components/autofill/core/browser/ui/region_combobox_model.cc index 3d912a483ff..0b5f7978e40 100644 --- a/chromium/components/autofill/core/browser/ui/region_combobox_model.cc +++ b/chromium/components/autofill/core/browser/ui/region_combobox_model.cc @@ -46,9 +46,9 @@ int RegionComboboxModel::GetItemCount() const { return regions_.size(); } -base::string16 RegionComboboxModel::GetItemAt(int index) { +base::string16 RegionComboboxModel::GetItemAt(int index) const { DCHECK_GE(index, 0); - // This might happen because of the asynchonous nature of the data. + // This might happen because of the asynchronous nature of the data. if (static_cast<size_t>(index) >= regions_.size()) return l10n_util::GetStringUTF16(IDS_AUTOFILL_LOADING_REGIONS); @@ -60,8 +60,8 @@ base::string16 RegionComboboxModel::GetItemAt(int index) { return base::ASCIIToUTF16("---"); } -bool RegionComboboxModel::IsItemSeparatorAt(int index) { - // This might happen because of the asynchonous nature of the data. +bool RegionComboboxModel::IsItemSeparatorAt(int index) const { + // This might happen because of the asynchronous nature of the data. DCHECK_GE(index, 0); if (static_cast<size_t>(index) >= regions_.size()) return false; diff --git a/chromium/components/autofill/core/browser/ui/region_combobox_model.h b/chromium/components/autofill/core/browser/ui/region_combobox_model.h index 7e1018fe7d0..52d6a50df82 100644 --- a/chromium/components/autofill/core/browser/ui/region_combobox_model.h +++ b/chromium/components/autofill/core/browser/ui/region_combobox_model.h @@ -49,8 +49,8 @@ class RegionComboboxModel : public ui::ComboboxModel { // ui::ComboboxModel implementation: int GetItemCount() const override; - base::string16 GetItemAt(int index) override; - bool IsItemSeparatorAt(int index) override; + base::string16 GetItemAt(int index) const override; + bool IsItemSeparatorAt(int index) const override; void AddObserver(ui::ComboboxModelObserver* observer) override; void RemoveObserver(ui::ComboboxModelObserver* observer) override; diff --git a/chromium/components/autofill/core/browser/webdata/autofill_change.h b/chromium/components/autofill/core/browser/webdata/autofill_change.h index 605ff81bf0c..670858ac8d5 100644 --- a/chromium/components/autofill/core/browser/webdata/autofill_change.h +++ b/chromium/components/autofill/core/browser/webdata/autofill_change.h @@ -8,7 +8,7 @@ #include <string> #include <vector> -#include "base/logging.h" +#include "base/check.h" #include "components/autofill/core/browser/data_model/autofill_profile.h" #include "components/autofill/core/browser/webdata/autofill_entry.h" diff --git a/chromium/components/autofill/core/browser/webdata/autofill_table.cc b/chromium/components/autofill/core/browser/webdata/autofill_table.cc index ca4e266d38c..d2db94bca69 100644 --- a/chromium/components/autofill/core/browser/webdata/autofill_table.cc +++ b/chromium/components/autofill/core/browser/webdata/autofill_table.cc @@ -148,6 +148,7 @@ void BindCreditCardToStatement(const CreditCard& credit_card, s->BindInt64(index++, modification_date.ToTimeT()); s->BindString(index++, credit_card.origin()); s->BindString(index++, credit_card.billing_address_id()); + s->BindString16(index++, credit_card.nickname()); } base::string16 UnencryptedCardFromColumn( @@ -187,8 +188,7 @@ std::unique_ptr<CreditCard> CreditCardFromStatement( base::Time::FromTimeT(s.ColumnInt64(index++))); credit_card->set_origin(s.ColumnString(index++)); credit_card->set_billing_address_id(s.ColumnString(index++)); - credit_card->set_bank_name(s.ColumnString(index++)); - + credit_card->SetNickname(s.ColumnString16(index++)); return credit_card; } @@ -499,6 +499,9 @@ bool AutofillTable::MigrateToVersion(int version, case 86: *update_compatible_version = false; return MigrateToVersion86RemoveUnmaskedCreditCardsUseColumns(); + case 87: + *update_compatible_version = false; + return MigrateToVersion87AddCreditCardNicknameColumn(); } return true; } @@ -1103,8 +1106,8 @@ bool AutofillTable::AddCreditCard(const CreditCard& credit_card) { "INSERT INTO credit_cards" "(guid, name_on_card, expiration_month, expiration_year, " " card_number_encrypted, use_count, use_date, date_modified, origin," - " billing_address_id)" - "VALUES (?,?,?,?,?,?,?,?,?,?)")); + " billing_address_id, nickname)" + "VALUES (?,?,?,?,?,?,?,?,?,?,?)")); BindCreditCardToStatement(credit_card, AutofillClock::Now(), &s, *autofill_table_encryptor_); @@ -1129,7 +1132,7 @@ bool AutofillTable::UpdateCreditCard(const CreditCard& credit_card) { "UPDATE credit_cards " "SET guid=?, name_on_card=?, expiration_month=?," "expiration_year=?, card_number_encrypted=?, use_count=?, use_date=?," - "date_modified=?, origin=?, billing_address_id=?" + "date_modified=?, origin=?, billing_address_id=?, nickname=?" "WHERE guid=?1")); BindCreditCardToStatement(credit_card, update_modification_date @@ -1184,7 +1187,7 @@ std::unique_ptr<CreditCard> AutofillTable::GetCreditCard( sql::Statement s(db_->GetUniqueStatement( "SELECT guid, name_on_card, expiration_month, expiration_year, " "card_number_encrypted, use_count, use_date, date_modified, " - "origin, billing_address_id " + "origin, billing_address_id, nickname " "FROM credit_cards " "WHERE guid = ?")); s.BindString(0, guid); @@ -2825,6 +2828,12 @@ bool AutofillTable::MigrateToVersion86RemoveUnmaskedCreditCardsUseColumns() { transaction.Commit(); } +bool AutofillTable::MigrateToVersion87AddCreditCardNicknameColumn() { + // Add the nickname column to the credit_card table. + return db_->DoesColumnExist("credit_cards", "nickname") || + db_->Execute("ALTER TABLE credit_cards ADD COLUMN nickname VARCHAR"); +} + bool AutofillTable::AddFormFieldValuesTime( const std::vector<FormFieldData>& elements, std::vector<AutofillChange>* changes, @@ -3085,7 +3094,8 @@ bool AutofillTable::InitCreditCardsTable() { "origin VARCHAR DEFAULT '', " "use_count INTEGER NOT NULL DEFAULT 0, " "use_date INTEGER NOT NULL DEFAULT 0, " - "billing_address_id VARCHAR) ")) { + "billing_address_id VARCHAR, " + "nickname VARCHAR)")) { NOTREACHED(); return false; } diff --git a/chromium/components/autofill/core/browser/webdata/autofill_table.h b/chromium/components/autofill/core/browser/webdata/autofill_table.h index 2e61fdeed6e..a283890f43f 100644 --- a/chromium/components/autofill/core/browser/webdata/autofill_table.h +++ b/chromium/components/autofill/core/browser/webdata/autofill_table.h @@ -154,6 +154,8 @@ struct PaymentsCustomerData; // is the billing address for this card. Can be null in the // database, but always returned as an empty string in // CreditCard. Added in version 66. +// nickname A nickname for the card, entered by the user. Added in +// version 87. // // masked_credit_cards // This table contains "masked" credit card information @@ -556,6 +558,7 @@ class AutofillTable : public WebDatabaseTable, bool MigrateToVersion84AddNicknameColumn(); bool MigrateToVersion85AddCardIssuerColumnToMaskedCreditCard(); bool MigrateToVersion86RemoveUnmaskedCreditCardsUseColumns(); + bool MigrateToVersion87AddCreditCardNicknameColumn(); // Max data length saved in the table, AKA the maximum length allowed for // form data. diff --git a/chromium/components/autofill/core/browser/webdata/autofill_table_unittest.cc b/chromium/components/autofill/core/browser/webdata/autofill_table_unittest.cc index 84c2a30305e..5572c259b0d 100644 --- a/chromium/components/autofill/core/browser/webdata/autofill_table_unittest.cc +++ b/chromium/components/autofill/core/browser/webdata/autofill_table_unittest.cc @@ -986,6 +986,7 @@ TEST_F(AutofillTableTest, CreditCard) { work_creditcard.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("04")); work_creditcard.SetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, ASCIIToUTF16("2013")); + work_creditcard.SetNickname(ASCIIToUTF16("Corporate card")); Time pre_creation_time = AutofillClock::Now(); EXPECT_TRUE(table_->AddCreditCard(work_creditcard)); @@ -998,7 +999,7 @@ TEST_F(AutofillTableTest, CreditCard) { EXPECT_EQ(work_creditcard, *db_creditcard); sql::Statement s_work(db_->GetSQLConnection()->GetUniqueStatement( "SELECT guid, name_on_card, expiration_month, expiration_year, " - "card_number_encrypted, date_modified " + "card_number_encrypted, date_modified, nickname " "FROM credit_cards WHERE guid=?")); s_work.BindString(0, work_creditcard.guid()); ASSERT_TRUE(s_work.is_valid()); @@ -1017,6 +1018,7 @@ TEST_F(AutofillTableTest, CreditCard) { target_creditcard.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("06")); target_creditcard.SetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, ASCIIToUTF16("2012")); + target_creditcard.SetNickname(ASCIIToUTF16("Grocery card")); pre_creation_time = AutofillClock::Now(); EXPECT_TRUE(table_->AddCreditCard(target_creditcard)); @@ -1026,7 +1028,7 @@ TEST_F(AutofillTableTest, CreditCard) { EXPECT_EQ(target_creditcard, *db_creditcard); sql::Statement s_target(db_->GetSQLConnection()->GetUniqueStatement( "SELECT guid, name_on_card, expiration_month, expiration_year, " - "card_number_encrypted, date_modified " + "card_number_encrypted, date_modified, nickname " "FROM credit_cards WHERE guid=?")); s_target.BindString(0, target_creditcard.guid()); ASSERT_TRUE(s_target.is_valid()); @@ -1039,6 +1041,7 @@ TEST_F(AutofillTableTest, CreditCard) { target_creditcard.set_origin("Interactive Autofill dialog"); target_creditcard.SetRawInfo(CREDIT_CARD_NAME_FULL, ASCIIToUTF16("Charles Grady")); + target_creditcard.SetNickname(ASCIIToUTF16("Supermarket")); Time pre_modification_time = AutofillClock::Now(); EXPECT_TRUE(table_->UpdateCreditCard(target_creditcard)); Time post_modification_time = AutofillClock::Now(); @@ -1047,7 +1050,7 @@ TEST_F(AutofillTableTest, CreditCard) { EXPECT_EQ(target_creditcard, *db_creditcard); sql::Statement s_target_updated(db_->GetSQLConnection()->GetUniqueStatement( "SELECT guid, name_on_card, expiration_month, expiration_year, " - "card_number_encrypted, date_modified " + "card_number_encrypted, date_modified, nickname " "FROM credit_cards WHERE guid=?")); s_target_updated.BindString(0, target_creditcard.guid()); ASSERT_TRUE(s_target_updated.is_valid()); diff --git a/chromium/components/autofill/core/browser/webdata/autofill_wallet_metadata_sync_bridge.cc b/chromium/components/autofill/core/browser/webdata/autofill_wallet_metadata_sync_bridge.cc index 56e5daeb9f7..e43c36f6c40 100644 --- a/chromium/components/autofill/core/browser/webdata/autofill_wallet_metadata_sync_bridge.cc +++ b/chromium/components/autofill/core/browser/webdata/autofill_wallet_metadata_sync_bridge.cc @@ -664,7 +664,10 @@ AutofillWalletMetadataSyncBridge::MergeRemoteChanges( if (is_any_local_modified) { web_data_backend_->NotifyOfMultipleAutofillChanges(); } - return base::nullopt; + + return static_cast<syncer::SyncMetadataStoreChangeList*>( + metadata_change_list.get()) + ->TakeError(); } template <class DataType> diff --git a/chromium/components/autofill/core/common/autofill_data_validation.cc b/chromium/components/autofill/core/common/autofill_data_validation.cc index 92254628e63..67010537037 100644 --- a/chromium/components/autofill/core/common/autofill_data_validation.cc +++ b/chromium/components/autofill/core/common/autofill_data_validation.cc @@ -54,7 +54,7 @@ bool IsValidFormData(const FormData& form) { } bool IsValidPasswordFormFillData(const PasswordFormFillData& form) { - if (!IsValidString16(form.name) || !IsValidGURL(form.origin) || + if (!IsValidString16(form.name) || !IsValidGURL(form.url) || !IsValidGURL(form.action) || !IsValidFormFieldData(form.username_field) || !IsValidFormFieldData(form.password_field) || !IsValidString(form.preferred_realm)) { diff --git a/chromium/components/autofill/core/common/autofill_features.cc b/chromium/components/autofill/core/common/autofill_features.cc index 3856188c371..bfa1eec50ee 100644 --- a/chromium/components/autofill/core/common/autofill_features.cc +++ b/chromium/components/autofill/core/common/autofill_features.cc @@ -54,7 +54,7 @@ const base::Feature kAutofillCreditCardAssist{ // Controls whether we download server credit cards to the ephemeral // account-based storage when sync the transport is enabled. -const base::Feature kAutofillEnableAccountWalletStorage{ +const base::Feature kAutofillEnableAccountWalletStorage { "AutofillEnableAccountWalletStorage", #if defined(OS_CHROMEOS) || defined(OS_ANDROID) || defined(OS_IOS) // Wallet transport is only currently available on Win/Mac/Linux. @@ -65,6 +65,12 @@ const base::Feature kAutofillEnableAccountWalletStorage{ #endif }; +// Controls whether to detect and fill the augmented phone country code field +// when enabled. +const base::Feature kAutofillEnableAugmentedPhoneCountryCode{ + "AutofillEnableAugmentedPhoneCountryCode", + base::FEATURE_DISABLED_BY_DEFAULT}; + // Controls whether we use COMPANY as part of Autofill const base::Feature kAutofillEnableCompanyName{ "AutofillEnableCompanyName", base::FEATURE_ENABLED_BY_DEFAULT}; @@ -91,6 +97,13 @@ const base::Feature kAutofillEnforceMinRequiredFieldsForUpload{ "AutofillEnforceMinRequiredFieldsForUpload", base::FEATURE_DISABLED_BY_DEFAULT}; +// Controls whether or not all datalist shall be extracted into FormFieldData. +// This feature is enabled in both WebView and WebLayer where all datalists +// instead of only the focused one shall be extracted and sent to Android +// autofill service when the autofill session created. +const base::Feature kAutofillExtractAllDatalists{ + "AutofillExtractAllDatalists", base::FEATURE_DISABLED_BY_DEFAULT}; + // Autofill uses the local heuristic such that address forms are only filled if // at least 3 fields are fillable according to local heuristics. Unfortunately, // the criterion for fillability is only that the field type is unknown. So many @@ -99,6 +112,24 @@ const base::Feature kAutofillEnforceMinRequiredFieldsForUpload{ const base::Feature kAutofillFixFillableFieldTypes{ "AutofillFixFillableFieldTypes", base::FEATURE_DISABLED_BY_DEFAULT}; +// If enabled, prefilled country and state values are not reset before +// an address profile import. +// TODO(crbug.com/1100231): Remove once fix is tested. +const base::Feature kAutofillImportPrefilledCountryAndStateValues{ + "AutofillImportPrefilledCountryAndStateValues", + base::FEATURE_ENABLED_BY_DEFAULT}; + +// When enabled, Autofill keeps the initial field values in the |FormStructure| +// cache for all field types. +const base::Feature kAutofillKeepInitialFormValuesInCache{ + "AutofillKeepCachedFormValues", base::FEATURE_ENABLED_BY_DEFAULT}; + +// When enabled, Autofill will use FieldRendererIds instead of unique_names +// to align forms in FormStructure::RetrieveFromCache(). +const base::Feature kAutofillRetrieveFromCacheWithRendererIds{ + "AutofillRetrieveFromCacheWithRendererIds", + base::FEATURE_DISABLED_BY_DEFAULT}; + // When enabled, autofill suggestions are displayed in the keyboard accessory // instead of the regular popup. const base::Feature kAutofillKeyboardAccessory{ @@ -124,6 +155,15 @@ const base::Feature kAutofillPreferServerNamePredictions{ const base::Feature kAutofillProfileClientValidation{ "AutofillProfileClientValidation", base::FEATURE_DISABLED_BY_DEFAULT}; +const base::Feature kAutofillProfileImportFromUnifiedSection{ + "AutofillProfileImportFromUnifiedSection", + base::FEATURE_DISABLED_BY_DEFAULT}; + +// TODO(crbug.com/1101280): Remove once feature is tested. +const base::Feature kAutofillProfileImportFromUnfocusableFields{ + "AutofillProfileImportFromUnfocusableFields", + base::FEATURE_DISABLED_BY_DEFAULT}; + // Controls whether Autofill uses server-side validation to ensure that fields // with invalid data are not suggested. const base::Feature kAutofillProfileServerValidation{ diff --git a/chromium/components/autofill/core/common/autofill_features.h b/chromium/components/autofill/core/common/autofill_features.h index 30cf3076fc1..b0cf11a8ae2 100644 --- a/chromium/components/autofill/core/common/autofill_features.h +++ b/chromium/components/autofill/core/common/autofill_features.h @@ -29,12 +29,17 @@ extern const base::Feature kAutofillCacheQueryResponses; extern const base::Feature kAutofillCreateDataForTest; extern const base::Feature kAutofillCreditCardAssist; extern const base::Feature kAutofillEnableAccountWalletStorage; +extern const base::Feature kAutofillEnableAugmentedPhoneCountryCode; extern const base::Feature kAutofillEnableCompanyName; extern const base::Feature kAutofillEnableHideSuggestionsUI; extern const base::Feature kAutofillEnforceMinRequiredFieldsForHeuristics; extern const base::Feature kAutofillEnforceMinRequiredFieldsForQuery; extern const base::Feature kAutofillEnforceMinRequiredFieldsForUpload; +extern const base::Feature kAutofillExtractAllDatalists; extern const base::Feature kAutofillFixFillableFieldTypes; +extern const base::Feature kAutofillImportPrefilledCountryAndStateValues; +extern const base::Feature kAutofillKeepInitialFormValuesInCache; +extern const base::Feature kAutofillRetrieveFromCacheWithRendererIds; extern const base::Feature kAutofillKeyboardAccessory; extern const base::Feature kAutofillPruneSuggestions; extern const base::Feature kAutofillMetadataUploads; @@ -42,6 +47,8 @@ extern const base::Feature kAutofillOffNoServerData; extern const base::Feature kAutofillOverrideWithRaterConsensus; extern const base::Feature kAutofillPreferServerNamePredictions; extern const base::Feature kAutofillProfileClientValidation; +extern const base::Feature kAutofillProfileImportFromUnfocusableFields; +extern const base::Feature kAutofillProfileImportFromUnifiedSection; extern const base::Feature kAutofillProfileServerValidation; extern const base::Feature kAutofillRejectCompanyBirthyear; extern const base::Feature kAutofillRejectCompanySocialTitle; diff --git a/chromium/components/autofill/core/common/autofill_internals/log_message.cc b/chromium/components/autofill/core/common/autofill_internals/log_message.cc index 60da13de84e..db6483203a5 100644 --- a/chromium/components/autofill/core/common/autofill_internals/log_message.cc +++ b/chromium/components/autofill/core/common/autofill_internals/log_message.cc @@ -5,6 +5,7 @@ #include "components/autofill/core/common/autofill_internals/log_message.h" #include "base/logging.h" +#include "base/notreached.h" #include "components/autofill/core/common/logging/log_buffer.h" namespace autofill { diff --git a/chromium/components/autofill/core/common/autofill_internals/logging_scope.cc b/chromium/components/autofill/core/common/autofill_internals/logging_scope.cc index 2e1597b07bf..0c5f9a033b7 100644 --- a/chromium/components/autofill/core/common/autofill_internals/logging_scope.cc +++ b/chromium/components/autofill/core/common/autofill_internals/logging_scope.cc @@ -5,6 +5,7 @@ #include "components/autofill/core/common/autofill_internals/logging_scope.h" #include "base/logging.h" +#include "base/notreached.h" #include "components/autofill/core/common/logging/log_buffer.h" namespace autofill { diff --git a/chromium/components/autofill/core/common/autofill_payments_features.cc b/chromium/components/autofill/core/common/autofill_payments_features.cc index 24c62fb5a3c..5d728a56936 100644 --- a/chromium/components/autofill/core/common/autofill_payments_features.cc +++ b/chromium/components/autofill/core/common/autofill_payments_features.cc @@ -39,7 +39,14 @@ const base::Feature kAutofillCreditCardAblationExperiment{ // Enables the use of platform authenticators through WebAuthn to retrieve // credit cards from Google payments. const base::Feature kAutofillCreditCardAuthentication{ - "AutofillCreditCardAuthentication", base::FEATURE_DISABLED_BY_DEFAULT}; + "AutofillCreditCardAuthentication", +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + // Better Auth project is fully launched on Win/Mac. + base::FEATURE_ENABLED_BY_DEFAULT +#else + base::FEATURE_DISABLED_BY_DEFAULT +#endif +}; // When enabled, if credit card upload succeeded, the avatar icon will show a // highlight otherwise, the credit card icon image will be updated and if user @@ -48,10 +55,15 @@ const base::Feature kAutofillCreditCardUploadFeedback{ "AutofillCreditCardUploadFeedback", base::FEATURE_DISABLED_BY_DEFAULT}; // When enabled, the credit card nicknames will be manageable. They can be -// uploaded to Payments or be modified locally. +// modified locally. const base::Feature kAutofillEnableCardNicknameManagement{ "AutofillEnableCardNicknameManagement", base::FEATURE_DISABLED_BY_DEFAULT}; +// When enabled, the credit card nicknames will be manageable. They can be +// uploaded to Payments. +const base::Feature kAutofillEnableCardNicknameUpstream{ + "AutofillEnableCardNicknameUpstream", base::FEATURE_DISABLED_BY_DEFAULT}; + // When enabled, autofill payments bubbles' result will be recorded as either // 'accepted', 'cancelled', 'closed', 'not interacted' or 'lost focus'. const base::Feature kAutofillEnableFixedPaymentsBubbleLogging{ @@ -62,6 +74,11 @@ const base::Feature kAutofillEnableFixedPaymentsBubbleLogging{ const base::Feature kAutofillEnableGoogleIssuedCard{ "AutofillEnableGoogleIssuedCard", base::FEATURE_DISABLED_BY_DEFAULT}; +// When enabled, offer data will be retrieved during downstream and shown in +// the dropdown list. +const base::Feature kAutofillEnableOffersInDownstream{ + "kAutofillEnableOffersInDownstream", base::FEATURE_DISABLED_BY_DEFAULT}; + // When enabled, all payments related bubbles will not be dismissed upon page // navigation. const base::Feature kAutofillEnableStickyPaymentsBubble{ @@ -83,11 +100,6 @@ const base::Feature kAutofillEnableToolbarStatusChip{ const base::Feature kAutofillEnableVirtualCard{ "AutofillEnableVirtualCard", base::FEATURE_DISABLED_BY_DEFAULT}; -// When enabled, will remove the option to save unmasked server cards as -// FULL_SERVER_CARDs upon successful unmask. -const base::Feature kAutofillNoLocalSaveOnUnmaskSuccess{ - "AutofillNoLocalSaveOnUnmaskSuccess", base::FEATURE_ENABLED_BY_DEFAULT}; - // When enabled, the Save Card infobar will be dismissed by a user initiated // navigation other than one caused by submitted form. const base::Feature kAutofillSaveCardDismissOnNavigation{ @@ -110,9 +122,6 @@ const base::Feature kAutofillUpstream{"AutofillUpstream", const base::Feature kAutofillUpstreamAllowAllEmailDomains{ "AutofillUpstreamAllowAllEmailDomains", base::FEATURE_DISABLED_BY_DEFAULT}; -const base::Feature kAutofillUpstreamEditableExpirationDate{ - "AutofillUpstreamEditableExpirationDate", base::FEATURE_ENABLED_BY_DEFAULT}; - bool ShouldShowImprovedUserConsentForCreditCardSave() { #if defined(OS_WIN) || defined(OS_MACOSX) || \ (defined(OS_LINUX) && !defined(OS_CHROMEOS)) diff --git a/chromium/components/autofill/core/common/autofill_payments_features.h b/chromium/components/autofill/core/common/autofill_payments_features.h index 4e5c628c5c1..e76903e0ca6 100644 --- a/chromium/components/autofill/core/common/autofill_payments_features.h +++ b/chromium/components/autofill/core/common/autofill_payments_features.h @@ -25,18 +25,18 @@ extern const base::Feature kAutofillCreditCardAblationExperiment; extern const base::Feature kAutofillCreditCardAuthentication; extern const base::Feature kAutofillCreditCardUploadFeedback; extern const base::Feature kAutofillEnableCardNicknameManagement; +extern const base::Feature kAutofillEnableCardNicknameUpstream; extern const base::Feature kAutofillEnableFixedPaymentsBubbleLogging; extern const base::Feature kAutofillEnableGoogleIssuedCard; +extern const base::Feature kAutofillEnableOffersInDownstream; extern const base::Feature kAutofillEnableStickyPaymentsBubble; extern const base::Feature kAutofillEnableSurfacingServerCardNickname; extern const base::Feature kAutofillEnableToolbarStatusChip; extern const base::Feature kAutofillEnableVirtualCard; -extern const base::Feature kAutofillNoLocalSaveOnUnmaskSuccess; extern const base::Feature kAutofillSaveCardDismissOnNavigation; extern const base::Feature kAutofillSaveCardInfobarEditSupport; extern const base::Feature kAutofillUpstream; extern const base::Feature kAutofillUpstreamAllowAllEmailDomains; -extern const base::Feature kAutofillUpstreamEditableExpirationDate; // Return whether a [No thanks] button and new messaging is shown in the save // card bubbles. This will be called only on desktop platforms. diff --git a/chromium/components/autofill/core/common/autofill_regex_constants.cc b/chromium/components/autofill/core/common/autofill_regex_constants.cc index 697333ffb6c..f1334a24043 100644 --- a/chromium/components/autofill/core/common/autofill_regex_constants.cc +++ b/chromium/components/autofill/core/common/autofill_regex_constants.cc @@ -259,6 +259,7 @@ const char kGiftCardRe[] = "gift.?(card|cert)"; const char kDebitGiftCardRe[] = "(?:visa|mastercard|discover|amex|american express).*gift.?card"; const char kDebitCardRe[] = "debit.*card"; +const char kDayRe[] = "day"; ///////////////////////////////////////////////////////////////////////////// // email_field.cc @@ -350,6 +351,8 @@ const char kPhoneRe[] = "|电话" // zh-CN "|മൊബൈല്" // ml for mobile "|(?:전화|핸드폰|휴대폰|휴대전화)(?:.?번호)?"; // ko-KR +const char kAugmentedPhoneCountryCodeRe[] = + "^[^0-9+]*(?:\\+|00)\\s*([1-9]\\d{0,3})\\D*$"; const char kCountryCodeRe[] = "country.*code|ccode|_cc|phone.*code|user.*phone.*code"; const char kAreaCodeNotextRe[] = "^\\($"; diff --git a/chromium/components/autofill/core/common/autofill_regex_constants.h b/chromium/components/autofill/core/common/autofill_regex_constants.h index 5cd1e1d80f8..af69056041b 100644 --- a/chromium/components/autofill/core/common/autofill_regex_constants.h +++ b/chromium/components/autofill/core/common/autofill_regex_constants.h @@ -37,6 +37,7 @@ extern const char kCardIgnoredRe[]; extern const char kGiftCardRe[]; extern const char kDebitGiftCardRe[]; extern const char kDebitCardRe[]; +extern const char kDayRe[]; extern const char kEmailRe[]; extern const char kNameIgnoredRe[]; extern const char kNameRe[]; @@ -46,6 +47,7 @@ extern const char kMiddleInitialRe[]; extern const char kMiddleNameRe[]; extern const char kLastNameRe[]; extern const char kPhoneRe[]; +extern const char kAugmentedPhoneCountryCodeRe[]; extern const char kCountryCodeRe[]; extern const char kAreaCodeNotextRe[]; extern const char kAreaCodeRe[]; diff --git a/chromium/components/autofill/core/common/autofill_regexes.cc b/chromium/components/autofill/core/common/autofill_regexes.cc index 8053897a792..02254fa266a 100644 --- a/chromium/components/autofill/core/common/autofill_regexes.cc +++ b/chromium/components/autofill/core/common/autofill_regexes.cc @@ -64,7 +64,8 @@ namespace autofill { bool MatchesPattern(const base::string16& input, const base::string16& pattern, - base::string16* match) { + base::string16* match, + int32_t group_to_be_captured) { static base::NoDestructor<AutofillRegexes> g_autofill_regexes; static base::NoDestructor<base::Lock> g_lock; base::AutoLock lock(*g_lock); @@ -78,7 +79,8 @@ bool MatchesPattern(const base::string16& input, DCHECK(U_SUCCESS(status)); if (matched == TRUE && match) { - icu::UnicodeString match_unicode = matcher->group(0, status); + icu::UnicodeString match_unicode = + matcher->group(group_to_be_captured, status); DCHECK(U_SUCCESS(status)); *match = base::i18n::UnicodeStringToString16(match_unicode); } diff --git a/chromium/components/autofill/core/common/autofill_regexes.h b/chromium/components/autofill/core/common/autofill_regexes.h index 9bed8f717ce..837130b582c 100644 --- a/chromium/components/autofill/core/common/autofill_regexes.h +++ b/chromium/components/autofill/core/common/autofill_regexes.h @@ -12,10 +12,11 @@ namespace autofill { // Case-insensitive regular expression matching. // Returns true if |pattern| is found in |input|. -// If |match| is not nullptr, the matched part is assigned to it. +// The |group_to_be_captured| numbered group is captured into |match|. bool MatchesPattern(const base::string16& input, const base::string16& pattern, - base::string16* match = nullptr); + base::string16* match = nullptr, + int32_t group_to_be_captured = 0); } // namespace autofill diff --git a/chromium/components/autofill/core/common/form_data.h b/chromium/components/autofill/core/common/form_data.h index 06b7805ac2b..9b591942ef9 100644 --- a/chromium/components/autofill/core/common/form_data.h +++ b/chromium/components/autofill/core/common/form_data.h @@ -11,6 +11,7 @@ #include <vector> #include "base/strings/string16.h" +#include "build/build_config.h" #include "components/autofill/core/common/form_field_data.h" #include "components/autofill/core/common/mojom/autofill_types.mojom-shared.h" #include "components/autofill/core/common/renderer_id.h" @@ -114,6 +115,9 @@ struct FormData { std::vector<FieldRendererId> username_predictions; // True if this is a Gaia form which should be skipped on saving. bool is_gaia_with_skip_save_password_form = false; +#if defined(OS_IOS) + std::string frame_id; +#endif }; // For testing. diff --git a/chromium/components/autofill/core/common/form_field_data.cc b/chromium/components/autofill/core/common/form_field_data.cc index 81aa5d980ff..fc998f879ef 100644 --- a/chromium/components/autofill/core/common/form_field_data.cc +++ b/chromium/components/autofill/core/common/form_field_data.cc @@ -441,6 +441,7 @@ LogBuffer& operator<<(LogBuffer& buffer, const FormFieldData& field) { buffer << Tr{} << "Is focusable:" << field.is_focusable; buffer << Tr{} << "Is enabled:" << field.is_enabled; buffer << Tr{} << "Is readonly:" << field.is_readonly; + buffer << Tr{} << "Is empty:" << (field.value.empty() ? "Yes" : "No"); buffer << CTag{"table"}; return buffer; } diff --git a/chromium/components/autofill/core/common/form_field_data.h b/chromium/components/autofill/core/common/form_field_data.h index 10ea42b8524..84becbef96c 100644 --- a/chromium/components/autofill/core/common/form_field_data.h +++ b/chromium/components/autofill/core/common/form_field_data.h @@ -188,6 +188,14 @@ struct FormFieldData { // is valid if not empty, will not be synced to the server side or be used for // field comparison and isn't in serialize methods. gfx::RectF bounds; + + // The datalist is associated with this field, if any. The following two + // vectors valid if not empty, will not be synced to the server side or be + // used for field comparison and aren't in serialize methods. + // The datalist option is intentionally separated from option_values and + // option_contents because they are handled very differently in autofill. + std::vector<base::string16> datalist_values; + std::vector<base::string16> datalist_labels; }; // Serialize and deserialize FormFieldData. These are used when FormData objects diff --git a/chromium/components/autofill/core/common/mojom/autofill_types.mojom b/chromium/components/autofill/core/common/mojom/autofill_types.mojom index bd4ea2c4bff..fabc693b7c4 100644 --- a/chromium/components/autofill/core/common/mojom/autofill_types.mojom +++ b/chromium/components/autofill/core/common/mojom/autofill_types.mojom @@ -150,6 +150,9 @@ struct FormFieldData { LabelSource label_source; gfx.mojom.RectF bounds; + + array<mojo_base.mojom.String16> datalist_values; + array<mojo_base.mojom.String16> datalist_labels; }; struct ButtonTitleInfo { @@ -206,7 +209,7 @@ struct PasswordAndMetadata { // autofill::PasswordFormFillData struct PasswordFormFillData { FormRendererId form_renderer_id; - url.mojom.Url origin; + url.mojom.Url url; url.mojom.Url action; FormFieldData username_field; FormFieldData password_field; @@ -278,7 +281,7 @@ struct PasswordForm { Scheme scheme; string signon_realm; - url.mojom.Url origin_with_path; + url.mojom.Url url; url.mojom.Url action; string affiliated_web_realm; mojo_base.mojom.String16 submit_element; diff --git a/chromium/components/autofill/core/common/mojom/autofill_types_mojom_traits.cc b/chromium/components/autofill/core/common/mojom/autofill_types_mojom_traits.cc index 968c22d22ba..7b14bd7ce24 100644 --- a/chromium/components/autofill/core/common/mojom/autofill_types_mojom_traits.cc +++ b/chromium/components/autofill/core/common/mojom/autofill_types_mojom_traits.cc @@ -104,6 +104,11 @@ bool StructTraits< if (!data.ReadBounds(&out->bounds)) return false; + if (!data.ReadDatalistValues(&out->datalist_values)) + return false; + if (!data.ReadDatalistLabels(&out->datalist_labels)) + return false; + return true; } @@ -219,7 +224,7 @@ bool StructTraits<autofill::mojom::PasswordFormFillDataDataView, Read(autofill::mojom::PasswordFormFillDataDataView data, autofill::PasswordFormFillData* out) { if (!data.ReadFormRendererId(&out->form_renderer_id) || - !data.ReadOrigin(&out->origin) || !data.ReadAction(&out->action) || + !data.ReadUrl(&out->url) || !data.ReadAction(&out->action) || !data.ReadUsernameField(&out->username_field) || !data.ReadPasswordField(&out->password_field) || !data.ReadPreferredRealm(&out->preferred_realm) || @@ -269,8 +274,7 @@ bool StructTraits< autofill::PasswordForm>::Read(autofill::mojom::PasswordFormDataView data, autofill::PasswordForm* out) { if (!data.ReadScheme(&out->scheme) || - !data.ReadSignonRealm(&out->signon_realm) || - !data.ReadOriginWithPath(&out->origin) || + !data.ReadSignonRealm(&out->signon_realm) || !data.ReadUrl(&out->url) || !data.ReadAction(&out->action) || !data.ReadAffiliatedWebRealm(&out->affiliated_web_realm) || !data.ReadSubmitElement(&out->submit_element) || diff --git a/chromium/components/autofill/core/common/mojom/autofill_types_mojom_traits.h b/chromium/components/autofill/core/common/mojom/autofill_types_mojom_traits.h index 0d19056c39f..650f438831b 100644 --- a/chromium/components/autofill/core/common/mojom/autofill_types_mojom_traits.h +++ b/chromium/components/autofill/core/common/mojom/autofill_types_mojom_traits.h @@ -177,6 +177,16 @@ struct StructTraits<autofill::mojom::FormFieldDataDataView, return r.bounds; } + static const std::vector<base::string16>& datalist_values( + const autofill::FormFieldData& r) { + return r.datalist_values; + } + + static const std::vector<base::string16>& datalist_labels( + const autofill::FormFieldData& r) { + return r.datalist_labels; + } + static bool Read(autofill::mojom::FormFieldDataDataView data, autofill::FormFieldData* out); }; @@ -363,8 +373,8 @@ struct StructTraits<autofill::mojom::PasswordFormFillDataDataView, return r.form_renderer_id; } - static const GURL& origin(const autofill::PasswordFormFillData& r) { - return r.origin; + static const GURL& url(const autofill::PasswordFormFillData& r) { + return r.url; } static const GURL& action(const autofill::PasswordFormFillData& r) { @@ -484,9 +494,7 @@ struct StructTraits<autofill::mojom::PasswordFormDataView, return r.signon_realm; } - static const GURL& origin_with_path(const autofill::PasswordForm& r) { - return r.origin; - } + static const GURL& url(const autofill::PasswordForm& r) { return r.url; } static const GURL& action(const autofill::PasswordForm& r) { return r.action; diff --git a/chromium/components/autofill/core/common/mojom/autofill_types_mojom_traits_unittest.cc b/chromium/components/autofill/core/common/mojom/autofill_types_mojom_traits_unittest.cc index 438d958d7df..366c50b514e 100644 --- a/chromium/components/autofill/core/common/mojom/autofill_types_mojom_traits_unittest.cc +++ b/chromium/components/autofill/core/common/mojom/autofill_types_mojom_traits_unittest.cc @@ -49,7 +49,7 @@ void CreateTestFieldDataPredictions(const std::string& signature, void CreateTestPasswordFormFillData(PasswordFormFillData* fill_data) { fill_data->form_renderer_id = autofill::FormRendererId(1234); - fill_data->origin = GURL("https://foo.com/"); + fill_data->url = GURL("https://foo.com/"); fill_data->action = GURL("https://foo.com/login"); test::CreateTestSelectField("TestUsernameFieldLabel", "TestUsernameFieldName", "TestUsernameFieldValue", kOptions, kOptions, 4, @@ -78,7 +78,7 @@ void CreateTestPasswordFormFillData(PasswordFormFillData* fill_data) { void CreateTestPasswordForm(PasswordForm* form) { form->scheme = PasswordForm::Scheme::kHtml; form->signon_realm = "https://foo.com/"; - form->origin = GURL("https://foo.com/"); + form->url = GURL("https://foo.com/"); form->action = GURL("https://foo.com/login"); form->affiliated_web_realm = "https://foo.com/"; form->submit_element = base::ASCIIToUTF16("test_submit"); @@ -132,7 +132,7 @@ void CreatePasswordGenerationUIData( void CheckEqualPasswordFormFillData(const PasswordFormFillData& expected, const PasswordFormFillData& actual) { EXPECT_EQ(expected.form_renderer_id, actual.form_renderer_id); - EXPECT_EQ(expected.origin, actual.origin); + EXPECT_EQ(expected.url, actual.url); EXPECT_EQ(expected.action, actual.action); EXPECT_TRUE(EquivalentData(expected.username_field, actual.username_field)); EXPECT_TRUE(EquivalentData(expected.password_field, actual.password_field)); @@ -326,6 +326,36 @@ TEST_F(AutofillTypeTraitsTestImpl, PassFormFieldData) { loop.Run(); } +TEST_F(AutofillTypeTraitsTestImpl, PassDataListFormFieldData) { + // Basically copied from PassFormFieldData and replaced Select with Datalist. + FormFieldData input; + test::CreateTestDatalistField("DatalistLabel", "DatalistName", + "DatalistValue", kOptions, kOptions, &input); + // Set other attributes to check if they are passed correctly. + input.id_attribute = base::ASCIIToUTF16("id"); + input.name_attribute = base::ASCIIToUTF16("name"); + input.autocomplete_attribute = "on"; + input.placeholder = base::ASCIIToUTF16("placeholder"); + input.css_classes = base::ASCIIToUTF16("class1"); + input.aria_label = base::ASCIIToUTF16("aria label"); + input.aria_description = base::ASCIIToUTF16("aria description"); + input.max_length = 12345; + input.is_autofilled = true; + input.check_status = FormFieldData::CheckStatus::kChecked; + input.should_autocomplete = true; + input.role = FormFieldData::RoleAttribute::kPresentation; + input.text_direction = base::i18n::RIGHT_TO_LEFT; + input.properties_mask = FieldPropertiesFlags::kHadFocus; + input.typed_value = base::ASCIIToUTF16("TestTypedValue"); + input.bounds = gfx::RectF(1, 2, 10, 100); + + base::RunLoop loop; + mojo::Remote<mojom::TypeTraitsTest> remote(GetTypeTraitsTestRemote()); + remote->PassFormFieldData( + input, base::BindOnce(&ExpectFormFieldData, input, loop.QuitClosure())); + loop.Run(); +} + TEST_F(AutofillTypeTraitsTestImpl, PassFormData) { FormData input; test::CreateTestAddressFormData(&input); diff --git a/chromium/components/autofill/core/common/password_form.cc b/chromium/components/autofill/core/common/password_form.cc index 4ef730d00c7..4f164e30c13 100644 --- a/chromium/components/autofill/core/common/password_form.cc +++ b/chromium/components/autofill/core/common/password_form.cc @@ -46,7 +46,7 @@ void PasswordFormToJSON(const PasswordForm& form, target->SetBoolean("is_public_suffix_match", form.is_public_suffix_match); target->SetBoolean("is_affiliation_based_match", form.is_affiliation_based_match); - target->SetString("origin", form.origin.possibly_invalid_spec()); + target->SetString("url", form.url.possibly_invalid_spec()); target->SetString("action", form.action.possibly_invalid_spec()); target->SetString("submit_element", form.submit_element); target->SetBoolean("has_renderer_ids", form.has_renderer_ids); @@ -162,7 +162,7 @@ bool PasswordForm::HasNonEmptyPasswordValue() const { bool PasswordForm::operator==(const PasswordForm& form) const { return scheme == form.scheme && signon_realm == form.signon_realm && - origin == form.origin && action == form.action && + url == form.url && action == form.action && submit_element == form.submit_element && has_renderer_ids == form.has_renderer_ids && username_element == form.username_element && @@ -214,8 +214,7 @@ bool PasswordForm::operator!=(const PasswordForm& form) const { bool ArePasswordFormUniqueKeysEqual(const PasswordForm& left, const PasswordForm& right) { - return (left.signon_realm == right.signon_realm && - left.origin == right.origin && + return (left.signon_realm == right.signon_realm && left.url == right.url && left.username_element == right.username_element && left.username_value == right.username_value && left.password_element == right.password_element); diff --git a/chromium/components/autofill/core/common/password_form.h b/chromium/components/autofill/core/common/password_form.h index e4fea845c48..67694d8d4e6 100644 --- a/chromium/components/autofill/core/common/password_form.h +++ b/chromium/components/autofill/core/common/password_form.h @@ -64,14 +64,13 @@ struct PasswordForm { // data from the database, so it must not be empty. std::string signon_realm; - // An origin URL consists of the scheme, host, port and path; the rest is - // stripped. This is the primary data used by the PasswordManager to decide - // (in longest matching prefix fashion) whether or not a given PasswordForm - // result from the database is a good fit for a particular form on a page. + // An URL consists of the scheme, host, port and path; the rest is stripped. + // This is the primary data used by the PasswordManager to decide (in longest + // matching prefix fashion) whether or not a given PasswordForm result from + // the database is a good fit for a particular form on a page. + // // This should not be empty except for Android based credentials. - // TODO(melandory): origin should be renamed in order to be consistent with - // GURL definition of origin. - GURL origin; + GURL url; // The action target of the form; like |origin| URL consists of the scheme, // host, port and path; the rest is stripped. This is the primary data used by @@ -298,13 +297,16 @@ struct PasswordForm { bool is_new_password_reliable = false; // Serialized to prefs, so don't change numeric values! + // These values are persisted to logs. Entries should not be renumbered and + // numeric values should never be reused. enum class Store { // Default value. kNotSet = 0, // Credential came from the profile (i.e. local) storage. kProfileStore = 1, // Credential came from the Gaia-account-scoped storage. - kAccountStore = 2 + kAccountStore = 2, + kMaxValue = kAccountStore }; Store in_store = Store::kNotSet; diff --git a/chromium/components/autofill/core/common/password_form_fill_data.h b/chromium/components/autofill/core/common/password_form_fill_data.h index 0e0ed4bc63e..abe21a09684 100644 --- a/chromium/components/autofill/core/common/password_form_fill_data.h +++ b/chromium/components/autofill/core/common/password_form_fill_data.h @@ -62,11 +62,10 @@ struct PasswordFormFillData { // The name of the form. base::string16 name; - // An origin URL consists of the scheme, host, port and path; the rest is - // stripped. - GURL origin; + // An URL consisting of the scheme, host, port and path; the rest is stripped. + GURL url; - // The action target of the form; like |origin| URL consists of the scheme, + // The action target of the form; like |url| URL consists of the scheme, // host, port and path; the rest is stripped. GURL action; diff --git a/chromium/components/autofill/core/common/password_form_generation_data.cc b/chromium/components/autofill/core/common/password_form_generation_data.cc index 6ebec23be8c..04cccb19fbb 100644 --- a/chromium/components/autofill/core/common/password_form_generation_data.cc +++ b/chromium/components/autofill/core/common/password_form_generation_data.cc @@ -19,11 +19,9 @@ PasswordFormGenerationData::PasswordFormGenerationData( #if defined(OS_IOS) PasswordFormGenerationData::PasswordFormGenerationData( FormRendererId form_renderer_id, - base::string16 new_password_element, FieldRendererId new_password_renderer_id, FieldRendererId confirmation_password_renderer_id) : form_renderer_id(form_renderer_id), - new_password_element(new_password_element), new_password_renderer_id(new_password_renderer_id), confirmation_password_renderer_id(confirmation_password_renderer_id) {} diff --git a/chromium/components/autofill/core/common/password_form_generation_data.h b/chromium/components/autofill/core/common/password_form_generation_data.h index c76f1eccaa5..2618629d47a 100644 --- a/chromium/components/autofill/core/common/password_form_generation_data.h +++ b/chromium/components/autofill/core/common/password_form_generation_data.h @@ -22,7 +22,6 @@ struct PasswordFormGenerationData { FieldRendererId confirmation_password_renderer_id); #if defined(OS_IOS) PasswordFormGenerationData(FormRendererId form_renderer_id, - base::string16 new_password_element, FieldRendererId new_password_renderer_id, FieldRendererId confirmation_password_renderer_id); @@ -33,9 +32,6 @@ struct PasswordFormGenerationData { ~PasswordFormGenerationData(); FormRendererId form_renderer_id; - // TODO(crbug.com/1075444): Remove this once VotesUploader starts to use - // unique renderer IDs. - base::string16 new_password_element; #endif FieldRendererId new_password_renderer_id; FieldRendererId confirmation_password_renderer_id; diff --git a/chromium/components/autofill/core/common/renderer_id.h b/chromium/components/autofill/core/common/renderer_id.h index 79eaab6620d..511d4d28461 100644 --- a/chromium/components/autofill/core/common/renderer_id.h +++ b/chromium/components/autofill/core/common/renderer_id.h @@ -29,6 +29,8 @@ using FieldRendererIdType = // order to avoid having to define out-of-line constructors in all structs that // contain renderer IDs. +// The FormRendererId of a synthetic form is_null(). A synthetic form is the +// collection of form fields outside of the scope of any <form> tag in a page. class FormRendererId : public internal::FormRendererIdType { using internal::FormRendererIdType::IdType; }; diff --git a/chromium/components/autofill/ios/browser/autofill_agent.mm b/chromium/components/autofill/ios/browser/autofill_agent.mm index fceb06e2fda..fafe880b37e 100644 --- a/chromium/components/autofill/ios/browser/autofill_agent.mm +++ b/chromium/components/autofill/ios/browser/autofill_agent.mm @@ -187,8 +187,7 @@ void GetFormField(autofill::FormFieldData* field, _prefObserverBridge->ObserveChangesForPreference( autofill::prefs::kAutofillProfileEnabled, &_prefChangeRegistrar); - _jsAutofillManager = [[JsAutofillManager alloc] - initWithReceiver:_webState->GetJSInjectionReceiver()]; + _jsAutofillManager = [[JsAutofillManager alloc] init]; } return self; } diff --git a/chromium/components/autofill/ios/browser/autofill_util.h b/chromium/components/autofill/ios/browser/autofill_util.h index 7065abd8203..8c4f61538ee 100644 --- a/chromium/components/autofill/ios/browser/autofill_util.h +++ b/chromium/components/autofill/ios/browser/autofill_util.h @@ -72,7 +72,6 @@ bool ExtractFormFieldData(const base::DictionaryValue& field, void ExecuteJavaScriptFunction(const std::string& name, const std::vector<base::Value>& parameters, web::WebFrame* frame, - CRWJSInjectionReceiver* js_injection_receiver, base::OnceCallback<void(NSString*)> callback); } // namespace autofill diff --git a/chromium/components/autofill/ios/browser/autofill_util.mm b/chromium/components/autofill/ios/browser/autofill_util.mm index 8e14a554ba2..af140f29505 100644 --- a/chromium/components/autofill/ios/browser/autofill_util.mm +++ b/chromium/components/autofill/ios/browser/autofill_util.mm @@ -19,7 +19,6 @@ #include "components/autofill/core/common/autofill_util.h" #include "components/autofill/core/common/form_data.h" #include "components/autofill/core/common/form_field_data.h" -#import "ios/web/public/deprecated/crw_js_injection_receiver.h" #import "ios/web/public/navigation/navigation_item.h" #import "ios/web/public/navigation/navigation_manager.h" #include "ios/web/public/security/ssl_status.h" @@ -27,6 +26,9 @@ #include "url/gurl.h" #include "url/origin.h" +using base::NumberToString; +using base::StringToUint; + namespace { // The timeout for any JavaScript call in this file. const int64_t kJavaScriptExecutionTimeoutInSeconds = 5; @@ -50,17 +52,11 @@ bool IsContextSecureForWebState(web::WebState* web_state) { } std::unique_ptr<base::Value> ParseJson(NSString* json_string) { - // Convert JSON string to JSON object |JSONValue|. - int error_code = 0; - std::string error_message; - std::unique_ptr<base::Value> json_value( - base::JSONReader::ReadAndReturnErrorDeprecated( - base::SysNSStringToUTF8(json_string), base::JSON_PARSE_RFC, - &error_code, &error_message)); - if (error_code) + base::Optional<base::Value> json_value = + base::JSONReader::Read(base::SysNSStringToUTF8(json_string)); + if (!json_value) return nullptr; - - return json_value; + return base::Value::ToUniquePtrValue(std::move(*json_value)); } bool ExtractFormsData(NSString* forms_json, @@ -123,9 +119,9 @@ bool ExtractFormData(const base::Value& form_value, std::string unique_renderer_id; form_dictionary->GetString("unique_renderer_id", &unique_renderer_id); - if (!unique_renderer_id.empty()) { - base::StringToUint(unique_renderer_id, - &form_data->unique_renderer_id.value()); + if (!unique_renderer_id.empty() && + unique_renderer_id != NumberToString(kNotSetRendererID)) { + StringToUint(unique_renderer_id, &form_data->unique_renderer_id.value()); } else { form_data->unique_renderer_id = FormRendererId(); } @@ -141,6 +137,7 @@ bool ExtractFormData(const base::Value& form_value, form_dictionary->GetBoolean("is_form_tag", &form_data->is_form_tag); form_dictionary->GetBoolean("is_formless_checkout", &form_data->is_formless_checkout); + form_dictionary->GetString("frame_id", &form_data->frame_id); // Field list (mandatory) is extracted. const base::ListValue* fields_list = nullptr; @@ -169,9 +166,9 @@ bool ExtractFormFieldData(const base::DictionaryValue& field, std::string unique_renderer_id; field.GetString("unique_renderer_id", &unique_renderer_id); - if (!unique_renderer_id.empty()) { - base::StringToUint(unique_renderer_id, - &field_data->unique_renderer_id.value()); + if (!unique_renderer_id.empty() && + unique_renderer_id != NumberToString(kNotSetRendererID)) { + StringToUint(unique_renderer_id, &field_data->unique_renderer_id.value()); } else { field_data->unique_renderer_id = FieldRendererId(); } @@ -233,7 +230,6 @@ bool ExtractFormFieldData(const base::DictionaryValue& field, void ExecuteJavaScriptFunction(const std::string& name, const std::vector<base::Value>& parameters, web::WebFrame* frame, - CRWJSInjectionReceiver* js_injection_receiver, base::OnceCallback<void(NSString*)> callback) { __block base::OnceCallback<void(NSString*)> cb = std::move(callback); diff --git a/chromium/components/autofill/ios/browser/js_autofill_manager.h b/chromium/components/autofill/ios/browser/js_autofill_manager.h index 15c404b9b3a..7ebd532dcd1 100644 --- a/chromium/components/autofill/ios/browser/js_autofill_manager.h +++ b/chromium/components/autofill/ios/browser/js_autofill_manager.h @@ -8,7 +8,6 @@ #include "base/ios/block_types.h" #include "base/values.h" #include "components/autofill/core/common/autofill_constants.h" -#import "ios/web/public/deprecated/crw_js_injection_receiver.h" namespace web { class WebFrame; @@ -72,12 +71,6 @@ class WebFrame; - (void)toggleTrackingUserEditedFields:(BOOL)state inFrame:(web::WebFrame*)frame; -// Designated initializer. |receiver| should not be nil. -- (instancetype)initWithReceiver:(CRWJSInjectionReceiver*)receiver - NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; - @end #endif // COMPONENTS_AUTOFILL_IOS_BROWSER_JS_AUTOFILL_MANAGER_H_ diff --git a/chromium/components/autofill/ios/browser/js_autofill_manager.mm b/chromium/components/autofill/ios/browser/js_autofill_manager.mm index 13592c9ba3a..4e8f9645df4 100644 --- a/chromium/components/autofill/ios/browser/js_autofill_manager.mm +++ b/chromium/components/autofill/ios/browser/js_autofill_manager.mm @@ -12,8 +12,6 @@ #include "base/command_line.h" #include "base/feature_list.h" #include "base/format_macros.h" -#include "base/json/json_writer.h" -#include "base/json/string_escape.h" #include "base/mac/foundation_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/sys_string_conversions.h" @@ -26,19 +24,7 @@ #error "This file requires ARC support." #endif -@implementation JsAutofillManager { - // The injection receiver used to evaluate JavaScript. - __weak CRWJSInjectionReceiver* _receiver; -} - -- (instancetype)initWithReceiver:(CRWJSInjectionReceiver*)receiver { - DCHECK(receiver); - self = [super init]; - if (self) { - _receiver = receiver; - } - return self; -} +@implementation JsAutofillManager - (void)addJSDelayInFrame:(web::WebFrame*)frame { const base::CommandLine* command_line = @@ -52,7 +38,7 @@ std::vector<base::Value> parameters; parameters.push_back(base::Value(commandLineDelay)); autofill::ExecuteJavaScriptFunction( - "autofill.setDelay", parameters, frame, _receiver, + "autofill.setDelay", parameters, frame, base::OnceCallback<void(NSString*)>()); } } @@ -70,8 +56,7 @@ parameters.push_back(base::Value(static_cast<int>(requiredFieldsCount))); parameters.push_back(base::Value(restrictUnownedFieldsToFormlessCheckout)); autofill::ExecuteJavaScriptFunction("autofill.extractForms", parameters, - frame, _receiver, - base::BindOnce(completionHandler)); + frame, base::BindOnce(completionHandler)); } #pragma mark - @@ -84,7 +69,7 @@ std::vector<base::Value> parameters; parameters.push_back(std::move(*data)); autofill::ExecuteJavaScriptFunction("autofill.fillActiveFormField", - parameters, frame, _receiver, + parameters, frame, base::BindOnce(^(NSString*) { completionHandler(); })); @@ -94,7 +79,7 @@ std::vector<base::Value> parameters; parameters.push_back(base::Value(state ? 200 : 0)); autofill::ExecuteJavaScriptFunction("formHandlers.trackFormMutations", - parameters, frame, _receiver, + parameters, frame, base::OnceCallback<void(NSString*)>()); } @@ -104,7 +89,7 @@ parameters.push_back(base::Value(static_cast<bool>(state))); autofill::ExecuteJavaScriptFunction( "formHandlers.toggleTrackingUserEditedFields", parameters, frame, - _receiver, base::OnceCallback<void(NSString*)>()); + base::OnceCallback<void(NSString*)>()); } - (void)fillForm:(std::unique_ptr<base::Value>)data @@ -121,7 +106,7 @@ parameters.push_back(std::move(*data)); parameters.push_back(base::Value(fieldIdentifier)); autofill::ExecuteJavaScriptFunction("autofill.fillForm", parameters, frame, - _receiver, base::BindOnce(^(NSString*) { + base::BindOnce(^(NSString*) { completionHandler(); })); } @@ -135,7 +120,7 @@ parameters.push_back(base::Value(base::SysNSStringToUTF8(formName))); parameters.push_back(base::Value(base::SysNSStringToUTF8(fieldIdentifier))); autofill::ExecuteJavaScriptFunction("autofill.clearAutofilledFields", - parameters, frame, _receiver, + parameters, frame, base::BindOnce(^(NSString*) { completionHandler(); })); @@ -147,7 +132,7 @@ std::vector<base::Value> parameters; parameters.push_back(std::move(*data)); autofill::ExecuteJavaScriptFunction("autofill.fillPredictionData", parameters, - frame, _receiver, + frame, base::OnceCallback<void(NSString*)>()); } diff --git a/chromium/components/autofill/ios/browser/js_suggestion_manager.mm b/chromium/components/autofill/ios/browser/js_suggestion_manager.mm index e5767bba023..837546f40de 100644 --- a/chromium/components/autofill/ios/browser/js_suggestion_manager.mm +++ b/chromium/components/autofill/ios/browser/js_suggestion_manager.mm @@ -56,8 +56,7 @@ parameters.push_back(base::Value(base::SysNSStringToUTF8(fieldName))); autofill::ExecuteJavaScriptFunction( "suggestion.selectNextElement", parameters, - [self frameWithFrameID:frameID], _receiver, - base::OnceCallback<void(NSString*)>()); + [self frameWithFrameID:frameID], base::OnceCallback<void(NSString*)>()); } - (void)selectPreviousElementInFrameWithID:(NSString*)frameID { @@ -72,8 +71,7 @@ parameters.push_back(base::Value(base::SysNSStringToUTF8(fieldName))); autofill::ExecuteJavaScriptFunction( "suggestion.selectPreviousElement", parameters, - [self frameWithFrameID:frameID], _receiver, - base::OnceCallback<void(NSString*)>()); + [self frameWithFrameID:frameID], base::OnceCallback<void(NSString*)>()); } - (void)fetchPreviousAndNextElementsPresenceInFrameWithID:(NSString*)frameID @@ -98,8 +96,7 @@ parameters.push_back(base::Value(base::SysNSStringToUTF8(fieldName))); autofill::ExecuteJavaScriptFunction( "suggestion.hasPreviousNextElements", parameters, - [self frameWithFrameID:frameID], _receiver, - base::BindOnce(^(NSString* result) { + [self frameWithFrameID:frameID], base::BindOnce(^(NSString* result) { // The result maybe an empty string here due to 2 reasons: // 1) When there is an exception running the JS // 2) There is a race when the page is changing due to which @@ -127,8 +124,7 @@ std::vector<base::Value> parameters; autofill::ExecuteJavaScriptFunction( "suggestion.blurActiveElement", parameters, - [self frameWithFrameID:frameID], _receiver, - base::OnceCallback<void(NSString*)>()); + [self frameWithFrameID:frameID], base::OnceCallback<void(NSString*)>()); } - (web::WebFrame*)frameWithFrameID:(NSString*)frameID { diff --git a/chromium/components/autofill/ios/form_util/form_activity_tab_helper.mm b/chromium/components/autofill/ios/form_util/form_activity_tab_helper.mm index e04639c4d0c..d4e4020a628 100644 --- a/chromium/components/autofill/ios/form_util/form_activity_tab_helper.mm +++ b/chromium/components/autofill/ios/form_util/form_activity_tab_helper.mm @@ -7,6 +7,7 @@ #import <Foundation/Foundation.h> #include "base/bind.h" +#include "base/logging.h" #include "base/strings/string_number_conversions.h" #include "base/values.h" #include "components/autofill/ios/form_util/form_activity_observer.h" diff --git a/chromium/components/autofill/ios/form_util/form_activity_tab_helper_unittest.mm b/chromium/components/autofill/ios/form_util/form_activity_tab_helper_unittest.mm index cffa22b4fa1..9acd740d56f 100644 --- a/chromium/components/autofill/ios/form_util/form_activity_tab_helper_unittest.mm +++ b/chromium/components/autofill/ios/form_util/form_activity_tab_helper_unittest.mm @@ -16,6 +16,8 @@ #import "ios/web/public/test/web_test_with_web_state.h" #include "testing/platform_test.h" +using web::WebFrame; + class FormTestClient : public web::TestWebClient { public: NSString* GetDocumentStartScriptForAllFrames( @@ -61,19 +63,22 @@ TEST_F(FormActivityTabHelperTest, TestObserverDocumentSubmitted) { ExecuteJavaScript(@"__gCrWeb.fill.setUpForUniqueIDs(0);"); ASSERT_FALSE(observer_->submit_document_info()); const std::string kTestFormName("form-name"); - const std::string kTestFormData( - "[{\"name\":\"form-name\",\"origin\":\"https://chromium.test/" - "\",\"action\":\"https://chromium.test/\"," - "\"name_attribute\":\"form-name\",\"id_attribute\":\"\"," - "\"unique_renderer_id\":\"0\"}]"); + + WebFrame* main_frame = web_state()->GetWebFramesManager()->GetMainWebFrame(); + std::string mainFrameID = main_frame->GetFrameId(); + const std::string kTestFormData = + std::string("[{\"name\":\"form-name\",\"origin\":\"https://chromium.test/" + "\",\"action\":\"https://chromium.test/\"," + "\"name_attribute\":\"form-name\",\"id_attribute\":\"\"," + "\"unique_renderer_id\":\"0\",\"frame_id\":\"") + + mainFrameID + std::string("\"}]"); + bool has_user_gesture = false; bool form_in_main_frame = true; EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout( base::test::ios::kWaitForJSCompletionTimeout, ^bool { return web_state()->GetWebFramesManager()->GetMainWebFrame() != nullptr; })); - web::WebFrame* main_frame = - web_state()->GetWebFramesManager()->GetMainWebFrame(); ExecuteJavaScript(@"document.getElementById('submit').click();"); ASSERT_TRUE(observer_->submit_document_info()); @@ -96,19 +101,22 @@ TEST_F(FormActivityTabHelperTest, TestFormSubmittedHook) { ExecuteJavaScript(@"__gCrWeb.fill.setUpForUniqueIDs(0);"); ASSERT_FALSE(observer_->submit_document_info()); const std::string kTestFormName("form-name"); - const std::string kTestFormData( - "[{\"name\":\"form-name\",\"origin\":\"https://chromium.test/" - "\",\"action\":\"https://chromium.test/\"," - "\"name_attribute\":\"form-name\",\"id_attribute\":\"form\"," - "\"unique_renderer_id\":\"0\"}]"); + + WebFrame* main_frame = web_state()->GetWebFramesManager()->GetMainWebFrame(); + std::string mainFrameID = main_frame->GetFrameId(); + const std::string kTestFormData = + std::string("[{\"name\":\"form-name\",\"origin\":\"https://chromium.test/" + "\",\"action\":\"https://chromium.test/\"," + "\"name_attribute\":\"form-name\",\"id_attribute\":\"form\"," + "\"unique_renderer_id\":\"0\",\"frame_id\":\"") + + mainFrameID + std::string("\"}]"); + bool has_user_gesture = false; bool form_in_main_frame = true; EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout( base::test::ios::kWaitForJSCompletionTimeout, ^bool { return web_state()->GetWebFramesManager()->GetMainWebFrame() != nullptr; })); - web::WebFrame* main_frame = - web_state()->GetWebFramesManager()->GetMainWebFrame(); ExecuteJavaScript(@"document.getElementById('form').submit();"); ASSERT_TRUE(observer_->submit_document_info()); @@ -133,8 +141,7 @@ TEST_F(FormActivityTabHelperTest, TestObserverFormActivityFrameMessaging) { base::test::ios::kWaitForJSCompletionTimeout, ^bool { return web_state()->GetWebFramesManager()->GetMainWebFrame() != nullptr; })); - web::WebFrame* main_frame = - web_state()->GetWebFramesManager()->GetMainWebFrame(); + WebFrame* main_frame = web_state()->GetWebFramesManager()->GetMainWebFrame(); ASSERT_FALSE(observer_->form_activity_info()); // First call will set document.activeElement (which is usually set by user // action. Second call will trigger the message. diff --git a/chromium/components/autofill/ios/form_util/form_unittest.mm b/chromium/components/autofill/ios/form_util/form_unittest.mm index b5e60552017..3f505ad8cd7 100644 --- a/chromium/components/autofill/ios/form_util/form_unittest.mm +++ b/chromium/components/autofill/ios/form_util/form_unittest.mm @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#import "base/test/ios/wait_util.h" #include "components/autofill/ios/form_util/form_activity_tab_helper.h" #include "components/autofill/ios/form_util/test_form_activity_observer.h" #import "ios/web/public/browser_state.h" @@ -16,6 +17,9 @@ #error "This file requires ARC support." #endif +using base::test::ios::WaitUntilConditionOrTimeout; +using base::test::ios::kWaitForJSCompletionTimeout; + class FormTestClient : public web::TestWebClient { public: NSString* GetDocumentStartScriptForAllFrames( @@ -60,9 +64,9 @@ TEST_F(FormJsTest, KeyUpEventFocused) { "var ev = new KeyboardEvent('keyup', {bubbles:true});" "e.dispatchEvent(ev);"); autofill::TestFormActivityObserver* block_observer = observer_.get(); - WaitForCondition(^bool { + ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{ return block_observer->form_activity_info() != nullptr; - }); + })); autofill::TestFormActivityInfo* info = observer_->form_activity_info(); ASSERT_TRUE(info); EXPECT_EQ("keyup", info->form_activity.type); @@ -93,9 +97,9 @@ TEST_F(FormJsTest, FocusMainFrame) { ASSERT_FALSE(observer_->form_activity_info()); ExecuteJavaScript(@"document.getElementById('id1').focus();"); autofill::TestFormActivityObserver* block_observer = observer_.get(); - WaitForCondition(^bool { + ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{ return block_observer->form_activity_info() != nullptr; - }); + })); autofill::TestFormActivityInfo* info = observer_->form_activity_info(); ASSERT_TRUE(info); EXPECT_EQ("focus", info->form_activity.type); @@ -131,9 +135,9 @@ TEST_F(FormJsTest, FocusSameOriginIFrame) { @"document.getElementById('frame1').contentDocument.getElementById('id1')" @".focus()"); autofill::TestFormActivityObserver* block_observer = observer_.get(); - WaitForCondition(^bool { + ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{ return block_observer->form_activity_info() != nullptr; - }); + })); autofill::TestFormActivityInfo* info = observer_->form_activity_info(); ASSERT_TRUE(info); EXPECT_EQ("focus", info->form_activity.type); @@ -168,10 +172,10 @@ TEST_F(FormJsTest, AddForm) { @"document.body.appendChild(form);"); autofill::TestFormActivityObserver* block_observer = observer_.get(); __block autofill::TestFormActivityInfo* info = nil; - WaitForCondition(^{ + ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{ info = block_observer->form_activity_info(); return info != nil; - }); + })); EXPECT_EQ("form_changed", info->form_activity.type); EXPECT_FALSE(info->form_activity.input_missing); } @@ -186,10 +190,10 @@ TEST_F(FormJsTest, AddInput) { @"document.getElementById('formId').appendChild(input);"); autofill::TestFormActivityObserver* block_observer = observer_.get(); __block autofill::TestFormActivityInfo* info = nil; - WaitForCondition(^{ + ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{ info = block_observer->form_activity_info(); return info != nil; - }); + })); EXPECT_EQ("form_changed", info->form_activity.type); EXPECT_FALSE(info->form_activity.input_missing); } @@ -204,10 +208,10 @@ TEST_F(FormJsTest, AddSelect) { @"document.getElementById('formId').appendChild(select);"); autofill::TestFormActivityObserver* block_observer = observer_.get(); __block autofill::TestFormActivityInfo* info = nil; - WaitForCondition(^{ + ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{ info = block_observer->form_activity_info(); return info != nil; - }); + })); EXPECT_EQ("form_changed", info->form_activity.type); EXPECT_FALSE(info->form_activity.input_missing); } @@ -225,10 +229,32 @@ TEST_F(FormJsTest, AddOption) { @"document.getElementById('select1').appendChild(option);"); autofill::TestFormActivityObserver* block_observer = observer_.get(); __block autofill::TestFormActivityInfo* info = nil; - WaitForCondition(^{ + ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{ info = block_observer->form_activity_info(); return info != nil; - }); + })); EXPECT_EQ("form_changed", info->form_activity.type); EXPECT_FALSE(info->form_activity.input_missing); } + +// Tests that removing password form triggers 'password_form_removed" event. +TEST_F(FormJsTest, RemoveForm) { + LoadHtml(@"<form id=\"form1\">" + "<input type=\"text\" name=\"username\" id=\"id1\">" + "<input type=\"password\" name=\"password\" id=\"id2\">" + "<input type=\"submit\" id=\"submit_input\"/>" + "</form>"); + ExecuteJavaScript(@"__gCrWeb.fill.setUpForUniqueIDs(0);" + @"__gCrWeb.formHandlers.trackFormMutations(10);" + @"var form1 = document.getElementById('form1');" + @"__gCrWeb.fill.setUniqueIDIfNeeded(form1);" + @"form1.parentNode.removeChild(form1);"); + autofill::TestFormActivityObserver* block_observer = observer_.get(); + __block autofill::TestFormActivityInfo* info = nil; + ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{ + info = block_observer->form_activity_info(); + return info != nil; + })); + EXPECT_EQ("password_form_removed", info->form_activity.type); + EXPECT_FALSE(info->form_activity.input_missing); +} diff --git a/chromium/components/autofill/ios/form_util/resources/fill.js b/chromium/components/autofill/ios/form_util/resources/fill.js index a48befb78a5..1108ece5273 100644 --- a/chromium/components/autofill/ios/form_util/resources/fill.js +++ b/chromium/components/autofill/ios/form_util/resources/fill.js @@ -34,6 +34,7 @@ let AutofillFormFieldData; * origin: string, * action: string, * fields: Array<AutofillFormFieldData> + * frame_id: string * }} */ let AutofillFormData; @@ -144,6 +145,16 @@ __gCrWeb.fill.ROLE_ATTRIBUTE_PRESENTATION = 0; __gCrWeb.fill.RENDERER_ID_NOT_SET = '-1'; /** + * The name of the JS Symbol used to set stable unique form and field IDs. + * + * This variable is |kNotSetRendererID| from + * chromium/src/components/autofill/ios/browser/autofill_util.h + * + * @const {string} + */ +__gCrWeb.fill.UNIQUE_ID_SYMBOL_NAME = '__gChrome~uniqueID'; + +/** * Returns true if an element can be autocompleted. * * This method aims to provide the same logic as method @@ -838,13 +849,10 @@ __gCrWeb.fill.webFormElementToFormData = function( form['name_attribute'] = formElement.getAttribute('name') || ''; form['id_attribute'] = formElement.getAttribute('id') || ''; - try { - __gCrWeb.fill.setUniqueIDIfNeeded(formElement); - const uniqueID = Symbol.for('__gChrome~uniqueID'); - form['unique_renderer_id'] = formElement[uniqueID].toString(); - } catch (e) { - form['unique_renderer_id'] = __gCrWeb.fill.RENDERER_ID_NOT_SET; - } + __gCrWeb.fill.setUniqueIDIfNeeded(formElement); + form['unique_renderer_id'] = __gCrWeb.fill.getUniqueID(formElement); + + form['frame_id'] = frame.__gCrWeb.message.getFrameId(); // Note different from form_autofill_util.cc version of this method, which // computes |form.action| using document.completeURL(form_element.action()) @@ -1938,12 +1946,8 @@ __gCrWeb.fill.webFormControlElementToFormField = function( field['name_attribute'] = element.getAttribute('name') || ''; field['id_attribute'] = element.getAttribute('id') || ''; - try { - const uniqueID = Symbol.for('__gChrome~uniqueID'); - field['unique_renderer_id'] = element[uniqueID].toString(); - } catch (e) { - field['unique_renderer_id'] = __gCrWeb.fill.RENDERER_ID_NOT_SET; - } + __gCrWeb.fill.setUniqueIDIfNeeded(element); + field['unique_renderer_id'] = __gCrWeb.fill.getUniqueID(element); field['form_control_type'] = element.type; const autocompleteAttribute = element.getAttribute('autocomplete'); @@ -2298,7 +2302,7 @@ __gCrWeb.fill.extractAutofillableElementsFromSet = function(controlElements) { * @param {int} nextAvailableID Next available integer. */ __gCrWeb.fill['setUpForUniqueIDs'] = function(nextAvailableID) { - const uniqueID = Symbol.for('__gChrome~uniqueID'); + const uniqueID = Symbol.for(__gCrWeb.fill.UNIQUE_ID_SYMBOL_NAME); document[uniqueID] = nextAvailableID; }; @@ -2306,9 +2310,29 @@ __gCrWeb.fill['setUpForUniqueIDs'] = function(nextAvailableID) { * @param {Element} element Form or form input element. */ __gCrWeb.fill.setUniqueIDIfNeeded = function(element) { - const uniqueID = Symbol.for('__gChrome~uniqueID'); - if (typeof element[uniqueID] === 'undefined') { - element[uniqueID] = document[uniqueID]++; + try { + const uniqueID = Symbol.for(__gCrWeb.fill.UNIQUE_ID_SYMBOL_NAME); + if (typeof element[uniqueID] === 'undefined') { + element[uniqueID] = document[uniqueID]++; + } + } catch (e) { + } +}; + +/** + * @param {Element} element Form or form input element. + * @return {String} Unique stable ID converted to string.. + */ +__gCrWeb.fill.getUniqueID = function(element) { + try { + const uniqueID = Symbol.for(__gCrWeb.fill.UNIQUE_ID_SYMBOL_NAME); + if (typeof element[uniqueID] !== 'undefined' && !isNaN(element[uniqueID])) { + return element[uniqueID].toString(); + } else { + return __gCrWeb.fill.RENDERER_ID_NOT_SET; + } + } catch (e) { + return __gCrWeb.fill.RENDERER_ID_NOT_SET; } }; diff --git a/chromium/components/autofill/ios/form_util/resources/form.js b/chromium/components/autofill/ios/form_util/resources/form.js index d393d81f905..e857c9e88b5 100644 --- a/chromium/components/autofill/ios/form_util/resources/form.js +++ b/chromium/components/autofill/ios/form_util/resources/form.js @@ -280,7 +280,7 @@ __gCrWeb.form.getFormElementFromUniqueFormId = function(identifier) { const forms = document.forms; for (let i = 0; i < forms.length; i++) { const form = forms[i]; - const uniqueID = Symbol.for('__gChrome~uniqueID'); + const uniqueID = Symbol.for(__gCrWeb.fill.UNIQUE_ID_SYMBOL_NAME); if (identifier === form[uniqueID]) { return form; } diff --git a/chromium/components/autofill/ios/form_util/resources/form_handlers.js b/chromium/components/autofill/ios/form_util/resources/form_handlers.js index a0e2bad0577..69f0d8d2c9c 100644 --- a/chromium/components/autofill/ios/form_util/resources/form_handlers.js +++ b/chromium/components/autofill/ios/form_util/resources/form_handlers.js @@ -110,22 +110,16 @@ function formActivity_(evt) { if (target !== lastFocusedElement) { return; } - let formUniqueIdString = __gCrWeb.fill.RENDERER_ID_NOT_SET; - let fieldUniqueIdString = __gCrWeb.fill.RENDERER_ID_NOT_SET; - try { - __gCrWeb.fill.setUniqueIDIfNeeded(target.form); - __gCrWeb.fill.setUniqueIDIfNeeded(target); - const uniqueID = Symbol.for('__gChrome~uniqueID'); - formUniqueIdString = target.form[uniqueID].toString(); - fieldUniqueIdString = target[uniqueID].toString(); - } catch (e) { - } + __gCrWeb.fill.setUniqueIDIfNeeded(target.form); + __gCrWeb.fill.setUniqueIDIfNeeded(target); + const formUniqueId = __gCrWeb.fill.getUniqueID(target.form); + const fieldUniqueId = __gCrWeb.fill.getUniqueID(target); const msg = { 'command': 'form.activity', 'formName': __gCrWeb.form.getFormIdentifier(target.form), - 'uniqueFormID': formUniqueIdString, + 'uniqueFormID': formUniqueId, 'fieldIdentifier': __gCrWeb.form.getFieldIdentifier(target), - 'uniqueFieldID': fieldUniqueIdString, + 'uniqueFieldID': fieldUniqueId, 'fieldType': fieldType, 'type': evt.type, 'value': value, @@ -267,6 +261,45 @@ __gCrWeb.formHandlers['trackFormMutations'] = function(delay) { }; return sendFormMutationMessageAfterDelay_(msg, delay); } + + const removedElements = []; + for (let j = 0; j < mutation.removedNodes.length; j++) { + const node = mutation.removedNodes[j]; + // Ignore non-element nodes. + if (node.nodeType !== Node.ELEMENT_NODE) { + continue; + } + removedElements.push(node); + [].push.apply( + removedElements, [].slice.call(node.getElementsByTagName('FORM'))); + } + const formGone = removedElements.find(function(element) { + if (element.tagName.match(/(FORM)/)) { + for (let k = 0; k < element.elements.length; k++) { + if (element.elements[k].tagName.match(/(INPUT)/) && + element.elements[k].type === 'password') { + return true; + } + } + return false; + } + return false; + }); + const uniqueFormId = __gCrWeb.fill.getUniqueID(formGone); + if (formGone) { + const msg = { + 'command': 'form.activity', + 'formName': '', + 'uniqueFormID': uniqueFormId, + 'fieldIdentifier': '', + 'uniqueFieldID': '', + 'fieldType': '', + 'type': 'password_form_removed', + 'value': '', + 'hasUserGesture': false + }; + return sendFormMutationMessageAfterDelay_(msg, delay); + } } }); formMutationObserver.observe(document, {childList: true, subtree: true}); |