diff options
Diffstat (limited to 'chromium/components/autofill_assistant')
369 files changed, 9149 insertions, 3186 deletions
diff --git a/chromium/components/autofill_assistant/android/BUILD.gn b/chromium/components/autofill_assistant/android/BUILD.gn index c9a01527422..4570b138f6b 100644 --- a/chromium/components/autofill_assistant/android/BUILD.gn +++ b/chromium/components/autofill_assistant/android/BUILD.gn @@ -19,6 +19,8 @@ android_library("java") { ":public_dependencies_java", ":public_java", "//base:base_java", + "//base:jni_java", + "//build/android:build_java", "//components/autofill/android:autofill_java", "//components/autofill/android:prefeditor_autofill_java", "//components/browser_ui/bottomsheet/android:java", @@ -36,18 +38,18 @@ android_library("java") { "//components/version_info/android:version_constants_java", "//content/public/android:content_java", "//mojo/public/java:bindings_java", - "//third_party/android_deps:android_support_v7_appcompat_java", "//third_party/android_deps:com_android_support_support_annotations_java", "//third_party/android_deps:material_design_java", "//third_party/androidx:androidx_annotation_annotation_java", + "//third_party/androidx:androidx_appcompat_appcompat_java", "//third_party/androidx:androidx_appcompat_appcompat_resources_java", "//third_party/androidx:androidx_collection_collection_java", "//third_party/androidx:androidx_coordinatorlayout_coordinatorlayout_java", "//third_party/androidx:androidx_core_core_java", "//third_party/androidx:androidx_gridlayout_gridlayout_java", - "//third_party/androidx:androidx_lifecycle_lifecycle_common_java", "//third_party/androidx:androidx_lifecycle_lifecycle_runtime_java", "//third_party/androidx:androidx_recyclerview_recyclerview_java", + "//third_party/androidx:androidx_swiperefreshlayout_swiperefreshlayout_java", "//third_party/blink/public/mojom:android_mojo_bindings_java", "//ui/android:ui_java", "//url:gurl_java", @@ -149,6 +151,7 @@ android_library("java") { "java/src/org/chromium/components/autofill_assistant/user_data/AssistantDataOriginNotice.java", "java/src/org/chromium/components/autofill_assistant/user_data/AssistantDateTime.java", "java/src/org/chromium/components/autofill_assistant/user_data/AssistantInfoSection.java", + "java/src/org/chromium/components/autofill_assistant/user_data/AssistantLoadingSpinner.java", "java/src/org/chromium/components/autofill_assistant/user_data/AssistantLoginChoice.java", "java/src/org/chromium/components/autofill_assistant/user_data/AssistantLoginSection.java", "java/src/org/chromium/components/autofill_assistant/user_data/AssistantPaymentMethodSection.java", @@ -191,7 +194,6 @@ android_library("public_java") { sources = [ "public/java/src/org/chromium/components/autofill_assistant/AssistantAddressEditorGms.java", - "public/java/src/org/chromium/components/autofill_assistant/AssistantContactEditorAccount.java", "public/java/src/org/chromium/components/autofill_assistant/AssistantFeatures.java", "public/java/src/org/chromium/components/autofill_assistant/AssistantModuleInstallUi.java", "public/java/src/org/chromium/components/autofill_assistant/AssistantOnboardingHelper.java", @@ -335,7 +337,6 @@ android_library("autofill_assistant_public_java") { deps = [ ":animated_poodle_resources", "//base:base_java", - "//third_party/android_deps:android_support_v7_appcompat_java", "//third_party/androidx:androidx_appcompat_appcompat_resources_java", "//ui/android:ui_java", ] @@ -359,7 +360,6 @@ android_library("autofill_assistant_public_impl_java") { deps = [ ":animated_poodle_resources", "//base:base_java", - "//third_party/android_deps:android_support_v7_appcompat_java", "//third_party/androidx:androidx_appcompat_appcompat_resources_java", "//ui/android:ui_java", ] @@ -422,6 +422,7 @@ android_resources("java_resources") { "internal/java/res/layout/autofill_assistant_form_selection_input.xml", "internal/java/res/layout/autofill_assistant_header.xml", "internal/java/res/layout/autofill_assistant_info_box.xml", + "internal/java/res/layout/autofill_assistant_loading_spinner.xml", "internal/java/res/layout/autofill_assistant_login.xml", "internal/java/res/layout/autofill_assistant_onboarding_no_button.xml", "internal/java/res/layout/autofill_assistant_onboarding_terms.xml", diff --git a/chromium/components/autofill_assistant/android/internal/java/res/drawable/autofill_assistant_circle_background.xml b/chromium/components/autofill_assistant/android/internal/java/res/drawable/autofill_assistant_circle_background.xml index c1f7b3591a6..ec60e06b6a4 100644 --- a/chromium/components/autofill_assistant/android/internal/java/res/drawable/autofill_assistant_circle_background.xml +++ b/chromium/components/autofill_assistant/android/internal/java/res/drawable/autofill_assistant_circle_background.xml @@ -5,5 +5,5 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> - <solid android:color="@color/blue_when_enabled"/> + <solid android:color="@color/blue_when_enabled_list"/> </shape>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/res/drawable/autofill_assistant_rounded_corner_background.xml b/chromium/components/autofill_assistant/android/internal/java/res/drawable/autofill_assistant_rounded_corner_background.xml index 1b024cf3799..91e08ed2529 100644 --- a/chromium/components/autofill_assistant/android/internal/java/res/drawable/autofill_assistant_rounded_corner_background.xml +++ b/chromium/components/autofill_assistant/android/internal/java/res/drawable/autofill_assistant_rounded_corner_background.xml @@ -7,5 +7,5 @@ <corners android:radius="4dp" /> <solid - android:color="@color/blue_when_enabled" /> + android:color="@color/blue_when_enabled_list" /> </shape>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/res/layout/autofill_assistant_form_counter.xml b/chromium/components/autofill_assistant/android/internal/java/res/layout/autofill_assistant_form_counter.xml index 0f46cf0e247..4cff57162a3 100644 --- a/chromium/components/autofill_assistant/android/internal/java/res/layout/autofill_assistant_form_counter.xml +++ b/chromium/components/autofill_assistant/android/internal/java/res/layout/autofill_assistant_form_counter.xml @@ -36,7 +36,7 @@ android:layout_width="@dimen/autofill_assistant_minimum_touch_target_size" android:layout_height="@dimen/autofill_assistant_minimum_touch_target_size" android:padding="@dimen/autofill_assistant_form_counter_button_padding" - android:tint="@color/blue_when_enabled" + android:tint="@color/blue_when_enabled_list" android:contentDescription="@string/autofill_assistant_decrease_value" app:srcCompat="@drawable/ic_remove_outline_white_24dp"/> <TextView @@ -50,7 +50,7 @@ android:layout_width="@dimen/autofill_assistant_minimum_touch_target_size" android:layout_height="@dimen/autofill_assistant_minimum_touch_target_size" android:padding="@dimen/autofill_assistant_form_counter_button_padding" - android:tint="@color/blue_when_enabled" + android:tint="@color/blue_when_enabled_list" android:contentDescription="@string/autofill_assistant_increase_value" app:srcCompat="@drawable/ic_add_outline_white_24dp"/> </LinearLayout> diff --git a/chromium/components/autofill_assistant/android/internal/java/res/layout/autofill_assistant_form_counter_input.xml b/chromium/components/autofill_assistant/android/internal/java/res/layout/autofill_assistant_form_counter_input.xml index d8f1caa8116..0518d74cb86 100644 --- a/chromium/components/autofill_assistant/android/internal/java/res/layout/autofill_assistant_form_counter_input.xml +++ b/chromium/components/autofill_assistant/android/internal/java/res/layout/autofill_assistant_form_counter_input.xml @@ -44,7 +44,7 @@ android:id="@+id/chevron" android:layout_width="16dp" android:layout_height="16dp" - android:tint="@color/blue_when_enabled" + android:tint="@color/blue_when_enabled_list" app:srcCompat="@drawable/ic_expand_more_black_24dp" tools:ignore="ContentDescription"/> </LinearLayout> diff --git a/chromium/components/autofill_assistant/android/internal/java/res/layout/autofill_assistant_header.xml b/chromium/components/autofill_assistant/android/internal/java/res/layout/autofill_assistant_header.xml index 3195a882f56..91e3b7ef2b2 100644 --- a/chromium/components/autofill_assistant/android/internal/java/res/layout/autofill_assistant_header.xml +++ b/chromium/components/autofill_assistant/android/internal/java/res/layout/autofill_assistant_header.xml @@ -39,7 +39,6 @@ android:ellipsize="end" android:textAppearance="@style/TextAppearance.AssistantBlackTitle"/> - <!-- Not adding contentDescription as we will hide the button in Talkback mode --> <org.chromium.ui.widget.ChromeImageView android:id="@+id/tts_button" android:layout_width="@dimen/autofill_assistant_minimum_touch_target_size" @@ -47,6 +46,7 @@ android:padding="@dimen/autofill_assistant_tts_button_padding" android:visibility="gone" android:tint="@macro/default_icon_color" + android:contentDescription="@string/autofill_assistant_tts_button" app:srcCompat="@drawable/ic_volume_on_white_24dp"/> <org.chromium.ui.widget.ChromeImageView diff --git a/chromium/components/autofill_assistant/android/internal/java/res/layout/autofill_assistant_loading_spinner.xml b/chromium/components/autofill_assistant/android/internal/java/res/layout/autofill_assistant_loading_spinner.xml new file mode 100644 index 00000000000..224467b2f0e --- /dev/null +++ b/chromium/components/autofill_assistant/android/internal/java/res/layout/autofill_assistant_loading_spinner.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2022 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. --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingTop="@dimen/autofill_assistant_loading_spinner_padding" + android:paddingBottom="@dimen/autofill_assistant_loading_spinner_padding"> + <org.chromium.components.autofill_assistant.user_data.AssistantLoadingSpinner + android:id="@+id/loading_spinner" + android:layout_width="@dimen/autofill_assistant_loading_spinner_size" + android:layout_height="@dimen/autofill_assistant_loading_spinner_size"/> +</LinearLayout>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/res/layout/autofill_assistant_payment_request_section_title.xml b/chromium/components/autofill_assistant/android/internal/java/res/layout/autofill_assistant_payment_request_section_title.xml index c3ed5d06a10..d7a8819f2a6 100644 --- a/chromium/components/autofill_assistant/android/internal/java/res/layout/autofill_assistant_payment_request_section_title.xml +++ b/chromium/components/autofill_assistant/android/internal/java/res/layout/autofill_assistant_payment_request_section_title.xml @@ -25,11 +25,11 @@ android:layout_height="wrap_content" android:orientation="horizontal"> <org.chromium.ui.widget.ChromeImageView + android:id="@+id/section_title_add_button_icon" android:layout_width="24dp" android:layout_height="24dp" android:layout_gravity="center_vertical" - android:src="@drawable/plus" - app:tint="@macro/default_icon_color_accent1"/> + android:src="@drawable/plus"/> <Space android:layout_width="8dp" android:layout_height="0dp"/> <TextView android:id="@+id/section_title_add_button_label" android:layout_width="wrap_content" diff --git a/chromium/components/autofill_assistant/android/internal/java/res/values-v17/dimens.xml b/chromium/components/autofill_assistant/android/internal/java/res/values-v17/dimens.xml index 8dadd63b5c4..ad19668fa1e 100644 --- a/chromium/components/autofill_assistant/android/internal/java/res/values-v17/dimens.xml +++ b/chromium/components/autofill_assistant/android/internal/java/res/values-v17/dimens.xml @@ -56,4 +56,7 @@ <dimen name="autofill_assistant_form_line_height_3">88dp</dimen> <dimen name="autofill_assistant_root_view_top_padding">12dp</dimen> + + <dimen name="autofill_assistant_loading_spinner_size">20dp</dimen> + <dimen name="autofill_assistant_loading_spinner_padding">4dp</dimen> </resources> diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/android_chrome_autofill_assistant_strings.grd b/chromium/components/autofill_assistant/android/internal/java/strings/android_chrome_autofill_assistant_strings.grd index 63e425f44a5..38aa74f68da 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/android_chrome_autofill_assistant_strings.grd +++ b/chromium/components/autofill_assistant/android/internal/java/strings/android_chrome_autofill_assistant_strings.grd @@ -252,6 +252,9 @@ <message name="IDS_AUTOFILL_ASSISTANT_OVERFLOW_OPTIONS" desc="Content description for the overflow icon displayed in the first prompt, which will open a popup menu with additional options."> Preferences </message> + <message name="IDS_AUTOFILL_ASSISTANT_TTS_BUTTON" desc="Content description for the text to speech button, which is used to start or stop voice instructions."> + Start or stop voice instructions + </message> <message name="IDS_AUTOFILL_ASSISTANT_SPLIT_ONBOARDING_TERMS_TITLE" desc="The title of the terms and conditions popup dialog."> Google Assistant needs your permission </message> diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/android_chrome_autofill_assistant_strings_grd/IDS_AUTOFILL_ASSISTANT_TTS_BUTTON.png.sha1 b/chromium/components/autofill_assistant/android/internal/java/strings/android_chrome_autofill_assistant_strings_grd/IDS_AUTOFILL_ASSISTANT_TTS_BUTTON.png.sha1 new file mode 100644 index 00000000000..26841cb6e61 --- /dev/null +++ b/chromium/components/autofill_assistant/android/internal/java/strings/android_chrome_autofill_assistant_strings_grd/IDS_AUTOFILL_ASSISTANT_TTS_BUTTON.png.sha1 @@ -0,0 +1 @@ +7b4ed7fb2264e7a91721c405b3e0b7bfdfe4fb5b
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_af.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_af.xtb index d51d7cf39e6..67018d072bf 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_af.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_af.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google Assistent in Chrome.</translation> <translation id="8500511870202433545">Huur 'n motor\ndeur net 'n paar keer te tik</translation> <translation id="9084406551994160152">Jou Google Assistent maak dit makliker om fliekkaartjies te koop deur jou besonderhede wat veilig gestoor is, te gebruik</translation> +<translation id="9202590983572380008">Begin of stop steminstruksies</translation> <translation id="945522503751344254">Stuur terugvoer</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_am.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_am.xtb index 75012024372..90e28089969 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_am.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_am.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">በChrome ውስጥ Google ረዳት።</translation> <translation id="8500511870202433545">በጥቂት መታ\nማድረጎች ብቻ መኪና ይከራዩ</translation> <translation id="9084406551994160152">የእርስዎ Google ረዳት ደህንነታቸው በተጠበቀ ሁኔታ የተከማቹ ዝርዝሮችዎን በመጠቀም የፊልም ቲኬቶችን መግዛትን ይበልጥ ቀላል ያደርገዋል</translation> +<translation id="9202590983572380008">የድምጽ መመሪያዎችን ይጀምሩ ወይም ያቁሙ</translation> <translation id="945522503751344254">ግብረመልስ ላክ</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ar.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ar.xtb index ed0f71db7ba..228d4a61785 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ar.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ar.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">"مساعد Google" في Chrome</translation> <translation id="8500511870202433545">يمكنك استئجار سيارة \nببضع نقرات فقط.</translation> <translation id="9084406551994160152">يسهِّل "مساعد Google" عمليات شراء تذاكر الأفلام باستخدام بياناتك المخزَّنة بشكل آمن.</translation> +<translation id="9202590983572380008">تشغيل التعليمات الصوتية أو إيقافها</translation> <translation id="945522503751344254">إرسال تعليقات</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_as.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_as.xtb index 6c8ce4cf9ac..7078fbd1f36 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_as.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_as.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chromeত Google Assistant</translation> <translation id="8500511870202433545">মাত্ৰ কেইবাৰমান টিপি\nএখন গাড়ী ভাড়াত লওক</translation> <translation id="9084406551994160152">সুৰক্ষিতভাৱে ষ্ট’ৰ কৰি ৰখা আপোনাৰ সবিশেষ ব্যৱহাৰ কৰি আপোনাৰ Google Assistantএ চিনেমাৰ টিকেট কটাটো সহজ কৰি তোলে</translation> +<translation id="9202590983572380008">কণ্ঠধ্বনিৰে দিয়া নিৰ্দেশনা আৰম্ভ অথবা বন্ধ কৰক</translation> <translation id="945522503751344254">মতামত পঠিয়াওক</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_az.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_az.xtb index 65a34d1d582..0e31e010e2a 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_az.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_az.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google Assistent Chrome'da.</translation> <translation id="8500511870202433545">Sadəcə bir neçə toxunuşla\navtomobil icarəyə götürün</translation> <translation id="9084406551994160152">Google Assistent təhlükəsiz şəkildə saxlanılan məlumatlarınızdan istifadə edərək kino biletləri almağı asanlaşdırır</translation> +<translation id="9202590983572380008">Səsli təlimatları başladın və ya dayandırın</translation> <translation id="945522503751344254">Geri əlaqə göndərin</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_be.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_be.xtb index 66b46d3814d..11d4faf505a 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_be.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_be.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Памочнік Google у Chrome</translation> <translation id="8500511870202433545">Афармленне пракату аўтамабіля\nў некалькі дотыкаў</translation> <translation id="9084406551994160152">Памочнік Google дазваляе лёгка купляць білеты ў кіно, выкарыстоўваючы вашы звесткі, якія надзейна захоўваюцца</translation> +<translation id="9202590983572380008">Уключыць або выключыць галасавыя інструкцыі</translation> <translation id="945522503751344254">Адправіць водгук</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_bg.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_bg.xtb index c4170b98cd9..2c81002e58d 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_bg.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_bg.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google Асистент в Chrome.</translation> <translation id="8500511870202433545">Наемете автомобил\nсамо с няколко докосвания</translation> <translation id="9084406551994160152">С помощта на Google Асистент можете по-лесно да купувате билети за кино посредством данните си, съхранявани на сигурно място</translation> +<translation id="9202590983572380008">Стартиране или спиране на гласовите инструкции</translation> <translation id="945522503751344254">Изпращане на отзиви</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_bn.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_bn.xtb index cac3e86a4f6..921bb214ba3 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_bn.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_bn.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chrome-এ Google অ্যাসিস্ট্যান্ট।</translation> <translation id="8500511870202433545">মাত্র কয়েক বার ট্যাপ করেই\nগাড়ি ভাড়া করুন</translation> <translation id="9084406551994160152">আপনার নিরাপদে স্টোর করা বিবরণ ব্যবহার করে Google Assistant সিনেমার টিকিট কেনার বিষয়টি সহজ করে তোলে</translation> +<translation id="9202590983572380008">ভয়েস নির্দেশাবলী শুরু বা বন্ধ করুন</translation> <translation id="945522503751344254">মতামত জানান</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_bs.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_bs.xtb index 747f02fcca3..f2243218f96 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_bs.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_bs.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google Asistent u Chromeu.</translation> <translation id="8500511870202433545">Iznajmite automobil\nu svega nekoliko dodira</translation> <translation id="9084406551994160152">Google Asistent vam olakšava da kupite karte pomoću sigurno pohranjenih detalja</translation> +<translation id="9202590983572380008">Pokrenite ili zaustavite glasovne upute</translation> <translation id="945522503751344254">Pošaljite povratne informacije</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ca.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ca.xtb index 462899e27b0..35bda99b5c3 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ca.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ca.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Assistent de Google a Chrome.</translation> <translation id="8500511870202433545">Lloga un cotxe\namb només uns quants tocs</translation> <translation id="9084406551994160152">Amb el teu Assistent de Google, és més fàcil comprar entrades per al cinema utilitzant els detalls emmagatzemats de manera segura</translation> +<translation id="9202590983572380008">Inicia o atura les ordres de veu</translation> <translation id="945522503751344254">Envia suggeriments</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_cs.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_cs.xtb index 6c9bfdae52d..cd6dc824069 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_cs.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_cs.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Asistent Google v Chromu.</translation> <translation id="8500511870202433545">Půjčte si auto\npouhými několika klepnutími</translation> <translation id="9084406551994160152">Asistent Google vám usnadní nákup lístků do kina prostřednictvím bezpečně uložených údajů</translation> +<translation id="9202590983572380008">Spustit nebo zastavit hlasové pokyny</translation> <translation id="945522503751344254">Odeslat zpětnou vazbu</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_cy.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_cy.xtb index bd82dd6893b..69f41e0541b 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_cy.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_cy.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google Assistant yn Chrome.</translation> <translation id="8500511870202433545">Rhentu car\nmewn ychydig o dapiau yn unig</translation> <translation id="9084406551994160152">Mae eich Google Assistant yn ei gwneud yn haws prynu tocynnau ffilmiau gan ddefnyddio eich manylion sydd wedi'u storio'n ddiogel</translation> +<translation id="9202590983572380008">Cychwyn neu stopio cyfarwyddiadau llais</translation> <translation id="945522503751344254">Danfon adborth</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_da.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_da.xtb index bc2c749b2ab..e4f39ab6175 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_da.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_da.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google Assistent i Chrome</translation> <translation id="8500511870202433545">Lej en bil\nmed nogle få tryk</translation> <translation id="9084406551994160152">Google Assistent gør det nemmere for dig at købe filmbilletter ved hjælp af dine oplysninger, som opbevares sikkert</translation> +<translation id="9202590983572380008">Start eller stop stemmekommandoer</translation> <translation id="945522503751344254">Send feedback</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_de.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_de.xtb index e32426165be..0514bda63ba 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_de.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_de.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google Assistant für Chrome.</translation> <translation id="8500511870202433545">Ganz einfach\nein Auto mieten</translation> <translation id="9084406551994160152">Mit Google Assistant kannst du mithilfe deiner gespeicherten Daten ganz einfach Kinokarten kaufen</translation> +<translation id="9202590983572380008">Sprachbefehle starten oder anhalten</translation> <translation id="945522503751344254">Feedback geben</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_el.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_el.xtb index f71db6e1285..a6f05839f32 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_el.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_el.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Βοηθός Google στο Chrome.</translation> <translation id="8500511870202433545">Νοικιάστε ένα αυτοκίνητο\nμε λίγα μόνο πατήματα.</translation> <translation id="9084406551994160152">Ο Βοηθός Google διευκολύνει την αγορά εισιτηρίων κινηματογράφου χρησιμοποιώντας τα στοιχεία σας που αποθηκεύονται με ασφάλεια</translation> +<translation id="9202590983572380008">Έναρξη ή διακοπή φωνητικών οδηγιών</translation> <translation id="945522503751344254">Αποστολή σχολίων</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_en-GB.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_en-GB.xtb index c26aecbed67..6dce64b8bcc 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_en-GB.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_en-GB.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google Assistant in Chrome.</translation> <translation id="8500511870202433545">Hire a car\nin just a few taps</translation> <translation id="9084406551994160152">Your Google Assistant makes it easier to buy cinema tickets using your securely stored details</translation> +<translation id="9202590983572380008">Start or stop voice instructions</translation> <translation id="945522503751344254">Send feedback</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_es-419.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_es-419.xtb index 1527023012a..137aa449b76 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_es-419.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_es-419.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Asistente de Google en Chrome</translation> <translation id="8500511870202433545">Alquila un automóvil\ncon unos pocos pasos</translation> <translation id="9084406551994160152">Asistente de Google usa tus detalles almacenados de forma segura para que comprar entradas para el cine resulte más fácil</translation> +<translation id="9202590983572380008">Iniciar o detener los comandos de voz</translation> <translation id="945522503751344254">Enviar comentarios</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_es.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_es.xtb index 37953aee1ab..bf90600088a 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_es.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_es.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Asistente de Google en Chrome.</translation> <translation id="8500511870202433545">Alquila un coche\ncon solo unos toques</translation> <translation id="9084406551994160152">Tu Asistente de Google facilita la compra de entradas de cine usando tus datos guardados de forma segura</translation> +<translation id="9202590983572380008">Iniciar o detener instrucciones de voz</translation> <translation id="945522503751344254">Enviar comentarios</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_et.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_et.xtb index 8dd9785e96e..a5099f038ce 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_et.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_et.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google'i assistent Chrome'is.</translation> <translation id="8500511870202433545">Rentige auto \nvaid mõne puudutusega</translation> <translation id="9084406551994160152">Google’i assistendi abil on lihtne kinopileteid osta, kasutades teie turvaliselt salvestatud andmeid</translation> +<translation id="9202590983572380008">Hääljuhiste käivitamine või peatamine</translation> <translation id="945522503751344254">Tagasiside saatmine</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_eu.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_eu.xtb index cf0b4ee4739..883805b4b11 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_eu.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_eu.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chrome-ko Google-ren Laguntzailea.</translation> <translation id="8500511870202433545">Alokatu auto bat\nsakatze gutxi batzuekin</translation> <translation id="9084406551994160152">Google-ren Laguntzailea zerbitzuari esker, errazagoa da zinemarako sarrerak erostea segurtasun osoz gordetako xehetasunak erabilita</translation> +<translation id="9202590983572380008">Abiarazi edo gelditu ahozko argibideak</translation> <translation id="945522503751344254">Bidali oharrak</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_fa.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_fa.xtb index bf7b39f4dcc..365a4a70bf7 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_fa.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_fa.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">«دستیار Google» در Chrome.</translation> <translation id="8500511870202433545">فقط با چند ضربه\nخودرو اجاره کنید</translation> <translation id="9084406551994160152">«دستیار Google» خرید بلیت بااستفاده از اطلاعات بهطور ایمن ذخیرهشدهتان را آسانتر میکند</translation> +<translation id="9202590983572380008">شروع یا توقف دستورالعملهای صوتی</translation> <translation id="945522503751344254">ارسال بازخورد</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_fi.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_fi.xtb index ad65c1df165..f0a93c8f77a 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_fi.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_fi.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chromen Google Assistant</translation> <translation id="8500511870202433545">Vuokraa auto\n parilla napautuksella</translation> <translation id="9084406551994160152">Google Assistantin avulla voit ostaa elokuvalippuja helpommin käyttämällä suojatusti tallennettuja tietojasi</translation> +<translation id="9202590983572380008">Aloita tai lopeta ääniohjeet</translation> <translation id="945522503751344254">Lähetä palautetta</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_fil.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_fil.xtb index 2349b2c43a5..f817a021625 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_fil.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_fil.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google Assistant sa Chrome.</translation> <translation id="8500511870202433545">Magrenta ng kotse\nsa ilang pag-tap lang</translation> <translation id="9084406551994160152">Mas pinapadali ng Google Assistant mo ang pagbili ng mga ticket sa pelikula gamit ang iyong mga detalyeng secure na na-store</translation> +<translation id="9202590983572380008">Simulan o ihinto ang mga tagubilin gamit ang boses</translation> <translation id="945522503751344254">Magpadala ng feedback</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_fr-CA.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_fr-CA.xtb index c86f76c0692..51ab7804c1c 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_fr-CA.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_fr-CA.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Assistant Google dans Chrome.</translation> <translation id="8500511870202433545">Louez une voiture \nen quelques touchers seulement</translation> <translation id="9084406551994160152">Votre Assistant Google facilite l'achat de billets de cinéma en utilisant vos renseignements stockés en toute sécurité</translation> +<translation id="9202590983572380008">Démarrer ou arrêter les commandes vocales</translation> <translation id="945522503751344254">Envoyer un commentaire à Google</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_fr.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_fr.xtb index 9d11c66eacf..ebe3a3167b2 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_fr.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_fr.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Assistant Google dans Chrome.</translation> <translation id="8500511870202433545">Louez une voiture\nen quelques gestes seulement</translation> <translation id="9084406551994160152">L'Assistant Google vous permet d'acheter des places de cinéma facilement grâce à vos infos stockées de façon sécurisée</translation> +<translation id="9202590983572380008">Activer ou désactiver les instructions vocales</translation> <translation id="945522503751344254">Envoyer un commentaire</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_gl.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_gl.xtb index f2a26c5e2ba..8682244593a 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_gl.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_gl.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Asistente de Google en Chrome.</translation> <translation id="8500511870202433545">Aluga un coche\ncon só uns toques</translation> <translation id="9084406551994160152">Co teu Asistente de Google, resultarache máis doado comprar entradas de cine usando os teus datos almacenados de xeito seguro</translation> +<translation id="9202590983572380008">Iniciar ou parar instrucións de voz</translation> <translation id="945522503751344254">Enviar comentarios</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_gu.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_gu.xtb index e4b1693d402..20d90c73d1a 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_gu.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_gu.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chromeમાં Google Assistant.</translation> <translation id="8500511870202433545">માત્ર અમુક ટૅપમાં જ\nકાર ભાડે લો</translation> <translation id="9084406551994160152">તમારું Google Assistant તમારી સુરક્ષિત રીતે સ્ટોર કરેલી વિગતો વડે મૂવી ટિકિટ ખરીદવાનું વધુ સરળ બનાવે છે</translation> +<translation id="9202590983572380008">વૉઇસ સૂચનાઓ શરૂ અથવા બંધ કરો</translation> <translation id="945522503751344254">પ્રતિસાદ મોકલો</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_hi.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_hi.xtb index 0d0dc8a3ee6..40eaa444e8d 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_hi.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_hi.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chrome में Google Assistant.</translation> <translation id="8500511870202433545">सिर्फ़ कुछ टैप में कार\n किराये पर लें</translation> <translation id="9084406551994160152">फ़िल्म के टिकट आसानी से बुक करने में, Google Assistant आपकी मदद कर सकती है. इसके लिए, यह सुरक्षित तरीके से सेव किए गए आपके क्रेडेंशियल का इस्तेमाल करती है</translation> +<translation id="9202590983572380008">बोलकर निर्देश देने की सुविधा को चालू या बंद करें</translation> <translation id="945522503751344254">फ़ीडबैक भेजें</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_hr.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_hr.xtb index b5637d63d1f..c7a69de56f8 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_hr.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_hr.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google asistent u Chromeu.</translation> <translation id="8500511870202433545">Unajmite automobil\nu samo nekoliko dodira</translation> <translation id="9084406551994160152">Google asistent olakšava vam kupnju karata za kino pomoću sigurno pohranjenih podataka</translation> +<translation id="9202590983572380008">Pokretanje ili zaustavljanje glasovnih uputa</translation> <translation id="945522503751344254">Pošaljite povratne informacije</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_hu.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_hu.xtb index c104071e420..a2118a3eb2b 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_hu.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_hu.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google Segéd a Chrome-ban.</translation> <translation id="8500511870202433545">Autót bérelhet\ncsupán néhány koppintással</translation> <translation id="9084406551994160152">Google Segédje az Ön biztonságosan tárolt adatait felhasználva könnyítheti meg a mozijegyvásárlást</translation> +<translation id="9202590983572380008">Hangutasítások elindítása vagy leállítása</translation> <translation id="945522503751344254">Visszajelzés küldése</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_hy.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_hy.xtb index 50180bb4b21..d7ef7433d03 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_hy.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_hy.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google Օգնականը Chrome-ում:</translation> <translation id="8500511870202433545">Մեքենա վարձեք՝\nկատարելով մի քանի հպում</translation> <translation id="9084406551994160152">Google Օգնականի միջոցով կարող եք հեշտությամբ կինոյի տոմսեր գնել՝ օգտագործելով ձեր պահված տվյալները</translation> +<translation id="9202590983572380008">Միացնել կամ անջատել ձայնային հուշումները</translation> <translation id="945522503751344254">Կարծիք հայտնել</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_id.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_id.xtb index 663062249d1..e349c88cf76 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_id.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_id.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Asisten Google di Chrome.</translation> <translation id="8500511870202433545">Sewa mobil\ncukup dengan beberapa kali ketuk</translation> <translation id="9084406551994160152">Asisten Google memudahkan pembelian tiket film menggunakan detail Anda yang disimpan secara aman</translation> +<translation id="9202590983572380008">Mulai atau hentikan perintah suara</translation> <translation id="945522503751344254">Kirim masukan</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_is.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_is.xtb index fde6e15d9c4..0911455f020 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_is.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_is.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google hjálpari í Chrome.</translation> <translation id="8500511870202433545">Leigðu bíl\ní örfáum skrefum</translation> <translation id="9084406551994160152">Google hjálpari auðveldar þér að kaupa bíómiða með upplýsingunum þínum sem eru vistaðar á öruggan hátt</translation> +<translation id="9202590983572380008">Hefja eða stöðva raddleiðbeiningar</translation> <translation id="945522503751344254">Senda ábendingu</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_it.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_it.xtb index cb683cf705a..994bfc4a314 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_it.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_it.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Assistente Google in Chrome.</translation> <translation id="8500511870202433545">Noleggia un'auto\ncon pochi tocchi</translation> <translation id="9084406551994160152">Con l'Assistente Google è più facile acquistare biglietti per il cinema usando i tuoi dati memorizzati in modo sicuro</translation> +<translation id="9202590983572380008">Avvia o interrompi istruzioni vocali</translation> <translation id="945522503751344254">Invia feedback</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_iw.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_iw.xtb index 374a95f9c86..6b41e0bbc68 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_iw.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_iw.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google Assistant ב-Chrome.</translation> <translation id="8500511870202433545">שוכרים רכב\nבכמה הקשות בלבד</translation> <translation id="9084406551994160152">בעזרת Google Assistant, קל יותר לקנות כרטיסים לסרט באמצעות פרטים ששמורים באופן מאובטח</translation> +<translation id="9202590983572380008">הפעלה או הפסקה של הפקודות הקוליות</translation> <translation id="945522503751344254"> שליחת משוב</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ja.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ja.xtb index 970c0573c6c..919b9c21147 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ja.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ja.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chrome の Google アシスタント。</translation> <translation id="8500511870202433545">数回のタップで\nレンタカーを予約できます</translation> <translation id="9084406551994160152">Google アシスタントを利用すると、安全に保存された詳細情報を使用して、映画チケットを簡単に購入できます</translation> +<translation id="9202590983572380008">音声コマンドを開始または停止します</translation> <translation id="945522503751344254">フィードバックを送信</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ka.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ka.xtb index 281cdedfae9..30b7a3a634d 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ka.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ka.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google ასისტენტი Chrome-ში.</translation> <translation id="8500511870202433545">დაიქირავეთ მანქანა\nრამდენიმე შეხებით</translation> <translation id="9084406551994160152">Google ასისტენტის დახმარებით უფრო მარტივად იყიდით კინოთეატრების ბილეთებს უსაფრთხოდ შენახული თქვენი მონაცემების მეშვეობით</translation> +<translation id="9202590983572380008">ხმოვანი მითითებების დაწყება ან შეწყვეტა</translation> <translation id="945522503751344254">შეფასების გაგზავნა</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_kk.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_kk.xtb index b2b95ff5915..0a6c1905707 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_kk.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_kk.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chrome браузеріндегі Google Assistant.</translation> <translation id="8500511870202433545">Бірнеше рет түртіп қана,\nкөлікті жалға алыңыз.</translation> <translation id="9084406551994160152">Google Assistant қызметі қауіпсіз сақталған мәліметтеріңізді қолданып, киноға билет сатып алуды жеңілдетеді.</translation> +<translation id="9202590983572380008">Дауыстық нұсқауларды қосу не өшіру</translation> <translation id="945522503751344254">Пікір жіберу</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_km.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_km.xtb index 3298e7a1706..52f018b8a08 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_km.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_km.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google ជំនួយការនៅក្នុង Chrome ។</translation> <translation id="8500511870202433545">ជួលរថយន្ត\nដោយគ្រាន់តែចុចពីរបីដងប៉ុណ្ណោះ</translation> <translation id="9084406551994160152">Google Assistant របស់អ្នកធ្វើឱ្យកាន់តែងាយស្រួលក្នុងការទិញសំបុត្រភាពយន្ត ដោយប្រើព័ត៌មានលម្អិតដែលអ្នកបានរក្សាទុកយ៉ាងមានសុវត្ថិភាព</translation> +<translation id="9202590983572380008">ចាប់ផ្ដើម ឬបញ្ឈប់ការណែនាំជាសំឡេង</translation> <translation id="945522503751344254">ផ្ញើមតិ</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_kn.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_kn.xtb index f3431f639a6..d0e873521ba 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_kn.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_kn.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chrome ನಲ್ಲಿ Google Assistant.</translation> <translation id="8500511870202433545">ಕೆಲವೇ ಟ್ಯಾಪ್ಗಳ ಮೂಲಕ\nಕಾರನ್ನು ಬಾಡಿಗೆಗೆ ಪಡೆಯಿರಿ</translation> <translation id="9084406551994160152">ಸುರಕ್ಷಿತವಾಗಿ ಸಂಗ್ರಹಿಸಿರುವ ನಿಮ್ಮ ವಿವರಗಳನ್ನು ಬಳಸಿಕೊಂಡು ಚಲನಚಿತ್ರ ಟಿಕೆಟ್ಗಳನ್ನು ಖರೀದಿಸುವುದನ್ನು Google Assistant ಸುಲಭಗೊಳಿಸುತ್ತದೆ</translation> +<translation id="9202590983572380008">ಧ್ವನಿ ಸೂಚನೆಗಳನ್ನು ಪ್ರಾರಂಭಿಸಿ ಅಥವಾ ನಿಲ್ಲಿಸಿ</translation> <translation id="945522503751344254">ಪ್ರತಿಕ್ರಿಯೆಯನ್ನು ಕಳುಹಿಸಿ</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ko.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ko.xtb index 2015523a004..8c45869f0c8 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ko.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ko.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chrome의 Google 어시스턴트입니다.</translation> <translation id="8500511870202433545">탭 몇 번으로\n차량을 대여하세요.</translation> <translation id="9084406551994160152">Google 어시스턴트를 사용하면 안전하게 저장된 세부정보를 사용하여 영화 티켓을 더 쉽게 구매하실 수 있습니다</translation> +<translation id="9202590983572380008">음성 안내 시작 또는 중지</translation> <translation id="945522503751344254">의견 보내기</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ky.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ky.xtb index bc178333202..14371b8c35d 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ky.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ky.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chrome'догу Google Жардамчы.</translation> <translation id="8500511870202433545">Бир нече жолу таптап,\nижарага унаа алыңыз</translation> <translation id="9084406551994160152">Google Жардамчыңыз коопсуз сакталган маалыматыңызды колдонуп, киного билеттерди оңой сатып алууга жардам берет</translation> +<translation id="9202590983572380008">Оозеки нускамаларды баштоо же токтотуу</translation> <translation id="945522503751344254">Пикириңизди билдириңиз</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_lo.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_lo.xtb index 07016937006..ba6cf723588 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_lo.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_lo.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">ຜູ້ຊ່ວຍ Google ໃນ Chrome.</translation> <translation id="8500511870202433545">ເຊົ່າລົດ\nໂດຍແຕະສອງສາມບາດເທົ່ານັ້ນ</translation> <translation id="9084406551994160152">ຜູ້ຊ່ວຍ Google ຂອງທ່ານຈະເຮັດໃຫ້ສາມາດຊື້ປີ້ໂດຍໃຊ້ລາຍລະອຽດທີ່ຈັດເກັບໄວ້ຢ່າງປອດໄພຂອງທ່ານໄດ້ງ່າຍຂຶ້ນ</translation> +<translation id="9202590983572380008">ເລີ່ມ ຫຼື ຢຸດຄຳແນະນຳສຽງ</translation> <translation id="945522503751344254">ສົ່ງຄໍາຄິດເຫັນ</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_lt.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_lt.xtb index 8a783cdca8f..954147681b4 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_lt.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_lt.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">„Google Assistant“ sistemoje „Chrome“.</translation> <translation id="8500511870202433545">Išsinuomokite automobilį vos keliais palietimais</translation> <translation id="9084406551994160152">„Google“ padėjėjas gali padėti lengvai įsigyti bilietų į kiną naudodamas jūsų saugiai saugomą išsamią informaciją</translation> +<translation id="9202590983572380008">Pradėkite arba sustabdykite instrukcijas balsu</translation> <translation id="945522503751344254">Siųsti atsiliepimą</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_lv.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_lv.xtb index 0923e37cb35..ddbe5836ead 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_lv.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_lv.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google asistents pārlūkprogrammā Chrome.</translation> <translation id="8500511870202433545">Nonomājiet automašīnu,\nveicot tikai dažus pieskārienus</translation> <translation id="9084406551994160152">Google asistents ļauj jums ērtāk iegādāties biļetes uz filmām, izmantojot jūsu droši saglabāto informāciju.</translation> +<translation id="9202590983572380008">Sākt vai apturēt balss komandas</translation> <translation id="945522503751344254">Sūtīt atsauksmes</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_mk.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_mk.xtb index bb4ffc8d283..9d878b33079 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_mk.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_mk.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">„Помошникот на Google“ во Chrome.</translation> <translation id="8500511870202433545">Изнајмете автомобил\nсо само неколку допири</translation> <translation id="9084406551994160152">Вашиот „Помошник на Google“ ви го олеснува купувањето билети за кино со безбедно зачуваните детали</translation> +<translation id="9202590983572380008">Започнете ги или сопрете ги гласовните упатства</translation> <translation id="945522503751344254">Испратете повратни информации</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ml.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ml.xtb index 809b9e3c85b..68363d7083c 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ml.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ml.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chrome-ലെ Google അസിസ്റ്റൻ്റ്.</translation> <translation id="8500511870202433545">ഏതാനും ടാപ്പുകളിലൂടെ\nകാർ വാടകയ്ക്കെടുക്കൂ</translation> <translation id="9084406551994160152">നിങ്ങൾ സുരക്ഷിതമായി സംഭരിച്ചിരിക്കുന്ന വിശദാംശങ്ങള് ഉപയോഗിച്ച് സിനിമാ ടിക്കറ്റുകൾ വാങ്ങുന്നത് നിങ്ങളുടെ Google Assistant എളുപ്പമാക്കുന്നു</translation> +<translation id="9202590983572380008">ശബ്ദ നിർദ്ദേശങ്ങൾ ആരംഭിക്കുകയോ നിർത്തുകയോ ചെയ്യുക</translation> <translation id="945522503751344254">ഫീഡ്ബാക്ക് അയയ്ക്കുക</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_mn.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_mn.xtb index 00761bdcdd0..7b09cfa53af 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_mn.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_mn.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chrome-н Google Туслах.</translation> <translation id="8500511870202433545">Хэдхэн товшилтоор\nмашин түрээслээрэй</translation> <translation id="9084406551994160152">Таны Google Туслах аюулгүйгээр хадгалсан дэлгэрэнгүй мэдээллээ ашиглан киноны тасалбар худалдан авахад хялбар болгоно</translation> +<translation id="9202590983572380008">Дуут зааварчилгааг эхлүүлэх эсвэл зогсоох</translation> <translation id="945522503751344254">Санал хүсэлт илгээх</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_mr.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_mr.xtb index 2e92cd671b4..3f2dc6fc68c 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_mr.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_mr.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chrome मधील Google असिस्टंट.</translation> <translation id="8500511870202433545">फक्त काही टॅपमध्ये\nकार भाड्याने घ्या</translation> <translation id="9084406551994160152">Google Assistant हे तुमचे सुरक्षितरीत्या स्टोअर केलेले तपशील वापरून चित्रपटाची तिकिटे खरेदी करणे सोपे करते</translation> +<translation id="9202590983572380008">बोलून दिल्या जाणाऱ्या सूचना सुरू करा किंवा थांबवा</translation> <translation id="945522503751344254">अभिप्राय पाठवा</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ms.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ms.xtb index d63ac1ee3a6..62dae70da91 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ms.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ms.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google Assistant dalam Chrome.</translation> <translation id="8500511870202433545">Sewa kereta\ndengan hanya beberapa ketikan</translation> <translation id="9084406551994160152">Google Assistant memudahkan anda untuk membeli tiket wayang menggunakan butiran anda yang disimpan dengan selamat</translation> +<translation id="9202590983572380008">Mulakan atau hentikan arahan suara</translation> <translation id="945522503751344254">Hantar maklum balas</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_my.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_my.xtb index f0cf2f6c7fb..2a2ad30c5fb 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_my.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_my.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chrome ရှိ Google Assistant။</translation> <translation id="8500511870202433545">အကြိမ်အနည်းငယ် တို့ရုံဖြင့်\nကားငှားရမ်းနိုင်သည်</translation> <translation id="9084406551994160152">Google Assistant သည် လုံခြုံစွာသိမ်းထားသည့် သင်၏အသေးစိတ်အချက်အလက်ကိုအသုံးပြုခြင်းဖြင့် ရုပ်ရှင်လက်မှတ်ဝယ်ယူရာတွင် ပိုမိုလွယ်ကူစေသည်</translation> +<translation id="9202590983572380008">အသံဖြင့် ညွှန်ကြားချက်များကို စတင် (သို့) ရပ်တန့်နိုင်သည်</translation> <translation id="945522503751344254">အကြံပြုချက် ပေးပို့မည်</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ne.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ne.xtb index 450fd74117c..84756e5571b 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ne.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ne.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chrome को Google सहायक।</translation> <translation id="8500511870202433545">केही पटक ट्याप गरेकै भरमा\nकार भाडामा लिनुहोस्</translation> <translation id="9084406551994160152">तपाईंको Google सहायकले तपाईंका सुरक्षित तरिकाले भण्डारण गरिएका विवरण प्रयोग गरी चलचित्रका टिकट खरिद गर्ने कार्य अझ सजिलो बनाउँछ</translation> +<translation id="9202590983572380008">बोलेर निर्देशन दिन थाल्नुहोस् वा रोक्नुहोस्</translation> <translation id="945522503751344254">पृष्ठपोषण पठाउनुहोस्</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_nl.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_nl.xtb index cf2eae9f641..9c203d2c396 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_nl.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_nl.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">De Google Assistent in Chrome.</translation> <translation id="8500511870202433545">Met slechts een paar tikken\nhuur je een auto</translation> <translation id="9084406551994160152">Met de Google Assistent kun je makkelijker filmtickets kopen via je beveiligd opgeslagen gegevens</translation> +<translation id="9202590983572380008">Gesproken instructies starten of stoppen</translation> <translation id="945522503751344254">Feedback sturen</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_no.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_no.xtb index 914c938d2b0..d8cb3fc7bb0 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_no.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_no.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google-assistenten i Chrome.</translation> <translation id="8500511870202433545">Lei en bil\nmed bare noen få trykk</translation> <translation id="9084406551994160152">Google-assistenten gjør det lettere å kjøpe kinobilletter med trygt lagrede betalingsopplysninger</translation> +<translation id="9202590983572380008">Start eller stopp taleveiledning</translation> <translation id="945522503751344254">Send tilbakemelding</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_or.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_or.xtb index 2535257df10..e08b58310e3 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_or.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_or.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chromeରେ Google Assistant।</translation> <translation id="8500511870202433545">କେବଳ କିଛି ଟାପରେ\nଏକ କାର ଭଡ଼ାରେ ନିଅନ୍ତୁ</translation> <translation id="9084406551994160152">ଆପଣଙ୍କ Google Assistant ଆପଣଙ୍କର ସୁରକ୍ଷିତ ଭାବେ ଷ୍ଟୋର କରାଯାଇଥିବା ବିବରଣୀ ବ୍ୟବହାର କରି ମୁଭି ଟିକେଟଗୁଡ଼ିକ କିଣିବା ସହଜ କରିଥାଏ</translation> +<translation id="9202590983572380008">ଭଏସ ନିର୍ଦ୍ଦେଶାବଳୀ ଆରମ୍ଭ କିମ୍ବା ବନ୍ଦ କରନ୍ତୁ</translation> <translation id="945522503751344254">ମତାମତ ପଠାନ୍ତୁ</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_pa.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_pa.xtb index 766b6d5cfa4..25b3a335ea5 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_pa.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_pa.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chrome ਵਿੱਚ Google Assistant.</translation> <translation id="8500511870202433545">ਬਸ ਕੁਝ ਟੈਪਾਂ ਵਿੱਚ\nਕਾਰ ਕਿਰਾਏ 'ਤੇ ਲਓ</translation> <translation id="9084406551994160152">ਤੁਹਾਡੀ Google Assistant ਤੁਹਾਡੇ ਸਟੋਰ ਕੀਤੇ ਸੁਰੱਖਿਅਤ ਵੇਰਵਿਆਂ ਨੂੰ ਵਰਤ ਕੇ ਫ਼ਿਲਮ ਟਿਕਟਾਂ ਨੂੰ ਖਰੀਦਣਾ ਵਧੇਰੇ ਆਸਾਨ ਬਣਾ ਦਿੰਦੀ ਹੈ</translation> +<translation id="9202590983572380008">ਅਵਾਜ਼ੀ ਹਿਦਾਇਤਾਂ ਸ਼ੁਰੂ ਜਾਂ ਬੰਦ ਕਰੋ</translation> <translation id="945522503751344254">ਪ੍ਰਤੀਕਰਮ ਭੇਜੋ</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_pl.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_pl.xtb index adec27b6363..1d4b35b9fca 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_pl.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_pl.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Asystent Google w Chrome.</translation> <translation id="8500511870202433545">Wypożyczaj samochody\nkilkoma kliknięciami</translation> <translation id="9084406551994160152">Asystent Google ułatwia kupowanie biletów do kina przy użyciu bezpiecznie przechowywanych danych</translation> +<translation id="9202590983572380008">Włącz lub wyłącz instrukcje głosowe</translation> <translation id="945522503751344254">Prześlij opinię</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_pt-BR.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_pt-BR.xtb index 522c51e47b4..ca73320f6f6 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_pt-BR.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_pt-BR.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google Assistente no Chrome.</translation> <translation id="8500511870202433545">Alugue um carro\ncom apenas alguns toques</translation> <translation id="9084406551994160152">O Google Assistente usa seus dados armazenados em segurança para ajudar a comprar ingressos de cinema.</translation> +<translation id="9202590983572380008">Começar ou parar instruções por voz</translation> <translation id="945522503751344254">Enviar comentários</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_pt-PT.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_pt-PT.xtb index aa28379ddf2..cc9f8cf849c 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_pt-PT.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_pt-PT.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Assistente Google no Chrome.</translation> <translation id="8500511870202433545">Alugue um automóvel\ncom apenas alguns toques.</translation> <translation id="9084406551994160152">O Assistente Google facilita a compra de bilhetes de cinema ao usar os seus detalhes armazenados em segurança</translation> +<translation id="9202590983572380008">Inicie ou pare as instruções por voz</translation> <translation id="945522503751344254">Enviar feedback</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ro.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ro.xtb index 16579f8c277..2229351209a 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ro.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ro.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Asistentul Google în Chrome.</translation> <translation id="8500511870202433545">Închiriază o mașină\ncu doar câteva atingeri</translation> <translation id="9084406551994160152">Asistentul Google te ajută să cumperi bilete la film folosind detaliile stocate în siguranță</translation> +<translation id="9202590983572380008">Pornește sau oprește comenzile vocale</translation> <translation id="945522503751344254">Trimite feedback</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ru.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ru.xtb index 323cb94530b..0f1c3ad2e4b 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ru.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ru.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google Ассистент в Chrome</translation> <translation id="8500511870202433545">Брать автомобиль напрокат\nтеперь ещё удобнее.</translation> <translation id="9084406551994160152">С помощью Google Ассистента очень удобно покупать билеты в кино (для этого используются ваши данные, и они надежно защищены).</translation> +<translation id="9202590983572380008">Включить или отключить голосовые подсказки</translation> <translation id="945522503751344254">Отправить отзыв</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_si.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_si.xtb index 5c62e625196..f55abbf189d 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_si.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_si.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chrome තුළ Google සහකරු.</translation> <translation id="8500511870202433545">තට්ටු කිරීම් කිහිපයකින්\n මෝටර් රථයක් කුලියට ගන්න</translation> <translation id="9084406551994160152">ඔබගේ Google සහායක ඔබගේ ආරක්ෂිතව ගබඩා කර ඇති විස්තර භාවිතයෙන් චිත්රපට ප්රවේශපත් මිලදී ගැනීම පහසු කරයි</translation> +<translation id="9202590983572380008">හඬ උපදෙස් ආරම්භ කරන්න හෝ නවත්වන්න</translation> <translation id="945522503751344254">අදහස් හා යෝජනා යවන්න</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sk.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sk.xtb index be659bbc8d5..2a503102c32 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sk.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sk.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Asistent Google v Chrome</translation> <translation id="8500511870202433545">Požičajte si auto\nniekoľkými klepnutiami</translation> <translation id="9084406551994160152">Asistent Google vám uľahčí kúpu lístkov do kina pomocou vašich bezpečne uložených údajov</translation> +<translation id="9202590983572380008">Spustenie alebo zastavenie hlasových pokynov</translation> <translation id="945522503751344254">Odoslať spätnú väzbu</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sl.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sl.xtb index 76fe23e2b06..771653b68f9 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sl.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sl.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Pomočnik Google v Chromu.</translation> <translation id="8500511870202433545">Najemite avtomobil\ns samo nekaj dotiki</translation> <translation id="9084406551994160152">S Pomočnikom Google je preprostejše kupiti vstopnice za kino z varno shranjenimi podatki.</translation> +<translation id="9202590983572380008">Začetek ali ustavitev glasovnih navodil</translation> <translation id="945522503751344254">Pošiljanje povratnih informacij</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sq.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sq.xtb index deb15b5c5ff..ed92a1de335 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sq.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sq.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">"Asistenti i Google" në Chrome.</translation> <translation id="8500511870202433545">Merr me qira një makinë\nme vetëm pak trokitje</translation> <translation id="9084406551994160152">"Asistenti yt i Google" e bën më të lehtë blerjen e biletave për filma duke përdorur detajet e tua të ruajtura në mënyrë të sigurt</translation> +<translation id="9202590983572380008">Nis ose ndalo udhëzimet zanore</translation> <translation id="945522503751344254">Dërgo komente</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sr-Latn.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sr-Latn.xtb index 7c7ea46e1d3..90a13877d84 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sr-Latn.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sr-Latn.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google pomoćnik u Chrome-u.</translation> <translation id="8500511870202433545">Iznajmite automobil\nu samo nekoliko dodira</translation> <translation id="9084406551994160152">Google pomoćnik vam olakšava kupovinu karata za bioskop preko bezbedno sačuvanih podataka</translation> +<translation id="9202590983572380008">Pokrenite ili zaustavite glasovne komande</translation> <translation id="945522503751344254">Pošalji povratne informacije</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sr.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sr.xtb index 0e2103945b3..cea26d62b8f 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sr.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sr.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google помоћник у Chrome-у.</translation> <translation id="8500511870202433545">Изнајмите аутомобил\nу само неколико додира</translation> <translation id="9084406551994160152">Google помоћник вам олакшава куповину карата за биоскоп преко безбедно сачуваних података</translation> +<translation id="9202590983572380008">Покрените или зауставите гласовне команде</translation> <translation id="945522503751344254">Пошаљи повратне информације</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sv.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sv.xtb index 58b46178ece..0d45e24761c 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sv.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sv.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google-assistenten i Chrome.</translation> <translation id="8500511870202433545">Hyr en bil\nmed några tryck</translation> <translation id="9084406551994160152">Med Google-assistenten blir det enklare att köpa biobiljetter med dina säkert sparade uppgifter</translation> +<translation id="9202590983572380008">Starta eller stoppa röstanvisningar</translation> <translation id="945522503751344254">Skicka feedback</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sw.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sw.xtb index d1a9baf7e05..ca3046f1a66 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sw.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_sw.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Programu ya Mratibu wa Google katika Chrome.</translation> <translation id="8500511870202433545">Kodisha gari\nkwa gusa mara chache tu</translation> <translation id="9084406551994160152">Mratibu wako wa Google hurahisisha kununua tiketi za filamu kwa kutumia maelezo yako yaliyohifadhiwa kwa usalama</translation> +<translation id="9202590983572380008">Anzisha au sitisha maagizo ya sauti</translation> <translation id="945522503751344254">Tuma maoni</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ta.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ta.xtb index d12c5ea6d5c..5e37c75cbe9 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ta.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ta.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chromeமில் Google அசிஸ்டண்ட்</translation> <translation id="8500511870202433545">சில தட்டல்களிலேயே காரை\n வாடகைக்கு எடுக்கலாம்</translation> <translation id="9084406551994160152">பாதுகாப்பாகச் சேமிக்கப்பட்ட உங்கள் விவரங்களைப் பயன்படுத்தி திரைப்பட டிக்கெட்களை வாங்குவதை Google Assistant மேலும் எளிதாக்குகிறது</translation> +<translation id="9202590983572380008">வழிமுறைகளை வாசித்துக் காட்டும் அல்லது வாசிப்பதை நிறுத்தும்</translation> <translation id="945522503751344254">கருத்தை அனுப்பு</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_te.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_te.xtb index 17017b3fff4..f7904625d24 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_te.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_te.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chromeలో Google Assistant.</translation> <translation id="8500511870202433545">కేవలం కొన్ని సార్లు ట్యాప్ చేయడం ద్వారా \nకారు అద్దెకు తీసుకోండి</translation> <translation id="9084406551994160152">మీ Google Assistant మీరు సురక్షితంగా స్టోర్ చేసిన వివరాలను ఉపయోగించి సినిమా టికెట్లను కొనుగోలు చేయడాన్ని సులభతరం చేస్తుంది</translation> +<translation id="9202590983572380008">వాయిస్ సూచనలను ప్రారంభించండి లేదా ఆపివేయండి</translation> <translation id="945522503751344254">ఫీడ్బ్యాక్ పంపండి</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_th.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_th.xtb index 921dc9ad6b4..a2ad368289d 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_th.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_th.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google Assistant ใน Chrome</translation> <translation id="8500511870202433545">เช่ารถ\nโดยแตะเพียงไม่กี่ครั้ง</translation> <translation id="9084406551994160152">Google Assistant ช่วยให้คุณซื้อตั๋วภาพยนตร์ได้ง่ายขึ้นโดยใช้รายละเอียดที่จัดเก็บไว้อย่างปลอดภัยของคุณ</translation> +<translation id="9202590983572380008">เริ่มหรือหยุดคำสั่งเสียง</translation> <translation id="945522503751344254">ส่งความคิดเห็น</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_tr.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_tr.xtb index f2e30cfc707..a8c0fe5ac76 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_tr.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_tr.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chrome'da Google Asistan.</translation> <translation id="8500511870202433545">Yalnızca birkaç\ndokunuşla araç kiralayın</translation> <translation id="9084406551994160152">Google Asistanınız, güvenli bir şekilde depolanan bilgilerinizi kullanarak sinema bileti almanızı kolaylaştırır</translation> +<translation id="9202590983572380008">Sesli talimatları başlatın veya durdurun</translation> <translation id="945522503751344254">Geri bildirim gönder</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_uk.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_uk.xtb index 8ae1721dc69..2924a844d25 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_uk.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_uk.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Google Асистент у Chrome.</translation> <translation id="8500511870202433545">Орендуйте автомобіль\nу кілька дотиків</translation> <translation id="9084406551994160152">Google Асистент полегшує купівлю квитків, використовуючи ваші надійно збережені дані</translation> +<translation id="9202590983572380008">Увімкнути або вимкнути голосові вказівки</translation> <translation id="945522503751344254">Надіслати відгук</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ur.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ur.xtb index d92031d2106..2192aeb106e 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ur.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_ur.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chrome میں Google اسسٹنٹ۔</translation> <translation id="8500511870202433545">صرف چند تھپتھپاہٹوں میں\nکار کرائے پر لیں</translation> <translation id="9084406551994160152">آپ کی Google اسسٹنٹ آپ کی محفوظ طریقے سے اسٹور کردہ تفصیلات کا استعمال کر کے مووی ٹکٹس خریدنا آسان بناتی ہے</translation> +<translation id="9202590983572380008">صوتی ہدایات شروع کریں یا بند کریں</translation> <translation id="945522503751344254">تاثرات بھیجیں</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_uz.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_uz.xtb index 3ce691af4a8..e5bd094446f 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_uz.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_uz.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chromedagi Google Assistent</translation> <translation id="8500511870202433545">Bir nechta harakat bilan\navtomobilni ijaraga oling</translation> <translation id="9084406551994160152">Your Google Assistent xavfsiz saqlangan maʼlumotlaringiz asosida film uchun chiptalarni osongina sotib olishda yordam beradi</translation> +<translation id="9202590983572380008">Ovoz koʻrsatmalarini boshlash yoki toʻxtatish</translation> <translation id="945522503751344254">Fikr-mulohaza</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_vi.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_vi.xtb index 7404bf2a889..f3aa7b88d33 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_vi.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_vi.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Trợ lý Google trong Chrome.</translation> <translation id="8500511870202433545">Thuê ô tô\nchỉ trong vài thao tác nhấn</translation> <translation id="9084406551994160152">Trợ lý Google sẽ giúp bạn mua vé xem phim dễ dàng hơn bằng cách dùng những thông tin được lưu trữ an toàn của bạn</translation> +<translation id="9202590983572380008">Bắt đầu hoặc dừng hướng dẫn bằng giọng nói</translation> <translation id="945522503751344254">Gửi phản hồi</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_zh-CN.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_zh-CN.xtb index fbec634ee21..c7679f929f5 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_zh-CN.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_zh-CN.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chrome 中的 Google 助理。</translation> <translation id="8500511870202433545">点按几下,\n即可租车</translation> <translation id="9084406551994160152">通过 Google 助理,您可以使用安全存储的详细信息购买电影票</translation> +<translation id="9202590983572380008">启用或停用语音指令</translation> <translation id="945522503751344254">发送反馈</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_zh-HK.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_zh-HK.xtb index 62e0ccf7700..3a51287f513 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_zh-HK.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_zh-HK.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chrome 的「Google 助理」。</translation> <translation id="8500511870202433545">要租車?\n輕按幾下即可</translation> <translation id="9084406551994160152">「Google 助理」可使用安全儲存的詳細資料,讓您更輕鬆地購買電影票</translation> +<translation id="9202590983572380008">啟動或停止語音指示</translation> <translation id="945522503751344254">提供意見反映</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_zh-TW.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_zh-TW.xtb index 2b7372966da..709a74db287 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_zh-TW.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_zh-TW.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Chrome 版 Google 助理。</translation> <translation id="8500511870202433545">只要輕觸幾下\n即可租車</translation> <translation id="9084406551994160152">Google 助理可以使用安全儲存的詳細資料,讓你更輕鬆地購買電影票</translation> +<translation id="9202590983572380008">啟動或停止語音指令</translation> <translation id="945522503751344254">提供意見</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_zu.xtb b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_zu.xtb index 4615770fc33..dd2a32b7b1f 100644 --- a/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_zu.xtb +++ b/chromium/components/autofill_assistant/android/internal/java/strings/translations/android_chrome_autofill_assistant_strings_zu.xtb @@ -30,5 +30,6 @@ <translation id="8253702004019660079">Umsizi we-Google ku-Chrome</translation> <translation id="8500511870202433545">Qasha imoto\nngokuthepha okumbalwa nje</translation> <translation id="9084406551994160152">I-Google Assistant ikwenza kube lula ukuthenga amathikithi e-movie usebenzisa imininingwane yakho egcinwe ngokuphephile</translation> +<translation id="9202590983572380008">Qala noma misa imiyalelo yezwi</translation> <translation id="945522503751344254">Thumela impendulo</translation> </translationbundle>
\ No newline at end of file diff --git a/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantAutofillCreditCard.java b/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantAutofillCreditCard.java index 7e9dff7b7bc..8fd99592596 100644 --- a/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantAutofillCreditCard.java +++ b/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantAutofillCreditCard.java @@ -64,13 +64,15 @@ public class AssistantAutofillCreditCard { private final String mNickname; private final GURL mCardArtUrl; private final @VirtualCardEnrollmentState int mVirtualCardEnrollmentState; + private final String mProductDescription; @CalledByNative public AssistantAutofillCreditCard(String guid, String origin, boolean isLocal, boolean isCached, String name, String number, String obfuscatedNumber, String month, String year, String basicCardIssuerNetwork, int issuerIconDrawableId, String billingAddressId, String serverId, long instrumentId, String nickname, - GURL cardArtUrl, @VirtualCardEnrollmentState int virtualCardEnrollmentState) { + GURL cardArtUrl, @VirtualCardEnrollmentState int virtualCardEnrollmentState, + String productDescription) { mGUID = guid; mOrigin = origin; mIsLocal = isLocal; @@ -88,6 +90,7 @@ public class AssistantAutofillCreditCard { mNickname = nickname; mCardArtUrl = cardArtUrl; mVirtualCardEnrollmentState = virtualCardEnrollmentState; + mProductDescription = productDescription; } @CalledByNative @@ -178,4 +181,9 @@ public class AssistantAutofillCreditCard { public @VirtualCardEnrollmentState int getVirtualCardEnrollmentState() { return mVirtualCardEnrollmentState; } + + @CalledByNative + public String getProductDescription() { + return mProductDescription; + } } diff --git a/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantBrowserControls.java b/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantBrowserControls.java index 2066cf44a8c..ca8cb043304 100644 --- a/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantBrowserControls.java +++ b/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantBrowserControls.java @@ -15,9 +15,8 @@ public interface AssistantBrowserControls extends Destroyable { * Observer for different browser control events. */ public interface Observer { - void onControlsOffsetChanged(int topOffset, int topControlsMinHeightOffset, - int bottomOffset, int bottomControlsMinHeightOffset, boolean needsAnimate); - void onBottomControlsHeightChanged(int bottomControlsHeight, int bottomControlsMinHeight); + void onControlsOffsetChanged(); + void onBottomControlsHeightChanged(); } void setObserver(Observer browserControlsObserver); diff --git a/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantContactEditorAccount.java b/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantContactEditorAccount.java deleted file mode 100644 index a80151d5b93..00000000000 --- a/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantContactEditorAccount.java +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2022 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_assistant; - -import android.app.Activity; - -import androidx.annotation.Nullable; - -import org.chromium.base.Callback; -import org.chromium.components.autofill_assistant.AssistantEditor.AssistantContactEditor; -import org.chromium.components.autofill_assistant.AssistantOptionModel.ContactModel; -import org.chromium.components.autofill_assistant.user_data.GmsIntegrator; -import org.chromium.ui.base.WindowAndroid; - -/** - * Editor for contact information in Chrome/WebLayer using a GMS intent. - */ -public class AssistantContactEditorAccount implements AssistantContactEditor { - // Enums defined in resource_id.proto of AccountSettings. - private static final int SCREEN_ID_PERSONAL_INFO_SCREEN = 10003; // All info. - private static final int SCREEN_ID_MISC_CONTACT_EMAIL_SCREEN = 501; - private static final int SCREEN_ID_PRIVACY_PHONE_SCREEN = 204; - - private final WindowAndroid mWindowAndroid; - private final GmsIntegrator mGmsIntegrator; - private final boolean mRequestEmail; - private final boolean mRequestPhone; - - public AssistantContactEditorAccount(Activity activity, WindowAndroid windowAndroid, - String accountEmail, boolean requestEmail, boolean requestPhone) { - mWindowAndroid = windowAndroid; - mGmsIntegrator = new GmsIntegrator(accountEmail, activity); - mRequestEmail = requestEmail; - mRequestPhone = requestPhone; - } - - /** - * Edit the user's personal information. If the email is requested, the editor opens to the - * contact email screen. If phone number is requested, the editor opens to the phone screen. - * It is not allowed to request email and phone at the same time! If neither is requested - - * e.g. we're looking for name only - the editor opens to the main view, where all information - * is available. - * - * @param oldItem The item to be edited, can be null in which case a new item is created. - * @param doneCallback Called after the editor is closed, assuming that the item has been - * successfully edited. The callback will be called with the - * {@code oldItem} which can be null. The list of new items needs to be - * requested. - * @param cancelCallback Only called if the intent failed to be launched. - */ - @Override - public void createOrEditItem(@Nullable ContactModel oldItem, - Callback<ContactModel> doneCallback, Callback<ContactModel> cancelCallback) { - Callback<Boolean> callback = success -> { - if (success) { - doneCallback.onResult(oldItem); - } else { - cancelCallback.onResult(oldItem); - } - }; - - int screenId; - if (mRequestEmail) { - assert !mRequestPhone; - screenId = SCREEN_ID_MISC_CONTACT_EMAIL_SCREEN; - } else if (mRequestPhone) { - assert !mRequestEmail; - screenId = SCREEN_ID_PRIVACY_PHONE_SCREEN; - } else { - screenId = SCREEN_ID_PERSONAL_INFO_SCREEN; - } - mGmsIntegrator.launchAccountIntent(screenId, mWindowAndroid, callback); - } -} diff --git a/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantDependencies.java b/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantDependencies.java index 4800608545c..b763f6ede7a 100644 --- a/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantDependencies.java +++ b/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantDependencies.java @@ -27,11 +27,10 @@ import org.chromium.ui.base.WindowAndroid; @JNINamespace("autofill_assistant") public interface AssistantDependencies extends AssistantStaticDependencies { /** - * Updates dependencies that are tied to the activity. + * Updates dependencies that are tied to the activity. The activity attached to the WebContents + * is used (if any). * @return Whether a new activity could be found. */ - boolean maybeUpdateDependencies(Activity activity); - boolean maybeUpdateDependencies(WebContents webContents); Activity getActivity(); diff --git a/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantStaticDependencies.java b/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantStaticDependencies.java index d969195b91c..7ce77684758 100644 --- a/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantStaticDependencies.java +++ b/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AssistantStaticDependencies.java @@ -66,11 +66,9 @@ public interface AssistantStaticDependencies { LargeIconBridge createIconBridge(); @Nullable - String getSignedInAccountEmailOrNull(); - - @Nullable AssistantProfileImageUtil createProfileImageUtilOrNull( Context context, @DimenRes int imageSizeRedId); + @Nullable AssistantEditorFactory createEditorFactory(); } diff --git a/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AutofillAssistantActionHandler.java b/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AutofillAssistantActionHandler.java index ef896c46a1e..3226d520e6b 100644 --- a/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AutofillAssistantActionHandler.java +++ b/chromium/components/autofill_assistant/android/public/java/src/org/chromium/components/autofill_assistant/AutofillAssistantActionHandler.java @@ -73,4 +73,10 @@ public interface AutofillAssistantActionHandler { * Displays a generic error message to the user. */ void showFatalError(); + + /** + * Check whether the user is supervised. + * @return supervised state + */ + boolean isSupervisedUser(); } diff --git a/chromium/components/autofill_assistant/browser/BUILD.gn b/chromium/components/autofill_assistant/browser/BUILD.gn index c88c318f6b2..3d9ed33f52f 100644 --- a/chromium/components/autofill_assistant/browser/BUILD.gn +++ b/chromium/components/autofill_assistant/browser/BUILD.gn @@ -20,11 +20,18 @@ proto_library("proto") { "service.proto", "view_layout.proto", ] - link_deps = [ "//components/autofill_assistant/content/common/proto:proto" ] + link_deps = [ + "//components/autofill_assistant/browser/public:proto", + "//components/autofill_assistant/content/common/proto:proto", + ] } proto_library("test_proto") { - sources = [ "parse_jspb_test.proto" ] + sources = [ + "external_action_extension_test.proto", + "parse_jspb_test.proto", + ] + link_deps = [ "//components/autofill_assistant/browser/public:proto" ] } static_library("browser") { @@ -56,6 +63,8 @@ static_library("browser") { "actions/execute_js_action.h", "actions/expect_navigation_action.cc", "actions/expect_navigation_action.h", + "actions/external_action.cc", + "actions/external_action.h", "actions/fallback_handler/required_field.cc", "actions/fallback_handler/required_field.h", "actions/fallback_handler/required_fields_fallback_handler.cc", @@ -76,6 +85,8 @@ static_library("browser") { "actions/presave_generated_password_action.h", "actions/prompt_action.cc", "actions/prompt_action.h", + "actions/register_password_reset_request_action.cc", + "actions/register_password_reset_request_action.h", "actions/release_elements_action.cc", "actions/release_elements_action.h", "actions/reset_pending_credentials_action.cc", @@ -151,10 +162,14 @@ static_library("browser") { "client_settings.h", "client_status.cc", "client_status.h", + "common_dependencies.cc", + "common_dependencies.h", "controller.cc", "controller.h", "controller_observer.cc", "controller_observer.h", + "dependencies_util.cc", + "dependencies_util.h", "desktop/starter_delegate_desktop.cc", "desktop/starter_delegate_desktop.h", "details.cc", @@ -191,6 +206,8 @@ static_library("browser") { "info_box.h", "intent_strings.cc", "intent_strings.h", + "js_flow_devtools_wrapper.cc", + "js_flow_devtools_wrapper.h", "js_flow_executor.h", "js_flow_executor_impl.cc", "js_flow_executor_impl.h", @@ -202,6 +219,8 @@ static_library("browser") { "overlay_state.h", "parse_jspb.cc", "parse_jspb.h", + "platform_dependencies.cc", + "platform_dependencies.h", "protocol_utils.cc", "protocol_utils.h", "radio_button_controller.cc", @@ -301,16 +320,23 @@ static_library("browser") { "wait_for_dom_observer.h", "wait_for_dom_operation.cc", "wait_for_dom_operation.h", + "web/base_element_finder.cc", + "web/base_element_finder.h", "web/check_on_top_worker.cc", "web/check_on_top_worker.h", "web/click_or_tap_worker.cc", "web/click_or_tap_worker.h", + "web/css_element_finder.cc", + "web/css_element_finder.h", "web/element.cc", "web/element.h", "web/element_action_util.cc", "web/element_action_util.h", "web/element_finder.cc", "web/element_finder.h", + "web/element_finder_result.cc", + "web/element_finder_result.h", + "web/element_finder_result_type.h", "web/element_position_getter.cc", "web/element_position_getter.h", "web/element_rect_getter.cc", @@ -326,6 +352,8 @@ static_library("browser") { "web/selector_observer.cc", "web/selector_observer.h", "web/selector_observer_script.h", + "web/semantic_element_finder.cc", + "web/semantic_element_finder.h", "web/send_keyboard_input_worker.cc", "web/send_keyboard_input_worker.h", "web/web_controller.cc", @@ -362,6 +390,7 @@ static_library("browser") { "//components/strings:components_strings_grit", "//components/ukm/content:content", "//components/url_matcher", + "//components/variations/service", "//components/version_info", "//content/public/browser", "//google_apis", @@ -377,6 +406,8 @@ static_library("unit_test_support") { sources = [ "actions/mock_action_delegate.cc", "actions/mock_action_delegate.h", + "actions/wait_for_dom_test_base.cc", + "actions/wait_for_dom_test_base.h", "fake_script_executor_delegate.cc", "fake_script_executor_delegate.h", "fake_script_executor_ui_delegate.cc", @@ -389,6 +420,8 @@ static_library("unit_test_support") { "mock_autofill_assistant_tts_controller.h", "mock_client.cc", "mock_client.h", + "mock_common_dependencies.cc", + "mock_common_dependencies.h", "mock_controller_observer.cc", "mock_controller_observer.h", "mock_execution_delegate.cc", @@ -439,6 +472,7 @@ source_set("unit_tests") { "actions/edit_password_action_unittest.cc", "actions/execute_js_action_unittest.cc", "actions/expect_navigation_action_unittest.cc", + "actions/external_action_unittest.cc", "actions/fallback_handler/required_field_unittest.cc", "actions/fallback_handler/required_fields_fallback_handler_unittest.cc", "actions/generate_password_for_form_field_action_unittest.cc", @@ -449,6 +483,7 @@ source_set("unit_tests") { "actions/popup_message_action_unittest.cc", "actions/presave_generated_password_action_unittest.cc", "actions/prompt_action_unittest.cc", + "actions/register_password_reset_request_action_unittest.cc", "actions/release_elements_action_unittest.cc", "actions/reset_pending_credentials_action_unittest.cc", "actions/save_generated_password_action_unittest.cc", @@ -548,9 +583,13 @@ source_set("unit_tests") { "web/element_action_util_unittest.cc", "web/element_store_unittest.cc", "web/send_keyboard_input_worker_unittest.cc", - "website_login_manager_impl_unittest.cc", ] + # TODO(crbug.com/1329148): enable these tests on desktop. + if (is_android) { + sources += [ "website_login_manager_impl_unittest.cc" ] + } + deps = [ ":browser", ":proto", @@ -571,6 +610,7 @@ source_set("unit_tests") { "//components/ukm:test_support", "//components/ukm/content:content", "//components/url_matcher", + "//components/variations/service", "//components/version_info", "//content/test:test_support", "//services/network:test_support", @@ -593,7 +633,10 @@ if (is_android) { "service.proto", "view_layout.proto", ] - deps = [ "//components/autofill_assistant/content/common/proto:proto_java" ] + deps = [ + "//components/autofill_assistant/browser/public:proto_java", + "//components/autofill_assistant/content/common/proto:proto_java", + ] } java_cpp_enum("autofill_assistant_enums_java") { diff --git a/chromium/components/autofill_assistant/browser/DEPS b/chromium/components/autofill_assistant/browser/DEPS index d10f5905f86..3e9015556eb 100644 --- a/chromium/components/autofill_assistant/browser/DEPS +++ b/chromium/components/autofill_assistant/browser/DEPS @@ -39,5 +39,11 @@ specific_include_rules = { "+components/prefs", "+components/safe_browsing/core/common", "+third_party/blink/public/common/features.h", - ] + ], + 'dependencies_util.h': [ + '+components/variations/service/variations_service.h', + ], + 'dependencies_util.cc': [ + '+components/variations/service/variations_service.h', + ], } diff --git a/chromium/components/autofill_assistant/browser/action_value.proto b/chromium/components/autofill_assistant/browser/action_value.proto index 5f4ea46fff1..1e5fcc5a38b 100644 --- a/chromium/components/autofill_assistant/browser/action_value.proto +++ b/chromium/components/autofill_assistant/browser/action_value.proto @@ -18,6 +18,16 @@ message AutofillProfile { // A value expression. message ValueExpression { + // Used to perform regexp-based replacements. + message RegexpReplacement { + // If the text filter matches ... + optional TextFilter text_filter = 1; + // ... replace by this value. + optional string replacement = 2; + // If true, replaces all occurrences, otherwise the first one only. + optional bool global = 3; + } + message Chunk { oneof chunk { // An integer representation to resolve a piece of Autofill information. @@ -35,10 +45,29 @@ message ValueExpression { string memory_key = 4; } - // If the chunk fully matches the key, it will be replaced. When used - // in a regular expression context, the key needs to be quoted. Similarly, - // the replacement will be substituted as is, without being quoted. + // If the chunk fully matches the given case-sensitive key, it will be + // replaced with the specified value. + // When used in a regular expression context, no escaping is applied to + // either the key or value. As a result, both fields must be explicitly + // escaped in the message. + // Examples: + // - key: GB, value: (UK|GB) + // - key: United\ States, value: (United States|USA|U\.S\.A\.?) map<string, string> replacements = 3; + // If any of the replacements match, apply them. While not explicitly + // forbidden it should not be required to use this in combination with + // the key/value replacements. In case they are chained, the key/value + // replacements are applied first. Backslash-escaped digits (\1 to \9) can + // be used to insert text matching the corresponding parenthesized group + // from the pattern. \0 refers to the entire matching text. + // Examples: + // - text_filter: ^0, replacement: "", is_global: false + // Replaces a leading 0 in the chunk + // - text_filter: \s+, replacement: "", is_global: true + // Removes all whitespace in the chunk + // - text_filter: (\w+)\s(\w+), replacement: \2 \1 + // Flips two words + repeated RegexpReplacement regexp_replacements = 5; } repeated Chunk chunk = 1; } diff --git a/chromium/components/autofill_assistant/browser/actions/action.cc b/chromium/components/autofill_assistant/browser/actions/action.cc index 0189c29bbdf..8103f5a3c72 100644 --- a/chromium/components/autofill_assistant/browser/actions/action.cc +++ b/chromium/components/autofill_assistant/browser/actions/action.cc @@ -273,6 +273,15 @@ std::ostream& operator<<(std::ostream& out, case ActionProto::ActionInfoCase::kJsFlow: out << "JsFlow"; break; + case ActionProto::ActionInfoCase::kExternalAction: + out << "ExternalAction"; + break; + case ActionProto::ActionInfoCase::kRegisterPasswordResetRequest: + out << "RegisterPasswordResetRequest"; + break; + case ActionProto::ActionInfoCase::kSetNativeValue: + out << "SetNativeValue"; + break; case ActionProto::ActionInfoCase::ACTION_INFO_NOT_SET: out << "ACTION_INFO_NOT_SET"; break; diff --git a/chromium/components/autofill_assistant/browser/actions/action.h b/chromium/components/autofill_assistant/browser/actions/action.h index b0545ea45ce..452d0009db3 100644 --- a/chromium/components/autofill_assistant/browser/actions/action.h +++ b/chromium/components/autofill_assistant/browser/actions/action.h @@ -102,6 +102,7 @@ class Action { base::WeakPtrFactory<Action> weak_ptr_factory_{this}; private: + friend class CollectUserDataActionTest; friend class JsFlowActionTest; }; diff --git a/chromium/components/autofill_assistant/browser/actions/action_delegate.h b/chromium/components/autofill_assistant/browser/actions/action_delegate.h index b9e5997890b..3db5123227d 100644 --- a/chromium/components/autofill_assistant/browser/actions/action_delegate.h +++ b/chromium/components/autofill_assistant/browser/actions/action_delegate.h @@ -11,6 +11,10 @@ #include "base/callback.h" #include "base/callback_helpers.h" +#include "components/autofill_assistant/browser/js_flow_devtools_wrapper.h" +#include "components/autofill_assistant/browser/public/external_action_delegate.h" +#include "components/autofill_assistant/browser/public/external_script_controller.h" +#include "components/autofill_assistant/browser/service.pb.h" #include "components/autofill_assistant/browser/tts_button_state.h" #include "components/autofill_assistant/browser/viewport_mode.h" @@ -66,11 +70,13 @@ class ShowProgressBarProto_StepProgressBarConfiguration; class ProcessedActionStatusDetailsProto; class GetUserDataResponseProto; class ElementAreaProto; +class ExternalActionProto; enum ConfigureBottomSheetProto_PeekMode : int; enum ConfigureUiStateProto_OverlayBehavior : int; enum DocumentReadyState : int; enum class UserDataFieldChange; +enum class UserDataEventField; // Action delegate called when processing actions. class ActionDelegate { @@ -194,7 +200,7 @@ class ActionDelegate { bool browse_mode_invisible = false) = 0; // Have the UI leave the prompt state and go back to its previous state. - virtual void CleanUpAfterPrompt() = 0; + virtual void CleanUpAfterPrompt(bool consume_touchable_area = true) = 0; // Set the list of allowed domains to be used when we enter a browse state. // This list is used to determine whether a user initiated navigation to a @@ -312,6 +318,10 @@ class ActionDelegate { // Get associated web contents. virtual content::WebContents* GetWebContents() const = 0; + // Get the wrapper that owns the web contents and devtools client for js + // flows. + virtual JsFlowDevtoolsWrapper* GetJsFlowDevtoolsWrapper() const = 0; + // Get the ElementStore. virtual ElementStore* GetElementStore() const = 0; @@ -457,11 +467,35 @@ class ActionDelegate { // gets attached to the action's response if non empty. virtual ProcessedActionStatusDetailsProto& GetLogInfo() = 0; + // Sends a request to retrieve the required user data for this flow. Returns + // the result through the |callback|. Enters the |RUNNING| state while doing + // so. virtual void RequestUserData( + UserDataEventField event_field, const CollectUserDataOptions& options, base::OnceCallback<void(bool, const GetUserDataResponseProto&)> callback) = 0; + // Whether the current flow supports external actions. + virtual bool SupportsExternalActions() = 0; + + // Executes the |external_action|. + virtual void RequestExternalAction( + const ExternalActionProto& external_action, + base::OnceCallback<void(ExternalActionDelegate::DomUpdateCallback)> + start_dom_checks_callback, + base::OnceCallback<void(const external::Result& result)> + end_action_callback) = 0; + + // Returns whether or not this instance of Autofill Assistant must use a + // backend endpoint to query data. + virtual bool MustUseBackendData() const = 0; + + // Maybe sets the previously executed action. JS flow actions are excluded + // because they act as a script executor. + virtual void MaybeSetPreviousAction( + const ProcessedActionProto& processed_action) = 0; + virtual base::WeakPtr<ActionDelegate> GetWeakPtr() const = 0; protected: diff --git a/chromium/components/autofill_assistant/browser/actions/action_delegate_util.cc b/chromium/components/autofill_assistant/browser/actions/action_delegate_util.cc index e81fbad233d..78bda865794 100644 --- a/chromium/components/autofill_assistant/browser/actions/action_delegate_util.cc +++ b/chromium/components/autofill_assistant/browser/actions/action_delegate_util.cc @@ -12,7 +12,7 @@ #include "components/autofill_assistant/browser/service.pb.h" #include "components/autofill_assistant/browser/string_conversions_util.h" #include "components/autofill_assistant/browser/user_data_util.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/element_store.h" #include "components/autofill_assistant/browser/web/web_controller.h" #include "ui/events/keycodes/dom/dom_key.h" diff --git a/chromium/components/autofill_assistant/browser/actions/action_delegate_util.h b/chromium/components/autofill_assistant/browser/actions/action_delegate_util.h index 11cfb7e2048..4f02bafe801 100644 --- a/chromium/components/autofill_assistant/browser/actions/action_delegate_util.h +++ b/chromium/components/autofill_assistant/browser/actions/action_delegate_util.h @@ -11,9 +11,10 @@ #include "components/autofill_assistant/browser/selector.h" #include "components/autofill_assistant/browser/service.pb.h" #include "components/autofill_assistant/browser/web/element_action_util.h" -#include "components/autofill_assistant/browser/web/element_finder.h" namespace autofill_assistant { +class ElementFinderResult; + namespace action_delegate_util { // Finds the element given by the selector. If the resolution fails, it diff --git a/chromium/components/autofill_assistant/browser/actions/action_delegate_util_unittest.cc b/chromium/components/autofill_assistant/browser/actions/action_delegate_util_unittest.cc index 533d06988f1..d7f9b58c75c 100644 --- a/chromium/components/autofill_assistant/browser/actions/action_delegate_util_unittest.cc +++ b/chromium/components/autofill_assistant/browser/actions/action_delegate_util_unittest.cc @@ -218,7 +218,7 @@ TEST_F(ActionDelegateUtilTest, PerformWithPasswordManagerValue) { auto element = std::make_unique<ElementFinderResult>(); content::WebContentsTester::For(web_contents_.get()) ->NavigateAndCommit(GURL("https://www.example.com")); - element->SetRenderFrameHost(web_contents_->GetMainFrame()); + element->SetRenderFrameHost(web_contents_->GetPrimaryMainFrame()); user_data_.selected_login_ = absl::make_optional<WebsiteLoginManager::Login>( GURL("https://www.example.com"), "username"); @@ -241,7 +241,7 @@ TEST_F(ActionDelegateUtilTest, PerformWithPasswordManagerValue) { TEST_F(ActionDelegateUtilTest, PerformWithFailingPasswordManagerValue) { auto element = std::make_unique<ElementFinderResult>(); - element->SetRenderFrameHost(web_contents_->GetMainFrame()); + element->SetRenderFrameHost(web_contents_->GetPrimaryMainFrame()); user_data_.selected_login_ = absl::make_optional<WebsiteLoginManager::Login>( GURL("https://www.example.com"), "username"); diff --git a/chromium/components/autofill_assistant/browser/actions/action_test_utils.cc b/chromium/components/autofill_assistant/browser/actions/action_test_utils.cc index cb6104214ac..0ee8694db9c 100644 --- a/chromium/components/autofill_assistant/browser/actions/action_test_utils.cc +++ b/chromium/components/autofill_assistant/browser/actions/action_test_utils.cc @@ -7,7 +7,7 @@ #include "components/autofill_assistant/browser/actions/mock_action_delegate.h" #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/selector.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/mock_web_controller.h" #include "testing/gmock/include/gmock/gmock.h" diff --git a/chromium/components/autofill_assistant/browser/actions/action_test_utils.h b/chromium/components/autofill_assistant/browser/actions/action_test_utils.h index 8e596decef6..8b3e991c8fd 100644 --- a/chromium/components/autofill_assistant/browser/actions/action_test_utils.h +++ b/chromium/components/autofill_assistant/browser/actions/action_test_utils.h @@ -9,7 +9,7 @@ #include "components/autofill_assistant/browser/action_value.pb.h" #include "components/autofill_assistant/browser/actions/mock_action_delegate.h" #include "components/autofill_assistant/browser/selector.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/mock_web_controller.h" #include "testing/gmock/include/gmock/gmock.h" diff --git a/chromium/components/autofill_assistant/browser/actions/check_element_tag_action.h b/chromium/components/autofill_assistant/browser/actions/check_element_tag_action.h index d2bcabcd316..4635396b154 100644 --- a/chromium/components/autofill_assistant/browser/actions/check_element_tag_action.h +++ b/chromium/components/autofill_assistant/browser/actions/check_element_tag_action.h @@ -11,7 +11,7 @@ #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/dom_action.pb.h" #include "components/autofill_assistant/browser/service.pb.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" namespace autofill_assistant { diff --git a/chromium/components/autofill_assistant/browser/actions/check_element_tag_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/check_element_tag_action_unittest.cc index 22f8bd54593..a32a113f446 100644 --- a/chromium/components/autofill_assistant/browser/actions/check_element_tag_action_unittest.cc +++ b/chromium/components/autofill_assistant/browser/actions/check_element_tag_action_unittest.cc @@ -11,7 +11,7 @@ #include "components/autofill_assistant/browser/actions/mock_action_delegate.h" #include "components/autofill_assistant/browser/dom_action.pb.h" #include "components/autofill_assistant/browser/service.pb.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/element_store.h" #include "components/autofill_assistant/browser/web/mock_web_controller.h" #include "testing/gmock/include/gmock/gmock.h" diff --git a/chromium/components/autofill_assistant/browser/actions/check_option_element_action.h b/chromium/components/autofill_assistant/browser/actions/check_option_element_action.h index 6db01ca3fb2..42ef5498b62 100644 --- a/chromium/components/autofill_assistant/browser/actions/check_option_element_action.h +++ b/chromium/components/autofill_assistant/browser/actions/check_option_element_action.h @@ -11,7 +11,7 @@ #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/dom_action.pb.h" #include "components/autofill_assistant/browser/service.pb.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" namespace autofill_assistant { diff --git a/chromium/components/autofill_assistant/browser/actions/check_option_element_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/check_option_element_action_unittest.cc index f82b321eccb..4214ba45fcb 100644 --- a/chromium/components/autofill_assistant/browser/actions/check_option_element_action_unittest.cc +++ b/chromium/components/autofill_assistant/browser/actions/check_option_element_action_unittest.cc @@ -11,7 +11,7 @@ #include "components/autofill_assistant/browser/actions/mock_action_delegate.h" #include "components/autofill_assistant/browser/dom_action.pb.h" #include "components/autofill_assistant/browser/service.pb.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/element_store.h" #include "components/autofill_assistant/browser/web/mock_web_controller.h" #include "testing/gmock/include/gmock/gmock.h" diff --git a/chromium/components/autofill_assistant/browser/actions/collect_user_data_action.cc b/chromium/components/autofill_assistant/browser/actions/collect_user_data_action.cc index 5edb25d5659..5177a342664 100644 --- a/chromium/components/autofill_assistant/browser/actions/collect_user_data_action.cc +++ b/chromium/components/autofill_assistant/browser/actions/collect_user_data_action.cc @@ -31,7 +31,6 @@ #include "components/autofill_assistant/browser/user_data_util.h" #include "components/autofill_assistant/browser/website_login_manager_impl.h" #include "components/strings/grit/components_strings.h" -#include "components/ukm/content/source_url_recorder.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/web_contents.h" #include "third_party/blink/public/mojom/payments/payment_request.mojom.h" @@ -608,38 +607,81 @@ void CollectUserDataAction::OnShowToUser(UserData* user_data, void CollectUserDataAction::UpdateUserData(UserData* user_data) { if (proto_.collect_user_data().has_data_source()) { delegate_->RequestUserData( - *collect_user_data_options_, + UserDataEventField::NONE, *collect_user_data_options_, base::BindOnce(&CollectUserDataAction::OnRequestUserData, - weak_ptr_factory_.GetWeakPtr(), user_data)); + weak_ptr_factory_.GetWeakPtr(), + /* is_initial_request= */ true, user_data)); return; } + UseChromeData(user_data); +} + +void CollectUserDataAction::UseChromeData(UserData* user_data) { DCHECK(delegate_->GetPersonalDataManager()); delegate_->GetPersonalDataManager()->AddObserver(this); UpdatePersonalDataManagerProfiles(user_data); UpdatePersonalDataManagerCards(user_data); - UpdateMetrics(user_data); + UpdateMetrics(user_data, Metrics::UserDataSource::CHROME_AUTOFILL); UpdateUi(); action_stopwatch_.StartWaitTime(); } void CollectUserDataAction::OnRequestUserData( + bool is_initial_request, UserData* user_data, bool success, const GetUserDataResponseProto& response) { if (!success) { + if (is_initial_request && !delegate_->MustUseBackendData() && + proto_.collect_user_data().data_source().allow_fallback()) { + FallbackToChromeData(user_data); + return; + } + EndAction(ClientStatus(USER_DATA_REQUEST_FAILED), Metrics::CollectUserDataResult::FAILURE); return; } UpdateUserDataFromProto(response, user_data); - UpdateMetrics(user_data); + UpdateMetrics(user_data, Metrics::UserDataSource::BACKEND); UpdateUi(); action_stopwatch_.StartWaitTime(); } +void CollectUserDataAction::FallbackToChromeData(UserData* user_data) { + if (collect_user_data_options_->request_phone_number_separately) { + collect_user_data_options_->request_payer_phone = true; + collect_user_data_options_->request_phone_number_separately = false; + collect_user_data_options_->phone_number_section_title = std::string(); + + for (const auto& required_data_piece : + collect_user_data_options_->required_phone_number_data_pieces) { + collect_user_data_options_->required_contact_data_pieces.emplace_back( + required_data_piece); + } + collect_user_data_options_->required_phone_number_data_pieces.clear(); + + collect_user_data_options_->contact_summary_fields.emplace_back( + AutofillContactField::PHONE_HOME_WHOLE_NUMBER); + collect_user_data_options_->contact_summary_max_lines++; + + collect_user_data_options_->contact_full_fields.emplace_back( + AutofillContactField::PHONE_HOME_WHOLE_NUMBER); + collect_user_data_options_->contact_full_max_lines++; + } + + collect_user_data_options_->data_origin_notice.reset(); + + collect_user_data_options_->should_store_data_changes = + !delegate_->GetWebContents()->GetBrowserContext()->IsOffTheRecord(); + collect_user_data_options_->use_alternative_edit_dialogs = false; + + UseChromeData(user_data); +} + void CollectUserDataAction::UpdateUi() { const auto& collect_user_data = proto_.collect_user_data(); if (collect_user_data.has_prompt()) { @@ -650,16 +692,16 @@ void CollectUserDataAction::UpdateUi() { delegate_->CollectUserData(collect_user_data_options_.get()); } -void CollectUserDataAction::UpdateMetrics(UserData* user_data) { +void CollectUserDataAction::UpdateMetrics( + UserData* user_data, + Metrics::UserDataSource user_data_source) { DCHECK(user_data); if (!shown_to_user_) { shown_to_user_ = true; - metrics_data_.source_id = - ukm::GetSourceIdForWebContentsDocument(delegate_->GetWebContents()); - metrics_data_.user_data_source = - ShouldUseBackendData(proto_.collect_user_data()) - ? Metrics::UserDataSource::BACKEND - : Metrics::UserDataSource::CHROME_AUTOFILL; + metrics_data_.source_id = delegate_->GetWebContents() + ->GetPrimaryMainFrame() + ->GetPageUkmSourceId(); + metrics_data_.user_data_source = user_data_source; FillInitialDataStateForMetrics(user_data->available_contacts_, user_data->available_addresses_, user_data->available_payment_instruments_); @@ -800,7 +842,8 @@ void CollectUserDataAction::OnTermsAndConditionsLinkClicked( Metrics::CollectUserDataResult::TERMS_AND_CONDITIONS_LINK_CLICKED); } -void CollectUserDataAction::ReloadUserData(UserData* user_data) { +void CollectUserDataAction::ReloadUserData(UserDataEventField event_field, + UserData* user_data) { if (HasActionEnded()) { return; } @@ -810,28 +853,32 @@ void CollectUserDataAction::ReloadUserData(UserData* user_data) { collect_user_data_options_->reload_data_callback = base::BindOnce( &CollectUserDataAction::ReloadUserData, weak_ptr_factory_.GetWeakPtr()); delegate_->RequestUserData( - *collect_user_data_options_, + event_field, *collect_user_data_options_, base::BindOnce(&CollectUserDataAction::OnRequestUserData, - weak_ptr_factory_.GetWeakPtr(), user_data)); + weak_ptr_factory_.GetWeakPtr(), + /* is_initial_request= */ false, user_data)); } void CollectUserDataAction::OnSelectionStateChanged( UserDataEventField field, UserDataEventType event_type) { switch (field) { - case CONTACT_EVENT: + case UserDataEventField::CONTACT_EVENT: metrics_data_.contact_selection_state = user_data::GetNewSelectionState( metrics_data_.contact_selection_state, event_type); break; - case CREDIT_CARD_EVENT: + case UserDataEventField::CREDIT_CARD_EVENT: metrics_data_.credit_card_selection_state = user_data::GetNewSelectionState( metrics_data_.credit_card_selection_state, event_type); break; - case SHIPPING_EVENT: + case UserDataEventField::SHIPPING_EVENT: metrics_data_.shipping_selection_state = user_data::GetNewSelectionState( metrics_data_.shipping_selection_state, event_type); break; + case UserDataEventField::PHONE_NUMBER_EVENT: + case UserDataEventField::NONE: + break; } } @@ -988,13 +1035,16 @@ bool CollectUserDataAction::CreateOptionsFromProto() { collect_user_data.required_shipping_address_data_piece().end()); } + bool should_use_backend_data = ShouldUseBackendData(collect_user_data); + if (delegate_->MustUseBackendData() && !should_use_backend_data) { + VLOG(1) << "This run must use backend data but does not."; + return false; + } collect_user_data_options_->should_store_data_changes = !delegate_->GetWebContents()->GetBrowserContext()->IsOffTheRecord() && - !ShouldUseBackendData(collect_user_data); - collect_user_data_options_->can_edit_contacts = - !ShouldUseBackendData(collect_user_data); - collect_user_data_options_->use_gms_core_edit_dialogs = - ShouldUseBackendData(collect_user_data); + !should_use_backend_data; + collect_user_data_options_->use_alternative_edit_dialogs = + should_use_backend_data; collect_user_data_options_->request_login_choice = collect_user_data.has_login_details(); @@ -1137,6 +1187,11 @@ bool CollectUserDataAction::CreateOptionsFromProto() { delegate_->GetEmailAddressForAccessTokenAccount(); if (collect_user_data.has_data_origin_notice()) { + if (!should_use_backend_data) { + VLOG(1) << "Data origin notice should only be shown for backend provided " + "data."; + return false; + } const auto& notice = collect_user_data.data_origin_notice(); if (notice.link_text().empty() || notice.dialog_title().empty() || notice.dialog_text().empty() || notice.dialog_button_text().empty()) { @@ -1410,6 +1465,12 @@ void CollectUserDataAction::UpdateUserDataFromProto( if (RequiresContact(*collect_user_data_options_)) { user_data->available_contacts_.clear(); + for (const auto& transient_contact : user_data->transient_contacts_) { + auto contact = std::make_unique<Contact>( + user_data::MakeUniqueFromProfile(*transient_contact->profile)); + contact->identifier = transient_contact->identifier; + user_data->available_contacts_.emplace_back(std::move(contact)); + } for (const auto& profile_data : proto_data.available_contacts()) { auto profile = std::make_unique<autofill::AutofillProfile>(); AddProtoDataToAutofillDataModel(profile_data.values(), @@ -1424,13 +1485,15 @@ void CollectUserDataAction::UpdateUserDataFromProto( if (profile_data.has_identifier()) { contact->identifier = profile_data.identifier(); } + contact->can_edit = false; user_data->available_contacts_.emplace_back(std::move(contact)); } if (proto_data.has_selected_contact_identifier()) { const auto& it = base::ranges::find_if( user_data->available_contacts_, [&](const auto& contact) { - return proto_data.selected_contact_identifier() == - contact->identifier.value_or(std::string()); + return contact->identifier && + proto_data.selected_contact_identifier() == + contact->identifier; }); if (it == user_data->available_contacts_.end()) { NOTREACHED(); @@ -1450,6 +1513,13 @@ void CollectUserDataAction::UpdateUserDataFromProto( if (RequiresPhoneNumberSeparately(*collect_user_data_options_)) { user_data->available_phone_numbers_.clear(); + for (const auto& transient_phone_number : + user_data->transient_phone_numbers_) { + auto phone_number = std::make_unique<PhoneNumber>( + user_data::MakeUniqueFromProfile(*transient_phone_number->profile)); + phone_number->identifier = transient_phone_number->identifier; + user_data->available_phone_numbers_.emplace_back(std::move(phone_number)); + } for (const auto& phone_number_data : proto_data.available_phone_numbers()) { auto profile = std::make_unique<autofill::AutofillProfile>(); AddAutofillEntryToDataModel( @@ -1459,6 +1529,7 @@ void CollectUserDataAction::UpdateUserDataFromProto( if (phone_number_data.has_identifier()) { phone_number->identifier = phone_number_data.identifier(); } + phone_number->can_edit = false; user_data->available_phone_numbers_.emplace_back(std::move(phone_number)); } if (proto_data.has_selected_phone_number_identifier()) { diff --git a/chromium/components/autofill_assistant/browser/actions/collect_user_data_action.h b/chromium/components/autofill_assistant/browser/actions/collect_user_data_action.h index 5e8f61c1d68..789958e9b8b 100644 --- a/chromium/components/autofill_assistant/browser/actions/collect_user_data_action.h +++ b/chromium/components/autofill_assistant/browser/actions/collect_user_data_action.h @@ -77,7 +77,7 @@ class CollectUserDataAction : public Action, const UserModel* user_model); bool IsValidUserFormSection( const autofill_assistant::UserFormSectionProto& proto); - void ReloadUserData(UserData* user_data); + void ReloadUserData(UserDataEventField event_field, UserData* user_data); // Only used for logging purposes. void OnSelectionStateChanged(UserDataEventField field, @@ -88,10 +88,14 @@ class CollectUserDataAction : public Action, void ShowToUser(); void OnShowToUser(UserData* user_data, UserDataFieldChange* field_change); void UpdateUserData(UserData* user_data); - void OnRequestUserData(UserData* user_data, + void UseChromeData(UserData* user_data); + void OnRequestUserData(bool is_initial_request, + UserData* user_data, bool success, const GetUserDataResponseProto& response); - void UpdateMetrics(UserData* user_data); + void FallbackToChromeData(UserData* user_data); + void UpdateMetrics(UserData* user_data, + Metrics::UserDataSource user_data_source); void UpdateUi(); // Creates a new instance of |CollectUserDataOptions| from |proto_|. diff --git a/chromium/components/autofill_assistant/browser/actions/collect_user_data_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/collect_user_data_action_unittest.cc index 82b7cc92b95..7febc8cf02c 100644 --- a/chromium/components/autofill_assistant/browser/actions/collect_user_data_action_unittest.cc +++ b/chromium/components/autofill_assistant/browser/actions/collect_user_data_action_unittest.cc @@ -143,6 +143,8 @@ using ::testing::SizeIs; using ::testing::StrEq; using ::testing::UnorderedElementsAre; +} // namespace + class CollectUserDataActionTest : public testing::Test { public: void SetUp() override { @@ -151,7 +153,7 @@ class CollectUserDataActionTest : public testing::Test { content::WebContentsTester::For(web_contents_.get()) ->SetLastCommittedURL(GURL(kFakeUrl)); ukm::InitializeSourceUrlRecorderForWebContents(web_contents_.get()); - source_id_ = ukm::GetSourceIdForWebContentsDocument(web_contents_.get()); + source_id_ = web_contents_->GetPrimaryMainFrame()->GetPageUkmSourceId(); ON_CALL(mock_action_delegate_, GetPersonalDataManager) .WillByDefault(Return(&mock_personal_data_manager_)); @@ -184,6 +186,8 @@ class CollectUserDataActionTest : public testing::Test { .WillByDefault(Return(web_contents_.get())); ON_CALL(mock_action_delegate_, GetUkmRecorder()) .WillByDefault(Return(&ukm_recorder_)); + ON_CALL(mock_action_delegate_, MustUseBackendData()) + .WillByDefault(Return(false)); } void ExpectSelectedProfileMatches(const std::string& profile_name, @@ -212,6 +216,10 @@ class CollectUserDataActionTest : public testing::Test { Pointee(MatchesCard(*card))); } + void AddWaitTime(CollectUserDataAction* action, base::TimeDelta delta) { + action->action_stopwatch_.TransferToWaitTime(delta); + } + protected: content::BrowserTaskEnvironment task_environment_; content::RenderViewHostTestEnabler rvh_test_enabler_; @@ -2567,16 +2575,86 @@ TEST_F(CollectUserDataActionTest, ConfirmButtonFallbackText) { action.ProcessAction(callback_.Get()); } +TEST_F(CollectUserDataActionTest, FailsForWebLayerRunsWithoutBackendData) { + ON_CALL(mock_action_delegate_, MustUseBackendData) + .WillByDefault(Return(true)); + EXPECT_CALL(mock_action_delegate_, CollectUserData).Times(0); + EXPECT_CALL(mock_action_delegate_, RequestUserData).Times(0); + + ActionProto action_proto; + auto* collect_user_data = action_proto.mutable_collect_user_data(); + collect_user_data->set_request_terms_and_conditions(false); + collect_user_data->set_request_payment_method(true); + collect_user_data->set_billing_address_name("billing"); + + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, INVALID_ACTION)))); + + CollectUserDataAction action(&mock_action_delegate_, action_proto); + action.ProcessAction(callback_.Get()); +} + +TEST_F(CollectUserDataActionTest, FailsForDataOriginNoticeWithoutBackendData) { + EXPECT_CALL(mock_action_delegate_, CollectUserData).Times(0); + EXPECT_CALL(mock_action_delegate_, RequestUserData).Times(0); + + ActionProto action_proto; + auto* collect_user_data = action_proto.mutable_collect_user_data(); + collect_user_data->set_request_terms_and_conditions(false); + collect_user_data->set_request_payment_method(true); + collect_user_data->set_billing_address_name("billing"); + collect_user_data->mutable_data_origin_notice()->set_link_text("Link"); + collect_user_data->mutable_data_origin_notice()->set_dialog_title("Title"); + collect_user_data->mutable_data_origin_notice()->set_dialog_text("Text"); + collect_user_data->mutable_data_origin_notice()->set_dialog_button_text( + "Button"); + + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, INVALID_ACTION)))); + + CollectUserDataAction action(&mock_action_delegate_, action_proto); + action.ProcessAction(callback_.Get()); +} + +TEST_F(CollectUserDataActionTest, SucceedsWithDataOriginNoticeAndBackendData) { + EXPECT_CALL(mock_action_delegate_, RequestUserData) + .WillOnce(RunOnceCallback<2>(true, GetUserDataResponseProto())); + ON_CALL(mock_action_delegate_, CollectUserData) + .WillByDefault([&](CollectUserDataOptions* collect_user_data_options) { + EXPECT_TRUE(collect_user_data_options->data_origin_notice); + // Do not finish the action. + }); + + ActionProto action_proto; + auto* collect_user_data = action_proto.mutable_collect_user_data(); + collect_user_data->set_request_terms_and_conditions(false); + collect_user_data->set_request_payment_method(true); + collect_user_data->set_billing_address_name("billing"); + collect_user_data->mutable_data_origin_notice()->set_link_text("Link"); + collect_user_data->mutable_data_origin_notice()->set_dialog_title("Title"); + collect_user_data->mutable_data_origin_notice()->set_dialog_text("Text"); + collect_user_data->mutable_data_origin_notice()->set_dialog_button_text( + "Button"); + collect_user_data->mutable_data_source(); + + CollectUserDataAction action(&mock_action_delegate_, action_proto); + action.ProcessAction(callback_.Get()); +} + TEST_F(CollectUserDataActionTest, ContactDataFromProto) { - ON_CALL(mock_action_delegate_, GetPersonalDataManager()) + ON_CALL(mock_action_delegate_, GetPersonalDataManager) .WillByDefault(Return(nullptr)); - ON_CALL(mock_action_delegate_, CollectUserData(_)) + ON_CALL(mock_action_delegate_, MustUseBackendData) + .WillByDefault(Return(true)); + ON_CALL(mock_action_delegate_, CollectUserData) .WillByDefault([&](CollectUserDataOptions* collect_user_data_options) { EXPECT_FALSE(collect_user_data_options->should_store_data_changes); - EXPECT_FALSE(collect_user_data_options->can_edit_contacts); ASSERT_EQ(user_data_.available_contacts_.size(), 1u); EXPECT_THAT(user_data_.available_contacts_[0]->profile->guid(), Not(IsEmpty())); + EXPECT_FALSE(user_data_.available_contacts_[0]->can_edit); auto mappings = field_formatter::CreateAutofillMappings( *user_data_.available_contacts_[0]->profile, "en-US"); EXPECT_THAT(mappings, @@ -2598,7 +2676,7 @@ TEST_F(CollectUserDataActionTest, ContactDataFromProto) { auto* incomplete = user_data_response.add_available_contacts(); (*incomplete->mutable_values())[7] = MakeAutofillEntry("Jane Doe"); EXPECT_CALL(mock_action_delegate_, RequestUserData) - .WillOnce(RunOnceCallback<1>(true, user_data_response)); + .WillOnce(RunOnceCallback<2>(true, user_data_response)); ActionProto action_proto; auto* collect_user_data = action_proto.mutable_collect_user_data(); @@ -2623,16 +2701,17 @@ TEST_F(CollectUserDataActionTest, ContactDataFromProto) { } TEST_F(CollectUserDataActionTest, PhoneNumberFromProto) { - ON_CALL(mock_action_delegate_, GetPersonalDataManager()) + ON_CALL(mock_action_delegate_, GetPersonalDataManager) .WillByDefault(Return(nullptr)); - ON_CALL(mock_action_delegate_, CollectUserData(_)) + ON_CALL(mock_action_delegate_, MustUseBackendData) + .WillByDefault(Return(true)); + ON_CALL(mock_action_delegate_, CollectUserData) .WillByDefault([&](CollectUserDataOptions* collect_user_data_options) { EXPECT_FALSE(collect_user_data_options->should_store_data_changes); - EXPECT_FALSE(collect_user_data_options->can_edit_contacts); - ASSERT_EQ(user_data_.available_contacts_.size(), 1u); EXPECT_THAT(user_data_.available_contacts_[0]->profile->guid(), Not(IsEmpty())); + EXPECT_FALSE(user_data_.available_contacts_[0]->can_edit); auto contact_mappings = field_formatter::CreateAutofillMappings( *user_data_.available_contacts_[0]->profile, "en-US"); // Initially the contact contains the backend data. @@ -2668,7 +2747,7 @@ TEST_F(CollectUserDataActionTest, PhoneNumberFromProto) { *user_data_response.add_available_phone_numbers()->mutable_value() = MakeAutofillEntry("+1 187-654-3210"); EXPECT_CALL(mock_action_delegate_, RequestUserData) - .WillOnce(RunOnceCallback<1>(true, user_data_response)); + .WillOnce(RunOnceCallback<2>(true, user_data_response)); ActionProto action_proto; auto* collect_user_data = action_proto.mutable_collect_user_data(); @@ -2680,6 +2759,9 @@ TEST_F(CollectUserDataActionTest, PhoneNumberFromProto) { kMemoryLocation); collect_user_data->mutable_contact_details() ->set_separate_phone_number_section(true); + *collect_user_data->mutable_contact_details() + ->add_phone_number_required_data_piece() = + MakeRequiredDataPiece(autofill::ServerFieldType::PHONE_HOME_WHOLE_NUMBER); collect_user_data->mutable_contact_details()->set_phone_number_section_title( "Phone number"); collect_user_data->mutable_data_source(); @@ -2706,9 +2788,11 @@ TEST_F(CollectUserDataActionTest, PhoneNumberFromProto) { TEST_F(CollectUserDataActionTest, PaymentDataFromProto) { autofill::CountryNames::SetLocaleString("en-US"); - ON_CALL(mock_action_delegate_, GetPersonalDataManager()) + ON_CALL(mock_action_delegate_, GetPersonalDataManager) .WillByDefault(Return(nullptr)); - ON_CALL(mock_action_delegate_, CollectUserData(_)) + ON_CALL(mock_action_delegate_, MustUseBackendData) + .WillByDefault(Return(true)); + ON_CALL(mock_action_delegate_, CollectUserData) .WillByDefault([&](CollectUserDataOptions* collect_user_data_options) { EXPECT_FALSE(collect_user_data_options->should_store_data_changes); EXPECT_THAT(user_data_.available_payment_instruments_[0]->card->guid(), @@ -2771,7 +2855,7 @@ TEST_F(CollectUserDataActionTest, PaymentDataFromProto) { AddCompleteAddressEntriesToMap("John Doe", payment_instrument->mutable_address_values()); EXPECT_CALL(mock_action_delegate_, RequestUserData) - .WillOnce(RunOnceCallback<1>(true, user_data_response)); + .WillOnce(RunOnceCallback<2>(true, user_data_response)); ActionProto action_proto; auto* collect_user_data = action_proto.mutable_collect_user_data(); @@ -2791,9 +2875,11 @@ TEST_F(CollectUserDataActionTest, PaymentDataFromProto) { TEST_F(CollectUserDataActionTest, ShippingDataFromProto) { autofill::CountryNames::SetLocaleString("en-US"); - ON_CALL(mock_action_delegate_, GetPersonalDataManager()) + ON_CALL(mock_action_delegate_, GetPersonalDataManager) .WillByDefault(Return(nullptr)); - ON_CALL(mock_action_delegate_, CollectUserData(_)) + ON_CALL(mock_action_delegate_, MustUseBackendData) + .WillByDefault(Return(true)); + ON_CALL(mock_action_delegate_, CollectUserData) .WillByDefault([&](CollectUserDataOptions* collect_user_data_options) { EXPECT_FALSE(collect_user_data_options->should_store_data_changes); EXPECT_THAT(user_data_.available_addresses_[0]->profile->guid(), @@ -2820,7 +2906,7 @@ TEST_F(CollectUserDataActionTest, ShippingDataFromProto) { auto* address = user_data_response.add_available_addresses(); AddCompleteAddressEntriesToMap("John Doe", address->mutable_values()); EXPECT_CALL(mock_action_delegate_, RequestUserData) - .WillOnce(RunOnceCallback<1>(true, user_data_response)); + .WillOnce(RunOnceCallback<2>(true, user_data_response)); ActionProto action_proto; auto* collect_user_data = action_proto.mutable_collect_user_data(); @@ -2838,9 +2924,11 @@ TEST_F(CollectUserDataActionTest, ShippingDataFromProto) { } TEST_F(CollectUserDataActionTest, RawDataFromProtoDoesNotGetFormatted) { - ON_CALL(mock_action_delegate_, GetPersonalDataManager()) + ON_CALL(mock_action_delegate_, GetPersonalDataManager) .WillByDefault(Return(nullptr)); - ON_CALL(mock_action_delegate_, CollectUserData(_)) + ON_CALL(mock_action_delegate_, MustUseBackendData) + .WillByDefault(Return(true)); + ON_CALL(mock_action_delegate_, CollectUserData) .WillByDefault([&](CollectUserDataOptions* collect_user_data_options) { EXPECT_FALSE(collect_user_data_options->should_store_data_changes); EXPECT_THAT(user_data_.available_contacts_[0]->profile->guid(), @@ -2871,7 +2959,7 @@ TEST_F(CollectUserDataActionTest, RawDataFromProtoDoesNotGetFormatted) { (*profile->mutable_values())[14] = MakeAutofillEntry("+1 123-456-7890", /* raw= */ true); EXPECT_CALL(mock_action_delegate_, RequestUserData) - .WillOnce(RunOnceCallback<1>(true, user_data_response)); + .WillOnce(RunOnceCallback<2>(true, user_data_response)); ActionProto action_proto; auto* collect_user_data = action_proto.mutable_collect_user_data(); @@ -2892,9 +2980,11 @@ TEST_F(CollectUserDataActionTest, RawDataFromProtoDoesNotGetFormatted) { TEST_F(CollectUserDataActionTest, SelectEntriesFromProtoFromIdentifiers) { autofill::CountryNames::SetLocaleString("en-US"); - ON_CALL(mock_action_delegate_, GetPersonalDataManager()) + ON_CALL(mock_action_delegate_, GetPersonalDataManager) .WillByDefault(Return(nullptr)); - ON_CALL(mock_action_delegate_, CollectUserData(_)) + ON_CALL(mock_action_delegate_, MustUseBackendData) + .WillByDefault(Return(true)); + ON_CALL(mock_action_delegate_, CollectUserData) .WillByDefault([&](CollectUserDataOptions* collect_user_data_options) { ASSERT_TRUE(user_data_.has_selected_address("contact")); EXPECT_EQ(user_data_.selected_address("contact")->GetRawInfo( @@ -2956,7 +3046,7 @@ TEST_F(CollectUserDataActionTest, SelectEntriesFromProtoFromIdentifiers) { AddCompleteAddressEntriesToMap( "Jane Doe", payment_instrument_2->mutable_address_values()); EXPECT_CALL(mock_action_delegate_, RequestUserData) - .WillOnce(RunOnceCallback<1>(true, user_data_response)); + .WillOnce(RunOnceCallback<2>(true, user_data_response)); ActionProto action_proto; auto* collect_user_data = action_proto.mutable_collect_user_data(); @@ -2980,9 +3070,11 @@ TEST_F(CollectUserDataActionTest, SelectEntriesFromProtoFromIdentifiers) { TEST_F(CollectUserDataActionTest, DefaultSelectEntriesFromProtoWithoutIdentifiers) { autofill::CountryNames::SetLocaleString("en-US"); - ON_CALL(mock_action_delegate_, GetPersonalDataManager()) + ON_CALL(mock_action_delegate_, GetPersonalDataManager) .WillByDefault(Return(nullptr)); - ON_CALL(mock_action_delegate_, CollectUserData(_)) + ON_CALL(mock_action_delegate_, MustUseBackendData) + .WillByDefault(Return(true)); + ON_CALL(mock_action_delegate_, CollectUserData) .WillByDefault([&](CollectUserDataOptions* collect_user_data_options) { ASSERT_TRUE(user_data_.has_selected_address("contact")); EXPECT_EQ(user_data_.selected_address("contact")->GetRawInfo( @@ -3031,7 +3123,7 @@ TEST_F(CollectUserDataActionTest, AddCompleteAddressEntriesToMap( "Jane Doe", payment_instrument_2->mutable_address_values()); EXPECT_CALL(mock_action_delegate_, RequestUserData) - .WillOnce(RunOnceCallback<1>(true, user_data_response)); + .WillOnce(RunOnceCallback<2>(true, user_data_response)); ActionProto action_proto; auto* collect_user_data = action_proto.mutable_collect_user_data(); @@ -3307,11 +3399,11 @@ TEST_F(CollectUserDataActionTest, LogsUkmSelectionStateUpdated) { .WillByDefault([&](CollectUserDataOptions* collect_user_data_options) { user_data_.terms_and_conditions_ = ACCEPTED; collect_user_data_options->selected_user_data_changed_callback.Run( - CONTACT_EVENT, SELECTION_CHANGED); + UserDataEventField::CONTACT_EVENT, SELECTION_CHANGED); collect_user_data_options->selected_user_data_changed_callback.Run( - CREDIT_CARD_EVENT, ENTRY_CREATED); + UserDataEventField::CREDIT_CARD_EVENT, ENTRY_CREATED); collect_user_data_options->selected_user_data_changed_callback.Run( - SHIPPING_EVENT, ENTRY_EDITED); + UserDataEventField::SHIPPING_EVENT, ENTRY_EDITED); user_model_.SetSelectedCreditCard( std::make_unique<autofill::CreditCard>(credit_card), &user_data_); user_model_.SetSelectedAutofillProfile( @@ -3570,8 +3662,6 @@ TEST_F(CollectUserDataActionTest, LogsUkmMoreThanFiveProfilesCount) { } TEST_F(CollectUserDataActionTest, LogUkmSuccess) { - base::subtle::ScopedTimeClockOverrides overrides( - nullptr, &TimeTicksOverride::Now, nullptr); ActionProto action_proto; auto* collect_user_data_proto = action_proto.mutable_collect_user_data(); collect_user_data_proto->set_privacy_notice_text("privacy"); @@ -3594,11 +3684,10 @@ TEST_F(CollectUserDataActionTest, LogUkmSuccess) { callback_, Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED)))); CollectUserDataAction action(&mock_action_delegate_, action_proto); + + AddWaitTime(&action, base::Milliseconds(4000)); action.ProcessAction(callback_.Get()); - // We start counting the "wait time" after CollecUserData is called, so we - // need to increase the timer and call the callback at this point. - TimeTicksOverride::now_ticks_ += base::Seconds(4); ASSERT_TRUE(confirm_callback); std::move(confirm_callback).Run(&user_data_, &user_model_); @@ -3649,8 +3738,6 @@ TEST_F(CollectUserDataActionTest, LogUkmAdditionalActionSelected) { } TEST_F(CollectUserDataActionTest, LogUkmFailure) { - base::subtle::ScopedTimeClockOverrides overrides( - nullptr, &TimeTicksOverride::Now, nullptr); ActionProto action_proto; auto* collect_user_data_proto = action_proto.mutable_collect_user_data(); collect_user_data_proto->set_privacy_notice_text("privacy"); @@ -3666,10 +3753,8 @@ TEST_F(CollectUserDataActionTest, LogUkmFailure) { .WillByDefault([&](CollectUserDataOptions* collect_user_data_options) { // The continue button is never pressed. }); + AddWaitTime(&action, base::Milliseconds(3000)); action.ProcessAction(callback_.Get()); - // We start counting the "wait time" after CollecUserData is called, so we - // need to increase the timer at this point. - TimeTicksOverride::now_ticks_ += base::Seconds(3); // The CollectUserDataAction destructor is called, this simulates the user // closing the bottom sheet or the tab. @@ -3696,7 +3781,7 @@ TEST_F(CollectUserDataActionTest, LogUkmDataFromBackend) { collect_user_data_proto->mutable_data_source(); EXPECT_CALL(mock_action_delegate_, RequestUserData) - .WillRepeatedly(RunOnceCallback<1>(true, GetUserDataResponseProto())); + .WillRepeatedly(RunOnceCallback<2>(true, GetUserDataResponseProto())); ON_CALL(mock_action_delegate_, CollectUserData(_)) .WillByDefault([&](CollectUserDataOptions* collect_user_data_options) { user_data_.terms_and_conditions_ = ACCEPTED; @@ -3881,17 +3966,19 @@ TEST_F(CollectUserDataActionTest, ReloadsDataIfRequested) { ON_CALL(mock_action_delegate_, GetPersonalDataManager) .WillByDefault(Return(nullptr)); + ON_CALL(mock_action_delegate_, MustUseBackendData) + .WillByDefault(Return(true)); EXPECT_CALL(mock_action_delegate_, RequestUserData) .Times(3) - .WillRepeatedly(RunOnceCallback<1>(true, GetUserDataResponseProto())); + .WillRepeatedly(RunOnceCallback<2>(true, GetUserDataResponseProto())); EXPECT_CALL(mock_action_delegate_, CollectUserData(_)) .WillOnce(Invoke([=](CollectUserDataOptions* collect_user_data_options) { std::move(collect_user_data_options->reload_data_callback) - .Run(&user_data_); + .Run(UserDataEventField::NONE, &user_data_); })) .WillOnce(Invoke([=](CollectUserDataOptions* collect_user_data_options) { std::move(collect_user_data_options->reload_data_callback) - .Run(&user_data_); + .Run(UserDataEventField::NONE, &user_data_); })) .WillOnce(Invoke([=](CollectUserDataOptions* collect_user_data_options) { // We can't submit here since the user data is not complete. @@ -3914,5 +4001,205 @@ TEST_F(CollectUserDataActionTest, ReloadsDataIfRequested) { "Android.AutofillAssistant.PaymentRequest.AutofillChanged", 1u); } -} // namespace +TEST_F(CollectUserDataActionTest, MergesTransientDataWithUserDataFromBackend) { + auto transient_contact = std::make_unique<autofill::AutofillProfile>(); + transient_contact->SetRawInfo(autofill::NAME_FULL, u"Jane Doe"); + user_data_.transient_contacts_.emplace_back( + std::make_unique<Contact>(std::move(transient_contact))); + + auto transient_phone_number = std::make_unique<autofill::AutofillProfile>(); + transient_phone_number->SetRawInfo(autofill::PHONE_HOME_WHOLE_NUMBER, + u"+16505678910"); + user_data_.transient_phone_numbers_.emplace_back( + std::make_unique<PhoneNumber>(std::move(transient_phone_number))); + + GetUserDataResponseProto user_data_response; + user_data_response.set_locale("en-US"); + auto* profile = user_data_response.add_available_contacts(); + (*profile->mutable_values())[7] = MakeAutofillEntry("John Doe"); + *user_data_response.add_available_phone_numbers()->mutable_value() = + MakeAutofillEntry("+1 187-654-3210"); + + ON_CALL(mock_action_delegate_, GetPersonalDataManager) + .WillByDefault(Return(nullptr)); + ON_CALL(mock_action_delegate_, MustUseBackendData) + .WillByDefault(Return(true)); + EXPECT_CALL(mock_action_delegate_, RequestUserData) + .Times(2) + .WillRepeatedly(RunOnceCallback<2>(true, user_data_response)); + EXPECT_CALL(mock_action_delegate_, CollectUserData(_)) + .WillOnce(Invoke([=](CollectUserDataOptions* collect_user_data_options) { + ASSERT_EQ(user_data_.available_contacts_.size(), 2u); + EXPECT_EQ(user_data_.available_contacts_[0]->profile->GetRawInfo( + autofill::NAME_FULL), + u"Jane Doe"); + EXPECT_EQ(user_data_.available_contacts_[1]->profile->GetRawInfo( + autofill::NAME_FULL), + u"John Doe"); + + ASSERT_EQ(user_data_.available_phone_numbers_.size(), 2u); + EXPECT_EQ(user_data_.available_phone_numbers_[0]->profile->GetRawInfo( + autofill::PHONE_HOME_WHOLE_NUMBER), + u"+16505678910"); + EXPECT_EQ(user_data_.available_phone_numbers_[1]->profile->GetRawInfo( + autofill::PHONE_HOME_WHOLE_NUMBER), + u"+1 187-654-3210"); + + std::move(collect_user_data_options->reload_data_callback) + .Run(UserDataEventField::NONE, &user_data_); + })) + .WillOnce(Invoke([=](CollectUserDataOptions* collect_user_data_options) { + EXPECT_EQ(user_data_.available_contacts_.size(), 2u); + EXPECT_EQ(user_data_.available_phone_numbers_.size(), 2u); + + // Don't end the action. + })); + + ActionProto action_proto; + auto* collect_user_data = action_proto.mutable_collect_user_data(); + collect_user_data->set_request_terms_and_conditions(false); + collect_user_data->mutable_contact_details()->set_request_payer_name(true); + collect_user_data->mutable_contact_details() + ->set_separate_phone_number_section(true); + collect_user_data->mutable_contact_details()->set_phone_number_section_title( + "Phone number"); + collect_user_data->mutable_contact_details()->set_contact_details_name( + kMemoryLocation); + collect_user_data->mutable_data_source(); + + CollectUserDataAction action(&mock_action_delegate_, action_proto); + action.ProcessAction(callback_.Get()); +} + +TEST_F(CollectUserDataActionTest, FallBackToChromeDataOnFailedRequest) { + ON_CALL(mock_personal_data_manager_, IsAutofillProfileEnabled) + .WillByDefault(Return(true)); + + autofill::AutofillProfile profile; + autofill::test::SetProfileInfo( + &profile, "Adam", "", "West", "adam.west@gmail.com", "", "Main St. 18", + "", "abc", "New York", "NY", "10001", "us", "+1 123-456-7890"); + + ON_CALL(mock_personal_data_manager_, GetProfiles) + .WillByDefault( + Return(std::vector<autofill::AutofillProfile*>({&profile}))); + ON_CALL(mock_action_delegate_, MustUseBackendData) + .WillByDefault(Return(false)); + EXPECT_CALL(mock_action_delegate_, RequestUserData) + .WillOnce(RunOnceCallback<2>(false, GetUserDataResponseProto())); + + ON_CALL(mock_action_delegate_, CollectUserData(_)) + .WillByDefault([&](CollectUserDataOptions* collect_user_data_options) { + ExpectSelectedProfileMatches(kMemoryLocation, &profile); + + EXPECT_TRUE(collect_user_data_options->request_payer_phone); + EXPECT_FALSE( + collect_user_data_options->request_phone_number_separately); + EXPECT_THAT(collect_user_data_options->contact_summary_fields, + ElementsAre(AutofillContactField::NAME_FULL, + AutofillContactField::PHONE_HOME_WHOLE_NUMBER)); + EXPECT_EQ(collect_user_data_options->contact_summary_max_lines, 2); + EXPECT_THAT(collect_user_data_options->contact_full_fields, + ElementsAre(AutofillContactField::NAME_FULL, + AutofillContactField::PHONE_HOME_WHOLE_NUMBER)); + EXPECT_EQ(collect_user_data_options->contact_full_max_lines, 2); + EXPECT_FALSE(collect_user_data_options->data_origin_notice.has_value()); + EXPECT_TRUE(collect_user_data_options->should_store_data_changes); + EXPECT_FALSE(collect_user_data_options->use_alternative_edit_dialogs); + + std::move(collect_user_data_options->confirm_callback) + .Run(&user_data_, nullptr); + }); + + ActionProto action_proto; + auto* collect_user_data = action_proto.mutable_collect_user_data(); + collect_user_data->set_request_terms_and_conditions(false); + collect_user_data->mutable_contact_details()->set_request_payer_name(true); + collect_user_data->mutable_contact_details()->add_summary_fields( + ContactDetailsProto::NAME_FULL); + collect_user_data->mutable_contact_details()->set_max_number_summary_lines(1); + collect_user_data->mutable_contact_details()->add_full_fields( + ContactDetailsProto::NAME_FULL); + collect_user_data->mutable_contact_details()->set_max_number_full_lines(1); + *collect_user_data->mutable_contact_details()->add_required_data_piece() = + MakeRequiredDataPiece(autofill::ServerFieldType::NAME_FULL); + collect_user_data->mutable_contact_details()->set_contact_details_name( + kMemoryLocation); + collect_user_data->mutable_contact_details() + ->set_separate_phone_number_section(true); + collect_user_data->mutable_contact_details()->set_phone_number_section_title( + "Phone number"); + *collect_user_data->mutable_contact_details() + ->add_phone_number_required_data_piece() = + MakeRequiredDataPiece(autofill::ServerFieldType::PHONE_HOME_WHOLE_NUMBER); + collect_user_data->mutable_data_source()->set_allow_fallback(true); + collect_user_data->mutable_data_origin_notice()->set_link_text("Link"); + collect_user_data->mutable_data_origin_notice()->set_dialog_title("Title"); + collect_user_data->mutable_data_origin_notice()->set_dialog_text("Text"); + collect_user_data->mutable_data_origin_notice()->set_dialog_button_text( + "Button"); + + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED)))); + CollectUserDataAction action(&mock_action_delegate_, action_proto); + action.ProcessAction(callback_.Get()); + + EXPECT_THAT( + GetUkmUserDataSource(ukm_recorder_), + ElementsAreArray({ToHumanReadableEntry( + source_id_, kUserDataSource, + static_cast<int64_t>(Metrics::UserDataSource::CHROME_AUTOFILL))})); +} + +TEST_F(CollectUserDataActionTest, FailActionIfFallbackIsNotPossible) { + ON_CALL(mock_action_delegate_, GetPersonalDataManager) + .WillByDefault(Return(nullptr)); + ON_CALL(mock_action_delegate_, MustUseBackendData) + .WillByDefault(Return(true)); + EXPECT_CALL(mock_action_delegate_, RequestUserData) + .WillOnce(RunOnceCallback<2>(false, GetUserDataResponseProto())); + + ActionProto action_proto; + auto* collect_user_data = action_proto.mutable_collect_user_data(); + collect_user_data->set_request_terms_and_conditions(false); + collect_user_data->mutable_contact_details()->set_request_payer_name(true); + collect_user_data->mutable_contact_details()->set_contact_details_name( + kMemoryLocation); + collect_user_data->mutable_data_source()->set_allow_fallback(true); + + EXPECT_CALL(callback_, Run(Pointee(Property(&ProcessedActionProto::status, + USER_DATA_REQUEST_FAILED)))); + CollectUserDataAction action(&mock_action_delegate_, action_proto); + action.ProcessAction(callback_.Get()); +} + +TEST_F(CollectUserDataActionTest, FailActionIfReloadFails) { + ON_CALL(mock_action_delegate_, GetPersonalDataManager) + .WillByDefault(Return(nullptr)); + ON_CALL(mock_action_delegate_, MustUseBackendData) + .WillByDefault(Return(false)); + EXPECT_CALL(mock_action_delegate_, RequestUserData) + .WillOnce(RunOnceCallback<2>(true, GetUserDataResponseProto())) + .WillOnce(RunOnceCallback<2>(false, GetUserDataResponseProto())); + EXPECT_CALL(mock_action_delegate_, CollectUserData(_)) + .WillOnce([&](CollectUserDataOptions* collect_user_data_options) { + std::move(collect_user_data_options->reload_data_callback) + .Run(UserDataEventField::NONE, &user_data_); + }); + + ActionProto action_proto; + auto* collect_user_data = action_proto.mutable_collect_user_data(); + collect_user_data->set_request_terms_and_conditions(false); + collect_user_data->mutable_contact_details()->set_request_payer_name(true); + collect_user_data->mutable_contact_details()->set_contact_details_name( + kMemoryLocation); + collect_user_data->mutable_data_source()->set_allow_fallback(true); + + EXPECT_CALL(callback_, Run(Pointee(Property(&ProcessedActionProto::status, + USER_DATA_REQUEST_FAILED)))); + CollectUserDataAction action(&mock_action_delegate_, action_proto); + action.ProcessAction(callback_.Get()); +} + } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/actions/execute_js_action.h b/chromium/components/autofill_assistant/browser/actions/execute_js_action.h index dafb2957aef..0be489f1884 100644 --- a/chromium/components/autofill_assistant/browser/actions/execute_js_action.h +++ b/chromium/components/autofill_assistant/browser/actions/execute_js_action.h @@ -11,7 +11,7 @@ #include "components/autofill_assistant/browser/actions/action.h" #include "components/autofill_assistant/browser/actions/action_delegate.h" #include "components/autofill_assistant/browser/dom_action.pb.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" namespace autofill_assistant { diff --git a/chromium/components/autofill_assistant/browser/actions/execute_js_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/execute_js_action_unittest.cc index ed157496535..e96375f533c 100644 --- a/chromium/components/autofill_assistant/browser/actions/execute_js_action_unittest.cc +++ b/chromium/components/autofill_assistant/browser/actions/execute_js_action_unittest.cc @@ -12,7 +12,7 @@ #include "components/autofill_assistant/browser/actions/mock_action_delegate.h" #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/dom_action.pb.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/element_store.h" #include "components/autofill_assistant/browser/web/mock_web_controller.h" #include "testing/gmock/include/gmock/gmock.h" diff --git a/chromium/components/autofill_assistant/browser/actions/external_action.cc b/chromium/components/autofill_assistant/browser/actions/external_action.cc new file mode 100644 index 00000000000..ec25401d244 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/actions/external_action.cc @@ -0,0 +1,201 @@ +// Copyright 2022 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_assistant/browser/actions/external_action.h" + +#include "base/logging.h" +#include "components/autofill_assistant/browser/actions/action_delegate.h" +#include "components/autofill_assistant/browser/client_status.h" + +namespace autofill_assistant { + +ExternalAction::ExternalAction(ActionDelegate* delegate, + const ActionProto& proto) + : Action(delegate, proto) { + DCHECK(proto_.has_external_action()); +} + +ExternalAction::~ExternalAction() = default; + +void ExternalAction::InternalProcessAction(ProcessActionCallback callback) { + callback_ = std::move(callback); + if (!delegate_->SupportsExternalActions()) { + VLOG(1) << "External action are not supported for this run."; + EndAction(ClientStatus(INVALID_ACTION)); + return; + } + if (!proto_.external_action().has_info()) { + VLOG(1) << "The ExternalAction's |info| is missing."; + EndAction(ClientStatus(INVALID_ACTION)); + return; + } + + delegate_->RequestExternalAction( + proto_.external_action(), + base::BindOnce(&ExternalAction::StartDomChecks, + weak_ptr_factory_.GetWeakPtr()), + base::BindOnce(&ExternalAction::OnExternalActionFinished, + weak_ptr_factory_.GetWeakPtr())); + + // Do not add any code here. External delegates may choose to end the action + // immediately, which could result in *this being deleted and UaF errors for + // code after the above call. +} + +void ExternalAction::StartDomChecks( + ExternalActionDelegate::DomUpdateCallback dom_update_callback) { + const auto& external_action = proto_.external_action(); + if (!external_action.conditions().empty() || + external_action.allow_interrupt()) { + // We keep track of the fact that we have an active WaitForDom to make sure + // we end it gracefully. + has_pending_wait_for_dom_ = true; + dom_update_callback_ = std::move(dom_update_callback); + SetupConditions(); + // TODO(b/201964908): fix time tracking. + delegate_->WaitForDom( + /* max_wait_time= */ base::TimeDelta::Max(), + /* allow_observer_mode = */ false, external_action.allow_interrupt(), + /* observer= */ nullptr, + base::BindRepeating(&ExternalAction::RegisterChecks, + weak_ptr_factory_.GetWeakPtr()), + base::BindOnce(&ExternalAction::OnWaitForElementTimed, + weak_ptr_factory_.GetWeakPtr(), + base::BindOnce(&ExternalAction::OnDoneWaitForDom, + weak_ptr_factory_.GetWeakPtr()))); + } +} + +void ExternalAction::SetupConditions() { + for (const auto& condition_proto : proto_.external_action().conditions()) { + ConditionStatus condition_status; + condition_status.proto = condition_proto; + conditions_.emplace_back(condition_status); + } +} + +void ExternalAction::RegisterChecks( + BatchElementChecker* checker, + base::OnceCallback<void(const ClientStatus&)> wait_for_dom_callback) { + if (!callback_) { + // Action is done; checks aren't necessary anymore. + std::move(wait_for_dom_callback).Run(OkClientStatus()); + return; + } + + for (size_t i = 0; i < conditions_.size(); i++) { + checker->AddElementConditionCheck( + conditions_[i].proto.element_condition(), + base::BindOnce(&ExternalAction::OnPreconditionResult, + weak_ptr_factory_.GetWeakPtr(), i)); + } + + checker->AddAllDoneCallback(base::BindOnce( + &ExternalAction::OnElementChecksDone, weak_ptr_factory_.GetWeakPtr(), + std::move(wait_for_dom_callback))); +} + +void ExternalAction::OnPreconditionResult( + size_t condition_index, + const ClientStatus& status, + const std::vector<std::string>& ignored_payloads, + const std::vector<std::string>& ignored_tags, + const base::flat_map<std::string, DomObjectFrameStack>& ignored_elements) { + DCHECK_LT(condition_index, conditions_.size()); + bool precondition_is_met = status.ok(); + + // If this is the first time we perform the check, we consider the + // precondition as 'changed' since we always want to send the notification + // after the first check. + if (first_condition_notification_sent_ && + conditions_[condition_index].result == precondition_is_met) { + return; + } + + conditions_[condition_index].result = precondition_is_met; + conditions_[condition_index].changed = true; +} + +void ExternalAction::OnElementChecksDone( + base::OnceCallback<void(const ClientStatus&)> wait_for_dom_callback) { + // If it was decided to end the action in the meantime, we send an OK status + // to WaitForDom so that it can end gracefully. Note that it is possible for + // WaitForDom to decide not to call OnDoneWaitForDom, if an interrupt triggers + // at the same time. + if (external_action_end_requested_) { + std::move(wait_for_dom_callback).Run(OkClientStatus()); + return; + } + + external::ElementConditionsUpdate update_proto; + for (auto& condition : conditions_) { + if (!condition.changed) + continue; + + condition.changed = false; + external::ElementConditionsUpdate::ConditionResult result; + result.set_id(condition.proto.id()); + result.set_satisfied(condition.result); + *update_proto.add_results() = result; + } + + // We only send the notification if there were any changes since the last + // check. + if (!update_proto.results().empty()) { + first_condition_notification_sent_ = true; + dom_update_callback_.Run(update_proto); + } + + // Whether we had satisfied element conditions or not, we run this callback + // with |ELEMENT_RESOLUTION_FAILED| to let the WaitForDom know that we want to + // keep running checks. + std::move(wait_for_dom_callback).Run(ClientStatus(ELEMENT_RESOLUTION_FAILED)); +} + +void ExternalAction::OnDoneWaitForDom(const ClientStatus& status) { + if (!callback_) { + return; + } + + // If |status| is OK we send the external response. Otherwise we ignore the + // external response and report the error, as we might not be in a state to + // continue with the script. + if (status.ok() && external_action_end_requested_) { + EndWithExternalResult(); + return; + } + EndAction(status); +} + +void ExternalAction::OnExternalActionFinished(const external::Result& result) { + if (!callback_) { + return; + } + + external_action_result_ = result; + + // If there is an ongoing WaitForDom, we end the action on the next WaitForDom + // notification to make sure we end gracefully. + if (has_pending_wait_for_dom_) { + external_action_end_requested_ = true; + return; + } + + EndWithExternalResult(); +} + +void ExternalAction::EndWithExternalResult() { + *processed_action_proto_->mutable_external_action_result() + ->mutable_result_info() = external_action_result_.result_info(); + EndAction(external_action_result_.success() + ? ClientStatus(ACTION_APPLIED) + : ClientStatus(UNKNOWN_ACTION_STATUS)); +} + +void ExternalAction::EndAction(const ClientStatus& status) { + UpdateProcessedAction(status); + std::move(callback_).Run(std::move(processed_action_proto_)); +} + +} // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/actions/external_action.h b/chromium/components/autofill_assistant/browser/actions/external_action.h new file mode 100644 index 00000000000..aaacd8d310d --- /dev/null +++ b/chromium/components/autofill_assistant/browser/actions/external_action.h @@ -0,0 +1,80 @@ +// Copyright 2022 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_ASSISTANT_BROWSER_ACTIONS_EXTERNAL_ACTION_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_EXTERNAL_ACTION_H_ + +#include "components/autofill_assistant/browser/actions/action.h" +#include "components/autofill_assistant/browser/batch_element_checker.h" +#include "components/autofill_assistant/browser/public/external_action_delegate.h" +#include "components/autofill_assistant/browser/public/external_script_controller.h" +#include "components/autofill_assistant/browser/wait_for_dom_observer.h" + +namespace autofill_assistant { + +class ExternalAction : public Action { + public: + explicit ExternalAction(ActionDelegate* delegate, const ActionProto& proto); + + ExternalAction(const ExternalAction&) = delete; + ExternalAction& operator=(const ExternalAction&) = delete; + + ~ExternalAction() override; + + private: + struct ConditionStatus { + ExternalActionProto::ExternalCondition proto; + + // We always update the result and send the notification for each condition + // after the first check so the initial value of these does not matter. + bool result = false; + bool changed = false; + }; + + // Overrides Action: + void InternalProcessAction(ProcessActionCallback callback) override; + + void StartDomChecks( + ExternalActionDelegate::DomUpdateCallback dom_update_callback); + void SetupConditions(); + void OnPreconditionResult( + size_t condition_index, + const ClientStatus& status, + const std::vector<std::string>& ignored_payloads, + const std::vector<std::string>& ignored_tags, + const base::flat_map<std::string, DomObjectFrameStack>& ignored_elements); + void RegisterChecks( + BatchElementChecker* checker, + base::OnceCallback<void(const ClientStatus&)> wait_for_dom_callback); + void OnElementChecksDone( + base::OnceCallback<void(const ClientStatus&)> wait_for_dom_callback); + void OnDoneWaitForDom(const ClientStatus& status); + void OnExternalActionFinished(const external::Result& success); + void EndWithExternalResult(); + void EndAction(const ClientStatus& status); + + ProcessActionCallback callback_; + + // The list of conditions to be checked. + std::vector<ConditionStatus> conditions_; + // Keeps track of whether we have already sent the first notification about + // the conditions. + bool first_condition_notification_sent_ = false; + // Whether there is a currently running WaitForDom. + bool has_pending_wait_for_dom_ = false; + // The callback to notify element condition updates. + ExternalActionDelegate::DomUpdateCallback dom_update_callback_; + + // Whether we received a notification from the external caller to end the + // action. + bool external_action_end_requested_ = false; + // The external result reported when the external caller requested to end the + // action. + external::Result external_action_result_; + + base::WeakPtrFactory<ExternalAction> weak_ptr_factory_{this}; +}; + +} // namespace autofill_assistant +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_EXTERNAL_ACTION_H_ diff --git a/chromium/components/autofill_assistant/browser/actions/external_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/external_action_unittest.cc new file mode 100644 index 00000000000..ac8be3fd66b --- /dev/null +++ b/chromium/components/autofill_assistant/browser/actions/external_action_unittest.cc @@ -0,0 +1,359 @@ +// Copyright 2022 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_assistant/browser/actions/external_action.h" + +#include "base/test/gmock_callback_support.h" +#include "base/test/mock_callback.h" +#include "base/test/task_environment.h" +#include "base/test/test_simple_task_runner.h" +#include "components/autofill_assistant/browser/actions/mock_action_delegate.h" +#include "components/autofill_assistant/browser/actions/wait_for_dom_test_base.h" +#include "components/autofill_assistant/browser/external_action_extension_test.pb.h" +#include "components/autofill_assistant/browser/service.pb.h" +#include "components/autofill_assistant/browser/web/mock_web_controller.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace autofill_assistant { +namespace { + +using ::base::test::RunOnceCallback; +using ::testing::_; +using ::testing::ElementsAre; +using ::testing::Eq; +using ::testing::Pointee; +using ::testing::Property; +using ::testing::Return; +using ::testing::UnorderedElementsAre; +using ::testing::WithArgs; + +class ExternalActionTest : public WaitForDomTestBase { + public: + ExternalActionTest() = default; + + protected: + void Run() { + ON_CALL(mock_action_delegate_, SupportsExternalActions) + .WillByDefault(Return(true)); + + ActionProto action_proto; + *action_proto.mutable_external_action() = proto_; + action_ = + std::make_unique<ExternalAction>(&mock_action_delegate_, action_proto); + action_->ProcessAction(callback_.Get()); + } + + base::MockCallback<Action::ProcessActionCallback> callback_; + ExternalActionProto proto_; + std::unique_ptr<ExternalAction> action_; +}; + +external::Result MakeResult(bool success) { + external::Result result; + result.set_success(success); + testing::TestResultExtension test_extension_proto; + test_extension_proto.set_text("test text"); + + *result.mutable_result_info()->MutableExtension( + testing::test_result_extension) = std::move(test_extension_proto); + return result; +} + +TEST_F(ExternalActionTest, Success) { + proto_.mutable_info(); + + EXPECT_CALL(mock_action_delegate_, RequestExternalAction) + .WillOnce(RunOnceCallback<2>(MakeResult(/* success= */ true))); + + std::unique_ptr<ProcessedActionProto> returned_processed_action_proto; + EXPECT_CALL(callback_, Run) + .WillOnce( + [&returned_processed_action_proto]( + std::unique_ptr<ProcessedActionProto> processed_action_proto) { + returned_processed_action_proto = std::move(processed_action_proto); + }); + Run(); + EXPECT_THAT(returned_processed_action_proto->status(), Eq(ACTION_APPLIED)); + EXPECT_TRUE(returned_processed_action_proto->has_external_action_result()); + EXPECT_THAT(returned_processed_action_proto->external_action_result() + .result_info() + .GetExtension(testing::test_result_extension) + .text(), + Eq("test text")); +} + +TEST_F(ExternalActionTest, ExternalFailure) { + proto_.mutable_info(); + + std::unique_ptr<ProcessedActionProto> returned_processed_action_proto; + EXPECT_CALL(callback_, Run) + .WillOnce( + [&returned_processed_action_proto]( + std::unique_ptr<ProcessedActionProto> processed_action_proto) { + returned_processed_action_proto = std::move(processed_action_proto); + }); + EXPECT_CALL(mock_action_delegate_, RequestExternalAction) + .WillOnce(RunOnceCallback<2>(MakeResult(/* success= */ false))); + Run(); + EXPECT_THAT(returned_processed_action_proto->status(), + Eq(UNKNOWN_ACTION_STATUS)); + EXPECT_TRUE(returned_processed_action_proto->has_external_action_result()); + EXPECT_THAT(returned_processed_action_proto->external_action_result() + .result_info() + .GetExtension(testing::test_result_extension) + .text(), + Eq("test text")); +} + +TEST_F(ExternalActionTest, FailsIfProtoExtensionInfoNotSet) { + EXPECT_CALL(mock_action_delegate_, RequestExternalAction).Times(0); + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, INVALID_ACTION)))); + Run(); +} + +TEST_F(ExternalActionTest, FailsIfDelegateDoesNotSupportExternalActions) { + proto_.mutable_info(); + EXPECT_CALL(mock_action_delegate_, SupportsExternalActions()) + .WillOnce(Return(false)); + EXPECT_CALL(mock_action_delegate_, RequestExternalAction).Times(0); + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, INVALID_ACTION)))); + Run(); +} + +TEST_F(ExternalActionTest, ExternalActionWithInterrupts) { + proto_.mutable_info(); + proto_.set_allow_interrupt(true); + + EXPECT_CALL(mock_action_delegate_, RequestExternalAction) + .WillOnce( + [](const ExternalActionProto& external_action, + base::OnceCallback<void(ExternalActionDelegate::DomUpdateCallback)> + start_dom_checks_callback, + base::OnceCallback<void(const external::Result& result)> + end_action_callback) { + std::move(start_dom_checks_callback).Run(base::DoNothing()); + std::move(end_action_callback).Run(MakeResult(/* success= */ true)); + }); + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED)))); + Run(); + // The action should end at the next WaitForDom notification. + task_env_.FastForwardBy(base::Seconds(1)); +} + +TEST_F(ExternalActionTest, ExternalActionWithoutInterrupts) { + proto_.mutable_info(); + proto_.set_allow_interrupt(false); + + EXPECT_CALL(mock_action_delegate_, RequestExternalAction) + .WillOnce( + [](const ExternalActionProto& external_action, + base::OnceCallback<void(ExternalActionDelegate::DomUpdateCallback)> + start_dom_checks_callback, + base::OnceCallback<void(const external::Result& result)> + end_action_callback) { + std::move(start_dom_checks_callback).Run(base::DoNothing()); + std::move(end_action_callback).Run(MakeResult(/* success= */ true)); + }); + EXPECT_CALL(mock_action_delegate_, WaitForDom).Times(0); + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED)))); + Run(); +} + +TEST_F(ExternalActionTest, DoesNotStartWaitForDomIfDomChecksAreNotRequested) { + proto_.mutable_info(); + proto_.set_allow_interrupt(true); + + EXPECT_CALL(mock_action_delegate_, RequestExternalAction) + .WillOnce( + [](const ExternalActionProto& external_action, + base::OnceCallback<void(ExternalActionDelegate::DomUpdateCallback)> + start_dom_checks_callback, + base::OnceCallback<void(const external::Result& result)> + end_action_callback) { + // We call the |end_action_callback| without calling + // |start_dom_checks_callback|. + std::move(end_action_callback).Run(MakeResult(/* success= */ true)); + }); + EXPECT_CALL(mock_action_delegate_, WaitForDom).Times(0); + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED)))); + Run(); +} + +TEST_F(ExternalActionTest, ExternalActionWithDomChecks) { + proto_.mutable_info(); + ExternalActionProto::ExternalCondition condition; + condition.set_id(55); + *condition.mutable_element_condition()->mutable_match() = + ToSelectorProto("element"); + *proto_.add_conditions() = condition; + + base::MockCallback<ExternalActionDelegate::DomUpdateCallback> + dom_update_callback; + + EXPECT_CALL(mock_action_delegate_, RequestExternalAction) + .WillOnce([&dom_update_callback]( + const ExternalActionProto& external_action, + base::OnceCallback<void( + ExternalActionDelegate::DomUpdateCallback)> + start_dom_checks_callback, + base::OnceCallback<void(const external::Result& result)> + end_action_callback) { + std::move(start_dom_checks_callback).Run(dom_update_callback.Get()); + std::move(end_action_callback).Run(MakeResult(/* success= */ true)); + }); + + EXPECT_CALL( + dom_update_callback, + Run(Property( + &external::ElementConditionsUpdate::results, + ElementsAre(AllOf( + Property(&external::ElementConditionsUpdate::ConditionResult::id, + 55), + Property(&external::ElementConditionsUpdate::ConditionResult:: + satisfied, + false)))))); + Run(); + + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED)))); + // The action should end at the next WaitForDom notification. + task_env_.FastForwardBy(base::Seconds(1)); +} + +TEST_F(ExternalActionTest, DomChecksOnlyUpdateOnChange) { + proto_.mutable_info(); + ExternalActionProto::ExternalCondition changing_condition; + changing_condition.set_id(55); + *changing_condition.mutable_element_condition()->mutable_match() = + ToSelectorProto("changing_condition"); + ExternalActionProto::ExternalCondition unchanging_condition; + unchanging_condition.set_id(9); + *unchanging_condition.mutable_element_condition()->mutable_match() = + ToSelectorProto("unchanging_condition"); + *proto_.add_conditions() = changing_condition; + *proto_.add_conditions() = unchanging_condition; + + base::MockCallback<ExternalActionDelegate::DomUpdateCallback> + dom_update_callback; + + EXPECT_CALL(mock_action_delegate_, RequestExternalAction) + .WillOnce([&dom_update_callback]( + const ExternalActionProto& external_action, + base::OnceCallback<void( + ExternalActionDelegate::DomUpdateCallback)> + start_dom_checks_callback, + base::OnceCallback<void(const external::Result& result)> + end_action_callback) { + std::move(start_dom_checks_callback).Run(dom_update_callback.Get()); + }); + + // For the first rounds of checks, all elements should be in the notification. + // Note that the |mock_web_controller_| reports an element as missing by + // default in the fixture. + EXPECT_CALL( + dom_update_callback, + Run(Property( + &external::ElementConditionsUpdate::results, + UnorderedElementsAre( + AllOf(Property( + &external::ElementConditionsUpdate::ConditionResult::id, + 55), + Property(&external::ElementConditionsUpdate:: + ConditionResult::satisfied, + false)), + AllOf(Property( + &external::ElementConditionsUpdate::ConditionResult::id, + 9), + Property(&external::ElementConditionsUpdate:: + ConditionResult::satisfied, + false)))))); + + Run(); + + // For the second rounds of checks, we simulate the |changing_condition| + // changing to being satisfied and |unchanging_condition| remaining + // unsatisfied. + EXPECT_CALL(mock_web_controller_, + FindElement(Selector({"changing_condition"}), _, _)) + .WillOnce(WithArgs<2>([](auto&& callback) { + std::move(callback).Run(OkClientStatus(), + std::make_unique<ElementFinderResult>()); + })); + EXPECT_CALL(mock_web_controller_, + FindElement(Selector({"unchanging_condition"}), _, _)) + .WillOnce(WithArgs<2>([](auto&& callback) { + std::move(callback).Run(ClientStatus(ELEMENT_RESOLUTION_FAILED), + std::make_unique<ElementFinderResult>()); + })); + + // The notification should now only contain an entry for |changed_condition|. + EXPECT_CALL( + dom_update_callback, + Run(Property( + &external::ElementConditionsUpdate::results, + UnorderedElementsAre(AllOf( + Property(&external::ElementConditionsUpdate::ConditionResult::id, + 55), + Property(&external::ElementConditionsUpdate::ConditionResult:: + satisfied, + true)))))); + task_env_.FastForwardBy(base::Seconds(1)); + + // We keep the same state as the last roundtrip. + EXPECT_CALL(mock_web_controller_, + FindElement(Selector({"changing_condition"}), _, _)) + .WillOnce(WithArgs<2>([](auto&& callback) { + std::move(callback).Run(OkClientStatus(), + std::make_unique<ElementFinderResult>()); + })); + EXPECT_CALL(mock_web_controller_, + FindElement(Selector({"unchanging_condition"}), _, _)) + .WillOnce(WithArgs<2>([](auto&& callback) { + std::move(callback).Run(ClientStatus(ELEMENT_RESOLUTION_FAILED), + std::make_unique<ElementFinderResult>()); + })); + // Since there were no changes, no notification is sent. + EXPECT_CALL(dom_update_callback, Run(_)).Times(0); + task_env_.FastForwardBy(base::Seconds(1)); +} + +TEST_F(ExternalActionTest, WaitForDomFailure) { + proto_.mutable_info(); + proto_.set_allow_interrupt(true); + + EXPECT_CALL(mock_action_delegate_, RequestExternalAction) + .WillOnce( + [](const ExternalActionProto& external_action, + base::OnceCallback<void(ExternalActionDelegate::DomUpdateCallback)> + start_dom_checks_callback, + base::OnceCallback<void(const external::Result& result)> + end_action_callback) { + std::move(start_dom_checks_callback).Run(base::DoNothing()); + std::move(end_action_callback).Run(MakeResult(/* success= */ true)); + }); + + // Even if the external action ended in a success, if the WaitForDom ends in + // an error we expect the error to be reported. + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, INTERRUPT_FAILED)))); + Run(); + wait_for_dom_status_ = ClientStatus(INTERRUPT_FAILED); + // The action should end at the next WaitForDom notification. + task_env_.FastForwardBy(base::Seconds(1)); +} + +} // namespace +} // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/actions/fallback_handler/required_fields_fallback_handler.cc b/chromium/components/autofill_assistant/browser/actions/fallback_handler/required_fields_fallback_handler.cc index f2d1a438e00..a489f10de90 100644 --- a/chromium/components/autofill_assistant/browser/actions/fallback_handler/required_fields_fallback_handler.cc +++ b/chromium/components/autofill_assistant/browser/actions/fallback_handler/required_fields_fallback_handler.cc @@ -17,7 +17,7 @@ #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/field_formatter.h" #include "components/autofill_assistant/browser/web/element_action_util.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/web_controller.h" #include "third_party/re2/src/re2/re2.h" diff --git a/chromium/components/autofill_assistant/browser/actions/fallback_handler/required_fields_fallback_handler.h b/chromium/components/autofill_assistant/browser/actions/fallback_handler/required_fields_fallback_handler.h index a109f926868..b9cd534da4e 100644 --- a/chromium/components/autofill_assistant/browser/actions/fallback_handler/required_fields_fallback_handler.h +++ b/chromium/components/autofill_assistant/browser/actions/fallback_handler/required_fields_fallback_handler.h @@ -18,10 +18,10 @@ #include "components/autofill_assistant/browser/actions/fallback_handler/required_field.h" #include "components/autofill_assistant/browser/batch_element_checker.h" #include "components/autofill_assistant/browser/field_formatter.h" -#include "components/autofill_assistant/browser/web/element_finder.h" namespace autofill_assistant { class ClientStatus; +class ElementFinderResult; // A handler for required fields and fallback values, used by UseAddressAction // and UseCreditCardAction. diff --git a/chromium/components/autofill_assistant/browser/actions/fallback_handler/required_fields_fallback_handler_unittest.cc b/chromium/components/autofill_assistant/browser/actions/fallback_handler/required_fields_fallback_handler_unittest.cc index 755ab627521..67174c2b648 100644 --- a/chromium/components/autofill_assistant/browser/actions/fallback_handler/required_fields_fallback_handler_unittest.cc +++ b/chromium/components/autofill_assistant/browser/actions/fallback_handler/required_fields_fallback_handler_unittest.cc @@ -18,7 +18,7 @@ #include "components/autofill_assistant/browser/actions/mock_action_delegate.h" #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/service.pb.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/mock_web_controller.h" #include "testing/gmock/include/gmock/gmock.h" diff --git a/chromium/components/autofill_assistant/browser/actions/get_element_status_action.cc b/chromium/components/autofill_assistant/browser/actions/get_element_status_action.cc index 15d076fd1e6..408c02df978 100644 --- a/chromium/components/autofill_assistant/browser/actions/get_element_status_action.cc +++ b/chromium/components/autofill_assistant/browser/actions/get_element_status_action.cc @@ -13,6 +13,7 @@ #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/service.pb.h" #include "components/autofill_assistant/browser/user_data_util.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/element_store.h" #include "components/autofill_assistant/browser/web/web_controller.h" #include "third_party/re2/src/re2/re2.h" @@ -27,7 +28,7 @@ struct MaybeRe2 { std::string RemoveWhitespace(const std::string& value) { std::string copy = value; - base::EraseIf(copy, base::IsUnicodeWhitespace); + base::EraseIf(copy, base::IsUnicodeWhitespace<char>); return copy; } diff --git a/chromium/components/autofill_assistant/browser/actions/get_element_status_action.h b/chromium/components/autofill_assistant/browser/actions/get_element_status_action.h index c5dfa40f316..422cdc2f910 100644 --- a/chromium/components/autofill_assistant/browser/actions/get_element_status_action.h +++ b/chromium/components/autofill_assistant/browser/actions/get_element_status_action.h @@ -11,9 +11,9 @@ #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/dom_action.pb.h" #include "components/autofill_assistant/browser/service.pb.h" -#include "components/autofill_assistant/browser/web/element_finder.h" namespace autofill_assistant { +class ElementFinderResult; // Action to get an element's status. class GetElementStatusAction : public Action { diff --git a/chromium/components/autofill_assistant/browser/actions/get_element_status_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/get_element_status_action_unittest.cc index 66a282680bd..4fdd67b0dce 100644 --- a/chromium/components/autofill_assistant/browser/actions/get_element_status_action_unittest.cc +++ b/chromium/components/autofill_assistant/browser/actions/get_element_status_action_unittest.cc @@ -18,6 +18,7 @@ #include "components/autofill_assistant/browser/selector.h" #include "components/autofill_assistant/browser/service.pb.h" #include "components/autofill_assistant/browser/user_model.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/element_store.h" #include "components/autofill_assistant/browser/web/mock_web_controller.h" #include "content/public/test/browser_task_environment.h" @@ -704,7 +705,7 @@ TEST_F(GetElementStatusActionTest, SucceedsWithPasswordManagerValue) { .WillOnce(WithArgs<1>([this](auto&& callback) { std::unique_ptr<ElementFinderResult> element = std::make_unique<ElementFinderResult>(); - element->SetRenderFrameHost(web_contents_->GetMainFrame()); + element->SetRenderFrameHost(web_contents_->GetPrimaryMainFrame()); std::move(callback).Run(OkClientStatus(), std::move(element)); })); EXPECT_CALL(mock_website_login_manager_, GetPasswordForLogin(_, _)) diff --git a/chromium/components/autofill_assistant/browser/actions/js_flow_action.cc b/chromium/components/autofill_assistant/browser/actions/js_flow_action.cc index 5eb7e1b7f61..5f0825f5c7e 100644 --- a/chromium/components/autofill_assistant/browser/actions/js_flow_action.cc +++ b/chromium/components/autofill_assistant/browser/actions/js_flow_action.cc @@ -26,9 +26,9 @@ const char kJsFlowActionEnabledGroup[] = "Enabled"; JsFlowAction::JsFlowAction(ActionDelegate* delegate, const ActionProto& proto) : Action(delegate, proto), - js_flow_executor_( - std::make_unique<JsFlowExecutorImpl>(delegate->GetWebContents(), - this)) { + js_flow_executor_(std::make_unique<JsFlowExecutorImpl>( + /* delegate= */ this, + delegate->GetJsFlowDevtoolsWrapper())) { DCHECK(proto_.has_js_flow()); } @@ -90,6 +90,8 @@ void JsFlowAction::OnNativeActionFinished( current_native_action_.reset(); + delegate_->MaybeSetPreviousAction(*processed_action); + std::move(finished_callback) .Run(ClientStatus(processed_action->status(), processed_action->status_details()), @@ -109,6 +111,11 @@ void JsFlowAction::InternalProcessAction(ProcessActionCallback callback) { void JsFlowAction::OnFlowFinished(ProcessActionCallback callback, const ClientStatus& status, std::unique_ptr<base::Value> return_value) { + // Since we can not know in advance how many native actions need to be run + // we will create a dangling promise. By destroying the flow executor we make + // sure that these will not be executed. + js_flow_executor_.reset(nullptr); + UpdateProcessedAction(status); // If the flow returned a value, we extract the status and possibly a flow diff --git a/chromium/components/autofill_assistant/browser/actions/js_flow_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/js_flow_action_unittest.cc index a39302e577f..0811cf9e226 100644 --- a/chromium/components/autofill_assistant/browser/actions/js_flow_action_unittest.cc +++ b/chromium/components/autofill_assistant/browser/actions/js_flow_action_unittest.cc @@ -363,4 +363,48 @@ TEST_F(JsFlowActionTest, NativeActionReturnsActionResult) { action->ProcessAction(callback_.Get()); } +TEST_F(JsFlowActionTest, NativeActionReturnsAutofillErrorInfo) { + auto mock_js_flow_executor = std::make_unique<MockJsFlowExecutor>(); + auto* mock_js_flow_executor_ptr = mock_js_flow_executor.get(); + auto action = CreateAction(std::move(mock_js_flow_executor)); + + EXPECT_CALL(*mock_js_flow_executor_ptr, Start) + .WillOnce(WithArg<1>([&](auto finished_callback) { + ActionProto native_action; + native_action.mutable_wait_for_dom()->mutable_wait_condition(); + + action->RunNativeAction( + /* action_id = */ static_cast<int>( + native_action.action_info_case()), + /* action = */ + native_action.wait_for_dom().SerializeAsString(), + native_action_callback_.Get()); + + std::move(finished_callback).Run(ClientStatus(ACTION_APPLIED), nullptr); + })); + + AutofillErrorInfoProto autofill_error_info; + autofill_error_info.set_client_memory_address_key_names("key_names"); + std::string autofill_error_info_base64; + base::Base64Encode(autofill_error_info.SerializeAsString(), + &autofill_error_info_base64); + + EXPECT_CALL(mock_action_delegate_, WaitForDomWithSlowWarning) + .WillOnce(WithArg<4>([&](auto dom_finished_callback) { + *GetInnerProcessedAction(*action) + ->mutable_status_details() + ->mutable_autofill_error_info() = autofill_error_info; + std::move(dom_finished_callback) + .Run(ClientStatus(ACTION_APPLIED), base::Seconds(0)); + })); + + EXPECT_CALL(native_action_callback_, Run(_, Pointee(IsJson(R"( + { + "navigationStarted": false, + "autofillErrorInfo": ")" + autofill_error_info_base64 + + "\"}")))); + + action->ProcessAction(callback_.Get()); +} + } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/actions/mock_action_delegate.h b/chromium/components/autofill_assistant/browser/actions/mock_action_delegate.h index a411caa93e5..1089b688aa8 100644 --- a/chromium/components/autofill_assistant/browser/actions/mock_action_delegate.h +++ b/chromium/components/autofill_assistant/browser/actions/mock_action_delegate.h @@ -31,6 +31,7 @@ class PasswordChangeSuccessTracker; } namespace autofill_assistant { +class ElementFinderResult; class UserModel; class MockActionDelegate : public ActionDelegate { @@ -95,7 +96,7 @@ class MockActionDelegate : public ActionDelegate { base::OnceCallback<void()> end_on_navigation_callback, bool browse_mode, bool browse_mode_invisible)); - MOCK_METHOD0(CleanUpAfterPrompt, void()); + MOCK_METHOD1(CleanUpAfterPrompt, void(bool)); MOCK_METHOD1(SetBrowseDomainsAllowlist, void(std::vector<std::string> domains)); MOCK_METHOD2( @@ -134,6 +135,10 @@ class MockActionDelegate : public ActionDelegate { MOCK_CONST_METHOD0(GetPasswordChangeSuccessTracker, password_manager::PasswordChangeSuccessTracker*()); MOCK_CONST_METHOD0(GetWebContents, content::WebContents*()); + MOCK_METHOD(JsFlowDevtoolsWrapper*, + GetJsFlowDevtoolsWrapper, + (), + (const override)); MOCK_CONST_METHOD0(GetWebController, WebController*()); MOCK_CONST_METHOD0(GetEmailAddressForAccessTokenAccount, std::string()); MOCK_CONST_METHOD0(GetUkmRecorder, ukm::UkmRecorder*()); @@ -207,11 +212,23 @@ class MockActionDelegate : public ActionDelegate { MOCK_METHOD0(MaybeShowSlowConnectionWarning, void()); MOCK_METHOD0(GetLogInfo, ProcessedActionStatusDetailsProto&()); MOCK_CONST_METHOD0(GetElementStore, ElementStore*()); - MOCK_METHOD2( + MOCK_METHOD3( RequestUserData, - void(const CollectUserDataOptions& options, + void(UserDataEventField event_field, + const CollectUserDataOptions& options, base::OnceCallback<void(bool, const GetUserDataResponseProto&)> callback)); + MOCK_METHOD0(SupportsExternalActions, bool()); + MOCK_METHOD3( + RequestExternalAction, + void(const ExternalActionProto& external_action, + base::OnceCallback<void(ExternalActionDelegate::DomUpdateCallback)> + start_dom_checks_callback, + base::OnceCallback<void(const external::Result& result)> + end_action_callback)); + MOCK_CONST_METHOD0(MustUseBackendData, bool()); + MOCK_METHOD1(MaybeSetPreviousAction, + void(const ProcessedActionProto& processed_action)); base::WeakPtr<ActionDelegate> GetWeakPtr() const override { return weak_ptr_factory_.GetWeakPtr(); diff --git a/chromium/components/autofill_assistant/browser/actions/perform_on_single_element_action.h b/chromium/components/autofill_assistant/browser/actions/perform_on_single_element_action.h index bd96382a777..9ddc810069b 100644 --- a/chromium/components/autofill_assistant/browser/actions/perform_on_single_element_action.h +++ b/chromium/components/autofill_assistant/browser/actions/perform_on_single_element_action.h @@ -11,7 +11,7 @@ #include "components/autofill_assistant/browser/actions/action.h" #include "components/autofill_assistant/browser/actions/action_delegate.h" #include "components/autofill_assistant/browser/dom_action.pb.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" namespace autofill_assistant { diff --git a/chromium/components/autofill_assistant/browser/actions/perform_on_single_element_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/perform_on_single_element_action_unittest.cc index 6d843548a0a..2b985b6b378 100644 --- a/chromium/components/autofill_assistant/browser/actions/perform_on_single_element_action_unittest.cc +++ b/chromium/components/autofill_assistant/browser/actions/perform_on_single_element_action_unittest.cc @@ -11,7 +11,7 @@ #include "components/autofill_assistant/browser/actions/mock_action_delegate.h" #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/dom_action.pb.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/element_store.h" #include "testing/gmock/include/gmock/gmock.h" diff --git a/chromium/components/autofill_assistant/browser/actions/prompt_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/prompt_action_unittest.cc index cb6b74c59aa..9a7869f9a5b 100644 --- a/chromium/components/autofill_assistant/browser/actions/prompt_action_unittest.cc +++ b/chromium/components/autofill_assistant/browser/actions/prompt_action_unittest.cc @@ -16,7 +16,7 @@ #include "base/test/test_simple_task_runner.h" #include "base/time/time.h" #include "base/timer/timer.h" -#include "components/autofill_assistant/browser/actions/mock_action_delegate.h" +#include "components/autofill_assistant/browser/actions/wait_for_dom_test_base.h" #include "components/autofill_assistant/browser/wait_for_dom_observer.h" #include "components/autofill_assistant/browser/web/mock_web_controller.h" #include "testing/gmock/include/gmock/gmock.h" @@ -27,6 +27,7 @@ namespace { using ::base::test::RunOnceCallback; using ::testing::_; using ::testing::ElementsAre; +using ::testing::Eq; using ::testing::Invoke; using ::testing::IsEmpty; using ::testing::IsNull; @@ -38,20 +39,12 @@ using ::testing::StrEq; using ::testing::UnorderedElementsAre; using ::testing::WithArgs; -class PromptActionTest : public testing::Test { +class PromptActionTest : public WaitForDomTestBase { public: - PromptActionTest() - : task_env_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} + PromptActionTest() = default; void SetUp() override { - ON_CALL(mock_web_controller_, FindElement(_, _, _)) - .WillByDefault(WithArgs<2>([](auto&& callback) { - std::move(callback).Run(ClientStatus(ELEMENT_RESOLUTION_FAILED), - std::make_unique<ElementFinderResult>()); - })); - EXPECT_CALL(mock_action_delegate_, WaitForDom) - .WillRepeatedly(Invoke(this, &PromptActionTest::FakeWaitForDom)); - ON_CALL(mock_action_delegate_, Prompt(_, _, _, _, _)) + ON_CALL(mock_action_delegate_, Prompt) .WillByDefault( [this](std::unique_ptr<std::vector<UserAction>> user_actions, bool disable_force_expand_sheet, @@ -63,92 +56,10 @@ class PromptActionTest : public testing::Test { } protected: - // Fakes ActionDelegate::WaitForDom. - // - // This simulates a WaitForDom that calls |check_elements_| every seconds - // until it gets a successful callback, then calls done_waiting_callback. - void FakeWaitForDom( - base::TimeDelta max_wait_time, - bool allow_observer_mode, - bool allow_interrupt, - WaitForDomObserver* observer, - base::RepeatingCallback< - void(BatchElementChecker*, - base::OnceCallback<void(const ClientStatus&)>)> check_elements, - base::OnceCallback<void(const ClientStatus&, base::TimeDelta)> - done_waiting_callback) { - fake_wait_for_dom_done_ = std::move(done_waiting_callback); - RunFakeWaitForDom(check_elements); - } - - void RunFakeWaitForDom( - base::RepeatingCallback< - void(BatchElementChecker*, - base::OnceCallback<void(const ClientStatus&)>)> check_elements) { - if (!fake_wait_for_dom_done_) - return; - - checker_ = std::make_unique<BatchElementChecker>(); - has_check_elements_result_ = false; - check_elements.Run(checker_.get(), - base::BindOnce(&PromptActionTest::OnCheckElementsDone, - base::Unretained(this))); - task_env_.FastForwardBy(base::Milliseconds(fake_check_time_)); - checker_->AddAllDoneCallback( - base::BindOnce(&PromptActionTest::OnWaitForDomDone, - base::Unretained(this), check_elements)); - checker_->Run(&mock_web_controller_); - } - - // Called from the check_elements callback passed to FakeWaitForDom. - void OnCheckElementsDone(const ClientStatus& result) { - ASSERT_FALSE(has_check_elements_result_); // Duplicate calls - has_check_elements_result_ = true; - check_elements_result_ = result; - } - - // Called by |checker_| once it's done and either ends the WaitForDom or - // schedule another run. - void OnWaitForDomDone( - base::RepeatingCallback< - void(BatchElementChecker*, - base::OnceCallback<void(const ClientStatus&)>)> check_elements) { - ASSERT_TRUE( - has_check_elements_result_); // OnCheckElementsDone() not called - - if (!fake_wait_for_dom_done_) - return; - - if (check_elements_result_.ok()) { - std::move(fake_wait_for_dom_done_) - .Run(check_elements_result_, base::Milliseconds(fake_wait_time_)); - } else { - wait_for_dom_timer_ = std::make_unique<base::OneShotTimer>(); - wait_for_dom_timer_->Start( - FROM_HERE, base::Seconds(1), - base::BindOnce(&PromptActionTest::RunFakeWaitForDom, - base::Unretained(this), check_elements)); - } - } - - // task_env_ must be first to guarantee other field - // creation run in that environment. - base::test::TaskEnvironment task_env_; - - MockActionDelegate mock_action_delegate_; - MockWebController mock_web_controller_; base::MockCallback<Action::ProcessActionCallback> callback_; - base::OnceCallback<void(const ClientStatus&, base::TimeDelta)> - fake_wait_for_dom_done_; ActionProto proto_; raw_ptr<PromptProto> prompt_proto_; std::unique_ptr<std::vector<UserAction>> user_actions_; - std::unique_ptr<BatchElementChecker> checker_; - bool has_check_elements_result_ = false; - ClientStatus check_elements_result_; - std::unique_ptr<base::OneShotTimer> wait_for_dom_timer_; - int fake_wait_time_ = 0; - int fake_check_time_ = 0; }; TEST_F(PromptActionTest, ChoicesMissing) { @@ -312,7 +223,7 @@ TEST_F(PromptActionTest, AutoSelectWhenElementExists) { std::make_unique<ElementFinderResult>()); })); - EXPECT_CALL(mock_action_delegate_, CleanUpAfterPrompt()); + EXPECT_CALL(mock_action_delegate_, CleanUpAfterPrompt(Eq(true))); EXPECT_CALL( callback_, Run(Pointee(AllOf(Property(&ProcessedActionProto::status, ACTION_APPLIED), @@ -339,7 +250,7 @@ TEST_F(PromptActionTest, TimingStatsAutoSelect) { std::make_unique<ElementFinderResult>()); })); - EXPECT_CALL(mock_action_delegate_, CleanUpAfterPrompt()); + EXPECT_CALL(mock_action_delegate_, CleanUpAfterPrompt(Eq(true))); ProcessedActionProto capture; EXPECT_CALL(callback_, Run(_)).WillOnce(SaveArgPointee<0>(&capture)); task_env_.FastForwardBy(base::Seconds(1)); @@ -523,7 +434,7 @@ TEST_F(PromptActionTest, EndActionOnNavigation) { PromptAction action(&mock_action_delegate_, proto_); // Set new expectations for when the navigation event arrives. - EXPECT_CALL(mock_action_delegate_, CleanUpAfterPrompt()); + EXPECT_CALL(mock_action_delegate_, CleanUpAfterPrompt(Eq(true))); EXPECT_CALL( callback_, Run(Pointee(AllOf( @@ -553,7 +464,7 @@ TEST_F(PromptActionTest, TimingStatsEndActionOnNavigation) { PromptAction action(&mock_action_delegate_, proto_); // Set new expectations for when the navigation event arrives. - EXPECT_CALL(mock_action_delegate_, CleanUpAfterPrompt()); + EXPECT_CALL(mock_action_delegate_, CleanUpAfterPrompt(Eq(true))); ProcessedActionProto capture; EXPECT_CALL(callback_, Run(_)).WillOnce(SaveArgPointee<0>(&capture)); action.ProcessAction(callback_.Get()); diff --git a/chromium/components/autofill_assistant/browser/actions/register_password_reset_request_action.cc b/chromium/components/autofill_assistant/browser/actions/register_password_reset_request_action.cc new file mode 100644 index 00000000000..ef4d636edc3 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/actions/register_password_reset_request_action.cc @@ -0,0 +1,49 @@ +// Copyright 2022 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_assistant/browser/actions/register_password_reset_request_action.h" + +#include "components/autofill_assistant/browser/actions/action_delegate.h" +#include "components/autofill_assistant/browser/client_status.h" +#include "components/autofill_assistant/browser/user_data.h" +#include "components/password_manager/core/browser/password_change_success_tracker.h" + +namespace autofill_assistant { + +RegisterPasswordResetRequestAction::RegisterPasswordResetRequestAction( + ActionDelegate* delegate, + const ActionProto& proto) + : Action(delegate, proto) { + DCHECK(proto_.has_register_password_reset_request()); +} + +RegisterPasswordResetRequestAction::~RegisterPasswordResetRequestAction() = + default; + +void RegisterPasswordResetRequestAction::InternalProcessAction( + ProcessActionCallback callback) { + callback_ = std::move(callback); + + if (!delegate_->GetUserData()->selected_login_) { + VLOG(1) << "RegisterPasswordResetRequestAction: requested login details " + "not available in client memory."; + EndAction(ClientStatus(PRECONDITION_FAILED)); + return; + } + + delegate_->GetPasswordChangeSuccessTracker()->OnChangePasswordFlowModified( + delegate_->GetUserData()->selected_login_->origin, + delegate_->GetUserData()->selected_login_->username, + password_manager::PasswordChangeSuccessTracker::StartEvent:: + kManualResetLinkFlow); + + EndAction(ClientStatus(ACTION_APPLIED)); +} + +void RegisterPasswordResetRequestAction::EndAction(const ClientStatus& status) { + UpdateProcessedAction(status); + std::move(callback_).Run(std::move(processed_action_proto_)); +} + +} // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/actions/register_password_reset_request_action.h b/chromium/components/autofill_assistant/browser/actions/register_password_reset_request_action.h new file mode 100644 index 00000000000..5c94b0f2138 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/actions/register_password_reset_request_action.h @@ -0,0 +1,35 @@ +// Copyright 2022 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_ASSISTANT_BROWSER_ACTIONS_REGISTER_PASSWORD_RESET_REQUEST_ACTION_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_REGISTER_PASSWORD_RESET_REQUEST_ACTION_H_ + +#include "components/autofill_assistant/browser/actions/action.h" + +namespace autofill_assistant { + +// Action to notify the password change success tracker that a password reset +// has been requested. +class RegisterPasswordResetRequestAction : public Action { + public: + explicit RegisterPasswordResetRequestAction(ActionDelegate* delegate, + const ActionProto& proto); + ~RegisterPasswordResetRequestAction() override; + + RegisterPasswordResetRequestAction( + const RegisterPasswordResetRequestAction&) = delete; + RegisterPasswordResetRequestAction& operator=( + const RegisterPasswordResetRequestAction&) = delete; + + private: + // Overrides Action: + void InternalProcessAction(ProcessActionCallback callback) override; + + void EndAction(const ClientStatus& status); + + ProcessActionCallback callback_; +}; + +} // namespace autofill_assistant +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_REGISTER_PASSWORD_RESET_REQUEST_ACTION_H_ diff --git a/chromium/components/autofill_assistant/browser/actions/register_password_reset_request_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/register_password_reset_request_action_unittest.cc new file mode 100644 index 00000000000..e6ec84b9ee9 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/actions/register_password_reset_request_action_unittest.cc @@ -0,0 +1,78 @@ +// Copyright 2022 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_assistant/browser/actions/register_password_reset_request_action.h" + +#include <string> + +#include "base/test/mock_callback.h" +#include "components/autofill_assistant/browser/actions/mock_action_delegate.h" +#include "components/autofill_assistant/browser/client_status.h" +#include "components/password_manager/core/browser/mock_password_change_success_tracker.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace autofill_assistant { +namespace { + +using password_manager::MockPasswordChangeSuccessTracker; +using password_manager::PasswordChangeSuccessTracker; +using ::testing::Pointee; +using ::testing::Property; +using ::testing::Return; +using ::testing::StrictMock; + +const char kOrigin[] = "https://example.com"; +const char kUsername[] = "username"; + +class RegisterPasswordResetRequestActionTest : public testing::Test { + public: + void SetUp() override { + ON_CALL(mock_action_delegate_, GetPasswordChangeSuccessTracker) + .WillByDefault(Return(&mock_password_change_success_tracker_)); + + ON_CALL(mock_action_delegate_, GetUserData) + .WillByDefault(Return(&user_data_)); + } + + protected: + void Run() { + proto_.mutable_register_password_reset_request(); + RegisterPasswordResetRequestAction action(&mock_action_delegate_, proto_); + action.ProcessAction(callback_.Get()); + } + + ActionProto proto_; + MockActionDelegate mock_action_delegate_; + StrictMock<MockPasswordChangeSuccessTracker> + mock_password_change_success_tracker_; + base::MockCallback<Action::ProcessActionCallback> callback_; + UserData user_data_; +}; + +TEST_F(RegisterPasswordResetRequestActionTest, RegisterResetSuccess) { + user_data_.selected_login_.emplace(GURL(kOrigin), kUsername); + + EXPECT_CALL( + mock_password_change_success_tracker_, + OnChangePasswordFlowModified( + GURL(kOrigin), kUsername, + PasswordChangeSuccessTracker::StartEvent::kManualResetLinkFlow)); + + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED)))); + + Run(); +} + +TEST_F(RegisterPasswordResetRequestActionTest, UserDataSelectLoginNotSet) { + // Leave user_data_.select_login_ empty. + EXPECT_CALL(callback_, Run(Pointee(Property(&ProcessedActionProto::status, + PRECONDITION_FAILED)))); + + Run(); +} + +} // namespace +} // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/actions/save_generated_password_action.cc b/chromium/components/autofill_assistant/browser/actions/save_generated_password_action.cc index 772bce77b0c..021f79571e2 100644 --- a/chromium/components/autofill_assistant/browser/actions/save_generated_password_action.cc +++ b/chromium/components/autofill_assistant/browser/actions/save_generated_password_action.cc @@ -61,7 +61,8 @@ void SaveGeneratedPasswordAction::InternalProcessAction( delegate_->GetPasswordChangeSuccessTracker()->OnChangePasswordFlowCompleted( delegate_->GetUserData()->selected_login_->origin, delegate_->GetUserData()->selected_login_->username, - PasswordChangeSuccessTracker::EndEvent::kAutomatedGeneratedPasswordFlow); + PasswordChangeSuccessTracker::EndEvent:: + kAutomatedFlowGeneratedPasswordChosen); EndAction(ClientStatus(ACTION_APPLIED)); } diff --git a/chromium/components/autofill_assistant/browser/actions/save_generated_password_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/save_generated_password_action_unittest.cc index ef69b05f8a7..55157ef2481 100644 --- a/chromium/components/autofill_assistant/browser/actions/save_generated_password_action_unittest.cc +++ b/chromium/components/autofill_assistant/browser/actions/save_generated_password_action_unittest.cc @@ -82,7 +82,7 @@ TEST_F(SaveGeneratedPasswordActionTest, SavedPassword) { mock_password_change_success_tracker_, OnChangePasswordFlowCompleted(GURL(kOrigin), kUsername, PasswordChangeSuccessTracker::EndEvent:: - kAutomatedGeneratedPasswordFlow)); + kAutomatedFlowGeneratedPasswordChosen)); action.ProcessAction(callback_.Get()); diff --git a/chromium/components/autofill_assistant/browser/actions/save_submitted_password_action.cc b/chromium/components/autofill_assistant/browser/actions/save_submitted_password_action.cc index 6d7b0654471..81381d7ed95 100644 --- a/chromium/components/autofill_assistant/browser/actions/save_submitted_password_action.cc +++ b/chromium/components/autofill_assistant/browser/actions/save_submitted_password_action.cc @@ -52,7 +52,8 @@ void SaveSubmittedPasswordAction::InternalProcessAction( delegate_->GetPasswordChangeSuccessTracker()->OnChangePasswordFlowCompleted( delegate_->GetUserData()->selected_login_->origin, delegate_->GetUserData()->selected_login_->username, - PasswordChangeSuccessTracker::EndEvent::kAutomatedOwnPasswordFlow); + PasswordChangeSuccessTracker::EndEvent:: + kAutomatedFlowOwnPasswordChosen); } // If a timeout is specified, perform a leak check. diff --git a/chromium/components/autofill_assistant/browser/actions/save_submitted_password_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/save_submitted_password_action_unittest.cc index edb13113d77..a1a7a481c59 100644 --- a/chromium/components/autofill_assistant/browser/actions/save_submitted_password_action_unittest.cc +++ b/chromium/components/autofill_assistant/browser/actions/save_submitted_password_action_unittest.cc @@ -71,9 +71,9 @@ TEST_F(SaveSubmittedPasswordActionTest, SaveSubmittedPasswordSuccess) { EXPECT_CALL(mock_website_login_manager_, SaveSubmittedPassword); EXPECT_CALL( mock_password_change_success_tracker_, - OnChangePasswordFlowCompleted( - GURL(kOrigin), kUsername, - PasswordChangeSuccessTracker::EndEvent::kAutomatedOwnPasswordFlow)); + OnChangePasswordFlowCompleted(GURL(kOrigin), kUsername, + PasswordChangeSuccessTracker::EndEvent:: + kAutomatedFlowOwnPasswordChosen)); // Check for leaked credentials. EXPECT_CALL(mock_website_login_manager_, @@ -143,9 +143,9 @@ TEST_F(SaveSubmittedPasswordActionTest, SaveLeakedNewSubmittedPassword) { EXPECT_CALL(mock_website_login_manager_, SaveSubmittedPassword); EXPECT_CALL( mock_password_change_success_tracker_, - OnChangePasswordFlowCompleted( - GURL(kOrigin), kUsername, - PasswordChangeSuccessTracker::EndEvent::kAutomatedOwnPasswordFlow)); + OnChangePasswordFlowCompleted(GURL(kOrigin), kUsername, + PasswordChangeSuccessTracker::EndEvent:: + kAutomatedFlowOwnPasswordChosen)); // Check for leaked credentials. EXPECT_CALL(mock_website_login_manager_, @@ -180,9 +180,9 @@ TEST_F(SaveSubmittedPasswordActionTest, SaveSubmittedPasswordLeakError) { EXPECT_CALL(mock_website_login_manager_, SaveSubmittedPassword); EXPECT_CALL( mock_password_change_success_tracker_, - OnChangePasswordFlowCompleted( - GURL(kOrigin), kUsername, - PasswordChangeSuccessTracker::EndEvent::kAutomatedOwnPasswordFlow)); + OnChangePasswordFlowCompleted(GURL(kOrigin), kUsername, + PasswordChangeSuccessTracker::EndEvent:: + kAutomatedFlowOwnPasswordChosen)); // Check for leaked credentials. EXPECT_CALL(mock_website_login_manager_, @@ -221,9 +221,9 @@ TEST_F(SaveSubmittedPasswordActionTest, EXPECT_CALL(mock_website_login_manager_, SaveSubmittedPassword); EXPECT_CALL( mock_password_change_success_tracker_, - OnChangePasswordFlowCompleted( - GURL(kOrigin), kUsername, - PasswordChangeSuccessTracker::EndEvent::kAutomatedOwnPasswordFlow)); + OnChangePasswordFlowCompleted(GURL(kOrigin), kUsername, + PasswordChangeSuccessTracker::EndEvent:: + kAutomatedFlowOwnPasswordChosen)); // Since no timeout was submitted in the action, no leak check is performed. EXPECT_CALL(mock_website_login_manager_, @@ -271,9 +271,9 @@ TEST_F(SaveSubmittedPasswordActionTest, EXPECT_CALL(mock_website_login_manager_, SaveSubmittedPassword); EXPECT_CALL( mock_password_change_success_tracker_, - OnChangePasswordFlowCompleted( - GURL(kOrigin), kUsername, - PasswordChangeSuccessTracker::EndEvent::kAutomatedOwnPasswordFlow)); + OnChangePasswordFlowCompleted(GURL(kOrigin), kUsername, + PasswordChangeSuccessTracker::EndEvent:: + kAutomatedFlowOwnPasswordChosen)); // Since no timeout was submitted in the action, no leak check is performed. EXPECT_CALL(mock_website_login_manager_, diff --git a/chromium/components/autofill_assistant/browser/actions/select_option_action.cc b/chromium/components/autofill_assistant/browser/actions/select_option_action.cc index b829a05e367..bb08268c0df 100644 --- a/chromium/components/autofill_assistant/browser/actions/select_option_action.cc +++ b/chromium/components/autofill_assistant/browser/actions/select_option_action.cc @@ -13,6 +13,7 @@ #include "components/autofill_assistant/browser/actions/action_delegate_util.h" #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/user_data_util.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/web_controller.h" namespace autofill_assistant { diff --git a/chromium/components/autofill_assistant/browser/actions/select_option_action.h b/chromium/components/autofill_assistant/browser/actions/select_option_action.h index 62b4f299681..ef0af3251b3 100644 --- a/chromium/components/autofill_assistant/browser/actions/select_option_action.h +++ b/chromium/components/autofill_assistant/browser/actions/select_option_action.h @@ -11,7 +11,6 @@ #include "components/autofill_assistant/browser/actions/action.h" #include "components/autofill_assistant/browser/actions/action_delegate.h" #include "components/autofill_assistant/browser/client_status.h" -#include "components/autofill_assistant/browser/web/element_finder.h" namespace autofill_assistant { diff --git a/chromium/components/autofill_assistant/browser/actions/send_keystroke_events_action.h b/chromium/components/autofill_assistant/browser/actions/send_keystroke_events_action.h index 728dbc6494b..b4562f1c7c0 100644 --- a/chromium/components/autofill_assistant/browser/actions/send_keystroke_events_action.h +++ b/chromium/components/autofill_assistant/browser/actions/send_keystroke_events_action.h @@ -9,7 +9,8 @@ #include "base/memory/weak_ptr.h" #include "components/autofill_assistant/browser/actions/action.h" #include "components/autofill_assistant/browser/actions/action_delegate.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" +#include "third_party/abseil-cpp/absl/types/optional.h" namespace autofill_assistant { diff --git a/chromium/components/autofill_assistant/browser/actions/send_keystroke_events_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/send_keystroke_events_action_unittest.cc index 0a890b757d8..f41c3ff1535 100644 --- a/chromium/components/autofill_assistant/browser/actions/send_keystroke_events_action_unittest.cc +++ b/chromium/components/autofill_assistant/browser/actions/send_keystroke_events_action_unittest.cc @@ -13,7 +13,7 @@ #include "components/autofill_assistant/browser/dom_action.pb.h" #include "components/autofill_assistant/browser/mock_website_login_manager.h" #include "components/autofill_assistant/browser/value_util.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/element_store.h" #include "components/autofill_assistant/browser/web/mock_web_controller.h" #include "content/public/test/browser_task_environment.h" @@ -120,7 +120,7 @@ TEST_F(SendKeystrokeEventsActionTest, PasswordTextValueReturnLastTimeUsed) { content::WebContentsTester::For(web_contents_.get()) ->NavigateAndCommit(GURL(kUrl)); element.SetObjectId("id"); - element.SetRenderFrameHost(web_contents_->GetMainFrame()); + element.SetRenderFrameHost(web_contents_->GetPrimaryMainFrame()); mock_action_delegate_.GetElementStore()->AddElement("e", element.dom_object()); @@ -161,7 +161,7 @@ TEST_F(SendKeystrokeEventsActionTest, content::WebContentsTester::For(web_contents_.get()) ->NavigateAndCommit(GURL(kUrl)); element.SetObjectId("id"); - element.SetRenderFrameHost(web_contents_->GetMainFrame()); + element.SetRenderFrameHost(web_contents_->GetPrimaryMainFrame()); mock_action_delegate_.GetElementStore()->AddElement("e", element.dom_object()); diff --git a/chromium/components/autofill_assistant/browser/actions/set_attribute_action.cc b/chromium/components/autofill_assistant/browser/actions/set_attribute_action.cc index 51ef3ecde9d..b2394af4e79 100644 --- a/chromium/components/autofill_assistant/browser/actions/set_attribute_action.cc +++ b/chromium/components/autofill_assistant/browser/actions/set_attribute_action.cc @@ -11,7 +11,6 @@ #include "components/autofill_assistant/browser/actions/action_delegate.h" #include "components/autofill_assistant/browser/actions/action_delegate_util.h" #include "components/autofill_assistant/browser/client_status.h" -#include "components/autofill_assistant/browser/web/element_finder.h" #include "components/autofill_assistant/browser/web/web_controller.h" namespace autofill_assistant { diff --git a/chromium/components/autofill_assistant/browser/actions/set_attribute_action.h b/chromium/components/autofill_assistant/browser/actions/set_attribute_action.h index d9bc69d6b49..98e6c0b75a1 100644 --- a/chromium/components/autofill_assistant/browser/actions/set_attribute_action.h +++ b/chromium/components/autofill_assistant/browser/actions/set_attribute_action.h @@ -9,7 +9,6 @@ #include "components/autofill_assistant/browser/actions/action.h" #include "components/autofill_assistant/browser/actions/action_delegate.h" #include "components/autofill_assistant/browser/client_status.h" -#include "components/autofill_assistant/browser/web/element_finder.h" namespace autofill_assistant { diff --git a/chromium/components/autofill_assistant/browser/actions/show_cast_action.cc b/chromium/components/autofill_assistant/browser/actions/show_cast_action.cc index 3927bd4530c..c06ace78b71 100644 --- a/chromium/components/autofill_assistant/browser/actions/show_cast_action.cc +++ b/chromium/components/autofill_assistant/browser/actions/show_cast_action.cc @@ -15,6 +15,7 @@ #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/service.pb.h" #include "components/autofill_assistant/browser/web/element_action_util.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/web_controller.h" namespace autofill_assistant { diff --git a/chromium/components/autofill_assistant/browser/actions/show_cast_action.h b/chromium/components/autofill_assistant/browser/actions/show_cast_action.h index f72fa3b98d9..8252ab67c18 100644 --- a/chromium/components/autofill_assistant/browser/actions/show_cast_action.h +++ b/chromium/components/autofill_assistant/browser/actions/show_cast_action.h @@ -11,9 +11,9 @@ #include "components/autofill_assistant/browser/actions/action.h" #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/top_padding.h" -#include "components/autofill_assistant/browser/web/element_finder.h" namespace autofill_assistant { +class ElementFinderResult; // An action to show cast a given element on Web. Scrolling to it first if // required. diff --git a/chromium/components/autofill_assistant/browser/actions/show_generic_ui_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/show_generic_ui_action_unittest.cc index d8066870a12..95e6172729a 100644 --- a/chromium/components/autofill_assistant/browser/actions/show_generic_ui_action_unittest.cc +++ b/chromium/components/autofill_assistant/browser/actions/show_generic_ui_action_unittest.cc @@ -122,7 +122,7 @@ TEST_F(ShowGenericUiActionTest, GoesIntoPromptState) { EXPECT_CALL(mock_action_delegate_, Prompt(_, _, _, _, _)).Times(1); EXPECT_CALL(mock_action_delegate_, SetGenericUi(_, _, _)).Times(1); EXPECT_CALL(mock_action_delegate_, ClearGenericUi()).Times(1); - EXPECT_CALL(mock_action_delegate_, CleanUpAfterPrompt()).Times(1); + EXPECT_CALL(mock_action_delegate_, CleanUpAfterPrompt(Eq(true))).Times(1); EXPECT_CALL( callback_, Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED)))); @@ -289,7 +289,7 @@ TEST_F(ShowGenericUiActionTest, EndActionOnNavigation) { bool browse_mode, bool browse_mode_invisible) { std::move(end_navigation_callback).Run(); }); - EXPECT_CALL(mock_action_delegate_, CleanUpAfterPrompt()).Times(1); + EXPECT_CALL(mock_action_delegate_, CleanUpAfterPrompt(Eq(true))).Times(1); EXPECT_CALL( callback_, Run(Pointee( @@ -324,7 +324,7 @@ TEST_F(ShowGenericUiActionTest, BreakingNavigationBeforeUiIsSet) { std::move(end_action_callback) .Run(ClientStatus(OTHER_ACTION_STATUS)); })); - EXPECT_CALL(mock_action_delegate_, CleanUpAfterPrompt()).Times(1); + EXPECT_CALL(mock_action_delegate_, CleanUpAfterPrompt(Eq(true))).Times(1); EXPECT_CALL( callback_, Run(Pointee( diff --git a/chromium/components/autofill_assistant/browser/actions/upload_dom_action.h b/chromium/components/autofill_assistant/browser/actions/upload_dom_action.h index 3cf6ed01ba3..2b1010bc2f5 100644 --- a/chromium/components/autofill_assistant/browser/actions/upload_dom_action.h +++ b/chromium/components/autofill_assistant/browser/actions/upload_dom_action.h @@ -10,7 +10,6 @@ #include "base/callback.h" #include "base/memory/weak_ptr.h" #include "components/autofill_assistant/browser/actions/action.h" -#include "components/autofill_assistant/browser/web/element_finder.h" namespace autofill_assistant { diff --git a/chromium/components/autofill_assistant/browser/actions/wait_for_document_action.cc b/chromium/components/autofill_assistant/browser/actions/wait_for_document_action.cc index c59e6ae49a8..66141bb686f 100644 --- a/chromium/components/autofill_assistant/browser/actions/wait_for_document_action.cc +++ b/chromium/components/autofill_assistant/browser/actions/wait_for_document_action.cc @@ -6,7 +6,7 @@ #include "components/autofill_assistant/browser/actions/action_delegate.h" #include "components/autofill_assistant/browser/client_status.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/web_controller.h" namespace autofill_assistant { diff --git a/chromium/components/autofill_assistant/browser/actions/wait_for_document_action.h b/chromium/components/autofill_assistant/browser/actions/wait_for_document_action.h index 14e19d9aac9..295041374cf 100644 --- a/chromium/components/autofill_assistant/browser/actions/wait_for_document_action.h +++ b/chromium/components/autofill_assistant/browser/actions/wait_for_document_action.h @@ -8,9 +8,9 @@ #include "base/memory/weak_ptr.h" #include "base/timer/timer.h" #include "components/autofill_assistant/browser/actions/action.h" -#include "components/autofill_assistant/browser/web/element_finder.h" namespace autofill_assistant { +class ElementFinderResult; class WaitForDocumentAction : public Action { public: diff --git a/chromium/components/autofill_assistant/browser/actions/wait_for_dom_test_base.cc b/chromium/components/autofill_assistant/browser/actions/wait_for_dom_test_base.cc new file mode 100644 index 00000000000..d83424816d6 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/actions/wait_for_dom_test_base.cc @@ -0,0 +1,96 @@ +// Copyright 2022 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_assistant/browser/actions/wait_for_dom_test_base.h" + +namespace autofill_assistant { + +namespace { + +using ::testing::WithArgs; + +} + +WaitForDomTestBase::WaitForDomTestBase() + : task_env_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) { + ON_CALL(mock_web_controller_, FindElement) + .WillByDefault(WithArgs<2>([](auto&& callback) { + std::move(callback).Run(ClientStatus(ELEMENT_RESOLUTION_FAILED), + std::make_unique<ElementFinderResult>()); + })); + EXPECT_CALL(mock_action_delegate_, WaitForDom) + .WillRepeatedly(Invoke(this, &WaitForDomTestBase::FakeWaitForDom)); +} + +WaitForDomTestBase::~WaitForDomTestBase() = default; + +void WaitForDomTestBase::FakeWaitForDom( + base::TimeDelta max_wait_time, + bool allow_observer_mode, + bool allow_interrupt, + WaitForDomObserver* observer, + base::RepeatingCallback<void(BatchElementChecker*, + base::OnceCallback<void(const ClientStatus&)>)> + check_elements, + base::OnceCallback<void(const ClientStatus&, base::TimeDelta)> + done_waiting_callback) { + fake_wait_for_dom_done_ = std::move(done_waiting_callback); + RunFakeWaitForDom(check_elements); +} + +void WaitForDomTestBase::RunFakeWaitForDom( + base::RepeatingCallback<void(BatchElementChecker*, + base::OnceCallback<void(const ClientStatus&)>)> + check_elements) { + if (!fake_wait_for_dom_done_) + return; + + checker_ = std::make_unique<BatchElementChecker>(); + has_check_elements_result_ = false; + check_elements.Run(checker_.get(), + base::BindOnce(&WaitForDomTestBase::OnCheckElementsDone, + base::Unretained(this))); + task_env_.FastForwardBy(base::Milliseconds(fake_check_time_)); + checker_->AddAllDoneCallback( + base::BindOnce(&WaitForDomTestBase::OnWaitForDomDone, + base::Unretained(this), check_elements)); + checker_->Run(&mock_web_controller_); +} + +void WaitForDomTestBase::OnCheckElementsDone(const ClientStatus& result) { + ASSERT_FALSE(has_check_elements_result_); // Duplicate calls + has_check_elements_result_ = true; + check_elements_result_ = result; +} + +void WaitForDomTestBase::OnWaitForDomDone( + base::RepeatingCallback<void(BatchElementChecker*, + base::OnceCallback<void(const ClientStatus&)>)> + check_elements) { + ASSERT_TRUE(has_check_elements_result_); // OnCheckElementsDone() not called + + if (!fake_wait_for_dom_done_) + return; + + // If we manually set |wait_for_dom_status_| to an error status we simulate + // the WaitForDom ending in an error. + if (!wait_for_dom_status_.ok()) { + std::move(fake_wait_for_dom_done_) + .Run(wait_for_dom_status_, base::Milliseconds(0)); + return; + } + + if (check_elements_result_.ok()) { + std::move(fake_wait_for_dom_done_) + .Run(check_elements_result_, base::Milliseconds(fake_wait_time_)); + } else { + wait_for_dom_timer_ = std::make_unique<base::OneShotTimer>(); + wait_for_dom_timer_->Start( + FROM_HERE, base::Seconds(1), + base::BindOnce(&WaitForDomTestBase::RunFakeWaitForDom, + base::Unretained(this), check_elements)); + } +} + +} // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/actions/wait_for_dom_test_base.h b/chromium/components/autofill_assistant/browser/actions/wait_for_dom_test_base.h new file mode 100644 index 00000000000..0e1d7e3d28c --- /dev/null +++ b/chromium/components/autofill_assistant/browser/actions/wait_for_dom_test_base.h @@ -0,0 +1,81 @@ +// Copyright 2022 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_ASSISTANT_BROWSER_ACTIONS_WAIT_FOR_DOM_TEST_BASE_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_WAIT_FOR_DOM_TEST_BASE_H_ + +#include <utility> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/test/bind.h" +#include "base/test/gmock_callback_support.h" +#include "base/test/mock_callback.h" +#include "base/test/task_environment.h" +#include "base/test/test_simple_task_runner.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "components/autofill_assistant/browser/actions/mock_action_delegate.h" +#include "components/autofill_assistant/browser/batch_element_checker.h" +#include "components/autofill_assistant/browser/wait_for_dom_observer.h" +#include "components/autofill_assistant/browser/web/mock_web_controller.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace autofill_assistant { + +class WaitForDomTestBase : public testing::Test { + public: + WaitForDomTestBase(); + ~WaitForDomTestBase() override; + + protected: + // Fakes ActionDelegate::WaitForDom. + // + // This simulates a WaitForDom that calls |check_elements_| every seconds + // until it gets a successful callback, then calls done_waiting_callback. + void FakeWaitForDom( + base::TimeDelta max_wait_time, + bool allow_observer_mode, + bool allow_interrupt, + WaitForDomObserver* observer, + base::RepeatingCallback< + void(BatchElementChecker*, + base::OnceCallback<void(const ClientStatus&)>)> check_elements, + base::OnceCallback<void(const ClientStatus&, base::TimeDelta)> + done_waiting_callback); + + void RunFakeWaitForDom( + base::RepeatingCallback< + void(BatchElementChecker*, + base::OnceCallback<void(const ClientStatus&)>)> check_elements); + + // Called from the check_elements callback passed to FakeWaitForDom. + void OnCheckElementsDone(const ClientStatus& result); + + // Called by |checker_| once it's done and either ends the WaitForDom or + // schedule another run. + void OnWaitForDomDone( + base::RepeatingCallback< + void(BatchElementChecker*, + base::OnceCallback<void(const ClientStatus&)>)> check_elements); + + // task_env_ must be first to guarantee other field + // creation run in that environment. + base::test::TaskEnvironment task_env_; + + MockActionDelegate mock_action_delegate_; + MockWebController mock_web_controller_; + base::OnceCallback<void(const ClientStatus&, base::TimeDelta)> + fake_wait_for_dom_done_; + std::unique_ptr<BatchElementChecker> checker_; + bool has_check_elements_result_ = false; + ClientStatus check_elements_result_; + std::unique_ptr<base::OneShotTimer> wait_for_dom_timer_; + int fake_wait_time_ = 0; + int fake_check_time_ = 0; + ClientStatus wait_for_dom_status_ = OkClientStatus(); +}; + +} // namespace autofill_assistant +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_WAIT_FOR_DOM_TEST_BASE_H_ diff --git a/chromium/components/autofill_assistant/browser/android/BUILD.gn b/chromium/components/autofill_assistant/browser/android/BUILD.gn index 9b87db63b02..ebe35af8f8f 100644 --- a/chromium/components/autofill_assistant/browser/android/BUILD.gn +++ b/chromium/components/autofill_assistant/browser/android/BUILD.gn @@ -64,7 +64,6 @@ static_library("android") { "//components/password_manager/content/browser", "//components/password_manager/core/browser", "//components/strings:components_strings_grit", - "//components/variations/service", "//components/version_info:channel", "//components/version_info/android:channel_getter", "//content/public/browser", @@ -79,8 +78,8 @@ static_library("android") { static_library("dependencies_android") { sources = [ - "dependencies.cc", - "dependencies.h", + "dependencies_android.cc", + "dependencies_android.h", ] deps = [ diff --git a/chromium/components/autofill_assistant/browser/android/DEPS b/chromium/components/autofill_assistant/browser/android/DEPS index 67f82c5b5c9..98a0a0dc6d9 100644 --- a/chromium/components/autofill_assistant/browser/android/DEPS +++ b/chromium/components/autofill_assistant/browser/android/DEPS @@ -1,15 +1,3 @@ include_rules = [ "+components/keyed_service/content", ] - -specific_include_rules = { - 'dependencies.h': [ - '+components/variations/service/variations_service.h', - ], - 'dependencies.cc': [ - '+components/variations/service/variations_service.h', - ], - 'client_android.cc': [ - '+components/variations/service/variations_service.h', - ], -} diff --git a/chromium/components/autofill_assistant/browser/android/assistant_header_model.cc b/chromium/components/autofill_assistant/browser/android/assistant_header_model.cc index b1c6b947405..c71ad2ccae3 100644 --- a/chromium/components/autofill_assistant/browser/android/assistant_header_model.cc +++ b/chromium/components/autofill_assistant/browser/android/assistant_header_model.cc @@ -6,7 +6,6 @@ #include "base/android/jni_string.h" #include "components/autofill_assistant/android/jni_headers/AssistantHeaderModel_jni.h" -#include "components/autofill_assistant/browser/android/dependencies.h" #include "components/autofill_assistant/browser/android/ui_controller_android.h" #include "components/autofill_assistant/browser/android/ui_controller_android_utils.h" @@ -76,7 +75,7 @@ void AssistantHeaderModel::SetProgressBarErrorState(bool error) { void AssistantHeaderModel::SetStepProgressBarConfiguration( const ShowProgressBarProto::StepProgressBarConfiguration& configuration, const base::android::JavaRef<jobject>& jcontext, - const Dependencies& dependencies) { + const DependenciesAndroid& dependencies) { JNIEnv* env = AttachCurrentThread(); if (!configuration.annotated_step_icons().empty()) { auto jlist = Java_AssistantHeaderModel_createIconList(env); diff --git a/chromium/components/autofill_assistant/browser/android/assistant_header_model.h b/chromium/components/autofill_assistant/browser/android/assistant_header_model.h index 18c4a4873f6..964af00290b 100644 --- a/chromium/components/autofill_assistant/browser/android/assistant_header_model.h +++ b/chromium/components/autofill_assistant/browser/android/assistant_header_model.h @@ -10,7 +10,7 @@ #include "base/android/jni_android.h" #include "base/android/scoped_java_ref.h" #include "components/autofill_assistant/browser/android/assistant_header_delegate.h" -#include "components/autofill_assistant/browser/android/dependencies.h" +#include "components/autofill_assistant/browser/android/dependencies_android.h" #include "components/autofill_assistant/browser/service.pb.h" #include "components/autofill_assistant/browser/tts_button_state.h" @@ -38,7 +38,7 @@ class AssistantHeaderModel { void SetStepProgressBarConfiguration( const ShowProgressBarProto::StepProgressBarConfiguration& configuration, const base::android::JavaRef<jobject>& jcontext, - const Dependencies& dependencies); + const DependenciesAndroid& dependencies); void SetSpinPoodle(bool enabled); void SetChips(const base::android::ScopedJavaLocalRef<jobject>& jchips); void SetTtsButtonVisible(bool visible); diff --git a/chromium/components/autofill_assistant/browser/android/client_android.cc b/chromium/components/autofill_assistant/browser/android/client_android.cc index 066ea3d030e..35552dac2ef 100644 --- a/chromium/components/autofill_assistant/browser/android/client_android.cc +++ b/chromium/components/autofill_assistant/browser/android/client_android.cc @@ -33,7 +33,6 @@ #include "components/password_manager/content/browser/password_change_success_tracker_factory.h" #include "components/password_manager/core/browser/password_change_success_tracker.h" #include "components/password_manager/core/browser/password_manager_client.h" -#include "components/variations/service/variations_service.h" #include "components/version_info/android/channel_getter.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" @@ -42,11 +41,13 @@ #include "services/metrics/public/cpp/ukm_recorder.h" #include "url/gurl.h" -using base::android::AttachCurrentThread; -using base::android::JavaParamRef; -using base::android::JavaRef; -using base::android::ScopedJavaGlobalRef; -using base::android::ScopedJavaLocalRef; +using ::base::android::AttachCurrentThread; +using ::base::android::ConvertJavaStringToUTF8; +using ::base::android::ConvertUTF8ToJavaString; +using ::base::android::JavaParamRef; +using ::base::android::JavaRef; +using ::base::android::ScopedJavaGlobalRef; +using ::base::android::ScopedJavaLocalRef; namespace autofill_assistant { namespace { @@ -95,7 +96,11 @@ static void JNI_AutofillAssistantClient_OnOnboardingUiChange( ClientAndroid::ClientAndroid(content::WebContents* web_contents, const ScopedJavaGlobalRef<jobject>& jdependencies) : content::WebContentsUserData<ClientAndroid>(*web_contents), - dependencies_(Dependencies::CreateFromJavaDependencies(jdependencies)), + dependencies_( + DependenciesAndroid::CreateFromJavaDependencies(jdependencies)), + annotate_dom_model_service_(dependencies_->GetCommonDependencies() + ->GetOrCreateAnnotateDomModelService( + web_contents->GetBrowserContext())), jdependencies_(jdependencies), java_object_(Java_AutofillAssistantClient_Constructor( AttachCurrentThread(), @@ -127,7 +132,7 @@ bool ClientAndroid::IsVisible() const { ui_controller_android_->IsAttached(); } -bool ClientAndroid::Start( +void ClientAndroid::Start( const GURL& url, std::unique_ptr<TriggerContext> trigger_context, std::unique_ptr<Service> test_service_to_inject, @@ -157,9 +162,11 @@ bool ClientAndroid::Start( // Register TTS Synthetic Field Trial. const bool enable_tts = trigger_context->GetScriptParameters().GetEnableTts().value_or(false); - dependencies_->CreateFieldTrialUtil()->RegisterSyntheticFieldTrial( - kAutofillAssistantTtsTrialName, - enable_tts ? kEnabledGroupName : kDisabledGroupName); + dependencies_->GetCommonDependencies() + ->CreateFieldTrialUtil() + ->RegisterSyntheticFieldTrial( + kAutofillAssistantTtsTrialName, + enable_tts ? kEnabledGroupName : kDisabledGroupName); DCHECK(!trigger_context->GetDirectAction()); if (VLOG_IS_ON(2)) { @@ -172,7 +179,7 @@ bool ClientAndroid::Start( DVLOG(2) << "\t\t" << param.name() << ": " << param.value(); } } - return controller_->Start(url, std::move(trigger_context)); + controller_->Start(url, std::move(trigger_context)); } void ClientAndroid::OnJavaDestroyUI( @@ -181,38 +188,10 @@ void ClientAndroid::OnJavaDestroyUI( DestroyUI(); } -void ClientAndroid::TransferUITo( - JNIEnv* env, - const base::android::JavaParamRef<jobject>& jcaller, - const base::android::JavaParamRef<jobject>& jother_web_contents) { - if (!ui_controller_android_) - return; - - auto ui_ptr = std::move(ui_controller_android_); - // From this point on, the UIController, in ui_ptr, is either transferred or - // deleted. - - if (!jother_web_contents) - return; - - auto* other_web_contents = - content::WebContents::FromJavaWebContents(jother_web_contents); - DCHECK_NE(other_web_contents, GetWebContents()); - - ClientAndroid* other_client = - ClientAndroid::FromWebContents(other_web_contents); - if (!other_client || !other_client->NeedsUI()) - return; - - other_client->ui_controller_android_ = std::move(ui_ptr); - other_client->AttachUI(); -} - base::android::ScopedJavaLocalRef<jstring> ClientAndroid::GetPrimaryAccountName( JNIEnv* env, const JavaParamRef<jobject>& jcaller) { - return base::android::ConvertUTF8ToJavaString( - env, GetChromeSignedInEmailAddress()); + return ConvertUTF8ToJavaString(env, GetSignedInEmail()); } void ClientAndroid::OnAccessToken(JNIEnv* env, @@ -248,7 +227,9 @@ void ClientAndroid::FetchWebsiteActions( /* onboarding_shown = */ false, /* is_direct_action = */ true, /* jinitial_url = */ nullptr, - /* is_custom_tab = */ dependencies_->IsCustomTab(*GetWebContents())), + /* is_custom_tab = */ + dependencies_->GetPlatformDependencies()->IsCustomTab( + *GetWebContents())), base::BindOnce(&ClientAndroid::OnFetchWebsiteActions, weak_ptr_factory_.GetWeakPtr(), scoped_jcallback)); } @@ -359,7 +340,8 @@ bool ClientAndroid::PerformDirectAction( /* is_direct_action = */ true, /* jinitial_url = */ nullptr, - /* is_custom_tab = */ dependencies_->IsCustomTab(*GetWebContents())); + /* is_custom_tab = */ + dependencies_->GetPlatformDependencies()->IsCustomTab(*GetWebContents())); int action_index = FindDirectAction(action_name); if (action_index == -1) @@ -387,6 +369,13 @@ void ClientAndroid::ShowFatalError( Metrics::DropOutReason::NO_SCRIPTS); } +bool ClientAndroid::IsSupervisedUser( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& jcaller) { + return dependencies_->GetCommonDependencies()->IsSupervisedUser( + GetWebContents()->GetBrowserContext()); +} + void ClientAndroid::OnSpokenFeedbackAccessibilityServiceChanged( JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller, @@ -474,7 +463,7 @@ void ClientAndroid::DestroyUI() { } version_info::Channel ClientAndroid::GetChannel() const { - return version_info::android::GetChannel(); + return dependencies_->GetCommonDependencies()->GetChannel(); } std::string ClientAndroid::GetEmailAddressForAccessTokenAccount() const { @@ -484,8 +473,9 @@ std::string ClientAndroid::GetEmailAddressForAccessTokenAccount() const { env, java_object_)); } -std::string ClientAndroid::GetChromeSignedInEmailAddress() const { - return dependencies_->GetChromeSignedInEmailAddress(GetWebContents()); +std::string ClientAndroid::GetSignedInEmail() const { + return dependencies_->GetCommonDependencies()->GetSignedInEmail( + GetWebContents()->GetBrowserContext()); } absl::optional<std::pair<int, int>> ClientAndroid::GetWindowSize() const { @@ -530,13 +520,15 @@ AccessTokenFetcher* ClientAndroid::GetAccessTokenFetcher() { } autofill::PersonalDataManager* ClientAndroid::GetPersonalDataManager() const { - return dependencies_->GetPersonalDataManager(); + return dependencies_->GetCommonDependencies()->GetPersonalDataManager( + GetWebContents()->GetBrowserContext()); } WebsiteLoginManager* ClientAndroid::GetWebsiteLoginManager() const { if (!website_login_manager_) { auto* password_manager_client = - dependencies_->GetPasswordManagerClient(GetWebContents()); + dependencies_->GetCommonDependencies()->GetPasswordManagerClient( + GetWebContents()); if (password_manager_client) { website_login_manager_ = std::make_unique<WebsiteLoginManagerImpl>( password_manager_client, GetWebContents()); @@ -554,16 +546,12 @@ ClientAndroid::GetPasswordChangeSuccessTracker() const { } std::string ClientAndroid::GetLocale() const { + // TODO(b/201964911): use dependencies instead. return base::android::GetDefaultLocaleString(); } std::string ClientAndroid::GetCountryCode() const { - variations::VariationsService* variations_service = - dependencies_->GetVariationsService(); - // Use fallback "ZZ" if no country is available. - if (!variations_service || variations_service->GetLatestCountry().empty()) - return "ZZ"; - return base::ToUpperASCII(variations_service->GetLatestCountry()); + return dependencies_->GetCommonDependencies()->GetCountryCode(); } DeviceContext ClientAndroid::GetDeviceContext() const { @@ -614,6 +602,39 @@ ScriptExecutorUiDelegate* ClientAndroid::GetScriptExecutorUiDelegate() { return ui_controller_.get(); } +bool ClientAndroid::MustUseBackendData() const { + // For WebLayer flows the client does not have access to Chrome's Autofill + // data and must use data from our backend. Similarly the client can not use + // e.g. Autofill's data editors and must rely on GMS Core provided + // replacements. + return dependencies_->GetCommonDependencies()->IsWebLayer(); +} + +void ClientAndroid::GetAnnotateDomModelVersion( + base::OnceCallback<void(absl::optional<int64_t>)> callback) const { + if (!annotate_dom_model_service_) { + std::move(callback).Run(absl::nullopt); + return; + } + + auto model_version = annotate_dom_model_service_->GetModelVersion(); + if (model_version.has_value()) { + std::move(callback).Run(model_version); + return; + } + + annotate_dom_model_service_->NotifyOnModelFileAvailable( + base::BindOnce(&ClientAndroid::OnAnnotateDomModelFileAvailable, + weak_ptr_factory_.GetWeakPtr(), std::move(callback))); +} + +void ClientAndroid::OnAnnotateDomModelFileAvailable( + base::OnceCallback<void(absl::optional<int64_t>)> callback, + bool available) { + DCHECK(annotate_dom_model_service_); + std::move(callback).Run(annotate_dom_model_service_->GetModelVersion()); +} + void ClientAndroid::Shutdown(Metrics::DropOutReason reason) { if (!controller_) return; @@ -687,9 +708,7 @@ void ClientAndroid::CreateController( GetWebContents(), /* client= */ this, base::DefaultTickClock::GetInstance(), RuntimeManager::GetForWebContents(GetWebContents())->GetWeakPtr(), - std::move(service), ukm::UkmRecorder::Get(), - dependencies_->GetOrCreateAnnotateDomModelService( - GetWebContents()->GetBrowserContext())); + std::move(service), ukm::UkmRecorder::Get(), annotate_dom_model_service_); ui_controller_ = std::make_unique<UiController>( /* client= */ this, controller_.get(), std::move(tts_controller)); ui_controller_->StartListening(); diff --git a/chromium/components/autofill_assistant/browser/android/client_android.h b/chromium/components/autofill_assistant/browser/android/client_android.h index 240182908a9..c427dcefb3e 100644 --- a/chromium/components/autofill_assistant/browser/android/client_android.h +++ b/chromium/components/autofill_assistant/browser/android/client_android.h @@ -11,7 +11,7 @@ #include "base/android/scoped_java_ref.h" #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" -#include "components/autofill_assistant/browser/android/dependencies.h" +#include "components/autofill_assistant/browser/android/dependencies_android.h" #include "components/autofill_assistant/browser/android/ui_controller_android.h" #include "components/autofill_assistant/browser/client.h" #include "components/autofill_assistant/browser/controller.h" @@ -58,17 +58,13 @@ class ClientAndroid : public Client, // Returns whether UI is currently being displayed to the user. bool IsVisible() const; - bool Start(const GURL& url, + void Start(const GURL& url, std::unique_ptr<TriggerContext> trigger_context, std::unique_ptr<Service> test_service_to_inject, const base::android::JavaRef<jobject>& joverlay_coordinator, const absl::optional<TriggerScriptProto>& trigger_script); void OnJavaDestroyUI(JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller); - void TransferUITo( - JNIEnv* env, - const base::android::JavaParamRef<jobject>& jcaller, - const base::android::JavaParamRef<jobject>& jother_web_contents); base::android::ScopedJavaLocalRef<jstring> GetPrimaryAccountName( JNIEnv* env, @@ -110,6 +106,9 @@ class ClientAndroid : public Client, void ShowFatalError(JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller); + bool IsSupervisedUser(JNIEnv* env, + const base::android::JavaParamRef<jobject>& jcaller); + void OnSpokenFeedbackAccessibilityServiceChanged( JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller, @@ -126,7 +125,7 @@ class ClientAndroid : public Client, void DestroyUI() override; version_info::Channel GetChannel() const override; std::string GetEmailAddressForAccessTokenAccount() const override; - std::string GetChromeSignedInEmailAddress() const override; + std::string GetSignedInEmail() const override; absl::optional<std::pair<int, int>> GetWindowSize() const override; ClientContextProto::ScreenOrientation GetScreenOrientation() const override; void FetchPaymentsClientToken( @@ -146,6 +145,10 @@ class ClientAndroid : public Client, void RecordDropOut(Metrics::DropOutReason reason) override; bool HasHadUI() const override; ScriptExecutorUiDelegate* GetScriptExecutorUiDelegate() override; + bool MustUseBackendData() const override; + void GetAnnotateDomModelVersion( + base::OnceCallback<void(absl::optional<int64_t>)> callback) + const override; // Overrides AccessTokenFetcher void FetchAccessToken( @@ -179,10 +182,15 @@ class ClientAndroid : public Client, // UiDelegate::PerformUserAction() or -1 if not found. int FindDirectAction(const std::string& action_name); + void OnAnnotateDomModelFileAvailable( + base::OnceCallback<void(absl::optional<int64_t>)> callback, + bool available); + WEB_CONTENTS_USER_DATA_KEY_DECL(); // Contains AssistantStaticDependencies which do not change. - const std::unique_ptr<const Dependencies> dependencies_; + const std::unique_ptr<const DependenciesAndroid> dependencies_; + const raw_ptr<AnnotateDomModelService> annotate_dom_model_service_; // Can change based on activity attachment. const base::android::ScopedJavaGlobalRef<jobject> jdependencies_; diff --git a/chromium/components/autofill_assistant/browser/android/dependencies.cc b/chromium/components/autofill_assistant/browser/android/dependencies_android.cc index a639cbdce81..c66bd3e84d9 100644 --- a/chromium/components/autofill_assistant/browser/android/dependencies.cc +++ b/chromium/components/autofill_assistant/browser/android/dependencies_android.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "components/autofill_assistant/browser/android/dependencies.h" +#include "components/autofill_assistant/browser/android/dependencies_android.h" #include "base/android/jni_android.h" #include "base/android/scoped_java_ref.h" @@ -18,14 +18,16 @@ using ::base::android::ScopedJavaGlobalRef; namespace autofill_assistant { -std::unique_ptr<Dependencies> Dependencies::CreateFromJavaStaticDependencies( +std::unique_ptr<DependenciesAndroid> +DependenciesAndroid::CreateFromJavaStaticDependencies( const JavaRef<jobject>& jstatic_dependencies) { - return base::WrapUnique(reinterpret_cast<Dependencies*>( + return base::WrapUnique(reinterpret_cast<DependenciesAndroid*>( Java_AssistantStaticDependencies_createNative(AttachCurrentThread(), jstatic_dependencies))); } -std::unique_ptr<Dependencies> Dependencies::CreateFromJavaDependencies( +std::unique_ptr<DependenciesAndroid> +DependenciesAndroid::CreateFromJavaDependencies( const JavaRef<jobject>& jdependencies) { const auto jstatic_dependencies = Java_AssistantDependencies_getStaticDependencies(AttachCurrentThread(), @@ -33,43 +35,46 @@ std::unique_ptr<Dependencies> Dependencies::CreateFromJavaDependencies( return CreateFromJavaStaticDependencies(jstatic_dependencies); } -Dependencies::Dependencies(JNIEnv* env, - const JavaParamRef<jobject>& jstatic_dependencies) +DependenciesAndroid::DependenciesAndroid( + JNIEnv* env, + const JavaParamRef<jobject>& jstatic_dependencies) : jstatic_dependencies_(jstatic_dependencies) {} -ScopedJavaGlobalRef<jobject> Dependencies::GetJavaStaticDependencies() const { +ScopedJavaGlobalRef<jobject> DependenciesAndroid::GetJavaStaticDependencies() + const { return jstatic_dependencies_; } -ScopedJavaGlobalRef<jobject> Dependencies::CreateInfoPageUtil() const { +ScopedJavaGlobalRef<jobject> DependenciesAndroid::CreateInfoPageUtil() const { return ScopedJavaGlobalRef<jobject>( Java_AssistantStaticDependencies_createInfoPageUtil( AttachCurrentThread(), jstatic_dependencies_)); } -ScopedJavaGlobalRef<jobject> Dependencies::CreateAccessTokenUtil() const { +ScopedJavaGlobalRef<jobject> DependenciesAndroid::CreateAccessTokenUtil() + const { return ScopedJavaGlobalRef<jobject>( Java_AssistantStaticDependencies_createAccessTokenUtil( AttachCurrentThread(), jstatic_dependencies_)); } -ScopedJavaGlobalRef<jobject> Dependencies::CreateImageFetcher() const { +ScopedJavaGlobalRef<jobject> DependenciesAndroid::CreateImageFetcher() const { return ScopedJavaGlobalRef<jobject>( Java_AssistantStaticDependencies_createImageFetcher( AttachCurrentThread(), jstatic_dependencies_)); } -ScopedJavaGlobalRef<jobject> Dependencies::CreateIconBridge() const { +ScopedJavaGlobalRef<jobject> DependenciesAndroid::CreateIconBridge() const { return ScopedJavaGlobalRef<jobject>( Java_AssistantStaticDependencies_createIconBridge(AttachCurrentThread(), jstatic_dependencies_)); } -bool Dependencies::IsAccessibilityEnabled() const { +bool DependenciesAndroid::IsAccessibilityEnabled() const { return Java_AssistantStaticDependencies_isAccessibilityEnabled( AttachCurrentThread(), jstatic_dependencies_); } -Dependencies::~Dependencies() = default; +DependenciesAndroid::~DependenciesAndroid() = default; } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/android/dependencies.h b/chromium/components/autofill_assistant/browser/android/dependencies_android.h index 7ae45983e07..26e630eee68 100644 --- a/chromium/components/autofill_assistant/browser/android/dependencies.h +++ b/chromium/components/autofill_assistant/browser/android/dependencies_android.h @@ -2,30 +2,34 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ANDROID_DEPENDENCIES_H_ -#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ANDROID_DEPENDENCIES_H_ +#ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ANDROID_DEPENDENCIES_ANDROID_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ANDROID_DEPENDENCIES_ANDROID_H_ #include <memory> #include "base/android/scoped_java_ref.h" #include "base/strings/string_piece.h" #include "components/autofill/core/browser/personal_data_manager.h" #include "components/autofill_assistant/browser/assistant_field_trial_util.h" +#include "components/autofill_assistant/browser/common_dependencies.h" +#include "components/autofill_assistant/browser/platform_dependencies.h" #include "components/autofill_assistant/content/browser/annotate_dom_model_service.h" #include "components/password_manager/core/browser/password_manager_client.h" -#include "components/variations/service/variations_service.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/web_contents.h" namespace autofill_assistant { -// Interface for platform delegates that provide platform-dependent features -// and dependencies to the starter. -class Dependencies { +// Wrapper for all dependencies needed in android flows using legacy UI. +// +// Provides the right implementation of |CommonDependencies| and +// |PlatformDependencies| depending on platform and whether we are in Chrome or +// Weblayer. +class DependenciesAndroid { public: - static std::unique_ptr<Dependencies> CreateFromJavaStaticDependencies( + static std::unique_ptr<DependenciesAndroid> CreateFromJavaStaticDependencies( const base::android::JavaRef<jobject>& jstatic_dependencies); - static std::unique_ptr<Dependencies> CreateFromJavaDependencies( + static std::unique_ptr<DependenciesAndroid> CreateFromJavaDependencies( const base::android::JavaRef<jobject>& jdependencies); base::android::ScopedJavaGlobalRef<jobject> GetJavaStaticDependencies() const; @@ -36,37 +40,19 @@ class Dependencies { bool IsAccessibilityEnabled() const; - virtual ~Dependencies(); + virtual const CommonDependencies* GetCommonDependencies() const = 0; + virtual const PlatformDependencies* GetPlatformDependencies() const = 0; - virtual std::unique_ptr<AssistantFieldTrialUtil> CreateFieldTrialUtil() - const = 0; - - virtual autofill::PersonalDataManager* GetPersonalDataManager() const = 0; - - virtual password_manager::PasswordManagerClient* GetPasswordManagerClient( - content::WebContents* web_contents) const = 0; - - virtual variations::VariationsService* GetVariationsService() const = 0; - - virtual std::string GetChromeSignedInEmailAddress( - content::WebContents* web_contents) const = 0; - - virtual AnnotateDomModelService* GetOrCreateAnnotateDomModelService( - content::BrowserContext* browser_context) const = 0; - - virtual bool IsCustomTab(const content::WebContents& web_contents) const = 0; - - virtual bool IsWebLayer() const = 0; + virtual ~DependenciesAndroid(); protected: - Dependencies( + DependenciesAndroid( JNIEnv* env, const base::android::JavaParamRef<jobject>& jstatic_dependencies); - private: const base::android::ScopedJavaGlobalRef<jobject> jstatic_dependencies_; }; } // namespace autofill_assistant -#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ANDROID_DEPENDENCIES_H_ +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ANDROID_DEPENDENCIES_ANDROID_H_ diff --git a/chromium/components/autofill_assistant/browser/android/generic_ui_nested_controller_android.cc b/chromium/components/autofill_assistant/browser/android/generic_ui_nested_controller_android.cc index 04648364057..f17be270bb8 100644 --- a/chromium/components/autofill_assistant/browser/android/generic_ui_nested_controller_android.cc +++ b/chromium/components/autofill_assistant/browser/android/generic_ui_nested_controller_android.cc @@ -7,7 +7,7 @@ #include "base/android/jni_string.h" #include "components/autofill_assistant/android/jni_headers/AssistantViewFactory_jni.h" #include "components/autofill_assistant/browser/android/assistant_generic_ui_delegate.h" -#include "components/autofill_assistant/browser/android/dependencies.h" +#include "components/autofill_assistant/browser/android/dependencies_android.h" #include "components/autofill_assistant/browser/android/generic_ui_events_android.h" #include "components/autofill_assistant/browser/android/generic_ui_interactions_android.h" #include "components/autofill_assistant/browser/android/interaction_handler_android.h" @@ -30,7 +30,7 @@ base::android::ScopedJavaGlobalRef<jobject> CreateViewHierarchy( JNIEnv* env, const JavaRef<jobject>& jcontext, const JavaRef<jobject>& jdelegate, - const Dependencies& dependencies, + const DependenciesAndroid& dependencies, const ViewProto& proto, InteractionHandlerAndroid* interaction_handler, ViewHandlerAndroid* view_handler, @@ -102,7 +102,7 @@ base::android::ScopedJavaLocalRef<jobject> CreateJavaVerticalExpander( const JavaRef<jobject>& jcontext, const JavaRef<jobject>& jdelegate, const JavaRef<jstring>& jidentifier, - const Dependencies& dependencies, + const DependenciesAndroid& dependencies, const VerticalExpanderViewProto& proto, InteractionHandlerAndroid* interaction_handler, ViewHandlerAndroid* view_handler, @@ -160,7 +160,7 @@ base::android::ScopedJavaLocalRef<jobject> CreateJavaToggleButton( const JavaRef<jobject>& jcontext, const JavaRef<jobject>& jdelegate, const JavaRef<jstring>& jidentifier, - const Dependencies& dependencies, + const DependenciesAndroid& dependencies, const ToggleButtonViewProto& proto, InteractionHandlerAndroid* interaction_handler, ViewHandlerAndroid* view_handler, @@ -214,7 +214,7 @@ base::android::ScopedJavaGlobalRef<jobject> CreateJavaView( JNIEnv* env, const JavaRef<jobject>& jcontext, const JavaRef<jobject>& jdelegate, - const Dependencies& dependencies, + const DependenciesAndroid& dependencies, const ViewProto& proto, InteractionHandlerAndroid* interaction_handler, ViewHandlerAndroid* view_handler, @@ -409,7 +409,7 @@ base::android::ScopedJavaGlobalRef<jobject> CreateViewHierarchy( JNIEnv* env, const JavaRef<jobject>& jcontext, const JavaRef<jobject>& jdelegate, - const Dependencies& dependencies, + const DependenciesAndroid& dependencies, const ViewProto& proto, InteractionHandlerAndroid* interaction_handler, ViewHandlerAndroid* view_handler, @@ -477,7 +477,7 @@ GenericUiNestedControllerAndroid::CreateFromProto( const GenericUserInterfaceProto& proto, base::android::ScopedJavaGlobalRef<jobject> jcontext, base::android::ScopedJavaGlobalRef<jobject> jinfo_page_util, - const Dependencies& dependencies, + const DependenciesAndroid& dependencies, base::android::ScopedJavaGlobalRef<jobject> jdelegate, EventHandler* event_handler, UserModel* user_model, diff --git a/chromium/components/autofill_assistant/browser/android/generic_ui_nested_controller_android.h b/chromium/components/autofill_assistant/browser/android/generic_ui_nested_controller_android.h index b127eab45a4..b21b3e9a643 100644 --- a/chromium/components/autofill_assistant/browser/android/generic_ui_nested_controller_android.h +++ b/chromium/components/autofill_assistant/browser/android/generic_ui_nested_controller_android.h @@ -11,7 +11,7 @@ #include "base/android/jni_android.h" #include "base/android/scoped_java_ref.h" #include "base/memory/raw_ptr.h" -#include "components/autofill_assistant/browser/android/dependencies.h" +#include "components/autofill_assistant/browser/android/dependencies_android.h" #include "components/autofill_assistant/browser/service.pb.h" namespace autofill_assistant { @@ -31,7 +31,7 @@ class GenericUiNestedControllerAndroid { const GenericUserInterfaceProto& proto, base::android::ScopedJavaGlobalRef<jobject> jcontext, base::android::ScopedJavaGlobalRef<jobject> jinfo_page_util, - const Dependencies& dependencies, + const DependenciesAndroid& dependencies, base::android::ScopedJavaGlobalRef<jobject> jdelegate, EventHandler* event_handler, UserModel* user_model, diff --git a/chromium/components/autofill_assistant/browser/android/generic_ui_root_controller_android.cc b/chromium/components/autofill_assistant/browser/android/generic_ui_root_controller_android.cc index 86ff1229eeb..b8ba3f66142 100644 --- a/chromium/components/autofill_assistant/browser/android/generic_ui_root_controller_android.cc +++ b/chromium/components/autofill_assistant/browser/android/generic_ui_root_controller_android.cc @@ -4,7 +4,7 @@ #include "components/autofill_assistant/browser/android/generic_ui_root_controller_android.h" -#include "components/autofill_assistant/browser/android/dependencies.h" +#include "components/autofill_assistant/browser/android/dependencies_android.h" #include "components/autofill_assistant/browser/android/generic_ui_nested_controller_android.h" #include "components/autofill_assistant/browser/radio_button_controller.h" @@ -29,7 +29,7 @@ GenericUiRootControllerAndroid::CreateFromProto( const GenericUserInterfaceProto& proto, base::android::ScopedJavaGlobalRef<jobject> jcontext, base::android::ScopedJavaGlobalRef<jobject> jinfo_page_util, - const Dependencies& dependencies, + const DependenciesAndroid& dependencies, base::android::ScopedJavaGlobalRef<jobject> jdelegate, EventHandler* event_handler, UserModel* user_model, diff --git a/chromium/components/autofill_assistant/browser/android/generic_ui_root_controller_android.h b/chromium/components/autofill_assistant/browser/android/generic_ui_root_controller_android.h index 6d1f616712d..36de2af6948 100644 --- a/chromium/components/autofill_assistant/browser/android/generic_ui_root_controller_android.h +++ b/chromium/components/autofill_assistant/browser/android/generic_ui_root_controller_android.h @@ -9,7 +9,7 @@ #include "base/android/jni_android.h" #include "base/android/scoped_java_ref.h" -#include "components/autofill_assistant/browser/android/dependencies.h" +#include "components/autofill_assistant/browser/android/dependencies_android.h" #include "components/autofill_assistant/browser/service.pb.h" namespace autofill_assistant { @@ -28,7 +28,7 @@ class GenericUiRootControllerAndroid { const GenericUserInterfaceProto& proto, base::android::ScopedJavaGlobalRef<jobject> jcontext, base::android::ScopedJavaGlobalRef<jobject> jinfo_page_util, - const Dependencies& dependencies, + const DependenciesAndroid& dependencies, base::android::ScopedJavaGlobalRef<jobject> jdelegate, EventHandler* event_handler, UserModel* user_model, diff --git a/chromium/components/autofill_assistant/browser/android/interaction_handler_android.cc b/chromium/components/autofill_assistant/browser/android/interaction_handler_android.cc index e88e5bf6842..90bc5cb9e57 100644 --- a/chromium/components/autofill_assistant/browser/android/interaction_handler_android.cc +++ b/chromium/components/autofill_assistant/browser/android/interaction_handler_android.cc @@ -9,7 +9,7 @@ #include "base/callback_helpers.h" #include "base/memory/weak_ptr.h" #include "base/strings/string_number_conversions.h" -#include "components/autofill_assistant/browser/android/dependencies.h" +#include "components/autofill_assistant/browser/android/dependencies_android.h" #include "components/autofill_assistant/browser/android/generic_ui_interactions_android.h" #include "components/autofill_assistant/browser/android/generic_ui_nested_controller_android.h" #include "components/autofill_assistant/browser/android/view_handler_android.h" @@ -90,7 +90,7 @@ InteractionHandlerAndroid::InteractionHandlerAndroid( BasicInteractions* basic_interactions, ViewHandlerAndroid* view_handler, RadioButtonController* radio_button_controller, - const Dependencies* dependencies, + const DependenciesAndroid* dependencies, base::android::ScopedJavaGlobalRef<jobject> jcontext, base::android::ScopedJavaGlobalRef<jobject> jinfo_page_util, base::android::ScopedJavaGlobalRef<jobject> jdelegate) diff --git a/chromium/components/autofill_assistant/browser/android/interaction_handler_android.h b/chromium/components/autofill_assistant/browser/android/interaction_handler_android.h index 28999086ea1..b8f7200fcf2 100644 --- a/chromium/components/autofill_assistant/browser/android/interaction_handler_android.h +++ b/chromium/components/autofill_assistant/browser/android/interaction_handler_android.h @@ -14,7 +14,7 @@ #include "base/callback.h" #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" -#include "components/autofill_assistant/browser/android/dependencies.h" +#include "components/autofill_assistant/browser/android/dependencies_android.h" #include "components/autofill_assistant/browser/event_handler.h" #include "components/autofill_assistant/browser/service.pb.h" @@ -42,7 +42,7 @@ class InteractionHandlerAndroid : public EventHandler::Observer { BasicInteractions* basic_interactions, ViewHandlerAndroid* view_handler, RadioButtonController* radio_button_controller, - const Dependencies* dependencies, + const DependenciesAndroid* dependencies, base::android::ScopedJavaGlobalRef<jobject> jcontext, base::android::ScopedJavaGlobalRef<jobject> jinfo_page_util, base::android::ScopedJavaGlobalRef<jobject> jdelegate); @@ -108,7 +108,7 @@ class InteractionHandlerAndroid : public EventHandler::Observer { raw_ptr<BasicInteractions> basic_interactions_ = nullptr; raw_ptr<ViewHandlerAndroid> view_handler_ = nullptr; raw_ptr<RadioButtonController> radio_button_controller_ = nullptr; - raw_ptr<const Dependencies> dependencies_ = nullptr; + raw_ptr<const DependenciesAndroid> dependencies_ = nullptr; base::android::ScopedJavaGlobalRef<jobject> jcontext_ = nullptr; base::android::ScopedJavaGlobalRef<jobject> jinfo_page_util_ = nullptr; base::android::ScopedJavaGlobalRef<jobject> jdelegate_ = nullptr; diff --git a/chromium/components/autofill_assistant/browser/android/starter_delegate_android.cc b/chromium/components/autofill_assistant/browser/android/starter_delegate_android.cc index 13fed287599..77661dba646 100644 --- a/chromium/components/autofill_assistant/browser/android/starter_delegate_android.cc +++ b/chromium/components/autofill_assistant/browser/android/starter_delegate_android.cc @@ -37,7 +37,7 @@ static jlong JNI_Starter_FromWebContents( auto* web_contents = content::WebContents::FromJavaWebContents(jweb_contents); CHECK(web_contents); - auto dependencies = Dependencies::CreateFromJavaStaticDependencies( + auto dependencies = DependenciesAndroid::CreateFromJavaStaticDependencies( ScopedJavaGlobalRef<jobject>(env, jstatic_dependencies)); StarterDelegateAndroid::CreateForWebContents(web_contents, std::move(dependencies)); @@ -52,15 +52,15 @@ static jlong JNI_Starter_FromWebContents( StarterDelegateAndroid::StarterDelegateAndroid( content::WebContents* web_contents, - std::unique_ptr<Dependencies> dependencies) + std::unique_ptr<DependenciesAndroid> dependencies) : content::WebContentsUserData<StarterDelegateAndroid>(*web_contents), dependencies_(std::move(dependencies)), website_login_manager_(std::make_unique<WebsiteLoginManagerImpl>( - dependencies_->GetPasswordManagerClient(web_contents), + GetCommonDependencies()->GetPasswordManagerClient(web_contents), web_contents)) { // Create the AnnotateDomModelService when the browser starts, such that it // starts listening to model changes early enough. - dependencies_->GetOrCreateAnnotateDomModelService( + GetCommonDependencies()->GetOrCreateAnnotateDomModelService( web_contents->GetBrowserContext()); } @@ -248,16 +248,23 @@ bool StarterDelegateAndroid::GetMakeSearchesAndBrowsingBetterEnabled() const { } bool StarterDelegateAndroid::GetIsLoggedIn() { - return !dependencies_->GetChromeSignedInEmailAddress(&GetWebContents()) + return !GetCommonDependencies() + ->GetSignedInEmail(GetWebContents().GetBrowserContext()) .empty(); } +bool StarterDelegateAndroid::GetIsSupervisedUser() { + return GetCommonDependencies()->IsSupervisedUser( + GetWebContents().GetBrowserContext()); +} + bool StarterDelegateAndroid::GetIsCustomTab() const { - return dependencies_->IsCustomTab(GetWebContents()); + return dependencies_->GetPlatformDependencies()->IsCustomTab( + GetWebContents()); } bool StarterDelegateAndroid::GetIsWebLayer() const { - return dependencies_->IsWebLayer(); + return GetCommonDependencies()->IsWebLayer(); } bool StarterDelegateAndroid::GetIsTabCreatedByGSA() const { @@ -346,13 +353,23 @@ bool StarterDelegateAndroid::IsRegularScriptVisible() const { std::unique_ptr<AssistantFieldTrialUtil> StarterDelegateAndroid::CreateFieldTrialUtil() { - return dependencies_->CreateFieldTrialUtil(); + return GetCommonDependencies()->CreateFieldTrialUtil(); } bool StarterDelegateAndroid::IsAttached() { return !!java_object_; } +const CommonDependencies* StarterDelegateAndroid::GetCommonDependencies() + const { + return dependencies_->GetCommonDependencies(); +} + +const PlatformDependencies* StarterDelegateAndroid::GetPlatformDependencies() + const { + return dependencies_->GetPlatformDependencies(); +} + base::WeakPtr<StarterPlatformDelegate> StarterDelegateAndroid::GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); } diff --git a/chromium/components/autofill_assistant/browser/android/starter_delegate_android.h b/chromium/components/autofill_assistant/browser/android/starter_delegate_android.h index 7cbdcb06c6d..4b44ed681ab 100644 --- a/chromium/components/autofill_assistant/browser/android/starter_delegate_android.h +++ b/chromium/components/autofill_assistant/browser/android/starter_delegate_android.h @@ -11,7 +11,7 @@ #include "base/android/jni_weak_ref.h" #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" -#include "components/autofill_assistant/browser/android/dependencies.h" +#include "components/autofill_assistant/browser/android/dependencies_android.h" #include "components/autofill_assistant/browser/assistant_field_trial_util.h" #include "components/autofill_assistant/browser/metrics.h" #include "components/autofill_assistant/browser/onboarding_result.h" @@ -78,11 +78,14 @@ class StarterDelegateAndroid void SetProactiveHelpSettingEnabled(bool enabled) override; bool GetMakeSearchesAndBrowsingBetterEnabled() const override; bool GetIsLoggedIn() override; + bool GetIsSupervisedUser() override; bool GetIsCustomTab() const override; bool GetIsWebLayer() const override; bool GetIsTabCreatedByGSA() const override; std::unique_ptr<AssistantFieldTrialUtil> CreateFieldTrialUtil() override; bool IsAttached() override; + const CommonDependencies* GetCommonDependencies() const override; + const PlatformDependencies* GetPlatformDependencies() const override; base::WeakPtr<StarterPlatformDelegate> GetWeakPtr() override; // Called by Java to start an autofill-assistant flow for an incoming intent. @@ -123,14 +126,14 @@ class StarterDelegateAndroid private: friend class content::WebContentsUserData<StarterDelegateAndroid>; StarterDelegateAndroid(content::WebContents* web_contents, - std::unique_ptr<Dependencies> dependencies); + std::unique_ptr<DependenciesAndroid> dependencies); void CreateJavaDependenciesIfNecessary(); WEB_CONTENTS_USER_DATA_KEY_DECL(); base::WeakPtr<Starter> starter_; // Contains AssistantStaticDependencies which do not change. - const std::unique_ptr<const Dependencies> dependencies_; + const std::unique_ptr<const DependenciesAndroid> dependencies_; // Can change based on activity attachment. base::android::ScopedJavaGlobalRef<jobject> java_dependencies_; diff --git a/chromium/components/autofill_assistant/browser/android/trigger_script_bridge_android.cc b/chromium/components/autofill_assistant/browser/android/trigger_script_bridge_android.cc index d46bdc58171..ef47ea09c4f 100644 --- a/chromium/components/autofill_assistant/browser/android/trigger_script_bridge_android.cc +++ b/chromium/components/autofill_assistant/browser/android/trigger_script_bridge_android.cc @@ -8,7 +8,7 @@ #include "base/android/jni_string.h" #include "components/autofill_assistant/android/jni_headers/AssistantTriggerScriptBridge_jni.h" #include "components/autofill_assistant/browser/android/assistant_header_model.h" -#include "components/autofill_assistant/browser/android/dependencies.h" +#include "components/autofill_assistant/browser/android/dependencies_android.h" #include "components/autofill_assistant/browser/android/ui_controller_android_utils.h" using base::android::AttachCurrentThread; @@ -23,7 +23,8 @@ TriggerScriptBridgeAndroid::TriggerScriptBridgeAndroid( JNIEnv* env, const base::android::JavaRef<jobject>& jweb_contents, const base::android::JavaRef<jobject>& jassistant_deps) - : dependencies_(Dependencies::CreateFromJavaDependencies(jassistant_deps)) { + : dependencies_( + DependenciesAndroid::CreateFromJavaDependencies(jassistant_deps)) { java_object_ = Java_AssistantTriggerScriptBridge_Constructor( env, jweb_contents, jassistant_deps); Java_AssistantTriggerScriptBridge_setNativePtr( diff --git a/chromium/components/autofill_assistant/browser/android/trigger_script_bridge_android.h b/chromium/components/autofill_assistant/browser/android/trigger_script_bridge_android.h index 6530233073d..3fcd84a2bdf 100644 --- a/chromium/components/autofill_assistant/browser/android/trigger_script_bridge_android.h +++ b/chromium/components/autofill_assistant/browser/android/trigger_script_bridge_android.h @@ -7,7 +7,7 @@ #include "base/android/jni_android.h" #include "base/memory/raw_ptr.h" -#include "components/autofill_assistant/browser/android/dependencies.h" +#include "components/autofill_assistant/browser/android/dependencies_android.h" #include "components/autofill_assistant/browser/metrics.h" #include "components/autofill_assistant/browser/service.pb.h" #include "components/autofill_assistant/browser/trigger_context.h" @@ -64,7 +64,7 @@ class TriggerScriptBridgeAndroid : public TriggerScriptCoordinator::UiDelegate { // Java-side AssistantStaticDependencies object. This never changes during the // life of the application. - const std::unique_ptr<const Dependencies> dependencies_; + const std::unique_ptr<const DependenciesAndroid> dependencies_; }; } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/android/ui_controller_android.cc b/chromium/components/autofill_assistant/browser/android/ui_controller_android.cc index 386e39b04c4..87009600c8c 100644 --- a/chromium/components/autofill_assistant/browser/android/ui_controller_android.cc +++ b/chromium/components/autofill_assistant/browser/android/ui_controller_android.cc @@ -31,7 +31,7 @@ #include "components/autofill_assistant/android/jni_headers/AssistantPlaceholdersConfiguration_jni.h" #include "components/autofill_assistant/android/jni_headers/AutofillAssistantUiController_jni.h" #include "components/autofill_assistant/browser/android/client_android.h" -#include "components/autofill_assistant/browser/android/dependencies.h" +#include "components/autofill_assistant/browser/android/dependencies_android.h" #include "components/autofill_assistant/browser/android/generic_ui_root_controller_android.h" #include "components/autofill_assistant/browser/android/ui_controller_android_utils.h" #include "components/autofill_assistant/browser/bottom_sheet_state.h" @@ -246,17 +246,18 @@ std::unique_ptr<UiControllerAndroid> UiControllerAndroid::CreateFromWebContents( const base::android::JavaRef<jobject>& jdependencies, const base::android::JavaRef<jobject>& joverlay_coordinator) { JNIEnv* env = AttachCurrentThread(); - if (!Java_AutofillAssistantUiController_shouldCreateNewInstance( + if (!Java_AutofillAssistantUiController_canAttachUi( env, web_contents->GetJavaWebContents(), jdependencies)) { return nullptr; } - return std::make_unique<UiControllerAndroid>(env, jdependencies, + return std::make_unique<UiControllerAndroid>(env, web_contents, jdependencies, joverlay_coordinator); } UiControllerAndroid::UiControllerAndroid( JNIEnv* env, + content::WebContents* web_contents, const base::android::JavaRef<jobject>& jdependencies, const base::android::JavaRef<jobject>& joverlay_coordinator) : overlay_delegate_(this), @@ -265,9 +266,11 @@ UiControllerAndroid::UiControllerAndroid( form_delegate_(this), generic_ui_delegate_(this), bottom_bar_delegate_(this), - dependencies_(Dependencies::CreateFromJavaDependencies(jdependencies)) { + dependencies_( + DependenciesAndroid::CreateFromJavaDependencies(jdependencies)) { java_object_ = Java_AutofillAssistantUiController_Constructor( - env, reinterpret_cast<intptr_t>(this), jdependencies, + env, reinterpret_cast<intptr_t>(this), web_contents->GetJavaWebContents(), + jdependencies, /* allowTabSwitching= */ base::FeatureList::IsEnabled(features::kAutofillAssistantChromeEntry), joverlay_coordinator); @@ -281,8 +284,8 @@ UiControllerAndroid::UiControllerAndroid( // Register header_delegate_ as delegate for clicks on header buttons. header_model_->SetDelegate(header_delegate_); - // Register collect_user_data_delegate_ as delegate for the collect user data - // UI. + // Register collect_user_data_delegate_ as delegate for the collect user + // data UI. Java_AssistantCollectUserDataModel_setDelegate( env, GetCollectUserDataModel(), collect_user_data_delegate_.GetJavaObject()); @@ -315,12 +318,11 @@ void UiControllerAndroid::Attach(content::WebContents* web_contents, JNIEnv* env = AttachCurrentThread(); auto java_web_contents = web_contents->GetJavaWebContents(); - Java_AutofillAssistantUiController_setWebContents(env, java_object_, - java_web_contents); Java_AssistantCollectUserDataModel_setWebContents( env, GetCollectUserDataModel(), java_web_contents); Java_AssistantOverlayModel_setWebContents(env, GetOverlayModel(), java_web_contents); + OnClientSettingsChanged(execution_delegate_->GetClientSettings()); Java_AssistantModel_setPeekModeDisabled(env, GetModel(), false); @@ -642,6 +644,8 @@ void UiControllerAndroid::RestoreUi() { OnDetailsChanged(ui_delegate_->GetDetails()); OnUserActionsChanged(ui_delegate_->GetUserActions()); OnCollectUserDataOptionsChanged(ui_delegate_->GetCollectUserDataOptions()); + OnCollectUserDataUiStateChanged(/* loading= */ false, + UserDataEventField::NONE); OnUserDataChanged(*execution_delegate_->GetUserData(), UserDataFieldChange::ALL); OnPersistentGenericUserInterfaceChanged( @@ -803,13 +807,13 @@ void UiControllerAndroid::UpdateActions( jcancel_chip = Java_AutofillAssistantUiController_createCloseButton( env, java_object_, ICON_CLEAR, ConvertUTF8ToJavaString(env, ""), /* disabled= */ false, /* sticky= */ true, /* visible=*/true, - /* content_description= */ nullptr); + /* contentDescription= */ nullptr); } else if (execution_delegate_->GetState() != AutofillAssistantState::INACTIVE) { jcancel_chip = Java_AutofillAssistantUiController_createCancelButton( env, java_object_, ICON_CLEAR, ConvertUTF8ToJavaString(env, ""), -1, /* disabled= */ false, /* sticky= */ true, /* visible=*/true, - /* content_description= */ nullptr); + /* contentDescription= */ nullptr); } if (jcancel_chip) { Java_AutofillAssistantUiController_appendChipToList(env, jchips, @@ -1177,8 +1181,8 @@ void UiControllerAndroid::OnCollectUserDataOptionsChanged( Java_AssistantCollectUserDataModel_setShouldStoreUserDataChanges( env, jmodel, collect_user_data_options->should_store_data_changes); - Java_AssistantCollectUserDataModel_setUseGmsCoreEditDialogs( - env, jmodel, collect_user_data_options->use_gms_core_edit_dialogs); + Java_AssistantCollectUserDataModel_setUseAlternativeEditDialogs( + env, jmodel, collect_user_data_options->use_alternative_edit_dialogs); Java_AssistantCollectUserDataModel_setRequestName( env, jmodel, collect_user_data_options->request_payer_name); Java_AssistantCollectUserDataModel_setRequestEmail( @@ -1238,24 +1242,12 @@ void UiControllerAndroid::OnCollectUserDataOptionsChanged( base::android::ToJavaArrayOfStrings( env, collect_user_data_options->supported_basic_card_networks)); if (collect_user_data_options->data_origin_notice) { - Java_AssistantCollectUserDataModel_setDataOriginLinkText( - env, jmodel, - ConvertUTF8ToJavaString( - env, collect_user_data_options->data_origin_notice->link_text())); - Java_AssistantCollectUserDataModel_setDataOriginDialogTitle( - env, jmodel, - ConvertUTF8ToJavaString( - env, - collect_user_data_options->data_origin_notice->dialog_title())); - Java_AssistantCollectUserDataModel_setDataOriginDialogText( - env, jmodel, - ConvertUTF8ToJavaString( - env, collect_user_data_options->data_origin_notice->dialog_text())); - Java_AssistantCollectUserDataModel_setDataOriginDialogButtonText( - env, jmodel, - ConvertUTF8ToJavaString(env, - collect_user_data_options->data_origin_notice - ->dialog_button_text())); + const auto& configuration = *collect_user_data_options->data_origin_notice; + Java_AssistantCollectUserDataModel_setDataOriginNoticeConfiguration( + env, jmodel, ConvertUTF8ToJavaString(env, configuration.link_text()), + ConvertUTF8ToJavaString(env, configuration.dialog_title()), + ConvertUTF8ToJavaString(env, configuration.dialog_text()), + ConvertUTF8ToJavaString(env, configuration.dialog_button_text())); } if (collect_user_data_options->request_login_choice) { auto jlist = CreateJavaLoginChoiceList( @@ -1323,6 +1315,27 @@ void UiControllerAndroid::OnCollectUserDataOptionsChanged( Java_AssistantCollectUserDataModel_setVisible(env, jmodel, true); } +void UiControllerAndroid::OnCollectUserDataUiStateChanged( + bool loading, + UserDataEventField event_field) { + JNIEnv* env = AttachCurrentThread(); + auto jmodel = GetCollectUserDataModel(); + + Java_AssistantCollectUserDataModel_setEnableUiInteractions(env, jmodel, + !loading); + Java_AssistantCollectUserDataModel_setMarkContactsLoading( + env, jmodel, loading && event_field == UserDataEventField::CONTACT_EVENT); + Java_AssistantCollectUserDataModel_setMarkPhoneNumbersLoading( + env, jmodel, + loading && event_field == UserDataEventField::PHONE_NUMBER_EVENT); + Java_AssistantCollectUserDataModel_setMarkShippingAddressesLoading( + env, jmodel, + loading && event_field == UserDataEventField::SHIPPING_EVENT); + Java_AssistantCollectUserDataModel_setMarkPaymentMethodsLoading( + env, jmodel, + loading && event_field == UserDataEventField::CREDIT_CARD_EVENT); +} + void UiControllerAndroid::OnUserDataChanged(const UserData& user_data, UserDataFieldChange field_change) { DCHECK(execution_delegate_ != nullptr); @@ -1362,7 +1375,7 @@ void UiControllerAndroid::OnUserDataChanged(const UserData& user_data, env, *user_data.available_contacts_[index]->profile, base::android::GetDefaultLocaleString()), base::android::ToJavaArrayOfStrings(env, errors), - collect_user_data_options->can_edit_contacts); + user_data.available_contacts_[index]->can_edit); } Java_AssistantCollectUserDataModel_setAvailableContacts(env, jmodel, jcontactlist); @@ -1409,7 +1422,7 @@ void UiControllerAndroid::OnUserDataChanged(const UserData& user_data, const auto& errors = user_data::GetShippingAddressValidationErrors( shipping_address->profile.get(), *collect_user_data_options); auto jedit_token = - collect_user_data_options->use_gms_core_edit_dialogs + collect_user_data_options->use_alternative_edit_dialogs ? ToJavaByteArray( env, shipping_address->edit_token.value_or(std::string())) : nullptr; @@ -1443,13 +1456,26 @@ void UiControllerAndroid::OnUserDataChanged(const UserData& user_data, const auto& selected_contact_errors = user_data::GetContactValidationErrors( selected_contact_profile, *collect_user_data_options); + bool can_edit = true; + if (selected_contact_profile) { + const auto& contact_it = base::ranges::find_if( + user_data.available_contacts_, + [&](const std::unique_ptr<Contact>& contact) { + return contact->profile && + contact->profile->guid() == selected_contact_profile->guid(); + }); + if (contact_it != user_data.available_contacts_.end()) { + can_edit = (*contact_it)->can_edit; + } + } + // In the UserDataFieldChange::CONTACT_PROFILE case the selection is // already known in Java, but it has no errors. The PDM off case does not // set updated contacts. Java_AssistantCollectUserDataModel_setSelectedContactDetails( env, jmodel, jselected_contact, base::android::ToJavaArrayOfStrings(env, selected_contact_errors), - collect_user_data_options->can_edit_contacts); + can_edit); } if (field_change == UserDataFieldChange::ALL || @@ -1467,13 +1493,26 @@ void UiControllerAndroid::OnUserDataChanged(const UserData& user_data, user_data::GetPhoneNumberValidationErrors(selected_phone_number, *collect_user_data_options); + bool can_edit = true; + if (selected_phone_number) { + const auto& phone_number_it = base::ranges::find_if( + user_data.available_phone_numbers_, + [&](const std::unique_ptr<PhoneNumber>& phone_number) { + return phone_number->profile && phone_number->profile->guid() == + selected_phone_number->guid(); + }); + if (phone_number_it != user_data.available_phone_numbers_.end()) { + can_edit = (*phone_number_it)->can_edit; + } + } + // In the UserDataFieldChange::PHONE_NUMBER case the selection is already // known in Java, but it has no errors. The PDM off case does not set // updated phone numbers. Java_AssistantCollectUserDataModel_setSelectedPhoneNumber( env, jmodel, jselected_phone_number, base::android::ToJavaArrayOfStrings(env, selected_phone_number_errors), - /* canEdit = */ false); + can_edit); } if (field_change == UserDataFieldChange::ALL || @@ -1492,7 +1531,7 @@ void UiControllerAndroid::OnUserDataChanged(const UserData& user_data, user_data::GetShippingAddressValidationErrors( selected_shipping_address, *collect_user_data_options); auto jselected_shipping_address_edit_token = - collect_user_data_options->use_gms_core_edit_dialogs + collect_user_data_options->use_alternative_edit_dialogs ? ToJavaByteArray(env, std::string()) : nullptr; if (selected_shipping_address) { @@ -1545,7 +1584,7 @@ void UiControllerAndroid::OnUserDataChanged(const UserData& user_data, instrument->card.get(), instrument->billing_address.get(), *collect_user_data_options); auto jedit_token = - collect_user_data_options->use_gms_core_edit_dialogs + collect_user_data_options->use_alternative_edit_dialogs ? ToJavaByteArray(env, instrument->edit_token.value_or(std::string())) : nullptr; @@ -1588,7 +1627,7 @@ void UiControllerAndroid::OnUserDataChanged(const UserData& user_data, selected_card, selected_billing_address, *collect_user_data_options); auto jselected_payment_instrument_edit_token = - collect_user_data_options->use_gms_core_edit_dialogs + collect_user_data_options->use_alternative_edit_dialogs ? ToJavaByteArray(env, std::string()) : nullptr; if (selected_card) { @@ -1991,7 +2030,6 @@ void UiControllerAndroid::OnStart(const TriggerContext& trigger_context) {} void UiControllerAndroid::OnStop() {} void UiControllerAndroid::OnResetState() {} void UiControllerAndroid::OnUiShownChanged(bool shown) {} -void UiControllerAndroid::OnShutdown(Metrics::DropOutReason reason) {} base::android::ScopedJavaLocalRef<jobject> UiControllerAndroid::GetGenericUiModel() { diff --git a/chromium/components/autofill_assistant/browser/android/ui_controller_android.h b/chromium/components/autofill_assistant/browser/android/ui_controller_android.h index 21a4b6ce263..563ca9e418a 100644 --- a/chromium/components/autofill_assistant/browser/android/ui_controller_android.h +++ b/chromium/components/autofill_assistant/browser/android/ui_controller_android.h @@ -19,7 +19,7 @@ #include "components/autofill_assistant/browser/android/assistant_header_delegate.h" #include "components/autofill_assistant/browser/android/assistant_header_model.h" #include "components/autofill_assistant/browser/android/assistant_overlay_delegate.h" -#include "components/autofill_assistant/browser/android/dependencies.h" +#include "components/autofill_assistant/browser/android/dependencies_android.h" #include "components/autofill_assistant/browser/chip.h" #include "components/autofill_assistant/browser/controller_observer.h" #include "components/autofill_assistant/browser/details.h" @@ -54,6 +54,7 @@ class UiControllerAndroid : public ControllerObserver, UiControllerObserver { UiControllerAndroid( JNIEnv* env, + content::WebContents* web_contents, const base::android::JavaRef<jobject>& jdependencies, const base::android::JavaRef<jobject>& joverlay_coordinator); @@ -118,7 +119,6 @@ class UiControllerAndroid : public ControllerObserver, UiControllerObserver { void OnStop() override; void OnResetState() override; void OnUiShownChanged(bool shown) override; - void OnShutdown(Metrics::DropOutReason reason) override; // Overrides UiControllerObserver: void OnStatusMessageChanged(const std::string& message) override; @@ -126,6 +126,8 @@ class UiControllerAndroid : public ControllerObserver, UiControllerObserver { void OnUserActionsChanged(const std::vector<UserAction>& actions) override; void OnCollectUserDataOptionsChanged( const CollectUserDataOptions* collect_user_data_options) override; + void OnCollectUserDataUiStateChanged(bool loading, + UserDataEventField event_field) override; void OnDetailsChanged(const std::vector<Details>& details) override; void OnInfoBoxChanged(const InfoBox* info_box) override; void OnProgressActiveStepChanged(int active_step) override; @@ -308,7 +310,7 @@ class UiControllerAndroid : public ControllerObserver, UiControllerObserver { // Java-side AssistantStaticDependencies object. This never changes during the // life of the application. - const std::unique_ptr<const Dependencies> dependencies_; + const std::unique_ptr<const DependenciesAndroid> dependencies_; // Native controllers for generic UI. std::unique_ptr<GenericUiRootControllerAndroid> diff --git a/chromium/components/autofill_assistant/browser/android/ui_controller_android_utils.cc b/chromium/components/autofill_assistant/browser/android/ui_controller_android_utils.cc index 0b4f2b6d36a..2193988eea8 100644 --- a/chromium/components/autofill_assistant/browser/android/ui_controller_android_utils.cc +++ b/chromium/components/autofill_assistant/browser/android/ui_controller_android_utils.cc @@ -25,7 +25,7 @@ #include "components/autofill_assistant/android/jni_headers_public/AssistantAutofillCreditCard_jni.h" #include "components/autofill_assistant/android/jni_headers_public/AssistantAutofillProfile_jni.h" #include "components/autofill_assistant/browser/android/client_android.h" -#include "components/autofill_assistant/browser/android/dependencies.h" +#include "components/autofill_assistant/browser/android/dependencies_android.h" #include "components/autofill_assistant/browser/generic_ui_java_generated_enums.h" #include "components/autofill_assistant/browser/service/service.h" #include "components/autofill_assistant/browser/service/service_request_sender.h" @@ -172,7 +172,7 @@ int GetPixelSizeOrDefault(JNIEnv* env, base::android::ScopedJavaLocalRef<jobject> CreateJavaDrawable( JNIEnv* env, const JavaRef<jobject>& jcontext, - const Dependencies& dependencies, + const DependenciesAndroid& dependencies, const DrawableProto& proto, const UserModel* user_model) { switch (proto.drawable_case()) { @@ -724,7 +724,8 @@ base::android::ScopedJavaLocalRef<jobject> CreateAssistantAutofillCreditCard( credit_card.instrument_id(), ConvertUTF16ToJavaString(env, credit_card.nickname()), url::GURLAndroid::FromNativeGURL(env, credit_card.card_art_url()), - static_cast<jint>(credit_card.virtual_card_enrollment_state())); + static_cast<jint>(credit_card.virtual_card_enrollment_state()), + ConvertUTF16ToJavaString(env, credit_card.product_description())); } void PopulateAutofillCreditCardFromJava( @@ -792,6 +793,9 @@ void PopulateAutofillCreditCardFromJava( static_cast<autofill::CreditCard::VirtualCardEnrollmentState>( Java_AssistantAutofillCreditCard_getVirtualCardEnrollmentState( env, jcredit_card))); + credit_card->set_product_description(ConvertJavaStringToUTF16( + Java_AssistantAutofillCreditCard_getProductDescription(env, + jcredit_card))); } } // namespace ui_controller_android_utils diff --git a/chromium/components/autofill_assistant/browser/android/ui_controller_android_utils.h b/chromium/components/autofill_assistant/browser/android/ui_controller_android_utils.h index 8bb0b256664..4519ae5839a 100644 --- a/chromium/components/autofill_assistant/browser/android/ui_controller_android_utils.h +++ b/chromium/components/autofill_assistant/browser/android/ui_controller_android_utils.h @@ -12,7 +12,7 @@ #include "base/containers/flat_map.h" #include "components/autofill/core/browser/data_model/autofill_profile.h" #include "components/autofill/core/browser/data_model/credit_card.h" -#include "components/autofill_assistant/browser/android/dependencies.h" +#include "components/autofill_assistant/browser/android/dependencies_android.h" #include "components/autofill_assistant/browser/autofill_assistant_tts_controller.h" #include "components/autofill_assistant/browser/bottom_sheet_state.h" #include "components/autofill_assistant/browser/service.pb.h" @@ -63,7 +63,7 @@ int GetPixelSizeOrDefault(JNIEnv* env, base::android::ScopedJavaLocalRef<jobject> CreateJavaDrawable( JNIEnv* env, const base::android::JavaRef<jobject>& jcontext, - const Dependencies& dependencies, + const DependenciesAndroid& dependencies, const DrawableProto& proto, const UserModel* user_model = nullptr); diff --git a/chromium/components/autofill_assistant/browser/autofill_assistant_factory.cc b/chromium/components/autofill_assistant/browser/autofill_assistant_factory.cc index 425214f1bdc..c3161c808ca 100644 --- a/chromium/components/autofill_assistant/browser/autofill_assistant_factory.cc +++ b/chromium/components/autofill_assistant/browser/autofill_assistant_factory.cc @@ -4,8 +4,10 @@ #include "components/autofill_assistant/browser/public/autofill_assistant_factory.h" +#include <memory> + #include "components/autofill_assistant/browser/autofill_assistant_impl.h" -#include "components/version_info/channel.h" +#include "components/autofill_assistant/browser/common_dependencies.h" namespace autofill_assistant { @@ -13,11 +15,9 @@ namespace autofill_assistant { std::unique_ptr<AutofillAssistant> AutofillAssistantFactory::CreateForBrowserContext( content::BrowserContext* browser_context, - version_info::Channel channel, - const std::string& country_code, - const std::string& locale) { - return AutofillAssistantImpl::Create(browser_context, channel, country_code, - locale); + std::unique_ptr<CommonDependencies> dependencies) { + return AutofillAssistantImpl::Create(browser_context, + std::move(dependencies)); } } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/autofill_assistant_impl.cc b/chromium/components/autofill_assistant/browser/autofill_assistant_impl.cc index aa09db0d553..f4cd369d786 100644 --- a/chromium/components/autofill_assistant/browser/autofill_assistant_impl.cc +++ b/chromium/components/autofill_assistant/browser/autofill_assistant_impl.cc @@ -4,8 +4,10 @@ #include "components/autofill_assistant/browser/autofill_assistant_impl.h" +#include <memory> #include <vector> +#include "components/autofill_assistant/browser/common_dependencies.h" #include "components/autofill_assistant/browser/desktop/starter_delegate_desktop.h" #include "components/autofill_assistant/browser/headless/external_script_controller_impl.h" #include "components/autofill_assistant/browser/protocol_utils.h" @@ -16,7 +18,6 @@ #include "components/autofill_assistant/browser/service/service_request_sender.h" #include "components/autofill_assistant/browser/service/service_request_sender_impl.h" #include "components/autofill_assistant/browser/service/simple_url_loader_factory.h" -#include "components/version_info/channel.h" #include "components/version_info/version_info.h" #include "content/public/browser/browser_context.h" #include "net/http/http_status_code.h" @@ -68,31 +69,30 @@ void OnCapabilitiesResponse( // static std::unique_ptr<AutofillAssistantImpl> AutofillAssistantImpl::Create( content::BrowserContext* browser_context, - version_info::Channel channel, - const std::string& country_code, - const std::string& locale) { + std::unique_ptr<CommonDependencies> dependencies) { auto request_sender = std::make_unique<ServiceRequestSenderImpl>( browser_context, /* access_token_fetcher = */ nullptr, std::make_unique<cup::CUPImplFactory>(), std::make_unique<NativeURLLoaderFactory>(), - ApiKeyFetcher().GetAPIKey(channel)); + ApiKeyFetcher().GetAPIKey(dependencies->GetChannel())); const ServerUrlFetcher& url_fetcher = ServerUrlFetcher(ServerUrlFetcher::GetDefaultServerUrl()); + return std::make_unique<AutofillAssistantImpl>( - std::move(request_sender), url_fetcher.GetCapabilitiesByHashEndpoint(), - country_code, locale); + browser_context, std::move(request_sender), std::move(dependencies), + url_fetcher.GetCapabilitiesByHashEndpoint()); } AutofillAssistantImpl::AutofillAssistantImpl( + content::BrowserContext* browser_context, std::unique_ptr<ServiceRequestSender> request_sender, - const GURL& script_server_url, - const std::string& country_code, - const std::string& locale) - : request_sender_(std::move(request_sender)), + std::unique_ptr<CommonDependencies> dependencies, + const GURL& script_server_url) + : browser_context_(browser_context), + request_sender_(std::move(request_sender)), script_server_url_(script_server_url), - country_code_(country_code), - locale_(locale) {} + dependencies_(std::move(dependencies)) {} AutofillAssistantImpl::~AutofillAssistantImpl() = default; @@ -101,16 +101,30 @@ void AutofillAssistantImpl::GetCapabilitiesByHashPrefix( const std::vector<uint64_t>& hash_prefixes, const std::string& intent, GetCapabilitiesResponseCallback callback) { + // Always return an empty response for supervised users. + if (dependencies_->IsSupervisedUser(browser_context_)) { + std::move(callback).Run(net::HTTP_OK, {}); + return; + } + const ScriptParameters& parameters = { base::flat_map<std::string, std::string>{ {kIntentScriptParameterKey, intent}}}; ClientContextProto client_context; - client_context.set_country(country_code_); - client_context.set_locale(locale_); + client_context.set_country(dependencies_->GetCountryCode()); + client_context.set_locale(dependencies_->GetLocale()); client_context.mutable_chrome()->set_chrome_version( version_info::GetProductNameAndVersionForUserAgent()); +#if BUILDFLAG(IS_ANDROID) + client_context.set_platform_type(ClientContextProto::PLATFORM_TYPE_ANDROID); +#endif +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC) || \ + BUILDFLAG(IS_WIN) || BUILDFLAG(IS_FUCHSIA) + client_context.set_platform_type(ClientContextProto::PLATFORM_TYPE_DESKTOP); +#endif + request_sender_->SendRequest( script_server_url_, ProtocolUtils::CreateCapabilitiesByHashRequest( @@ -118,13 +132,14 @@ void AutofillAssistantImpl::GetCapabilitiesByHashPrefix( ServiceRequestSender::AuthMode::API_KEY, base::BindOnce(&OnCapabilitiesResponse, std::move(callback)), RpcType::GET_CAPABILITIES_BY_HASH_PREFIX); - return; } std::unique_ptr<ExternalScriptController> AutofillAssistantImpl::CreateExternalScriptController( - content::WebContents* web_contents) { - return std::make_unique<ExternalScriptControllerImpl>(web_contents); + content::WebContents* web_contents, + ExternalActionDelegate* action_extension_delegate) { + return std::make_unique<ExternalScriptControllerImpl>( + web_contents, action_extension_delegate); } } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/autofill_assistant_impl.h b/chromium/components/autofill_assistant/browser/autofill_assistant_impl.h index c973ecf1ce9..1d12585b564 100644 --- a/chromium/components/autofill_assistant/browser/autofill_assistant_impl.h +++ b/chromium/components/autofill_assistant/browser/autofill_assistant_impl.h @@ -5,31 +5,37 @@ #ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_AUTOFILL_ASSISTANT_IMPL_H_ #define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_AUTOFILL_ASSISTANT_IMPL_H_ +#include <memory> #include <vector> +#include "base/memory/raw_ptr.h" #include "components/autofill_assistant/browser/public/autofill_assistant.h" #include "components/autofill_assistant/browser/public/external_script_controller.h" #include "components/autofill_assistant/browser/service/service_request_sender.h" -#include "components/version_info/version_info.h" -#include "content/public/browser/browser_context.h" + +namespace content { +class BrowserContext; +class WebContents; +} // namespace content namespace autofill_assistant { +class CommonDependencies; + class AutofillAssistantImpl : public autofill_assistant::AutofillAssistant { public: static std::unique_ptr<AutofillAssistantImpl> Create( content::BrowserContext* browser_context, - version_info::Channel channel, - const std::string& country_code, - const std::string& locale); - - AutofillAssistantImpl(std::unique_ptr<ServiceRequestSender> request_sender, - const GURL& script_server_url, - const std::string& country_code, - const std::string& locale); + std::unique_ptr<CommonDependencies> dependencies); + + AutofillAssistantImpl(content::BrowserContext* browser_context, + std::unique_ptr<ServiceRequestSender> request_sender, + std::unique_ptr<CommonDependencies> dependencies, + const GURL& script_server_url); + ~AutofillAssistantImpl() override; + AutofillAssistantImpl(const AutofillAssistantImpl&) = delete; AutofillAssistantImpl& operator=(const AutofillAssistantImpl&) = delete; - ~AutofillAssistantImpl() override; void GetCapabilitiesByHashPrefix( uint32_t hash_prefix_length, @@ -38,17 +44,22 @@ class AutofillAssistantImpl : public autofill_assistant::AutofillAssistant { GetCapabilitiesResponseCallback callback) override; std::unique_ptr<ExternalScriptController> CreateExternalScriptController( - content::WebContents* web_contents) override; + content::WebContents* web_contents, + ExternalActionDelegate* action_extension_delegate) override; private: + // The `BrowserContext` for which this `AutofillAssistantImpl` was created + // and which must outlive it. + const raw_ptr<content::BrowserContext> browser_context_; + // The request sender responsible for communicating with a remote endpoint. std::unique_ptr<ServiceRequestSender> request_sender_; + // The RPC endpoint to send requests to. GURL script_server_url_; - // The client's country code. - std::string country_code_; - // The client's locale. - std::string locale_; + + // Dependencies on client code such as country code or locale. + std::unique_ptr<CommonDependencies> dependencies_; }; } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/autofill_assistant_impl_unittest.cc b/chromium/components/autofill_assistant/browser/autofill_assistant_impl_unittest.cc index 65d49dd0f99..9e6e5ef71e4 100644 --- a/chromium/components/autofill_assistant/browser/autofill_assistant_impl_unittest.cc +++ b/chromium/components/autofill_assistant/browser/autofill_assistant_impl_unittest.cc @@ -7,6 +7,7 @@ #include "base/memory/raw_ptr.h" #include "base/test/gmock_callback_support.h" #include "base/test/mock_callback.h" +#include "components/autofill_assistant/browser/mock_common_dependencies.h" #include "components/autofill_assistant/browser/service.pb.h" #include "components/autofill_assistant/browser/service/mock_service_request_sender.h" #include "components/version_info/version_info.h" @@ -32,8 +33,18 @@ class AutofillAssistantImpTest : public testing::Test { std::make_unique<NiceMock<MockServiceRequestSender>>(); mock_request_sender_ = mock_request_sender.get(); + auto mock_common_dependencies = std::make_unique<MockCommonDependencies>(); + mock_dependencies_ = mock_common_dependencies.get(); + ON_CALL(*mock_dependencies_, GetCountryCode).WillByDefault(Return("US")); + ON_CALL(*mock_dependencies_, GetLocale).WillByDefault(Return("en-US")); + ON_CALL(*mock_dependencies_, IsSupervisedUser).WillByDefault(Return(false)); + + // As long as the `BrowserContext` is only passed as an argument during + // `CommonDependencies` calls, we do not need to set up a test environment + // for it. service_ = std::make_unique<AutofillAssistantImpl>( - std::move(mock_request_sender), GURL(kScriptServerUrl), "US", "en-US"); + /* browser_context= */ nullptr, std::move(mock_request_sender), + std::move(mock_common_dependencies), GURL(kScriptServerUrl)); } ~AutofillAssistantImpTest() override = default; @@ -41,6 +52,7 @@ class AutofillAssistantImpTest : public testing::Test { base::MockCallback<AutofillAssistant::GetCapabilitiesResponseCallback> mock_response_callback_; raw_ptr<NiceMock<MockServiceRequestSender>> mock_request_sender_; + raw_ptr<MockCommonDependencies> mock_dependencies_; std::unique_ptr<AutofillAssistantImpl> service_; }; @@ -132,4 +144,18 @@ TEST_F(AutofillAssistantImpTest, GetCapabilitiesByHashPrefix) { mock_response_callback_.Get()); } +TEST_F(AutofillAssistantImpTest, + GetCapabilitiesByHashPrefixDoesNotExecuteForSupervisedUsers) { + EXPECT_CALL(*mock_dependencies_, IsSupervisedUser).WillOnce(Return(true)); + + EXPECT_CALL(*mock_request_sender_, OnSendRequest).Times(0); + + EXPECT_CALL( + mock_response_callback_, + Run(net::HTTP_OK, std::vector<AutofillAssistant::CapabilitiesInfo>())); + + service_->GetCapabilitiesByHashPrefix(16, {1339}, "DUMMY_INTENT", + mock_response_callback_.Get()); +} + } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/autofill_assistant_tts_controller_unittest.cc b/chromium/components/autofill_assistant/browser/autofill_assistant_tts_controller_unittest.cc index fe18483994b..0e4f322768c 100644 --- a/chromium/components/autofill_assistant/browser/autofill_assistant_tts_controller_unittest.cc +++ b/chromium/components/autofill_assistant/browser/autofill_assistant_tts_controller_unittest.cc @@ -44,6 +44,7 @@ class MockTtsController : public content::TtsController { content::TtsEngineDelegate* GetTtsEngineDelegate() override { return nullptr; } + void RefreshVoices() override {} void SetTtsPlatform(content::TtsPlatform* tts_platform) override {} int QueueSize() override { return 0; } void StripSSML( diff --git a/chromium/components/autofill_assistant/browser/basic_interactions.cc b/chromium/components/autofill_assistant/browser/basic_interactions.cc index 5f6d8ab32c2..56f3a5416a8 100644 --- a/chromium/components/autofill_assistant/browser/basic_interactions.cc +++ b/chromium/components/autofill_assistant/browser/basic_interactions.cc @@ -136,14 +136,20 @@ bool ValueToString(UserModel* user_model, return false; } auto date = value->dates().values(i); + + // Technically we are setting the wrong |day_of_week|, but it's ignored + // in practice and the formatted string will have the correct day for + // the date. Setting an invalid value here (e.g. -1) causes issues on + // Windows. base::Time::Exploded exploded_time = {static_cast<int>(date.year()), date.month(), - /* day_of_week = */ -1, + /* day_of_week = */ 0, date.day(), /* hour = */ 0, /* minute = */ 0, /* second = */ 0, /* millisecond = */ 0}; + base::Time time; if (!base::Time::FromLocalExploded(exploded_time, &time)) { DVLOG(2) << "Error evaluating " << __func__ << ": invalid date " diff --git a/chromium/components/autofill_assistant/browser/batch_element_checker.cc b/chromium/components/autofill_assistant/browser/batch_element_checker.cc index d76adcc46e6..4dd8817c36d 100644 --- a/chromium/components/autofill_assistant/browser/batch_element_checker.cc +++ b/chromium/components/autofill_assistant/browser/batch_element_checker.cc @@ -15,7 +15,7 @@ #include "base/threading/thread_task_runner_handle.h" #include "base/time/time.h" #include "components/autofill_assistant/browser/web/element_action_util.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/selector_observer.h" #include "components/autofill_assistant/browser/web/web_controller.h" @@ -76,19 +76,13 @@ void BatchElementChecker::AddAllDoneCallback( } void BatchElementChecker::EnableObserver( - base::TimeDelta max_wait_time, - base::TimeDelta periodic_check_interval, - base::TimeDelta extra_timeout) { - DCHECK(!use_observers_); + const SelectorObserver::Settings& settings) { + DCHECK(!observer_settings_); DCHECK(!started_); DCHECK(get_field_value_callbacks_.empty()) << "Observer-based BatchElementChecker doesn't work with " "AddFieldValueCheck"; - - use_observers_ = true; - observer_max_wait_time_ = max_wait_time; - observer_periodic_check_interval_ = periodic_check_interval; - observer_extra_timeout_ = extra_timeout; + observer_settings_.emplace(settings); } void BatchElementChecker::Run(WebController* web_controller) { @@ -97,7 +91,7 @@ void BatchElementChecker::Run(WebController* web_controller) { for (size_t i = 0; i < element_condition_checks_.size(); ++i) { AddElementConditionResults(element_condition_checks_[i].proto, i); } - if (use_observers_) { + if (observer_settings_) { RunWithObserver(web_controller); return; } @@ -149,6 +143,7 @@ void BatchElementChecker::RunWithObserver(WebController* web_controller) { DCHECK(get_field_value_callbacks_.empty()) << "Observer-based BatchElementChecker doesn't work with " "AddFieldValueCheck"; + DCHECK(observer_settings_); DCHECK(!started_); std::vector<SelectorObserver::ObservableSelector> selectors; @@ -164,8 +159,7 @@ void BatchElementChecker::RunWithObserver(WebController* web_controller) { } started_ = true; auto result = web_controller->ObserveSelectors( - selectors, observer_max_wait_time_, observer_periodic_check_interval_, - observer_extra_timeout_, + selectors, *observer_settings_, base::BindRepeating(&BatchElementChecker::OnResultsUpdated, weak_ptr_factory_.GetWeakPtr()) diff --git a/chromium/components/autofill_assistant/browser/batch_element_checker.h b/chromium/components/autofill_assistant/browser/batch_element_checker.h index 9b9067b79ed..9052da9244b 100644 --- a/chromium/components/autofill_assistant/browser/batch_element_checker.h +++ b/chromium/components/autofill_assistant/browser/batch_element_checker.h @@ -20,10 +20,10 @@ #include "components/autofill_assistant/browser/selector.h" #include "components/autofill_assistant/browser/service.pb.h" #include "components/autofill_assistant/browser/web/element.h" -#include "components/autofill_assistant/browser/web/element_finder.h" #include "components/autofill_assistant/browser/web/selector_observer.h" namespace autofill_assistant { +class ElementFinderResult; class WebController; // Helper for checking a set of elements at the same time. It avoids duplicate @@ -84,9 +84,7 @@ class BatchElementChecker { // Turns on observer mode. When BatchElementChecker runs in observer mode, it // waits until any element condition checks or element checks become true. - void EnableObserver(base::TimeDelta max_wait_time, - base::TimeDelta periodic_check_interval, - base::TimeDelta extra_timeout); + void EnableObserver(const SelectorObserver::Settings& settings); // Runs the checks. Once all checks are done, calls the callbacks registered // to AddAllDoneCallback(). @@ -207,11 +205,10 @@ class BatchElementChecker { // Run() was called. Checking elements might or might not have finished yet. bool started_ = false; - // Whether to wait until one of the conditions becomes true. - bool use_observers_ = false; - base::TimeDelta observer_max_wait_time_; - base::TimeDelta observer_periodic_check_interval_; - base::TimeDelta observer_extra_timeout_; + // Whether to wait until one of the conditions becomes true. If it's a nullopt + // it will check only once, if it has a value it will observe the DOM until + // one of the conditions becomes true or the observation times out. + absl::optional<const SelectorObserver::Settings> observer_settings_; std::vector<base::OnceCallback<void()>> all_done_; diff --git a/chromium/components/autofill_assistant/browser/batch_element_checker_unittest.cc b/chromium/components/autofill_assistant/browser/batch_element_checker_unittest.cc index 48e52d40d3e..7b02f501e4e 100644 --- a/chromium/components/autofill_assistant/browser/batch_element_checker_unittest.cc +++ b/chromium/components/autofill_assistant/browser/batch_element_checker_unittest.cc @@ -12,7 +12,7 @@ #include "base/test/mock_callback.h" #include "base/test/task_environment.h" #include "components/autofill_assistant/browser/actions/action_test_utils.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/mock_web_controller.h" #include "testing/gmock/include/gmock/gmock.h" diff --git a/chromium/components/autofill_assistant/browser/client.h b/chromium/components/autofill_assistant/browser/client.h index aff4d5916ab..8e597c1defd 100644 --- a/chromium/components/autofill_assistant/browser/client.h +++ b/chromium/components/autofill_assistant/browser/client.h @@ -61,7 +61,7 @@ class Client { // Returns the e-mail address used to sign into Chrome, or an empty string if // the user is not signed in. - virtual std::string GetChromeSignedInEmailAddress() const = 0; + virtual std::string GetSignedInEmail() const = 0; // Returns the AccessTokenFetcher to use to get oauth credentials. virtual AccessTokenFetcher* GetAccessTokenFetcher() = 0; @@ -122,6 +122,14 @@ class Client { // nullptr. virtual ScriptExecutorUiDelegate* GetScriptExecutorUiDelegate() = 0; + // Returns whether or not this instance of Autofill Assistant must use a + // backend endpoint to query data. + virtual bool MustUseBackendData() const = 0; + + // Return the annotate DOM model version, if available. + virtual void GetAnnotateDomModelVersion( + base::OnceCallback<void(absl::optional<int64_t>)> callback) const = 0; + protected: Client() = default; }; diff --git a/chromium/components/autofill_assistant/browser/client_context.cc b/chromium/components/autofill_assistant/browser/client_context.cc index e3cbab6677e..35589ce16d8 100644 --- a/chromium/components/autofill_assistant/browser/client_context.cc +++ b/chromium/components/autofill_assistant/browser/client_context.cc @@ -14,6 +14,15 @@ ClientContextImpl::ClientContextImpl(const Client* client) : client_(client) { version_info::GetProductNameAndVersionForUserAgent()); proto_.set_locale(client->GetLocale()); proto_.set_country(client->GetCountryCode()); +// TODO(crbug.com/1321034): Once PlatformDependencies exist and are exposed to +// |Client|, move this check to calls of type |client->IsDesktop()|. +#if BUILDFLAG(IS_ANDROID) + proto_.set_platform_type(ClientContextProto::PLATFORM_TYPE_ANDROID); +#endif +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC) || \ + BUILDFLAG(IS_WIN) || BUILDFLAG(IS_FUCHSIA) + proto_.set_platform_type(ClientContextProto::PLATFORM_TYPE_DESKTOP); +#endif base::FieldTrial::ActiveGroups active_groups; base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups); @@ -30,9 +39,8 @@ ClientContextImpl::ClientContextImpl(const Client* client) : client_(client) { void ClientContextImpl::Update(const TriggerContext& trigger_context) { proto_.set_accessibility_enabled(client_->IsAccessibilityEnabled()); - std::string chrome_signed_in_email_address = - client_->GetChromeSignedInEmailAddress(); - proto_.set_signed_into_chrome_status(chrome_signed_in_email_address.empty() + const std::string signed_in_email = client_->GetSignedInEmail(); + proto_.set_signed_into_chrome_status(signed_in_email.empty() ? ClientContextProto::NOT_SIGNED_IN : ClientContextProto::SIGNED_IN); @@ -58,7 +66,7 @@ void ClientContextImpl::Update(const TriggerContext& trigger_context) { if (!caller_email.has_value()) { proto_.set_accounts_matching_status(ClientContextProto::UNKNOWN); } else { - if (chrome_signed_in_email_address == caller_email) { + if (signed_in_email == caller_email) { proto_.set_accounts_matching_status( ClientContextProto::ACCOUNTS_MATCHING); } else { @@ -78,6 +86,15 @@ void ClientContextImpl::Update(const TriggerContext& trigger_context) { proto_.set_screen_orientation(client_->GetScreenOrientation()); } +void ClientContextImpl::UpdateAnnotateDomModelContext(int64_t model_version) { + proto_.mutable_annotate_dom_model_context()->set_model_version(model_version); +} + +void ClientContextImpl::UpdateJsFlowLibraryLoaded( + const bool js_flow_library_loaded) { + proto_.set_js_flow_library_loaded(js_flow_library_loaded); +} + ClientContextProto ClientContextImpl::AsProto() const { return proto_; } diff --git a/chromium/components/autofill_assistant/browser/client_context.h b/chromium/components/autofill_assistant/browser/client_context.h index e6cede8ac45..64665a00aa1 100644 --- a/chromium/components/autofill_assistant/browser/client_context.h +++ b/chromium/components/autofill_assistant/browser/client_context.h @@ -18,6 +18,10 @@ class ClientContext { virtual ~ClientContext() = default; // Updates the client context based on the current state of the client. virtual void Update(const TriggerContext& trigger_context) = 0; + // Updates the annotate DOM model context. + virtual void UpdateAnnotateDomModelContext(int64_t model_version) {} + // Updates whether the JS flow library is loaded. + virtual void UpdateJsFlowLibraryLoaded(bool js_flow_library_loaded){}; // Returns the proto representation of this client context. virtual ClientContextProto AsProto() const = 0; }; @@ -26,9 +30,11 @@ class ClientContext { class ClientContextImpl : public ClientContext { public: // |client| must outlive this instance. - ClientContextImpl(const Client* client); + explicit ClientContextImpl(const Client* client); ~ClientContextImpl() override = default; void Update(const TriggerContext& trigger_context) override; + void UpdateAnnotateDomModelContext(int64_t model_version) override; + void UpdateJsFlowLibraryLoaded(bool js_flow_library_loaded) override; ClientContextProto AsProto() const override; private: diff --git a/chromium/components/autofill_assistant/browser/client_context_unittest.cc b/chromium/components/autofill_assistant/browser/client_context_unittest.cc index 279956c51ae..413f905326c 100644 --- a/chromium/components/autofill_assistant/browser/client_context_unittest.cc +++ b/chromium/components/autofill_assistant/browser/client_context_unittest.cc @@ -43,7 +43,7 @@ TEST_F(ClientContextTest, Initialize) { .WillOnce(Return(std::make_pair(1080, 1920))); EXPECT_CALL(mock_client_, GetScreenOrientation()) .WillOnce(Return(ClientContextProto::PORTRAIT)); - EXPECT_CALL(mock_client_, GetChromeSignedInEmailAddress()) + EXPECT_CALL(mock_client_, GetSignedInEmail()) .WillOnce(Return("john.doe@chromium.org")); EXPECT_CALL(mock_client_, IsAccessibilityEnabled()).WillOnce(Return(true)); @@ -67,6 +67,16 @@ TEST_F(ClientContextTest, Initialize) { EXPECT_THAT(actual_client_context.window_size().height_pixels(), Eq(1920)); EXPECT_THAT(actual_client_context.screen_orientation(), ClientContextProto::PORTRAIT); + EXPECT_EQ(actual_client_context.js_flow_library_loaded(), false); +#if BUILDFLAG(IS_ANDROID) + EXPECT_THAT(actual_client_context.platform_type(), + ClientContextProto::PLATFORM_TYPE_ANDROID); +#endif +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC) || \ + BUILDFLAG(IS_WIN) || BUILDFLAG(IS_FUCHSIA) + EXPECT_THAT(actual_client_context.platform_type(), + ClientContextProto::PLATFORM_TYPE_DESKTOP); +#endif auto actual_device_context = actual_client_context.device_context(); EXPECT_THAT(actual_device_context.version().sdk_int(), Eq(123)); @@ -74,10 +84,10 @@ TEST_F(ClientContextTest, Initialize) { EXPECT_THAT(actual_device_context.model(), Eq("model")); } -TEST_F(ClientContextTest, UpdateWithTriggerContext) { +TEST_F(ClientContextTest, UpdatesToClientContext) { // Calls expected when the constructor is called. EXPECT_CALL(mock_client_, IsAccessibilityEnabled()).WillOnce(Return(false)); - EXPECT_CALL(mock_client_, GetChromeSignedInEmailAddress()) + EXPECT_CALL(mock_client_, GetSignedInEmail()) .WillOnce(Return("john.doe@chromium.org")); EXPECT_CALL(mock_client_, GetWindowSize()) .WillOnce(Return(std::make_pair(0, 0))); @@ -88,22 +98,21 @@ TEST_F(ClientContextTest, UpdateWithTriggerContext) { // Calls expected when Update is called. We expect the previous entries to // be overwritten. EXPECT_CALL(mock_client_, IsAccessibilityEnabled()).WillOnce(Return(true)); - EXPECT_CALL(mock_client_, GetChromeSignedInEmailAddress()) - .WillOnce(Return("")); + EXPECT_CALL(mock_client_, GetSignedInEmail()).WillOnce(Return("")); EXPECT_CALL(mock_client_, GetWindowSize()) .WillOnce(Return(std::pair<int, int>(1080, 1920))); EXPECT_CALL(mock_client_, GetScreenOrientation()) .WillOnce(Return(ClientContextProto::LANDSCAPE)); + client_context.Update({std::make_unique<ScriptParameters>( base::flat_map<std::string, std::string>{ {"USER_EMAIL", "example@chromium.org"}}), - /* exp = */ "1,2,3", + /* experiment_ids = */ "1,2,3", /* is_cct = */ true, /* onboarding_shown = */ true, /* is_direct_action = */ true, /* initial_url = */ "https://www.example.com", /* is_in_chrome_triggered = */ true}); - auto actual_client_context = client_context.AsProto(); EXPECT_THAT(actual_client_context.experiment_ids(), Eq("1,2,3")); EXPECT_THAT(actual_client_context.is_cct(), Eq(true)); @@ -120,6 +129,13 @@ TEST_F(ClientContextTest, UpdateWithTriggerContext) { EXPECT_THAT(actual_client_context.window_size().height_pixels(), Eq(1920)); EXPECT_THAT(actual_client_context.screen_orientation(), ClientContextProto::LANDSCAPE); + EXPECT_FALSE(actual_client_context.has_annotate_dom_model_context()); + + client_context.UpdateAnnotateDomModelContext(123456); + actual_client_context = client_context.AsProto(); + EXPECT_THAT( + actual_client_context.annotate_dom_model_context().model_version(), + 123456); } TEST_F(ClientContextTest, WindowSizeIsClearedIfNoLongerAvailable) { @@ -147,7 +163,7 @@ TEST_F(ClientContextTest, WindowSizeIsClearedIfNoLongerAvailable) { } TEST_F(ClientContextTest, AccountMatching) { - EXPECT_CALL(mock_client_, GetChromeSignedInEmailAddress()) + EXPECT_CALL(mock_client_, GetSignedInEmail()) .WillRepeatedly(Return("john.doe@chromium.org")); ClientContextImpl client_context(&mock_client_); @@ -180,19 +196,27 @@ TEST_F(ClientContextTest, AccountMatching) { } TEST_F(ClientContextTest, SignedInStatus) { - EXPECT_CALL(mock_client_, GetChromeSignedInEmailAddress()) - .WillOnce(Return("")); + EXPECT_CALL(mock_client_, GetSignedInEmail()).WillOnce(Return("")); ClientContextImpl client_context_a(&mock_client_); EXPECT_THAT(client_context_a.AsProto().signed_into_chrome_status(), Eq(ClientContextProto::NOT_SIGNED_IN)); - EXPECT_CALL(mock_client_, GetChromeSignedInEmailAddress()) + EXPECT_CALL(mock_client_, GetSignedInEmail()) .WillOnce(Return("john.doe@chromium.org")); ClientContextImpl client_context_b(&mock_client_); EXPECT_THAT(client_context_b.AsProto().signed_into_chrome_status(), Eq(ClientContextProto::SIGNED_IN)); } +TEST_F(ClientContextTest, UpdateJsFlowLibraryLoaded) { + ClientContextImpl client_context(&mock_client_); + EXPECT_EQ(client_context.AsProto().js_flow_library_loaded(), false); + client_context.UpdateJsFlowLibraryLoaded(true); + EXPECT_EQ(client_context.AsProto().js_flow_library_loaded(), true); + client_context.UpdateJsFlowLibraryLoaded(false); + EXPECT_EQ(client_context.AsProto().js_flow_library_loaded(), false); +} + } // namespace } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/client_settings.cc b/chromium/components/autofill_assistant/browser/client_settings.cc index 436c24c4518..2d2f5e20db7 100644 --- a/chromium/components/autofill_assistant/browser/client_settings.cc +++ b/chromium/components/autofill_assistant/browser/client_settings.cc @@ -176,6 +176,10 @@ void ClientSettings::UpdateFromProto(const ClientSettingsProto& proto) { selector_observer_extra_timeout = base::Milliseconds(proto.selector_observer_extra_timeout_ms()); } + if (proto.has_selector_observer_debounce_interval_ms()) { + selector_observer_debounce_interval = + base::Milliseconds(proto.selector_observer_debounce_interval_ms()); + } } } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/client_settings.h b/chromium/components/autofill_assistant/browser/client_settings.h index 096e20dd03f..b3a40296ade 100644 --- a/chromium/components/autofill_assistant/browser/client_settings.h +++ b/chromium/components/autofill_assistant/browser/client_settings.h @@ -147,6 +147,10 @@ struct ClientSettings { // time spent waiting so a extra delay of 1 to 10 seconds for javascript // execution and checking selectors is conceivable. base::TimeDelta selector_observer_extra_timeout = base::Seconds(15); + + // Wait until no DOM changes are received for this amount of time to check + // the selectors. An interval of 0 effectively disables debouncing. + base::TimeDelta selector_observer_debounce_interval = base::Milliseconds(100); }; } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/common_dependencies.cc b/chromium/components/autofill_assistant/browser/common_dependencies.cc new file mode 100644 index 00000000000..e1b64870625 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/common_dependencies.cc @@ -0,0 +1,11 @@ +// Copyright 2022 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_assistant/browser/common_dependencies.h" + +namespace autofill_assistant { + +CommonDependencies::~CommonDependencies() = default; + +} // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/common_dependencies.h b/chromium/components/autofill_assistant/browser/common_dependencies.h new file mode 100644 index 00000000000..518c4db12d7 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/common_dependencies.h @@ -0,0 +1,77 @@ +// Copyright 2022 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_ASSISTANT_BROWSER_COMMON_DEPENDENCIES_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_COMMON_DEPENDENCIES_H_ + +#include <memory> +#include <string> + +namespace autofill { +class PersonalDataManager; +} // namespace autofill + +namespace password_manager { +class PasswordManagerClient; +} // namespace password_manager + +namespace content { +class WebContents; +class BrowserContext; +} // namespace content + +namespace signin { +class IdentityManager; +} // namespace signin + +namespace version_info { +enum class Channel; +} // namespace version_info + +namespace autofill_assistant { + +class AnnotateDomModelService; +class AssistantFieldTrialUtil; + +// Interface for platform delegates that provide dependencies to the starter. +// +// This interface contains all methods with a common implementation across +// platforms (desktop and Android) but a different implementation on WebLayer. +class CommonDependencies { + public: + virtual ~CommonDependencies(); + + virtual std::unique_ptr<AssistantFieldTrialUtil> CreateFieldTrialUtil() + const = 0; + + virtual std::string GetLocale() const = 0; + + virtual std::string GetCountryCode() const = 0; + + virtual autofill::PersonalDataManager* GetPersonalDataManager( + content::BrowserContext* browser_context) const = 0; + + virtual password_manager::PasswordManagerClient* GetPasswordManagerClient( + content::WebContents* web_contents) const = 0; + + virtual std::string GetSignedInEmail( + content::BrowserContext* browser_context) const = 0; + + virtual bool IsSupervisedUser( + content::BrowserContext* browser_context) const = 0; + + virtual AnnotateDomModelService* GetOrCreateAnnotateDomModelService( + content::BrowserContext* browser_context) const = 0; + + virtual bool IsWebLayer() const = 0; + + virtual signin::IdentityManager* GetIdentityManager( + content::BrowserContext* browser_context) const = 0; + + virtual version_info::Channel GetChannel() const = 0; +}; + +} // namespace autofill_assistant + +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_COMMON_DEPENDENCIES_H_ diff --git a/chromium/components/autofill_assistant/browser/controller.cc b/chromium/components/autofill_assistant/browser/controller.cc index c669d229234..d8425da3aa7 100644 --- a/chromium/components/autofill_assistant/browser/controller.cc +++ b/chromium/components/autofill_assistant/browser/controller.cc @@ -8,6 +8,7 @@ #include "base/bind.h" #include "base/callback_helpers.h" +#include "base/command_line.h" #include "base/feature_list.h" #include "base/no_destructor.h" #include "base/ranges/algorithm.h" @@ -21,6 +22,7 @@ #include "components/autofill_assistant/browser/metrics.h" #include "components/autofill_assistant/browser/protocol_utils.h" #include "components/autofill_assistant/browser/service/service_impl.h" +#include "components/autofill_assistant/browser/switches.h" #include "components/autofill_assistant/browser/trigger_context.h" #include "components/autofill_assistant/browser/url_utils.h" #include "components/autofill_assistant/browser/user_data.h" @@ -29,7 +31,6 @@ #include "components/google/core/common/google_util.h" #include "components/password_manager/core/browser/password_change_success_tracker_impl.h" #include "components/strings/grit/components_strings.h" -#include "components/ukm/content/source_url_recorder.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/navigation_handle.h" @@ -59,6 +60,14 @@ bool ShouldSuppressKeyboardForState(AutofillAssistantState state) { } } +bool ShouldSendModelVersionInContext(const TriggerContext& trigger_context) { + return trigger_context.GetScriptParameters() + .GetSendAnnotateDomModelVersion() + .value_or(false) || + base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kAutofillAssistantAnnotateDom); +} + } // namespace Controller::Controller(content::WebContents* web_contents, @@ -139,6 +148,24 @@ content::WebContents* Controller::GetWebContents() { return web_contents(); } +void Controller::SetJsFlowLibrary(const std::string& js_flow_library) { + if (js_flow_library.empty()) { + return; + } + + GetJsFlowDevtoolsWrapper()->SetJsFlowLibrary(js_flow_library); + GetService()->UpdateJsFlowLibraryLoaded(!js_flow_library.empty()); +} + +JsFlowDevtoolsWrapper* Controller::GetJsFlowDevtoolsWrapper() { + if (!js_flow_devtools_wrapper_) { + js_flow_devtools_wrapper_ = std::make_unique<JsFlowDevtoolsWrapper>( + GetWebContents()->GetBrowserContext()); + } + + return js_flow_devtools_wrapper_.get(); +} + std::string Controller::GetEmailAddressForAccessTokenAccount() { return client_->GetEmailAddressForAccessTokenAccount(); } @@ -195,6 +222,10 @@ ProcessedActionStatusDetailsProto& Controller::GetLogInfo() { return log_info_; } +bool Controller::MustUseBackendData() const { + return client_->MustUseBackendData(); +} + void Controller::AddNavigationListener( ScriptExecutorDelegate::NavigationListener* listener) { navigation_listeners_.AddObserver(listener); @@ -409,16 +440,42 @@ void Controller::GetOrCheckScripts() { #else VLOG(2) << "GetScripts for " << script_url_.host(); #endif - - GetService()->GetScriptsForUrl( - url, *trigger_context_, - base::BindOnce(&Controller::OnGetScripts, base::Unretained(this), url)); + MaybeUpdateClientContextAndGetScriptsForUrl(url); } else { script_tracker()->CheckScripts(); StartPeriodicScriptChecks(); } } +void Controller::MaybeUpdateClientContextAndGetScriptsForUrl(const GURL& url) { + DCHECK(trigger_context_); + if (!ShouldSendModelVersionInContext(*trigger_context_)) { + GetScriptsForUrl(url); + return; + } + + DCHECK(client_); + client_->GetAnnotateDomModelVersion( + base::BindOnce(&Controller::OnGetAnnotateDomModelVersionForGetScripts, + weak_ptr_factory_.GetWeakPtr(), url)); +} + +void Controller::OnGetAnnotateDomModelVersionForGetScripts( + const GURL& url, + absl::optional<int64_t> model_version) { + if (model_version) { + GetService()->UpdateAnnotateDomModelContext(*model_version); + } + GetScriptsForUrl(url); +} + +void Controller::GetScriptsForUrl(const GURL& url) { + GetService()->GetScriptsForUrl( + url, *trigger_context_, + base::BindOnce(&Controller::OnGetScripts, weak_ptr_factory_.GetWeakPtr(), + url)); +} + void Controller::StartPeriodicScriptChecks() { periodic_script_check_count_ = settings_.periodic_script_check_count; // If periodic checks are running, setting periodic_script_check_count_ keeps @@ -507,6 +564,14 @@ void Controller::OnGetScripts( Metrics::DropOutReason::GET_SCRIPTS_UNPARSABLE); return; } + + if (response_proto.has_semantic_selector_policy()) { + // TODO(b/228987849): A semantic policy is set unconditionally. It may be + // more appropriate to only set one if there are actual eligible scripts for + // the given domain. + SetSemanticSelectorPolicy( + std::move(response_proto.semantic_selector_policy())); + } if (response_proto.has_client_settings()) { SetClientSettings(response_proto.client_settings()); } @@ -737,14 +802,28 @@ void Controller::InitFromParameters() { DCHECK(GetDeeplinkURL().is_valid()); // |deeplink_url_| must be set. user_data_.selected_login_.emplace( GetDeeplinkURL().DeprecatedGetOriginAsURL(), *password_change_username); - GetPasswordChangeSuccessTracker()->OnChangePasswordFlowStarted( - user_data_.selected_login_->origin, - user_data_.selected_login_->username, - password_manager::PasswordChangeSuccessTracker::StartEvent:: - kAutomatedFlow); + + // We only start password change success tracking here if the run was + // started from the Google Password Manager. The other cases are + // handled directly in the UI. + if (trigger_context_->GetScriptParameters().GetCaller().value_or(0) == + static_cast<int>( + Metrics::AutofillAssistantCaller::GOOGLE_PASSWORD_MANAGER)) { + GetPasswordChangeSuccessTracker()->OnChangePasswordFlowStarted( + user_data_.selected_login_->origin, + user_data_.selected_login_->username, + password_manager::PasswordChangeSuccessTracker::StartEvent:: + kAutomatedFlow, + password_manager::PasswordChangeSuccessTracker::EntryPoint:: + kLeakCheckInSettings); + } } user_model_.SetCurrentURL(GetCurrentURL()); + + GetService()->SetDisableRpcSigning( + trigger_context_->GetScriptParameters().GetDisableRpcSigning().value_or( + false)); } void Controller::Track(std::unique_ptr<TriggerContext> trigger_context, @@ -809,9 +888,6 @@ void Controller::ShowFirstMessageAndStart() { } void Controller::Shutdown(Metrics::DropOutReason reason) { - for (ControllerObserver& observer : observers_) { - observer.OnShutdown(reason); - } client_->Shutdown(reason); } @@ -987,6 +1063,13 @@ void Controller::SetDirectActionScripts( } } +void Controller::SetSemanticSelectorPolicy(SemanticSelectorPolicy policy) { + DCHECK(annotate_dom_model_service_); + if (!annotate_dom_model_service_->SetOverridesPolicy(std::move(policy))) { + NOTREACHED() << "Setting overrides policy failed!"; + } +} + void Controller::OnRunnableScriptsChanged( const std::vector<ScriptHandle>& runnable_scripts) { base::ScopedClosureRunner report_first_check; diff --git a/chromium/components/autofill_assistant/browser/controller.h b/chromium/components/autofill_assistant/browser/controller.h index fc5e71d069b..4363a370b02 100644 --- a/chromium/components/autofill_assistant/browser/controller.h +++ b/chromium/components/autofill_assistant/browser/controller.h @@ -124,6 +124,8 @@ class Controller : public ScriptExecutorDelegate, password_manager::PasswordChangeSuccessTracker* GetPasswordChangeSuccessTracker() override; content::WebContents* GetWebContents() override; + void SetJsFlowLibrary(const std::string& js_flow_library) override; + JsFlowDevtoolsWrapper* GetJsFlowDevtoolsWrapper() override; std::string GetEmailAddressForAccessTokenAccount() override; ukm::UkmRecorder* GetUkmRecorder() override; void SetTouchableElementArea(const ElementAreaProto& area) override; @@ -135,6 +137,7 @@ class Controller : public ScriptExecutorDelegate, void SetBrowseModeInvisible(bool invisible) override; bool ShouldShowWarning() override; ProcessedActionStatusDetailsProto& GetLogInfo() override; + bool MustUseBackendData() const override; // Show the UI if it's not already shown. This is only meaningful while in // states where showing the UI is optional, such as RUNNING, in tracking mode. @@ -295,6 +298,15 @@ class Controller : public ScriptExecutorDelegate, void SetDirectActionScripts( const std::vector<ScriptHandle>& direct_action_scripts); + // Sets the semantic selector in the DOM annotation service. + void SetSemanticSelectorPolicy(SemanticSelectorPolicy policy); + + void MaybeUpdateClientContextAndGetScriptsForUrl(const GURL& url); + void OnGetAnnotateDomModelVersionForGetScripts( + const GURL& url, + absl::optional<int64_t> model_version); + void GetScriptsForUrl(const GURL& url); + ClientSettings settings_; const raw_ptr<Client> client_; const raw_ptr<const base::TickClock> tick_clock_; @@ -372,6 +384,8 @@ class Controller : public ScriptExecutorDelegate, // The next DidStartNavigation will not cause an error. bool expect_navigation_ = false; + std::unique_ptr<JsFlowDevtoolsWrapper> js_flow_devtools_wrapper_; + // Tracks scripts and script execution. It's kept at the end, as it tend to // depend on everything the controller support, through script and script // actions. diff --git a/chromium/components/autofill_assistant/browser/controller_observer.h b/chromium/components/autofill_assistant/browser/controller_observer.h index 80ae6bdfeef..b2ead99fb3d 100644 --- a/chromium/components/autofill_assistant/browser/controller_observer.h +++ b/chromium/components/autofill_assistant/browser/controller_observer.h @@ -97,9 +97,6 @@ class ControllerObserver : public base::CheckedObserver { // Called whenever the UI is shown or hidden. virtual void OnUiShownChanged(bool shown) = 0; - - // Called before shutting down the Controller. - virtual void OnShutdown(Metrics::DropOutReason reason) = 0; }; } // namespace autofill_assistant #endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_CONTROLLER_OBSERVER_H_ diff --git a/chromium/components/autofill_assistant/browser/controller_unittest.cc b/chromium/components/autofill_assistant/browser/controller_unittest.cc index a8b5f7419fd..1cbc9d6eafe 100644 --- a/chromium/components/autofill_assistant/browser/controller_unittest.cc +++ b/chromium/components/autofill_assistant/browser/controller_unittest.cc @@ -32,6 +32,7 @@ #include "components/autofill_assistant/browser/public/mock_runtime_manager.h" #include "components/autofill_assistant/browser/service/mock_service.h" #include "components/autofill_assistant/browser/service/service.h" +#include "components/autofill_assistant/browser/switches.h" #include "components/autofill_assistant/browser/test_util.h" #include "components/autofill_assistant/browser/trigger_context.h" #include "components/autofill_assistant/browser/web/mock_web_controller.h" @@ -74,6 +75,14 @@ using ::testing::StrEq; using ::testing::UnorderedElementsAre; using ::testing::WithArgs; +class MockAnnotateDomModelService : public AnnotateDomModelService { + public: + MockAnnotateDomModelService() : AnnotateDomModelService(nullptr, nullptr) {} + ~MockAnnotateDomModelService() override = default; + + MOCK_METHOD1(SetOverridesPolicy, bool(SemanticSelectorPolicy)); +}; + class ControllerTest : public testing::Test { public: ControllerTest() { @@ -99,7 +108,7 @@ class ControllerTest : public testing::Test { controller_ = std::make_unique<Controller>( web_contents(), &mock_client_, task_environment()->GetMockTickClock(), mock_runtime_manager_->GetWeakPtr(), std::move(service), &ukm_recorder_, - /* annotate_dom_model_service= */ nullptr); + &mock_annotate_dom_model_service_); controller_->SetWebControllerForTest(std::move(web_controller)); @@ -207,7 +216,7 @@ class ControllerTest : public testing::Test { void SimulateNavigateToUrl(const GURL& url) { SetLastCommittedUrl(url); content::NavigationSimulator::NavigateAndCommitFromDocument( - url, web_contents()->GetMainFrame()); + url, web_contents()->GetPrimaryMainFrame()); content::WebContentsTester::For(web_contents())->TestSetIsLoading(false); controller_->DidFinishLoad(nullptr, GURL("")); } @@ -261,6 +270,7 @@ class ControllerTest : public testing::Test { mock_password_change_success_tracker_; ukm::TestAutoSetUkmRecorder ukm_recorder_; std::unique_ptr<Controller> controller_; + NiceMock<MockAnnotateDomModelService> mock_annotate_dom_model_service_; }; struct NavigationState { @@ -774,7 +784,7 @@ TEST_F(ControllerTest, SuccessfulNavigation) { NavigationStateChangeListener listener(controller_.get()); controller_->AddNavigationListener(&listener); content::NavigationSimulator::NavigateAndCommitFromDocument( - GURL("http://initialurl.com"), web_contents()->GetMainFrame()); + GURL("http://initialurl.com"), web_contents()->GetPrimaryMainFrame()); controller_->RemoveNavigationListener(&listener); EXPECT_FALSE(controller_->IsNavigatingToNewDocument()); @@ -792,7 +802,7 @@ TEST_F(ControllerTest, FailedNavigation) { controller_->AddNavigationListener(&listener); content::NavigationSimulator::NavigateAndFailFromDocument( GURL("http://initialurl.com"), net::ERR_CONNECTION_TIMED_OUT, - web_contents()->GetMainFrame()); + web_contents()->GetPrimaryMainFrame()); controller_->RemoveNavigationListener(&listener); EXPECT_FALSE(controller_->IsNavigatingToNewDocument()); @@ -811,7 +821,8 @@ TEST_F(ControllerTest, NavigationWithRedirects) { std::unique_ptr<content::NavigationSimulator> simulator = content::NavigationSimulator::CreateRendererInitiated( - GURL("http://original.example.com/"), web_contents()->GetMainFrame()); + GURL("http://original.example.com/"), + web_contents()->GetPrimaryMainFrame()); simulator->SetTransition(ui::PAGE_TRANSITION_LINK); simulator->Start(); EXPECT_TRUE(controller_->IsNavigatingToNewDocument()); @@ -840,9 +851,9 @@ TEST_F(ControllerTest, EventuallySuccessfulNavigation) { controller_->AddNavigationListener(&listener); content::NavigationSimulator::NavigateAndFailFromDocument( GURL("http://initialurl.com"), net::ERR_CONNECTION_TIMED_OUT, - web_contents()->GetMainFrame()); + web_contents()->GetPrimaryMainFrame()); content::NavigationSimulator::NavigateAndCommitFromDocument( - GURL("http://initialurl.com"), web_contents()->GetMainFrame()); + GURL("http://initialurl.com"), web_contents()->GetPrimaryMainFrame()); controller_->RemoveNavigationListener(&listener); EXPECT_FALSE(controller_->IsNavigatingToNewDocument()); @@ -864,15 +875,15 @@ TEST_F(ControllerTest, RemoveListener) { NavigationStateChangeListener listener(controller_.get()); controller_->AddNavigationListener(&listener); content::NavigationSimulator::NavigateAndCommitFromDocument( - GURL("http://initialurl.com"), web_contents()->GetMainFrame()); + GURL("http://initialurl.com"), web_contents()->GetPrimaryMainFrame()); listener.events.clear(); controller_->RemoveNavigationListener(&listener); content::NavigationSimulator::NavigateAndFailFromDocument( GURL("http://initialurl.com"), net::ERR_CONNECTION_TIMED_OUT, - web_contents()->GetMainFrame()); + web_contents()->GetPrimaryMainFrame()); content::NavigationSimulator::NavigateAndCommitFromDocument( - GURL("http://initialurl.com"), web_contents()->GetMainFrame()); + GURL("http://initialurl.com"), web_contents()->GetPrimaryMainFrame()); EXPECT_THAT(listener.events, IsEmpty()); } @@ -966,7 +977,8 @@ TEST_F(ControllerTest, WaitForNavigationActionStartWithinTimeout) { EXPECT_THAT(processed_actions_capture, SizeIs(0)); std::unique_ptr<content::NavigationSimulator> simulator = content::NavigationSimulator::CreateRendererInitiated( - GURL("http://a.example.com/path"), web_contents()->GetMainFrame()); + GURL("http://a.example.com/path"), + web_contents()->GetPrimaryMainFrame()); simulator->SetTransition(ui::PAGE_TRANSITION_LINK); simulator->Start(); task_environment()->FastForwardBy(base::Seconds(1)); @@ -1718,7 +1730,7 @@ TEST_F(ControllerTest, UnexpectedNavigationDuringPromptAction) { EXPECT_CALL(mock_client_, Shutdown(_)).Times(0); EXPECT_CALL(mock_client_, RecordDropOut(_)).Times(0); content::NavigationSimulator::NavigateAndCommitFromDocument( - GURL("http://a.example.com/page"), web_contents()->GetMainFrame()); + GURL("http://a.example.com/page"), web_contents()->GetPrimaryMainFrame()); EXPECT_EQ(AutofillAssistantState::PROMPT, controller_->GetState()); // Expected browser initiated navigation is allowed. @@ -1766,7 +1778,7 @@ TEST_F(ControllerTest, UnexpectedNavigationInRunningState) { EXPECT_CALL(mock_client_, Shutdown(_)).Times(0); EXPECT_CALL(mock_client_, RecordDropOut(_)).Times(0); content::NavigationSimulator::NavigateAndCommitFromDocument( - GURL("http://a.example.com/page"), web_contents()->GetMainFrame()); + GURL("http://a.example.com/page"), web_contents()->GetPrimaryMainFrame()); EXPECT_EQ(AutofillAssistantState::RUNNING, controller_->GetState()); // Expected browser initiated navigation while in RUNNING state: @@ -1994,7 +2006,7 @@ TEST_F(ControllerTest, WriteUserData) { TermsAndConditionsState::ACCEPTED); } -TEST_F(ControllerTest, StartPasswordChangeFlow) { +TEST_F(ControllerTest, StartPasswordChangeFlowFromUPM) { const GURL initialUrl("http://example.com/password"); const std::string username = "test_username"; EXPECT_CALL(*mock_service_, GetScriptsForUrl(Eq(initialUrl), _, _)) @@ -2004,7 +2016,38 @@ TEST_F(ControllerTest, StartPasswordChangeFlow) { OnChangePasswordFlowStarted( initialUrl.DeprecatedGetOriginAsURL(), username, password_manager::PasswordChangeSuccessTracker::StartEvent:: - kAutomatedFlow)); + kAutomatedFlow, + password_manager::PasswordChangeSuccessTracker::EntryPoint:: + kLeakCheckInSettings)); + + EXPECT_TRUE(controller_->Start( + initialUrl, + // 9 is the enum value of |GOOGLE_PASSWORD_MANAGER|, i.e. a call + // from the Unified Password Manager. + std::make_unique<TriggerContext>( + /* parameters=*/std::make_unique<ScriptParameters>( + base::flat_map<std::string, std::string>{ + {"PASSWORD_CHANGE_USERNAME", username}, {"CALLER", "9"}}), + TriggerContext::Options()))); + // Initial navigation. + SimulateNavigateToUrl(GURL("http://b.example.com")); + EXPECT_EQ(GetUserData()->selected_login_->username, username); + EXPECT_EQ(GetUserData()->selected_login_->origin, + initialUrl.DeprecatedGetOriginAsURL()); + EXPECT_EQ(controller_->GetCurrentURL().host(), "b.example.com"); +} + +TEST_F(ControllerTest, StartPasswordChangeFlow) { + const GURL initialUrl("http://example.com/password"); + const std::string username = "test_username"; + EXPECT_CALL(*mock_service_, GetScriptsForUrl(Eq(initialUrl), _, _)) + .WillOnce(RunOnceCallback<2>(net::HTTP_OK, "", + ServiceRequestSender::ResponseInfo{})); + // We do not expect a call to the tracker, since the flow is not started + // from UPM. + EXPECT_CALL(mock_password_change_success_tracker_, + OnChangePasswordFlowStarted) + .Times(0); EXPECT_TRUE(controller_->Start( initialUrl, std::make_unique<TriggerContext>( @@ -2057,7 +2100,8 @@ TEST_F(ControllerTest, EndPromptWithOnEndNavigation) { std::unique_ptr<content::NavigationSimulator> simulator = content::NavigationSimulator::CreateRendererInitiated( - GURL("http://a.example.com/path"), web_contents()->GetMainFrame()); + GURL("http://a.example.com/path"), + web_contents()->GetPrimaryMainFrame()); simulator->SetTransition(ui::PAGE_TRANSITION_LINK); simulator->Start(); task_environment()->FastForwardBy(base::Seconds(1)); @@ -2182,7 +2226,7 @@ TEST_F(ControllerPrerenderTest, SuccessfulNavigation) { controller_->AddNavigationListener(&listener); content::NavigationSimulator::NavigateAndCommitFromDocument( - GURL("http://initialurl.com"), web_contents()->GetMainFrame()); + GURL("http://initialurl.com"), web_contents()->GetPrimaryMainFrame()); EXPECT_THAT( listener.events, @@ -2208,4 +2252,160 @@ TEST_F(ControllerPrerenderTest, SuccessfulNavigation) { EXPECT_THAT(listener.events, IsEmpty()); } +TEST_F(ControllerTest, MustUseBackendData) { + EXPECT_CALL(mock_client_, MustUseBackendData).WillOnce(Return(true)); + EXPECT_TRUE(controller_->MustUseBackendData()); +} + +class ControllerFencedFrameTest : public ControllerTest { + public: + ControllerFencedFrameTest() { + scoped_feature_list_.InitAndEnableFeatureWithParameters( + blink::features::kFencedFrames, {{"implementation_type", "mparch"}}); + } + ~ControllerFencedFrameTest() override = default; + + content::RenderFrameHost* CreateFencedFrame( + content::RenderFrameHost* parent) { + content::RenderFrameHost* fenced_frame = + content::RenderFrameHostTester::For(parent)->AppendFencedFrame(); + return fenced_frame; + } + + private: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +TEST_F(ControllerFencedFrameTest, DoNotNavigateInFencedFrame) { + EXPECT_FALSE(controller_->IsNavigatingToNewDocument()); + EXPECT_FALSE(controller_->HasNavigationError()); + + NavigationStateChangeListener listener(controller_.get()); + controller_->AddNavigationListener(&listener); + + content::NavigationSimulator::NavigateAndCommitFromDocument( + GURL("http://initialurl.com"), web_contents()->GetPrimaryMainFrame()); + + EXPECT_THAT( + listener.events, + ElementsAre( + NavigationState{/* navigating= */ true, /* has_errors= */ false}, + NavigationState{/* navigating= */ false, /* has_errors= */ false})); + + listener.events.clear(); + + // Create a fenced frame. + content::RenderFrameHostTester::For(web_contents()->GetPrimaryMainFrame()) + ->InitializeRenderFrameIfNeeded(); + content::RenderFrameHost* fenced_frame_rfh = + CreateFencedFrame(web_contents()->GetPrimaryMainFrame()); + GURL kFencedFrameUrl("https://fencedframe.com"); + std::unique_ptr<content::NavigationSimulator> navigation_simulator = + content::NavigationSimulator::CreateRendererInitiated(kFencedFrameUrl, + fenced_frame_rfh); + navigation_simulator->Commit(); + fenced_frame_rfh = navigation_simulator->GetFinalRenderFrameHost(); + EXPECT_TRUE(fenced_frame_rfh->IsFencedFrameRoot()); + + // Autofill assistant controller doesn't handle navigations in fenced frames. + EXPECT_FALSE(controller_->IsNavigatingToNewDocument()); + EXPECT_FALSE(controller_->HasNavigationError()); + + controller_->RemoveNavigationListener(&listener); + + EXPECT_THAT(listener.events, IsEmpty()); +} + +TEST_F(ControllerTest, SemanticOverridesSetInService) { + EXPECT_CALL(mock_annotate_dom_model_service_, SetOverridesPolicy) + .WillOnce(Return(true)); + + SupportsScriptResponseProto script_response; + script_response.mutable_semantic_selector_policy() + ->mutable_bag_of_words() + ->add_data_point_map(); + AddRunnableScript(&script_response, "runnable"); + SetNextScriptResponse(script_response); + + EXPECT_CALL(mock_client_, AttachUI()); + Start("http://a.example.com/path"); + EXPECT_EQ(AutofillAssistantState::STARTING, controller_->GetState()); +} + +TEST_F(ControllerTest, SkipModelVersionIfParameterNotSpecified) { + EXPECT_CALL(mock_client_, GetAnnotateDomModelVersion).Times(0); + EXPECT_CALL(*mock_service_, UpdateAnnotateDomModelContext).Times(0); + EXPECT_CALL(*mock_service_, GetScriptsForUrl) + .WillOnce(RunOnceCallback<2>(net::HTTP_OK, "", + ServiceRequestSender::ResponseInfo{})); + + controller_->Start(GURL("https://www.example.com"), + std::make_unique<TriggerContext>( + /* parameters = */ std::make_unique<ScriptParameters>( + base::flat_map<std::string, std::string>{{}}), + TriggerContext::Options())); +} + +TEST_F(ControllerTest, AttachesAvailableModelVersionOnStart) { + EXPECT_CALL(mock_client_, GetAnnotateDomModelVersion) + .WillOnce(RunOnceCallback<0>(123456)); + EXPECT_CALL(*mock_service_, UpdateAnnotateDomModelContext(123456)); + EXPECT_CALL(*mock_service_, GetScriptsForUrl) + .WillOnce(RunOnceCallback<2>(net::HTTP_OK, "", + ServiceRequestSender::ResponseInfo{})); + + controller_->Start(GURL("https://www.example.com"), + std::make_unique<TriggerContext>( + /* parameters = */ std::make_unique<ScriptParameters>( + base::flat_map<std::string, std::string>{ + {"SEND_ANNOTATE_DOM_MODEL_VERSION", "true"}}), + TriggerContext::Options())); +} + +TEST_F(ControllerTest, DoesNotAttachUnavailableModelVersionOnStart) { + EXPECT_CALL(mock_client_, GetAnnotateDomModelVersion) + .WillOnce(RunOnceCallback<0>(absl::nullopt)); + EXPECT_CALL(*mock_service_, UpdateAnnotateDomModelContext).Times(0); + EXPECT_CALL(*mock_service_, GetScriptsForUrl) + .WillOnce(RunOnceCallback<2>(net::HTTP_OK, "", + ServiceRequestSender::ResponseInfo{})); + + controller_->Start(GURL("https://www.example.com"), + std::make_unique<TriggerContext>( + /* parameters = */ std::make_unique<ScriptParameters>( + base::flat_map<std::string, std::string>{ + {"SEND_ANNOTATE_DOM_MODEL_VERSION", "true"}}), + TriggerContext::Options())); +} + +TEST_F(ControllerTest, AttachesAvailableModelVersionForCommandLineSwitch) { + base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( + switches::kAutofillAssistantAnnotateDom, "true"); + + EXPECT_CALL(mock_client_, GetAnnotateDomModelVersion) + .WillOnce(RunOnceCallback<0>(123456)); + EXPECT_CALL(*mock_service_, UpdateAnnotateDomModelContext(123456)); + EXPECT_CALL(*mock_service_, GetScriptsForUrl) + .WillOnce(RunOnceCallback<2>(net::HTTP_OK, "", + ServiceRequestSender::ResponseInfo{})); + + controller_->Start(GURL("https://www.example.com"), + std::make_unique<TriggerContext>( + /* parameters = */ std::make_unique<ScriptParameters>( + base::flat_map<std::string, std::string>{}), + TriggerContext::Options())); +} + +TEST_F(ControllerTest, UpdatesJsFlowLibraryLoaded) { + EXPECT_CALL(*mock_service_, UpdateJsFlowLibraryLoaded(true)); + + controller_->SetJsFlowLibrary("const st = 2;"); +} + +TEST_F(ControllerTest, JsFlowLibraryNotLoadedForEmpty) { + EXPECT_CALL(*mock_service_, UpdateJsFlowLibraryLoaded(true)).Times(0); + + controller_->SetJsFlowLibrary(""); +} + } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/dependencies_util.cc b/chromium/components/autofill_assistant/browser/dependencies_util.cc new file mode 100644 index 00000000000..0272c889141 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/dependencies_util.cc @@ -0,0 +1,23 @@ +// Copyright 2022 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_assistant/browser/dependencies_util.h" + +#include <string> + +#include "components/variations/service/variations_service.h" + +using ::variations::VariationsService; + +namespace autofill_assistant::dependencies_util { + +std::string GetCountryCode(VariationsService* variations_service) { + if (!variations_service || variations_service->GetLatestCountry().empty()) { + // Use fallback "ZZ" if no country is available. + return "ZZ"; + } + return base::ToUpperASCII(variations_service->GetLatestCountry()); +} + +} // namespace autofill_assistant::dependencies_util diff --git a/chromium/components/autofill_assistant/browser/dependencies_util.h b/chromium/components/autofill_assistant/browser/dependencies_util.h new file mode 100644 index 00000000000..758f9b2b038 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/dependencies_util.h @@ -0,0 +1,18 @@ +// Copyright 2022 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_ASSISTANT_BROWSER_DEPENDENCIES_UTIL_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_DEPENDENCIES_UTIL_H_ + +#include <string> + +#include "components/variations/service/variations_service.h" + +namespace autofill_assistant::dependencies_util { + +std::string GetCountryCode(variations::VariationsService* variations_service); + +} // namespace autofill_assistant::dependencies_util + +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_DEPENDENCIES_UTIL_H_ diff --git a/chromium/components/autofill_assistant/browser/desktop/starter_delegate_desktop.cc b/chromium/components/autofill_assistant/browser/desktop/starter_delegate_desktop.cc index bcd956694c2..12f126d1139 100644 --- a/chromium/components/autofill_assistant/browser/desktop/starter_delegate_desktop.cc +++ b/chromium/components/autofill_assistant/browser/desktop/starter_delegate_desktop.cc @@ -16,8 +16,12 @@ namespace autofill_assistant { StarterDelegateDesktop::StarterDelegateDesktop( - content::WebContents* web_contents) - : content::WebContentsUserData<StarterDelegateDesktop>(*web_contents) {} + content::WebContents* web_contents, + std::unique_ptr<CommonDependencies> common_dependencies, + std::unique_ptr<PlatformDependencies> platform_dependencies) + : content::WebContentsUserData<StarterDelegateDesktop>(*web_contents), + common_dependencies_(std::move(common_dependencies)), + platform_dependencies_(std::move(platform_dependencies)) {} StarterDelegateDesktop::~StarterDelegateDesktop() = default; @@ -36,8 +40,7 @@ WebsiteLoginManager* StarterDelegateDesktop::GetWebsiteLoginManager() const { } version_info::Channel StarterDelegateDesktop::GetChannel() const { - // TODO(b/201964911): Inject on instantiation. - return version_info::Channel::DEV; + return common_dependencies_->GetChannel(); } bool StarterDelegateDesktop::GetFeatureModuleInstalled() const { @@ -52,7 +55,6 @@ void StarterDelegateDesktop::InstallFeatureModule( } bool StarterDelegateDesktop::GetIsFirstTimeUser() const { - NOTREACHED(); return false; } @@ -96,12 +98,18 @@ bool StarterDelegateDesktop::GetMakeSearchesAndBrowsingBetterEnabled() const { } bool StarterDelegateDesktop::GetIsLoggedIn() { - // Only relevant for trigger scripts, which don't exist in headless. - return false; + return !common_dependencies_ + ->GetSignedInEmail(GetWebContents().GetBrowserContext()) + .empty(); +} + +bool StarterDelegateDesktop::GetIsSupervisedUser() { + return common_dependencies_->IsSupervisedUser( + GetWebContents().GetBrowserContext()); } bool StarterDelegateDesktop::GetIsCustomTab() const { - return false; + return platform_dependencies_->IsCustomTab(GetWebContents()); } bool StarterDelegateDesktop::GetIsWebLayer() const { @@ -114,8 +122,7 @@ bool StarterDelegateDesktop::GetIsTabCreatedByGSA() const { std::unique_ptr<AssistantFieldTrialUtil> StarterDelegateDesktop::CreateFieldTrialUtil() { - // TODO(b/201964911): Create a field trial util. - return nullptr; + return common_dependencies_->CreateFieldTrialUtil(); } void StarterDelegateDesktop::StartScriptDefaultUi( @@ -139,6 +146,16 @@ bool StarterDelegateDesktop::IsAttached() { return true; } +const CommonDependencies* StarterDelegateDesktop::GetCommonDependencies() + const { + return common_dependencies_.get(); +} + +const PlatformDependencies* StarterDelegateDesktop::GetPlatformDependencies() + const { + return platform_dependencies_.get(); +} + base::WeakPtr<StarterPlatformDelegate> StarterDelegateDesktop::GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); } diff --git a/chromium/components/autofill_assistant/browser/desktop/starter_delegate_desktop.h b/chromium/components/autofill_assistant/browser/desktop/starter_delegate_desktop.h index 86f82f326c8..f2abd050753 100644 --- a/chromium/components/autofill_assistant/browser/desktop/starter_delegate_desktop.h +++ b/chromium/components/autofill_assistant/browser/desktop/starter_delegate_desktop.h @@ -8,8 +8,10 @@ #include <memory> #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" +#include "components/autofill_assistant/browser/common_dependencies.h" #include "components/autofill_assistant/browser/metrics.h" #include "components/autofill_assistant/browser/onboarding_result.h" +#include "components/autofill_assistant/browser/platform_dependencies.h" #include "components/autofill_assistant/browser/starter_platform_delegate.h" #include "components/autofill_assistant/browser/trigger_context.h" #include "components/autofill_assistant/browser/website_login_manager.h" @@ -60,16 +62,25 @@ class StarterDelegateDesktop void SetProactiveHelpSettingEnabled(bool enabled) override; bool GetMakeSearchesAndBrowsingBetterEnabled() const override; bool GetIsLoggedIn() override; + bool GetIsSupervisedUser() override; bool GetIsCustomTab() const override; bool GetIsWebLayer() const override; bool GetIsTabCreatedByGSA() const override; std::unique_ptr<AssistantFieldTrialUtil> CreateFieldTrialUtil() override; bool IsAttached() override; + const CommonDependencies* GetCommonDependencies() const override; + const PlatformDependencies* GetPlatformDependencies() const override; base::WeakPtr<StarterPlatformDelegate> GetWeakPtr() override; private: friend class content::WebContentsUserData<StarterDelegateDesktop>; - explicit StarterDelegateDesktop(content::WebContents* web_contents); + StarterDelegateDesktop( + content::WebContents* web_contents, + std::unique_ptr<CommonDependencies> common_dependencies, + std::unique_ptr<PlatformDependencies> platform_dependencies); + + const std::unique_ptr<CommonDependencies> common_dependencies_; + const std::unique_ptr<PlatformDependencies> platform_dependencies_; WEB_CONTENTS_USER_DATA_KEY_DECL(); diff --git a/chromium/components/autofill_assistant/browser/details.cc b/chromium/components/autofill_assistant/browser/details.cc index 7684d718e31..808710b7cfb 100644 --- a/chromium/components/autofill_assistant/browser/details.cc +++ b/chromium/components/autofill_assistant/browser/details.cc @@ -40,14 +40,18 @@ std::string FormatDateTimeProto(const DateTimeProto& date_time) { auto date_proto = date_time.date(); auto time_proto = date_time.time(); + // Technically we are setting the wrong |day_of_week|, but it's ignored in + // practice and the formatted string will have the correct day for the + // date. Setting an invalid value here (e.g. -1) causes issues on Windows. base::Time::Exploded exploded_time = {static_cast<int>(date_proto.year()), date_proto.month(), - /* day_of_week = */ -1, + /* day_of_week = */ 0, date_proto.day(), time_proto.hour(), time_proto.minute(), time_proto.second(), - 0}; + /* millisecond = */ 0}; + base::Time time; if (base::Time::FromLocalExploded(exploded_time, &time)) { diff --git a/chromium/components/autofill_assistant/browser/devtools/value_conversions.h b/chromium/components/autofill_assistant/browser/devtools/value_conversions.h index 288c53a85aa..621d9949c4a 100644 --- a/chromium/components/autofill_assistant/browser/devtools/value_conversions.h +++ b/chromium/components/autofill_assistant/browser/devtools/value_conversions.h @@ -64,7 +64,7 @@ std::unique_ptr<base::Value> ToValueImpl(const std::vector<T>& vector, const std::vector<T>*) { std::unique_ptr<base::ListValue> result(new base::ListValue()); for (const auto& it : vector) - result->Append(ToValue(it)); + result->GetList().Append(base::Value::FromUniquePtrValue(ToValue(it))); return std::move(result); } diff --git a/chromium/components/autofill_assistant/browser/dom_action.proto b/chromium/components/autofill_assistant/browser/dom_action.proto index 1336d6fe4f9..c3c8805efa4 100644 --- a/chromium/components/autofill_assistant/browser/dom_action.proto +++ b/chromium/components/autofill_assistant/browser/dom_action.proto @@ -220,3 +220,12 @@ message ExecuteJsProto { // not resolved in time, the action will report a |TIMED_OUT| error. optional int32 timeout_ms = 3; } + +// Set an element value through native. +message SetNativeValueProto { + // The target element. Must be an instance of a |WebFormControlElement|, + // otherwise the action will fail. + optional ClientIdProto client_id = 1; + // The value to set. + optional TextValue value = 2; +} diff --git a/chromium/components/autofill_assistant/browser/external_action_extension_test.proto b/chromium/components/autofill_assistant/browser/external_action_extension_test.proto new file mode 100644 index 00000000000..18efa162ca1 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/external_action_extension_test.proto @@ -0,0 +1,19 @@ +// Copyright 2022 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. + +syntax = "proto2"; + +package autofill_assistant.testing; + +option optimize_for = LITE_RUNTIME; + +import "public/external_action.proto"; + +extend external.ResultInfo { + optional TestResultExtension test_result_extension = 100; +} + +message TestResultExtension { + optional string text = 1; +} diff --git a/chromium/components/autofill_assistant/browser/fake_script_executor_delegate.cc b/chromium/components/autofill_assistant/browser/fake_script_executor_delegate.cc index d25d60e5a9c..cc48be03dd0 100644 --- a/chromium/components/autofill_assistant/browser/fake_script_executor_delegate.cc +++ b/chromium/components/autofill_assistant/browser/fake_script_executor_delegate.cc @@ -61,6 +61,24 @@ content::WebContents* FakeScriptExecutorDelegate::GetWebContents() { return web_contents_; } +void FakeScriptExecutorDelegate::SetJsFlowLibrary( + const std::string& js_flow_library) { + GetJsFlowDevtoolsWrapper()->SetJsFlowLibrary(js_flow_library); +} + +JsFlowDevtoolsWrapper* FakeScriptExecutorDelegate::GetJsFlowDevtoolsWrapper() { + if (!js_flow_devtools_wrapper_) { + content::WebContents* web_contents = GetWebContents(); + DCHECK(web_contents_) + << "devtools wrapper is only available in browsertests"; + + js_flow_devtools_wrapper_ = + std::make_unique<JsFlowDevtoolsWrapper>(web_contents); + } + + return js_flow_devtools_wrapper_.get(); +} + std::string FakeScriptExecutorDelegate::GetEmailAddressForAccessTokenAccount() { return std::string(); } @@ -116,6 +134,10 @@ ProcessedActionStatusDetailsProto& FakeScriptExecutorDelegate::GetLogInfo() { return log_info_; } +bool FakeScriptExecutorDelegate::MustUseBackendData() const { + return must_use_backend_data_; +} + void FakeScriptExecutorDelegate::AddNavigationListener( ScriptExecutorDelegate::NavigationListener* listener) { navigation_listeners_.insert(listener); diff --git a/chromium/components/autofill_assistant/browser/fake_script_executor_delegate.h b/chromium/components/autofill_assistant/browser/fake_script_executor_delegate.h index ca1256c77a4..2d70a8632b9 100644 --- a/chromium/components/autofill_assistant/browser/fake_script_executor_delegate.h +++ b/chromium/components/autofill_assistant/browser/fake_script_executor_delegate.h @@ -45,6 +45,8 @@ class FakeScriptExecutorDelegate : public ScriptExecutorDelegate { password_manager::PasswordChangeSuccessTracker* GetPasswordChangeSuccessTracker() override; content::WebContents* GetWebContents() override; + void SetJsFlowLibrary(const std::string& js_flow_library) override; + JsFlowDevtoolsWrapper* GetJsFlowDevtoolsWrapper() override; std::string GetEmailAddressForAccessTokenAccount() override; ukm::UkmRecorder* GetUkmRecorder() override; bool EnterState(AutofillAssistantState state) override; @@ -69,6 +71,7 @@ class FakeScriptExecutorDelegate : public ScriptExecutorDelegate { ConfigureUiStateProto::OverlayBehavior overlay_behavior) override; void SetBrowseModeInvisible(bool invisible) override; ProcessedActionStatusDetailsProto& GetLogInfo() override; + bool MustUseBackendData() const override; bool ShouldShowWarning() override; @@ -113,12 +116,18 @@ class FakeScriptExecutorDelegate : public ScriptExecutorDelegate { std::vector<std::string>* GetCurrentBrowseDomainsList(); + void SetMustUseBackendData(bool must_use_backend_data) { + must_use_backend_data_ = must_use_backend_data; + } + private: ClientSettings client_settings_; GURL current_url_; raw_ptr<Service> service_ = nullptr; raw_ptr<WebController> web_controller_ = nullptr; raw_ptr<content::WebContents> web_contents_ = nullptr; + std::unique_ptr<JsFlowDevtoolsWrapper> js_flow_devtools_wrapper_; + std::string js_flow_library_; std::unique_ptr<TriggerContext> trigger_context_; std::vector<AutofillAssistantState> state_history_; std::vector<ElementAreaProto> touchable_element_area_history_; @@ -131,6 +140,7 @@ class FakeScriptExecutorDelegate : public ScriptExecutorDelegate { std::vector<std::string> browse_domains_; raw_ptr<UserModel> user_model_ = nullptr; ProcessedActionStatusDetailsProto log_info_; + bool must_use_backend_data_ = false; bool require_ui_ = false; }; diff --git a/chromium/components/autofill_assistant/browser/fake_script_executor_ui_delegate.cc b/chromium/components/autofill_assistant/browser/fake_script_executor_ui_delegate.cc index ba03a35aa75..c354be7249a 100644 --- a/chromium/components/autofill_assistant/browser/fake_script_executor_ui_delegate.cc +++ b/chromium/components/autofill_assistant/browser/fake_script_executor_ui_delegate.cc @@ -92,17 +92,23 @@ void FakeScriptExecutorUiDelegate::SetUserActions( void FakeScriptExecutorUiDelegate::SetCollectUserDataOptions( CollectUserDataOptions* options) { - payment_request_options_ = options; + collect_user_data_options_ = options; +} + +void FakeScriptExecutorUiDelegate::SetCollectUserDataUiState( + bool loading, + UserDataEventField event_field) { + collect_user_data_ui_loading__field_ = event_field; } void FakeScriptExecutorUiDelegate::SetLastSuccessfulUserDataOptions( std::unique_ptr<CollectUserDataOptions> collect_user_data_options) { - last_payment_request_options_ = std::move(collect_user_data_options); + last_collect_user_data_options_ = std::move(collect_user_data_options); } const CollectUserDataOptions* FakeScriptExecutorUiDelegate::GetLastSuccessfulUserDataOptions() const { - return last_payment_request_options_.get(); + return last_collect_user_data_options_.get(); } void FakeScriptExecutorUiDelegate::SetPeekMode( @@ -158,4 +164,27 @@ void FakeScriptExecutorUiDelegate::ClearPersistentGenericUi() { void FakeScriptExecutorUiDelegate::SetShowFeedbackChip( bool show_feedback_chip) {} +bool FakeScriptExecutorUiDelegate::SupportsExternalActions() { + return true; +} + +void FakeScriptExecutorUiDelegate::ExecuteExternalAction( + const external::Action& external_action, + base::OnceCallback<void(ExternalActionDelegate::DomUpdateCallback)> + start_dom_checks_callback, + base::OnceCallback<void(const external::Result& result)> + end_action_callback) { + external::Result result; + result.set_success(true); + std::move(end_action_callback).Run(result); +} + +void FakeScriptExecutorUiDelegate::OnInterruptStarted() { + interrupt_notification_history_.emplace_back(INTERRUPT_STARTED); +} + +void FakeScriptExecutorUiDelegate::OnInterruptFinished() { + interrupt_notification_history_.emplace_back(INTERRUPT_FINISHED); +} + } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/fake_script_executor_ui_delegate.h b/chromium/components/autofill_assistant/browser/fake_script_executor_ui_delegate.h index fc3106592a8..d6a64b5bb92 100644 --- a/chromium/components/autofill_assistant/browser/fake_script_executor_ui_delegate.h +++ b/chromium/components/autofill_assistant/browser/fake_script_executor_ui_delegate.h @@ -20,6 +20,8 @@ namespace autofill_assistant { // unittests. class FakeScriptExecutorUiDelegate : public ScriptExecutorUiDelegate { public: + enum InterruptNotification { INTERRUPT_STARTED = 0, INTERRUPT_FINISHED = 1 }; + FakeScriptExecutorUiDelegate(); FakeScriptExecutorUiDelegate(const FakeScriptExecutorUiDelegate&) = delete; @@ -52,6 +54,8 @@ class FakeScriptExecutorUiDelegate : public ScriptExecutorUiDelegate { void SetUserActions( std::unique_ptr<std::vector<UserAction>> user_actions) override; void SetCollectUserDataOptions(CollectUserDataOptions* options) override; + void SetCollectUserDataUiState(bool loading, + UserDataEventField event_field) override; void SetLastSuccessfulUserDataOptions(std::unique_ptr<CollectUserDataOptions> collect_user_data_options) override; const CollectUserDataOptions* GetLastSuccessfulUserDataOptions() @@ -77,6 +81,15 @@ class FakeScriptExecutorUiDelegate : public ScriptExecutorUiDelegate { void ClearGenericUi() override; void ClearPersistentGenericUi() override; void SetShowFeedbackChip(bool show_feedback_chip) override; + bool SupportsExternalActions() override; + void ExecuteExternalAction( + const external::Action& external_action, + base::OnceCallback<void(ExternalActionDelegate::DomUpdateCallback)> + start_dom_checks_callback, + base::OnceCallback<void(const external::Result& result)> + end_action_callback) override; + void OnInterruptStarted() override; + void OnInterruptFinished() override; const std::vector<Details>& GetDetails() { return details_; } @@ -88,7 +101,15 @@ class FakeScriptExecutorUiDelegate : public ScriptExecutorUiDelegate { std::vector<UserAction>* GetUserActions() { return user_actions_.get(); } - CollectUserDataOptions* GetOptions() { return payment_request_options_; } + CollectUserDataOptions* GetOptions() { return collect_user_data_options_; } + + UserDataEventField GetCollectUserDataUiLoadingField() { + return collect_user_data_ui_loading__field_; + } + + std::vector<InterruptNotification> GetInterruptNotificationHistory() { + return interrupt_notification_history_; + } private: std::string status_message_; @@ -97,8 +118,10 @@ class FakeScriptExecutorUiDelegate : public ScriptExecutorUiDelegate { std::vector<Details> details_; std::unique_ptr<InfoBox> info_box_; std::unique_ptr<std::vector<UserAction>> user_actions_; - std::unique_ptr<CollectUserDataOptions> last_payment_request_options_; - raw_ptr<CollectUserDataOptions> payment_request_options_; + std::unique_ptr<CollectUserDataOptions> last_collect_user_data_options_; + raw_ptr<CollectUserDataOptions> collect_user_data_options_; + UserDataEventField collect_user_data_ui_loading__field_ = + UserDataEventField::NONE; std::unique_ptr<UserData> payment_request_info_; ConfigureBottomSheetProto::PeekMode peek_mode_ = ConfigureBottomSheetProto::HANDLE; @@ -106,6 +129,7 @@ class FakeScriptExecutorUiDelegate : public ScriptExecutorUiDelegate { bool expand_or_collapse_value_ = false; bool expand_sheet_for_prompt_ = true; std::unique_ptr<GenericUserInterfaceProto> persistent_generic_ui_; + std::vector<InterruptNotification> interrupt_notification_history_; }; } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/fake_starter_platform_delegate.cc b/chromium/components/autofill_assistant/browser/fake_starter_platform_delegate.cc index b1ae7191ba6..28b9938ed80 100644 --- a/chromium/components/autofill_assistant/browser/fake_starter_platform_delegate.cc +++ b/chromium/components/autofill_assistant/browser/fake_starter_platform_delegate.cc @@ -107,6 +107,10 @@ bool FakeStarterPlatformDelegate::GetIsLoggedIn() { return is_logged_in_; } +bool FakeStarterPlatformDelegate::GetIsSupervisedUser() { + return is_supervised_user_; +} + bool FakeStarterPlatformDelegate::GetIsCustomTab() const { return is_custom_tab_; } @@ -131,6 +135,16 @@ bool FakeStarterPlatformDelegate::IsAttached() { return is_attached_; } +const CommonDependencies* FakeStarterPlatformDelegate::GetCommonDependencies() + const { + return nullptr; +} + +const PlatformDependencies* +FakeStarterPlatformDelegate::GetPlatformDependencies() const { + return nullptr; +} + base::WeakPtr<StarterPlatformDelegate> FakeStarterPlatformDelegate::GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); diff --git a/chromium/components/autofill_assistant/browser/fake_starter_platform_delegate.h b/chromium/components/autofill_assistant/browser/fake_starter_platform_delegate.h index ecaf6d9dd5c..70884dd2623 100644 --- a/chromium/components/autofill_assistant/browser/fake_starter_platform_delegate.h +++ b/chromium/components/autofill_assistant/browser/fake_starter_platform_delegate.h @@ -50,11 +50,14 @@ class FakeStarterPlatformDelegate : public StarterPlatformDelegate { void SetProactiveHelpSettingEnabled(bool enabled) override; bool GetMakeSearchesAndBrowsingBetterEnabled() const override; bool GetIsLoggedIn() override; + bool GetIsSupervisedUser() override; bool GetIsCustomTab() const override; bool GetIsWebLayer() const override; bool GetIsTabCreatedByGSA() const override; std::unique_ptr<AssistantFieldTrialUtil> CreateFieldTrialUtil() override; bool IsAttached() override; + const CommonDependencies* GetCommonDependencies() const override; + const PlatformDependencies* GetPlatformDependencies() const override; base::WeakPtr<StarterPlatformDelegate> GetWeakPtr() override; // Intentionally public to give tests direct access. @@ -76,6 +79,7 @@ class FakeStarterPlatformDelegate : public StarterPlatformDelegate { bool proactive_help_enabled_ = true; bool msbb_enabled_ = true; bool is_logged_in_ = true; + bool is_supervised_user_ = false; bool is_custom_tab_ = true; bool is_web_layer_ = true; bool is_tab_created_by_gsa_ = true; diff --git a/chromium/components/autofill_assistant/browser/field_formatter.cc b/chromium/components/autofill_assistant/browser/field_formatter.cc index 0b0bd50a125..e4cd5949a56 100644 --- a/chromium/components/autofill_assistant/browser/field_formatter.cc +++ b/chromium/components/autofill_assistant/browser/field_formatter.cc @@ -88,14 +88,26 @@ void GetNameAndAbbreviationViaAlternativeStateNameMap( } } -std::string ApplyChunkReplacement( - const google::protobuf::Map<std::string, std::string>& replacements, - const std::string& value) { - const auto& it = replacements.find(value); - if (it != replacements.end()) { - return it->second; +std::string ApplyChunkReplacement(const ValueExpression::Chunk& chunk, + const std::string& value) { + std::string result = value; + const auto& it = chunk.replacements().find(value); + if (it != chunk.replacements().end()) { + result = it->second; } - return value; + for (const auto& regexp_replacement : chunk.regexp_replacements()) { + re2::RE2::Options options; + options.set_case_sensitive( + regexp_replacement.text_filter().case_sensitive()); + re2::RE2 regexp(regexp_replacement.text_filter().re2(), options); + + if (regexp_replacement.global()) { + RE2::GlobalReplace(&result, regexp, regexp_replacement.replacement()); + } else { + RE2::Replace(&result, regexp, regexp_replacement.replacement()); + } + } + return result; } std::string GetMaybeQuotedChunk(const std::string& value, bool quote_meta) { @@ -190,7 +202,7 @@ ClientStatus FormatExpression(const ValueExpression& value_expression, case ValueExpression::Chunk::CHUNK_NOT_SET: return ClientStatus(INVALID_ACTION); } - out_value->append(ApplyChunkReplacement(chunk.replacements(), chunk_value)); + out_value->append(ApplyChunkReplacement(chunk, chunk_value)); } return OkClientStatus(); diff --git a/chromium/components/autofill_assistant/browser/field_formatter_unittest.cc b/chromium/components/autofill_assistant/browser/field_formatter_unittest.cc index a45180c290a..174f6d001cb 100644 --- a/chromium/components/autofill_assistant/browser/field_formatter_unittest.cc +++ b/chromium/components/autofill_assistant/browser/field_formatter_unittest.cc @@ -439,6 +439,96 @@ TEST(FieldFormatterTest, FormatExpressionWithReplacements) { EXPECT_EQ("\\+0041", result); } +TEST(FieldFormatterTest, FormatExpressionWithRegexpReplacement) { + base::flat_map<Key, std::string> mappings = { + {Key(1), "AA"}, {Key(2), "Bb"}, {Key(3), "Cc"}, {Key(4), "DD"}}; + std::string result; + + ValueExpression value_expression; + auto* chunk = value_expression.add_chunk(); + // AA -> rA + chunk->set_key(1); + auto* case_insensitive_local = chunk->add_regexp_replacements(); + case_insensitive_local->mutable_text_filter()->set_re2("a"); + case_insensitive_local->mutable_text_filter()->set_case_sensitive(false); + case_insensitive_local->set_global(false); + case_insensitive_local->set_replacement("r"); + // Bb -> Br + chunk = value_expression.add_chunk(); + chunk->set_key(2); + auto* case_sensitive_local = chunk->add_regexp_replacements(); + case_sensitive_local->mutable_text_filter()->set_re2("b"); + case_sensitive_local->mutable_text_filter()->set_case_sensitive(true); + case_sensitive_local->set_global(false); + case_sensitive_local->set_replacement("r"); + // Cc -> rr + chunk = value_expression.add_chunk(); + chunk->set_key(3); + auto* case_insensitive_global = chunk->add_regexp_replacements(); + case_insensitive_global->mutable_text_filter()->set_re2("c"); + case_insensitive_global->mutable_text_filter()->set_case_sensitive(false); + case_insensitive_global->set_global(true); + case_insensitive_global->set_replacement("r"); + // DD -> rr + chunk = value_expression.add_chunk(); + chunk->set_key(4); + auto* case_insensitive_local_first = chunk->add_regexp_replacements(); + case_insensitive_local_first->mutable_text_filter()->set_re2("d"); + case_insensitive_local_first->mutable_text_filter()->set_case_sensitive( + false); + case_insensitive_local_first->set_global(false); + case_insensitive_local_first->set_replacement("r"); + auto* case_insensitive_local_second = chunk->add_regexp_replacements(); + case_insensitive_local_second->mutable_text_filter()->set_re2("d"); + case_insensitive_local_second->mutable_text_filter()->set_case_sensitive( + false); + case_insensitive_local_second->set_global(false); + case_insensitive_local_second->set_replacement("r"); + + EXPECT_EQ(ACTION_APPLIED, FormatExpression(value_expression, mappings, + /* quote_meta= */ false, &result) + .proto_status()); + EXPECT_EQ("rABrrrrr", result); +} + +TEST(FieldFormatterTest, FormatExpressionWithRegexpReplacementCapturingGroups) { + std::string result; + + ValueExpression value_expression; + auto* chunk = value_expression.add_chunk(); + chunk->set_text("John Doe"); + auto* group_replacement = chunk->add_regexp_replacements(); + group_replacement->mutable_text_filter()->set_re2("(\\w+)\\s(\\w+)"); + group_replacement->set_replacement("\\2 \\1"); + + EXPECT_EQ(ACTION_APPLIED, + FormatExpression(value_expression, + /* mappings= */ base::flat_map<Key, std::string>(), + /* quote_meta= */ false, &result) + .proto_status()); + EXPECT_EQ("Doe John", result); +} + +TEST(FieldFormatterTest, FormatExpressionWithInvalidRegexpReplacement) { + base::flat_map<Key, std::string> mappings = {{Key(1), "AA"}}; + std::string result; + + ValueExpression value_expression; + auto* chunk = value_expression.add_chunk(); + // AA -> ? + chunk->set_key(1); + auto* invalid_replacement = chunk->add_regexp_replacements(); + invalid_replacement->mutable_text_filter()->set_re2("^*"); + invalid_replacement->mutable_text_filter()->set_case_sensitive(false); + invalid_replacement->set_global(false); + invalid_replacement->set_replacement(""); + + EXPECT_EQ(ACTION_APPLIED, FormatExpression(value_expression, mappings, + /* quote_meta= */ false, &result) + .proto_status()); + EXPECT_EQ("AA", result); +} + TEST(FieldFormatterTest, FormatExpressionWithMemoryKey) { base::flat_map<Key, std::string> mappings = {{Key("_var0"), "valueA"}, {Key("_var1"), "val.ueB"}}; @@ -505,8 +595,15 @@ TEST(FieldFormatterTest, DifferentLocales) { Contains(Pair(Key(36), "Vereinigte Staaten"))); // Invalid locales default to "en-US". + // Android and Desktop use a different default. +#if BUILDFLAG(IS_ANDROID) EXPECT_THAT(CreateAutofillMappings(profile, ""), Contains(Pair(Key(36), "United States"))); +#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC) || \ + BUILDFLAG(IS_WIN) || BUILDFLAG(IS_FUCHSIA) + EXPECT_THAT(CreateAutofillMappings(profile, ""), + Contains(Pair(Key(36), "US"))); +#endif EXPECT_THAT(CreateAutofillMappings(profile, "invalid"), Contains(Pair(Key(36), "United States"))); } diff --git a/chromium/components/autofill_assistant/browser/full_card_requester.cc b/chromium/components/autofill_assistant/browser/full_card_requester.cc index c676a49e38d..f5359ca959a 100644 --- a/chromium/components/autofill_assistant/browser/full_card_requester.cc +++ b/chromium/components/autofill_assistant/browser/full_card_requester.cc @@ -38,16 +38,20 @@ void FullCardRequester::GetFullCard( } autofill::ContentAutofillDriver* driver = - factory->DriverForFrame(web_contents->GetMainFrame()); + factory->DriverForFrame(web_contents->GetPrimaryMainFrame()); if (!driver) { OnFullCardRequestFailed(FullCardRequest::FailureType::GENERIC_FAILURE); return; } - driver->browser_autofill_manager()->GetOrCreateFullCardRequest()->GetFullCard( + autofill::CreditCardCVCAuthenticator* cvc_authenticator = + driver->autofill_manager() + ->GetCreditCardAccessManager() + ->GetOrCreateCVCAuthenticator(); + cvc_authenticator->GetFullCardRequest()->GetFullCard( *card, autofill::AutofillClient::UnmaskCardReason::kAutofill, weak_ptr_factory_.GetWeakPtr(), - driver->browser_autofill_manager()->GetAsFullCardRequestUIDelegate()); + cvc_authenticator->GetAsFullCardRequestUIDelegate()); } FullCardRequester::~FullCardRequester() = default; diff --git a/chromium/components/autofill_assistant/browser/full_card_requester_unittest.cc b/chromium/components/autofill_assistant/browser/full_card_requester_unittest.cc index 7844e44aa12..4012f736681 100644 --- a/chromium/components/autofill_assistant/browser/full_card_requester_unittest.cc +++ b/chromium/components/autofill_assistant/browser/full_card_requester_unittest.cc @@ -50,8 +50,9 @@ class FullCardRequesterTest : public testing::Test { &browser_context_, nullptr); autofill_client_.SetPrefs(autofill::test::PrefServiceForTesting()); autofill::ContentAutofillDriverFactory::CreateForWebContentsAndDelegate( - web_contents_.get(), &autofill_client_, "en-US", - autofill::AutofillManager::DISABLE_AUTOFILL_DOWNLOAD_MANAGER); + web_contents_.get(), &autofill_client_, + base::BindRepeating(&autofill::BrowserDriverInitHook, &autofill_client_, + "en-US")); autofill_client_.set_test_payments_client( std::make_unique<autofill::payments::TestPaymentsClient>( test_url_loader_factory_.GetSafeWeakWrapper(), diff --git a/chromium/components/autofill_assistant/browser/headless/client_headless.cc b/chromium/components/autofill_assistant/browser/headless/client_headless.cc index 5849a3b1a16..62e79f666aa 100644 --- a/chromium/components/autofill_assistant/browser/headless/client_headless.cc +++ b/chromium/components/autofill_assistant/browser/headless/client_headless.cc @@ -16,13 +16,18 @@ #include "components/autofill_assistant/browser/autofill_assistant_tts_controller.h" #include "components/autofill_assistant/browser/controller.h" #include "components/autofill_assistant/browser/display_strings_util.h" +#include "components/autofill_assistant/browser/empty_website_login_manager_impl.h" #include "components/autofill_assistant/browser/features.h" +#include "components/autofill_assistant/browser/headless/external_script_controller_impl.h" #include "components/autofill_assistant/browser/public/ui_state.h" #include "components/autofill_assistant/browser/service/access_token_fetcher.h" #include "components/autofill_assistant/browser/switches.h" #include "components/autofill_assistant/browser/website_login_manager_impl.h" +#include "components/password_manager/content/browser/password_change_success_tracker_factory.h" #include "components/password_manager/core/browser/password_change_success_tracker.h" #include "components/password_manager/core/browser/password_manager_client.h" +#include "components/signin/public/identity_manager/account_info.h" +#include "components/signin/public/identity_manager/identity_manager.h" #include "components/version_info/version_info.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" @@ -33,22 +38,40 @@ namespace autofill_assistant { -ClientHeadless::ClientHeadless(content::WebContents* web_contents) - : web_contents_(web_contents) { - headless_ui_controller_ = std::make_unique<HeadlessUiController>(); +const char kOAuth2Scope[] = "https://www.googleapis.com/auth/userinfo.profile"; +const char kConsumerName[] = "autofill_assistant"; + +ClientHeadless::ClientHeadless( + content::WebContents* web_contents, + const CommonDependencies* common_dependencies, + ExternalActionDelegate* action_extension_delegate, + ExternalScriptControllerImpl* external_script_controller) + : web_contents_(web_contents), + common_dependencies_(common_dependencies), + external_script_controller_(external_script_controller) { + auto* password_manager_client = + common_dependencies_->GetPasswordManagerClient(web_contents); + if (password_manager_client) { + website_login_manager_ = std::make_unique<WebsiteLoginManagerImpl>( + password_manager_client, web_contents); + } else { + website_login_manager_ = std::make_unique<EmptyWebsiteLoginManagerImpl>(); + } + headless_ui_controller_ = + std::make_unique<HeadlessUiController>(action_extension_delegate); } ClientHeadless::~ClientHeadless() = default; void ClientHeadless::Start(const GURL& url, - std::unique_ptr<TriggerContext> trigger_context, - ControllerObserver* observer) { + std::unique_ptr<TriggerContext> trigger_context) { controller_ = std::make_unique<Controller>( web_contents_, /* client= */ this, base::DefaultTickClock::GetInstance(), RuntimeManager::GetForWebContents(web_contents_)->GetWeakPtr(), /* service= */ nullptr, ukm::UkmRecorder::Get(), - /* annotate_dom_model_service= */ nullptr); - controller_->AddObserver(observer); + /* annotate_dom_model_service= */ + common_dependencies_->GetOrCreateAnnotateDomModelService( + GetWebContents()->GetBrowserContext())); controller_->Start(url, std::move(trigger_context)); } @@ -65,18 +88,16 @@ void ClientHeadless::DestroyUISoon() {} void ClientHeadless::DestroyUI() {} version_info::Channel ClientHeadless::GetChannel() const { - // TODO(b/201964911): Inject on instantiation. - return version_info::Channel::DEV; + return common_dependencies_->GetChannel(); } std::string ClientHeadless::GetEmailAddressForAccessTokenAccount() const { - // TODO(b/201964911): return the Chrome signed in user. - return ""; + return GetSignedInEmail(); } -std::string ClientHeadless::GetChromeSignedInEmailAddress() const { - // TODO(b/201964911): return the Chrome signed in user. - return ""; +std::string ClientHeadless::GetSignedInEmail() const { + return common_dependencies_->GetSignedInEmail( + GetWebContents()->GetBrowserContext()); } absl::optional<std::pair<int, int>> ClientHeadless::GetWindowSize() const { @@ -95,34 +116,30 @@ void ClientHeadless::FetchPaymentsClientToken( } AccessTokenFetcher* ClientHeadless::GetAccessTokenFetcher() { - // TODO(b/201964911): get access token via native. - return nullptr; + return this; } autofill::PersonalDataManager* ClientHeadless::GetPersonalDataManager() const { - // TODO(b/201964911): support PersonalDataManager. - return nullptr; + return common_dependencies_->GetPersonalDataManager( + GetWebContents()->GetBrowserContext()); } WebsiteLoginManager* ClientHeadless::GetWebsiteLoginManager() const { - // TODO(b/201964911): return instance. - return nullptr; + return website_login_manager_.get(); } password_manager::PasswordChangeSuccessTracker* ClientHeadless::GetPasswordChangeSuccessTracker() const { - // TODO(b/201964911): return instance. - return nullptr; + return password_manager::PasswordChangeSuccessTrackerFactory:: + GetForBrowserContext(GetWebContents()->GetBrowserContext()); } std::string ClientHeadless::GetLocale() const { - // TODO(b/201964911): get locale via native. - return "en-us"; + return common_dependencies_->GetLocale(); } std::string ClientHeadless::GetCountryCode() const { - // TODO(b/201964911): get country code via native. - return "us"; + return common_dependencies_->GetCountryCode(); } DeviceContext ClientHeadless::GetDeviceContext() const { @@ -141,9 +158,7 @@ content::WebContents* ClientHeadless::GetWebContents() const { return web_contents_; } -void ClientHeadless::RecordDropOut(Metrics::DropOutReason reason) { - // TODO(b/201964911): Add metrics. -} +void ClientHeadless::RecordDropOut(Metrics::DropOutReason reason) {} bool ClientHeadless::HasHadUI() const { return false; @@ -153,16 +168,67 @@ ScriptExecutorUiDelegate* ClientHeadless::GetScriptExecutorUiDelegate() { return headless_ui_controller_.get(); } -void ClientHeadless::Shutdown(Metrics::DropOutReason reason) {} +bool ClientHeadless::MustUseBackendData() const { + return false; +} + +void ClientHeadless::GetAnnotateDomModelVersion( + base::OnceCallback<void(absl::optional<int64_t>)> callback) const { + std::move(callback).Run(absl::nullopt); +} + +void ClientHeadless::Shutdown(Metrics::DropOutReason reason) { + // This call can cause Controller to be destroyed. For this reason we delay it + // to avoid UAF errors in the controller. + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&ClientHeadless::NotifyScriptEnded, + weak_ptr_factory_.GetWeakPtr(), reason)); +} + +void ClientHeadless::NotifyScriptEnded(Metrics::DropOutReason reason) { + external_script_controller_->NotifyScriptEnded(reason); + + // This instance can be destroyed by the above call, so nothing should be + // added here. +} void ClientHeadless::FetchAccessToken( base::OnceCallback<void(bool, const std::string&)> callback) { - // TODO(b/201964911): get access token via native. - std::move(callback).Run(false, ""); + DCHECK(!fetch_access_token_callback_); + fetch_access_token_callback_ = std::move(callback); + auto* identity_manager = common_dependencies_->GetIdentityManager( + GetWebContents()->GetBrowserContext()); + access_token_fetcher_ = identity_manager->CreateAccessTokenFetcherForAccount( + identity_manager->GetPrimaryAccountId(signin::ConsentLevel::kSync), + kConsumerName, {kOAuth2Scope}, + base::BindOnce(&ClientHeadless::OnAccessTokenFetchComplete, + weak_ptr_factory_.GetWeakPtr()), + signin::AccessTokenFetcher::Mode::kImmediate); +} + +void ClientHeadless::OnAccessTokenFetchComplete( + GoogleServiceAuthError error, + signin::AccessTokenInfo access_token_info) { + if (!fetch_access_token_callback_) { + return; + } + + if (error.state() != GoogleServiceAuthError::NONE) { + VLOG(2) << "OAuth2 token request failed. " << error.state() << ": " + << error.ToString(); + + std::move(fetch_access_token_callback_).Run(false, ""); + return; + } + std::move(fetch_access_token_callback_).Run(true, access_token_info.token); } void ClientHeadless::InvalidateAccessToken(const std::string& access_token) { - // TODO(b/201964911): get access token via native. + auto* identity_manager = common_dependencies_->GetIdentityManager( + GetWebContents()->GetBrowserContext()); + identity_manager->RemoveAccessTokenFromCache( + identity_manager->GetPrimaryAccountId(signin::ConsentLevel::kSync), + {kOAuth2Scope}, access_token); } } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/headless/client_headless.h b/chromium/components/autofill_assistant/browser/headless/client_headless.h index a06f67a67b8..a29e05344ea 100644 --- a/chromium/components/autofill_assistant/browser/headless/client_headless.h +++ b/chromium/components/autofill_assistant/browser/headless/client_headless.h @@ -11,32 +11,39 @@ #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "components/autofill_assistant/browser/client.h" +#include "components/autofill_assistant/browser/common_dependencies.h" #include "components/autofill_assistant/browser/controller.h" #include "components/autofill_assistant/browser/device_context.h" #include "components/autofill_assistant/browser/headless/headless_ui_controller.h" +#include "components/autofill_assistant/browser/platform_dependencies.h" #include "components/autofill_assistant/browser/service.pb.h" #include "components/autofill_assistant/browser/service/access_token_fetcher.h" #include "components/autofill_assistant/browser/service/service.h" #include "components/autofill_assistant/browser/website_login_manager.h" +#include "components/signin/public/identity_manager/identity_manager.h" #include "content/public/browser/web_contents.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "url/gurl.h" namespace autofill_assistant { +class ExternalScriptControllerImpl; + // An Autofill Assistant client for headless runs. class ClientHeadless : public Client, public AccessTokenFetcher { public: - explicit ClientHeadless(content::WebContents* web_contents); + explicit ClientHeadless( + content::WebContents* web_contents, + const CommonDependencies* common_dependencies, + ExternalActionDelegate* action_extension_delegate, + ExternalScriptControllerImpl* external_script_controller); ClientHeadless(const ClientHeadless&) = delete; ClientHeadless& operator=(const ClientHeadless&) = delete; ~ClientHeadless() override; bool IsRunning() const; - void Start(const GURL& url, - std::unique_ptr<TriggerContext> trigger_context, - ControllerObserver* observer); + void Start(const GURL& url, std::unique_ptr<TriggerContext> trigger_context); // Overrides Client void AttachUI() override; @@ -44,7 +51,7 @@ class ClientHeadless : public Client, public AccessTokenFetcher { void DestroyUI() override; version_info::Channel GetChannel() const override; std::string GetEmailAddressForAccessTokenAccount() const override; - std::string GetChromeSignedInEmailAddress() const override; + std::string GetSignedInEmail() const override; absl::optional<std::pair<int, int>> GetWindowSize() const override; ClientContextProto::ScreenOrientation GetScreenOrientation() const override; void FetchPaymentsClientToken( @@ -64,6 +71,10 @@ class ClientHeadless : public Client, public AccessTokenFetcher { void RecordDropOut(Metrics::DropOutReason reason) override; bool HasHadUI() const override; ScriptExecutorUiDelegate* GetScriptExecutorUiDelegate() override; + bool MustUseBackendData() const override; + void GetAnnotateDomModelVersion( + base::OnceCallback<void(absl::optional<int64_t>)> callback) + const override; // Overrides AccessTokenFetcher void FetchAccessToken( @@ -74,10 +85,20 @@ class ClientHeadless : public Client, public AccessTokenFetcher { void CreateController(); void DestroyController(); void SafeDestroyController(Metrics::DropOutReason reason); + void OnAccessTokenFetchComplete(GoogleServiceAuthError error, + signin::AccessTokenInfo access_token_info); + void NotifyScriptEnded(Metrics::DropOutReason reason); - content::WebContents* web_contents_; + raw_ptr<content::WebContents> web_contents_; std::unique_ptr<Controller> controller_; + const raw_ptr<const CommonDependencies> common_dependencies_; + std::unique_ptr<WebsiteLoginManager> website_login_manager_; std::unique_ptr<HeadlessUiController> headless_ui_controller_; + raw_ptr<signin::IdentityManager> identity_manager_ = nullptr; + std::unique_ptr<signin::AccessTokenFetcher> access_token_fetcher_; + base::OnceCallback<void(bool, const std::string&)> + fetch_access_token_callback_; + const raw_ptr<ExternalScriptControllerImpl> external_script_controller_; base::WeakPtrFactory<ClientHeadless> weak_ptr_factory_{this}; }; diff --git a/chromium/components/autofill_assistant/browser/headless/external_script_controller_impl.cc b/chromium/components/autofill_assistant/browser/headless/external_script_controller_impl.cc index 477a5c8bf33..f1c5285d753 100644 --- a/chromium/components/autofill_assistant/browser/headless/external_script_controller_impl.cc +++ b/chromium/components/autofill_assistant/browser/headless/external_script_controller_impl.cc @@ -11,10 +11,17 @@ namespace autofill_assistant { ExternalScriptControllerImpl::ExternalScriptControllerImpl( - content::WebContents* web_contents) + content::WebContents* web_contents, + ExternalActionDelegate* action_extension_delegate) : web_contents_(web_contents) { DCHECK(web_contents_); - client_ = std::make_unique<ClientHeadless>(web_contents); + + auto* starter = Starter::FromWebContents(web_contents_); + if (starter) { + client_ = std::make_unique<ClientHeadless>(web_contents, + starter->GetCommonDependencies(), + action_extension_delegate, this); + } } ExternalScriptControllerImpl::~ExternalScriptControllerImpl() = default; @@ -29,8 +36,9 @@ void ExternalScriptControllerImpl::StartScript( return; } auto* starter = Starter::FromWebContents(web_contents_); - // The starter has not yet been initialized. - if (!starter) { + // The starter has not yet been initialized or was not initialized at the + // time the constructor was called. + if (!starter || !client_) { std::move(script_ended_callback).Run({false}); return; } @@ -40,8 +48,7 @@ void ExternalScriptControllerImpl::StartScript( auto trigger_context = std::make_unique<TriggerContext>( std::move(parameters), /* experiment_ids = */ "", - // TODO(b/201964911): set the right value for Android flows - /*is_cct = */ false, + starter->GetPlatformDependencies()->IsCustomTab(*web_contents_), /*onboarding_shown = */ false, /*is_direct_action = */ false, /* initial_url = */ "", @@ -62,43 +69,13 @@ void ExternalScriptControllerImpl::OnReadyToStart( } // TODO(b/201964911): At this point we should be sure no other Controller // exists on this tab. Add logic to the starter to check that's the case. - client_->Start(*url, std::move(trigger_context), this); + client_->Start(*url, std::move(trigger_context)); } -void ExternalScriptControllerImpl::OnShutdown(Metrics::DropOutReason reason) { - // TODO(b/201964911): since the Controller has not been destroyed yet, this - // could lead to a race condition if |StartScript| is called again right away - // after receiving this notification. +void ExternalScriptControllerImpl::NotifyScriptEnded( + Metrics::DropOutReason reason) { std::move(script_ended_callback_) .Run({reason == Metrics::DropOutReason::SCRIPT_SHUTDOWN}); } -void ExternalScriptControllerImpl::OnStateChanged( - AutofillAssistantState new_state) {} -void ExternalScriptControllerImpl::OnKeyboardSuppressionStateChanged( - bool should_suppress_keyboard) {} -void ExternalScriptControllerImpl::CloseCustomTab() {} -void ExternalScriptControllerImpl::OnError(const std::string& error_message, - Metrics::DropOutReason reason) {} -void ExternalScriptControllerImpl::OnUserDataChanged( - const UserData& user_data, - UserDataFieldChange field_change) {} -void ExternalScriptControllerImpl::OnTouchableAreaChanged( - const RectF& visual_viewport, - const std::vector<RectF>& touchable_areas, - const std::vector<RectF>& restricted_areas) {} -void ExternalScriptControllerImpl::OnViewportModeChanged(ViewportMode mode) {} -void ExternalScriptControllerImpl::OnOverlayColorsChanged( - const ExecutionDelegate::OverlayColors& colors) {} -void ExternalScriptControllerImpl::OnClientSettingsChanged( - const ClientSettings& settings) {} -void ExternalScriptControllerImpl::OnShouldShowOverlayChanged( - bool should_show) {} -void ExternalScriptControllerImpl::OnExecuteScript( - const std::string& start_message) {} -void ExternalScriptControllerImpl::OnStart( - const TriggerContext& trigger_context) {} -void ExternalScriptControllerImpl::OnStop() {} -void ExternalScriptControllerImpl::OnResetState() {} -void ExternalScriptControllerImpl::OnUiShownChanged(bool shown) {} } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/headless/external_script_controller_impl.h b/chromium/components/autofill_assistant/browser/headless/external_script_controller_impl.h index fb79972b327..88ae49bab94 100644 --- a/chromium/components/autofill_assistant/browser/headless/external_script_controller_impl.h +++ b/chromium/components/autofill_assistant/browser/headless/external_script_controller_impl.h @@ -10,19 +10,22 @@ #include <vector> #include "base/callback_helpers.h" +#include "base/memory/raw_ptr.h" #include "components/autofill_assistant/browser/autofill_assistant_impl.h" #include "components/autofill_assistant/browser/controller.h" #include "components/autofill_assistant/browser/controller_observer.h" #include "components/autofill_assistant/browser/execution_delegate.h" #include "components/autofill_assistant/browser/headless/client_headless.h" +#include "components/autofill_assistant/browser/metrics.h" #include "components/autofill_assistant/browser/script_executor_ui_delegate.h" namespace autofill_assistant { -class ExternalScriptControllerImpl : public ExternalScriptController, - public ControllerObserver { +class ExternalScriptControllerImpl : public ExternalScriptController { public: - ExternalScriptControllerImpl(content::WebContents* web_contents); + ExternalScriptControllerImpl( + content::WebContents* web_contents, + ExternalActionDelegate* action_extension_delegate); ExternalScriptControllerImpl(const ExternalScriptControllerImpl&) = delete; ExternalScriptControllerImpl& operator=(const ExternalScriptControllerImpl&) = @@ -35,38 +38,16 @@ class ExternalScriptControllerImpl : public ExternalScriptController, const base::flat_map<std::string, std::string>& script_parameters, base::OnceCallback<void(ScriptResult)> script_ended_callback) override; - // Overrides ControllerObserver. - // TODO(b/201964911): Add empty default ControllerObserver to avoid having all - // of these no-op implementations here. - void OnStateChanged(AutofillAssistantState new_state) override; - void OnKeyboardSuppressionStateChanged( - bool should_suppress_keyboard) override; - void CloseCustomTab() override; - void OnError(const std::string& error_message, - Metrics::DropOutReason reason) override; - void OnUserDataChanged(const UserData& user_data, - UserDataFieldChange field_change) override; - void OnTouchableAreaChanged( - const RectF& visual_viewport, - const std::vector<RectF>& touchable_areas, - const std::vector<RectF>& restricted_areas) override; - void OnViewportModeChanged(ViewportMode mode) override; - void OnOverlayColorsChanged( - const ExecutionDelegate::OverlayColors& colors) override; - void OnClientSettingsChanged(const ClientSettings& settings) override; - void OnShouldShowOverlayChanged(bool should_show) override; - void OnExecuteScript(const std::string& start_message) override; - void OnStart(const TriggerContext& trigger_context) override; - void OnStop() override; - void OnResetState() override; - void OnUiShownChanged(bool shown) override; - void OnShutdown(Metrics::DropOutReason reason) override; + // Notifies the external caller that the script has ended. Note that the + // external caller can decide to destroy this instance once it has been + // notified so this method should not be called directly to avoid UAF issues. + void NotifyScriptEnded(Metrics::DropOutReason reason); private: void OnReadyToStart(bool can_start, absl::optional<GURL> url, std::unique_ptr<TriggerContext> trigger_context); - content::WebContents* web_contents_; + raw_ptr<content::WebContents> web_contents_; std::unique_ptr<ClientHeadless> client_; base::OnceCallback<void(ScriptResult)> script_ended_callback_; diff --git a/chromium/components/autofill_assistant/browser/headless/headless_ui_controller.cc b/chromium/components/autofill_assistant/browser/headless/headless_ui_controller.cc index 89a8b28ddad..5def234bbd5 100644 --- a/chromium/components/autofill_assistant/browser/headless/headless_ui_controller.cc +++ b/chromium/components/autofill_assistant/browser/headless/headless_ui_controller.cc @@ -6,7 +6,37 @@ namespace autofill_assistant { -HeadlessUiController::HeadlessUiController() = default; +HeadlessUiController::HeadlessUiController( + ExternalActionDelegate* action_extension_delegate) + : action_extension_delegate_(action_extension_delegate) {} + +bool HeadlessUiController::SupportsExternalActions() { + return action_extension_delegate_ != nullptr; +} + +void HeadlessUiController::ExecuteExternalAction( + const external::Action& external_action, + base::OnceCallback<void(ExternalActionDelegate::DomUpdateCallback)> + start_dom_checks_callback, + base::OnceCallback<void(const external::Result& result)> + end_action_callback) { + DCHECK(action_extension_delegate_); + + action_extension_delegate_->OnActionRequested( + external_action, std::move(start_dom_checks_callback), + std::move(end_action_callback)); +} + +void HeadlessUiController::OnInterruptStarted() { + if (action_extension_delegate_) { + action_extension_delegate_->OnInterruptStarted(); + } +} +void HeadlessUiController::OnInterruptFinished() { + if (action_extension_delegate_) { + action_extension_delegate_->OnInterruptFinished(); + } +} // TODO(b/201964911): fail execution instead of just logging a warning if a // method is unexpectedly called. @@ -122,6 +152,11 @@ void HeadlessUiController::SetCollectUserDataOptions( CollectUserDataOptions* options) { VLOG(2) << "Unexpected UI method called: " << __func__; } +void HeadlessUiController::SetCollectUserDataUiState( + bool loading, + UserDataEventField event_field) { + VLOG(2) << "Unexpected UI method called: " << __func__; +} void HeadlessUiController::SetLastSuccessfulUserDataOptions( std::unique_ptr<CollectUserDataOptions> collect_user_data_options) { VLOG(2) << "Unexpected UI method called: " << __func__; diff --git a/chromium/components/autofill_assistant/browser/headless/headless_ui_controller.h b/chromium/components/autofill_assistant/browser/headless/headless_ui_controller.h index 79b79d39f9f..eae4973e94f 100644 --- a/chromium/components/autofill_assistant/browser/headless/headless_ui_controller.h +++ b/chromium/components/autofill_assistant/browser/headless/headless_ui_controller.h @@ -19,7 +19,10 @@ namespace autofill_assistant { class HeadlessUiController : public ScriptExecutorUiDelegate { public: - HeadlessUiController(); + // The |action_extension_delegate| parameter can be null but if an extension + // action is requested it will cause the script to fail. + explicit HeadlessUiController( + ExternalActionDelegate* action_extension_delegate); // Overrides ScriptExecutorUiDelegate void SetStatusMessage(const std::string& message) override; @@ -68,10 +71,24 @@ class HeadlessUiController : public ScriptExecutorUiDelegate { void SetExpandSheetForPromptAction(bool expand) override; void SetCollectUserDataOptions(CollectUserDataOptions* options) override; + void SetCollectUserDataUiState(bool loading, + UserDataEventField event_field) override; void SetLastSuccessfulUserDataOptions(std::unique_ptr<CollectUserDataOptions> collect_user_data_options) override; const CollectUserDataOptions* GetLastSuccessfulUserDataOptions() const override; + bool SupportsExternalActions() override; + void ExecuteExternalAction( + const external::Action& external_action, + base::OnceCallback<void(ExternalActionDelegate::DomUpdateCallback)> + start_dom_checks_callback, + base::OnceCallback<void(const external::Result& result)> + end_action_callback) override; + void OnInterruptStarted() override; + void OnInterruptFinished() override; + + private: + const raw_ptr<ExternalActionDelegate> action_extension_delegate_; }; } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/js_flow_devtools_wrapper.cc b/chromium/components/autofill_assistant/browser/js_flow_devtools_wrapper.cc new file mode 100644 index 00000000000..de0f43b0616 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/js_flow_devtools_wrapper.cc @@ -0,0 +1,172 @@ +// Copyright 2022 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_assistant/browser/js_flow_devtools_wrapper.h" + +#include "base/feature_list.h" +#include "base/logging.h" +#include "base/strings/strcat.h" +#include "components/autofill_assistant/browser/features.h" +#include "components/autofill_assistant/browser/js_flow_util.h" +#include "components/autofill_assistant/browser/service.pb.h" +#include "components/autofill_assistant/browser/web/web_controller_util.h" + +namespace autofill_assistant { + +constexpr char kAboutBlankURL[] = "about:blank"; + +namespace { +std::unique_ptr<DevtoolsClient> CreateDevtoolsClient( + content::WebContents* web_contents) { + return std::make_unique<DevtoolsClient>( + content::DevToolsAgentHost::GetOrCreateFor(web_contents), + base::FeatureList::IsEnabled( + autofill_assistant::features:: + kAutofillAssistantFullJsFlowStackTraces)); +} +} // namespace + +JsFlowDevtoolsWrapper::JsFlowDevtoolsWrapper( + content::BrowserContext* browser_context) + : browser_context_(browser_context) {} + +JsFlowDevtoolsWrapper::JsFlowDevtoolsWrapper(content::WebContents* web_contents) + : devtools_client_(CreateDevtoolsClient(web_contents)) {} + +void JsFlowDevtoolsWrapper::SetJsFlowLibrary( + const std::string& js_flow_library) { + if (InitStarted() || InitDone()) { + LOG(ERROR) << "The js flow library can't be set after the devtools wrapper " + "has started initializing."; + return; + } + + js_flow_library_ = js_flow_library; +} + +JsFlowDevtoolsWrapper::~JsFlowDevtoolsWrapper() = default; + +void JsFlowDevtoolsWrapper::GetDevtoolsAndMaybeInit( + base::OnceCallback<void(const ClientStatus& status, + DevtoolsClient* devtools_client, + int isolated_world_context_id)> callback) { + if (InitDone()) { + std::move(callback).Run(init_status_, devtools_client_.get(), + isolated_world_context_id_); + return; + } + + if (InitStarted()) { + LOG(ERROR) << "Invoked " << __func__ << " while already initializing"; + return; + } + callback_ = std::move(callback); + + MabyeCreateDevtoolsClient(); + devtools_client_->GetPage()->GetFrameTree( + js_flow_util::kMainFrame, + base::BindOnce(&JsFlowDevtoolsWrapper::OnGetFrameTree, + weak_ptr_factory_.GetWeakPtr())); +} + +void JsFlowDevtoolsWrapper::MabyeCreateDevtoolsClient() { + if (devtools_client_) { + return; + } + + // To execute JS flows we create a new web contents that persists + // across navigations. + web_contents_ = content::WebContents::Create( + content::WebContents::CreateParams(browser_context_)); + // Navigate to a blank page to connect to a frame tree. + web_contents_->GetController().LoadURLWithParams( + content::NavigationController::LoadURLParams(GURL(kAboutBlankURL))); + + devtools_client_ = CreateDevtoolsClient(web_contents_.get()); +} + +void JsFlowDevtoolsWrapper::OnGetFrameTree( + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<page::GetFrameTreeResult> result) { + if (!result) { + LOG(ERROR) << "failed to retrieve frame tree"; + init_status_ = + JavaScriptErrorStatus(reply_status, __FILE__, __LINE__, nullptr); + FinishInit(); + return; + } + VLOG(2) << "frame tree retrieved"; + + devtools_client_->GetPage()->CreateIsolatedWorld( + page::CreateIsolatedWorldParams::Builder() + .SetFrameId(result->GetFrameTree()->GetFrame()->GetId()) + .Build(), + js_flow_util::kMainFrame, + base::BindOnce(&JsFlowDevtoolsWrapper::OnIsolatedWorldCreated, + weak_ptr_factory_.GetWeakPtr())); +} + +void JsFlowDevtoolsWrapper::OnIsolatedWorldCreated( + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<page::CreateIsolatedWorldResult> result) { + if (!result) { + LOG(ERROR) << "failed to create isolated world"; + init_status_ = + JavaScriptErrorStatus(reply_status, __FILE__, __LINE__, nullptr); + FinishInit(); + return; + } + VLOG(2) << "isolated world created"; + + isolated_world_context_id_ = result->GetExecutionContextId(); + + // Append the source url. + const auto js_flow_library = base::StrCat( + {js_flow_library_, js_flow_util::GetDevtoolsSourceUrlCommentToAppend( + UnexpectedErrorInfoProto::JS_FLOW_LIBRARY)}); + + devtools_client_->GetRuntime()->Evaluate( + runtime::EvaluateParams::Builder() + .SetExpression(js_flow_library) + .SetContextId(isolated_world_context_id_) + .SetAwaitPromise(true) + .SetReturnByValue(true) + .Build(), + js_flow_util::kMainFrame, + base::BindOnce(&JsFlowDevtoolsWrapper::OnJsFlowLibraryEvaluated, + weak_ptr_factory_.GetWeakPtr())); +} + +void JsFlowDevtoolsWrapper::OnJsFlowLibraryEvaluated( + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<runtime::EvaluateResult> result) { + std::unique_ptr<base::Value> unused; + init_status_ = js_flow_util::ExtractFlowReturnValue( + reply_status, result.get(), unused, {}, 0); + + if (init_status_.ok()) { + VLOG(2) << "JS flow library (length " << js_flow_library_.length() + << ") evaluated"; + } else { + LOG(ERROR) << "JS flow library (length " << js_flow_library_.length() + << ") could not be evaluated"; + } + + FinishInit(); +} + +void JsFlowDevtoolsWrapper::FinishInit() { + std::move(callback_).Run(init_status_, devtools_client_.get(), + isolated_world_context_id_); +} + +bool JsFlowDevtoolsWrapper::InitStarted() { + return !callback_.is_null(); +} + +bool JsFlowDevtoolsWrapper::InitDone() { + return isolated_world_context_id_ != -1 || !init_status_.ok(); +} + +} // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/js_flow_devtools_wrapper.h b/chromium/components/autofill_assistant/browser/js_flow_devtools_wrapper.h new file mode 100644 index 00000000000..2f98ffa9d7d --- /dev/null +++ b/chromium/components/autofill_assistant/browser/js_flow_devtools_wrapper.h @@ -0,0 +1,98 @@ +// Copyright 2022 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_ASSISTANT_BROWSER_JS_FLOW_DEVTOOLS_WRAPPER_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_JS_FLOW_DEVTOOLS_WRAPPER_H_ + +#include "base/callback.h" +#include "base/callback_list.h" +#include "base/memory/weak_ptr.h" +#include "components/autofill_assistant/browser/client_status.h" +#include "components/autofill_assistant/browser/devtools/devtools_client.h" +#include "content/public/browser/web_contents.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace autofill_assistant { + +// Wraps a devtools client for js flow execution. After the first call to +// GetDevtoolsAndMaybeInit the js flow library can not be changed anymore. +// NOTE: This class is tested in ScriptExecutorBrowserTest and +// JsFlowExecutorImplBrowserTest. +class JsFlowDevtoolsWrapper { + public: + // Creates and owns the web contents. The devtools client and web contents are + // lazily initialized on the first call to GetDevtoolsAndMaybeInit. + explicit JsFlowDevtoolsWrapper(content::BrowserContext* browser_context); + + // Does not own the web contents. + explicit JsFlowDevtoolsWrapper(content::WebContents* web_contents); + + ~JsFlowDevtoolsWrapper(); + JsFlowDevtoolsWrapper(const JsFlowDevtoolsWrapper&) = delete; + JsFlowDevtoolsWrapper& operator=(const JsFlowDevtoolsWrapper&) = delete; + + // The first call to this function starts the initialization. Afterwards the + // callback is called immediately with the results from the initialization. + // + // If an error occurred during initialization status.ok() is false. The + // devtools client and isolated world context id are only guaranteed to be + // valid if status.ok() is true. + void GetDevtoolsAndMaybeInit( + base::OnceCallback<void(const ClientStatus& status, + DevtoolsClient* devtools_client, + int isolated_world_context_id)> callback); + + // Sets the js flow library. Can only be called before the first call to + // GetDevtoolsAndMaybeInit. + void SetJsFlowLibrary(const std::string& js_flow_library); + + private: + // Creates the web contents and devtools client if the browser context + // constructor was used. + void MabyeCreateDevtoolsClient(); + + void OnGetFrameTree(const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<page::GetFrameTreeResult> result); + + void OnIsolatedWorldCreated( + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<page::CreateIsolatedWorldResult> result); + + void OnJsFlowLibraryEvaluated( + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<runtime::EvaluateResult> result); + + // Called when the devtools environment has finished initializing. Guaranteed + // to be called after the first call to GetDevtoolsAndMaybeInit (eventually). + void FinishInit(); + + // True if the wrapper is currently initializing (after + // GetDevtoolsAndMaybeInit was called but + // before FinishInit was called). + bool InitStarted(); + // True after FinishInit was called. + bool InitDone(); + + content::BrowserContext* browser_context_; + std::string js_flow_library_; + + // Only set for the browser context constructor. Lazily instantiated. + std::unique_ptr<content::WebContents> web_contents_; + std::unique_ptr<DevtoolsClient> devtools_client_; + + // Set after the wrapper has finished initialization. + ClientStatus init_status_ = ClientStatus(ACTION_APPLIED); + int isolated_world_context_id_ = -1; + + base::OnceCallback<void(const ClientStatus& status, + DevtoolsClient* devtools_client, + int isolated_world_context_id)> + callback_; + + base::WeakPtrFactory<JsFlowDevtoolsWrapper> weak_ptr_factory_{this}; +}; + +} // namespace autofill_assistant + +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_JS_FLOW_DEVTOOLS_WRAPPER_H_ diff --git a/chromium/components/autofill_assistant/browser/js_flow_executor_impl.cc b/chromium/components/autofill_assistant/browser/js_flow_executor_impl.cc index ce9132f80c1..d6ab13e3a6c 100644 --- a/chromium/components/autofill_assistant/browser/js_flow_executor_impl.cc +++ b/chromium/components/autofill_assistant/browser/js_flow_executor_impl.cc @@ -14,6 +14,9 @@ #include "components/autofill_assistant/browser/js_flow_util.h" #include "components/autofill_assistant/browser/parse_jspb.h" #include "components/autofill_assistant/browser/web/web_controller_util.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/navigation_controller.h" +#include "content/public/browser/web_contents.h" namespace autofill_assistant { namespace { @@ -88,8 +91,6 @@ constexpr char kFulfillActionPromise[] = R"( } )"; -constexpr char kMainFrame[] = ""; - absl::optional<std::string> ConvertActionToBytes(const base::Value* action, std::string* error_message) { if (action == nullptr) { @@ -115,14 +116,11 @@ absl::optional<std::string> ConvertActionToBytes(const base::Value* action, } // namespace -JsFlowExecutorImpl::JsFlowExecutorImpl(content::WebContents* web_contents, - Delegate* delegate) +JsFlowExecutorImpl::JsFlowExecutorImpl( + Delegate* delegate, + JsFlowDevtoolsWrapper* js_flow_devtools_wrapper) : delegate_(delegate), - devtools_client_(std::make_unique<DevtoolsClient>( - content::DevToolsAgentHost::GetOrCreateFor(web_contents), - base::FeatureList::IsEnabled( - autofill_assistant::features:: - kAutofillAssistantFullJsFlowStackTraces))) {} + js_flow_devtools_wrapper_(js_flow_devtools_wrapper) {} JsFlowExecutorImpl::~JsFlowExecutorImpl() = default; @@ -138,53 +136,23 @@ void JsFlowExecutorImpl::Start( js_flow_ = std::make_unique<std::string>(js_flow); callback_ = std::move(callback); - if (isolated_world_context_id_ == -1) { - devtools_client_->GetPage()->GetFrameTree( - kMainFrame, base::BindOnce(&JsFlowExecutorImpl::OnGetFrameTree, - weak_ptr_factory_.GetWeakPtr())); - } else { - InternalStart(); - } -} - -void JsFlowExecutorImpl::OnGetFrameTree( - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<page::GetFrameTreeResult> result) { - if (!result) { - LOG(ERROR) << "Failed to retrieve frame tree"; - std::move(callback_).Run( - JavaScriptErrorStatus(reply_status, __FILE__, __LINE__, nullptr), - nullptr); - return; - } - devtools_client_->GetPage()->CreateIsolatedWorld( - page::CreateIsolatedWorldParams::Builder() - .SetFrameId(result->GetFrameTree()->GetFrame()->GetId()) - .Build(), - kMainFrame, - base::BindOnce(&JsFlowExecutorImpl::IsolatedWorldCreated, - weak_ptr_factory_.GetWeakPtr())); + js_flow_devtools_wrapper_->GetDevtoolsAndMaybeInit(base::BindOnce( + &JsFlowExecutorImpl::InternalStart, weak_ptr_factory_.GetWeakPtr())); } -void JsFlowExecutorImpl::IsolatedWorldCreated( - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<page::CreateIsolatedWorldResult> result) { - if (!result) { - LOG(ERROR) << "Failed to create isolated world"; - std::move(callback_).Run( - JavaScriptErrorStatus(reply_status, __FILE__, __LINE__, nullptr), - nullptr); +void JsFlowExecutorImpl::InternalStart(const ClientStatus& status, + DevtoolsClient* devtools_client, + const int isolated_world_context_id) { + DCHECK(callback_); + + if (!status.ok()) { + RunCallback(status, nullptr); return; } - isolated_world_context_id_ = result->GetExecutionContextId(); - InternalStart(); -} - -void JsFlowExecutorImpl::InternalStart() { - DCHECK(isolated_world_context_id_ != -1); - DCHECK(callback_); + devtools_client_ = devtools_client; + isolated_world_context_id_ = isolated_world_context_id; // Before running the flow in the sandbox, we define a promise that // the flow may fulfill to request execution of a native action. @@ -192,9 +160,11 @@ void JsFlowExecutorImpl::InternalStart() { // Wrap the main js_flow in an async function containing a method to // request native actions. This is essentially providing |js_flow| with a - // JS API to call native functionality. + // JS API to call native functionality. Also appends the source url. js_flow_ = std::make_unique<std::string>( - base::StrCat({kLeadingWrapper, *js_flow_, kTrailingWrapper})); + base::StrCat({kLeadingWrapper, *js_flow_, kTrailingWrapper, + js_flow_util::GetDevtoolsSourceUrlCommentToAppend( + UnexpectedErrorInfoProto::JS_FLOW)})); // Run the wrapped js_flow in the sandbox and serve potential native action // requests as they arrive. @@ -205,7 +175,7 @@ void JsFlowExecutorImpl::InternalStart() { .SetReturnByValue(true) .SetContextId(isolated_world_context_id_) .Build(), - kMainFrame, + js_flow_util::kMainFrame, base::BindOnce(&JsFlowExecutorImpl::OnFlowFinished, weak_ptr_factory_.GetWeakPtr())); } @@ -217,7 +187,7 @@ void JsFlowExecutorImpl::RefreshNativeActionPromise() { .SetAwaitPromise(true) .SetContextId(isolated_world_context_id_) .Build(), - kMainFrame, + js_flow_util::kMainFrame, base::BindOnce(&JsFlowExecutorImpl::OnNativeActionRequested, weak_ptr_factory_.GetWeakPtr())); } @@ -241,7 +211,7 @@ void JsFlowExecutorImpl::OnNativeActionRequested( .SetFunctionDeclaration(kArrayGetNthElement) .SetReturnByValue(true) .Build(), - kMainFrame, + js_flow_util::kMainFrame, base::BindOnce(&JsFlowExecutorImpl::OnNativeActionRequestActionRetrieved, weak_ptr_factory_.GetWeakPtr(), js_array_object_id)); } @@ -270,7 +240,7 @@ void JsFlowExecutorImpl::OnNativeActionRequestActionRetrieved( .SetArguments(std::move(arguments)) .SetFunctionDeclaration(kArrayGetNthElement) .Build(), - kMainFrame, + js_flow_util::kMainFrame, base::BindOnce( &JsFlowExecutorImpl::OnNativeActionRequestFulfillPromiseRetrieved, weak_ptr_factory_.GetWeakPtr(), @@ -352,7 +322,7 @@ void JsFlowExecutorImpl::OnNativeActionFinished( .SetArguments(std::move(arguments)) .SetFunctionDeclaration(kFulfillActionPromise) .Build(), - kMainFrame, + js_flow_util::kMainFrame, base::BindOnce(&JsFlowExecutorImpl::OnFlowResumed, weak_ptr_factory_.GetWeakPtr())); } @@ -375,7 +345,10 @@ void JsFlowExecutorImpl::OnFlowFinished( // values are allowed (see js_flow_util::ExtractFlowReturnValue for details). std::unique_ptr<base::Value> out_result_value; ClientStatus status = js_flow_util::ExtractFlowReturnValue( - reply_status, result.get(), out_result_value, kJsLineOffset, + reply_status, result.get(), out_result_value, + /* js_line_offsets= */ + {{js_flow_util::GetDevtoolsSourceUrl(UnexpectedErrorInfoProto::JS_FLOW), + kJsLineOffset}}, kNumStackEntriesToDrop); RunCallback(status, std::move(out_result_value)); @@ -387,7 +360,10 @@ void JsFlowExecutorImpl::RunCallback( if (!status.ok() && result_value) { VLOG(1) << "Flow failed with " << status << " and result: " << *result_value; + } else if (!status.ok()) { + VLOG(1) << "Flow failed with " << status; } + std::move(callback_).Run(status, std::move(result_value)); } diff --git a/chromium/components/autofill_assistant/browser/js_flow_executor_impl.h b/chromium/components/autofill_assistant/browser/js_flow_executor_impl.h index 4bbd621307c..91cbb1cf1ea 100644 --- a/chromium/components/autofill_assistant/browser/js_flow_executor_impl.h +++ b/chromium/components/autofill_assistant/browser/js_flow_executor_impl.h @@ -8,10 +8,12 @@ #include <memory> #include <string> #include "base/callback_forward.h" +#include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "base/values.h" #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/devtools/devtools_client.h" +#include "components/autofill_assistant/browser/js_flow_devtools_wrapper.h" #include "components/autofill_assistant/browser/js_flow_executor.h" namespace autofill_assistant { @@ -20,8 +22,9 @@ namespace autofill_assistant { // native actions to be performed by its delegate. class JsFlowExecutorImpl : public JsFlowExecutor { public: - // |delegate| must outlive the JsFlowExecutorImpl. - JsFlowExecutorImpl(content::WebContents* web_contents, Delegate* delegate); + // |delegate| and |devtools_wrapper| must outlive the JsFlowExecutorImpl. + JsFlowExecutorImpl(Delegate* delegate, + JsFlowDevtoolsWrapper* js_flow_devtools_wrapper); ~JsFlowExecutorImpl() override; JsFlowExecutorImpl(const JsFlowExecutorImpl&) = delete; JsFlowExecutorImpl& operator=(const JsFlowExecutorImpl&) = delete; @@ -74,12 +77,10 @@ class JsFlowExecutorImpl : public JsFlowExecutor { result_callback) override; private: - void InternalStart(); - void OnGetFrameTree(const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<page::GetFrameTreeResult> result); - void IsolatedWorldCreated( - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<page::CreateIsolatedWorldResult> result); + void InternalStart(const ClientStatus& status, + DevtoolsClient* devtools_client, + const int isolated_world_context_id); + void RefreshNativeActionPromise(); void OnNativeActionRequested(const DevtoolsClient::ReplyStatus& reply_status, std::unique_ptr<runtime::EvaluateResult> result); @@ -118,11 +119,12 @@ class JsFlowExecutorImpl : public JsFlowExecutor { return true; } - Delegate* const delegate_; - std::unique_ptr<DevtoolsClient> devtools_client_; - int isolated_world_context_id_ = -1; + const raw_ptr<Delegate> delegate_; + JsFlowDevtoolsWrapper* js_flow_devtools_wrapper_; // Only set during a flow. + DevtoolsClient* devtools_client_; + int isolated_world_context_id_ = -1; std::unique_ptr<std::string> js_flow_; base::OnceCallback<void(const ClientStatus&, std::unique_ptr<base::Value>)> callback_; diff --git a/chromium/components/autofill_assistant/browser/js_flow_executor_impl_browsertest.cc b/chromium/components/autofill_assistant/browser/js_flow_executor_impl_browsertest.cc index 7f97d15eff4..8bea9e9795a 100644 --- a/chromium/components/autofill_assistant/browser/js_flow_executor_impl_browsertest.cc +++ b/chromium/components/autofill_assistant/browser/js_flow_executor_impl_browsertest.cc @@ -9,7 +9,6 @@ #include <string> #include <type_traits> -#include "base/base64.h" #include "base/bind.h" #include "base/callback.h" #include "base/callback_forward.h" @@ -38,7 +37,6 @@ #include "net/http/http_status_code.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" -#include "third_party/abseil-cpp/absl/types/optional.h" namespace autofill_assistant { namespace { @@ -73,13 +71,15 @@ class MockJsFlowExecutorImplDelegate : public JsFlowExecutorImpl::Delegate { (override)); }; -class JsFlowExecutorImplTest : public BaseBrowserTest { +class JsFlowExecutorImplBrowserTest : public BaseBrowserTest { public: void SetUpOnMainThread() override { BaseBrowserTest::SetUpOnMainThread(); + js_flow_devtools_wrapper_ = + std::make_unique<JsFlowDevtoolsWrapper>(shell()->web_contents()); flow_executor_ = std::make_unique<JsFlowExecutorImpl>( - shell()->web_contents(), &mock_delegate_); + &mock_delegate_, js_flow_devtools_wrapper_.get()); } // Overload, ignore result value, just return the client status. @@ -92,8 +92,9 @@ class JsFlowExecutorImplTest : public BaseBrowserTest { std::unique_ptr<base::Value>& result_value) { ClientStatus status; base::RunLoop run_loop; + flow_executor_->Start( - js_flow, base::BindOnce(&JsFlowExecutorImplTest::OnFlowFinished, + js_flow, base::BindOnce(&JsFlowExecutorImplBrowserTest::OnFlowFinished, base::Unretained(this), run_loop.QuitClosure(), &status, std::ref(result_value))); run_loop.Run(); @@ -112,20 +113,23 @@ class JsFlowExecutorImplTest : public BaseBrowserTest { protected: NiceMock<MockJsFlowExecutorImplDelegate> mock_delegate_; + std::unique_ptr<JsFlowExecutorImpl> flow_executor_; + std::unique_ptr<JsFlowDevtoolsWrapper> js_flow_devtools_wrapper_; }; -IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, SmokeTest) { +IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplBrowserTest, SmokeTest) { EXPECT_THAT(RunTest(std::string()), Property(&ClientStatus::proto_status, ACTION_APPLIED)); } -IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, InvalidJs) { +IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplBrowserTest, InvalidJs) { EXPECT_THAT(RunTest("Not valid Javascript"), Property(&ClientStatus::proto_status, UNEXPECTED_JS_ERROR)); } -IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, RunNativeActionWithReturnValue) { +IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplBrowserTest, + RunNativeActionWithReturnValue) { std::unique_ptr<base::Value> native_return_value = std::make_unique<base::Value>(std::move(*base::JSONReader::Read( R"( @@ -185,7 +189,8 @@ IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, RunNativeActionWithReturnValue) { )")); } -IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, RunNativeActionAsBase64String) { +IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplBrowserTest, + RunNativeActionAsBase64String) { EXPECT_CALL(mock_delegate_, RunNativeAction) .WillOnce([&](int action_id, const std::string& action, auto callback) { EXPECT_EQ(12, action_id); @@ -194,7 +199,7 @@ IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, RunNativeActionAsBase64String) { }); std::unique_ptr<base::Value> result; - EXPECT_THAT(RunTest(R"( + ASSERT_THAT(RunTest(R"( let [status, value] = await runNativeAction(12, "dGVzdA==" /*test*/); return status; )", @@ -204,7 +209,7 @@ IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, RunNativeActionAsBase64String) { EXPECT_EQ(*result, base::Value(2)); } -IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, +IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplBrowserTest, RunNativeActionAsSerializedProto) { EXPECT_CALL(mock_delegate_, RunNativeAction) .WillOnce([&](int action_id, const std::string& action, auto callback) { @@ -216,7 +221,7 @@ IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, }); std::unique_ptr<base::Value> result; - EXPECT_THAT(RunTest(R"( + ASSERT_THAT(RunTest(R"( let [status, value] = await runNativeAction( 11, ["aa.msg", "my message"]); return status; @@ -226,7 +231,8 @@ IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, EXPECT_EQ(*result, base::Value(2)); } -IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, RunMultipleNativeActions) { +IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplBrowserTest, + RunMultipleNativeActions) { EXPECT_CALL(mock_delegate_, RunNativeAction) .WillOnce([&](int action_id, const std::string& action, auto callback) { EXPECT_EQ(1, action_id); @@ -243,7 +249,7 @@ IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, RunMultipleNativeActions) { // completed successfully, but the return value should hold // OTHER_ACTION_STATUS, i.e., 3. std::unique_ptr<base::Value> result; - EXPECT_THAT(RunTest(R"( + ASSERT_THAT(RunTest(R"( let [status, value] = await runNativeAction( 1, "dGVzdDE=" /*test1*/); if (status == 2) { // ACTION_APPLIED @@ -258,14 +264,14 @@ IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, RunMultipleNativeActions) { EXPECT_EQ(*result, base::Value(3)); } -IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, ReturnInteger) { +IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplBrowserTest, ReturnInteger) { std::unique_ptr<base::Value> result; ClientStatus status = RunTest("return 12345;", result); - EXPECT_EQ(status.proto_status(), ACTION_APPLIED); + ASSERT_EQ(status.proto_status(), ACTION_APPLIED); EXPECT_EQ(*result, base::Value(12345)); } -IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, ReturningStringFails) { +IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplBrowserTest, ReturningStringFails) { // Return value checking is more comprehensively tested in // js_flow_util::ContainsOnlyAllowedValues. This test is just to ensure that // that util is actually used for JS flow return values. @@ -275,7 +281,7 @@ IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, ReturningStringFails) { EXPECT_THAT(result, Eq(nullptr)); } -IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, ReturnDictionary) { +IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplBrowserTest, ReturnDictionary) { std::unique_ptr<base::Value> result; ClientStatus status = RunTest( R"( @@ -290,7 +296,7 @@ IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, ReturnDictionary) { }; )", result); - EXPECT_EQ(status.proto_status(), ACTION_APPLIED); + ASSERT_EQ(status.proto_status(), ACTION_APPLIED); EXPECT_EQ(*result, *base::JSONReader::Read(R"( { "keyA":12345, @@ -304,14 +310,15 @@ IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, ReturnDictionary) { )")); } -IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, ReturnNothing) { +IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplBrowserTest, ReturnNothing) { std::unique_ptr<base::Value> result; ClientStatus status = RunTest("", result); EXPECT_EQ(status.proto_status(), ACTION_APPLIED); EXPECT_THAT(result, Eq(nullptr)); } -IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, ReturnNonJsonObjectFails) { +IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplBrowserTest, + ReturnNonJsonObjectFails) { std::unique_ptr<base::Value> result; ClientStatus status = RunTest(R"( function test() { @@ -324,14 +331,14 @@ IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, ReturnNonJsonObjectFails) { EXPECT_THAT(result, Eq(nullptr)); } -IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, ReturnNull) { +IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplBrowserTest, ReturnNull) { std::unique_ptr<base::Value> result; ClientStatus status = RunTest("return null;", result); - EXPECT_EQ(status.proto_status(), ACTION_APPLIED); + ASSERT_EQ(status.proto_status(), ACTION_APPLIED); EXPECT_EQ(*result, *base::JSONReader::Read("null")); } -IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, ExceptionReporting) { +IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplBrowserTest, ExceptionReporting) { std::unique_ptr<base::Value> result; ClientStatus status = RunTest("notdefined;", result); EXPECT_EQ(status.proto_status(), UNEXPECTED_JS_ERROR); @@ -347,17 +354,18 @@ IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, ExceptionReporting) { ElementsAre(0)); } -IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, RunMultipleConsecutiveFlows) { +IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplBrowserTest, + RunMultipleConsecutiveFlows) { for (int i = 0; i < 10; ++i) { std::unique_ptr<base::Value> result; ClientStatus status = RunTest(base::StrCat({"return ", base::NumberToString(i)}), result); - EXPECT_EQ(status.proto_status(), ACTION_APPLIED); + ASSERT_EQ(status.proto_status(), ACTION_APPLIED); EXPECT_EQ(*result, base::Value(i)); } } -IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, +IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplBrowserTest, UnserializableRunNativeActionString) { std::unique_ptr<base::Value> result; EXPECT_CALL(mock_delegate_, RunNativeAction).Times(0); @@ -372,7 +380,7 @@ IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, EXPECT_EQ(status.proto_status(), INVALID_ACTION); } -IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, +IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplBrowserTest, UnserializableRunNativeActionId) { std::unique_ptr<base::Value> result; EXPECT_CALL(mock_delegate_, RunNativeAction).Times(0); @@ -387,7 +395,8 @@ IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, EXPECT_EQ(status.proto_status(), INVALID_ACTION); } -IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, StartWhileAlreadyRunningFails) { +IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplBrowserTest, + StartWhileAlreadyRunningFails) { EXPECT_CALL(mock_delegate_, RunNativeAction) .WillOnce(WithArg<2>([&](auto callback) { // Starting a second flow while the first one is running should fail. @@ -404,123 +413,31 @@ IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, StartWhileAlreadyRunningFails) { return status; )", result); - EXPECT_EQ(status.proto_status(), ACTION_APPLIED); + ASSERT_EQ(status.proto_status(), ACTION_APPLIED); EXPECT_EQ(*result, base::Value(2)); } -IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplTest, +IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplBrowserTest, EnvironmentIsPreservedBetweenRuns) { EXPECT_EQ(RunTest("globalFlowState.i = 5;").proto_status(), ACTION_APPLIED); std::unique_ptr<base::Value> result; - EXPECT_EQ(RunTest("return globalFlowState.i;", result).proto_status(), + ASSERT_EQ(RunTest("return globalFlowState.i;", result).proto_status(), ACTION_APPLIED); EXPECT_EQ(*result, base::Value(5)); } -class JsFlowExecutorImplScriptExecutorTest : public BaseBrowserTest { - public: - void SetUpOnMainThread() override { - BaseBrowserTest::SetUpOnMainThread(); +IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplBrowserTest, + JsFlowLibraryIsAvailable) { + js_flow_devtools_wrapper_->SetJsFlowLibrary("const status = 2;"); - web_controller_ = WebController::CreateForWebContents( - shell()->web_contents(), &user_data_, &log_info_, nullptr, - /*enable_full_stack_traces= */ true); - - fake_script_executor_delegate_.SetService(&mock_service_); - fake_script_executor_delegate_.SetWebController(web_controller_.get()); - fake_script_executor_delegate_.SetCurrentURL(GURL("http://example.com/")); - fake_script_executor_delegate_.SetWebContents(shell()->web_contents()); - - script_executor_ = std::make_unique<ScriptExecutor>( - /* script_path= */ "", - /* additional_context= */ std::make_unique<TriggerContext>(), - /* global_payload= */ "", - /* script_payload= */ "", - /* listener= */ nullptr, &ordered_interrupts_, - &fake_script_executor_delegate_, &fake_script_executor_ui_delegate_); - } - - protected: - void Run(const std::string& js_flow, - const ProcessedActionStatusProto& result) { - ActionsResponseProto actions_response; - actions_response.add_actions()->mutable_js_flow()->set_js_flow(js_flow); - /* actions_response.add_actions() */ - /* ->mutable_release_elements() */ - /* ->add_client_ids() */ - /* ->set_identifier("client_id"); */ - - EXPECT_CALL(mock_service_, GetActions) - .WillOnce(RunOnceCallback<5>(net::HTTP_OK, - actions_response.SerializeAsString(), - ServiceRequestSender::ResponseInfo{})); - - EXPECT_CALL(mock_service_, - GetNextActions(_, _, _, - ElementsAre(Property( - &ProcessedActionProto::status, result)), - _, _, _)) - .WillOnce(RunOnceCallback<6>(net::HTTP_OK, - ActionsResponseProto().SerializeAsString(), - ServiceRequestSender::ResponseInfo{})); - - base::RunLoop run_loop; - script_executor_->Run( - &user_data_, - base::BindOnce(&JsFlowExecutorImplScriptExecutorTest::OnFlowFinished, - base::Unretained(this), run_loop.QuitClosure())); - run_loop.Run(); - } - - void OnFlowFinished(base::OnceClosure done_callback, - const ScriptExecutor::Result& result) { - EXPECT_TRUE(result.success); - std::move(done_callback).Run(); - } - - std::vector<std::unique_ptr<Script>> ordered_interrupts_; - - ProcessedActionStatusDetailsProto log_info_; - std::unique_ptr<WebController> web_controller_; - - FakeScriptExecutorDelegate fake_script_executor_delegate_; - FakeScriptExecutorUiDelegate fake_script_executor_ui_delegate_; - UserData user_data_; - - NiceMock<MockService> mock_service_; - std::unique_ptr<ScriptExecutor> script_executor_; -}; - -IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplScriptExecutorTest, - WaitForDomSucceeds) { - WaitForDomProto wait_for_dom; - wait_for_dom.mutable_wait_condition() - ->mutable_match() - ->add_filters() - ->set_css_selector("#button"); - std::string wait_for_dom_base64; - base::Base64Encode(wait_for_dom.SerializeAsString(), &wait_for_dom_base64); - - Run(R"(const [status, value] = await runNativeAction(19, ')" + - wait_for_dom_base64 + R"('); - return {status};)", - ACTION_APPLIED); -} - -IN_PROC_BROWSER_TEST_F(JsFlowExecutorImplScriptExecutorTest, WaitForDomFails) { - WaitForDomProto wait_for_dom; - wait_for_dom.mutable_wait_condition() - ->mutable_match() - ->add_filters() - ->set_css_selector("#not-found"); - std::string wait_for_dom_base64; - base::Base64Encode(wait_for_dom.SerializeAsString(), &wait_for_dom_base64); - - Run(R"(const [status, value] = await runNativeAction(19, ')" + - wait_for_dom_base64 + R"('); - return {status};)", - ELEMENT_RESOLUTION_FAILED); + std::unique_ptr<base::Value> result; + ASSERT_THAT(RunTest(R"( + return status; + )", + result), + Property(&ClientStatus::proto_status, ACTION_APPLIED)); + EXPECT_EQ(*result, base::Value(2)); } } // namespace diff --git a/chromium/components/autofill_assistant/browser/js_flow_util.cc b/chromium/components/autofill_assistant/browser/js_flow_util.cc index ee0a5ecc6b2..c0ae4b70f4b 100644 --- a/chromium/components/autofill_assistant/browser/js_flow_util.cc +++ b/chromium/components/autofill_assistant/browser/js_flow_util.cc @@ -4,6 +4,7 @@ #include "components/autofill_assistant/browser/js_flow_util.h" #include "base/base64.h" +#include "base/logging.h" #include "base/strings/strcat.h" #include "components/autofill_assistant/browser/model.pb.h" #include "components/autofill_assistant/browser/service.pb.h" @@ -28,6 +29,13 @@ const char kActionSpecificResultKey[] = "actionSpecificResult"; // by runNativeAction(). // DO NOT CHANGE const char kNavigationStartedKey[] = "navigationStarted"; +// The key for the autofill error info in the result object returned by +// runNativeAction(). +// DO NOT CHANGE +const char kAutofillErrorInfo[] = "autofillErrorInfo"; +// By appending //# sourceUrl=some_name.js to a js snippet the snippet can be +// identified in devtools by url = some_name.js (for example in exceptions). +constexpr char kSourceUrlCommentPrefix[] = "\n//# sourceURL="; // Returns true for remote object types that flows are allowed to return. This // is mostly used to filter types like FUNCTION which would otherwise slip @@ -95,11 +103,11 @@ ClientStatus ExtractFlowReturnValue( const DevtoolsClient::ReplyStatus& devtools_reply_status, runtime::EvaluateResult* devtools_result, std::unique_ptr<base::Value>& out_flow_result, - int js_line_offset, + const JsLineOffsets& js_line_offsets, int num_stack_entries_to_drop) { ClientStatus status = CheckJavaScriptResult( devtools_reply_status, devtools_result, __FILE__, __LINE__, - js_line_offset, num_stack_entries_to_drop); + js_line_offsets, num_stack_entries_to_drop); if (!status.ok()) { return status; } @@ -146,6 +154,7 @@ ClientStatus ExtractJsFlowActionReturnValue( } if (!value.is_dict()) { + VLOG(1) << "JS flow did not return a dictionary."; return ClientStatusWithSourceLocation(INVALID_ACTION, __FILE__, __LINE__); } @@ -153,6 +162,7 @@ ClientStatus ExtractJsFlowActionReturnValue( absl::optional<int> flow_status = dict->FindInt(kStatusKey); const base::Value* flow_return_value = dict->Find(kResultKey); if (!flow_status || !ProcessedActionStatusProto_IsValid(*flow_status)) { + VLOG(1) << "JS flow did not return a valid ActionStatus in " << kStatusKey; return ClientStatusWithSourceLocation(INVALID_ACTION, __FILE__, __LINE__); } @@ -163,6 +173,12 @@ ClientStatus ExtractJsFlowActionReturnValue( return ClientStatus(static_cast<ProcessedActionStatusProto>(*flow_status)); } +std::string SerializeToBase64(const google::protobuf::MessageLite* proto) { + std::string serialized_result_base64; + base::Base64Encode(proto->SerializeAsString(), &serialized_result_base64); + return serialized_result_base64; +} + namespace { absl::optional<std::string> SerializeActionResult( @@ -205,13 +221,14 @@ absl::optional<std::string> SerializeActionResult( case ProcessedActionProto::kSaveSubmittedPasswordResult: proto = &processed_action.save_submitted_password_result(); break; + case ProcessedActionProto::kExternalActionResult: + proto = &processed_action.external_action_result(); + break; case ProcessedActionProto::RESULT_DATA_NOT_SET: return absl::nullopt; } - std::string serialized_result_base64; - base::Base64Encode(proto->SerializeAsString(), &serialized_result_base64); - return serialized_result_base64; + return SerializeToBase64(proto); } } // namespace @@ -228,8 +245,40 @@ std::unique_ptr<base::Value> NativeActionResultToResultValue( result_value.Set(kActionSpecificResultKey, *serialized_result); } + if (processed_action.status_details().has_autofill_error_info()) { + result_value.Set( + kAutofillErrorInfo, + SerializeToBase64( + &processed_action.status_details().autofill_error_info())); + } + return std::make_unique<base::Value>(std::move(result_value)); } +std::string GetDevtoolsSourceUrl( + UnexpectedErrorInfoProto::JsExceptionLocation js_exception_location) { + return UnexpectedErrorInfoProto::JsExceptionLocation_Name( + js_exception_location); +} + +UnexpectedErrorInfoProto::JsExceptionLocation GetExceptionLocation( + const std::string& devtools_source_url) { + UnexpectedErrorInfoProto::JsExceptionLocation js_exception_location; + return UnexpectedErrorInfoProto::JsExceptionLocation_Parse( + devtools_source_url, &js_exception_location) + ? js_exception_location + : UnexpectedErrorInfoProto::UNKNOWN; +} + +std::string GetDevtoolsSourceUrlCommentToAppend( + UnexpectedErrorInfoProto::JsExceptionLocation js_exception_location) { + if (js_exception_location == UnexpectedErrorInfoProto::UNKNOWN) { + return ""; + } + + return base::StrCat( + {kSourceUrlCommentPrefix, GetDevtoolsSourceUrl(js_exception_location)}); +} + } // namespace js_flow_util } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/js_flow_util.h b/chromium/components/autofill_assistant/browser/js_flow_util.h index 6f4ff790e60..0ee6e8e35e4 100644 --- a/chromium/components/autofill_assistant/browser/js_flow_util.h +++ b/chromium/components/autofill_assistant/browser/js_flow_util.h @@ -11,9 +11,11 @@ #include "base/values.h" #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/devtools/devtools_client.h" +#include "components/autofill_assistant/browser/web/web_controller_util.h" -namespace autofill_assistant { -namespace js_flow_util { +namespace autofill_assistant::js_flow_util { + +constexpr char kMainFrame[] = ""; // Returns true if |value| contains only allowed value types, which are INT, // BOOL, DOUBLE, and NONE. Dictionaries and lists are allowed, so long as they @@ -38,7 +40,7 @@ ClientStatus ExtractFlowReturnValue( const DevtoolsClient::ReplyStatus& devtools_reply_status, runtime::EvaluateResult* devtools_result, std::unique_ptr<base::Value>& out_flow_result, - int js_line_offset, + const JsLineOffsets& js_line_offsets, int num_stack_entries_to_drop); // Extracts client status and optionally return value from |value|. Expects @@ -67,7 +69,26 @@ ClientStatus ExtractJsFlowActionReturnValue( std::unique_ptr<base::Value> NativeActionResultToResultValue( const ProcessedActionProto& processed_action); -} // namespace js_flow_util -} // namespace autofill_assistant +// Serializes the proto as base64. +std::string SerializeToBase64(const google::protobuf::MessageLite* proto); + +// Returns the devtools source url comment to append to js code before +// evaluating by devtools. +// +// For example by appending //# sourceUrl=some_name.js to a js snippet the +// snippet can be identified in devtools by url = some_name.js (for example in +// exceptions). +std::string GetDevtoolsSourceUrlCommentToAppend( + UnexpectedErrorInfoProto::JsExceptionLocation js_exception_location); + +// Returns the devtools source url for the js exception location. +std::string GetDevtoolsSourceUrl( + UnexpectedErrorInfoProto::JsExceptionLocation js_exception_location); + +// Returns the js exception location for the devtools source url. +UnexpectedErrorInfoProto::JsExceptionLocation GetExceptionLocation( + const std::string& devtools_source_url); + +} // namespace autofill_assistant::js_flow_util #endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_JS_FLOW_UTIL_H_ diff --git a/chromium/components/autofill_assistant/browser/js_flow_util_unittest.cc b/chromium/components/autofill_assistant/browser/js_flow_util_unittest.cc index 2ab42294065..42f2787e445 100644 --- a/chromium/components/autofill_assistant/browser/js_flow_util_unittest.cc +++ b/chromium/components/autofill_assistant/browser/js_flow_util_unittest.cc @@ -145,7 +145,7 @@ TEST(JsFlowUtilTest, ExtractFlowReturnValue) { std::unique_ptr<base::Value> out_flow_value; ClientStatus status = ExtractFlowReturnValue( devtools_status, devtools_result.get(), out_flow_value, - /* js_line_offset= */ 0, /* num_stack_entries_to_drop= */ 0); + /* js_line_offsets= */ {}, /* num_stack_entries_to_drop= */ 0); EXPECT_TRUE(status.ok()); EXPECT_EQ(*out_flow_value, base::Value(12345)); } @@ -250,6 +250,23 @@ TEST(JsFlowUtilTest, NativeActionResultToResultValueHasSerializedActionResult) { "actionSpecificResult": ")" + wait_for_dom_result_base64 + "\"}"))); } +TEST(JsFlowUtilTest, NativeActionResultToResultValueHasAutofillErrorInfo) { + ProcessedActionProto processed_action; + AutofillErrorInfoProto* autofill_error_info = + processed_action.mutable_status_details()->mutable_autofill_error_info(); + autofill_error_info->set_client_memory_address_key_names("key_names"); + + std::string autofill_error_info_base64; + base::Base64Encode(autofill_error_info->SerializeAsString(), + &autofill_error_info_base64); + + EXPECT_THAT( + NativeActionResultToResultValue(processed_action), Pointee(IsJson(R"( + { + "navigationStarted": false, + "autofillErrorInfo": ")" + autofill_error_info_base64 + "\"}"))); +} + TEST(JsFlowUtilTest, NativeActionResultToResultValueHasEmptyActionResult) { ProcessedActionProto processed_action; @@ -259,6 +276,23 @@ TEST(JsFlowUtilTest, NativeActionResultToResultValueHasEmptyActionResult) { )"))); } +TEST(JsFlowUtilTest, ExceptionLocationToDevtoolsUrlMapping) { + const std::string url = + GetDevtoolsSourceUrl(UnexpectedErrorInfoProto::JS_FLOW); + EXPECT_THAT(GetExceptionLocation(url), UnexpectedErrorInfoProto::JS_FLOW); +} + +TEST(JsFlowUtilTest, UnknownUrl) { + EXPECT_THAT(GetExceptionLocation("SOME_STRING"), + UnexpectedErrorInfoProto::UNKNOWN); +} + +TEST(JsFlowUtilTest, GetDevtoolsSourceUrlCommentToAppend) { + EXPECT_THAT(GetDevtoolsSourceUrlCommentToAppend( + UnexpectedErrorInfoProto::JS_FLOW_LIBRARY), + "\n//# sourceURL=JS_FLOW_LIBRARY"); +} + } // namespace } // namespace js_flow_util } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/metrics.cc b/chromium/components/autofill_assistant/browser/metrics.cc index e2c0b673b2a..1a5fb172703 100644 --- a/chromium/components/autofill_assistant/browser/metrics.cc +++ b/chromium/components/autofill_assistant/browser/metrics.cc @@ -11,7 +11,6 @@ #include "components/autofill_assistant/browser/features.h" #include "components/autofill_assistant/browser/intent_strings.h" #include "components/autofill_assistant/browser/startup_util.h" -#include "components/ukm/content/source_url_recorder.h" #include "services/metrics/public/cpp/ukm_builders.h" namespace autofill_assistant { @@ -48,6 +47,10 @@ const char kDependenciesInvalidated[] = "Android.AutofillAssistant.DependenciesInvalidated"; const char kOnboardingFetcherResultStatus[] = "Android.AutofillAssistant.OnboardingFetcher.ResultStatus"; +const char kServiceRequestSuccessRetryCount[] = + "Android.AutofillAssistant.ServiceRequestSender.SuccessRetryCount"; +const char kServiceRequestFailureRetryCount[] = + "Android.AutofillAssistant.ServiceRequestSender.FailureRetryCount"; static bool DROPOUT_RECORDED = false; std::string GetSuffixForIntent(const std::string& intent) { @@ -110,7 +113,7 @@ Metrics::AutofillAssistantIntent ExtractIntentFromScriptParameters( return Metrics::AutofillAssistantIntent::UNDEFINED_INTENT; } return enum_value_iter->second; -} // namespace +} // Extracts the enum value corresponding to the caller specified in // |script_parameters|. @@ -562,6 +565,15 @@ void Metrics::RecordOnboardingFetcherResult( base::UmaHistogramEnumeration(kOnboardingFetcherResultStatus, status); } +// static +void Metrics::RecordServiceRequestRetryCount(int count, bool success) { + DCHECK_GE(count, 0); + base::UmaHistogramExactLinear(success ? kServiceRequestSuccessRetryCount + : kServiceRequestFailureRetryCount, + /* sample= */ count, + /* exclusive_max= */ 11); +} + std::ostream& operator<<(std::ostream& out, const Metrics::DropOutReason& reason) { #ifdef NDEBUG diff --git a/chromium/components/autofill_assistant/browser/metrics.h b/chromium/components/autofill_assistant/browser/metrics.h index 768236c2edf..994f0931bba 100644 --- a/chromium/components/autofill_assistant/browser/metrics.h +++ b/chromium/components/autofill_assistant/browser/metrics.h @@ -469,10 +469,16 @@ class Metrics { SEARCH_ADS = 4, SHOPPING_PROPERTY = 5, EMULATOR = 6, + // The run was started from within Chrome (e.g., URL heuristic match or + // password change launched from the settings page). IN_CHROME = 7, + // The run was triggered by the Direction Action API in Chrome. DIRECT_ACTION = 8, + // The run was started by Google Password Manager (passwords.google.com or + // credential_manager in gmscore module on Android). + GOOGLE_PASSWORD_MANAGER = 9, - kMaxValue = DIRECT_ACTION + kMaxValue = GOOGLE_PASSWORD_MANAGER }; // Used for logging the SOURCE script parameter. @@ -787,6 +793,7 @@ class Metrics { static void RecordOnboardingFetcherResult( OnboardingFetcherResultStatus status); static void RecordCupRpcVerificationEvent(CupRpcVerificationEvent event); + static void RecordServiceRequestRetryCount(int count, bool success); // Intended for debugging: writes string representation of |reason| to // |out|. diff --git a/chromium/components/autofill_assistant/browser/mock_client.h b/chromium/components/autofill_assistant/browser/mock_client.h index c0cf456f64e..04c316d0259 100644 --- a/chromium/components/autofill_assistant/browser/mock_client.h +++ b/chromium/components/autofill_assistant/browser/mock_client.h @@ -38,7 +38,7 @@ class MockClient : public Client { MOCK_CONST_METHOD0(IsAccessibilityEnabled, bool()); MOCK_CONST_METHOD0(IsSpokenFeedbackAccessibilityServiceEnabled, bool()); MOCK_CONST_METHOD0(GetEmailAddressForAccessTokenAccount, std::string()); - MOCK_CONST_METHOD0(GetChromeSignedInEmailAddress, std::string()); + MOCK_CONST_METHOD0(GetSignedInEmail, std::string()); MOCK_CONST_METHOD0(GetWebContents, content::WebContents*()); MOCK_CONST_METHOD0(GetPersonalDataManager, autofill::PersonalDataManager*()); MOCK_CONST_METHOD0(GetWebsiteLoginManager, WebsiteLoginManager*()); @@ -55,6 +55,9 @@ class MockClient : public Client { MOCK_METHOD1(FetchPaymentsClientToken, void(base::OnceCallback<void(const std::string&)>)); MOCK_METHOD0(GetScriptExecutorUiDelegate, ScriptExecutorUiDelegate*()); + MOCK_CONST_METHOD0(MustUseBackendData, bool()); + MOCK_CONST_METHOD1(GetAnnotateDomModelVersion, + void(base::OnceCallback<void(absl::optional<int64_t>)>)); private: std::unique_ptr<MockPersonalDataManager> mock_personal_data_manager_; diff --git a/chromium/components/autofill_assistant/browser/mock_client_context.h b/chromium/components/autofill_assistant/browser/mock_client_context.h index cc9a593c55b..43debe39aa6 100644 --- a/chromium/components/autofill_assistant/browser/mock_client_context.h +++ b/chromium/components/autofill_assistant/browser/mock_client_context.h @@ -18,8 +18,19 @@ class MockClientContext : public ClientContext { MockClientContext(); ~MockClientContext() override; - MOCK_METHOD1(Update, void(const TriggerContext& trigger_context)); - MOCK_CONST_METHOD0(AsProto, ClientContextProto()); + MOCK_METHOD(void, + Update, + (const TriggerContext& trigger_context), + (override)); + MOCK_METHOD(void, + UpdateAnnotateDomModelContext, + (int64_t model_version), + (override)); + MOCK_METHOD(void, + UpdateJsFlowLibraryLoaded, + (bool js_flow_library_loaded), + (override)); + MOCK_METHOD(ClientContextProto, AsProto, (), (const override)); }; } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/mock_common_dependencies.cc b/chromium/components/autofill_assistant/browser/mock_common_dependencies.cc new file mode 100644 index 00000000000..2ac0434bfa8 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/mock_common_dependencies.cc @@ -0,0 +1,13 @@ +// Copyright 2022 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_assistant/browser/mock_common_dependencies.h" + +namespace autofill_assistant { + +MockCommonDependencies::MockCommonDependencies() = default; + +MockCommonDependencies::~MockCommonDependencies() = default; + +} // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/mock_common_dependencies.h b/chromium/components/autofill_assistant/browser/mock_common_dependencies.h new file mode 100644 index 00000000000..a500b44942b --- /dev/null +++ b/chromium/components/autofill_assistant/browser/mock_common_dependencies.h @@ -0,0 +1,55 @@ +// Copyright 2022 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_ASSISTANT_BROWSER_MOCK_COMMON_DEPENDENCIES_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_MOCK_COMMON_DEPENDENCIES_H_ + +#include "components/autofill_assistant/browser/assistant_field_trial_util.h" +#include "components/autofill_assistant/browser/common_dependencies.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace autofill_assistant { + +class MockCommonDependencies : public CommonDependencies { + public: + MockCommonDependencies(); + ~MockCommonDependencies() override; + + MOCK_METHOD(std::unique_ptr<AssistantFieldTrialUtil>, + CreateFieldTrialUtil, + (), + (const override)); + MOCK_METHOD(std::string, GetLocale, (), (const override)); + MOCK_METHOD(std::string, GetCountryCode, (), (const override)); + MOCK_METHOD(autofill::PersonalDataManager*, + GetPersonalDataManager, + (content::BrowserContext*), + (const override)); + MOCK_METHOD(password_manager::PasswordManagerClient*, + GetPasswordManagerClient, + (content::WebContents*), + (const override)); + MOCK_METHOD(std::string, + GetSignedInEmail, + (content::BrowserContext*), + (const override)); + MOCK_METHOD(bool, + IsSupervisedUser, + (content::BrowserContext*), + (const override)); + MOCK_METHOD(AnnotateDomModelService*, + GetOrCreateAnnotateDomModelService, + (content::BrowserContext*), + (const override)); + MOCK_METHOD(bool, IsWebLayer, (), (const override)); + MOCK_METHOD(signin::IdentityManager*, + GetIdentityManager, + (content::BrowserContext*), + (const override)); + MOCK_METHOD(version_info::Channel, GetChannel, (), (const override)); +}; + +} // namespace autofill_assistant + +#endif // #COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_MOCK_COMMON_DEPENDENCIES_H_ diff --git a/chromium/components/autofill_assistant/browser/mock_controller_observer.h b/chromium/components/autofill_assistant/browser/mock_controller_observer.h index f67bd170fc0..3327e695f33 100644 --- a/chromium/components/autofill_assistant/browser/mock_controller_observer.h +++ b/chromium/components/autofill_assistant/browser/mock_controller_observer.h @@ -46,7 +46,6 @@ class MockControllerObserver : public ControllerObserver { MOCK_METHOD0(OnStop, void()); MOCK_METHOD0(OnResetState, void()); MOCK_METHOD1(OnUiShownChanged, void(bool shown)); - MOCK_METHOD1(OnShutdown, void(Metrics::DropOutReason reason)); }; } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/mock_script_executor_delegate.cc b/chromium/components/autofill_assistant/browser/mock_script_executor_delegate.cc index 2bc56bb7b01..c9450bf9cb2 100644 --- a/chromium/components/autofill_assistant/browser/mock_script_executor_delegate.cc +++ b/chromium/components/autofill_assistant/browser/mock_script_executor_delegate.cc @@ -11,6 +11,7 @@ using ::testing::ReturnRef; MockScriptExecutorDelegate::MockScriptExecutorDelegate() { ON_CALL(*this, GetSettings).WillByDefault(ReturnRef(client_settings_)); ON_CALL(*this, GetLogInfo).WillByDefault(ReturnRef(log_info_)); + ON_CALL(*this, GetCurrentURL).WillByDefault(ReturnRef(default_url_)); } MockScriptExecutorDelegate::~MockScriptExecutorDelegate() = default; diff --git a/chromium/components/autofill_assistant/browser/mock_script_executor_delegate.h b/chromium/components/autofill_assistant/browser/mock_script_executor_delegate.h index 89ac4818ccd..46c75c3ab7b 100644 --- a/chromium/components/autofill_assistant/browser/mock_script_executor_delegate.h +++ b/chromium/components/autofill_assistant/browser/mock_script_executor_delegate.h @@ -49,6 +49,12 @@ class MockScriptExecutorDelegate : public ScriptExecutorDelegate { (), (override)); MOCK_METHOD(content::WebContents*, GetWebContents, (), (override)); + MOCK_METHOD(void, + SetJsFlowLibrary, + (const std::string& js_flow_library), + (override)); + MOCK_METHOD(JsFlowDevtoolsWrapper*, GetJsFlowDevtoolsWrapper, (), (override)); + MOCK_METHOD(std::string, GetEmailAddressForAccessTokenAccount, (), @@ -95,10 +101,12 @@ class MockScriptExecutorDelegate : public ScriptExecutorDelegate { MOCK_METHOD(void, SetBrowseModeInvisible, (bool invisible), (override)); MOCK_METHOD(ProcessedActionStatusDetailsProto&, GetLogInfo, (), (override)); MOCK_METHOD(bool, ShouldShowWarning, (), (override)); + MOCK_METHOD(bool, MustUseBackendData, (), (const override)); private: ClientSettings client_settings_; ProcessedActionStatusDetailsProto log_info_; + const GURL default_url_ = GURL("https://example.com/"); }; } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/mock_ui_controller_observer.h b/chromium/components/autofill_assistant/browser/mock_ui_controller_observer.h index e1f59d5135c..cbdffdbdb71 100644 --- a/chromium/components/autofill_assistant/browser/mock_ui_controller_observer.h +++ b/chromium/components/autofill_assistant/browser/mock_ui_controller_observer.h @@ -30,6 +30,8 @@ class MockUiControllerObserver : public UiControllerObserver { void(const std::vector<UserAction>& user_actions)); MOCK_METHOD1(OnCollectUserDataOptionsChanged, void(const CollectUserDataOptions* options)); + MOCK_METHOD2(OnCollectUserDataUiStateChanged, + void(bool loading, UserDataEventField event_field)); MOCK_METHOD1(OnDetailsChanged, void(const std::vector<Details>& details)); MOCK_METHOD1(OnInfoBoxChanged, void(const InfoBox* info_box)); MOCK_METHOD1(OnProgressChanged, void(int progress)); diff --git a/chromium/components/autofill_assistant/browser/parse_jspb.cc b/chromium/components/autofill_assistant/browser/parse_jspb.cc index ac16d9a7a9b..efc2808b1b7 100644 --- a/chromium/components/autofill_assistant/browser/parse_jspb.cc +++ b/chromium/components/autofill_assistant/browser/parse_jspb.cc @@ -85,7 +85,7 @@ bool AppendFieldValue(const std::string& jspb_id_prefix, // Encode these as floats (in a fixed32) WriteTag(field_tag, WIRETYPE_FIXED32, out); out->WriteLittleEndian32( - bit_cast<uint32_t>(static_cast<float>(value.GetDouble()))); + base::bit_cast<uint32_t>(static_cast<float>(value.GetDouble()))); break; case base::Value::Type::STRING: { diff --git a/chromium/components/autofill_assistant/browser/platform_dependencies.cc b/chromium/components/autofill_assistant/browser/platform_dependencies.cc new file mode 100644 index 00000000000..c88724a9bdc --- /dev/null +++ b/chromium/components/autofill_assistant/browser/platform_dependencies.cc @@ -0,0 +1,11 @@ +// Copyright 2022 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_assistant/browser/platform_dependencies.h" + +namespace autofill_assistant { + +PlatformDependencies::~PlatformDependencies() = default; + +} // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/platform_dependencies.h b/chromium/components/autofill_assistant/browser/platform_dependencies.h new file mode 100644 index 00000000000..3415cf31b6d --- /dev/null +++ b/chromium/components/autofill_assistant/browser/platform_dependencies.h @@ -0,0 +1,25 @@ +// Copyright 2022 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_ASSISTANT_BROWSER_PLATFORM_DEPENDENCIES_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_PLATFORM_DEPENDENCIES_H_ + +#include "content/public/browser/web_contents.h" + +namespace autofill_assistant { + +// Interface for platform delegates that provide dependencies to the starter. +// +// This interface contains all methods which require a platform-specific +// implementation. +class PlatformDependencies { + public: + virtual ~PlatformDependencies(); + + virtual bool IsCustomTab(const content::WebContents& web_contents) const = 0; +}; + +} // namespace autofill_assistant + +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_PLATFORM_DEPENDENCIES_H_ diff --git a/chromium/components/autofill_assistant/browser/protocol_utils.cc b/chromium/components/autofill_assistant/browser/protocol_utils.cc index e608627469a..30fc7f0d825 100644 --- a/chromium/components/autofill_assistant/browser/protocol_utils.cc +++ b/chromium/components/autofill_assistant/browser/protocol_utils.cc @@ -22,6 +22,7 @@ #include "components/autofill_assistant/browser/actions/edit_password_action.h" #include "components/autofill_assistant/browser/actions/execute_js_action.h" #include "components/autofill_assistant/browser/actions/expect_navigation_action.h" +#include "components/autofill_assistant/browser/actions/external_action.h" #include "components/autofill_assistant/browser/actions/generate_password_for_form_field_action.h" #include "components/autofill_assistant/browser/actions/get_element_status_action.h" #include "components/autofill_assistant/browser/actions/js_flow_action.h" @@ -30,6 +31,7 @@ #include "components/autofill_assistant/browser/actions/popup_message_action.h" #include "components/autofill_assistant/browser/actions/presave_generated_password_action.h" #include "components/autofill_assistant/browser/actions/prompt_action.h" +#include "components/autofill_assistant/browser/actions/register_password_reset_request_action.h" #include "components/autofill_assistant/browser/actions/release_elements_action.h" #include "components/autofill_assistant/browser/actions/reset_pending_credentials_action.h" #include "components/autofill_assistant/browser/actions/save_generated_password_action.h" @@ -206,6 +208,7 @@ std::string ProtocolUtils::CreateNextScriptActionsRequest( ScriptActionRequestProto request_proto; request_proto.set_global_payload(global_payload); request_proto.set_script_payload(script_payload); + NextScriptActionsRequestProto* next_request = request_proto.mutable_next_request(); for (const auto& processed_action : processed_actions) { @@ -453,6 +456,19 @@ std::unique_ptr<Action> ProtocolUtils::CreateAction(ActionDelegate* delegate, return std::make_unique<ExecuteJsAction>(delegate, action); case ActionProto::ActionInfoCase::kJsFlow: return std::make_unique<JsFlowAction>(delegate, action); + case ActionProto::ActionInfoCase::kExternalAction: + return std::make_unique<ExternalAction>(delegate, action); + case ActionProto::ActionInfoCase::kRegisterPasswordResetRequest: + return std::make_unique<RegisterPasswordResetRequestAction>(delegate, + action); + case ActionProto::ActionInfoCase::kSetNativeValue: + return PerformOnSingleElementAction::WithClientId( + delegate, action, action.set_native_value().client_id(), + base::BindOnce( + &action_delegate_util::PerformWithTextValue, delegate, + action.set_native_value().value(), + base::BindOnce(&WebController::SetNativeValue, + delegate->GetWebController()->GetWeakPtr()))); case ActionProto::ActionInfoCase::ACTION_INFO_NOT_SET: { VLOG(1) << "Encountered action with ACTION_INFO_NOT_SET"; return std::make_unique<UnsupportedAction>(delegate, action); @@ -717,6 +733,19 @@ absl::optional<ActionProto> ProtocolUtils::ParseFromString( success = ParseActionFromString(action_id, bytes, error_message, proto.mutable_js_flow()); break; + case ActionProto::ActionInfoCase::kExternalAction: + success = ParseActionFromString(action_id, bytes, error_message, + proto.mutable_external_action()); + break; + case ActionProto::ActionInfoCase::kSetNativeValue: + success = ParseActionFromString(action_id, bytes, error_message, + proto.mutable_set_native_value()); + break; + case ActionProto::ActionInfoCase::kRegisterPasswordResetRequest: + success = ParseActionFromString( + action_id, bytes, error_message, + proto.mutable_register_password_reset_request()); + break; case ActionProto::ActionInfoCase::ACTION_INFO_NOT_SET: // This is an "unknown action", handled as such in CreateAction. return proto; @@ -740,7 +769,8 @@ bool ProtocolUtils::ParseActions(ActionDelegate* delegate, std::string* return_script_payload, std::vector<std::unique_ptr<Action>>* actions, std::vector<std::unique_ptr<Script>>* scripts, - bool* should_update_scripts) { + bool* should_update_scripts, + std::string* js_flow_library) { DCHECK(actions); DCHECK(scripts); @@ -759,6 +789,9 @@ bool ProtocolUtils::ParseActions(ActionDelegate* delegate, if (return_script_payload) { *return_script_payload = response_proto.script_payload(); } + if (js_flow_library) { + *js_flow_library = std::move(*response_proto.mutable_js_flow_library()); + } for (const auto& action : response_proto.actions()) { std::unique_ptr<Action> client_action = CreateAction(delegate, action); @@ -942,15 +975,23 @@ std::string ProtocolUtils::CreateGetUserDataRequest( bool request_email, bool request_phone, bool request_shipping, + const std::vector<std::string>& preexisting_address_ids, bool request_payment_methods, const std::vector<std::string>& supported_card_networks, + const std::vector<std::string>& preexisting_payment_instrument_ids, const std::string& client_token) { GetUserDataRequestProto request_proto; request_proto.set_run_id(run_id); request_proto.set_request_name(request_name); request_proto.set_request_email(request_email); request_proto.set_request_phone(request_phone); - request_proto.set_request_addresses(request_shipping); + + if (request_shipping) { + auto* address_request = request_proto.mutable_request_shipping_addresses(); + for (const std::string& id : preexisting_address_ids) { + address_request->add_preexisting_ids(id); + } + } if (request_payment_methods) { auto* payment_methods_request = @@ -960,6 +1001,9 @@ std::string ProtocolUtils::CreateGetUserDataRequest( payment_methods_request->add_supported_card_networks( supported_card_network); } + for (const std::string& id : preexisting_payment_instrument_ids) { + payment_methods_request->add_preexisting_ids(id); + } } std::string serialized_request_proto; diff --git a/chromium/components/autofill_assistant/browser/protocol_utils.h b/chromium/components/autofill_assistant/browser/protocol_utils.h index f3f885fc9f6..52e872ec4b9 100644 --- a/chromium/components/autofill_assistant/browser/protocol_utils.h +++ b/chromium/components/autofill_assistant/browser/protocol_utils.h @@ -81,8 +81,10 @@ class ProtocolUtils { bool request_email, bool request_phone, bool request_shipping, + const std::vector<std::string>& preexisting_address_ids, bool request_payment_methods, const std::vector<std::string>& supported_card_networks, + const std::vector<std::string>& preexisting_payment_instrument_ids, const std::string& client_token); // Create an action from the |action|. @@ -114,7 +116,8 @@ class ProtocolUtils { std::string* return_script_payload, std::vector<std::unique_ptr<Action>>* actions, std::vector<std::unique_ptr<Script>>* scripts, - bool* should_update_scripts); + bool* should_update_scripts, + std::string* js_flow_library); // Parses a single serialized ActionProto. Returns nullptr in the case of // parsing errors. diff --git a/chromium/components/autofill_assistant/browser/protocol_utils_unittest.cc b/chromium/components/autofill_assistant/browser/protocol_utils_unittest.cc index 7ce0b5da7e0..a4a30ff6143 100644 --- a/chromium/components/autofill_assistant/browser/protocol_utils_unittest.cc +++ b/chromium/components/autofill_assistant/browser/protocol_utils_unittest.cc @@ -230,11 +230,12 @@ TEST_F(ProtocolUtilsTest, ParseActionsParseError) { bool unused; std::vector<std::unique_ptr<Action>> unused_actions; std::vector<std::unique_ptr<Script>> unused_scripts; + std::string unused_js_flow_library; EXPECT_FALSE(ProtocolUtils::ParseActions( /* delegate= */ nullptr, /* response= */ "invalid", /* run_id= */ nullptr, - /* global_payload= */ nullptr, - /* script_payload= */ nullptr, &unused_actions, &unused_scripts, - /* should_update_scripts= */ &unused)); + /* return_global_payload= */ nullptr, + /* return_script_payload= */ nullptr, &unused_actions, &unused_scripts, + /* should_update_scripts= */ &unused, &unused_js_flow_library)); } TEST_F(ProtocolUtilsTest, ParseActionParseError) { @@ -258,10 +259,11 @@ TEST_F(ProtocolUtilsTest, ParseActionsValid) { bool should_update_scripts = true; std::vector<std::unique_ptr<Action>> actions; std::vector<std::unique_ptr<Script>> scripts; + std::string unused_js_flow_library; EXPECT_TRUE(ProtocolUtils::ParseActions( nullptr, proto_str, &run_id, &global_payload, &script_payload, &actions, - &scripts, &should_update_scripts)); + &scripts, &should_update_scripts, &unused_js_flow_library)); EXPECT_EQ(1u, run_id); EXPECT_EQ("global_payload", global_payload); EXPECT_EQ("script_payload", script_payload); @@ -288,11 +290,12 @@ TEST_F(ProtocolUtilsTest, ParseActionsEmptyUpdateScriptList) { bool should_update_scripts = false; std::vector<std::unique_ptr<Script>> scripts; std::vector<std::unique_ptr<Action>> unused_actions; + std::string unused_js_flow_library; EXPECT_TRUE(ProtocolUtils::ParseActions( nullptr, proto_str, /* run_id= */ nullptr, /* global_payload= */ nullptr, /* script_payload */ nullptr, &unused_actions, &scripts, - &should_update_scripts)); + &should_update_scripts, &unused_js_flow_library)); EXPECT_TRUE(should_update_scripts); EXPECT_TRUE(scripts.empty()); } @@ -313,11 +316,13 @@ TEST_F(ProtocolUtilsTest, ParseActionsUpdateScriptListFullFeatured) { bool should_update_scripts = false; std::vector<std::unique_ptr<Script>> scripts; std::vector<std::unique_ptr<Action>> unused_actions; + std::string unused_js_flow_library; EXPECT_TRUE(ProtocolUtils::ParseActions( - nullptr, proto_str, /* run_id= */ nullptr, /* global_payload= */ nullptr, - /* script_payload= */ nullptr, &unused_actions, &scripts, - &should_update_scripts)); + nullptr, proto_str, /* run_id= */ nullptr, + /* return_global_payload= */ nullptr, + /* return_script_payload= */ nullptr, &unused_actions, &scripts, + &should_update_scripts, &unused_js_flow_library)); EXPECT_TRUE(should_update_scripts); EXPECT_THAT(scripts, SizeIs(1)); EXPECT_THAT("a", Eq(scripts[0]->handle.path)); @@ -604,28 +609,39 @@ TEST_F(ProtocolUtilsTest, CreateGetUserDataRequest) { GetUserDataRequestProto request; EXPECT_TRUE(request.ParseFromString(ProtocolUtils::CreateGetUserDataRequest( /* run_id= */ 1, /* request_name= */ true, /* request_email= */ true, - /* request_phone= */ true, /* request_shipping= */ true, + /* request_phone= */ true, /* request_shipping= */ false, + /* preexisting_address_ids= */ std::vector<std::string>(), /* request_payment_methods= */ false, /* supported_card_networks= */ std::vector<std::string>(), + /* preexisting_payment_instrument_ids= */ std::vector<std::string>(), /* client_token= */ std::string()))); EXPECT_EQ(request.run_id(), 1u); EXPECT_TRUE(request.request_name()); EXPECT_TRUE(request.request_email()); EXPECT_TRUE(request.request_phone()); - EXPECT_TRUE(request.request_addresses()); + EXPECT_FALSE(request.has_request_shipping_addresses()); EXPECT_FALSE(request.has_request_payment_methods()); EXPECT_TRUE(request.ParseFromString(ProtocolUtils::CreateGetUserDataRequest( /* run_id= */ 1, /* request_name= */ true, /* request_email= */ true, /* request_phone= */ true, /* request_shipping= */ true, + /* preexisting_address_ids= */ + std::vector<std::string>({"address-1", "address-2"}), /* request_payment_methods= */ true, /* supported_card_networks= */ std::vector<std::string>({"VISA", "MASTERCARD"}), + /* preexisting_payment_instrument_ids= */ + std::vector<std::string>({"instrument-1", "instrument-2"}), /* client_token= */ "token"))); + EXPECT_TRUE(request.has_request_shipping_addresses()); + EXPECT_THAT(request.request_shipping_addresses().preexisting_ids(), + ElementsAre("address-1", "address-2")); EXPECT_TRUE(request.has_request_payment_methods()); EXPECT_EQ(request.request_payment_methods().client_token(), "token"); EXPECT_THAT(request.request_payment_methods().supported_card_networks(), ElementsAre("VISA", "MASTERCARD")); + EXPECT_THAT(request.request_payment_methods().preexisting_ids(), + ElementsAre("instrument-1", "instrument-2")); } TEST_F(ProtocolUtilsTest, ComputeNetworkStats) { diff --git a/chromium/components/autofill_assistant/browser/public/BUILD.gn b/chromium/components/autofill_assistant/browser/public/BUILD.gn index 7eed6462e1a..d19f754de67 100644 --- a/chromium/components/autofill_assistant/browser/public/BUILD.gn +++ b/chromium/components/autofill_assistant/browser/public/BUILD.gn @@ -2,11 +2,18 @@ # Use of this source code is governed by a BSD - style license that can be # found in the LICENSE file. +import("//third_party/protobuf/proto_library.gni") + +if (is_android) { + import("//build/config/android/rules.gni") +} + static_library("public") { sources = [ "autofill_assistant.cc", "autofill_assistant.h", "autofill_assistant_factory.h", + "external_action_delegate.h", "external_script_controller.h", "runtime_manager.cc", "runtime_manager.h", @@ -17,17 +24,33 @@ static_library("public") { ] deps = [ + ":proto", "//base", "//components/version_info:channel", "//content/public/browser", ] } +proto_library("proto") { + proto_in_dir = "//components/autofill_assistant/" + sources = [ "external_action.proto" ] +} + +# Java protos are only used for testing. +if (is_android) { + proto_java_library("proto_java") { + proto_path = "//components/autofill_assistant/browser/public" + sources = [ "$proto_path/external_action.proto" ] + } +} + static_library("unit_test_support") { testonly = true sources = [ "mock_autofill_assistant.cc", "mock_autofill_assistant.h", + "mock_external_script_controller.cc", + "mock_external_script_controller.h", "mock_runtime_manager.cc", "mock_runtime_manager.h", ] diff --git a/chromium/components/autofill_assistant/browser/public/autofill_assistant.h b/chromium/components/autofill_assistant/browser/public/autofill_assistant.h index 7651c53fa99..2fd5cb10467 100644 --- a/chromium/components/autofill_assistant/browser/public/autofill_assistant.h +++ b/chromium/components/autofill_assistant/browser/public/autofill_assistant.h @@ -10,8 +10,13 @@ #include "base/callback_forward.h" #include "base/containers/flat_map.h" +#include "components/autofill_assistant/browser/public/external_action_delegate.h" #include "components/autofill_assistant/browser/public/external_script_controller.h" +namespace content { +class WebContents; +} + namespace autofill_assistant { // Abstract interface for exported services. @@ -53,8 +58,14 @@ class AutofillAssistant { // |ExternalScriptController::StartScript|. // The returned |ExternalScriptController| instance has to survive for the // duration of the execution of the script. + // |action_extension_delegate| can be nullptr, but in that case the script + // execution will fail if it reaches an external action. If present, + // |action_extension_delegate| instance must outlive the + // |ExternalScriptController|. virtual std::unique_ptr<ExternalScriptController> - CreateExternalScriptController(content::WebContents* web_contents) = 0; + CreateExternalScriptController( + content::WebContents* web_contents, + ExternalActionDelegate* action_extension_delegate) = 0; protected: AutofillAssistant() = default; diff --git a/chromium/components/autofill_assistant/browser/public/autofill_assistant_factory.h b/chromium/components/autofill_assistant/browser/public/autofill_assistant_factory.h index b96790f9db1..1257facc9fe 100644 --- a/chromium/components/autofill_assistant/browser/public/autofill_assistant_factory.h +++ b/chromium/components/autofill_assistant/browser/public/autofill_assistant_factory.h @@ -5,19 +5,26 @@ #ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_PUBLIC_AUTOFILL_ASSISTANT_FACTORY_H_ #define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_PUBLIC_AUTOFILL_ASSISTANT_FACTORY_H_ +#include <memory> + #include "components/autofill_assistant/browser/public/autofill_assistant.h" #include "components/version_info/channel.h" #include "content/public/browser/browser_context.h" namespace autofill_assistant { -// Creates an instance of |AutofillAssistant|. + +class CommonDependencies; + +// Factory class for creating |AutofillAssistant|. class AutofillAssistantFactory { public: + // TODO(b/201964911) The |AutofillAssistant::CreateExternalScriptController| + // method ignores the |channel|, |country_code| and |locale| passed here and + // instead fetches them directly. Make the treatment between + // |ExternalScriptController| and |GetCapabilitiesByHashPrefix| consistent. static std::unique_ptr<AutofillAssistant> CreateForBrowserContext( content::BrowserContext* browser_context, - version_info::Channel channel, - const std::string& country_code, - const std::string& locale); + std::unique_ptr<CommonDependencies> dependencies); }; } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/public/external_action.proto b/chromium/components/autofill_assistant/browser/public/external_action.proto new file mode 100644 index 00000000000..595f21825fa --- /dev/null +++ b/chromium/components/autofill_assistant/browser/public/external_action.proto @@ -0,0 +1,52 @@ +// Copyright 2022 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. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; +option java_package = "org.chromium.chrome.browser.autofill_assistant.external.proto"; +option java_multiple_files = true; + +package autofill_assistant.external; + +// Defines an action to be executed by the ExternalActionDelegate. +message Action { + // Integrator-specific information. This was forwarded without modifications. + optional ActionInfo info = 1; +} + +// Extended by the integrator. +message ActionInfo { + extensions 100 to max; +} + +// The result of the execution of |Action| +message Result { + // Integrator-specific information. This will be forwarded to the backend + // without modification. + optional ResultInfo result_info = 1; + + // Whether the action was successful. + optional bool success = 2; +} + +// Extended by the integrator. +message ResultInfo { + extensions 100 to max; +} + +// An update on the status of the DOM conditions for the current action. +message ElementConditionsUpdate { + // The list of conditions whose status changed since the last update. The + // first update for each action will contain all the conditions which are + // specified in the equivalent action in the backend. + message ConditionResult { + // The identifier of this condition, matches the one specified in the + // equivalent action in the backend. + optional int32 id = 1; + // Whether the condition was satisfied. + optional bool satisfied = 2; + } + repeated ConditionResult results = 1; +} diff --git a/chromium/components/autofill_assistant/browser/public/external_action_delegate.h b/chromium/components/autofill_assistant/browser/public/external_action_delegate.h new file mode 100644 index 00000000000..427ef0fbc82 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/public/external_action_delegate.h @@ -0,0 +1,43 @@ +// Copyright 2022 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_ASSISTANT_BROWSER_PUBLIC_EXTERNAL_ACTION_DELEGATE_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_PUBLIC_EXTERNAL_ACTION_DELEGATE_H_ + +#include "base/callback.h" +#include "components/autofill_assistant/browser/public/external_action.pb.h" + +namespace autofill_assistant { + +// Allows to handle external actions happening during the execution of a +// script. +class ExternalActionDelegate { + public: + // Called to notify a change in the DOM. + using DomUpdateCallback = + base::RepeatingCallback<void(const external::ElementConditionsUpdate&)>; + + virtual ~ExternalActionDelegate() = default; + // Called when the script reaches an external action. + // The |start_dom_checks_callback| can optionally be called to start the DOM + // checks. This will allow interrupts to trigger (if the action itself allows + // them). Calling |end_action_callback| will end the external action and + // resume the execution of the rest of the script. + virtual void OnActionRequested( + const external::Action& action_info, + base::OnceCallback<void(DomUpdateCallback)> start_dom_checks_callback, + base::OnceCallback<void(const external::Result&)> + end_action_callback) = 0; + + // Called before starting the execution of an interrupt. + virtual void OnInterruptStarted() = 0; + + // Called after finishing to execute an interrupt, before resuming the + // execution of the main script. + virtual void OnInterruptFinished() = 0; +}; + +} // namespace autofill_assistant + +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_PUBLIC_EXTERNAL_ACTION_DELEGATE_H_ diff --git a/chromium/components/autofill_assistant/browser/public/external_script_controller.h b/chromium/components/autofill_assistant/browser/public/external_script_controller.h index 540197b1d7c..44dad58a0aa 100644 --- a/chromium/components/autofill_assistant/browser/public/external_script_controller.h +++ b/chromium/components/autofill_assistant/browser/public/external_script_controller.h @@ -5,10 +5,10 @@ #ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_PUBLIC_EXTERNAL_SCRIPT_CONTROLLER_H_ #define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_PUBLIC_EXTERNAL_SCRIPT_CONTROLLER_H_ -#include "base/memory/weak_ptr.h" -#include "components/autofill_assistant/browser/public/runtime_observer.h" -#include "components/autofill_assistant/browser/public/ui_state.h" -#include "content/public/browser/web_contents.h" +#include <string> + +#include "base/callback_forward.h" +#include "base/containers/flat_map.h" namespace autofill_assistant { diff --git a/chromium/components/autofill_assistant/browser/public/mock_autofill_assistant.h b/chromium/components/autofill_assistant/browser/public/mock_autofill_assistant.h index 2afa789ec3a..e24ad5791c0 100644 --- a/chromium/components/autofill_assistant/browser/public/mock_autofill_assistant.h +++ b/chromium/components/autofill_assistant/browser/public/mock_autofill_assistant.h @@ -25,7 +25,8 @@ class MockAutofillAssistant : public AutofillAssistant { (override)); MOCK_METHOD(std::unique_ptr<ExternalScriptController>, CreateExternalScriptController, - (content::WebContents * web_contents), + (content::WebContents * web_contents, + ExternalActionDelegate* action_extension_delegate), (override)); }; diff --git a/chromium/components/autofill_assistant/browser/public/mock_external_script_controller.cc b/chromium/components/autofill_assistant/browser/public/mock_external_script_controller.cc new file mode 100644 index 00000000000..b4f88b39434 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/public/mock_external_script_controller.cc @@ -0,0 +1,13 @@ +// Copyright 2022 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_assistant/browser/public/mock_external_script_controller.h" + +namespace autofill_assistant { + +MockExternalScriptController::MockExternalScriptController() = default; + +MockExternalScriptController::~MockExternalScriptController() = default; + +} // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/public/mock_external_script_controller.h b/chromium/components/autofill_assistant/browser/public/mock_external_script_controller.h new file mode 100644 index 00000000000..00e168088a8 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/public/mock_external_script_controller.h @@ -0,0 +1,28 @@ +// Copyright 2022 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_ASSISTANT_BROWSER_PUBLIC_MOCK_EXTERNAL_SCRIPT_CONTROLLER_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_PUBLIC_MOCK_EXTERNAL_SCRIPT_CONTROLLER_H_ + +#include "base/callback_helpers.h" +#include "components/autofill_assistant/browser/public/external_script_controller.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace autofill_assistant { + +class MockExternalScriptController : public ExternalScriptController { + public: + MockExternalScriptController(); + ~MockExternalScriptController() override; + + MOCK_METHOD(void, + StartScript, + ((const base::flat_map<std::string, std::string>&), + (base::OnceCallback<void(ScriptResult)>)), + (override)); +}; + +} // namespace autofill_assistant + +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_PUBLIC_MOCK_EXTERNAL_SCRIPT_CONTROLLER_H_ diff --git a/chromium/components/autofill_assistant/browser/script_executor.cc b/chromium/components/autofill_assistant/browser/script_executor.cc index 1f368e90b6a..f35b891e2cd 100644 --- a/chromium/components/autofill_assistant/browser/script_executor.cc +++ b/chromium/components/autofill_assistant/browser/script_executor.cc @@ -29,6 +29,7 @@ #include "components/autofill_assistant/browser/wait_for_dom_operation.h" #include "components/autofill_assistant/browser/web/element_action_util.h" #include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/element_store.h" #include "components/autofill_assistant/browser/web/web_controller.h" #include "components/strings/grit/components_strings.h" @@ -340,6 +341,8 @@ void ScriptExecutor::CollectUserData( weak_ptr_factory_.GetWeakPtr(), std::move(collect_user_data_options->terms_link_callback)); ui_delegate_->SetCollectUserDataOptions(collect_user_data_options); + ui_delegate_->SetCollectUserDataUiState(/* loading= */ false, + UserDataEventField::NONE); delegate_->EnterState(AutofillAssistantState::PROMPT); } @@ -453,11 +456,13 @@ void ScriptExecutor::Prompt( } } -void ScriptExecutor::CleanUpAfterPrompt() { +void ScriptExecutor::CleanUpAfterPrompt(bool consume_touchable_area) { ui_delegate_->SetUserActions(nullptr); - // Mark touchable_elements_ as consumed, so that it won't affect the next - // prompt or the end of the script. - touchable_element_area_.reset(); + if (consume_touchable_area) { + // Mark touchable_elements_ as consumed, so that it won't affect the next + // prompt or the end of the script. + touchable_element_area_.reset(); + } delegate_->ClearTouchableElementArea(); ui_delegate_->SetExpandSheetForPromptAction(true); @@ -624,6 +629,10 @@ content::WebContents* ScriptExecutor::GetWebContents() const { return delegate_->GetWebContents(); } +JsFlowDevtoolsWrapper* ScriptExecutor::GetJsFlowDevtoolsWrapper() const { + return delegate_->GetJsFlowDevtoolsWrapper(); +} + ElementStore* ScriptExecutor::GetElementStore() const { return element_store_.get(); } @@ -824,6 +833,7 @@ void ScriptExecutor::OnGetActions( roundtrip_duration.InMilliseconds()); bool success = http_status == net::HTTP_OK && ProcessNextActionResponse(response, response_info); + if (should_stop_script_) { // The last action forced the script to stop. Sending the result of the // action is considered best effort in this situation. Report a successful @@ -865,14 +875,19 @@ bool ScriptExecutor::ProcessNextActionResponse( actions_.clear(); bool should_update_scripts = false; + std::string js_flow_library; std::vector<std::unique_ptr<Script>> scripts; bool parse_result = ProtocolUtils::ParseActions( this, response, &run_id_, &last_global_payload_, &last_script_payload_, - &actions_, &scripts, &should_update_scripts); + &actions_, &scripts, &should_update_scripts, &js_flow_library); if (!parse_result) { return false; } + if (!js_flow_library.empty()) { + delegate_->SetJsFlowLibrary(js_flow_library); + } + roundtrip_network_stats_ = ProtocolUtils::ComputeNetworkStats(response, response_info, actions_); ReportPayloadsToListener(); @@ -974,12 +989,24 @@ void ScriptExecutor::GetNextActions() { weak_ptr_factory_.GetWeakPtr(), get_next_actions_start)); } +void ScriptExecutor::MaybeSetPreviousAction( + const ProcessedActionProto& processed_action) { + const auto action_info_case = processed_action.action().action_info_case(); + + // JS flows are themselves a way of executing a script. + if (action_info_case == ActionProto::kJsFlow) { + return; + } + + previous_action_type_ = action_info_case; +} + void ScriptExecutor::OnProcessedAction( base::TimeTicks start_time, std::unique_ptr<ProcessedActionProto> processed_action_proto) { DCHECK(current_action_); base::TimeDelta run_time = base::TimeTicks::Now() - start_time; - previous_action_type_ = processed_action_proto->action().action_info_case(); + MaybeSetPreviousAction(*processed_action_proto); processed_actions_.emplace_back(*processed_action_proto); #ifdef NDEBUG @@ -1075,13 +1102,16 @@ ProcessedActionStatusDetailsProto& ScriptExecutor::GetLogInfo() { } void ScriptExecutor::RequestUserData( + UserDataEventField event_field, const CollectUserDataOptions& options, base::OnceCallback<void(bool, const GetUserDataResponseProto&)> callback) { auto* service = delegate_->GetService(); DCHECK(service); + delegate_->EnterState(AutofillAssistantState::RUNNING); + ui_delegate_->SetCollectUserDataUiState(/* loading= */ true, event_field); service->GetUserData( - options, run_id_, + options, run_id_, user_data_, base::BindOnce(&ScriptExecutor::OnRequestUserData, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } @@ -1101,4 +1131,49 @@ void ScriptExecutor::OnRequestUserData( std::move(callback).Run(success, response_proto); } +bool ScriptExecutor::SupportsExternalActions() { + return ui_delegate_->SupportsExternalActions(); +} + +void ScriptExecutor::RequestExternalAction( + const ExternalActionProto& external_action, + base::OnceCallback<void(ExternalActionDelegate::DomUpdateCallback)> + start_dom_checks_callback, + base::OnceCallback<void(const external::Result& result)> + end_action_callback) { + bool prompt = external_action.allow_interrupt() || + external_action.show_touchable_area(); + if (prompt && delegate_->EnterState(AutofillAssistantState::PROMPT)) { + if (external_action.show_touchable_area() && touchable_element_area_) { + delegate_->SetTouchableElementArea(*touchable_element_area_); + + // The touchable element and overlays are cleared by calling + // ScriptExecutor::CleanUpAfterPrompt + } + } + external::Action action; + *action.mutable_info() = external_action.info(); + ui_delegate_->ExecuteExternalAction( + action, std::move(start_dom_checks_callback), + base::BindOnce(&ScriptExecutor::OnExternalActionFinished, + weak_ptr_factory_.GetWeakPtr(), external_action, prompt, + std::move(end_action_callback))); +} + +void ScriptExecutor::OnExternalActionFinished( + const ExternalActionProto& external_action, + const bool prompt, + base::OnceCallback<void(const external::Result& result)> + end_action_callback, + const external::Result& result) { + if (prompt) { + CleanUpAfterPrompt(external_action.show_touchable_area()); + } + std::move(end_action_callback).Run(result); +} + +bool ScriptExecutor::MustUseBackendData() const { + return delegate_->MustUseBackendData(); +} + } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/script_executor.h b/chromium/components/autofill_assistant/browser/script_executor.h index 51bd880cf11..8c09cc63c86 100644 --- a/chromium/components/autofill_assistant/browser/script_executor.h +++ b/chromium/components/autofill_assistant/browser/script_executor.h @@ -38,6 +38,7 @@ #include "services/metrics/public/cpp/ukm_recorder.h" namespace autofill_assistant { +class ElementFinderResult; class ElementStore; class UserModel; class WaitForDomOperation; @@ -173,7 +174,7 @@ class ScriptExecutor : public ActionDelegate, base::OnceCallback<void()> end_on_navigation_callback, bool browse_mode, bool browse_mode_invisible) override; - void CleanUpAfterPrompt() override; + void CleanUpAfterPrompt(bool consume_touchable_area = true) override; void SetBrowseDomainsAllowlist(std::vector<std::string> domains) override; void RetrieveElementFormAndFieldData( const Selector& selector, @@ -209,6 +210,7 @@ class ScriptExecutor : public ActionDelegate, password_manager::PasswordChangeSuccessTracker* GetPasswordChangeSuccessTracker() const override; content::WebContents* GetWebContents() const override; + JsFlowDevtoolsWrapper* GetJsFlowDevtoolsWrapper() const override; ElementStore* GetElementStore() const override; WebController* GetWebController() const override; std::string GetEmailAddressForAccessTokenAccount() const override; @@ -261,9 +263,20 @@ class ScriptExecutor : public ActionDelegate, base::WeakPtr<ActionDelegate> GetWeakPtr() const override; ProcessedActionStatusDetailsProto& GetLogInfo() override; void RequestUserData( + UserDataEventField event_field, const CollectUserDataOptions& options, base::OnceCallback<void(bool, const GetUserDataResponseProto&)> callback) override; + bool SupportsExternalActions() override; + void RequestExternalAction( + const ExternalActionProto& external_action, + base::OnceCallback<void(ExternalActionDelegate::DomUpdateCallback)> + start_dom_checks_callback, + base::OnceCallback<void(const external::Result& result)> + end_action_callback) override; + bool MustUseBackendData() const override; + void MaybeSetPreviousAction( + const ProcessedActionProto& processed_action) override; private: // TODO(b/220079189): remove this friend declaration. @@ -335,6 +348,11 @@ class ScriptExecutor : public ActionDelegate, int http_status, const std::string& response, const ServiceRequestSender::ResponseInfo& response_info); + void OnExternalActionFinished( + const ExternalActionProto& external_action, + const bool prompt, + base::OnceCallback<void(const external::Result& result)> callback, + const external::Result& result); // Maybe shows the message specified in a callout, depending on the current // state and client settings. diff --git a/chromium/components/autofill_assistant/browser/script_executor_browsertest.cc b/chromium/components/autofill_assistant/browser/script_executor_browsertest.cc new file mode 100644 index 00000000000..d0033b3193f --- /dev/null +++ b/chromium/components/autofill_assistant/browser/script_executor_browsertest.cc @@ -0,0 +1,481 @@ +// Copyright 2022 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 <string> + +#include "base/base64.h" +#include "base/callback.h" +#include "base/run_loop.h" +#include "base/test/gmock_callback_support.h" +#include "base/test/mock_callback.h" +#include "components/autofill_assistant/browser/base_browsertest.h" +#include "components/autofill_assistant/browser/client_status.h" +#include "components/autofill_assistant/browser/fake_script_executor_delegate.h" +#include "components/autofill_assistant/browser/fake_script_executor_ui_delegate.h" +#include "components/autofill_assistant/browser/js_flow_util.h" +#include "components/autofill_assistant/browser/model.pb.h" +#include "components/autofill_assistant/browser/script.h" +#include "components/autofill_assistant/browser/script_executor.h" +#include "components/autofill_assistant/browser/service.pb.h" +#include "components/autofill_assistant/browser/service/mock_service.h" +#include "components/autofill_assistant/browser/web/web_controller.h" +#include "content/public/test/browser_test.h" +#include "content/shell/browser/shell.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace autofill_assistant { +namespace { + +using ::base::test::RunOnceCallback; +using ::testing::_; +using ::testing::AllOf; +using ::testing::DoAll; +using ::testing::ElementsAre; +using ::testing::Eq; +using ::testing::Field; +using ::testing::Matcher; +using ::testing::Ne; +using ::testing::NiceMock; +using ::testing::Pair; +using ::testing::Pointee; +using ::testing::Property; +using ::testing::SizeIs; +using ::testing::StrictMock; +using ::testing::WithArg; +using ::testing::WithArgs; + +class ScriptExecutorBrowserTest : public BaseBrowserTest { + public: + void SetUpOnMainThread() override { + BaseBrowserTest::SetUpOnMainThread(); + + web_controller_ = WebController::CreateForWebContents( + shell()->web_contents(), &user_data_, &log_info_, nullptr, + /*enable_full_stack_traces= */ true); + + fake_script_executor_delegate_.SetService(&mock_service_); + fake_script_executor_delegate_.SetWebController(web_controller_.get()); + fake_script_executor_delegate_.SetCurrentURL(GURL("http://example.com/")); + fake_script_executor_delegate_.SetWebContents(shell()->web_contents()); + } + + protected: + void Run(const ActionsResponseProto& actions_response) { + EXPECT_CALL(mock_service_, GetActions) + .WillOnce(RunOnceCallback<5>(net::HTTP_OK, + actions_response.SerializeAsString(), + ServiceRequestSender::ResponseInfo{})); + + ON_CALL(mock_service_, GetNextActions) + .WillByDefault(RunOnceCallback<6>( + net::HTTP_OK, ActionsResponseProto().SerializeAsString(), + ServiceRequestSender::ResponseInfo{})); + + base::RunLoop run_loop; + + script_executor_ = std::make_unique<ScriptExecutor>( + /* script_path= */ "", + /* additional_context= */ std::make_unique<TriggerContext>(), + /* global_payload= */ "", + /* script_payload= */ "", + /* listener= */ nullptr, &ordered_interrupts_, + &fake_script_executor_delegate_, &fake_script_executor_ui_delegate_); + + script_executor_->Run( + &user_data_, executor_callback_.Get().Then(run_loop.QuitClosure())); + run_loop.Run(); + } + + void RunJsFlow(const std::string& js_flow, + const ProcessedActionStatusProto& result) { + EXPECT_CALL(executor_callback_, + Run(Field(&ScriptExecutor::Result::success, true))); + + EXPECT_CALL(mock_service_, GetNextActions) + .WillOnce(DoAll( + WithArgs<3>([&result](const std::vector<ProcessedActionProto>& + processed_actions) { + EXPECT_THAT( + processed_actions, + ElementsAre(Property(&ProcessedActionProto::status, result))); + }), + RunOnceCallback<6>(net::HTTP_OK, + ActionsResponseProto().SerializeAsString(), + ServiceRequestSender::ResponseInfo{}))); + + ActionsResponseProto actions_response; + actions_response.add_actions()->mutable_js_flow()->set_js_flow(js_flow); + Run(actions_response); + } + + std::unique_ptr<ScriptExecutor> script_executor_; + + std::vector<std::unique_ptr<Script>> ordered_interrupts_; + + ProcessedActionStatusDetailsProto log_info_; + std::unique_ptr<WebController> web_controller_; + + FakeScriptExecutorDelegate fake_script_executor_delegate_; + FakeScriptExecutorUiDelegate fake_script_executor_ui_delegate_; + UserData user_data_; + + NiceMock<MockService> mock_service_; + + StrictMock<base::MockCallback<ScriptExecutor::RunScriptCallback>> + executor_callback_; +}; + +std::string CreateRunNativeActionCall( + const google::protobuf::MessageLite* proto, + const ActionProto::ActionInfoCase action_info_case) { + return base::StrCat({"const [status, value] = await runNativeAction(", + base::NumberToString(action_info_case), ", '", + js_flow_util::SerializeToBase64(proto), "');"}); +} + +std::string CreateRunNativeActionCallReturn( + const google::protobuf::MessageLite* proto, + const ActionProto::ActionInfoCase action_info_case) { + return base::StrCat({"{", CreateRunNativeActionCall(proto, action_info_case), + "return {status}}"}); +} + +std::string CreateRunNativeActionCallReturnIfError( + const google::protobuf::MessageLite* proto, + const ActionProto::ActionInfoCase action_info_case) { + return base::StrCat({"{", CreateRunNativeActionCall(proto, action_info_case), + "if(status != 2) return {status}}"}); +} + +IN_PROC_BROWSER_TEST_F(ScriptExecutorBrowserTest, ShutdownAfter_Stop) { + ActionsResponseProto actions_response; + actions_response.add_actions()->mutable_stop(); + + EXPECT_CALL(executor_callback_, + Run(AllOf(Field(&ScriptExecutor::Result::success, true), + Field(&ScriptExecutor::Result::at_end, + ScriptExecutor::SHUTDOWN)))); + + Run(actions_response); +} + +IN_PROC_BROWSER_TEST_F(ScriptExecutorBrowserTest, + ShutdownGracefullyAfter_Tell_Stop) { + ActionsResponseProto actions_response; + actions_response.add_actions()->mutable_tell()->set_message("message"); + actions_response.add_actions()->mutable_stop(); + + EXPECT_CALL(executor_callback_, + Run(AllOf(Field(&ScriptExecutor::Result::success, true), + Field(&ScriptExecutor::Result::at_end, + ScriptExecutor::SHUTDOWN_GRACEFULLY)))); + + Run(actions_response); +} + +IN_PROC_BROWSER_TEST_F(ScriptExecutorBrowserTest, + ShutdownGracefullyAfter_Tell_EmptyJsFlow_Stop) { + ActionsResponseProto actions_response; + actions_response.add_actions()->mutable_tell()->set_message("message"); + actions_response.add_actions()->mutable_js_flow(); + actions_response.add_actions()->mutable_stop(); + + EXPECT_CALL(executor_callback_, + Run(AllOf(Field(&ScriptExecutor::Result::success, true), + Field(&ScriptExecutor::Result::at_end, + ScriptExecutor::SHUTDOWN_GRACEFULLY)))); + + Run(actions_response); +} + +IN_PROC_BROWSER_TEST_F(ScriptExecutorBrowserTest, + ShutdownGracefullyAfter_JsFlowTell_Stop) { + TellProto tell; + tell.set_message("message"); + + ActionsResponseProto actions_response; + actions_response.add_actions()->mutable_js_flow()->set_js_flow( + CreateRunNativeActionCallReturn(&tell, ActionProto::kTell)); + actions_response.add_actions()->mutable_stop(); + + EXPECT_CALL(executor_callback_, + Run(AllOf(Field(&ScriptExecutor::Result::success, true), + Field(&ScriptExecutor::Result::at_end, + ScriptExecutor::SHUTDOWN_GRACEFULLY)))); + + Run(actions_response); +} + +IN_PROC_BROWSER_TEST_F(ScriptExecutorBrowserTest, + ShutdownGracefullyAfter_JsFlowTellAndStop) { + TellProto tell; + tell.set_message("message"); + StopProto stop; + + ActionsResponseProto actions_response; + actions_response.add_actions()->mutable_js_flow()->set_js_flow( + CreateRunNativeActionCallReturnIfError(&tell, ActionProto::kTell) + + CreateRunNativeActionCallReturn(&stop, ActionProto::kStop)); + + EXPECT_CALL(executor_callback_, + Run(AllOf(Field(&ScriptExecutor::Result::success, true), + Field(&ScriptExecutor::Result::at_end, + ScriptExecutor::SHUTDOWN_GRACEFULLY)))); + + Run(actions_response); +} + +IN_PROC_BROWSER_TEST_F(ScriptExecutorBrowserTest, + ShutdownGracefullyAfter_JsFlowTell_JsFlowStop) { + TellProto tell; + tell.set_message("message"); + StopProto stop; + + ActionsResponseProto actions_response; + actions_response.add_actions()->mutable_js_flow()->set_js_flow( + CreateRunNativeActionCallReturnIfError(&tell, ActionProto::kTell)); + actions_response.add_actions()->mutable_js_flow()->set_js_flow( + CreateRunNativeActionCallReturn(&stop, ActionProto::kStop)); + + EXPECT_CALL(executor_callback_, + Run(AllOf(Field(&ScriptExecutor::Result::success, true), + Field(&ScriptExecutor::Result::at_end, + ScriptExecutor::SHUTDOWN_GRACEFULLY)))); + + Run(actions_response); +} + +IN_PROC_BROWSER_TEST_F(ScriptExecutorBrowserTest, + ShutdownGracefullyAfter_Tell_JsFlowStop) { + StopProto stop; + + ActionsResponseProto actions_response; + actions_response.add_actions()->mutable_tell()->set_message("message"); + actions_response.add_actions()->mutable_js_flow()->set_js_flow( + CreateRunNativeActionCallReturn(&stop, ActionProto::kStop)); + + EXPECT_CALL(executor_callback_, + Run(AllOf(Field(&ScriptExecutor::Result::success, true), + Field(&ScriptExecutor::Result::at_end, + ScriptExecutor::SHUTDOWN_GRACEFULLY)))); + + Run(actions_response); +} + +IN_PROC_BROWSER_TEST_F(ScriptExecutorBrowserTest, JsLibraryIsUsed) { + ActionsResponseProto actions_response; + actions_response.set_js_flow_library("const status = 2;"); + actions_response.add_actions()->mutable_js_flow()->set_js_flow( + "return {status};"); + + EXPECT_CALL(mock_service_, + GetNextActions(_, _, _, + ElementsAre(Property(&ProcessedActionProto::status, + ACTION_APPLIED)), + _, _, _)) + .WillOnce(RunOnceCallback<6>(net::HTTP_OK, + ActionsResponseProto().SerializeAsString(), + ServiceRequestSender::ResponseInfo{})); + + EXPECT_CALL(executor_callback_, + Run(Field(&ScriptExecutor::Result::success, true))); + + Run(actions_response); +} + +IN_PROC_BROWSER_TEST_F(ScriptExecutorBrowserTest, JsLibraryIsReused) { + ActionsResponseProto actions_response; + actions_response.set_js_flow_library("const status = 2;"); + actions_response.add_actions()->mutable_js_flow()->set_js_flow( + "return {status};"); + actions_response.add_actions()->mutable_js_flow()->set_js_flow( + "return {status};"); + + EXPECT_CALL( + mock_service_, + GetNextActions( + _, _, _, + ElementsAre(Property(&ProcessedActionProto::status, ACTION_APPLIED), + Property(&ProcessedActionProto::status, ACTION_APPLIED)), + _, _, _)) + .WillOnce(RunOnceCallback<6>(net::HTTP_OK, + ActionsResponseProto().SerializeAsString(), + ServiceRequestSender::ResponseInfo{})); + + EXPECT_CALL(executor_callback_, + Run(Field(&ScriptExecutor::Result::success, true))); + + Run(actions_response); +} + +IN_PROC_BROWSER_TEST_F(ScriptExecutorBrowserTest, JsLibraryIsUsedAccrossCalls) { + ActionsResponseProto actions_response_1; + actions_response_1.set_js_flow_library("const st = 2;"); + actions_response_1.add_actions()->mutable_js_flow()->set_js_flow( + "return {status: st};"); + + ActionsResponseProto actions_response_2; + actions_response_2.add_actions()->mutable_js_flow()->set_js_flow( + "return {status: st};"); + + EXPECT_CALL(mock_service_, + GetNextActions(_, _, _, + ElementsAre(Property(&ProcessedActionProto::status, + ACTION_APPLIED)), + _, _, _)) + .WillOnce(RunOnceCallback<6>(net::HTTP_OK, + actions_response_2.SerializeAsString(), + ServiceRequestSender::ResponseInfo{})) + .WillOnce(RunOnceCallback<6>(net::HTTP_OK, + ActionsResponseProto().SerializeAsString(), + ServiceRequestSender::ResponseInfo{})); + + EXPECT_CALL(executor_callback_, + Run(Field(&ScriptExecutor::Result::success, true))); + + Run(actions_response_1); +} + +IN_PROC_BROWSER_TEST_F(ScriptExecutorBrowserTest, JsFlowErrorInJsLibraryFlow) { + ActionsResponseProto actions_response_1; + actions_response_1.set_js_flow_library("throw new Error();"); + actions_response_1.add_actions()->mutable_js_flow()->set_js_flow( + "return {status: 2};"); + + EXPECT_CALL(mock_service_, GetNextActions) + .WillOnce(DoAll( + WithArgs<3>( + [](const std::vector<ProcessedActionProto>& processed_actions) { + ASSERT_THAT(processed_actions, SizeIs(1)); + + const auto& processed_action = processed_actions[0]; + EXPECT_THAT(processed_action, + Property(&ProcessedActionProto::status, + UNEXPECTED_JS_ERROR)); + + const auto& unexpected_error_info = + processed_action.status_details().unexpected_error_info(); + + EXPECT_THAT( + unexpected_error_info.js_exception_locations(), + ElementsAre(UnexpectedErrorInfoProto::JS_FLOW_LIBRARY)); + EXPECT_THAT(unexpected_error_info.js_exception_line_numbers(), + ElementsAre(0)); + EXPECT_THAT(unexpected_error_info.js_exception_column_numbers(), + ElementsAre(6)); + }), + RunOnceCallback<6>(net::HTTP_OK, + ActionsResponseProto().SerializeAsString(), + ServiceRequestSender::ResponseInfo{}))); + + EXPECT_CALL(executor_callback_, + Run(Field(&ScriptExecutor::Result::success, true))); + + Run(actions_response_1); +} + +IN_PROC_BROWSER_TEST_F(ScriptExecutorBrowserTest, JsFlowErrorInJsFlow) { + ActionsResponseProto actions_response_1; + actions_response_1.set_js_flow_library("const st = 2;\n"); + actions_response_1.add_actions()->mutable_js_flow()->set_js_flow( + "throw new Error();"); + + EXPECT_CALL(mock_service_, GetNextActions) + .WillOnce(DoAll( + WithArgs<3>( + [](const std::vector<ProcessedActionProto>& processed_actions) { + ASSERT_THAT(processed_actions, SizeIs(1)); + + const auto& processed_action = processed_actions[0]; + EXPECT_THAT(processed_action, + Property(&ProcessedActionProto::status, + UNEXPECTED_JS_ERROR)); + + const auto& unexpected_error_info = + processed_action.status_details().unexpected_error_info(); + + EXPECT_THAT(unexpected_error_info.js_exception_locations(), + ElementsAre(UnexpectedErrorInfoProto::JS_FLOW)); + EXPECT_THAT(unexpected_error_info.js_exception_line_numbers(), + ElementsAre(0)); + EXPECT_THAT(unexpected_error_info.js_exception_column_numbers(), + ElementsAre(6)); + }), + RunOnceCallback<6>(net::HTTP_OK, + ActionsResponseProto().SerializeAsString(), + ServiceRequestSender::ResponseInfo{}))); + + EXPECT_CALL(executor_callback_, + Run(Field(&ScriptExecutor::Result::success, true))); + + Run(actions_response_1); +} + +IN_PROC_BROWSER_TEST_F(ScriptExecutorBrowserTest, JsFlowNestedError) { + ActionsResponseProto actions_response_1; + actions_response_1.set_js_flow_library( + "function throwError() {throw new Error();}"); + actions_response_1.add_actions()->mutable_js_flow()->set_js_flow( + "throwError();"); + + EXPECT_CALL(mock_service_, GetNextActions) + .WillOnce(DoAll( + WithArgs<3>( + [](const std::vector<ProcessedActionProto>& processed_actions) { + ASSERT_THAT(processed_actions, SizeIs(1)); + + const auto& processed_action = processed_actions[0]; + EXPECT_THAT(processed_action, + Property(&ProcessedActionProto::status, + UNEXPECTED_JS_ERROR)); + + const auto& unexpected_error_info = + processed_action.status_details().unexpected_error_info(); + + EXPECT_THAT( + unexpected_error_info.js_exception_locations(), + ElementsAre(UnexpectedErrorInfoProto::JS_FLOW_LIBRARY, + UnexpectedErrorInfoProto::JS_FLOW)); + EXPECT_THAT(unexpected_error_info.js_exception_line_numbers(), + ElementsAre(0, 0)); + EXPECT_THAT(unexpected_error_info.js_exception_column_numbers(), + ElementsAre(29, 0)); + }), + RunOnceCallback<6>(net::HTTP_OK, + ActionsResponseProto().SerializeAsString(), + ServiceRequestSender::ResponseInfo{}))); + + EXPECT_CALL(executor_callback_, + Run(Field(&ScriptExecutor::Result::success, true))); + + Run(actions_response_1); +} + +IN_PROC_BROWSER_TEST_F(ScriptExecutorBrowserTest, WaitForDomSucceeds) { + WaitForDomProto wait_for_dom; + wait_for_dom.mutable_wait_condition() + ->mutable_match() + ->add_filters() + ->set_css_selector("#button"); + + RunJsFlow( + CreateRunNativeActionCallReturn(&wait_for_dom, ActionProto::kWaitForDom), + ACTION_APPLIED); +} + +IN_PROC_BROWSER_TEST_F(ScriptExecutorBrowserTest, WaitForDomFails) { + WaitForDomProto wait_for_dom; + wait_for_dom.mutable_wait_condition() + ->mutable_match() + ->add_filters() + ->set_css_selector("#not-found"); + + RunJsFlow( + CreateRunNativeActionCallReturn(&wait_for_dom, ActionProto::kWaitForDom), + ELEMENT_RESOLUTION_FAILED); +} +} // namespace +} // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/script_executor_delegate.h b/chromium/components/autofill_assistant/browser/script_executor_delegate.h index deb2c37c43a..787cb4a1479 100644 --- a/chromium/components/autofill_assistant/browser/script_executor_delegate.h +++ b/chromium/components/autofill_assistant/browser/script_executor_delegate.h @@ -13,6 +13,7 @@ #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/details.h" #include "components/autofill_assistant/browser/info_box.h" +#include "components/autofill_assistant/browser/js_flow_devtools_wrapper.h" #include "components/autofill_assistant/browser/state.h" #include "components/autofill_assistant/browser/tts_button_state.h" #include "components/autofill_assistant/browser/user_action.h" @@ -63,6 +64,10 @@ class ScriptExecutorDelegate { virtual password_manager::PasswordChangeSuccessTracker* GetPasswordChangeSuccessTracker() = 0; virtual content::WebContents* GetWebContents() = 0; + + virtual void SetJsFlowLibrary(const std::string& js_flow_library) = 0; + virtual JsFlowDevtoolsWrapper* GetJsFlowDevtoolsWrapper() = 0; + virtual std::string GetEmailAddressForAccessTokenAccount() = 0; virtual ukm::UkmRecorder* GetUkmRecorder() = 0; @@ -147,8 +152,12 @@ class ScriptExecutorDelegate { // gets attached to the action's response if non empty. virtual ProcessedActionStatusDetailsProto& GetLogInfo() = 0; + // Returns whether or not this instance of Autofill Assistant must use a + // backend endpoint to query data. + virtual bool MustUseBackendData() const = 0; + protected: - virtual ~ScriptExecutorDelegate() {} + virtual ~ScriptExecutorDelegate() = default; }; } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/script_executor_ui_delegate.h b/chromium/components/autofill_assistant/browser/script_executor_ui_delegate.h index 8e5512b4874..cd927d802d0 100644 --- a/chromium/components/autofill_assistant/browser/script_executor_ui_delegate.h +++ b/chromium/components/autofill_assistant/browser/script_executor_ui_delegate.h @@ -13,17 +13,20 @@ #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/details.h" #include "components/autofill_assistant/browser/info_box.h" +#include "components/autofill_assistant/browser/public/external_action_delegate.h" +#include "components/autofill_assistant/browser/public/external_script_controller.h" #include "components/autofill_assistant/browser/state.h" #include "components/autofill_assistant/browser/tts_button_state.h" #include "components/autofill_assistant/browser/user_action.h" #include "components/autofill_assistant/browser/user_data.h" +#include "components/autofill_assistant/browser/wait_for_dom_observer.h" #include "url/gurl.h" namespace autofill_assistant { // A delegate which provides the ScriptExecutor with methods to control the // Autofill Assistant UI. -class ScriptExecutorUiDelegate { +class ScriptExecutorUiDelegate : public WaitForDomObserver { public: virtual void SetStatusMessage(const std::string& message) = 0; virtual std::string GetStatusMessage() const = 0; @@ -41,6 +44,8 @@ class ScriptExecutorUiDelegate { virtual void ClearInfoBox() = 0; virtual void SetCollectUserDataOptions( CollectUserDataOptions* collect_user_data_options) = 0; + virtual void SetCollectUserDataUiState(bool loading, + UserDataEventField event_field) = 0; virtual void SetLastSuccessfulUserDataOptions( std::unique_ptr<CollectUserDataOptions> collect_user_data_options) = 0; virtual const CollectUserDataOptions* GetLastSuccessfulUserDataOptions() @@ -87,6 +92,17 @@ class ScriptExecutorUiDelegate { // Clears the persistent generic UI. virtual void ClearPersistentGenericUi() = 0; + // Whether this supports external actions. + virtual bool SupportsExternalActions() = 0; + + // Executes the external action. + virtual void ExecuteExternalAction( + const external::Action& external_action, + base::OnceCallback<void(ExternalActionDelegate::DomUpdateCallback)> + start_dom_checks_callback, + base::OnceCallback<void(const external::Result& result)> + end_action_callback) = 0; + protected: virtual ~ScriptExecutorUiDelegate() {} }; diff --git a/chromium/components/autofill_assistant/browser/script_executor_unittest.cc b/chromium/components/autofill_assistant/browser/script_executor_unittest.cc index 7a0e2bd668d..469b2dcd280 100644 --- a/chromium/components/autofill_assistant/browser/script_executor_unittest.cc +++ b/chromium/components/autofill_assistant/browser/script_executor_unittest.cc @@ -214,7 +214,7 @@ TEST_F(ScriptExecutorTest, ForwardParameters) { {{"additional_param", "additional_param_value"}, {"param", "value"}}))); - std::move(callback).Run(net::HTTP_OK, "", + std::move(callback).Run(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}); }); @@ -235,7 +235,7 @@ TEST_F(ScriptExecutorTest, RunOneActionReportAndReturn) { EXPECT_CALL(mock_service_, GetNextActions) .WillOnce( DoAll(SaveArg<3>(&processed_actions_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); EXPECT_CALL(executor_callback_, Run(AllOf(Field(&ScriptExecutor::Result::success, true), @@ -269,7 +269,7 @@ TEST_F(ScriptExecutorTest, RunMultipleActions) { ServiceRequestSender::ResponseInfo{}))) .WillOnce( DoAll(SaveArg<3>(&processed_actions2_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); EXPECT_CALL(executor_callback_, Run(Field(&ScriptExecutor::Result::success, true))); @@ -307,7 +307,7 @@ TEST_F(ScriptExecutorTest, ShowsSlowConnectionWarningReplace) { Delay(&task_environment_, 600), RunOnceCallback<6>(net::HTTP_OK, Serialize(next_actions_response), ServiceRequestSender::ResponseInfo{}))) - .WillOnce(RunOnceCallback<6>(net::HTTP_OK, "", + .WillOnce(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(executor_callback_, Run(Field(&ScriptExecutor::Result::success, true))); @@ -342,7 +342,7 @@ TEST_F(ScriptExecutorTest, ShowsSlowConnectionWarningConcatenate) { Delay(&task_environment_, 600), RunOnceCallback<6>(net::HTTP_OK, Serialize(next_actions_response), ServiceRequestSender::ResponseInfo{}))) - .WillOnce(RunOnceCallback<6>(net::HTTP_OK, "", + .WillOnce(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(executor_callback_, Run(Field(&ScriptExecutor::Result::success, true))); @@ -379,7 +379,7 @@ TEST_F(ScriptExecutorTest, SlowConnectionWarningTriggersOnlyOnce) { Delay(&task_environment_, 600), RunOnceCallback<6>(net::HTTP_OK, Serialize(next_actions_response), ServiceRequestSender::ResponseInfo{}))) - .WillOnce(RunOnceCallback<6>(net::HTTP_OK, "", + .WillOnce(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(executor_callback_, Run(Field(&ScriptExecutor::Result::success, true))); @@ -415,7 +415,7 @@ TEST_F(ScriptExecutorTest, SlowConnectionWarningTriggersMultipleTimes) { Delay(&task_environment_, 600), RunOnceCallback<6>(net::HTTP_OK, Serialize(next_actions_response), ServiceRequestSender::ResponseInfo{}))) - .WillOnce(RunOnceCallback<6>(net::HTTP_OK, "", + .WillOnce(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(executor_callback_, Run(Field(&ScriptExecutor::Result::success, true))); @@ -450,7 +450,7 @@ TEST_F(ScriptExecutorTest, SlowConnectionWarningNotShowingIfNotConsecutive) { Delay(&task_environment_, 600), RunOnceCallback<6>(net::HTTP_OK, Serialize(initial_actions_response), ServiceRequestSender::ResponseInfo{}))) - .WillOnce(RunOnceCallback<6>(net::HTTP_OK, "", + .WillOnce(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(executor_callback_, Run(Field(&ScriptExecutor::Result::success, true))); @@ -478,7 +478,7 @@ TEST_F(ScriptExecutorTest, SlowConnectionWarningNotShowingIfOnCompleted) { ServiceRequestSender::ResponseInfo{})) .WillOnce( DoAll(Delay(&task_environment_, 600), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); EXPECT_CALL(executor_callback_, Run(Field(&ScriptExecutor::Result::success, true))); @@ -532,7 +532,7 @@ TEST_F(ScriptExecutorTest, SlowConnectionWarningNotShownIfSlowWebsiteFirst) { .WillOnce(DoAll(Delay(&task_environment_, 600), RunOnceCallback<6>(net::HTTP_OK, Serialize(tell3), ServiceRequestSender::ResponseInfo{}))) - .WillOnce(RunOnceCallback<6>(net::HTTP_OK, "", + .WillOnce(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(executor_callback_, Run(Field(&ScriptExecutor::Result::success, true))); @@ -684,7 +684,7 @@ TEST_F(ScriptExecutorTest, SlowWebsiteWarningNotShownIfSlowConnectionFirst) { .WillOnce(DoAll(Delay(&task_environment_, 600), RunOnceCallback<6>(net::HTTP_OK, Serialize(tell3), ServiceRequestSender::ResponseInfo{}))) - .WillOnce(RunOnceCallback<6>(net::HTTP_OK, "", + .WillOnce(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(executor_callback_, Run(Field(&ScriptExecutor::Result::success, true))); @@ -745,7 +745,7 @@ TEST_F(ScriptExecutorTest, SlowWarningsBothShownIfConfigured) { .WillOnce(DoAll(Delay(&task_environment_, 600), RunOnceCallback<6>(net::HTTP_OK, Serialize(tell3), ServiceRequestSender::ResponseInfo{}))) - .WillOnce(RunOnceCallback<6>(net::HTTP_OK, "", + .WillOnce(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(executor_callback_, Run(Field(&ScriptExecutor::Result::success, true))); @@ -771,7 +771,7 @@ TEST_F(ScriptExecutorTest, UnsupportedAction) { EXPECT_CALL(mock_service_, GetNextActions) .WillOnce( DoAll(SaveArg<3>(&processed_actions_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); EXPECT_CALL(executor_callback_, Run(Field(&ScriptExecutor::Result::success, true))); @@ -790,7 +790,7 @@ TEST_F(ScriptExecutorTest, StopAfterEnd) { ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(mock_service_, GetNextActions) - .WillOnce(RunOnceCallback<6>(net::HTTP_OK, "", + .WillOnce(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(executor_callback_, Run(AllOf(Field(&ScriptExecutor::Result::success, true), @@ -812,7 +812,7 @@ TEST_F(ScriptExecutorTest, StopClearsUnexecutedActions) { EXPECT_CALL(mock_service_, GetNextActions) .WillOnce( DoAll(SaveArg<3>(&processed_actions_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); EXPECT_CALL(executor_callback_, Run(AllOf(Field(&ScriptExecutor::Result::success, true), @@ -825,6 +825,47 @@ TEST_F(ScriptExecutorTest, StopClearsUnexecutedActions) { EXPECT_EQ(processed_actions_capture[0].action(), actions_response.actions(0)); } +TEST_F(ScriptExecutorTest, StopActionGetsExecutedAfterEmptyResponse) { + ActionsResponseProto actions_response; + actions_response.add_actions()->mutable_stop(); + + EXPECT_CALL(mock_service_, GetActions) + .WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response), + ServiceRequestSender::ResponseInfo{})); + + ActionsResponseProto second_actions_response; + second_actions_response.add_actions()->mutable_tell()->set_message( + "tell message"); + std::vector<ProcessedActionProto> second_response_processed_actions_capture; + std::vector<ProcessedActionProto> third_response_processed_actions_capture; + EXPECT_CALL(mock_service_, GetNextActions) + // Second response. + .WillOnce(DoAll( + SaveArg<3>(&second_response_processed_actions_capture), + RunOnceCallback<6>(net::HTTP_OK, Serialize(second_actions_response), + ServiceRequestSender::ResponseInfo{}))) + // Third response - empty. We only expect the execution to stop after this + // response. + .WillOnce( + DoAll(SaveArg<3>(&third_response_processed_actions_capture), + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", + ServiceRequestSender::ResponseInfo{}))); + EXPECT_CALL(executor_callback_, + Run(AllOf(Field(&ScriptExecutor::Result::success, true), + Field(&ScriptExecutor::Result::at_end, + ScriptExecutor::SHUTDOWN)))); + executor_->Run(&user_data_, executor_callback_.Get()); + + // We expect the actions from the second response to have been executed. + EXPECT_EQ(ui_delegate_.GetStatusMessage(), "tell message"); + ASSERT_EQ(second_response_processed_actions_capture.size(), 1u); + EXPECT_EQ(second_response_processed_actions_capture[0].action(), + actions_response.actions(0)); + ASSERT_EQ(third_response_processed_actions_capture.size(), 1u); + EXPECT_EQ(third_response_processed_actions_capture[0].action(), + second_actions_response.actions(0)); +} + TEST_F(ScriptExecutorTest, InterruptActionListOnError) { ActionsResponseProto initial_actions_response; initial_actions_response.add_actions()->mutable_tell()->set_message( @@ -850,7 +891,7 @@ TEST_F(ScriptExecutorTest, InterruptActionListOnError) { ServiceRequestSender::ResponseInfo{}))) .WillOnce( DoAll(SaveArg<3>(&processed_actions2_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); EXPECT_CALL(executor_callback_, Run(Field(&ScriptExecutor::Result::success, true))); @@ -881,7 +922,7 @@ TEST_F(ScriptExecutorTest, RunDelayedAction) { EXPECT_CALL(mock_service_, GetNextActions) .WillOnce( DoAll(SaveArg<3>(&processed_actions_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); // executor_callback_.Run() not expected to be run just yet, as the action is @@ -908,7 +949,7 @@ TEST_F(ScriptExecutorTest, ClearDetailsWhenFinished) { .WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response), ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(mock_service_, GetNextActions) - .WillOnce(RunOnceCallback<6>(net::HTTP_OK, "", + .WillOnce(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(executor_callback_, Run(Field(&ScriptExecutor::Result::success, true))); @@ -931,7 +972,7 @@ TEST_F(ScriptExecutorTest, DontClearDetailsIfOtherActionsAreLeft) { .WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response), ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(mock_service_, GetNextActions) - .WillOnce(RunOnceCallback<6>(net::HTTP_OK, "", + .WillOnce(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(executor_callback_, Run(Field(&ScriptExecutor::Result::success, true))); @@ -1023,7 +1064,7 @@ TEST_F(ScriptExecutorTest, WaitForDomWaitUntil) { EXPECT_CALL(mock_service_, GetNextActions) .WillOnce( DoAll(SaveArg<3>(&processed_actions_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); // First check does not find the element, wait for dom waits 1s, then the @@ -1056,11 +1097,11 @@ TEST_F(ScriptExecutorTest, RunInterrupt) { std::vector<ProcessedActionProto> processed_actions2_capture; EXPECT_CALL(mock_service_, GetNextActions) .WillOnce(DoAll(SaveArg<3>(&processed_actions1_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))) .WillOnce( DoAll(SaveArg<3>(&processed_actions2_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); EXPECT_CALL(executor_callback_, @@ -1093,15 +1134,15 @@ TEST_F(ScriptExecutorTest, RunMultipleInterruptInOrder) { testing::InSequence seq; EXPECT_CALL(mock_service_, GetNextActions(_, _, "payload for interrupt1", _, _, _, _)) - .WillOnce(RunOnceCallback<6>(net::HTTP_OK, "", + .WillOnce(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(mock_service_, GetNextActions(_, _, "payload for interrupt2", _, _, _, _)) - .WillOnce(RunOnceCallback<6>(net::HTTP_OK, "", + .WillOnce(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(mock_service_, GetNextActions(_, _, "main script payload", _, _, _, _)) - .WillOnce(RunOnceCallback<6>(net::HTTP_OK, "", + .WillOnce(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); } @@ -1135,7 +1176,7 @@ TEST_F(ScriptExecutorTest, RunSameInterruptMultipleTimes) { // All scripts succeed with no more actions. EXPECT_CALL(mock_service_, GetNextActions) - .WillRepeatedly(RunOnceCallback<6>(net::HTTP_OK, "", + .WillRepeatedly(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(executor_callback_, @@ -1222,7 +1263,7 @@ TEST_F(ScriptExecutorTest, DoNotRunInterruptIfPreconditionsDontMatch) { EXPECT_CALL(mock_service_, GetNextActions) .WillOnce( DoAll(SaveArg<3>(&processed_actions_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); EXPECT_CALL(executor_callback_, @@ -1256,7 +1297,7 @@ TEST_F(ScriptExecutorTest, DoNotRunInterruptIfNotInterruptible) { EXPECT_CALL(mock_service_, GetNextActions) .WillOnce( DoAll(SaveArg<3>(&processed_actions_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); EXPECT_CALL(executor_callback_, @@ -1294,6 +1335,8 @@ TEST_F(ScriptExecutorTest, InterruptFailsMainScript) { EXPECT_CALL(executor_callback_, Run(Field(&ScriptExecutor::Result::success, false))); executor_->Run(&user_data_, executor_callback_.Get()); + EXPECT_THAT(ui_delegate_.GetInterruptNotificationHistory(), + ElementsAre(FakeScriptExecutorUiDelegate::INTERRUPT_STARTED)); } TEST_F(ScriptExecutorTest, InterruptReturnsShutdown) { @@ -1315,7 +1358,7 @@ TEST_F(ScriptExecutorTest, InterruptReturnsShutdown) { // action. EXPECT_CALL(mock_service_, GetNextActions) .Times(2) - .WillRepeatedly(RunOnceCallback<6>(net::HTTP_OK, "", + .WillRepeatedly(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(executor_callback_, @@ -1323,6 +1366,8 @@ TEST_F(ScriptExecutorTest, InterruptReturnsShutdown) { Field(&ScriptExecutor::Result::at_end, ScriptExecutor::SHUTDOWN)))); executor_->Run(&user_data_, executor_callback_.Get()); + EXPECT_THAT(ui_delegate_.GetInterruptNotificationHistory(), + ElementsAre(FakeScriptExecutorUiDelegate::INTERRUPT_STARTED)); } TEST_F(ScriptExecutorTest, RunInterruptDuringPrompt) { @@ -1379,7 +1424,7 @@ TEST_F(ScriptExecutorTest, RunInterruptDuringPrompt) { })); EXPECT_CALL(mock_service_, GetNextActions) - .WillRepeatedly(RunOnceCallback<6>(net::HTTP_OK, "", + .WillRepeatedly(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(executor_callback_, @@ -1415,6 +1460,9 @@ TEST_F(ScriptExecutorTest, RunInterruptDuringPrompt) { ElementAreaProto::default_instance(), interruptible_area, ElementAreaProto::default_instance())); EXPECT_EQ("done", ui_delegate_.GetStatusMessage()); + EXPECT_THAT(ui_delegate_.GetInterruptNotificationHistory(), + ElementsAre(FakeScriptExecutorUiDelegate::INTERRUPT_STARTED, + FakeScriptExecutorUiDelegate::INTERRUPT_FINISHED)); } TEST_F(ScriptExecutorTest, RunPromptInBrowseMode) { @@ -1493,7 +1541,7 @@ TEST_F(ScriptExecutorTest, RunInterruptMultipleTimesDuringPrompt) { })); EXPECT_CALL(mock_service_, GetNextActions) - .WillRepeatedly(RunOnceCallback<6>(net::HTTP_OK, "", + .WillRepeatedly(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(executor_callback_, @@ -1509,6 +1557,11 @@ TEST_F(ScriptExecutorTest, RunInterruptMultipleTimesDuringPrompt) { AutofillAssistantState::PROMPT, AutofillAssistantState::RUNNING, AutofillAssistantState::PROMPT, AutofillAssistantState::RUNNING, AutofillAssistantState::PROMPT, AutofillAssistantState::RUNNING)); + EXPECT_THAT(ui_delegate_.GetInterruptNotificationHistory(), + ElementsAre(FakeScriptExecutorUiDelegate::INTERRUPT_STARTED, + FakeScriptExecutorUiDelegate::INTERRUPT_FINISHED, + FakeScriptExecutorUiDelegate::INTERRUPT_STARTED, + FakeScriptExecutorUiDelegate::INTERRUPT_FINISHED)); } TEST_F(ScriptExecutorTest, UpdateScriptListGetNext) { @@ -1535,7 +1588,7 @@ TEST_F(ScriptExecutorTest, UpdateScriptListGetNext) { .WillOnce(RunOnceCallback<6>(net::HTTP_OK, Serialize(next_actions_response), ServiceRequestSender::ResponseInfo{})) - .WillOnce(RunOnceCallback<6>(net::HTTP_OK, "", + .WillOnce(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(executor_callback_, @@ -1568,7 +1621,7 @@ TEST_F(ScriptExecutorTest, UpdateScriptListShouldNotifyMultipleTimes) { EXPECT_CALL(mock_service_, GetNextActions) .WillOnce(RunOnceCallback<6>(net::HTTP_OK, Serialize(actions_response), ServiceRequestSender::ResponseInfo{})) - .WillOnce(RunOnceCallback<6>(net::HTTP_OK, "", + .WillOnce(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(executor_callback_, @@ -1608,7 +1661,7 @@ TEST_F(ScriptExecutorTest, UpdateScriptListFromInterrupt) { .Times(3) .WillOnce(RunOnceCallback<6>(net::HTTP_OK, Serialize(interrupt_actions), ServiceRequestSender::ResponseInfo{})) - .WillRepeatedly(RunOnceCallback<6>(net::HTTP_OK, "", + .WillRepeatedly(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(executor_callback_, @@ -1643,7 +1696,7 @@ TEST_F(ScriptExecutorTest, RestorePreInterruptStatusMessage) { ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(mock_service_, GetNextActions) - .WillRepeatedly(RunOnceCallback<6>(net::HTTP_OK, "", + .WillRepeatedly(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(executor_callback_, @@ -1667,7 +1720,7 @@ TEST_F(ScriptExecutorTest, KeepStatusMessageWhenNotInterrupted) { ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(mock_service_, GetNextActions) - .WillRepeatedly(RunOnceCallback<6>(net::HTTP_OK, "", + .WillRepeatedly(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{})); EXPECT_CALL(executor_callback_, @@ -1678,8 +1731,8 @@ TEST_F(ScriptExecutorTest, KeepStatusMessageWhenNotInterrupted) { EXPECT_EQ("pre-interrupt status", ui_delegate_.GetStatusMessage()); } -#if BUILDFLAG(IS_ANDROID) && defined(ADDRESS_SANITIZER) -// This test fails on Android ASAN: https://crbug.com/1315701 +#if defined(ADDRESS_SANITIZER) +// This test fails on ASAN: https://crbug.com/1315701 #define MAYBE_PauseWaitForDomWhileNavigating \ DISABLED_PauseWaitForDomWhileNavigating #else @@ -1699,7 +1752,7 @@ TEST_F(ScriptExecutorTest, MAYBE_PauseWaitForDomWhileNavigating) { EXPECT_CALL(mock_service_, GetNextActions) .WillOnce( DoAll(SaveArg<3>(&processed_actions_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); // First check does not find the element, wait for dom waits 1s. @@ -1743,7 +1796,7 @@ TEST_F(ScriptExecutorTest, StartWaitForDomWhileNavigating) { EXPECT_CALL(mock_service_, GetNextActions) .WillOnce( DoAll(SaveArg<3>(&processed_actions_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); // Navigation starts before WaitForDom starts. WaitForDom does not wait and @@ -1783,11 +1836,11 @@ TEST_F(ScriptExecutorTest, NavigateWhileRunningInterrupt) { std::vector<ProcessedActionProto> processed_actions2_capture; EXPECT_CALL(mock_service_, GetNextActions) .WillOnce(DoAll(SaveArg<3>(&processed_actions1_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))) .WillOnce( DoAll(SaveArg<3>(&processed_actions2_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); EXPECT_CALL(executor_callback_, Run(_)); @@ -1809,7 +1862,7 @@ TEST_F(ScriptExecutorTest, ReportNavigationErrors) { EXPECT_CALL(mock_service_, GetNextActions) .WillOnce( DoAll(SaveArg<3>(&processed_actions_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); delegate_.UpdateNavigationState(/* navigating= */ false, /* error= */ true); @@ -1836,7 +1889,7 @@ TEST_F(ScriptExecutorTest, ReportNavigationEnd) { EXPECT_CALL(mock_service_, GetNextActions) .WillOnce( DoAll(SaveArg<3>(&processed_actions_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); // WaitForDom does NOT wait for navigation to end, it immediately checks for @@ -1865,8 +1918,8 @@ TEST_F(ScriptExecutorTest, ReportNavigationEnd) { EXPECT_TRUE(processed_actions_capture[0].navigation_info().ended()); } -#if BUILDFLAG(IS_ANDROID) && defined(ADDRESS_SANITIZER) -// This test fails on Android ASAN: https://crbug.com/1315701 +#if defined(ADDRESS_SANITIZER) +// This test fails on ASAN: https://crbug.com/1315701 #define MAYBE_ReportUnexpectedNavigationStart \ DISABLED_ReportUnexpectedNavigationStart #else @@ -1885,7 +1938,7 @@ TEST_F(ScriptExecutorTest, MAYBE_ReportUnexpectedNavigationStart) { EXPECT_CALL(mock_service_, GetNextActions) .WillOnce( DoAll(SaveArg<3>(&processed_actions_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); // As the element doesn't exist, WaitForDom returns and waits for 1s. @@ -1911,8 +1964,8 @@ TEST_F(ScriptExecutorTest, MAYBE_ReportUnexpectedNavigationStart) { EXPECT_TRUE(processed_actions_capture[0].navigation_info().unexpected()); } -#if BUILDFLAG(IS_ANDROID) && defined(ADDRESS_SANITIZER) -// This test fails on Android ASAN: https://crbug.com/1315701 +#if defined(ADDRESS_SANITIZER) +// This test fails on ASAN: https://crbug.com/1315701 #define MAYBE_ReportExpectedNavigationStart \ DISABLED_ReportExpectedNavigationStart #else @@ -1932,7 +1985,7 @@ TEST_F(ScriptExecutorTest, MAYBE_ReportExpectedNavigationStart) { EXPECT_CALL(mock_service_, GetNextActions) .WillOnce( DoAll(SaveArg<3>(&processed_actions_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); // As the element doesn't exist, WaitForDom returns and waits for 1s. @@ -1970,7 +2023,7 @@ TEST_F(ScriptExecutorTest, WaitForNavigationWithoutExpectation) { EXPECT_CALL(mock_service_, GetNextActions) .WillOnce( DoAll(SaveArg<3>(&processed_actions_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); // WaitForNavigation returns immediately @@ -1993,7 +2046,7 @@ TEST_F(ScriptExecutorTest, ExpectNavigation) { EXPECT_CALL(mock_service_, GetNextActions) .WillOnce( DoAll(SaveArg<3>(&processed_actions_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); // WaitForNavigation waits for navigation to start after expect_navigation @@ -2021,7 +2074,7 @@ TEST_F(ScriptExecutorTest, MultipleWaitForNavigation) { EXPECT_CALL(mock_service_, GetNextActions) .WillOnce( DoAll(SaveArg<3>(&processed_actions_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); // The first wait_for_navigation waits for the navigation to happen. After @@ -2050,7 +2103,7 @@ TEST_F(ScriptExecutorTest, ExpectLaterNavigationIgnoringNavigationInProgress) { EXPECT_CALL(mock_service_, GetNextActions) .WillOnce( DoAll(SaveArg<3>(&processed_actions_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); delegate_.UpdateNavigationState(/* navigating= */ true, /* error= */ false); @@ -2087,7 +2140,7 @@ TEST_F(ScriptExecutorTest, WaitForNavigationReportsError) { EXPECT_CALL(mock_service_, GetNextActions) .WillOnce( DoAll(SaveArg<3>(&processed_actions_capture), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); // WaitForNavigation waits for navigation to start after expect_navigation @@ -2141,7 +2194,7 @@ TEST_F(ScriptExecutorTest, RoundtripTimingStats) { EXPECT_CALL(mock_service_, GetNextActions) .WillOnce( DoAll(SaveArg<4>(&timing_stats), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); executor_->Run(&user_data_, executor_callback_.Get()); EXPECT_TRUE(task_environment_.NextTaskIsDelayed()); @@ -2171,7 +2224,7 @@ TEST_F(ScriptExecutorTest, RoundtripNetworkStats) { EXPECT_CALL(mock_service_, GetNextActions) .WillOnce( DoAll(SaveArg<5>(&captured_network_stats), - RunOnceCallback<6>(net::HTTP_OK, "", + RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", ServiceRequestSender::ResponseInfo{}))); EXPECT_CALL(executor_callback_, @@ -2209,5 +2262,135 @@ TEST_F(ScriptExecutorTest, ClearPersistentUiOnError) { ASSERT_EQ(nullptr, ui_delegate_.GetPersistentGenericUi()); } +TEST_F(ScriptExecutorTest, RequestUserData) { + EXPECT_CALL(mock_service_, GetUserData) + .WillOnce(RunOnceCallback<3>(net::HTTP_OK, std::string(), + ServiceRequestSender::ResponseInfo{})); + + base::MockCallback< + base::OnceCallback<void(bool, const GetUserDataResponseProto&)>> + mock_callback; + EXPECT_CALL(mock_callback, Run(true, _)); + + executor_->RequestUserData(UserDataEventField::SHIPPING_EVENT, + CollectUserDataOptions(), mock_callback.Get()); + EXPECT_THAT(delegate_.GetStateHistory(), + ElementsAre(AutofillAssistantState::RUNNING)); + EXPECT_EQ(ui_delegate_.GetCollectUserDataUiLoadingField(), + UserDataEventField::SHIPPING_EVENT); +} + +TEST_F(ScriptExecutorTest, CollectUserData) { + // Ui has been disabled while loading. + ui_delegate_.SetCollectUserDataUiState(/* loading= */ true, + UserDataEventField::SHIPPING_EVENT); + EXPECT_EQ(ui_delegate_.GetCollectUserDataUiLoadingField(), + UserDataEventField::SHIPPING_EVENT); + + CollectUserDataOptions options; + executor_->CollectUserData(&options); + + EXPECT_TRUE(options.confirm_callback); + EXPECT_TRUE(options.additional_actions_callback); + EXPECT_TRUE(options.terms_link_callback); + EXPECT_EQ(ui_delegate_.GetOptions(), &options); + EXPECT_EQ(ui_delegate_.GetCollectUserDataUiLoadingField(), + UserDataEventField::NONE); +} + +TEST_F(ScriptExecutorTest, MustUseBackendData) { + delegate_.SetMustUseBackendData(true); + EXPECT_TRUE(executor_->MustUseBackendData()); + + delegate_.SetMustUseBackendData(false); + EXPECT_FALSE(executor_->MustUseBackendData()); +} + +TEST_F(ScriptExecutorTest, ExternalActionDoesNotApplyTouchableArea) { + ActionsResponseProto actions_response; + ElementAreaProto area = MakeElementAreaProto("#area"); + *actions_response.add_actions() + ->mutable_set_touchable_area() + ->mutable_element_area() = area; + actions_response.add_actions()->mutable_external_action()->mutable_info(); + + EXPECT_CALL(mock_service_, GetActions) + .WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response), + ServiceRequestSender::ResponseInfo{})); + EXPECT_CALL(mock_service_, GetNextActions) + .WillRepeatedly(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", + ServiceRequestSender::ResponseInfo{})); + + EXPECT_CALL(executor_callback_, + Run(Field(&ScriptExecutor::Result::success, true))); + + executor_->Run(&user_data_, executor_callback_.Get()); + // The touchable area was never applied. + EXPECT_THAT(delegate_.GetTouchableElementAreaHistory(), IsEmpty()); + // The delegate never entered prompt. + EXPECT_THAT(delegate_.GetStateHistory(), IsEmpty()); +} + +TEST_F(ScriptExecutorTest, ExternalActionDoesNotConsumeTouchableArea) { + ActionsResponseProto actions_response; + ElementAreaProto area = MakeElementAreaProto("#area"); + *actions_response.add_actions() + ->mutable_set_touchable_area() + ->mutable_element_area() = area; + actions_response.add_actions()->mutable_external_action()->mutable_info(); + auto* prompt_action = actions_response.add_actions()->mutable_prompt(); + *prompt_action->add_choices()->mutable_auto_select_when()->mutable_match() = + ToSelectorProto("end_prompt"); + + EXPECT_CALL(mock_service_, GetActions) + .WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response), + ServiceRequestSender::ResponseInfo{})); + EXPECT_CALL(mock_service_, GetNextActions) + .WillRepeatedly(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", + ServiceRequestSender::ResponseInfo{})); + + EXPECT_CALL(executor_callback_, + Run(Field(&ScriptExecutor::Result::success, true))); + EXPECT_CALL(mock_web_controller_, FindElement(Selector({"end_prompt"}), _, _)) + .WillOnce(RunOnceCallback<2>(OkClientStatus(), + std::make_unique<ElementFinderResult>())); + executor_->Run(&user_data_, executor_callback_.Get()); + // Since the ExternalAction did not consume the touchable area, the following + // prompt action was able to apply it. + EXPECT_THAT(delegate_.GetTouchableElementAreaHistory(), + ElementsAre(area, ElementAreaProto::default_instance())); +} + +TEST_F(ScriptExecutorTest, ExternalActionAppliesAndRestoresTouchableArea) { + ActionsResponseProto actions_response; + ElementAreaProto area = MakeElementAreaProto("#area"); + *actions_response.add_actions() + ->mutable_set_touchable_area() + ->mutable_element_area() = area; + auto* external_action = + actions_response.add_actions()->mutable_external_action(); + external_action->mutable_info(); + external_action->set_show_touchable_area(true); + + EXPECT_CALL(mock_service_, GetActions) + .WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response), + ServiceRequestSender::ResponseInfo{})); + EXPECT_CALL(mock_service_, GetNextActions) + .WillRepeatedly(RunOnceCallback<6>(net::HTTP_OK, /* response= */ "", + ServiceRequestSender::ResponseInfo{})); + + EXPECT_CALL(executor_callback_, + Run(Field(&ScriptExecutor::Result::success, true))); + + executor_->Run(&user_data_, executor_callback_.Get()); + // The touchable area was applied at the start of the ExternalAction and + // restored at the end of it. + EXPECT_THAT(delegate_.GetTouchableElementAreaHistory(), + ElementsAre(area, ElementAreaProto::default_instance())); + EXPECT_THAT(delegate_.GetStateHistory(), + ElementsAre(AutofillAssistantState::PROMPT, + AutofillAssistantState::RUNNING)); +} + } // namespace } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/script_parameters.cc b/chromium/components/autofill_assistant/browser/script_parameters.cc index 8c3d099c1da..99bab290794 100644 --- a/chromium/components/autofill_assistant/browser/script_parameters.cc +++ b/chromium/components/autofill_assistant/browser/script_parameters.cc @@ -107,6 +107,13 @@ const char kSourceParameterName[] = "SOURCE"; // Parameter to specify experiments. const char kExperimentsParameterName[] = "EXPERIMENT_IDS"; +// Parameter to disable CUP RPC signing. Intended for internal use only. +const char kDisableRpcSigningParamaterName[] = "DISABLE_RPC_SIGNING"; + +// Parameter to send the annotate DOM model version. Should only be used if we +// expect the model to be used. +const char kSendAnnotateDomModelVersion[] = "SEND_ANNOTATE_DOM_MODEL_VERSION"; + // The list of non sensitive script parameters that client requests are allowed // to send to the backend i.e., they do not require explicit approval in the // autofill-assistant onboarding. Even so, please always reach out to Chrome @@ -277,6 +284,14 @@ std::vector<std::string> ScriptParameters::GetExperiments() const { base::SplitResult::SPLIT_WANT_NONEMPTY); } +absl::optional<bool> ScriptParameters::GetDisableRpcSigning() const { + return GetTypedParameter<bool>(parameters_, kDisableRpcSigningParamaterName); +} + +absl::optional<bool> ScriptParameters::GetSendAnnotateDomModelVersion() const { + return GetTypedParameter<bool>(parameters_, kSendAnnotateDomModelVersion); +} + absl::optional<bool> ScriptParameters::GetDetailsShowInitial() const { return GetTypedParameter<bool>(parameters_, kDetailsShowInitialParameterName); } diff --git a/chromium/components/autofill_assistant/browser/script_parameters.h b/chromium/components/autofill_assistant/browser/script_parameters.h index a3f1c2d41e9..a87c2c7a829 100644 --- a/chromium/components/autofill_assistant/browser/script_parameters.h +++ b/chromium/components/autofill_assistant/browser/script_parameters.h @@ -65,6 +65,8 @@ class ScriptParameters { absl::optional<int> GetCaller() const; absl::optional<int> GetSource() const; std::vector<std::string> GetExperiments() const; + absl::optional<bool> GetDisableRpcSigning() const; + absl::optional<bool> GetSendAnnotateDomModelVersion() const; // Details parameters. absl::optional<bool> GetDetailsShowInitial() const; diff --git a/chromium/components/autofill_assistant/browser/script_parameters_unittest.cc b/chromium/components/autofill_assistant/browser/script_parameters_unittest.cc index 570b0c66371..a335cd6e9a5 100644 --- a/chromium/components/autofill_assistant/browser/script_parameters_unittest.cc +++ b/chromium/components/autofill_assistant/browser/script_parameters_unittest.cc @@ -108,6 +108,7 @@ TEST(ScriptParametersTest, SpecialScriptParameters) { {"CALLER", "3"}, {"SOURCE", "4"}, {"EXPERIMENT_IDS", "123,456,789"}, + {"DISABLE_RPC_SIGNING", "true"}, {"DETAILS_SHOW_INITIAL", "true"}, {"DETAILS_TITLE", "title"}, {"DETAILS_DESCRIPTION_LINE_1", "line1"}, @@ -134,6 +135,7 @@ TEST(ScriptParametersTest, SpecialScriptParameters) { EXPECT_THAT( parameters.GetExperiments(), UnorderedElementsAreArray(std::vector<std::string>{"123", "456", "789"})); + EXPECT_THAT(parameters.GetDisableRpcSigning(), Eq(true)); EXPECT_THAT(parameters.GetDetailsShowInitial(), Eq(true)); EXPECT_THAT(parameters.GetDetailsTitle(), Eq("title")); EXPECT_THAT(parameters.GetDetailsDescriptionLine1(), Eq("line1")); diff --git a/chromium/components/autofill_assistant/browser/selector.cc b/chromium/components/autofill_assistant/browser/selector.cc index d890c88829f..c02c6ddcab1 100644 --- a/chromium/components/autofill_assistant/browser/selector.cc +++ b/chromium/components/autofill_assistant/browser/selector.cc @@ -133,6 +133,7 @@ bool operator<(const SelectorProto::Filter& a, const SelectorProto::Filter& b) { case SelectorProto::Filter::kEnterFrame: case SelectorProto::Filter::kLabelled: + case SelectorProto::Filter::kParent: return false; case SelectorProto::Filter::kMatchCssSelector: @@ -391,6 +392,10 @@ std::ostream& operator<<(std::ostream& out, const SelectorProto::Filter& f) { out << f.property(); return out; + case SelectorProto::Filter::kParent: + out << "parent"; + return out; + case SelectorProto::Filter::FILTER_NOT_SET: // Either unset or set to an unsupported value. Let's assume the worse. out << "INVALID"; diff --git a/chromium/components/autofill_assistant/browser/service.proto b/chromium/components/autofill_assistant/browser/service.proto index c96b184deb8..93abc97df67 100644 --- a/chromium/components/autofill_assistant/browser/service.proto +++ b/chromium/components/autofill_assistant/browser/service.proto @@ -18,6 +18,7 @@ import "browser/generic_ui.proto"; import "browser/model.proto"; import "browser/view_layout.proto"; import "content/common/proto/semantic_feature_overrides.proto"; +import "browser/public/external_action.proto"; // A field trial containing the name of the trial and the name of the // randomly selected trial group. @@ -27,7 +28,7 @@ message FieldTrialProto { } // Context contains client environment details. -// Next ID: 20 +// Next ID: 24 message ClientContextProto { message Chrome { optional string chrome_version = 1; @@ -126,7 +127,26 @@ message ClientContextProto { } optional ScreenOrientation screen_orientation = 16; - reserved 19; + // The type of platform the device is running on, e.g. Android, Desktop, or + // iOS. + enum PlatformType { + PLATFORM_TYPE_UNDEFINED = 0; + PLATFORM_TYPE_ANDROID = 1; + PLATFORM_TYPE_IOS = 2; + PLATFORM_TYPE_DESKTOP = 3; + } + optional PlatformType platform_type = 20; + + // The model for semantic dom annotation. + message AnnotateDomModelContextProto { + optional int64 model_version = 1; + } + optional AnnotateDomModelContextProto annotate_dom_model_context = 22; + + // Set if the client has the JS flow library needed for JS flow execution. + optional bool js_flow_library_loaded = 23; + + reserved 19, 21; } // Get the list of scripts that can potentially be run on a url. @@ -230,6 +250,12 @@ message GetUserDataRequestProto { optional bytes client_token = 1; // The list of supported card networks. repeated string supported_card_networks = 2; + // The list of known payment instruments from a previous call. + repeated string preexisting_ids = 3; + } + message AddressRequest { + // The list of known addresses from a previous call. + repeated string preexisting_ids = 1; } // For logging, to know which run this request has originated from. @@ -238,10 +264,10 @@ message GetUserDataRequestProto { optional bool request_name = 2; optional bool request_email = 3; optional bool request_phone = 4; - optional bool request_addresses = 5; + optional AddressRequest request_shipping_addresses = 8; optional PaymentMethodRequest request_payment_methods = 7; - reserved 6; + reserved 5, 6; } // Response with user data. @@ -307,7 +333,7 @@ message OverlayImageProto { optional ClientDimensionProto text_size = 7; } -// Next ID: 25 +// Next ID: 26 message ClientSettingsProto { message IntegrationTestSettings { // Disables animations for the poodle and the progress bar. @@ -488,6 +514,10 @@ message ClientSettingsProto { // wrong and fails with a |TIMED_OUT| error. optional int32 selector_observer_extra_timeout_ms = 24; + // SelectorObserver will wait until no DOM mutation notifications are received + // for this amount of time to check the selectors. + optional int32 selector_observer_debounce_interval_ms = 25; + reserved 8 to 11; } @@ -721,6 +751,11 @@ message ActionsResponseProto { message UpdateScriptListProto { repeated SupportedScriptProto scripts = 1; } optional UpdateScriptListProto update_script_list = 5; + // Needs to be evaluated before each JS flow action. Contains function + // definitions that js flows might call. + // Only set if ClientContextProto::js_flow_library_loaded is false. + optional string js_flow_library = 13; + // Id of the current run. optional uint64 run_id = 12; @@ -1024,6 +1059,9 @@ message ActionProto { UpdateClientSettingsProto update_client_settings = 89; JsFlowProto js_flow = 92; ExecuteJsProto execute_js = 93; + RegisterPasswordResetRequestProto register_password_reset_request = 94; + ExternalActionProto external_action = 95; + SetNativeValueProto set_native_value = 96; } // Set to true to make the client remove any contextual information if the @@ -1125,6 +1163,8 @@ message ProcessedActionProto { JsFlowProto.Result js_flow_result = 37; // Should be set as a result of SaveSubmittedPassword. SaveSubmittedPasswordProto.Result save_submitted_password_result = 38; + // Should be set as a result of an ExternalAction. + ExternalActionProto.Result external_action_result = 39; } // Reports information about navigation that happened while @@ -1211,6 +1251,18 @@ message UnexpectedErrorInfoProto { // JavaScript exception class name, if reporting a JavaScript error. optional string js_exception_classname = 3; + enum JsExceptionLocation { + UNKNOWN = 0; + // Corresponds to ActionsResponseProto::js_flow_library + JS_FLOW_LIBRARY = 1; + // Corresponds to JsFlowProto::js_flow + JS_FLOW = 2; + } + + // The location that caused the JavaScript exception. Guaranteed to have the + // same number of entries as js_exception_line_numbers and + // js_exception_column_numbers. + repeated JsExceptionLocation js_exception_locations = 8 [packed = true]; // JavaScript exception line numbers, within the js snippet that was sent to // devtools runtime by the client, if reporting a JavaScript error. Together // with |js_exception_column_numbers| this forms a stack trace. @@ -1596,6 +1648,9 @@ message SelectorProto { // // This filter replaces |inner_text| and |value|. PropertyFilter property = 14; + + // Retrieve parent of current elements. + EmptyFilter parent = 15; } reserved 10; @@ -2633,7 +2688,14 @@ message CollectUserDataProto { } // Specifies information about the data source to be used. - message DataSource {} + message DataSource { + // If enabled and the user data request fails, fall back to the Chrome + // Autofill data instead where possible. E.g. in WebLayer this setting is + // ignored (Chrome Autofill data is not available). If this is false, the + // action will fail if the user data request fails, even if Chrome Autofill + // data would have been available. + optional bool allow_fallback = 1 [default = true]; + } optional string prompt = 1; // NOTE: The action does not ask separately for billing address. @@ -2924,6 +2986,11 @@ message SaveSubmittedPasswordProto { } } +// Notifies the client that a password reset has been requested. This is +// relevant for analyzing the outcome of password change flows. The action +// fails if login details are not saved in the client's |UserData|. +message RegisterPasswordResetRequestProto {} + // Configures the UI of the autofill assistant client. message ConfigureUiStateProto { enum OverlayBehavior { @@ -3381,6 +3448,39 @@ message JsFlowProto { // field will be empty and the action will return INVALID_ACTION. optional string result_json = 1; } - // The JS flow to execute in a sandbox. + + // The JS flow to execute in a sandbox. If present + // ActionsResponseProto::js_flow_library needs to be evaluated first. optional string js_flow = 1; } + +// Action which forwards a proto to the owner of the |ExternalScriptController| +// for the current flow. Not supported for internal flows. +message ExternalActionProto { + // The opaque proto to be forwarded to the owner of the + // |ExternalScriptController| for this flow. + optional external.ActionInfo info = 1; + + // Whether to show the touchable area in the overlay. + // If true, it will consume the current touchable area. + optional bool show_touchable_area = 2; + + // Whether interrupts should be executed while Autofill Assistant is in prompt + // state. + optional bool allow_interrupt = 3; + + // A condition on the DOM. Whenever its status changes a notification is sent + // to the external caller. + message ExternalCondition { + // The identifier to be used in the external notification. + optional int32 id = 1; + + // The condition to be checked internally. + optional ElementConditionProto element_condition = 2; + } + repeated ExternalCondition conditions = 4; + + message Result { + optional external.ResultInfo result_info = 1; + } +} diff --git a/chromium/components/autofill_assistant/browser/service/java_service.cc b/chromium/components/autofill_assistant/browser/service/java_service.cc index 754cd68e88f..46eb329fda3 100644 --- a/chromium/components/autofill_assistant/browser/service/java_service.cc +++ b/chromium/components/autofill_assistant/browser/service/java_service.cc @@ -94,6 +94,7 @@ void JavaService::GetNextActions( void JavaService::GetUserData(const CollectUserDataOptions& options, uint64_t run_id, + const UserData* user_data, ServiceRequestSender::ResponseCallback callback) { JNIEnv* env = base::android::AttachCurrentThread(); auto jresponse = diff --git a/chromium/components/autofill_assistant/browser/service/java_service.h b/chromium/components/autofill_assistant/browser/service/java_service.h index 471859f40e8..3828fba118d 100644 --- a/chromium/components/autofill_assistant/browser/service/java_service.h +++ b/chromium/components/autofill_assistant/browser/service/java_service.h @@ -60,6 +60,7 @@ class JavaService : public Service { // Get user data. void GetUserData(const CollectUserDataOptions& options, uint64_t run_id, + const UserData* user_data, ServiceRequestSender::ResponseCallback callback) override; private: diff --git a/chromium/components/autofill_assistant/browser/service/java_service_request_sender.cc b/chromium/components/autofill_assistant/browser/service/java_service_request_sender.cc index 8f841ffe132..f2cc520e396 100644 --- a/chromium/components/autofill_assistant/browser/service/java_service_request_sender.cc +++ b/chromium/components/autofill_assistant/browser/service/java_service_request_sender.cc @@ -53,4 +53,6 @@ void JavaServiceRequestSender::OnResponse( std::move(callback_).Run(http_status, response, ResponseInfo{}); } +void JavaServiceRequestSender::SetDisableRpcSigning(bool disable_rpc_signing) {} + } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/service/java_service_request_sender.h b/chromium/components/autofill_assistant/browser/service/java_service_request_sender.h index 95889e536a7..603ca2b8571 100644 --- a/chromium/components/autofill_assistant/browser/service/java_service_request_sender.h +++ b/chromium/components/autofill_assistant/browser/service/java_service_request_sender.h @@ -39,6 +39,8 @@ class JavaServiceRequestSender : public ServiceRequestSender { jint http_status, const base::android::JavaParamRef<jbyteArray>& jresponse); + void SetDisableRpcSigning(bool disable_rpc_signing) override; + private: ResponseCallback callback_; base::android::ScopedJavaGlobalRef<jobject> jservice_request_sender_; diff --git a/chromium/components/autofill_assistant/browser/service/java_test_endpoint_service.cc b/chromium/components/autofill_assistant/browser/service/java_test_endpoint_service.cc index b4020f97bc2..7d04fcaed5a 100644 --- a/chromium/components/autofill_assistant/browser/service/java_test_endpoint_service.cc +++ b/chromium/components/autofill_assistant/browser/service/java_test_endpoint_service.cc @@ -125,8 +125,9 @@ void JavaTestEndpointService::GetNextActions( void JavaTestEndpointService::GetUserData( const CollectUserDataOptions& options, uint64_t run_id, + const UserData* user_data, ServiceRequestSender::ResponseCallback callback) { - service_impl_->GetUserData(options, run_id, std::move(callback)); + service_impl_->GetUserData(options, run_id, user_data, std::move(callback)); } } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/service/java_test_endpoint_service.h b/chromium/components/autofill_assistant/browser/service/java_test_endpoint_service.h index 3e05aa34996..7ec5da033db 100644 --- a/chromium/components/autofill_assistant/browser/service/java_test_endpoint_service.h +++ b/chromium/components/autofill_assistant/browser/service/java_test_endpoint_service.h @@ -49,6 +49,7 @@ class JavaTestEndpointService : public Service { void GetUserData(const CollectUserDataOptions& options, uint64_t run_id, + const UserData* user_data, ServiceRequestSender::ResponseCallback callback) override; private: diff --git a/chromium/components/autofill_assistant/browser/service/mock_service.h b/chromium/components/autofill_assistant/browser/service/mock_service.h index cf6deedf466..02d06509b67 100644 --- a/chromium/components/autofill_assistant/browser/service/mock_service.h +++ b/chromium/components/autofill_assistant/browser/service/mock_service.h @@ -9,6 +9,7 @@ #include <vector> #include "components/autofill_assistant/browser/service/service.h" +#include "components/autofill_assistant/browser/user_data.h" #include "testing/gmock/include/gmock/gmock.h" namespace autofill_assistant { @@ -51,8 +52,21 @@ class MockService : public Service { GetUserData, (const CollectUserDataOptions& options, uint64_t run_id, + const UserData* user_data, ServiceRequestSender::ResponseCallback callback), (override)); + MOCK_METHOD(void, + SetDisableRpcSigning, + (bool disable_rpc_signing), + (override)); + MOCK_METHOD(void, + UpdateAnnotateDomModelContext, + (int64_t model_version), + (override)); + MOCK_METHOD(void, + UpdateJsFlowLibraryLoaded, + (bool js_flow_library_loaded), + (override)); }; } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/service/mock_service_request_sender.h b/chromium/components/autofill_assistant/browser/service/mock_service_request_sender.h index 07b4eb670b8..cf800cbc65b 100644 --- a/chromium/components/autofill_assistant/browser/service/mock_service_request_sender.h +++ b/chromium/components/autofill_assistant/browser/service/mock_service_request_sender.h @@ -31,6 +31,11 @@ class MockServiceRequestSender : public ServiceRequestSender { const std::string& request_body, ResponseCallback& callback, RpcType rpc_type)); + + MOCK_METHOD(void, + SetDisableRpcSigning, + (bool disable_rpc_signing), + (override)); }; } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/service/service.h b/chromium/components/autofill_assistant/browser/service/service.h index d2bf64ee2cc..c879077e095 100644 --- a/chromium/components/autofill_assistant/browser/service/service.h +++ b/chromium/components/autofill_assistant/browser/service/service.h @@ -55,8 +55,15 @@ class Service { // Get user data. virtual void GetUserData(const CollectUserDataOptions& options, uint64_t run_id, + const UserData* user_data, ServiceRequestSender::ResponseCallback callback) = 0; + virtual void SetDisableRpcSigning(bool disable_rpc_signing) {} + + virtual void UpdateAnnotateDomModelContext(int64_t model_version) {} + + virtual void UpdateJsFlowLibraryLoaded(bool js_flow_library_loaded){}; + protected: Service() = default; }; diff --git a/chromium/components/autofill_assistant/browser/service/service_impl.cc b/chromium/components/autofill_assistant/browser/service/service_impl.cc index 323405d3190..00f11d6f8b1 100644 --- a/chromium/components/autofill_assistant/browser/service/service_impl.cc +++ b/chromium/components/autofill_assistant/browser/service/service_impl.cc @@ -144,7 +144,24 @@ void ServiceImpl::GetNextActions( void ServiceImpl::GetUserData(const CollectUserDataOptions& options, uint64_t run_id, + const UserData* user_data, ServiceRequestSender::ResponseCallback callback) { + std::vector<std::string> preexisting_address_ids; + std::vector<std::string> preexisting_payment_instrument_ids; + if (user_data) { + for (const auto& address : user_data->available_addresses_) { + if (address->identifier.has_value()) { + preexisting_address_ids.emplace_back(*address->identifier); + } + } + for (const auto& instrument : user_data->available_payment_instruments_) { + if (instrument->identifier.has_value()) { + preexisting_payment_instrument_ids.emplace_back( + *instrument->identifier); + } + } + } + if (options.request_payment_method) { // We do not cache the payments client token. It could go stale (in practice // it currently doesn't). Getting the token is little overhead. @@ -152,17 +169,18 @@ void ServiceImpl::GetUserData(const CollectUserDataOptions& options, &ServiceImpl::SendUserDataRequest, weak_ptr_factory_.GetWeakPtr(), run_id, options.request_payer_name, options.request_payer_email, options.request_payer_phone || options.request_phone_number_separately, - options.request_shipping, options.request_payment_method, - options.supported_basic_card_networks, std::move(callback))); + options.request_shipping, preexisting_address_ids, + options.request_payment_method, options.supported_basic_card_networks, + preexisting_payment_instrument_ids, std::move(callback))); return; } SendUserDataRequest( run_id, options.request_payer_name, options.request_payer_email, options.request_payer_phone || options.request_phone_number_separately, - options.request_shipping, options.request_payment_method, - options.supported_basic_card_networks, std::move(callback), - std::string()); + options.request_shipping, preexisting_address_ids, + options.request_payment_method, options.supported_basic_card_networks, + preexisting_payment_instrument_ids, std::move(callback), std::string()); } void ServiceImpl::SendUserDataRequest( @@ -171,17 +189,33 @@ void ServiceImpl::SendUserDataRequest( bool request_email, bool request_phone, bool request_shipping, + const std::vector<std::string>& preexisting_address_ids, bool request_payment_methods, const std::vector<std::string>& supported_card_networks, + const std::vector<std::string>& preexisting_payment_instrument_ids, ServiceRequestSender::ResponseCallback callback, const std::string& client_token) { request_sender_->SendRequest( user_data_url_, ProtocolUtils::CreateGetUserDataRequest( run_id, request_name, request_email, request_phone, request_shipping, - request_payment_methods, supported_card_networks, client_token), + preexisting_address_ids, request_payment_methods, + supported_card_networks, preexisting_payment_instrument_ids, + client_token), ServiceRequestSender::AuthMode::OAUTH_STRICT, std::move(callback), RpcType::GET_USER_DATA); } +void ServiceImpl::SetDisableRpcSigning(bool disable_rpc_signing) { + request_sender_->SetDisableRpcSigning(disable_rpc_signing); +} + +void ServiceImpl::UpdateAnnotateDomModelContext(int64_t model_version) { + client_context_->UpdateAnnotateDomModelContext(model_version); +} + +void ServiceImpl::UpdateJsFlowLibraryLoaded(const bool js_flow_library_loaded) { + client_context_->UpdateJsFlowLibraryLoaded(js_flow_library_loaded); +} + } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/service/service_impl.h b/chromium/components/autofill_assistant/browser/service/service_impl.h index 8eab9725a26..aaadc0b67c2 100644 --- a/chromium/components/autofill_assistant/browser/service/service_impl.h +++ b/chromium/components/autofill_assistant/browser/service/service_impl.h @@ -10,6 +10,7 @@ #include <vector> #include "base/callback.h" +#include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "components/autofill_assistant/browser/client_context.h" #include "components/autofill_assistant/browser/device_context.h" @@ -18,6 +19,7 @@ #include "components/autofill_assistant/browser/service/server_url_fetcher.h" #include "components/autofill_assistant/browser/service/service.h" #include "components/autofill_assistant/browser/service/service_request_sender.h" +#include "components/autofill_assistant/browser/user_data.h" #include "components/signin/public/identity_manager/access_token_fetcher.h" #include "google_apis/gaia/google_service_auth_error.h" #include "services/network/public/cpp/simple_url_loader.h" @@ -87,8 +89,15 @@ class ServiceImpl : public Service { void GetUserData(const CollectUserDataOptions& options, uint64_t run_id, + const UserData* user_data, ServiceRequestSender::ResponseCallback callback) override; + void SetDisableRpcSigning(bool disable_rpc_signing) override; + + void UpdateAnnotateDomModelContext(int64_t model_version) override; + + void UpdateJsFlowLibraryLoaded(bool js_flow_library_loaded) override; + private: void SendUserDataRequest( uint64_t run_id, @@ -96,12 +105,14 @@ class ServiceImpl : public Service { bool request_email, bool request_phone, bool request_shipping, + const std::vector<std::string>& preexisting_address_ids, bool request_payment_methods, const std::vector<std::string>& supported_card_networks, + const std::vector<std::string>& preexisting_payment_instrument_ids, ServiceRequestSender::ResponseCallback callback, const std::string& client_token); - Client* const client_; + const raw_ptr<Client> client_; // The request sender responsible for communicating with a remote endpoint. std::unique_ptr<ServiceRequestSender> request_sender_; diff --git a/chromium/components/autofill_assistant/browser/service/service_impl_unittest.cc b/chromium/components/autofill_assistant/browser/service/service_impl_unittest.cc index b253073c812..860db4aa160 100644 --- a/chromium/components/autofill_assistant/browser/service/service_impl_unittest.cc +++ b/chromium/components/autofill_assistant/browser/service/service_impl_unittest.cc @@ -40,8 +40,12 @@ std::string ExpectedGetUserDataRequestBody(uint64_t run_id, return ProtocolUtils::CreateGetUserDataRequest( run_id, /* request_name= */ false, /* request_email= */ false, /* request_phone= */ false, - /* request_shipping= */ false, request_payment_methods, - /* supported_card_networks= */ {}, client_token); + /* request_shipping= */ false, + /* preexisting_address_ids= */ std::vector<std::string>(), + request_payment_methods, + /* supported_card_networks= */ std::vector<std::string>(), + /* preexisting_payment_instrument_ids= */ std::vector<std::string>(), + client_token); } class ServiceImplTest : public testing::Test { @@ -226,7 +230,8 @@ TEST_F(ServiceImplTest, GetUserDataWithPayments) { EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, std::string("response"), _)); - service_->GetUserData(options, run_id, mock_response_callback_.Get()); + service_->GetUserData(options, run_id, /* user_data= */ nullptr, + mock_response_callback_.Get()); } TEST_F(ServiceImplTest, GetUserDataWithoutPayments) { @@ -237,7 +242,7 @@ TEST_F(ServiceImplTest, GetUserDataWithoutPayments) { EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kUserDataServerUrl), ExpectedGetUserDataRequestBody( - run_id, /* client_token= */ "", + run_id, /* client_token= */ std::string(), options.request_payment_method), _, RpcType::GET_USER_DATA)) .WillOnce(RunOnceCallback<2>(net::HTTP_OK, std::string("response"), @@ -245,7 +250,18 @@ TEST_F(ServiceImplTest, GetUserDataWithoutPayments) { EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, std::string("response"), _)); - service_->GetUserData(options, run_id, mock_response_callback_.Get()); + service_->GetUserData(options, run_id, /* user_data= */ nullptr, + mock_response_callback_.Get()); +} + +TEST_F(ServiceImplTest, UpdateAnnotateDomModelService) { + EXPECT_CALL(*mock_client_context_, UpdateAnnotateDomModelContext(123456)); + service_->UpdateAnnotateDomModelContext(123456); +} + +TEST_F(ServiceImplTest, UpdateJsFlowLibraryLoaded) { + EXPECT_CALL(*mock_client_context_, UpdateJsFlowLibraryLoaded(true)); + service_->UpdateJsFlowLibraryLoaded(true); } } // namespace diff --git a/chromium/components/autofill_assistant/browser/service/service_request_sender.h b/chromium/components/autofill_assistant/browser/service/service_request_sender.h index 4df58e823be..8825abf13c7 100644 --- a/chromium/components/autofill_assistant/browser/service/service_request_sender.h +++ b/chromium/components/autofill_assistant/browser/service/service_request_sender.h @@ -19,7 +19,7 @@ class ServiceRequestSender { struct ResponseInfo { // The number of bytes transmitted over the network, before decoding. Can be // -1 in case of interrupted downloads. - size_t encoded_body_length = 0; + int64_t encoded_body_length = 0; }; using ResponseCallback = @@ -47,6 +47,8 @@ class ServiceRequestSender { AuthMode auth_mode, ResponseCallback response_callback, RpcType rpc_type) = 0; + + virtual void SetDisableRpcSigning(bool disable_rpc_signing) = 0; }; } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/service/service_request_sender_impl.cc b/chromium/components/autofill_assistant/browser/service/service_request_sender_impl.cc index 44b28e98196..866b0019741 100644 --- a/chromium/components/autofill_assistant/browser/service/service_request_sender_impl.cc +++ b/chromium/components/autofill_assistant/browser/service/service_request_sender_impl.cc @@ -49,6 +49,7 @@ constexpr int kMaxRetriesGetUserData = 2; void OnURLLoaderComplete( autofill_assistant::ServiceRequestSender::ResponseCallback callback, std::unique_ptr<::network::SimpleURLLoader> loader, + int max_retries, std::unique_ptr<std::string> response_body) { std::string response_str; if (response_body != nullptr) { @@ -56,16 +57,24 @@ void OnURLLoaderComplete( } int response_code = 0; + if (loader->ResponseInfo() && loader->ResponseInfo()->headers) { + response_code = loader->ResponseInfo()->headers->response_code(); + } + autofill_assistant::ServiceRequestSender::ResponseInfo response_info; - if (loader->ResponseInfo()) { + if (loader->CompletionStatus().has_value()) { response_info.encoded_body_length = - loader->ResponseInfo()->encoded_body_length; - if (loader->ResponseInfo()->headers) { - response_code = loader->ResponseInfo()->headers->response_code(); - } + loader->CompletionStatus()->encoded_body_length; } + VLOG(3) << "Received response: status=" << response_code << ", " - << response_str.length() << " bytes"; + << "encoded: " << response_info.encoded_body_length << " bytes, " + << "decoded: " << response_str.length() << " bytes"; + + if (max_retries > 0) { + autofill_assistant::Metrics::RecordServiceRequestRetryCount( + loader->GetNumRetries(), response_code == net::HTTP_OK); + } std::move(callback).Run(response_code, response_str, response_info); } @@ -90,7 +99,8 @@ void SendRequestImpl( if (max_retries > 0) { loader->SetRetryOptions( max_retries, network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE | - network::SimpleURLLoader::RETRY_ON_NAME_NOT_RESOLVED); + network::SimpleURLLoader::RETRY_ON_NAME_NOT_RESOLVED | + network::SimpleURLLoader::RETRY_ON_5XX); } loader->AttachStringForUpload(request_body, "application/x-protobuffer"); #ifndef NDEBUG @@ -102,7 +112,7 @@ void SendRequestImpl( ->GetURLLoaderFactoryForBrowserProcess() .get(), base::BindOnce(&OnURLLoaderComplete, std::move(callback), - std::move(loader))); + std::move(loader), max_retries)); } void SendRequestNoAuth( @@ -206,7 +216,7 @@ void ServiceRequestSenderImpl::SendRequest( max_retries = kMaxRetriesGetUserData; } - if (!cup::IsRpcTypeSupported(rpc_type)) { + if (!cup::IsRpcTypeSupported(rpc_type) || disable_rpc_signing_) { InternalSendRequest(url, request_body, auth_mode, max_retries, std::move(callback)); return; @@ -340,4 +350,8 @@ bool ServiceRequestSenderImpl::OAuthEnabled( !failed_to_fetch_oauth_token_); } +void ServiceRequestSenderImpl::SetDisableRpcSigning(bool disable_rpc_signing) { + disable_rpc_signing_ = disable_rpc_signing; +} + } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/service/service_request_sender_impl.h b/chromium/components/autofill_assistant/browser/service/service_request_sender_impl.h index 37827b61708..7bf23fe4698 100644 --- a/chromium/components/autofill_assistant/browser/service/service_request_sender_impl.h +++ b/chromium/components/autofill_assistant/browser/service/service_request_sender_impl.h @@ -56,6 +56,11 @@ class ServiceRequestSenderImpl : public ServiceRequestSender { ResponseCallback callback, RpcType rpc_type) override; + // Sets the value of the |disable_rpc_signing| field. If |true| CUP signing + // and verification will be bypassed even if it is enabled and the RPC type + // supported. Intended for internal use only. + void SetDisableRpcSigning(bool disable_rpc_signing) override; + private: // Unlike |ServiceRequestSenderImpl::SendRequest|, assumes that any necessary // CUP signing and validation is already done or accounted for in the @@ -101,6 +106,9 @@ class ServiceRequestSenderImpl : public ServiceRequestSender { // API key to add to the URL of unauthenticated requests. std::string api_key_; + // Disable CUP RPC signing. Intended for internal use only. + bool disable_rpc_signing_ = false; + // Getting the OAuth token failed. For requests with auth mode allowing to // fall back to API key, it will not be retried. For requests forcing auth, // the OAuth token will tried to be re-fetched. diff --git a/chromium/components/autofill_assistant/browser/service/service_request_sender_impl_unittest.cc b/chromium/components/autofill_assistant/browser/service/service_request_sender_impl_unittest.cc index 401eede7912..c2a296f6596 100644 --- a/chromium/components/autofill_assistant/browser/service/service_request_sender_impl_unittest.cc +++ b/chromium/components/autofill_assistant/browser/service/service_request_sender_impl_unittest.cc @@ -34,9 +34,12 @@ namespace autofill_assistant { using ::base::test::RunOnceCallback; +using ::network::URLLoaderCompletionStatus; using ::testing::_; +using ::testing::Field; using ::testing::NiceMock; using ::testing::Return; +using ::testing::ReturnRef; namespace { @@ -66,6 +69,8 @@ class ServiceRequestSenderImplTest : public testing::Test { content::TestBrowserContext context_; NiceMock<MockAccessTokenFetcher> mock_access_token_fetcher_; + absl::optional<URLLoaderCompletionStatus> completion_status_ = absl::nullopt; + void InitCupFeatures(bool enableSigning, bool enableVerifying) { std::vector<base::Feature> enabled_features; std::vector<base::Feature> disabled_features; @@ -115,6 +120,8 @@ TEST_F(ServiceRequestSenderImplTest, SendUnauthenticatedRequest) { .WillOnce(RunOnceCallback<1>(std::make_unique<std::string>("response"))); EXPECT_CALL(*loader, ResponseInfo) .WillRepeatedly(Return(response_info.get())); + EXPECT_CALL(*loader, CompletionStatus) + .WillRepeatedly(ReturnRef(completion_status_)); EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, "response", _)); ServiceRequestSenderImpl request_sender{ @@ -151,6 +158,8 @@ TEST_F(ServiceRequestSenderImplTest, SendAuthenticatedRequest) { .WillOnce(RunOnceCallback<1>(std::make_unique<std::string>("response"))); EXPECT_CALL(*loader, ResponseInfo) .WillRepeatedly(Return(response_info.get())); + EXPECT_CALL(*loader, CompletionStatus) + .WillRepeatedly(ReturnRef(completion_status_)); EXPECT_CALL(mock_access_token_fetcher_, OnFetchAccessToken) .WillOnce(RunOnceCallback<0>(true, "access_token")); EXPECT_CALL(mock_access_token_fetcher_, InvalidateAccessToken).Times(0); @@ -187,7 +196,8 @@ TEST_F(ServiceRequestSenderImplTest, ForceAuthenticatedRequest) { EXPECT_CALL(*loader, SetRetryOptions( 2, network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE | - network::SimpleURLLoader::RETRY_ON_NAME_NOT_RESOLVED)); + network::SimpleURLLoader::RETRY_ON_NAME_NOT_RESOLVED | + network::SimpleURLLoader::RETRY_ON_5XX)); EXPECT_CALL(*loader, AttachStringForUpload(std::string("request"), std::string("application/x-protobuffer"))); @@ -195,6 +205,8 @@ TEST_F(ServiceRequestSenderImplTest, ForceAuthenticatedRequest) { .WillOnce(RunOnceCallback<1>(std::make_unique<std::string>("response"))); EXPECT_CALL(*loader, ResponseInfo) .WillRepeatedly(Return(response_info.get())); + EXPECT_CALL(*loader, CompletionStatus) + .WillRepeatedly(ReturnRef(completion_status_)); EXPECT_CALL(mock_access_token_fetcher_, OnFetchAccessToken) .WillOnce(RunOnceCallback<0>(true, "access_token")); EXPECT_CALL(mock_access_token_fetcher_, InvalidateAccessToken).Times(0); @@ -238,6 +250,8 @@ TEST_F(ServiceRequestSenderImplTest, auto response_info = CreateResponseInfo(net::HTTP_OK, "OK"); EXPECT_CALL(*loader, ResponseInfo) .WillRepeatedly(Return(response_info.get())); + EXPECT_CALL(*loader, CompletionStatus) + .WillRepeatedly(ReturnRef(completion_status_)); EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, "response", _)); ServiceRequestSenderImpl request_sender{ @@ -278,6 +292,8 @@ TEST_F(ServiceRequestSenderImplTest, auto response_info = CreateResponseInfo(net::HTTP_OK, "OK"); EXPECT_CALL(*loader, ResponseInfo) .WillRepeatedly(Return(response_info.get())); + EXPECT_CALL(*loader, CompletionStatus) + .WillRepeatedly(ReturnRef(completion_status_)); EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, "response", _)); ServiceRequestSenderImpl request_sender{ @@ -304,6 +320,8 @@ TEST_F(ServiceRequestSenderImplTest, EXPECT_CALL(*loader, DownloadToStringOfUnboundedSizeUntilCrashAndDie) .Times(0); EXPECT_CALL(*loader, ResponseInfo).Times(0); + EXPECT_CALL(*loader, CompletionStatus) + .WillRepeatedly(ReturnRef(completion_status_)); EXPECT_CALL(mock_access_token_fetcher_, OnFetchAccessToken) .WillOnce(RunOnceCallback<0>(false, /*access_token = */ "")); EXPECT_CALL(mock_access_token_fetcher_, InvalidateAccessToken).Times(0); @@ -345,6 +363,8 @@ TEST_F(ServiceRequestSenderImplTest, SignsGetActionsRequestWhenFeatureEnabled) { .WillOnce(RunOnceCallback<1>(std::make_unique<std::string>("response"))); EXPECT_CALL(*loader, ResponseInfo) .WillRepeatedly(Return(response_info.get())); + EXPECT_CALL(*loader, CompletionStatus) + .WillRepeatedly(ReturnRef(completion_status_)); EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, "response", _)); EXPECT_CALL(*cup_factory, @@ -389,6 +409,8 @@ TEST_F(ServiceRequestSenderImplTest, ValidatesGetActionsResponsesWhenEnabled) { RunOnceCallback<1>(std::make_unique<std::string>("packed_response"))); EXPECT_CALL(*loader, ResponseInfo) .WillRepeatedly(Return(response_info.get())); + EXPECT_CALL(*loader, CompletionStatus) + .WillRepeatedly(ReturnRef(completion_status_)); EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, "response", _)); EXPECT_CALL(*cup_factory, @@ -429,6 +451,8 @@ TEST_F(ServiceRequestSenderImplTest, RecordsCupSigningDisabledEvent) { .WillOnce(RunOnceCallback<1>(std::make_unique<std::string>("response"))); EXPECT_CALL(*loader, ResponseInfo) .WillRepeatedly(Return(response_info.get())); + EXPECT_CALL(*loader, CompletionStatus) + .WillRepeatedly(ReturnRef(completion_status_)); EXPECT_CALL(mock_access_token_fetcher_, OnFetchAccessToken) .WillOnce(RunOnceCallback<0>(true, "access_token")); @@ -466,6 +490,8 @@ TEST_F(ServiceRequestSenderImplTest, RecordsCupVerificationDisabledEvent) { .WillOnce(RunOnceCallback<1>(std::make_unique<std::string>("response"))); EXPECT_CALL(*loader, ResponseInfo) .WillRepeatedly(Return(response_info.get())); + EXPECT_CALL(*loader, CompletionStatus) + .WillRepeatedly(ReturnRef(completion_status_)); EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, "response", _)); EXPECT_CALL(*cup_factory, @@ -507,6 +533,8 @@ TEST_F(ServiceRequestSenderImplTest, RecordsHttpFailureEventWithCupEnabled) { RunOnceCallback<1>(std::make_unique<std::string>("packed_response"))); EXPECT_CALL(*loader, ResponseInfo) .WillRepeatedly(Return(response_info.get())); + EXPECT_CALL(*loader, CompletionStatus) + .WillRepeatedly(ReturnRef(completion_status_)); EXPECT_CALL(mock_response_callback_, Run(net::HTTP_NOT_FOUND, "", _)); EXPECT_CALL(*cup_factory, CreateInstance).WillOnce([&]() { return std::move(cup); @@ -549,6 +577,8 @@ TEST_F(ServiceRequestSenderImplTest, RecordsHttpFailureEventWithCupDisabled) { RunOnceCallback<1>(std::make_unique<std::string>("packed_response"))); EXPECT_CALL(*loader, ResponseInfo) .WillRepeatedly(Return(response_info.get())); + EXPECT_CALL(*loader, CompletionStatus) + .WillRepeatedly(ReturnRef(completion_status_)); EXPECT_CALL(mock_response_callback_, Run(net::HTTP_NOT_FOUND, "", _)); EXPECT_CALL(*cup_factory, CreateInstance).WillOnce([&]() { return std::move(cup); @@ -590,6 +620,8 @@ TEST_F(ServiceRequestSenderImplTest, RunOnceCallback<1>(std::make_unique<std::string>("packed_response"))); EXPECT_CALL(*loader, ResponseInfo) .WillRepeatedly(Return(response_info.get())); + EXPECT_CALL(*loader, CompletionStatus) + .WillRepeatedly(ReturnRef(completion_status_)); EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, "response", _)); EXPECT_CALL(*cup_factory, CreateInstance).WillOnce([&]() { return std::move(cup); @@ -640,6 +672,8 @@ TEST_F(ServiceRequestSenderImplTest, DoesNotRecordCupEventForNonSupportedRpcs) { .WillOnce(RunOnceCallback<1>(std::make_unique<std::string>("response"))); EXPECT_CALL(*loader, ResponseInfo) .WillRepeatedly(Return(response_info.get())); + EXPECT_CALL(*loader, CompletionStatus) + .WillRepeatedly(ReturnRef(completion_status_)); EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, "response", _)); ServiceRequestSenderImpl request_sender{ @@ -663,6 +697,160 @@ TEST_F(ServiceRequestSenderImplTest, DoesNotRecordCupEventForNonSupportedRpcs) { Metrics::CupRpcVerificationEvent::VERIFICATION_DISABLED, 0); } +TEST_F(ServiceRequestSenderImplTest, + DoesNotPerformCupSigningIfRpcSigningDisabled) { + InitCupFeatures(true, true); + auto loader_factory = + std::make_unique<NiceMock<MockSimpleURLLoaderFactory>>(); + auto loader = std::make_unique<NiceMock<MockURLLoader>>(); + auto response_info = CreateResponseInfo(net::HTTP_OK, "OK"); + EXPECT_CALL(*loader_factory, OnCreateLoader) + .WillOnce([&](::network::ResourceRequest* resource_request, + const ::net::NetworkTrafficAnnotationTag& annotation_tag) { + return std::move(loader); + }); + EXPECT_CALL(*loader, DownloadToStringOfUnboundedSizeUntilCrashAndDie) + .WillOnce(RunOnceCallback<1>(std::make_unique<std::string>("response"))); + EXPECT_CALL(*loader, ResponseInfo) + .WillRepeatedly(Return(response_info.get())); + EXPECT_CALL(*loader, CompletionStatus) + .WillRepeatedly(ReturnRef(completion_status_)); + EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, "response", _)); + + auto cup_factory = + std::make_unique<NiceMock<autofill_assistant::cup::MockCUPFactory>>(); + EXPECT_CALL(*cup_factory, CreateInstance).Times(0); + + ServiceRequestSenderImpl request_sender{ + &context_, + /* access_token_fetcher = */ nullptr, std::move(cup_factory), + std::move(loader_factory), std::string("fake_api_key")}; + request_sender.SetDisableRpcSigning(true); + request_sender.SendRequest( + GURL("https://www.example.com"), std::string("request"), + ServiceRequestSender::AuthMode::API_KEY, mock_response_callback_.Get(), + autofill_assistant::RpcType::GET_ACTIONS); +} + +TEST_F(ServiceRequestSenderImplTest, TestRetryLoggingForGetUserData) { + base::HistogramTester histogram_tester; + auto loader_factory = + std::make_unique<NiceMock<MockSimpleURLLoaderFactory>>(); + auto loader = std::make_unique<NiceMock<MockURLLoader>>(); + auto* loader_ptr = loader.get(); + auto response_info = CreateResponseInfo(net::HTTP_OK, "OK"); + EXPECT_CALL(*loader_factory, OnCreateLoader) + .WillOnce([&](::network::ResourceRequest* resource_request, + const ::net::NetworkTrafficAnnotationTag& annotation_tag) { + return std::move(loader); + }); + EXPECT_CALL(*loader_ptr, DownloadToStringOfUnboundedSizeUntilCrashAndDie) + .WillOnce(RunOnceCallback<1>(std::make_unique<std::string>("response"))); + EXPECT_CALL(*loader_ptr, ResponseInfo) + .WillRepeatedly(Return(response_info.get())); + EXPECT_CALL(*loader, CompletionStatus) + .WillRepeatedly(ReturnRef(completion_status_)); + EXPECT_CALL(*loader_ptr, GetNumRetries).WillOnce(Return(1)); + EXPECT_CALL(mock_access_token_fetcher_, OnFetchAccessToken) + .WillOnce(RunOnceCallback<0>(true, "access_token")); + EXPECT_CALL(mock_access_token_fetcher_, InvalidateAccessToken).Times(0); + EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, "response", _)); + + ServiceRequestSenderImpl request_sender{ + &context_, &mock_access_token_fetcher_, /* cup_factory= */ nullptr, + std::move(loader_factory), + /* api_key= */ std::string()}; + request_sender.SendRequest(GURL("https://www.example.com"), + std::string("request"), + ServiceRequestSender::AuthMode::OAUTH_STRICT, + mock_response_callback_.Get(), + autofill_assistant::RpcType::GET_USER_DATA); + + histogram_tester.ExpectBucketCount( + "Android.AutofillAssistant.ServiceRequestSender.SuccessRetryCount", 1, 1); +} + +TEST_F(ServiceRequestSenderImplTest, TestNoRetryLoggingForSupportsScripts) { + base::HistogramTester histogram_tester; + auto loader_factory = + std::make_unique<NiceMock<MockSimpleURLLoaderFactory>>(); + auto loader = std::make_unique<NiceMock<MockURLLoader>>(); + auto* loader_ptr = loader.get(); + auto response_info = CreateResponseInfo(net::HTTP_OK, "OK"); + EXPECT_CALL(*loader_factory, OnCreateLoader) + .WillOnce([&](::network::ResourceRequest* resource_request, + const ::net::NetworkTrafficAnnotationTag& annotation_tag) { + return std::move(loader); + }); + EXPECT_CALL(*loader_ptr, DownloadToStringOfUnboundedSizeUntilCrashAndDie) + .WillOnce(RunOnceCallback<1>(std::make_unique<std::string>("response"))); + EXPECT_CALL(*loader_ptr, ResponseInfo) + .WillRepeatedly(Return(response_info.get())); + EXPECT_CALL(*loader, CompletionStatus) + .WillRepeatedly(ReturnRef(completion_status_)); + EXPECT_CALL(*loader_ptr, GetNumRetries).Times(0); + EXPECT_CALL(mock_access_token_fetcher_, OnFetchAccessToken) + .WillOnce(RunOnceCallback<0>(true, "access_token")); + EXPECT_CALL(mock_access_token_fetcher_, InvalidateAccessToken).Times(0); + EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, "response", _)); + + ServiceRequestSenderImpl request_sender{ + &context_, &mock_access_token_fetcher_, /* cup_factory= */ nullptr, + std::move(loader_factory), + /* api_key= */ std::string()}; + request_sender.SendRequest( + GURL("https://www.example.com"), std::string("request"), + ServiceRequestSender::AuthMode::OAUTH_WITH_API_KEY_FALLBACK, + mock_response_callback_.Get(), + autofill_assistant::RpcType::SUPPORTS_SCRIPT); + + histogram_tester.ExpectTotalCount( + "Android.AutofillAssistant.ServiceRequestSender.SuccessRetryCount", 0); + histogram_tester.ExpectTotalCount( + "Android.AutofillAssistant.ServiceRequestSender.FailureRetryCount", 0); +} + +TEST_F(ServiceRequestSenderImplTest, EncodedBodyLengthSet) { + auto cup_factory = + std::make_unique<NiceMock<autofill_assistant::cup::MockCUPFactory>>(); + auto loader_factory = + std::make_unique<NiceMock<MockSimpleURLLoaderFactory>>(); + auto loader = std::make_unique<NiceMock<MockURLLoader>>(); + auto response_info = CreateResponseInfo(net::HTTP_OK, "OK"); + EXPECT_CALL(*loader_factory, OnCreateLoader) + .WillOnce([&](::network::ResourceRequest* resource_request, + const ::net::NetworkTrafficAnnotationTag& annotation_tag) { + return std::move(loader); + }); + + EXPECT_CALL(*loader, + AttachStringForUpload(std::string("request"), + std::string("application/x-protobuffer"))); + EXPECT_CALL(*loader, DownloadToStringOfUnboundedSizeUntilCrashAndDie) + .WillOnce(RunOnceCallback<1>(std::make_unique<std::string>("response"))); + EXPECT_CALL(*loader, ResponseInfo) + .WillRepeatedly(Return(response_info.get())); + + auto completion_status = absl::make_optional<URLLoaderCompletionStatus>(); + completion_status->encoded_body_length = 1337; + EXPECT_CALL(*loader, CompletionStatus) + .WillRepeatedly(ReturnRef(completion_status)); + + EXPECT_CALL( + mock_response_callback_, + Run(_, _, + Field(&ServiceRequestSender::ResponseInfo::encoded_body_length, + 1337))); + ServiceRequestSenderImpl request_sender{ + &context_, + /* access_token_fetcher = */ nullptr, std::move(cup_factory), + std::move(loader_factory), std::string("fake_api_key")}; + request_sender.SendRequest( + GURL("https://www.example.com"), std::string("request"), + ServiceRequestSender::AuthMode::API_KEY, mock_response_callback_.Get(), + RpcType::GET_TRIGGER_SCRIPTS); +} + // TODO(b/170934170): Add tests for full unit test coverage of // service_request_sender. diff --git a/chromium/components/autofill_assistant/browser/service/service_request_sender_local_impl.cc b/chromium/components/autofill_assistant/browser/service/service_request_sender_local_impl.cc index ea6402c445b..b15fb855c45 100644 --- a/chromium/components/autofill_assistant/browser/service/service_request_sender_local_impl.cc +++ b/chromium/components/autofill_assistant/browser/service/service_request_sender_local_impl.cc @@ -25,4 +25,7 @@ void ServiceRequestSenderLocalImpl::SendRequest( /* response_info = */ {}); } +void ServiceRequestSenderLocalImpl::SetDisableRpcSigning( + bool disable_rpc_signing) {} + } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/service/service_request_sender_local_impl.h b/chromium/components/autofill_assistant/browser/service/service_request_sender_local_impl.h index add70eaef8c..07c74fea418 100644 --- a/chromium/components/autofill_assistant/browser/service/service_request_sender_local_impl.h +++ b/chromium/components/autofill_assistant/browser/service/service_request_sender_local_impl.h @@ -26,6 +26,8 @@ class ServiceRequestSenderLocalImpl : public ServiceRequestSender { ResponseCallback callback, RpcType rpc_type) override; + void SetDisableRpcSigning(bool disable_rpc_signing) override; + private: std::string response_; }; diff --git a/chromium/components/autofill_assistant/browser/starter.cc b/chromium/components/autofill_assistant/browser/starter.cc index aa0a5512473..2139db318be 100644 --- a/chromium/components/autofill_assistant/browser/starter.cc +++ b/chromium/components/autofill_assistant/browser/starter.cc @@ -30,7 +30,6 @@ #include "components/autofill_assistant/browser/trigger_scripts/dynamic_trigger_conditions.h" #include "components/autofill_assistant/browser/trigger_scripts/static_trigger_conditions.h" #include "components/autofill_assistant/browser/url_utils.h" -#include "components/ukm/content/source_url_recorder.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" @@ -180,7 +179,7 @@ Starter::Starter(content::WebContents* web_contents, : content::WebContentsObserver(web_contents), content::WebContentsUserData<Starter>(*web_contents), current_ukm_source_id_( - ukm::GetSourceIdForWebContentsDocument(web_contents)), + web_contents->GetPrimaryMainFrame()->GetPageUkmSourceId()), cached_failed_trigger_script_fetches_( GetOrCreateFailedTriggerScriptFetchesCache()), user_denylisted_domains_(kMaxUserDenylistedCacheSize), @@ -426,7 +425,7 @@ void Starter::Init() { fetch_trigger_scripts_on_navigation_) { MaybeStartImplicitlyForUrl( web_contents()->GetLastCommittedURL(), - ukm::GetSourceIdForWebContentsDocument(web_contents())); + web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId()); } } @@ -471,7 +470,13 @@ void Starter::Start(std::unique_ptr<TriggerContext> trigger_context) { CancelPendingStartup(Metrics::TriggerScriptFinishedState::CANCELED); pending_trigger_context_ = std::move(trigger_context); if (!platform_delegate_->IsAttached()) { - OnStartDone(/* start_regular_script = */ false); + OnStartDone(/* start_script= */ false); + return; + } + + if (platform_delegate_->GetIsSupervisedUser()) { + OnStartDone(/* start_script= */ false); + return; } if (base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( @@ -508,7 +513,7 @@ void Starter::Start(std::unique_ptr<TriggerContext> trigger_context) { if (IsTriggerScriptContext(*pending_trigger_context_) && !url_utils::IsSamePublicSuffixDomain( - web_contents()->GetMainFrame()->GetLastCommittedURL(), + web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL(), startup_url.value_or(GURL()))) { waiting_for_deeplink_navigation_ = true; return; @@ -847,6 +852,14 @@ void Starter::DeleteTriggerScriptCoordinator() { trigger_script_coordinator_.reset(); } +const CommonDependencies* Starter::GetCommonDependencies() { + return platform_delegate_->GetCommonDependencies(); +} + +const PlatformDependencies* Starter::GetPlatformDependencies() { + return platform_delegate_->GetPlatformDependencies(); +} + base::WeakPtr<Starter> Starter::GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); } diff --git a/chromium/components/autofill_assistant/browser/starter.h b/chromium/components/autofill_assistant/browser/starter.h index e5f7cec6bf0..795821eca5a 100644 --- a/chromium/components/autofill_assistant/browser/starter.h +++ b/chromium/components/autofill_assistant/browser/starter.h @@ -14,8 +14,10 @@ #include "base/memory/weak_ptr.h" #include "base/time/tick_clock.h" #include "base/time/time.h" +#include "components/autofill_assistant/browser/common_dependencies.h" #include "components/autofill_assistant/browser/controller.h" #include "components/autofill_assistant/browser/metrics.h" +#include "components/autofill_assistant/browser/platform_dependencies.h" #include "components/autofill_assistant/browser/public/runtime_manager.h" #include "components/autofill_assistant/browser/service.pb.h" #include "components/autofill_assistant/browser/starter_heuristic.h" @@ -81,6 +83,9 @@ class Starter : public content::WebContentsObserver, // When the activity is changed on Android. void OnDependenciesInvalidated(); + const CommonDependencies* GetCommonDependencies(); + const PlatformDependencies* GetPlatformDependencies(); + base::WeakPtr<Starter> GetWeakPtr(); private: diff --git a/chromium/components/autofill_assistant/browser/starter_heuristic.cc b/chromium/components/autofill_assistant/browser/starter_heuristic.cc index 9c1482e991c..872876f8a8e 100644 --- a/chromium/components/autofill_assistant/browser/starter_heuristic.cc +++ b/chromium/components/autofill_assistant/browser/starter_heuristic.cc @@ -74,8 +74,8 @@ void StarterHeuristic::InitFromTrialParams() { return; } url_matcher::URLMatcherConditionSet::Vector condition_sets; - base::flat_map<url_matcher::URLMatcherConditionSet::ID, std::string> mapping; - url_matcher::URLMatcherConditionSet::ID next_condition_set_id = 0; + base::flat_map<base::MatcherStringPattern::ID, std::string> mapping; + base::MatcherStringPattern::ID next_condition_set_id = 0; for (const auto& heuristic : heuristics->GetListDeprecated()) { auto* intent = heuristic.FindKeyOfType(kHeuristicIntentKey, base::Value::Type::STRING); @@ -87,11 +87,9 @@ void StarterHeuristic::InitFromTrialParams() { } std::string error; - const auto& url_conditions_dict = - base::Value::AsDictionaryValue(*url_conditions); condition_sets.emplace_back( url_matcher::URLMatcherFactory::CreateFromURLFilterDictionary( - url_matcher_.condition_factory(), &url_conditions_dict, + url_matcher_.condition_factory(), url_conditions->GetDict(), next_condition_set_id, &error)); if (!error.empty()) { VLOG(1) << "Error pasing url conditions: " << error; @@ -132,8 +130,7 @@ base::flat_set<std::string> StarterHeuristic::IsHeuristicMatch( return matching_intents; } - std::set<url_matcher::URLMatcherConditionSet::ID> matches = - url_matcher_.MatchURL(url); + std::set<base::MatcherStringPattern::ID> matches = url_matcher_.MatchURL(url); for (const auto& match : matches) { auto intent = matcher_id_to_intent_map_.find(match); if (intent == matcher_id_to_intent_map_.end()) { diff --git a/chromium/components/autofill_assistant/browser/starter_heuristic.h b/chromium/components/autofill_assistant/browser/starter_heuristic.h index 52d5e40e0c6..aa0e602664e 100644 --- a/chromium/components/autofill_assistant/browser/starter_heuristic.h +++ b/chromium/components/autofill_assistant/browser/starter_heuristic.h @@ -62,7 +62,7 @@ class StarterHeuristic : public base::RefCountedThreadSafe<StarterHeuristic> { // Arbitrary mapping of matcher IDs to intent strings. This mapping is built // dynamically to allow the heuristic to work on intents that are otherwise // unknown to the client. - base::flat_map<url_matcher::URLMatcherConditionSet::ID, std::string> + base::flat_map<base::MatcherStringPattern::ID, std::string> matcher_id_to_intent_map_; }; diff --git a/chromium/components/autofill_assistant/browser/starter_platform_delegate.h b/chromium/components/autofill_assistant/browser/starter_platform_delegate.h index 78108ba2e39..8c91b2bc56c 100644 --- a/chromium/components/autofill_assistant/browser/starter_platform_delegate.h +++ b/chromium/components/autofill_assistant/browser/starter_platform_delegate.h @@ -7,8 +7,10 @@ #include "base/callback_forward.h" #include "components/autofill_assistant/browser/assistant_field_trial_util.h" +#include "components/autofill_assistant/browser/common_dependencies.h" #include "components/autofill_assistant/browser/metrics.h" #include "components/autofill_assistant/browser/onboarding_result.h" +#include "components/autofill_assistant/browser/platform_dependencies.h" #include "components/autofill_assistant/browser/service/service_request_sender.h" #include "components/autofill_assistant/browser/trigger_context.h" #include "components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.h" @@ -87,6 +89,8 @@ class StarterPlatformDelegate { virtual bool GetMakeSearchesAndBrowsingBetterEnabled() const = 0; // Returns whether the user is logged in or not. virtual bool GetIsLoggedIn() = 0; + // Returns whether the user is restricted to any supervision. + virtual bool GetIsSupervisedUser() = 0; // Returns whether this is a custom tab or not. virtual bool GetIsCustomTab() const = 0; // Returns whether this is running in WebLayer or not. @@ -99,6 +103,10 @@ class StarterPlatformDelegate { // The starter platform delegate should only be interacted with while attached // as it might not be able to perform its functions while detached. virtual bool IsAttached() = 0; + // Returns the common dependencies. + virtual const CommonDependencies* GetCommonDependencies() const = 0; + // Returns the platform dependencies. + virtual const PlatformDependencies* GetPlatformDependencies() const = 0; virtual base::WeakPtr<StarterPlatformDelegate> GetWeakPtr() = 0; }; diff --git a/chromium/components/autofill_assistant/browser/starter_unittest.cc b/chromium/components/autofill_assistant/browser/starter_unittest.cc index 17ec78d8329..f871e1970b3 100644 --- a/chromium/components/autofill_assistant/browser/starter_unittest.cc +++ b/chromium/components/autofill_assistant/browser/starter_unittest.cc @@ -7,6 +7,7 @@ #include <memory> #include <string> #include "base/base64url.h" +#include "base/command_line.h" #include "base/containers/flat_map.h" #include "base/containers/lru_cache.h" #include "base/memory/raw_ptr.h" @@ -181,20 +182,20 @@ class StarterTest : public testing::Test { std::unique_ptr<content::NavigationSimulator> simulator = content::NavigationSimulator::CreateRendererInitiated( web_contents()->GetLastCommittedURL(), - web_contents()->GetMainFrame()); + web_contents()->GetPrimaryMainFrame()); simulator->Start(); for (const auto& url : urls) { simulator->Redirect(url); } simulator->Commit(); navigation_ids_.emplace_back( - ukm::GetSourceIdForWebContentsDocument(web_contents())); + web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId()); } void SimulateNavigateToUrl(const GURL& url) { content::WebContentsTester::For(web_contents())->NavigateAndCommit(url); navigation_ids_.emplace_back( - ukm::GetSourceIdForWebContentsDocument(web_contents())); + web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId()); } // Each request sender is only good for one trigger script. This call will @@ -1344,13 +1345,13 @@ TEST_F(StarterTest, RedirectFailsDuringPendingTriggerScriptStart) { std::unique_ptr<content::NavigationSimulator> simulator = content::NavigationSimulator::CreateRendererInitiated( web_contents()->GetLastCommittedURL(), - web_contents()->GetMainFrame()); + web_contents()->GetPrimaryMainFrame()); simulator->Start(); simulator->Redirect(GURL("https://redirect.com/to/www/example/com")); simulator->Fail(net::ERR_BLOCKED_BY_CLIENT); simulator->CommitErrorPage(); navigation_ids_.emplace_back( - ukm::GetSourceIdForWebContentsDocument(web_contents())); + web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId()); // Note that this impression is recorded for the last URL that a navigation- // start event occurred for. We never reached the target domain, so this is @@ -1390,7 +1391,7 @@ TEST_F(StarterTest, StartTriggerScriptDuringRedirectRecordsUkmForTargetUrl) { std::unique_ptr<content::NavigationSimulator> simulator = content::NavigationSimulator::CreateRendererInitiated( web_contents()->GetLastCommittedURL(), - web_contents()->GetMainFrame()); + web_contents()->GetPrimaryMainFrame()); simulator->Start(); simulator->Redirect(GURL("https://redirect.com/to/www/example/com")); starter_->Start(std::make_unique<TriggerContext>( @@ -1398,7 +1399,7 @@ TEST_F(StarterTest, StartTriggerScriptDuringRedirectRecordsUkmForTargetUrl) { simulator->Redirect(GURL(kExampleDeeplink)); simulator->Commit(); navigation_ids_.emplace_back( - ukm::GetSourceIdForWebContentsDocument(web_contents())); + web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId()); EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_), ElementsAreArray(ToHumanReadableMetrics( @@ -1425,7 +1426,7 @@ TEST_F(StarterTest, RegularStartupDoesNotWaitForNavigationToFinish) { std::unique_ptr<content::NavigationSimulator> simulator = content::NavigationSimulator::CreateRendererInitiated( web_contents()->GetLastCommittedURL(), - web_contents()->GetMainFrame()); + web_contents()->GetPrimaryMainFrame()); simulator->Start(); simulator->Redirect(GURL("https://redirect.com/to/www/example/com")); @@ -2253,6 +2254,55 @@ TEST_F(StarterPrerenderTest, DoNotAffectRecordUkmDuringPrendering) { EXPECT_THAT(GetUkmRegularScriptOnboarding(ukm_recorder_), IsEmpty()); } +class StarterFencedFrameTest : public StarterTest { + public: + StarterFencedFrameTest() { + scoped_feature_list_.InitAndEnableFeatureWithParameters( + blink::features::kFencedFrames, {{"implementation_type", "mparch"}}); + } + ~StarterFencedFrameTest() override = default; + + content::RenderFrameHost* CreateFencedFrame( + content::RenderFrameHost* parent) { + content::RenderFrameHost* fenced_frame = + content::RenderFrameHostTester::For(parent)->AppendFencedFrame(); + return fenced_frame; + } + + private: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +TEST_F(StarterFencedFrameTest, NoImplicitTriggeringForFencedFrames) { + // This test should not fetch trigger scripts. + SetupPlatformDelegateForReturningUser(); + PrepareTriggerScriptRequestSender(); + EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest) + .Times(0); + + base::flat_map<std::string, std::string> script_parameters = { + {"ENABLED", "true"}, + {"START_IMMEDIATELY", "false"}, + {"REQUEST_TRIGGER_SCRIPT", "true"}, + {"ORIGINAL_DEEPLINK", kExampleDeeplink}}; + + // Start on "different.com" and it shouldn't start immediately. + SimulateNavigateToUrl(GURL("https://www.different.com")); + starter_->Start(std::make_unique<TriggerContext>( + std::make_unique<ScriptParameters>(script_parameters), + TriggerContext::Options())); + // Create a fenced frame and navigate to the example deeplink. The navigation + // should not start because it is not in the primary main frame. + content::RenderFrameHostTester::For(web_contents()->GetPrimaryMainFrame()) + ->InitializeRenderFrameIfNeeded(); + content::RenderFrameHost* fenced_frame_rfh = + CreateFencedFrame(web_contents()->GetPrimaryMainFrame()); + std::unique_ptr<content::NavigationSimulator> navigation_simulator = + content::NavigationSimulator::CreateRendererInitiated( + GURL(kExampleDeeplink), fenced_frame_rfh); + navigation_simulator->Start(); +} + TEST_F(StarterTest, StartupRegistersTriggerFieldTrial) { auto mock_field_trial_util = std::make_unique<NiceMock<MockAssistantFieldTrialUtil>>(); @@ -2336,4 +2386,39 @@ TEST_F(StarterTest, CanStartSucceeds) { Metrics::AutofillAssistantExperiment::NO_EXPERIMENT}}}))); } +TEST_F(StarterTest, RegularStartupFailsForSupervisedUser) { + SetupPlatformDelegateForFirstTimeUser(); + fake_platform_delegate_.is_supervised_user_ = true; + + base::flat_map<std::string, std::string> script_parameters = { + {"ENABLED", "true"}, + {"START_IMMEDIATELY", "true"}, + {"ORIGINAL_DEEPLINK", kExampleDeeplink}}; + TriggerContext::Options options; + options.initial_url = "https://redirect.com/to/www/example/com"; + EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0); + + starter_->Start(std::make_unique<TriggerContext>( + std::make_unique<ScriptParameters>(script_parameters), options)); +} + +TEST_F(StarterTest, RpcTriggerScriptStartupFailsForSupervisedUser) { + SetupPlatformDelegateForReturningUser(); + fake_platform_delegate_.is_supervised_user_ = true; + + base::flat_map<std::string, std::string> script_parameters = { + {"ENABLED", "true"}, + {"START_IMMEDIATELY", "false"}, + {"REQUEST_TRIGGER_SCRIPT", "true"}, + {"ORIGINAL_DEEPLINK", kExampleDeeplink}}; + EXPECT_CALL(*mock_trigger_script_ui_delegate_, Attach).Times(0); + EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest) + .Times(0); + EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0); + + starter_->Start(std::make_unique<TriggerContext>( + std::make_unique<ScriptParameters>(script_parameters), + TriggerContext::Options())); +} + } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/suppress_keyboard_raii.cc b/chromium/components/autofill_assistant/browser/suppress_keyboard_raii.cc index 7873af5cd02..104bdec981f 100644 --- a/chromium/components/autofill_assistant/browser/suppress_keyboard_raii.cc +++ b/chromium/components/autofill_assistant/browser/suppress_keyboard_raii.cc @@ -36,7 +36,7 @@ void SuppressKeyboardRAII::RenderFrameCreated( } void SuppressKeyboardRAII::SuppressKeyboard(bool suppress) { - web_contents()->GetMainFrame()->ForEachRenderFrameHost( + web_contents()->GetPrimaryMainFrame()->ForEachRenderFrameHost( base::BindRepeating(&SuppressKeyboardForFrame, suppress)); } diff --git a/chromium/components/autofill_assistant/browser/tab_helper.cc b/chromium/components/autofill_assistant/browser/tab_helper.cc index a699db7b717..ea4b814ad05 100644 --- a/chromium/components/autofill_assistant/browser/tab_helper.cc +++ b/chromium/components/autofill_assistant/browser/tab_helper.cc @@ -10,8 +10,13 @@ namespace autofill_assistant { -void CreateForWebContents(content::WebContents* web_contents) { - StarterDelegateDesktop::CreateForWebContents(web_contents); +void CreateForWebContents( + content::WebContents* web_contents, + std::unique_ptr<CommonDependencies> common_dependencies, + std::unique_ptr<PlatformDependencies> platform_dependencies) { + StarterDelegateDesktop::CreateForWebContents( + web_contents, std::move(common_dependencies), + std::move(platform_dependencies)); auto starter_delegate = StarterDelegateDesktop::FromWebContents(web_contents)->GetWeakPtr(); diff --git a/chromium/components/autofill_assistant/browser/tab_helper.h b/chromium/components/autofill_assistant/browser/tab_helper.h index 494443b0df8..21aed6dc21f 100644 --- a/chromium/components/autofill_assistant/browser/tab_helper.h +++ b/chromium/components/autofill_assistant/browser/tab_helper.h @@ -5,11 +5,16 @@ #ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_TAB_HELPER_H_ #define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_TAB_HELPER_H_ +#include "components/autofill_assistant/browser/common_dependencies.h" +#include "components/autofill_assistant/browser/platform_dependencies.h" #include "content/public/browser/web_contents.h" namespace autofill_assistant { // Creates the starter instance for the |web_contents|. -void CreateForWebContents(content::WebContents* web_contents); +void CreateForWebContents( + content::WebContents* web_contents, + std::unique_ptr<CommonDependencies> common_dependencies, + std::unique_ptr<PlatformDependencies> platform_dependencies); } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.cc b/chromium/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.cc index ec1bd624039..2cc6ee34f1a 100644 --- a/chromium/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.cc +++ b/chromium/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.cc @@ -12,7 +12,6 @@ #include "components/autofill_assistant/browser/protocol_utils.h" #include "components/autofill_assistant/browser/starter_platform_delegate.h" #include "components/autofill_assistant/browser/url_utils.h" -#include "components/ukm/content/source_url_recorder.h" #include "components/version_info/version_info.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" @@ -627,7 +626,8 @@ TriggerScriptCoordinator::GetTriggerUiTypeForVisibleScript() const { } GURL TriggerScriptCoordinator::GetCurrentURL() const { - GURL current_url = web_contents()->GetMainFrame()->GetLastCommittedURL(); + GURL current_url = + web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL(); if (current_url.is_empty()) { return deeplink_url_; } diff --git a/chromium/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator_unittest.cc b/chromium/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator_unittest.cc index 842185e70e9..193eb909ee7 100644 --- a/chromium/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator_unittest.cc +++ b/chromium/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator_unittest.cc @@ -105,7 +105,7 @@ class TriggerScriptCoordinatorTest : public testing::Test { std::move(mock_web_controller), std::move(mock_request_sender), GURL(kFakeServerUrl), std::move(mock_static_trigger_conditions), std::move(mock_dynamic_trigger_conditions), &ukm_recorder_, - ukm::GetSourceIdForWebContentsDocument(web_contents())); + web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId()); } void TearDown() override { coordinator_.reset(); } @@ -127,10 +127,10 @@ class TriggerScriptCoordinatorTest : public testing::Test { void SimulateNavigateToUrl(const GURL& url) { content::WebContentsTester::For(web_contents())->SetLastCommittedURL(url); content::NavigationSimulator::NavigateAndCommitFromDocument( - url, web_contents()->GetMainFrame()); + url, web_contents()->GetPrimaryMainFrame()); content::WebContentsTester::For(web_contents())->TestSetIsLoading(false); navigation_ids_.emplace_back( - ukm::GetSourceIdForWebContentsDocument(web_contents())); + web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId()); } protected: @@ -1360,7 +1360,7 @@ TEST_F(TriggerScriptCoordinatorTest, UiTimeoutWhileShown) { EXPECT_CALL(*mock_ui_delegate_, ShowTriggerScript).Times(1); content::NavigationSimulator::Reload(web_contents()); navigation_ids_.emplace_back( - ukm::GetSourceIdForWebContentsDocument(web_contents())); + web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId()); EXPECT_CALL(*mock_ui_delegate_, HideTriggerScript).Times(0); task_environment()->FastForwardBy(base::Seconds(1)); diff --git a/chromium/components/autofill_assistant/browser/ui_controller.cc b/chromium/components/autofill_assistant/browser/ui_controller.cc index 6bfb1ebad7c..e17ba1f316c 100644 --- a/chromium/components/autofill_assistant/browser/ui_controller.cc +++ b/chromium/components/autofill_assistant/browser/ui_controller.cc @@ -76,6 +76,38 @@ bool ShouldShowFeedbackChipForReason(Metrics::DropOutReason reason) { } } +bool ShouldReloadData(const CollectUserDataOptions& options, + UserDataEventType event_type) { + if (!options.use_alternative_edit_dialogs) { + return false; + } + switch (event_type) { + case UserDataEventType::ENTRY_CREATED: + case UserDataEventType::ENTRY_EDITED: + return true; + case UserDataEventType::UNKNOWN: + case UserDataEventType::NO_NOTIFICATION: + case UserDataEventType::SELECTION_CHANGED: + return false; + } +} + +bool ShouldStoreTemporaryData(const CollectUserDataOptions& options, + UserDataEventType event_type) { + if (!options.use_alternative_edit_dialogs) { + return false; + } + switch (event_type) { + case UserDataEventType::ENTRY_CREATED: + case UserDataEventType::ENTRY_EDITED: + return true; + case UserDataEventType::UNKNOWN: + case UserDataEventType::NO_NOTIFICATION: + case UserDataEventType::SELECTION_CHANGED: + return false; + } +} + } // namespace UiController::UiController( @@ -817,13 +849,15 @@ void UiController::HandleShippingAddressChange( if (collect_user_data_options_ == nullptr) { return; } - if (collect_user_data_options_->use_gms_core_edit_dialogs) { + + collect_user_data_options_->selected_user_data_changed_callback.Run( + UserDataEventField::SHIPPING_EVENT, event_type); + + if (ShouldReloadData(*collect_user_data_options_, event_type)) { ReloadUserData(UserDataEventField::SHIPPING_EVENT, event_type); return; } - collect_user_data_options_->selected_user_data_changed_callback.Run( - SHIPPING_EVENT, event_type); DCHECK(!collect_user_data_options_->shipping_address_name.empty()); SetProfile(collect_user_data_options_->shipping_address_name, UserDataFieldChange::SHIPPING_ADDRESS, std::move(address)); @@ -835,13 +869,16 @@ void UiController::HandleContactInfoChange( if (collect_user_data_options_ == nullptr) { return; } - if (collect_user_data_options_->use_gms_core_edit_dialogs) { - ReloadUserData(UserDataEventField::CONTACT_EVENT, event_type); - return; - } collect_user_data_options_->selected_user_data_changed_callback.Run( - CONTACT_EVENT, event_type); + UserDataEventField::CONTACT_EVENT, event_type); + + if (ShouldStoreTemporaryData(*collect_user_data_options_, event_type)) { + UserData* user_data = GetUserData(); + DCHECK(user_data); + user_data::UpsertContact(*profile, user_data->transient_contacts_); + } + DCHECK(!collect_user_data_options_->contact_details_name.empty()); SetProfile(collect_user_data_options_->contact_details_name, UserDataFieldChange::CONTACT_PROFILE, std::move(profile)); @@ -853,13 +890,15 @@ void UiController::HandlePhoneNumberChange( if (collect_user_data_options_ == nullptr) { return; } - if (collect_user_data_options_->use_gms_core_edit_dialogs) { - ReloadUserData(UserDataEventField::CONTACT_EVENT, event_type); - return; - } - // We don't notify the UserDataEvent in this case since we currently don't log - // metrics for the phone number. + collect_user_data_options_->selected_user_data_changed_callback.Run( + UserDataEventField::PHONE_NUMBER_EVENT, event_type); + + if (ShouldStoreTemporaryData(*collect_user_data_options_, event_type)) { + UserData* user_data = GetUserData(); + DCHECK(user_data); + user_data::UpsertPhoneNumber(*profile, user_data->transient_phone_numbers_); + } GetUserData()->SetSelectedPhoneNumber(std::move(profile)); execution_delegate_->NotifyUserDataChange(UserDataFieldChange::PHONE_NUMBER); @@ -872,13 +911,15 @@ void UiController::HandleCreditCardChange( if (collect_user_data_options_ == nullptr) { return; } - if (collect_user_data_options_->use_gms_core_edit_dialogs) { + + collect_user_data_options_->selected_user_data_changed_callback.Run( + UserDataEventField::CREDIT_CARD_EVENT, event_type); + + if (ShouldReloadData(*collect_user_data_options_, event_type)) { ReloadUserData(UserDataEventField::CREDIT_CARD_EVENT, event_type); return; } - collect_user_data_options_->selected_user_data_changed_callback.Run( - CREDIT_CARD_EVENT, event_type); DCHECK(!collect_user_data_options_->billing_address_name.empty()); SetProfile(collect_user_data_options_->billing_address_name, UserDataFieldChange::BILLING_ADDRESS, std::move(billing_profile)); @@ -898,15 +939,9 @@ void UiController::SetProfile( void UiController::ReloadUserData(UserDataEventField event_field, UserDataEventType event_type) { - if (collect_user_data_options_ == nullptr) { - return; - } - - collect_user_data_options_->selected_user_data_changed_callback.Run( - event_field, event_type); - - auto callback = std::move(collect_user_data_options_->reload_data_callback); - std::move(callback).Run(GetUserData()); + DCHECK(collect_user_data_options_); + std::move(collect_user_data_options_->reload_data_callback) + .Run(event_field, GetUserData()); } void UiController::SetTermsAndConditions( @@ -987,6 +1022,13 @@ void UiController::SetCollectUserDataOptions(CollectUserDataOptions* options) { execution_delegate_->NotifyUserDataChange(UserDataFieldChange::ALL); } +void UiController::SetCollectUserDataUiState(bool loading, + UserDataEventField event) { + for (UiControllerObserver& observer : observers_) { + observer.OnCollectUserDataUiStateChanged(loading, event); + } +} + void UiController::SetLastSuccessfulUserDataOptions( std::unique_ptr<CollectUserDataOptions> collect_user_data_options) { last_collect_user_data_options_ = std::move(collect_user_data_options); @@ -1042,7 +1084,6 @@ void UiController::OnOverlayColorsChanged( const ExecutionDelegate::OverlayColors& colors) {} void UiController::OnClientSettingsChanged(const ClientSettings& settings) {} void UiController::OnShouldShowOverlayChanged(bool should_show) {} -void UiController::OnShutdown(Metrics::DropOutReason reason) {} void UiController::OnExecuteScript(const std::string& start_message) { if (!start_message.empty()) @@ -1132,4 +1173,20 @@ void UiController::OnUiShownChanged(bool shown) { } } +bool UiController::SupportsExternalActions() { + return false; +} + +void UiController::ExecuteExternalAction( + const external::Action& external_action, + base::OnceCallback<void(ExternalActionDelegate::DomUpdateCallback)> + start_dom_checks_callback, + base::OnceCallback<void(const external::Result& result)> + end_action_callback) { + NOTREACHED() << "Flows using default UI don't support external actions."; +} + +void UiController::OnInterruptStarted() {} +void UiController::OnInterruptFinished() {} + } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/ui_controller.h b/chromium/components/autofill_assistant/browser/ui_controller.h index 073545fa4cb..0b212ad807b 100644 --- a/chromium/components/autofill_assistant/browser/ui_controller.h +++ b/chromium/components/autofill_assistant/browser/ui_controller.h @@ -10,6 +10,7 @@ #include <vector> #include "base/callback_helpers.h" +#include "base/memory/raw_ptr.h" #include "components/autofill_assistant/browser/autofill_assistant_tts_controller.h" #include "components/autofill_assistant/browser/basic_interactions.h" #include "components/autofill_assistant/browser/bottom_sheet_state.h" @@ -107,10 +108,14 @@ class UiController : public ScriptExecutorUiDelegate, void SetExpandSheetForPromptAction(bool expand) override; void SetCollectUserDataOptions(CollectUserDataOptions* options) override; + void SetCollectUserDataUiState(bool loading, + UserDataEventField event_field) override; void SetLastSuccessfulUserDataOptions(std::unique_ptr<CollectUserDataOptions> collect_user_data_options) override; const CollectUserDataOptions* GetLastSuccessfulUserDataOptions() const override; + void OnInterruptStarted() override; + void OnInterruptFinished() override; // Overrides autofill_assistant::UiDelegate: std::vector<Details> GetDetails() const override; @@ -189,7 +194,13 @@ class UiController : public ScriptExecutorUiDelegate, void OnStop() override; void OnResetState() override; void OnUiShownChanged(bool shown) override; - void OnShutdown(Metrics::DropOutReason reason) override; + bool SupportsExternalActions() override; + void ExecuteExternalAction( + const external::Action& external_action, + base::OnceCallback<void(ExternalActionDelegate::DomUpdateCallback)> + start_dom_checks_callback, + base::OnceCallback<void(const external::Result& result)> + end_action_callback) override; // Overrides AutofillAssistantTtsController::TtsEventDelegate void OnTtsEvent(AutofillAssistantTtsController::TtsEventType event) override; @@ -264,7 +275,7 @@ class UiController : public ScriptExecutorUiDelegate, UserData* GetUserData(); UserModel* GetUserModel(); - Client* const client_; + const raw_ptr<Client> client_; // Current status message, may be empty. std::string status_message_; @@ -311,7 +322,7 @@ class UiController : public ScriptExecutorUiDelegate, base::ObserverList<UiControllerObserver> observers_; - ExecutionDelegate* execution_delegate_; + raw_ptr<ExecutionDelegate> execution_delegate_; EventHandler event_handler_; BasicInteractions basic_interactions_{this, execution_delegate_}; diff --git a/chromium/components/autofill_assistant/browser/ui_controller_observer.h b/chromium/components/autofill_assistant/browser/ui_controller_observer.h index daf33b94120..07dc7f0afe9 100644 --- a/chromium/components/autofill_assistant/browser/ui_controller_observer.h +++ b/chromium/components/autofill_assistant/browser/ui_controller_observer.h @@ -43,6 +43,11 @@ class UiControllerObserver : public base::CheckedObserver { virtual void OnCollectUserDataOptionsChanged( const CollectUserDataOptions* options) = 0; + // Report that the state of the User Data UI has changed. + virtual void OnCollectUserDataUiStateChanged( + bool loading, + UserDataEventField event_field) = 0; + // Called when details have changed. Details will be empty if they have been // cleared. virtual void OnDetailsChanged(const std::vector<Details>& details) = 0; diff --git a/chromium/components/autofill_assistant/browser/ui_controller_unittest.cc b/chromium/components/autofill_assistant/browser/ui_controller_unittest.cc index a691b5a6db7..e983efb5179 100644 --- a/chromium/components/autofill_assistant/browser/ui_controller_unittest.cc +++ b/chromium/components/autofill_assistant/browser/ui_controller_unittest.cc @@ -494,78 +494,93 @@ TEST_F(UiControllerTest, UserDataChangesByOutOfLoopWrite) { UserDataFieldChange::CONTACT_PROFILE); } -TEST_F(UiControllerTest, UserDataFormReloadFromContactChange) { +TEST_F(UiControllerTest, UserDataFormStoreContactChange) { auto options = std::make_unique<FakeCollectUserDataOptions>(); - base::MockCallback<base::OnceCallback<void(UserData*)>> reload_callback; - options->reload_data_callback = reload_callback.Get(); base::MockCallback< base::RepeatingCallback<void(UserDataEventField, UserDataEventType)>> change_callback; options->selected_user_data_changed_callback = change_callback.Get(); - options->use_gms_core_edit_dialogs = true; + options->contact_details_name = "CONTACT"; + options->use_alternative_edit_dialogs = true; ui_controller_->SetCollectUserDataOptions(options.get()); EXPECT_CALL(change_callback, Run(UserDataEventField::CONTACT_EVENT, UserDataEventType::ENTRY_CREATED)); - EXPECT_CALL(reload_callback, Run); - ui_controller_->HandleContactInfoChange(nullptr, - UserDataEventType::ENTRY_CREATED); + autofill::AutofillProfile profile; + profile.SetRawInfo(autofill::ServerFieldType::EMAIL_ADDRESS, + u"johndoe@google.com"); + ui_controller_->HandleContactInfoChange( + std::make_unique<autofill::AutofillProfile>(profile), + UserDataEventType::ENTRY_CREATED); + + ASSERT_EQ(user_data_.transient_contacts_.size(), 1u); + EXPECT_EQ(user_data_.transient_contacts_[0]->profile->GetRawInfo( + autofill::ServerFieldType::EMAIL_ADDRESS), + u"johndoe@google.com"); } -TEST_F(UiControllerTest, UserDataFormReloadFromPhoneNumberChange) { +TEST_F(UiControllerTest, UserDataFormStorePhoneNumberChange) { auto options = std::make_unique<FakeCollectUserDataOptions>(); - base::MockCallback<base::OnceCallback<void(UserData*)>> reload_callback; - options->reload_data_callback = reload_callback.Get(); base::MockCallback< base::RepeatingCallback<void(UserDataEventField, UserDataEventType)>> change_callback; options->selected_user_data_changed_callback = change_callback.Get(); - options->use_gms_core_edit_dialogs = true; + options->use_alternative_edit_dialogs = true; ui_controller_->SetCollectUserDataOptions(options.get()); - EXPECT_CALL(change_callback, Run(UserDataEventField::CONTACT_EVENT, + EXPECT_CALL(change_callback, Run(UserDataEventField::PHONE_NUMBER_EVENT, UserDataEventType::ENTRY_CREATED)); - EXPECT_CALL(reload_callback, Run); - ui_controller_->HandlePhoneNumberChange(nullptr, - UserDataEventType::ENTRY_CREATED); + autofill::AutofillProfile profile; + profile.SetRawInfo(autofill::ServerFieldType::PHONE_HOME_WHOLE_NUMBER, + u"+41441234567"); + ui_controller_->HandlePhoneNumberChange( + std::make_unique<autofill::AutofillProfile>(profile), + UserDataEventType::ENTRY_CREATED); + + ASSERT_EQ(user_data_.transient_phone_numbers_.size(), 1u); + EXPECT_EQ(user_data_.transient_phone_numbers_[0]->profile->GetRawInfo( + autofill::ServerFieldType::PHONE_HOME_WHOLE_NUMBER), + u"+41441234567"); } TEST_F(UiControllerTest, UserDataFormReloadFromShippingAddressChange) { auto options = std::make_unique<FakeCollectUserDataOptions>(); - base::MockCallback<base::OnceCallback<void(UserData*)>> reload_callback; + base::MockCallback<base::OnceCallback<void(UserDataEventField, UserData*)>> + reload_callback; options->reload_data_callback = reload_callback.Get(); base::MockCallback< base::RepeatingCallback<void(UserDataEventField, UserDataEventType)>> change_callback; options->selected_user_data_changed_callback = change_callback.Get(); - options->use_gms_core_edit_dialogs = true; + options->use_alternative_edit_dialogs = true; ui_controller_->SetCollectUserDataOptions(options.get()); EXPECT_CALL(change_callback, Run(UserDataEventField::SHIPPING_EVENT, UserDataEventType::ENTRY_CREATED)); - EXPECT_CALL(reload_callback, Run); + EXPECT_CALL(reload_callback, Run(UserDataEventField::SHIPPING_EVENT, _)); ui_controller_->HandleShippingAddressChange(nullptr, UserDataEventType::ENTRY_CREATED); } TEST_F(UiControllerTest, UserDataFormReloadFromCreditCardChange) { auto options = std::make_unique<FakeCollectUserDataOptions>(); - base::MockCallback<base::OnceCallback<void(UserData*)>> reload_callback; + base::MockCallback<base::OnceCallback<void(UserDataEventField, UserData*)>> + reload_callback; options->reload_data_callback = reload_callback.Get(); base::MockCallback< base::RepeatingCallback<void(UserDataEventField, UserDataEventType)>> change_callback; options->selected_user_data_changed_callback = change_callback.Get(); - options->use_gms_core_edit_dialogs = true; + options->use_alternative_edit_dialogs = true; ui_controller_->SetCollectUserDataOptions(options.get()); EXPECT_CALL(change_callback, Run(UserDataEventField::CREDIT_CARD_EVENT, UserDataEventType::ENTRY_CREATED)); - EXPECT_CALL(reload_callback, Run); + EXPECT_CALL(reload_callback, Run(UserDataEventField::CREDIT_CARD_EVENT, _)); ui_controller_->HandleCreditCardChange(nullptr, nullptr, UserDataEventType::ENTRY_CREATED); } @@ -1101,4 +1116,12 @@ TEST_F(UiControllerTest, OnExecuteScriptSetMessageAndClearUserActions) { EXPECT_EQ(ui_controller_->GetStatusMessage(), "script message"); } +TEST_F(UiControllerTest, SetCollectUserDataUiState) { + EXPECT_CALL(mock_observer_, + OnCollectUserDataUiStateChanged( + /* loading= */ true, UserDataEventField::SHIPPING_EVENT)); + ui_controller_->SetCollectUserDataUiState(/* loading= */ true, + UserDataEventField::SHIPPING_EVENT); +} + } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/user_data.h b/chromium/components/autofill_assistant/browser/user_data.h index 7d2aa061884..8ce76d942b1 100644 --- a/chromium/components/autofill_assistant/browser/user_data.h +++ b/chromium/components/autofill_assistant/browser/user_data.h @@ -61,7 +61,13 @@ enum UserDataEventType { ENTRY_CREATED }; -enum UserDataEventField { CONTACT_EVENT, CREDIT_CARD_EVENT, SHIPPING_EVENT }; +enum class UserDataEventField { + NONE, + CONTACT_EVENT, + PHONE_NUMBER_EVENT, + CREDIT_CARD_EVENT, + SHIPPING_EVENT +}; // Represents a concrete login choice in the UI, e.g., 'Guest checkout' or // a particular Chrome PWM login account. @@ -123,6 +129,7 @@ struct Contact { absl::optional<std::string> identifier; std::unique_ptr<autofill::AutofillProfile> profile; + bool can_edit = true; }; // Struct for holding a phone number. This is a wrapper around AutofillProfile @@ -134,6 +141,7 @@ struct PhoneNumber { absl::optional<std::string> identifier; std::unique_ptr<autofill::AutofillProfile> profile; + bool can_edit = true; }; // Struct for holding an address. This is a wrapper around AutofillProfile to @@ -221,6 +229,9 @@ class UserData { absl::optional<WebsiteLoginManager::Login> selected_login_; + std::vector<std::unique_ptr<Contact>> transient_contacts_; + std::vector<std::unique_ptr<PhoneNumber>> transient_phone_numbers_; + // Return true if address has been selected, otherwise return false. // Note that selected_address() might return nullptr when // has_selected_address() is true because fill manually was chosen. @@ -313,8 +324,7 @@ struct CollectUserDataOptions { std::vector<RequiredDataPiece> required_billing_address_data_pieces; bool should_store_data_changes = false; - bool can_edit_contacts = true; - bool use_gms_core_edit_dialogs = false; + bool use_alternative_edit_dialogs = false; absl::optional<std::string> add_payment_instrument_action_token; absl::optional<std::string> add_address_token; @@ -353,7 +363,7 @@ struct CollectUserDataOptions { additional_actions_callback; base::OnceCallback<void(int, UserData*, const UserModel*)> terms_link_callback; - base::OnceCallback<void(UserData*)> reload_data_callback; + base::OnceCallback<void(UserDataEventField, UserData*)> reload_data_callback; // Called whenever there is a change to the selected user data. base::RepeatingCallback<void(UserDataEventField, UserDataEventType)> selected_user_data_changed_callback; diff --git a/chromium/components/autofill_assistant/browser/user_data_util.cc b/chromium/components/autofill_assistant/browser/user_data_util.cc index 90dcedd787b..4b1f73b0cd9 100644 --- a/chromium/components/autofill_assistant/browser/user_data_util.cc +++ b/chromium/components/autofill_assistant/browser/user_data_util.cc @@ -18,6 +18,7 @@ #include "components/autofill_assistant/browser/field_formatter.h" #include "components/autofill_assistant/browser/model.pb.h" #include "components/autofill_assistant/browser/url_utils.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/website_login_manager.h" #include "components/strings/grit/components_strings.h" #include "third_party/libaddressinput/chromium/addressinput_util.h" @@ -319,6 +320,25 @@ ClientStatus MoveAutofillValueRegexpToTextFilter( return re2_status; } +template <typename T> +void UpsertAutofillProfile(const autofill::AutofillProfile& profile, + std::vector<std::unique_ptr<T>>& list) { + auto it = + base::ranges::find_if(list, [&profile](const std::unique_ptr<T>& ptr) { + return ptr->profile && ptr->profile->guid() == profile.guid(); + }); + + auto new_profile = user_data::MakeUniqueFromProfile(profile); + if (it == list.end()) { + auto entry = std::make_unique<T>(std::move(new_profile)); + entry->identifier = profile.guid(); + list.emplace_back(std::move(entry)); + return; + } + + (*it)->profile = std::move(new_profile); +} + } // namespace std::vector<std::string> GetContactValidationErrors( @@ -881,6 +901,7 @@ ClientStatus ResolveSelectorUserData(SelectorProto* selector, case SelectorProto::Filter::kLabelled: case SelectorProto::Filter::kMatchCssSelector: case SelectorProto::Filter::kOnTop: + case SelectorProto::Filter::kParent: case SelectorProto::Filter::FILTER_NOT_SET: break; // Do not add default here. In case a new filter gets added (that may @@ -890,5 +911,15 @@ ClientStatus ResolveSelectorUserData(SelectorProto* selector, return OkClientStatus(); } +void UpsertContact(const autofill::AutofillProfile& profile, + std::vector<std::unique_ptr<Contact>>& list) { + UpsertAutofillProfile(profile, list); +} + +void UpsertPhoneNumber(const autofill::AutofillProfile& profile, + std::vector<std::unique_ptr<PhoneNumber>>& list) { + UpsertAutofillProfile(profile, list); +} + } // namespace user_data } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/user_data_util.h b/chromium/components/autofill_assistant/browser/user_data_util.h index 1e35d1f94b5..f226c5192c0 100644 --- a/chromium/components/autofill_assistant/browser/user_data_util.h +++ b/chromium/components/autofill_assistant/browser/user_data_util.h @@ -16,10 +16,11 @@ #include "components/autofill_assistant/browser/service.pb.h" #include "components/autofill_assistant/browser/user_data.h" #include "components/autofill_assistant/browser/user_model.h" -#include "components/autofill_assistant/browser/web/element_finder.h" #include "components/autofill_assistant/browser/website_login_manager.h" namespace autofill_assistant { +class ElementFinderResult; + namespace user_data { // Validate the completeness of a contact. @@ -179,6 +180,13 @@ int GetFieldBitArrayForCreditCard(const autofill::CreditCard* card); ClientStatus ResolveSelectorUserData(SelectorProto* selector, const UserData* user_data); +// Update or insert a contact in the list. +void UpsertContact(const autofill::AutofillProfile& profile, + std::vector<std::unique_ptr<Contact>>& list); +// Update or insert a phone number in the list. +void UpsertPhoneNumber(const autofill::AutofillProfile& profile, + std::vector<std::unique_ptr<PhoneNumber>>& list); + } // namespace user_data } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/user_data_util_unittest.cc b/chromium/components/autofill_assistant/browser/user_data_util_unittest.cc index 6c4e77b9a5e..eb715bd4367 100644 --- a/chromium/components/autofill_assistant/browser/user_data_util_unittest.cc +++ b/chromium/components/autofill_assistant/browser/user_data_util_unittest.cc @@ -1263,7 +1263,7 @@ TEST_F(UserDataUtilTextValueTest, GetUsername) { GURL("https://www.example.com"), "username"); ElementFinderResult element; - element.SetRenderFrameHost(web_contents_->GetMainFrame()); + element.SetRenderFrameHost(web_contents_->GetPrimaryMainFrame()); PasswordManagerValue password_manager_value; password_manager_value.set_credential_type(PasswordManagerValue::USERNAME); @@ -1281,7 +1281,7 @@ TEST_F(UserDataUtilTextValueTest, GetStoredPassword) { GURL("https://www.example.com"), "username"); ElementFinderResult element; - element.SetRenderFrameHost(web_contents_->GetMainFrame()); + element.SetRenderFrameHost(web_contents_->GetPrimaryMainFrame()); PasswordManagerValue password_manager_value; password_manager_value.set_credential_type(PasswordManagerValue::PASSWORD); @@ -1303,7 +1303,7 @@ TEST_F(UserDataUtilTextValueTest, GetStoredPasswordFails) { ElementFinderResult element; content::WebContentsTester::For(web_contents_.get()) ->NavigateAndCommit(GURL("https://www.example.com")); - element.SetRenderFrameHost(web_contents_->GetMainFrame()); + element.SetRenderFrameHost(web_contents_->GetPrimaryMainFrame()); PasswordManagerValue password_manager_value; password_manager_value.set_credential_type(PasswordManagerValue::PASSWORD); @@ -1419,7 +1419,7 @@ TEST_F(UserDataUtilTextValueTest, TextValuePasswordManagerValue) { ElementFinderResult element; content::WebContentsTester::For(web_contents_.get()) ->NavigateAndCommit(GURL("https://www.example.com")); - element.SetRenderFrameHost(web_contents_->GetMainFrame()); + element.SetRenderFrameHost(web_contents_->GetPrimaryMainFrame()); TextValue text_value; text_value.mutable_password_manager_value()->set_credential_type( @@ -1611,6 +1611,106 @@ TEST_F(UserDataUtilTextValueTest, ResolveSelectorUserDataError) { ASSERT_FALSE(status.ok()); } +TEST(UserDataUtilTest, InsertNewContactToList) { + autofill::AutofillProfile new_profile; + autofill::test::SetProfileInfo(&new_profile, "Adam", "", "West", + "adam.west@gmail.com", "", "", "", "", "", "", + "", ""); + + std::unique_ptr<autofill::AutofillProfile> old_profile = + std::make_unique<autofill::AutofillProfile>(); + autofill::test::SetProfileInfo(old_profile.get(), "Berta", "", "West", + "berta.west@gmail.com", "", "", "", "", "", "", + "", ""); + + std::vector<std::unique_ptr<Contact>> list; + list.emplace_back(std::make_unique<Contact>(std::move(old_profile))); + + UpsertContact(new_profile, list); + + ASSERT_EQ(list.size(), 2u); + EXPECT_EQ(list[1]->profile->guid(), new_profile.guid()); + EXPECT_EQ(list[1]->identifier, new_profile.guid()); + EXPECT_EQ(list[0]->profile->GetInfo(autofill::NAME_FIRST, "en-US"), u"Berta"); + EXPECT_EQ(list[1]->profile->GetInfo(autofill::NAME_FIRST, "en-US"), u"Adam"); +} + +TEST(UserDataUtilTest, UpdateExistingContactInList) { + autofill::AutofillProfile updated_profile; + autofill::test::SetProfileInfo(&updated_profile, "Adam", "B.", "West", + "adam.west@gmail.com", "", "", "", "", "", "", + "", ""); + + std::unique_ptr<autofill::AutofillProfile> old_profile = + std::make_unique<autofill::AutofillProfile>(); + old_profile->set_guid(updated_profile.guid()); + autofill::test::SetProfileInfo(old_profile.get(), "Adam", "", "West", + "adam.west@gmail.com", "", "", "", "", "", "", + "", ""); + + std::vector<std::unique_ptr<Contact>> list; + list.emplace_back(std::make_unique<Contact>(std::move(old_profile))); + + EXPECT_EQ(list[0]->profile->GetInfo(autofill::NAME_MIDDLE, "en-US"), u""); + UpsertContact(updated_profile, list); + + ASSERT_EQ(list.size(), 1u); + EXPECT_EQ(list[0]->profile->guid(), updated_profile.guid()); + EXPECT_EQ(list[0]->profile->GetInfo(autofill::NAME_MIDDLE, "en-US"), u"B."); +} + +TEST(UserDataUtilTest, InsertNewPhoneNumberToList) { + autofill::AutofillProfile new_profile; + autofill::test::SetProfileInfo(&new_profile, "", "", "", "", "", "", "", "", + "", "", "", "+41441234567"); + + std::unique_ptr<autofill::AutofillProfile> old_profile = + std::make_unique<autofill::AutofillProfile>(); + autofill::test::SetProfileInfo(old_profile.get(), "", "", "", "", "", "", "", + "", "", "", "", "+4144765432"); + + std::vector<std::unique_ptr<Contact>> list; + list.emplace_back(std::make_unique<Contact>(std::move(old_profile))); + + UpsertContact(new_profile, list); + + ASSERT_EQ(list.size(), 2u); + EXPECT_EQ(list[1]->profile->guid(), new_profile.guid()); + EXPECT_EQ(list[1]->identifier, new_profile.guid()); + EXPECT_EQ( + list[0]->profile->GetInfo(autofill::PHONE_HOME_WHOLE_NUMBER, "en-US"), + u"+4144765432"); + EXPECT_EQ( + list[1]->profile->GetInfo(autofill::PHONE_HOME_WHOLE_NUMBER, "en-US"), + u"+41441234567"); +} + +TEST(UserDataUtilTest, UpdateExistingPhoneNumberInList) { + autofill::AutofillProfile updated_profile; + autofill::test::SetProfileInfo(&updated_profile, "", "", "", "", "", "", "", + "", "", "", "", "+41441234567"); + + std::unique_ptr<autofill::AutofillProfile> old_profile = + std::make_unique<autofill::AutofillProfile>(); + old_profile->set_guid(updated_profile.guid()); + autofill::test::SetProfileInfo(old_profile.get(), "", "", "", "", "", "", "", + "", "", "", "", "+4144765432"); + + std::vector<std::unique_ptr<Contact>> list; + list.emplace_back(std::make_unique<Contact>(std::move(old_profile))); + + EXPECT_EQ( + list[0]->profile->GetInfo(autofill::PHONE_HOME_WHOLE_NUMBER, "en-US"), + u"+4144765432"); + UpsertContact(updated_profile, list); + + ASSERT_EQ(list.size(), 1u); + EXPECT_EQ(list[0]->profile->guid(), updated_profile.guid()); + EXPECT_EQ( + list[0]->profile->GetInfo(autofill::PHONE_HOME_WHOLE_NUMBER, "en-US"), + u"+41441234567"); +} + } // namespace } // namespace user_data } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/wait_for_document_operation.cc b/chromium/components/autofill_assistant/browser/wait_for_document_operation.cc index 8a39304bf06..081736d26e4 100644 --- a/chromium/components/autofill_assistant/browser/wait_for_document_operation.cc +++ b/chromium/components/autofill_assistant/browser/wait_for_document_operation.cc @@ -10,7 +10,7 @@ #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/script_executor_delegate.h" #include "components/autofill_assistant/browser/service.pb.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/web_controller.h" namespace autofill_assistant { diff --git a/chromium/components/autofill_assistant/browser/wait_for_document_operation.h b/chromium/components/autofill_assistant/browser/wait_for_document_operation.h index 534366c3e0d..7aadca98f05 100644 --- a/chromium/components/autofill_assistant/browser/wait_for_document_operation.h +++ b/chromium/components/autofill_assistant/browser/wait_for_document_operation.h @@ -13,9 +13,9 @@ #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/script_executor_delegate.h" #include "components/autofill_assistant/browser/service.pb.h" -#include "components/autofill_assistant/browser/web/element_finder.h" namespace autofill_assistant { +class ElementFinderResult; // Waits for a minimal state of the document or times out if the state is not // reached in time. diff --git a/chromium/components/autofill_assistant/browser/wait_for_dom_operation.cc b/chromium/components/autofill_assistant/browser/wait_for_dom_operation.cc index fc859a11dd7..59a10e2bfd6 100644 --- a/chromium/components/autofill_assistant/browser/wait_for_dom_operation.cc +++ b/chromium/components/autofill_assistant/browser/wait_for_dom_operation.cc @@ -30,11 +30,15 @@ WaitForDomOperation::WaitForDomOperation( ->GetScriptParameters() .GetEnableObserverWaitForDom() .value_or(false)), - observer_(observer), check_elements_(std::move(check_elements)), callback_(std::move(callback)), timeout_warning_delay_(delegate_->GetSettings().warning_delay), - retry_timer_(delegate_->GetSettings().periodic_element_check_interval) {} + retry_timer_(delegate_->GetSettings().periodic_element_check_interval) { + if (observer) { + observers_.emplace_back(observer); + } + observers_.emplace_back(ui_delegate_); +} WaitForDomOperation::~WaitForDomOperation() { delegate_->RemoveNavigationListener(this); @@ -165,12 +169,14 @@ void WaitForDomOperation::RunChecks( base::Unretained(this), std::move(report_attempt_result))); if (use_observers_) { batch_element_checker_->EnableObserver( - /* max_wait_time= */ max_wait_time_ - - wait_time_stopwatch_.TotalElapsed(), - /* periodic_check_interval= */ - delegate_->GetSettings().periodic_element_check_interval, - /* extra_timeout= */ - delegate_->GetSettings().selector_observer_extra_timeout); + {/* max_wait_time= */ max_wait_time_ - + wait_time_stopwatch_.TotalElapsed(), + /* min_check_interval= */ + delegate_->GetSettings().periodic_element_check_interval, + /* extra_timeout= */ + delegate_->GetSettings().selector_observer_extra_timeout, + /* debounce_interval */ + delegate_->GetSettings().selector_observer_debounce_interval}); } batch_element_checker_->Run(delegate_->GetWebController()); } @@ -218,8 +224,9 @@ void WaitForDomOperation::OnAllChecksDone( void WaitForDomOperation::RunInterrupt(const std::string& path) { batch_element_checker_.reset(); - if (observer_) - observer_->OnInterruptStarted(); + for (auto* observer : observers_) { + observer->OnInterruptStarted(); + } SavePreInterruptState(); ran_interrupts_.insert(path); @@ -246,8 +253,9 @@ void WaitForDomOperation::OnInterruptDone( RunCallbackWithResult(ClientStatus(INTERRUPT_FAILED), &result); return; } - if (observer_) - observer_->OnInterruptFinished(); + for (auto* observer : observers_) { + observer->OnInterruptFinished(); + } RestorePreInterruptState(); RestorePreInterruptScroll(); diff --git a/chromium/components/autofill_assistant/browser/wait_for_dom_operation.h b/chromium/components/autofill_assistant/browser/wait_for_dom_operation.h index d2e7ff34582..c33d8446a1c 100644 --- a/chromium/components/autofill_assistant/browser/wait_for_dom_operation.h +++ b/chromium/components/autofill_assistant/browser/wait_for_dom_operation.h @@ -121,7 +121,7 @@ class WaitForDomOperation : public ScriptExecutor::Listener, const base::TimeDelta max_wait_time_; const bool allow_interrupt_; const bool use_observers_; - raw_ptr<WaitForDomObserver> observer_; + std::vector<WaitForDomObserver*> observers_; base::RepeatingCallback<void(BatchElementChecker*, base::OnceCallback<void(const ClientStatus&)>)> check_elements_; diff --git a/chromium/components/autofill_assistant/browser/web/base_element_finder.cc b/chromium/components/autofill_assistant/browser/web/base_element_finder.cc new file mode 100644 index 00000000000..ef74f9c2cd4 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/web/base_element_finder.cc @@ -0,0 +1,11 @@ +// Copyright 2022 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_assistant/browser/web/base_element_finder.h" + +namespace autofill_assistant { + +BaseElementFinder::~BaseElementFinder() = default; + +} // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/web/base_element_finder.h b/chromium/components/autofill_assistant/browser/web/base_element_finder.h new file mode 100644 index 00000000000..b9305f7947c --- /dev/null +++ b/chromium/components/autofill_assistant/browser/web/base_element_finder.h @@ -0,0 +1,41 @@ +// Copyright 2022 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_ASSISTANT_BROWSER_WEB_BASE_ELEMENT_FINDER_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_WEB_BASE_ELEMENT_FINDER_H_ + +#include <memory> + +#include "base/callback_forward.h" +#include "components/autofill_assistant/browser/service.pb.h" + +namespace autofill_assistant { +class ClientStatus; +class ElementFinderResult; + +class BaseElementFinder { + public: + using Callback = + base::OnceCallback<void(const ClientStatus&, + std::unique_ptr<ElementFinderResult>)>; + + virtual ~BaseElementFinder(); + + // Start looking for the element and return it through |callback| with + // a status. If |start_element| is not empty, use it as a starting point + // instead of starting from the main frame. + virtual void Start(const ElementFinderResult& start_element, + Callback callback) = 0; + + // Get the log information for the last run. Should only be run after the + // run has completed (i.e. |callback_| has been called). + virtual ElementFinderInfoProto GetLogInfo() const = 0; + + // Returns the backend node id that was previously collected. + virtual int GetBackendNodeId() const = 0; +}; + +} // namespace autofill_assistant + +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_WEB_BASE_ELEMENT_FINDER_H_ diff --git a/chromium/components/autofill_assistant/browser/web/batch_element_checker_browsertest.cc b/chromium/components/autofill_assistant/browser/web/batch_element_checker_browsertest.cc index b304148aead..38a800b185d 100644 --- a/chromium/components/autofill_assistant/browser/web/batch_element_checker_browsertest.cc +++ b/chromium/components/autofill_assistant/browser/web/batch_element_checker_browsertest.cc @@ -101,6 +101,12 @@ class BatchElementCheckerBrowserTest return proto; } + static SelectorObserver::Settings SelectorObserverDefaultSettings( + base::TimeDelta max_wait_time) { + return {max_wait_time, base::Seconds(1), base::Seconds(15), + base::Milliseconds(100)}; + } + // Run Observer BatchElementChecker on the provided conditions. The second // value in the pairs (bool) is the match expectation. void RunObserverBatchElementChecker( @@ -123,8 +129,7 @@ class BatchElementCheckerBrowserTest ObserverBatchElementCheckerAllDoneCallback, run_loop.QuitClosure(), &expected_results, &actual_results)); - checker.EnableObserver(base::Seconds(30), base::Seconds(1), - base::Seconds(15)); + checker.EnableObserver(SelectorObserverDefaultSettings(base::Seconds(30))); checker.Run(web_controller_.get()); run_loop.Run(); EXPECT_EQ(web_controller_->pending_workers_.size(), 0u); @@ -335,7 +340,7 @@ IN_PROC_BROWSER_TEST_F(BatchElementCheckerBrowserTest, SelectorObserver) { /* proto = */ Selector({"#iframeExternal", ".dynamic.about-2-seconds"}).proto, /* strict = */ true}}, - base::Seconds(30), base::Seconds(1), base::Seconds(15), update_callback); + SelectorObserverDefaultSettings(base::Seconds(30)), update_callback); run_loop.Run(); ASSERT_TRUE(expected_updates.empty()); @@ -375,7 +380,7 @@ IN_PROC_BROWSER_TEST_F(BatchElementCheckerBrowserTest, {{/* selector_id = */ button_id, /* proto = */ Selector({"#iframeRedirecting", "#button"}).proto, /* strict = */ true}}, - base::Seconds(30), base::Seconds(1), base::Seconds(15), update_callback); + SelectorObserverDefaultSettings(base::Seconds(30)), update_callback); run_loop.Run(); } @@ -411,7 +416,7 @@ IN_PROC_BROWSER_TEST_F(BatchElementCheckerBrowserTest, {{/* selector_id = */ SelectorObserver::SelectorId(1), /* proto = */ Selector({"#does_not_exist"}).proto, /* strict = */ true}}, - base::Milliseconds(300), base::Seconds(1), base::Seconds(15), + SelectorObserverDefaultSettings(base::Milliseconds(300)), mock_callback.Get()); run_loop.Run(); @@ -454,7 +459,7 @@ IN_PROC_BROWSER_TEST_F(BatchElementCheckerBrowserTest, {{/* selector_id = */ button_id, /* proto = */ Selector({"#iframe", "#button"}).proto, /* strict = */ true}}, - base::Milliseconds(1), base::Seconds(1), base::Seconds(15), + SelectorObserverDefaultSettings(base::Milliseconds(1)), update_callback.Get()); run_loop.Run(); diff --git a/chromium/components/autofill_assistant/browser/web/check_on_top_worker.cc b/chromium/components/autofill_assistant/browser/web/check_on_top_worker.cc index 7acdc2af7e6..eef35bb739e 100644 --- a/chromium/components/autofill_assistant/browser/web/check_on_top_worker.cc +++ b/chromium/components/autofill_assistant/browser/web/check_on_top_worker.cc @@ -7,6 +7,8 @@ #include <vector> #include "base/logging.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" +#include "components/autofill_assistant/browser/web/js_snippets.h" #include "components/autofill_assistant/browser/web/web_controller_util.h" namespace autofill_assistant { diff --git a/chromium/components/autofill_assistant/browser/web/check_on_top_worker.h b/chromium/components/autofill_assistant/browser/web/check_on_top_worker.h index 0cc1661cfb6..9aa9338a8d9 100644 --- a/chromium/components/autofill_assistant/browser/web/check_on_top_worker.h +++ b/chromium/components/autofill_assistant/browser/web/check_on_top_worker.h @@ -13,10 +13,10 @@ #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/devtools/devtools/domains/types_runtime.h" #include "components/autofill_assistant/browser/devtools/devtools_client.h" -#include "components/autofill_assistant/browser/web/element_finder.h" #include "components/autofill_assistant/browser/web/web_controller_worker.h" namespace autofill_assistant { +class ElementFinderResult; // Worker class to check whether an element is on top, in all frames. class CheckOnTopWorker : public WebControllerWorker { diff --git a/chromium/components/autofill_assistant/browser/web/click_or_tap_worker.cc b/chromium/components/autofill_assistant/browser/web/click_or_tap_worker.cc index bde3b44d25f..e0c1cc5a34a 100644 --- a/chromium/components/autofill_assistant/browser/web/click_or_tap_worker.cc +++ b/chromium/components/autofill_assistant/browser/web/click_or_tap_worker.cc @@ -7,6 +7,7 @@ #include <vector> #include "base/time/time.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/web_controller_util.h" namespace autofill_assistant { diff --git a/chromium/components/autofill_assistant/browser/web/click_or_tap_worker.h b/chromium/components/autofill_assistant/browser/web/click_or_tap_worker.h index dbdc25bb125..7326f7ecf6b 100644 --- a/chromium/components/autofill_assistant/browser/web/click_or_tap_worker.h +++ b/chromium/components/autofill_assistant/browser/web/click_or_tap_worker.h @@ -15,11 +15,11 @@ #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/devtools/devtools/domains/types_input.h" #include "components/autofill_assistant/browser/devtools/devtools_client.h" -#include "components/autofill_assistant/browser/web/element_finder.h" #include "components/autofill_assistant/browser/web/element_position_getter.h" #include "components/autofill_assistant/browser/web/web_controller_worker.h" namespace autofill_assistant { +class ElementFinderResult; // Worker class for sending click or tap events. class ClickOrTapWorker : public WebControllerWorker { diff --git a/chromium/components/autofill_assistant/browser/web/css_element_finder.cc b/chromium/components/autofill_assistant/browser/web/css_element_finder.cc new file mode 100644 index 00000000000..e268a10b3a2 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/web/css_element_finder.cc @@ -0,0 +1,754 @@ +// Copyright 2022 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_assistant/browser/web/css_element_finder.h" + +#include <utility> + +#include "components/autofill_assistant/browser/devtools/devtools_client.h" +#include "components/autofill_assistant/browser/service.pb.h" +#include "components/autofill_assistant/browser/user_data.h" +#include "components/autofill_assistant/browser/user_data_util.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" +#include "components/autofill_assistant/browser/web/js_filter_builder.h" +#include "components/autofill_assistant/browser/web/web_controller_util.h" +#include "components/autofill_assistant/content/browser/content_autofill_assistant_driver.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/web_contents.h" + +namespace autofill_assistant { + +namespace { +// Javascript code to get document root element. +const char kGetDocumentElement[] = "document.documentElement;"; + +const char kGetArrayElement[] = "function(index) { return this[index]; }"; + +bool ConvertPseudoType(const PseudoType pseudo_type, + dom::PseudoType* pseudo_type_output) { + switch (pseudo_type) { + case PseudoType::UNDEFINED: + break; + case PseudoType::FIRST_LINE: + *pseudo_type_output = dom::PseudoType::FIRST_LINE; + return true; + case PseudoType::FIRST_LETTER: + *pseudo_type_output = dom::PseudoType::FIRST_LETTER; + return true; + case PseudoType::BEFORE: + *pseudo_type_output = dom::PseudoType::BEFORE; + return true; + case PseudoType::AFTER: + *pseudo_type_output = dom::PseudoType::AFTER; + return true; + case PseudoType::BACKDROP: + *pseudo_type_output = dom::PseudoType::BACKDROP; + return true; + case PseudoType::SELECTION: + *pseudo_type_output = dom::PseudoType::SELECTION; + return true; + case PseudoType::FIRST_LINE_INHERITED: + *pseudo_type_output = dom::PseudoType::FIRST_LINE_INHERITED; + return true; + case PseudoType::SCROLLBAR: + *pseudo_type_output = dom::PseudoType::SCROLLBAR; + return true; + case PseudoType::SCROLLBAR_THUMB: + *pseudo_type_output = dom::PseudoType::SCROLLBAR_THUMB; + return true; + case PseudoType::SCROLLBAR_BUTTON: + *pseudo_type_output = dom::PseudoType::SCROLLBAR_BUTTON; + return true; + case PseudoType::SCROLLBAR_TRACK: + *pseudo_type_output = dom::PseudoType::SCROLLBAR_TRACK; + return true; + case PseudoType::SCROLLBAR_TRACK_PIECE: + *pseudo_type_output = dom::PseudoType::SCROLLBAR_TRACK_PIECE; + return true; + case PseudoType::SCROLLBAR_CORNER: + *pseudo_type_output = dom::PseudoType::SCROLLBAR_CORNER; + return true; + case PseudoType::RESIZER: + *pseudo_type_output = dom::PseudoType::RESIZER; + return true; + case PseudoType::INPUT_LIST_BUTTON: + *pseudo_type_output = dom::PseudoType::INPUT_LIST_BUTTON; + return true; + } + return false; +} + +} // namespace + +CssElementFinder::CssElementFinder(content::WebContents* web_contents, + DevtoolsClient* devtools_client, + const UserData* user_data, + const ElementFinderResultType result_type, + const Selector& selector) + : web_contents_(web_contents), + devtools_client_(devtools_client), + user_data_(user_data), + result_type_(result_type), + selector_(selector) {} + +CssElementFinder::~CssElementFinder() = default; + +void CssElementFinder::Start(const ElementFinderResult& start_element, + Callback callback) { + callback_ = std::move(callback); + + selector_proto_ = selector_.proto; + ClientStatus resolve_status = + user_data::ResolveSelectorUserData(&selector_proto_, user_data_); + if (!resolve_status.ok()) { + SendResult(resolve_status, ElementFinderResult::EmptyResult()); + return; + } + + auto* frame = start_element.render_frame_host(); + if (frame == nullptr) { + frame = web_contents_->GetPrimaryMainFrame(); + } + current_frame_global_id_ = frame->GetGlobalId(); + current_frame_devtools_id_ = start_element.node_frame_id(); + frame_stack_ = start_element.frame_stack(); + + if (start_element.object_id().empty()) { + GetDocumentElement(); + } else { + current_matches_.emplace_back(start_element.object_id()); + ExecuteNextTask(); + } +} + +ElementFinderInfoProto CssElementFinder::GetLogInfo() const { + DCHECK(!callback_); // Run after finish. + + ElementFinderInfoProto info; + if (!client_status_.ok()) { + info.set_failed_filter_index_range_start(current_filter_index_range_start_); + info.set_failed_filter_index_range_end(next_filter_index_); + info.set_get_document_failed(get_document_failed_); + } + + return info; +} + +int CssElementFinder::GetBackendNodeId() const { + return backend_node_id_.value_or(0); +} + +void CssElementFinder::GiveUpWithError(const ClientStatus& status) { + DCHECK(!status.ok()); + if (!callback_) { + return; + } + + SendResult(status, ElementFinderResult::EmptyResult()); +} + +void CssElementFinder::ResultFound(const std::string& object_id) { + if (!callback_) { + return; + } + + devtools_client_->GetDOM()->DescribeNode( + dom::DescribeNodeParams::Builder().SetObjectId(object_id).Build(), + current_frame_devtools_id_, + base::BindOnce(&CssElementFinder::OnDescribeNodeForId, + weak_ptr_factory_.GetWeakPtr(), object_id)); +} + +void CssElementFinder::OnDescribeNodeForId( + const std::string& object_id, + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<dom::DescribeNodeResult> node_result) { + if (node_result && node_result->GetNode()) { + backend_node_id_ = node_result->GetNode()->GetBackendNodeId(); + } + BuildAndSendResult(object_id); +} + +void CssElementFinder::BuildAndSendResult(const std::string& object_id) { + ElementFinderResult result; + result.SetRenderFrameHostGlobalId(current_frame_global_id_); + result.SetObjectId(object_id); + result.SetBackendNodeId(backend_node_id_); + result.SetNodeFrameId(current_frame_devtools_id_); + result.SetFrameStack(frame_stack_); + + SendResult(OkClientStatus(), result); +} + +void CssElementFinder::SendResult(const ClientStatus& status, + const ElementFinderResult& result) { + client_status_ = status; + DCHECK(callback_); + std::move(callback_).Run(status, + std::make_unique<ElementFinderResult>(result)); +} + +void CssElementFinder::ExecuteNextTask() { + const auto& filters = selector_proto_.filters(); + + if (next_filter_index_ >= filters.size()) { + std::string object_id; + switch (result_type_) { + case ElementFinderResultType::kExactlyOneMatch: + if (!ConsumeOneMatchOrFail(object_id)) { + return; + } + break; + + case ElementFinderResultType::kAnyMatch: + if (!ConsumeMatchAtOrFail(0, object_id)) { + return; + } + break; + + case ElementFinderResultType::kMatchArray: + if (!ConsumeMatchArrayOrFail(object_id)) { + return; + } + break; + } + ResultFound(object_id); + return; + } + + current_filter_index_range_start_ = next_filter_index_; + const auto& filter = filters.Get(next_filter_index_); + switch (filter.filter_case()) { + case SelectorProto::Filter::kEnterFrame: { + std::string object_id; + if (!ConsumeOneMatchOrFail(object_id)) + return; + + // The above fails if there is more than one frame. To preserve + // backward-compatibility with the previous, lax behavior, callers must + // add pick_one before enter_frame. TODO(b/155264465): allow searching in + // more than one frame. + next_filter_index_++; + EnterFrame(object_id); + return; + } + + case SelectorProto::Filter::kPseudoType: { + std::vector<std::string> matches; + if (!ConsumeAllMatchesOrFail(matches)) + return; + + next_filter_index_++; + matching_pseudo_elements_ = true; + ResolvePseudoElement(filter.pseudo_type(), matches); + return; + } + + case SelectorProto::Filter::kNthMatch: { + // TODO(b/205676462): This could be done with javascript like in + // |SelectorObserver|. + std::string object_id; + if (!ConsumeMatchAtOrFail(filter.nth_match().index(), object_id)) + return; + + next_filter_index_++; + current_matches_ = {object_id}; + ExecuteNextTask(); + return; + } + + case SelectorProto::Filter::kCssSelector: + case SelectorProto::Filter::kInnerText: + case SelectorProto::Filter::kValue: + case SelectorProto::Filter::kProperty: + case SelectorProto::Filter::kBoundingBox: + case SelectorProto::Filter::kPseudoElementContent: + case SelectorProto::Filter::kMatchCssSelector: + case SelectorProto::Filter::kCssStyle: + case SelectorProto::Filter::kLabelled: + case SelectorProto::Filter::kOnTop: + case SelectorProto::Filter::kParent: { + std::vector<std::string> matches; + if (!ConsumeAllMatchesOrFail(matches)) + return; + + JsFilterBuilder js_filter; + for (int i = next_filter_index_; i < filters.size(); i++) { + if (!js_filter.AddFilter(filters.Get(i))) { + break; + } + next_filter_index_++; + } + ApplyJsFilters(js_filter, matches); + return; + } + + case SelectorProto::Filter::FILTER_NOT_SET: + VLOG(1) << __func__ << " Unset or unknown filter in " << filter << " in " + << selector_; + GiveUpWithError(ClientStatus(INVALID_SELECTOR)); + return; + } +} + +bool CssElementFinder::ConsumeOneMatchOrFail(std::string& object_id_out) { + if (current_matches_.size() > 1) { + VLOG(1) << __func__ << " Got " << current_matches_.size() << " matches for " + << selector_ << ", when only 1 was expected."; + GiveUpWithError(ClientStatus(TOO_MANY_ELEMENTS)); + return false; + } + if (current_matches_.empty()) { + GiveUpWithError(ClientStatus(ELEMENT_RESOLUTION_FAILED)); + return false; + } + + object_id_out = current_matches_[0]; + current_matches_.clear(); + return true; +} + +bool CssElementFinder::ConsumeMatchAtOrFail(size_t index, + std::string& object_id_out) { + if (index < current_matches_.size()) { + object_id_out = current_matches_[index]; + current_matches_.clear(); + return true; + } + + GiveUpWithError(ClientStatus(ELEMENT_RESOLUTION_FAILED)); + return false; +} + +bool CssElementFinder::ConsumeAllMatchesOrFail( + std::vector<std::string>& matches_out) { + if (!current_matches_.empty()) { + matches_out = std::move(current_matches_); + current_matches_.clear(); + return true; + } + GiveUpWithError(ClientStatus(ELEMENT_RESOLUTION_FAILED)); + return false; +} + +bool CssElementFinder::ConsumeMatchArrayOrFail(std::string& array_object_id) { + if (!current_matches_js_array_.empty()) { + array_object_id = current_matches_js_array_; + current_matches_js_array_.clear(); + return true; + } + + if (current_matches_.empty()) { + GiveUpWithError(ClientStatus(ELEMENT_RESOLUTION_FAILED)); + return false; + } + + MoveMatchesToJSArrayRecursive(/* index= */ 0); + return false; +} + +void CssElementFinder::MoveMatchesToJSArrayRecursive(size_t index) { + if (index >= current_matches_.size()) { + current_matches_.clear(); + ExecuteNextTask(); + return; + } + + // Push the value at |current_matches_[index]| to |current_matches_js_array_|. + std::string function; + std::vector<std::unique_ptr<runtime::CallArgument>> arguments; + if (index == 0) { + // Create an array containing a single element. + function = "function() { return [this]; }"; + } else { + // Add an element to an existing array. + function = "function(dest) { dest.push(this); }"; + AddRuntimeCallArgumentObjectId(current_matches_js_array_, &arguments); + } + + devtools_client_->GetRuntime()->CallFunctionOn( + runtime::CallFunctionOnParams::Builder() + .SetObjectId(current_matches_[index]) + .SetArguments(std::move(arguments)) + .SetFunctionDeclaration(function) + .Build(), + current_frame_devtools_id_, + base::BindOnce(&CssElementFinder::OnMoveMatchesToJSArrayRecursive, + weak_ptr_factory_.GetWeakPtr(), index)); +} + +void CssElementFinder::OnMoveMatchesToJSArrayRecursive( + size_t index, + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<runtime::CallFunctionOnResult> result) { + ClientStatus status = + CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__); + if (!status.ok()) { + VLOG(1) << __func__ << ": Failed to push value to JS array."; + GiveUpWithError(status); + return; + } + + // We just created an array which contains the first element. We store its ID + // in |current_matches_js_array_|. + if (index == 0 && + !SafeGetObjectId(result->GetResult(), ¤t_matches_js_array_)) { + VLOG(1) << __func__ << " Failed to get array ID."; + GiveUpWithError(ClientStatus(ELEMENT_RESOLUTION_FAILED)); + return; + } + + // Continue the recursion to push the other values into the array. + MoveMatchesToJSArrayRecursive(index + 1); +} + +void CssElementFinder::GetDocumentElement() { + devtools_client_->GetRuntime()->Evaluate( + std::string(kGetDocumentElement), current_frame_devtools_id_, + base::BindOnce(&CssElementFinder::OnGetDocumentElement, + weak_ptr_factory_.GetWeakPtr())); +} + +void CssElementFinder::OnGetDocumentElement( + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<runtime::EvaluateResult> result) { + ClientStatus status = + CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__); + if (!status.ok()) { + VLOG(1) << __func__ << " Failed to get document root element."; + get_document_failed_ = true; + GiveUpWithError(status); + return; + } + std::string object_id; + if (!SafeGetObjectId(result->GetResult(), &object_id)) { + VLOG(1) << __func__ << " Failed to get document root element."; + get_document_failed_ = true; + GiveUpWithError(ClientStatus(ELEMENT_RESOLUTION_FAILED)); + return; + } + + // Use the node as root for the rest of the evaluation. + current_matches_.emplace_back(object_id); + + ExecuteNextTask(); +} + +void CssElementFinder::ApplyJsFilters( + const JsFilterBuilder& builder, + const std::vector<std::string>& object_ids) { + DCHECK(!object_ids.empty()); // Guaranteed by ExecuteNextTask() + PrepareBatchTasks(object_ids.size()); + std::string function = builder.BuildFunction(); + for (size_t task_id = 0; task_id < object_ids.size(); task_id++) { + devtools_client_->GetRuntime()->CallFunctionOn( + runtime::CallFunctionOnParams::Builder() + .SetObjectId(object_ids[task_id]) + .SetArguments(builder.BuildArgumentList()) + .SetFunctionDeclaration(function) + .Build(), + current_frame_devtools_id_, + base::BindOnce(&CssElementFinder::OnApplyJsFilters, + weak_ptr_factory_.GetWeakPtr(), task_id)); + } +} + +void CssElementFinder::OnApplyJsFilters( + size_t task_id, + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<runtime::CallFunctionOnResult> result) { + if (!result) { + // It is possible for a document element to already exist, but not be + // available yet to query because the document hasn't been loaded. This + // results in OnQuerySelectorAll getting a nullptr result. For this specific + // call, it is expected. + VLOG(1) << __func__ << ": Context doesn't exist yet to query frame " + << frame_stack_.size() << " of " << selector_; + GiveUpWithError(ClientStatus(ELEMENT_RESOLUTION_FAILED)); + return; + } + ClientStatus status = + CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__); + if (!status.ok()) { + VLOG(1) << __func__ << ": Failed to query selector for frame " + << frame_stack_.size() << " of " << selector_ << ": " << status; + GiveUpWithError(status); + return; + } + + // The result can be empty (nothing found), an array (multiple matches + // found) or a single node. + std::string object_id; + if (!SafeGetObjectId(result->GetResult(), &object_id)) { + ReportNoMatchingElement(task_id); + return; + } + + if (result->GetResult()->HasSubtype() && + result->GetResult()->GetSubtype() == + runtime::RemoteObjectSubtype::ARRAY) { + ReportMatchingElementsArray(task_id, object_id); + return; + } + + ReportMatchingElement(task_id, object_id); +} + +void CssElementFinder::ResolvePseudoElement( + PseudoType proto_pseudo_type, + const std::vector<std::string>& object_ids) { + dom::PseudoType pseudo_type; + if (!ConvertPseudoType(proto_pseudo_type, &pseudo_type)) { + VLOG(1) << __func__ << ": Unsupported pseudo-type " + << PseudoTypeName(proto_pseudo_type); + GiveUpWithError(ClientStatus(INVALID_ACTION)); + return; + } + + DCHECK(!object_ids.empty()); // Guaranteed by ExecuteNextTask() + PrepareBatchTasks(object_ids.size()); + for (size_t task_id = 0; task_id < object_ids.size(); task_id++) { + devtools_client_->GetDOM()->DescribeNode( + dom::DescribeNodeParams::Builder() + .SetObjectId(object_ids[task_id]) + .Build(), + current_frame_devtools_id_, + base::BindOnce(&CssElementFinder::OnDescribeNodeForPseudoElement, + weak_ptr_factory_.GetWeakPtr(), pseudo_type, task_id)); + } +} + +void CssElementFinder::OnDescribeNodeForPseudoElement( + dom::PseudoType pseudo_type, + size_t task_id, + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<dom::DescribeNodeResult> result) { + if (!result || !result->GetNode()) { + VLOG(1) << __func__ << " Failed to describe the node for pseudo element."; + GiveUpWithError( + UnexpectedDevtoolsErrorStatus(reply_status, __FILE__, __LINE__)); + return; + } + + auto* node = result->GetNode(); + if (node->HasPseudoElements()) { + for (const auto& pseudo_element : *(node->GetPseudoElements())) { + if (pseudo_element->HasPseudoType() && + pseudo_element->GetPseudoType() == pseudo_type) { + devtools_client_->GetDOM()->ResolveNode( + dom::ResolveNodeParams::Builder() + .SetBackendNodeId(pseudo_element->GetBackendNodeId()) + .Build(), + current_frame_devtools_id_, + base::BindOnce(&CssElementFinder::OnResolveNodeForPseudoElement, + weak_ptr_factory_.GetWeakPtr(), task_id)); + return; + } + } + } + + ReportNoMatchingElement(task_id); +} + +void CssElementFinder::OnResolveNodeForPseudoElement( + size_t task_id, + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<dom::ResolveNodeResult> result) { + if (result && result->GetObject() && result->GetObject()->HasObjectId()) { + ReportMatchingElement(task_id, result->GetObject()->GetObjectId()); + return; + } + + ReportNoMatchingElement(task_id); +} + +void CssElementFinder::EnterFrame(const std::string& object_id) { + devtools_client_->GetDOM()->DescribeNode( + dom::DescribeNodeParams::Builder().SetObjectId(object_id).Build(), + current_frame_devtools_id_, + base::BindOnce(&CssElementFinder::OnDescribeNodeForFrame, + weak_ptr_factory_.GetWeakPtr(), object_id)); +} + +void CssElementFinder::OnDescribeNodeForFrame( + const std::string& object_id, + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<dom::DescribeNodeResult> result) { + if (!result || !result->GetNode()) { + VLOG(1) << __func__ << " Failed to describe the node."; + GiveUpWithError( + UnexpectedDevtoolsErrorStatus(reply_status, __FILE__, __LINE__)); + return; + } + + auto* node = result->GetNode(); + std::vector<int> backend_ids; + + if (node->GetNodeName() == "IFRAME") { + // See: b/206647825 + if (!node->HasFrameId()) { + NOTREACHED() << "Frame without ID"; // Ensure all frames have an id. + GiveUpWithError(ClientStatus(FRAME_HOST_NOT_FOUND)); + return; + } + + frame_stack_.push_back({object_id, current_frame_devtools_id_}); + + auto* frame = + FindCorrespondingRenderFrameHost(node->GetFrameId(), web_contents_); + if (!frame) { + VLOG(1) << __func__ << " Failed to find corresponding owner frame."; + GiveUpWithError(ClientStatus(FRAME_HOST_NOT_FOUND)); + return; + } + current_frame_global_id_ = frame->GetGlobalId(); + + if (node->HasContentDocument()) { + // If the frame has a ContentDocument it's considered a local frame. In + // this case, current frame doesn't change and can directly use the + // content document as root for the evaluation. + backend_ids.emplace_back(node->GetContentDocument()->GetBackendNodeId()); + } else { + current_frame_devtools_id_ = node->GetFrameId(); + // Kick off another find element chain to walk down the OOP iFrame. + GetDocumentElement(); + return; + } + } + + if (node->HasShadowRoots()) { + // TODO(crbug.com/806868): Support multiple shadow roots. + backend_ids.emplace_back( + node->GetShadowRoots()->front()->GetBackendNodeId()); + } + + if (!backend_ids.empty()) { + devtools_client_->GetDOM()->ResolveNode( + dom::ResolveNodeParams::Builder() + .SetBackendNodeId(backend_ids[0]) + .Build(), + current_frame_devtools_id_, + base::BindOnce(&CssElementFinder::OnResolveNode, + weak_ptr_factory_.GetWeakPtr())); + return; + } + + // Element was not a frame and didn't have shadow dom. This is unexpected, but + // to remain backward compatible, don't complain and just continue filtering + // with the current element as root. + current_matches_.emplace_back(object_id); + ExecuteNextTask(); +} + +void CssElementFinder::OnResolveNode( + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<dom::ResolveNodeResult> result) { + if (!result || !result->GetObject() || !result->GetObject()->HasObjectId()) { + VLOG(1) << __func__ << " Failed to resolve object id from backend id."; + GiveUpWithError( + UnexpectedDevtoolsErrorStatus(reply_status, __FILE__, __LINE__)); + return; + } + + // Use the node as root for the rest of the evaluation. + current_matches_.emplace_back(result->GetObject()->GetObjectId()); + ExecuteNextTask(); +} + +void CssElementFinder::PrepareBatchTasks(int n) { + tasks_results_.clear(); + tasks_results_.resize(n); +} + +void CssElementFinder::ReportMatchingElement(size_t task_id, + const std::string& object_id) { + tasks_results_[task_id] = + std::make_unique<std::vector<std::string>>(1, object_id); + MaybeFinalizeBatchTasks(); +} + +void CssElementFinder::ReportNoMatchingElement(size_t task_id) { + tasks_results_[task_id] = std::make_unique<std::vector<std::string>>(); + MaybeFinalizeBatchTasks(); +} + +void CssElementFinder::ReportMatchingElementsArray( + size_t task_id, + const std::string& array_object_id) { + // Recursively add each element ID to a vector then report it as this task + // result. + ReportMatchingElementsArrayRecursive( + task_id, array_object_id, std::make_unique<std::vector<std::string>>(), + /* index= */ 0); +} + +void CssElementFinder::ReportMatchingElementsArrayRecursive( + size_t task_id, + const std::string& array_object_id, + std::unique_ptr<std::vector<std::string>> acc, + int index) { + std::vector<std::unique_ptr<runtime::CallArgument>> arguments; + AddRuntimeCallArgument(index, &arguments); + devtools_client_->GetRuntime()->CallFunctionOn( + runtime::CallFunctionOnParams::Builder() + .SetObjectId(array_object_id) + .SetArguments(std::move(arguments)) + .SetFunctionDeclaration(std::string(kGetArrayElement)) + .Build(), + current_frame_devtools_id_, + base::BindOnce(&CssElementFinder::OnReportMatchingElementsArrayRecursive, + weak_ptr_factory_.GetWeakPtr(), task_id, array_object_id, + std::move(acc), index)); +} + +void CssElementFinder::OnReportMatchingElementsArrayRecursive( + size_t task_id, + const std::string& array_object_id, + std::unique_ptr<std::vector<std::string>> acc, + int index, + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<runtime::CallFunctionOnResult> result) { + ClientStatus status = + CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__); + if (!status.ok()) { + VLOG(1) << __func__ << ": Failed to get element from array for " + << selector_; + GiveUpWithError(status); + return; + } + + std::string object_id; + if (!SafeGetObjectId(result->GetResult(), &object_id)) { + // We've reached the end of the array. + tasks_results_[task_id] = std::move(acc); + MaybeFinalizeBatchTasks(); + return; + } + + acc->emplace_back(object_id); + + // Fetch the next element. + ReportMatchingElementsArrayRecursive(task_id, array_object_id, std::move(acc), + index + 1); +} + +void CssElementFinder::MaybeFinalizeBatchTasks() { + // Return early if one of the tasks is still pending. + for (const auto& result : tasks_results_) { + if (!result) { + return; + } + } + + // Add all matching elements to current_matches_. + for (const auto& result : tasks_results_) { + current_matches_.insert(current_matches_.end(), result->begin(), + result->end()); + } + tasks_results_.clear(); + + ExecuteNextTask(); +} + +} // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/web/css_element_finder.h b/chromium/components/autofill_assistant/browser/web/css_element_finder.h new file mode 100644 index 00000000000..558f0e27ab1 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/web/css_element_finder.h @@ -0,0 +1,269 @@ +// Copyright 2022 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_ASSISTANT_BROWSER_WEB_CSS_ELEMENT_FINDER_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_WEB_CSS_ELEMENT_FINDER_H_ + +#include <string> +#include <vector> + +#include "base/callback_forward.h" +#include "base/memory/raw_ptr.h" +#include "base/memory/weak_ptr.h" +#include "components/autofill_assistant/browser/action_value.pb.h" +#include "components/autofill_assistant/browser/client_status.h" +#include "components/autofill_assistant/browser/devtools/devtools/domains/types_dom.h" +#include "components/autofill_assistant/browser/devtools/devtools/domains/types_runtime.h" +#include "components/autofill_assistant/browser/devtools/devtools_client.h" +#include "components/autofill_assistant/browser/selector.h" +#include "components/autofill_assistant/browser/web/base_element_finder.h" +#include "components/autofill_assistant/browser/web/element.h" +#include "components/autofill_assistant/browser/web/element_finder_result_type.h" +#include "components/autofill_assistant/browser/web/js_filter_builder.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace content { +class WebContents; +struct GlobalRenderFrameHostId; +} // namespace content + +namespace autofill_assistant { +class DevtoolsClient; +class UserData; +class ElementFinderResult; + +class CssElementFinder : public BaseElementFinder { + public: + CssElementFinder(content::WebContents* web_contents, + DevtoolsClient* devtools_client, + const UserData* user_data, + const ElementFinderResultType result_type, + const Selector& selector); + ~CssElementFinder() override; + + CssElementFinder(const CssElementFinder&) = delete; + CssElementFinder& operator=(const CssElementFinder&) = delete; + + void Start(const ElementFinderResult& start_element, + BaseElementFinder::Callback callback) override; + + ElementFinderInfoProto GetLogInfo() const override; + + // Returns the backend node id of the result if the proto contains + // |semantic_information|, or 0. + int GetBackendNodeId() const override; + + private: + // Returns the given status and no element. This expects an error status. + void GiveUpWithError(const ClientStatus& status); + + // Found a valid result. + void ResultFound(const std::string& object_id); + + // Builds a result from the current state of the finder and returns it with + // an ok status. + void BuildAndSendResult(const std::string& object_id); + + // Call |callback_| with the |status| and |result|. + void SendResult(const ClientStatus& status, + const ElementFinderResult& result); + + // Figures out what to do next given the current state. + // + // Most background operations in this worker end by updating the state and + // calling ExecuteNextTask() again either directly or through Report*(). + void ExecuteNextTask(); + + // Prepare a batch of |n| tasks that are sent at the same time to compute + // one or more matching elements. + // + // After calling this, Report*(i, ...) should be called *exactly once* for + // all 0 <= i < n to report the tasks results. + // + // Once all tasks reported their result, the object ID of all matching + // elements will be added to |current_matches_| and ExecuteNextTask() will + // be called. + void PrepareBatchTasks(int n); + + // Report that task with ID |task_id| didn't match any element. + void ReportNoMatchingElement(size_t task_id); + + // Report that task with ID |task_id| matched a single element with ID + // |object_id|. + void ReportMatchingElement(size_t task_id, const std::string& object_id); + + // Report that task with ID |task_id| matched multiple elements that are + // stored in the JS array with ID |object_id|. + void ReportMatchingElementsArray(size_t task_id, + const std::string& array_object_id); + void ReportMatchingElementsArrayRecursive( + size_t task_id, + const std::string& array_object_id, + std::unique_ptr<std::vector<std::string>> acc, + int index); + void OnReportMatchingElementsArrayRecursive( + size_t task_id, + const std::string& array_object_id, + std::unique_ptr<std::vector<std::string>> acc, + int index, + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<runtime::CallFunctionOnResult> result); + + // If all batch tasks reported their result, add all tasks results to + // |current_matches_| then call ExecuteNextTask(). + void MaybeFinalizeBatchTasks(); + + // Make sure there's exactly one match, set it |object_id_out| then return + // true. + // + // If there are too many or too few matches, this function sends an error + // and returns false. + // + // If this returns true, continue processing. If this returns false, return + // from ExecuteNextTask(). ExecuteNextTask() will be called again once the + // required data is available. + bool ConsumeOneMatchOrFail(std::string& object_id_out); + + // Make sure there's at least |index + 1| matches, take the one at that + // index and put it in |object_id_out|, then return true. + // + // If there are not enough matches, send an error response and return false. + bool ConsumeMatchAtOrFail(size_t index, std::string& object_id_out); + + // Make sure there's at least one match and move them all into + // |matches_out|. + // + // If there are no matches, send an error response and return false. + // If there are not enough matches yet, fetch them in the background and + // return false. This calls ExecuteNextTask() once matches have been + // fetched. + // + // If this returns true, continue processing. If this returns false, return + // from ExecuteNextTask(). ExecuteNextTask() will be called again once the + // required data is available. + bool ConsumeAllMatchesOrFail(std::vector<std::string>& matches_out); + + // Make sure there's at least one match and move them all into a single + // array. + // + // If there are no matches, call SendResult() and return false. + // + // If there are matches, return false directly and move the matches into + // an JS array in the background. ExecuteNextTask() is called again + // once the background tasks have executed, and calling this will return + // true and write the JS array id to |array_object_id_out|. + bool ConsumeMatchArrayOrFail(std::string& array_object_id_out); + + void OnConsumeMatchArray( + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<runtime::CallFunctionOnResult> result); + + // Gets a document element from the current frame and us it as root for the + // rest of the tasks, then call ExecuteNextTask(). + void GetDocumentElement(); + void OnGetDocumentElement(const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<runtime::EvaluateResult> result); + + // Handle Javascript filters + void ApplyJsFilters(const JsFilterBuilder& builder, + const std::vector<std::string>& object_ids); + void OnApplyJsFilters(size_t task_id, + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<runtime::CallFunctionOnResult> result); + + // Handle PSEUDO_TYPE + void ResolvePseudoElement(PseudoType pseudo_type, + const std::vector<std::string>& object_ids); + void OnDescribeNodeForPseudoElement( + dom::PseudoType pseudo_type, + size_t task_id, + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<dom::DescribeNodeResult> result); + void OnResolveNodeForPseudoElement( + size_t task_id, + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<dom::ResolveNodeResult> result); + + // Handle ENTER_FRAME + void EnterFrame(const std::string& object_id); + void OnDescribeNodeForFrame(const std::string& object_id, + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<dom::DescribeNodeResult> result); + void OnResolveNode(const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<dom::ResolveNodeResult> result); + + // Fill |current_matches_js_array_| with the values in |current_matches_| + // starting from |index|, then clear |current_matches_| and call + // ExecuteNextTask(). + void MoveMatchesToJSArrayRecursive(size_t index); + + void OnMoveMatchesToJSArrayRecursive( + size_t index, + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<runtime::CallFunctionOnResult> result); + + void OnDescribeNodeForId( + const std::string& object_id, + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<dom::DescribeNodeResult> node_result); + + const raw_ptr<content::WebContents> web_contents_; + const raw_ptr<DevtoolsClient> devtools_client_; + const raw_ptr<const UserData> user_data_; + const ElementFinderResultType result_type_; + const Selector selector_; + BaseElementFinder::Callback callback_; + + // The modified selector to use going forward. This is guaranteed to have + // resolved any filters that need a data lookup. + SelectorProto selector_proto_; + + // The index of the next filter to process, in selector__proto_.filters. + int next_filter_index_ = 0; + + // Getting the document failed. Used for error reporting. + bool get_document_failed_ = false; + + // The currently worked on filters are starting at this index.. + int current_filter_index_range_start_ = -1; + + // Pointer to the current frame + content::GlobalRenderFrameHostId current_frame_global_id_; + + // The frame id to use to execute devtools Javascript calls within the + // context of the frame. Might be empty if no frame id needs to be + // specified. + std::string current_frame_devtools_id_; + + // Object IDs of the current set matching elements. Cleared once it's used + // to query or filter. + std::vector<std::string> current_matches_; + + // Object ID of the JavaScript array of the currently matching elements. In + // practice, this is used by ConsumeMatchArrayOrFail() to convert + // |current_matches_| to a JavaScript array. + std::string current_matches_js_array_; + + // True if current_matches are pseudo-elements. + bool matching_pseudo_elements_ = false; + + // The result of the background tasks. |tasks_results_[i]| contains the + // elements matched by task i, or nullptr if the task is still running. + std::vector<std::unique_ptr<std::vector<std::string>>> tasks_results_; + + std::vector<JsObjectIdentifier> frame_stack_; + + // The backend node id of the result. Only gets assigned if required, when + // this will be used for a comparison with the result of a semantic run. + absl::optional<int> backend_node_id_; + + // The client status of the last run. + ClientStatus client_status_; + + base::WeakPtrFactory<CssElementFinder> weak_ptr_factory_{this}; +}; + +} // namespace autofill_assistant + +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_WEB_CSS_ELEMENT_FINDER_H_ diff --git a/chromium/components/autofill_assistant/browser/web/element.cc b/chromium/components/autofill_assistant/browser/web/element.cc index 8fb951d48b3..526ca8a6fbb 100644 --- a/chromium/components/autofill_assistant/browser/web/element.cc +++ b/chromium/components/autofill_assistant/browser/web/element.cc @@ -50,19 +50,21 @@ content::RenderFrameHost* FindCorrespondingRenderFrameHost( const std::string& frame_id, content::WebContents* web_contents) { if (frame_id.empty()) { - return web_contents->GetMainFrame(); + return web_contents->GetPrimaryMainFrame(); } content::RenderFrameHost* result = nullptr; - web_contents->GetMainFrame()->ForEachRenderFrameHost(base::BindRepeating( - [](const std::string& frame_id, content::RenderFrameHost** result, - content::RenderFrameHost* render_frame_host) { - if (render_frame_host->GetDevToolsFrameToken().ToString() == frame_id) { - *result = render_frame_host; - return content::RenderFrameHost::FrameIterationAction::kStop; - } - return content::RenderFrameHost::FrameIterationAction::kContinue; - }, - frame_id, &result)); + web_contents->GetPrimaryMainFrame()->ForEachRenderFrameHost( + base::BindRepeating( + [](const std::string& frame_id, content::RenderFrameHost** result, + content::RenderFrameHost* render_frame_host) { + if (render_frame_host->GetDevToolsFrameToken().ToString() == + frame_id) { + *result = render_frame_host; + return content::RenderFrameHost::FrameIterationAction::kStop; + } + return content::RenderFrameHost::FrameIterationAction::kContinue; + }, + frame_id, &result)); return result; } diff --git a/chromium/components/autofill_assistant/browser/web/element.h b/chromium/components/autofill_assistant/browser/web/element.h index 04aef40d18d..fa360ab696a 100644 --- a/chromium/components/autofill_assistant/browser/web/element.h +++ b/chromium/components/autofill_assistant/browser/web/element.h @@ -11,6 +11,7 @@ #include "content/public/browser/global_routing_id.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents.h" +#include "third_party/abseil-cpp/absl/types/optional.h" namespace autofill_assistant { @@ -25,6 +26,9 @@ struct JsObjectIdentifier { // context of the frame. Might be empty if no frame id needs to be // specified. std::string node_frame_id; + + // The node id of this object. This is only available for nodes. + absl::optional<int> backend_node_id; }; // DomObjectFrameStack contains all data required to use an object including diff --git a/chromium/components/autofill_assistant/browser/web/element_action_util.cc b/chromium/components/autofill_assistant/browser/web/element_action_util.cc index 5db0c8b39e3..ee44dab5d4a 100644 --- a/chromium/components/autofill_assistant/browser/web/element_action_util.cc +++ b/chromium/components/autofill_assistant/browser/web/element_action_util.cc @@ -7,7 +7,7 @@ #include "base/callback.h" #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/service.pb.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/web_controller.h" namespace autofill_assistant { diff --git a/chromium/components/autofill_assistant/browser/web/element_action_util.h b/chromium/components/autofill_assistant/browser/web/element_action_util.h index a7cebe63686..751f5a36d5d 100644 --- a/chromium/components/autofill_assistant/browser/web/element_action_util.h +++ b/chromium/components/autofill_assistant/browser/web/element_action_util.h @@ -11,9 +11,10 @@ #include "base/callback.h" #include "base/logging.h" #include "components/autofill_assistant/browser/client_status.h" -#include "components/autofill_assistant/browser/web/element_finder.h" namespace autofill_assistant { +class ElementFinderResult; + namespace element_action_util { namespace { diff --git a/chromium/components/autofill_assistant/browser/web/element_action_util_unittest.cc b/chromium/components/autofill_assistant/browser/web/element_action_util_unittest.cc index 6075341e6df..63ba145860a 100644 --- a/chromium/components/autofill_assistant/browser/web/element_action_util_unittest.cc +++ b/chromium/components/autofill_assistant/browser/web/element_action_util_unittest.cc @@ -9,7 +9,7 @@ #include "base/test/gmock_callback_support.h" #include "components/autofill_assistant/browser/action_value.pb.h" #include "components/autofill_assistant/browser/actions/action_test_utils.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/mock_web_controller.h" #include "testing/gmock/include/gmock/gmock.h" #include "url/gurl.h" diff --git a/chromium/components/autofill_assistant/browser/web/element_finder.cc b/chromium/components/autofill_assistant/browser/web/element_finder.cc index 0a648102d34..378a3d6dc9d 100644 --- a/chromium/components/autofill_assistant/browser/web/element_finder.cc +++ b/chromium/components/autofill_assistant/browser/web/element_finder.cc @@ -6,116 +6,16 @@ #include <utility> -#include "base/barrier_callback.h" -#include "base/time/time.h" #include "components/autofill_assistant/browser/devtools/devtools_client.h" #include "components/autofill_assistant/browser/service.pb.h" #include "components/autofill_assistant/browser/user_data.h" -#include "components/autofill_assistant/browser/user_data_util.h" -#include "components/autofill_assistant/browser/web/element.h" -#include "components/autofill_assistant/browser/web/js_filter_builder.h" -#include "components/autofill_assistant/browser/web/web_controller_util.h" -#include "components/autofill_assistant/content/browser/content_autofill_assistant_driver.h" -#include "content/public/browser/global_routing_id.h" -#include "content/public/browser/render_frame_host.h" +#include "components/autofill_assistant/browser/web/base_element_finder.h" +#include "components/autofill_assistant/browser/web/css_element_finder.h" +#include "components/autofill_assistant/browser/web/semantic_element_finder.h" #include "content/public/browser/web_contents.h" namespace autofill_assistant { -namespace { -// Javascript code to get document root element. -const char kGetDocumentElement[] = "document.documentElement;"; - -const char kGetArrayElement[] = "function(index) { return this[index]; }"; - -bool ConvertPseudoType(const PseudoType pseudo_type, - dom::PseudoType* pseudo_type_output) { - switch (pseudo_type) { - case PseudoType::UNDEFINED: - break; - case PseudoType::FIRST_LINE: - *pseudo_type_output = dom::PseudoType::FIRST_LINE; - return true; - case PseudoType::FIRST_LETTER: - *pseudo_type_output = dom::PseudoType::FIRST_LETTER; - return true; - case PseudoType::BEFORE: - *pseudo_type_output = dom::PseudoType::BEFORE; - return true; - case PseudoType::AFTER: - *pseudo_type_output = dom::PseudoType::AFTER; - return true; - case PseudoType::BACKDROP: - *pseudo_type_output = dom::PseudoType::BACKDROP; - return true; - case PseudoType::SELECTION: - *pseudo_type_output = dom::PseudoType::SELECTION; - return true; - case PseudoType::FIRST_LINE_INHERITED: - *pseudo_type_output = dom::PseudoType::FIRST_LINE_INHERITED; - return true; - case PseudoType::SCROLLBAR: - *pseudo_type_output = dom::PseudoType::SCROLLBAR; - return true; - case PseudoType::SCROLLBAR_THUMB: - *pseudo_type_output = dom::PseudoType::SCROLLBAR_THUMB; - return true; - case PseudoType::SCROLLBAR_BUTTON: - *pseudo_type_output = dom::PseudoType::SCROLLBAR_BUTTON; - return true; - case PseudoType::SCROLLBAR_TRACK: - *pseudo_type_output = dom::PseudoType::SCROLLBAR_TRACK; - return true; - case PseudoType::SCROLLBAR_TRACK_PIECE: - *pseudo_type_output = dom::PseudoType::SCROLLBAR_TRACK_PIECE; - return true; - case PseudoType::SCROLLBAR_CORNER: - *pseudo_type_output = dom::PseudoType::SCROLLBAR_CORNER; - return true; - case PseudoType::RESIZER: - *pseudo_type_output = dom::PseudoType::RESIZER; - return true; - case PseudoType::INPUT_LIST_BUTTON: - *pseudo_type_output = dom::PseudoType::INPUT_LIST_BUTTON; - return true; - } - return false; -} - -void AddHostToList(std::vector<content::GlobalRenderFrameHostId>& host_ids, - content::RenderFrameHost* host) { - host_ids.push_back(host->GetGlobalId()); -} - -ElementFinderInfoProto::SemanticInferenceStatus -NodeDataStatusToSemanticInferenceStatus( - mojom::NodeDataStatus node_data_status) { - switch (node_data_status) { - case mojom::NodeDataStatus::kSuccess: - return ElementFinderInfoProto::SUCCESS; - case mojom::NodeDataStatus::kUnexpectedError: - return ElementFinderInfoProto::UNEXPECTED_ERROR; - case mojom::NodeDataStatus::kInitializationError: - return ElementFinderInfoProto::INITIALIZATION_ERROR; - case mojom::NodeDataStatus::kModelLoadError: - return ElementFinderInfoProto::MODEL_LOAD_ERROR; - case mojom::NodeDataStatus::kModelLoadTimeout: - return ElementFinderInfoProto::MODEL_LOAD_TIMEOUT; - } -} - -} // namespace - -ElementFinderResult::ElementFinderResult() = default; - -ElementFinderResult::~ElementFinderResult() = default; - -ElementFinderResult::ElementFinderResult(const ElementFinderResult&) = default; - -ElementFinderResult ElementFinderResult::EmptyResult() { - return ElementFinderResult(); -} - ElementFinder::ElementFinder( content::WebContents* web_contents, DevtoolsClient* devtools_client, @@ -123,7 +23,7 @@ ElementFinder::ElementFinder( ProcessedActionStatusDetailsProto* log_info, AnnotateDomModelService* annotate_dom_model_service, const Selector& selector, - ResultType result_type) + ElementFinderResultType result_type) : web_contents_(web_contents), devtools_client_(devtools_client), user_data_(user_data), @@ -178,7 +78,7 @@ void ElementFinder::Start(const ElementFinderResult& start_element, void ElementFinder::AddAndStartRunner( const ElementFinderResult& start_element, - std::unique_ptr<ElementFinderBase> runner) { + std::unique_ptr<BaseElementFinder> runner) { auto* runner_ptr = runner.get(); runners_.emplace_back(std::move(runner)); results_.resize(runners_.size()); @@ -239,882 +139,4 @@ void ElementFinder::OnResult(size_t index, SendResult(results_[0].first, std::move(results_[0].second)); } -ElementFinder::ElementFinderBase::~ElementFinderBase() = default; - -ElementFinder::SemanticElementFinder::SemanticElementFinder( - content::WebContents* web_contents, - DevtoolsClient* devtools_client, - AnnotateDomModelService* annotate_dom_model_service, - const Selector& selector) - : web_contents_(web_contents), - devtools_client_(devtools_client), - annotate_dom_model_service_(annotate_dom_model_service), - selector_(selector) { - DCHECK(annotate_dom_model_service_); -} -ElementFinder::SemanticElementFinder::~SemanticElementFinder() = default; - -void ElementFinder::SemanticElementFinder::GiveUpWithError( - const ClientStatus& status) { - DCHECK(!status.ok()); - if (!callback_) { - return; - } - - SendResult(status, ElementFinderResult::EmptyResult()); -} - -void ElementFinder::SemanticElementFinder::ResultFound( - content::RenderFrameHost* render_frame_host, - const std::string& object_id) { - if (!callback_) { - return; - } - - ElementFinderResult result; - result.SetRenderFrameHost(render_frame_host); - result.SetObjectId(object_id); - - SendResult(OkClientStatus(), result); -} - -void ElementFinder::SemanticElementFinder::SendResult( - const ClientStatus& status, - const ElementFinderResult& result) { - DCHECK(callback_); - std::move(callback_).Run(status, - std::make_unique<ElementFinderResult>(result)); -} - -void ElementFinder::SemanticElementFinder::Start( - const ElementFinderResult& start_element, - Callback callback) { - callback_ = std::move(callback); - - auto* start_frame = start_element.render_frame_host(); - if (!start_frame) { - start_frame = web_contents_->GetMainFrame(); - } - RunAnnotateDomModel(start_frame); -} - -ElementFinderInfoProto ElementFinder::SemanticElementFinder::GetLogInfo() - const { - DCHECK(!callback_); // Run after finish. - - ElementFinderInfoProto info; - DCHECK(selector_.proto.has_semantic_information()); - for (auto node_data_status : node_data_frame_status_) { - info.mutable_semantic_inference_result()->add_status_per_frame( - NodeDataStatusToSemanticInferenceStatus(node_data_status)); - } - for (const auto& semantic_node_result : semantic_node_results_) { - auto* predicted_element = - info.mutable_semantic_inference_result()->add_predicted_elements(); - predicted_element->set_backend_node_id( - semantic_node_result.backend_node_id()); - *predicted_element->mutable_semantic_information() = - selector_.proto.semantic_information(); - // TODO(b/217160707): For the ignore_objective case this is not correct - // and the inferred objective should be returned from the Agent and used - // here. - } - - return info; -} - -int ElementFinder::SemanticElementFinder::GetBackendNodeId() const { - if (semantic_node_results_.empty()) { - return 0; - } - return semantic_node_results_[0].backend_node_id(); -} - -void ElementFinder::SemanticElementFinder::RunAnnotateDomModel( - content::RenderFrameHost* start_frame) { - std::vector<content::GlobalRenderFrameHostId> host_ids; - start_frame->ForEachRenderFrameHost( - base::BindRepeating(&AddHostToList, std::ref(host_ids))); - const auto run_on_frame = - base::BarrierCallback<std::vector<GlobalBackendNodeId>>( - host_ids.size(), - base::BindOnce(&SemanticElementFinder::OnRunAnnotateDomModel, - weak_ptr_factory_.GetWeakPtr())); - for (const auto& host_id : host_ids) { - RunAnnotateDomModelOnFrame(host_id, run_on_frame); - } -} - -void ElementFinder::SemanticElementFinder::RunAnnotateDomModelOnFrame( - const content::GlobalRenderFrameHostId& host_id, - base::OnceCallback<void(std::vector<GlobalBackendNodeId>)> callback) { - content::RenderFrameHost* render_frame_host = - content::RenderFrameHost::FromID(host_id); - if (!render_frame_host) { - std::move(callback).Run(std::vector<GlobalBackendNodeId>()); - return; - } - - auto* driver = ContentAutofillAssistantDriver::GetOrCreateForRenderFrameHost( - render_frame_host, annotate_dom_model_service_); - if (!driver) { - NOTREACHED(); - std::move(callback).Run(std::vector<GlobalBackendNodeId>()); - return; - } - - driver->GetAutofillAssistantAgent()->GetSemanticNodes( - selector_.proto.semantic_information().semantic_role(), - selector_.proto.semantic_information().objective(), - selector_.proto.semantic_information().ignore_objective(), - base::Milliseconds( - selector_.proto.semantic_information().model_timeout_ms()), - base::BindOnce(&SemanticElementFinder::OnRunAnnotateDomModelOnFrame, - weak_ptr_factory_.GetWeakPtr(), host_id, - std::move(callback))); -} - -void ElementFinder::SemanticElementFinder::OnRunAnnotateDomModelOnFrame( - const content::GlobalRenderFrameHostId& host_id, - base::OnceCallback<void(std::vector<GlobalBackendNodeId>)> callback, - mojom::NodeDataStatus status, - const std::vector<NodeData>& node_data) { - node_data_frame_status_.emplace_back(status); - - std::vector<GlobalBackendNodeId> node_ids; - for (const auto& node : node_data) { - node_ids.emplace_back(GlobalBackendNodeId(host_id, node.backend_node_id)); - } - std::move(callback).Run(node_ids); -} - -void ElementFinder::SemanticElementFinder::OnRunAnnotateDomModel( - const std::vector<std::vector<GlobalBackendNodeId>>& all_nodes) { - for (const auto& node_ids : all_nodes) { - semantic_node_results_.insert(semantic_node_results_.end(), - node_ids.begin(), node_ids.end()); - } - - // For now we only support finding a single element. - // TODO(b/224746702): Emit multiple ResolveNode calls for the case where the - // result type is not ResultType::kExactlyOneMatch. - if (semantic_node_results_.size() > 1) { - VLOG(1) << __func__ << " Got " << semantic_node_results_.size() - << " matches for " << selector_ << ", when only 1 was expected."; - GiveUpWithError(ClientStatus(TOO_MANY_ELEMENTS)); - return; - } - if (semantic_node_results_.empty()) { - GiveUpWithError(ClientStatus(ELEMENT_RESOLUTION_FAILED)); - return; - } - - // We need to set the empty string for the frame id. The expectation is that - // backend node ids are global and devtools is able to resolve the node - // without an explicit frame id. - devtools_client_->GetDOM()->ResolveNode( - dom::ResolveNodeParams::Builder() - .SetBackendNodeId(semantic_node_results_[0].backend_node_id()) - .Build(), - /* current_frame_id= */ std::string(), - base::BindOnce(&SemanticElementFinder::OnResolveNodeForAnnotateDom, - weak_ptr_factory_.GetWeakPtr(), - semantic_node_results_[0].host_id())); -} - -void ElementFinder::SemanticElementFinder::OnResolveNodeForAnnotateDom( - content::GlobalRenderFrameHostId host_id, - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<dom::ResolveNodeResult> result) { - if (result && result->GetObject() && result->GetObject()->HasObjectId()) { - ResultFound(content::RenderFrameHost::FromID(host_id), - result->GetObject()->GetObjectId()); - return; - } - SendResult(ClientStatus(ELEMENT_RESOLUTION_FAILED), - ElementFinderResult::EmptyResult()); -} - -ElementFinder::CssElementFinder::CssElementFinder( - content::WebContents* web_contents, - DevtoolsClient* devtools_client, - const UserData* user_data, - const ResultType result_type, - const Selector& selector) - : web_contents_(web_contents), - devtools_client_(devtools_client), - user_data_(user_data), - result_type_(result_type), - selector_(selector) {} -ElementFinder::CssElementFinder::~CssElementFinder() = default; - -void ElementFinder::CssElementFinder::Start( - const ElementFinderResult& start_element, - Callback callback) { - callback_ = std::move(callback); - - selector_proto_ = selector_.proto; - ClientStatus resolve_status = - user_data::ResolveSelectorUserData(&selector_proto_, user_data_); - if (!resolve_status.ok()) { - SendResult(resolve_status, ElementFinderResult::EmptyResult()); - return; - } - - current_frame_ = start_element.render_frame_host(); - if (current_frame_ == nullptr) { - current_frame_ = web_contents_->GetMainFrame(); - } - current_frame_id_ = start_element.node_frame_id(); - frame_stack_ = start_element.frame_stack(); - - if (start_element.object_id().empty()) { - GetDocumentElement(); - } else { - current_matches_.emplace_back(start_element.object_id()); - ExecuteNextTask(); - } -} - -ElementFinderInfoProto ElementFinder::CssElementFinder::GetLogInfo() const { - DCHECK(!callback_); // Run after finish. - - ElementFinderInfoProto info; - if (!client_status_.ok()) { - info.set_failed_filter_index_range_start(current_filter_index_range_start_); - info.set_failed_filter_index_range_end(next_filter_index_); - info.set_get_document_failed(get_document_failed_); - } - - return info; -} - -int ElementFinder::CssElementFinder::GetBackendNodeId() const { - return backend_node_id_.value_or(0); -} - -void ElementFinder::CssElementFinder::GiveUpWithError( - const ClientStatus& status) { - DCHECK(!status.ok()); - if (!callback_) { - return; - } - - SendResult(status, ElementFinderResult::EmptyResult()); -} - -void ElementFinder::CssElementFinder::ResultFound( - const std::string& object_id) { - if (!callback_) { - return; - } - - if (selector_.proto.has_semantic_information()) { - devtools_client_->GetDOM()->DescribeNode( - dom::DescribeNodeParams::Builder().SetObjectId(object_id).Build(), - current_frame_id_, - base::BindOnce(&CssElementFinder::OnDescribeNodeForId, - weak_ptr_factory_.GetWeakPtr(), object_id)); - return; - } - - BuildAndSendResult(object_id); -} - -void ElementFinder::CssElementFinder::OnDescribeNodeForId( - const std::string& object_id, - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<dom::DescribeNodeResult> node_result) { - if (node_result && node_result->GetNode()) { - backend_node_id_ = node_result->GetNode()->GetBackendNodeId(); - } - BuildAndSendResult(object_id); -} - -void ElementFinder::CssElementFinder::BuildAndSendResult( - const std::string& object_id) { - ElementFinderResult result; - result.SetRenderFrameHost(current_frame_); - result.SetObjectId(object_id); - result.SetNodeFrameId(current_frame_id_); - result.SetFrameStack(frame_stack_); - - SendResult(OkClientStatus(), result); -} - -void ElementFinder::CssElementFinder::SendResult( - const ClientStatus& status, - const ElementFinderResult& result) { - client_status_ = status; - DCHECK(callback_); - std::move(callback_).Run(status, - std::make_unique<ElementFinderResult>(result)); -} - -void ElementFinder::CssElementFinder::ExecuteNextTask() { - const auto& filters = selector_proto_.filters(); - - if (next_filter_index_ >= filters.size()) { - std::string object_id; - switch (result_type_) { - case ResultType::kExactlyOneMatch: - if (!ConsumeOneMatchOrFail(object_id)) { - return; - } - break; - - case ResultType::kAnyMatch: - if (!ConsumeMatchAtOrFail(0, object_id)) { - return; - } - break; - - case ResultType::kMatchArray: - if (!ConsumeMatchArrayOrFail(object_id)) { - return; - } - break; - } - ResultFound(object_id); - return; - } - - current_filter_index_range_start_ = next_filter_index_; - const auto& filter = filters.Get(next_filter_index_); - switch (filter.filter_case()) { - case SelectorProto::Filter::kEnterFrame: { - std::string object_id; - if (!ConsumeOneMatchOrFail(object_id)) - return; - - // The above fails if there is more than one frame. To preserve - // backward-compatibility with the previous, lax behavior, callers must - // add pick_one before enter_frame. TODO(b/155264465): allow searching in - // more than one frame. - next_filter_index_++; - EnterFrame(object_id); - return; - } - - case SelectorProto::Filter::kPseudoType: { - std::vector<std::string> matches; - if (!ConsumeAllMatchesOrFail(matches)) - return; - - next_filter_index_++; - matching_pseudo_elements_ = true; - ResolvePseudoElement(filter.pseudo_type(), matches); - return; - } - - case SelectorProto::Filter::kNthMatch: { - // TODO(b/205676462): This could be done with javascript like in - // |SelectorObserver|. - std::string object_id; - if (!ConsumeMatchAtOrFail(filter.nth_match().index(), object_id)) - return; - - next_filter_index_++; - current_matches_ = {object_id}; - ExecuteNextTask(); - return; - } - - case SelectorProto::Filter::kCssSelector: - case SelectorProto::Filter::kInnerText: - case SelectorProto::Filter::kValue: - case SelectorProto::Filter::kProperty: - case SelectorProto::Filter::kBoundingBox: - case SelectorProto::Filter::kPseudoElementContent: - case SelectorProto::Filter::kMatchCssSelector: - case SelectorProto::Filter::kCssStyle: - case SelectorProto::Filter::kLabelled: - case SelectorProto::Filter::kOnTop: { - std::vector<std::string> matches; - if (!ConsumeAllMatchesOrFail(matches)) - return; - - JsFilterBuilder js_filter; - for (int i = next_filter_index_; i < filters.size(); i++) { - if (!js_filter.AddFilter(filters.Get(i))) { - break; - } - next_filter_index_++; - } - ApplyJsFilters(js_filter, matches); - return; - } - - case SelectorProto::Filter::FILTER_NOT_SET: - VLOG(1) << __func__ << " Unset or unknown filter in " << filter << " in " - << selector_; - GiveUpWithError(ClientStatus(INVALID_SELECTOR)); - return; - } -} - -bool ElementFinder::CssElementFinder::ConsumeOneMatchOrFail( - std::string& object_id_out) { - if (current_matches_.size() > 1) { - VLOG(1) << __func__ << " Got " << current_matches_.size() << " matches for " - << selector_ << ", when only 1 was expected."; - GiveUpWithError(ClientStatus(TOO_MANY_ELEMENTS)); - return false; - } - if (current_matches_.empty()) { - GiveUpWithError(ClientStatus(ELEMENT_RESOLUTION_FAILED)); - return false; - } - - object_id_out = current_matches_[0]; - current_matches_.clear(); - return true; -} - -bool ElementFinder::CssElementFinder::ConsumeMatchAtOrFail( - size_t index, - std::string& object_id_out) { - if (index < current_matches_.size()) { - object_id_out = current_matches_[index]; - current_matches_.clear(); - return true; - } - - GiveUpWithError(ClientStatus(ELEMENT_RESOLUTION_FAILED)); - return false; -} - -bool ElementFinder::CssElementFinder::ConsumeAllMatchesOrFail( - std::vector<std::string>& matches_out) { - if (!current_matches_.empty()) { - matches_out = std::move(current_matches_); - current_matches_.clear(); - return true; - } - GiveUpWithError(ClientStatus(ELEMENT_RESOLUTION_FAILED)); - return false; -} - -bool ElementFinder::CssElementFinder::ConsumeMatchArrayOrFail( - std::string& array_object_id) { - if (!current_matches_js_array_.empty()) { - array_object_id = current_matches_js_array_; - current_matches_js_array_.clear(); - return true; - } - - if (current_matches_.empty()) { - GiveUpWithError(ClientStatus(ELEMENT_RESOLUTION_FAILED)); - return false; - } - - MoveMatchesToJSArrayRecursive(/* index= */ 0); - return false; -} - -void ElementFinder::CssElementFinder::MoveMatchesToJSArrayRecursive( - size_t index) { - if (index >= current_matches_.size()) { - current_matches_.clear(); - ExecuteNextTask(); - return; - } - - // Push the value at |current_matches_[index]| to |current_matches_js_array_|. - std::string function; - std::vector<std::unique_ptr<runtime::CallArgument>> arguments; - if (index == 0) { - // Create an array containing a single element. - function = "function() { return [this]; }"; - } else { - // Add an element to an existing array. - function = "function(dest) { dest.push(this); }"; - AddRuntimeCallArgumentObjectId(current_matches_js_array_, &arguments); - } - - devtools_client_->GetRuntime()->CallFunctionOn( - runtime::CallFunctionOnParams::Builder() - .SetObjectId(current_matches_[index]) - .SetArguments(std::move(arguments)) - .SetFunctionDeclaration(function) - .Build(), - current_frame_id_, - base::BindOnce(&CssElementFinder::OnMoveMatchesToJSArrayRecursive, - weak_ptr_factory_.GetWeakPtr(), index)); -} - -void ElementFinder::CssElementFinder::OnMoveMatchesToJSArrayRecursive( - size_t index, - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<runtime::CallFunctionOnResult> result) { - ClientStatus status = - CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__); - if (!status.ok()) { - VLOG(1) << __func__ << ": Failed to push value to JS array."; - GiveUpWithError(status); - return; - } - - // We just created an array which contains the first element. We store its ID - // in |current_matches_js_array_|. - if (index == 0 && - !SafeGetObjectId(result->GetResult(), ¤t_matches_js_array_)) { - VLOG(1) << __func__ << " Failed to get array ID."; - GiveUpWithError(ClientStatus(ELEMENT_RESOLUTION_FAILED)); - return; - } - - // Continue the recursion to push the other values into the array. - MoveMatchesToJSArrayRecursive(index + 1); -} - -void ElementFinder::CssElementFinder::GetDocumentElement() { - devtools_client_->GetRuntime()->Evaluate( - std::string(kGetDocumentElement), current_frame_id_, - base::BindOnce(&CssElementFinder::OnGetDocumentElement, - weak_ptr_factory_.GetWeakPtr())); -} - -void ElementFinder::CssElementFinder::OnGetDocumentElement( - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<runtime::EvaluateResult> result) { - ClientStatus status = - CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__); - if (!status.ok()) { - VLOG(1) << __func__ << " Failed to get document root element."; - get_document_failed_ = true; - GiveUpWithError(status); - return; - } - std::string object_id; - if (!SafeGetObjectId(result->GetResult(), &object_id)) { - VLOG(1) << __func__ << " Failed to get document root element."; - get_document_failed_ = true; - GiveUpWithError(ClientStatus(ELEMENT_RESOLUTION_FAILED)); - return; - } - - // Use the node as root for the rest of the evaluation. - current_matches_.emplace_back(object_id); - - ExecuteNextTask(); -} - -void ElementFinder::CssElementFinder::ApplyJsFilters( - const JsFilterBuilder& builder, - const std::vector<std::string>& object_ids) { - DCHECK(!object_ids.empty()); // Guaranteed by ExecuteNextTask() - PrepareBatchTasks(object_ids.size()); - std::string function = builder.BuildFunction(); - for (size_t task_id = 0; task_id < object_ids.size(); task_id++) { - devtools_client_->GetRuntime()->CallFunctionOn( - runtime::CallFunctionOnParams::Builder() - .SetObjectId(object_ids[task_id]) - .SetArguments(builder.BuildArgumentList()) - .SetFunctionDeclaration(function) - .Build(), - current_frame_id_, - base::BindOnce(&CssElementFinder::OnApplyJsFilters, - weak_ptr_factory_.GetWeakPtr(), task_id)); - } -} - -void ElementFinder::CssElementFinder::OnApplyJsFilters( - size_t task_id, - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<runtime::CallFunctionOnResult> result) { - if (!result) { - // It is possible for a document element to already exist, but not be - // available yet to query because the document hasn't been loaded. This - // results in OnQuerySelectorAll getting a nullptr result. For this specific - // call, it is expected. - VLOG(1) << __func__ << ": Context doesn't exist yet to query frame " - << frame_stack_.size() << " of " << selector_; - GiveUpWithError(ClientStatus(ELEMENT_RESOLUTION_FAILED)); - return; - } - ClientStatus status = - CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__); - if (!status.ok()) { - VLOG(1) << __func__ << ": Failed to query selector for frame " - << frame_stack_.size() << " of " << selector_ << ": " << status; - GiveUpWithError(status); - return; - } - - // The result can be empty (nothing found), an array (multiple matches - // found) or a single node. - std::string object_id; - if (!SafeGetObjectId(result->GetResult(), &object_id)) { - ReportNoMatchingElement(task_id); - return; - } - - if (result->GetResult()->HasSubtype() && - result->GetResult()->GetSubtype() == - runtime::RemoteObjectSubtype::ARRAY) { - ReportMatchingElementsArray(task_id, object_id); - return; - } - - ReportMatchingElement(task_id, object_id); -} - -void ElementFinder::CssElementFinder::ResolvePseudoElement( - PseudoType proto_pseudo_type, - const std::vector<std::string>& object_ids) { - dom::PseudoType pseudo_type; - if (!ConvertPseudoType(proto_pseudo_type, &pseudo_type)) { - VLOG(1) << __func__ << ": Unsupported pseudo-type " - << PseudoTypeName(proto_pseudo_type); - GiveUpWithError(ClientStatus(INVALID_ACTION)); - return; - } - - DCHECK(!object_ids.empty()); // Guaranteed by ExecuteNextTask() - PrepareBatchTasks(object_ids.size()); - for (size_t task_id = 0; task_id < object_ids.size(); task_id++) { - devtools_client_->GetDOM()->DescribeNode( - dom::DescribeNodeParams::Builder() - .SetObjectId(object_ids[task_id]) - .Build(), - current_frame_id_, - base::BindOnce(&CssElementFinder::OnDescribeNodeForPseudoElement, - weak_ptr_factory_.GetWeakPtr(), pseudo_type, task_id)); - } -} - -void ElementFinder::CssElementFinder::OnDescribeNodeForPseudoElement( - dom::PseudoType pseudo_type, - size_t task_id, - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<dom::DescribeNodeResult> result) { - if (!result || !result->GetNode()) { - VLOG(1) << __func__ << " Failed to describe the node for pseudo element."; - GiveUpWithError( - UnexpectedDevtoolsErrorStatus(reply_status, __FILE__, __LINE__)); - return; - } - - auto* node = result->GetNode(); - if (node->HasPseudoElements()) { - for (const auto& pseudo_element : *(node->GetPseudoElements())) { - if (pseudo_element->HasPseudoType() && - pseudo_element->GetPseudoType() == pseudo_type) { - devtools_client_->GetDOM()->ResolveNode( - dom::ResolveNodeParams::Builder() - .SetBackendNodeId(pseudo_element->GetBackendNodeId()) - .Build(), - current_frame_id_, - base::BindOnce(&CssElementFinder::OnResolveNodeForPseudoElement, - weak_ptr_factory_.GetWeakPtr(), task_id)); - return; - } - } - } - - ReportNoMatchingElement(task_id); -} - -void ElementFinder::CssElementFinder::OnResolveNodeForPseudoElement( - size_t task_id, - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<dom::ResolveNodeResult> result) { - if (result && result->GetObject() && result->GetObject()->HasObjectId()) { - ReportMatchingElement(task_id, result->GetObject()->GetObjectId()); - return; - } - - ReportNoMatchingElement(task_id); -} - -void ElementFinder::CssElementFinder::EnterFrame(const std::string& object_id) { - devtools_client_->GetDOM()->DescribeNode( - dom::DescribeNodeParams::Builder().SetObjectId(object_id).Build(), - current_frame_id_, - base::BindOnce(&CssElementFinder::OnDescribeNodeForFrame, - weak_ptr_factory_.GetWeakPtr(), object_id)); -} - -void ElementFinder::CssElementFinder::OnDescribeNodeForFrame( - const std::string& object_id, - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<dom::DescribeNodeResult> result) { - if (!result || !result->GetNode()) { - VLOG(1) << __func__ << " Failed to describe the node."; - GiveUpWithError( - UnexpectedDevtoolsErrorStatus(reply_status, __FILE__, __LINE__)); - return; - } - - auto* node = result->GetNode(); - std::vector<int> backend_ids; - - if (node->GetNodeName() == "IFRAME") { - // See: b/206647825 - if (!node->HasFrameId()) { - NOTREACHED() << "Frame without ID"; // Ensure all frames have an id. - GiveUpWithError(ClientStatus(FRAME_HOST_NOT_FOUND)); - return; - } - - frame_stack_.push_back({object_id, current_frame_id_}); - - auto* frame = - FindCorrespondingRenderFrameHost(node->GetFrameId(), web_contents_); - if (!frame) { - VLOG(1) << __func__ << " Failed to find corresponding owner frame."; - GiveUpWithError(ClientStatus(FRAME_HOST_NOT_FOUND)); - return; - } - current_frame_ = frame; - - if (node->HasContentDocument()) { - // If the frame has a ContentDocument it's considered a local frame. In - // this case, current_frame_ doesn't change and can directly use the - // content document as root for the evaluation. - backend_ids.emplace_back(node->GetContentDocument()->GetBackendNodeId()); - } else { - current_frame_id_ = node->GetFrameId(); - // Kick off another find element chain to walk down the OOP iFrame. - GetDocumentElement(); - return; - } - } - - if (node->HasShadowRoots()) { - // TODO(crbug.com/806868): Support multiple shadow roots. - backend_ids.emplace_back( - node->GetShadowRoots()->front()->GetBackendNodeId()); - } - - if (!backend_ids.empty()) { - devtools_client_->GetDOM()->ResolveNode( - dom::ResolveNodeParams::Builder() - .SetBackendNodeId(backend_ids[0]) - .Build(), - current_frame_id_, - base::BindOnce(&CssElementFinder::OnResolveNode, - weak_ptr_factory_.GetWeakPtr())); - return; - } - - // Element was not a frame and didn't have shadow dom. This is unexpected, but - // to remain backward compatible, don't complain and just continue filtering - // with the current element as root. - current_matches_.emplace_back(object_id); - ExecuteNextTask(); -} - -void ElementFinder::CssElementFinder::OnResolveNode( - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<dom::ResolveNodeResult> result) { - if (!result || !result->GetObject() || !result->GetObject()->HasObjectId()) { - VLOG(1) << __func__ << " Failed to resolve object id from backend id."; - GiveUpWithError( - UnexpectedDevtoolsErrorStatus(reply_status, __FILE__, __LINE__)); - return; - } - - // Use the node as root for the rest of the evaluation. - current_matches_.emplace_back(result->GetObject()->GetObjectId()); - ExecuteNextTask(); -} - -void ElementFinder::CssElementFinder::PrepareBatchTasks(int n) { - tasks_results_.clear(); - tasks_results_.resize(n); -} - -void ElementFinder::CssElementFinder::ReportMatchingElement( - size_t task_id, - const std::string& object_id) { - tasks_results_[task_id] = - std::make_unique<std::vector<std::string>>(1, object_id); - MaybeFinalizeBatchTasks(); -} - -void ElementFinder::CssElementFinder::ReportNoMatchingElement(size_t task_id) { - tasks_results_[task_id] = std::make_unique<std::vector<std::string>>(); - MaybeFinalizeBatchTasks(); -} - -void ElementFinder::CssElementFinder::ReportMatchingElementsArray( - size_t task_id, - const std::string& array_object_id) { - // Recursively add each element ID to a vector then report it as this task - // result. - ReportMatchingElementsArrayRecursive( - task_id, array_object_id, std::make_unique<std::vector<std::string>>(), - /* index= */ 0); -} - -void ElementFinder::CssElementFinder::ReportMatchingElementsArrayRecursive( - size_t task_id, - const std::string& array_object_id, - std::unique_ptr<std::vector<std::string>> acc, - int index) { - std::vector<std::unique_ptr<runtime::CallArgument>> arguments; - AddRuntimeCallArgument(index, &arguments); - devtools_client_->GetRuntime()->CallFunctionOn( - runtime::CallFunctionOnParams::Builder() - .SetObjectId(array_object_id) - .SetArguments(std::move(arguments)) - .SetFunctionDeclaration(std::string(kGetArrayElement)) - .Build(), - current_frame_id_, - base::BindOnce(&CssElementFinder::OnReportMatchingElementsArrayRecursive, - weak_ptr_factory_.GetWeakPtr(), task_id, array_object_id, - std::move(acc), index)); -} - -void ElementFinder::CssElementFinder::OnReportMatchingElementsArrayRecursive( - size_t task_id, - const std::string& array_object_id, - std::unique_ptr<std::vector<std::string>> acc, - int index, - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<runtime::CallFunctionOnResult> result) { - ClientStatus status = - CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__); - if (!status.ok()) { - VLOG(1) << __func__ << ": Failed to get element from array for " - << selector_; - GiveUpWithError(status); - return; - } - - std::string object_id; - if (!SafeGetObjectId(result->GetResult(), &object_id)) { - // We've reached the end of the array. - tasks_results_[task_id] = std::move(acc); - MaybeFinalizeBatchTasks(); - return; - } - - acc->emplace_back(object_id); - - // Fetch the next element. - ReportMatchingElementsArrayRecursive(task_id, array_object_id, std::move(acc), - index + 1); -} - -void ElementFinder::CssElementFinder::MaybeFinalizeBatchTasks() { - // Return early if one of the tasks is still pending. - for (const auto& result : tasks_results_) { - if (!result) { - return; - } - } - - // Add all matching elements to current_matches_. - for (const auto& result : tasks_results_) { - current_matches_.insert(current_matches_.end(), result->begin(), - result->end()); - } - tasks_results_.clear(); - - ExecuteNextTask(); -} - } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/web/element_finder.h b/chromium/components/autofill_assistant/browser/web/element_finder.h index d45d285c17f..a8e8f31c5db 100644 --- a/chromium/components/autofill_assistant/browser/web/element_finder.h +++ b/chromium/components/autofill_assistant/browser/web/element_finder.h @@ -9,124 +9,32 @@ #include <string> #include <vector> -#include "base/callback.h" +#include "base/callback_forward.h" #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" -#include "base/strings/strcat.h" -#include "components/autofill_assistant/browser/action_value.pb.h" #include "components/autofill_assistant/browser/client_status.h" -#include "components/autofill_assistant/browser/devtools/devtools/domains/types_dom.h" -#include "components/autofill_assistant/browser/devtools/devtools/domains/types_runtime.h" #include "components/autofill_assistant/browser/devtools/devtools_client.h" #include "components/autofill_assistant/browser/selector.h" -#include "components/autofill_assistant/browser/web/element.h" -#include "components/autofill_assistant/browser/web/js_filter_builder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" +#include "components/autofill_assistant/browser/web/element_finder_result_type.h" #include "components/autofill_assistant/browser/web/web_controller_worker.h" #include "components/autofill_assistant/content/browser/annotate_dom_model_service.h" -#include "components/autofill_assistant/content/common/autofill_assistant_agent.mojom.h" -#include "components/autofill_assistant/content/common/node_data.h" -#include "third_party/abseil-cpp/absl/types/optional.h" namespace content { class WebContents; -class RenderFrameHost; -struct GlobalRenderFrameHostId; } // namespace content namespace autofill_assistant { +class BaseElementFinder; class DevtoolsClient; class UserData; -// ElementFinderResult is the fully resolved element that can be used without -// limitations. This means that |render_frame_host()| has been found and is not -// nullptr. -class ElementFinderResult { - public: - ElementFinderResult(); - ~ElementFinderResult(); - ElementFinderResult(const ElementFinderResult&); - - // Create an instance that is deemed to be empty. This can be used for - // optional Elements (e.g. optional an frame). - static ElementFinderResult EmptyResult(); - - const DomObjectFrameStack& dom_object() const { return dom_object_; } - - content::RenderFrameHost* render_frame_host() const { - if (!render_frame_id_) { - return nullptr; - } - return content::RenderFrameHost::FromID(*render_frame_id_); - } - - const std::string& object_id() const { - return dom_object_.object_data.object_id; - } - - const std::string& node_frame_id() const { - return dom_object_.object_data.node_frame_id; - } - - const std::vector<JsObjectIdentifier>& frame_stack() const { - return dom_object_.frame_stack; - } - - bool IsEmpty() const { - return object_id().empty() && node_frame_id().empty(); - } - - void SetRenderFrameHost(content::RenderFrameHost* render_frame_host) { - if (!render_frame_host) { - return; - } - render_frame_id_ = render_frame_host->GetGlobalId(); - } - - void SetObjectId(const std::string& object_id) { - dom_object_.object_data.object_id = object_id; - } - - void SetNodeFrameId(const std::string& node_frame_id) { - dom_object_.object_data.node_frame_id = node_frame_id; - } - - void SetFrameStack(const std::vector<JsObjectIdentifier>& frame_stack) { - dom_object_.frame_stack = frame_stack; - } - - private: - DomObjectFrameStack dom_object_; - - // The id of the render frame host that contains the element. - absl::optional<content::GlobalRenderFrameHostId> render_frame_id_; -}; - // Worker class to find element(s) matching a selector. This will keep entering // iFrames until the element is found in the last frame, then returns the // element together with the owning frame. All subsequent operations should // be performed on that frame. class ElementFinder : public WebControllerWorker { public: - enum ResultType { - // ElementFinderResult.object_id contains the object ID of the single node - // that matched. - // If there are no matches, status is ELEMENT_RESOLUTION_FAILED. If there - // are more than one matches, status is TOO_MANY_ELEMENTS. - kExactlyOneMatch = 0, - - // ElementFinderResult.object_id contains the object ID of one of the nodes - // that matched. - // If there are no matches, status is ELEMENT_RESOLUTION_FAILED. - kAnyMatch, - - // ElementFinderResult.object_id contains the object ID of an array - // containing all the - // nodes - // that matched. If there are no matches, status is - // ELEMENT_RESOLUTION_FAILED. - kMatchArray, - }; - // |web_contents|, |devtools_client| and |user_data| must be valid for the // lifetime of the instance. If |annotate_dom_model_service| is not nullptr, // must be valid for the lifetime of the instance. @@ -136,7 +44,7 @@ class ElementFinder : public WebControllerWorker { ProcessedActionStatusDetailsProto* log_info, AnnotateDomModelService* annotate_dom_model_service, const Selector& selector, - ResultType result_type); + ElementFinderResultType result_type); ~ElementFinder() override; using Callback = @@ -148,328 +56,6 @@ class ElementFinder : public WebControllerWorker { void Start(const ElementFinderResult& start_element, Callback callback); private: - class ElementFinderBase { - public: - virtual ~ElementFinderBase(); - - // Start looking for the element and return it through |callback| with - // a status. If |start_element| is not empty, use it as a starting point - // instead of starting from the main frame. - virtual void Start(const ElementFinderResult& start_element, - Callback callback) = 0; - - // Get the log information for the last run. Should only be run after the - // run has completed (i.e. |callback_| has been called). - virtual ElementFinderInfoProto GetLogInfo() const = 0; - - // Returns the backend node id that was previously collected. - virtual int GetBackendNodeId() const = 0; - }; - - class SemanticElementFinder : public ElementFinderBase { - public: - SemanticElementFinder(content::WebContents* web_contents, - DevtoolsClient* devtools_client, - AnnotateDomModelService* annotate_dom_model_service, - const Selector& selector); - ~SemanticElementFinder() override; - - SemanticElementFinder(const SemanticElementFinder&) = delete; - SemanticElementFinder& operator=(const SemanticElementFinder&) = delete; - - void Start(const ElementFinderResult& start_element, - Callback callback) override; - - ElementFinderInfoProto GetLogInfo() const override; - - // Returns the backend node id of the first result (if any), or 0. - int GetBackendNodeId() const override; - - private: - // Returns the given status and no element. This expects an error status. - void GiveUpWithError(const ClientStatus& status); - - // Builds a result from the |render_frame_host| and the |object_id| and - // returns it withan ok status. - void ResultFound(content::RenderFrameHost* render_frame_host, - const std::string& object_id); - - // Call |callback_| with the |status| and |result|. - void SendResult(const ClientStatus& status, - const ElementFinderResult& result); - - // Run the model annotation on all frames for the current |start_frame|. - void RunAnnotateDomModel(content::RenderFrameHost* start_frame); - - // Runs the model on the frame identified by |host_id|. - void RunAnnotateDomModelOnFrame( - const content::GlobalRenderFrameHostId& host_id, - base::OnceCallback<void(std::vector<GlobalBackendNodeId>)> callback); - void OnRunAnnotateDomModelOnFrame( - const content::GlobalRenderFrameHostId& host_id, - base::OnceCallback<void(std::vector<GlobalBackendNodeId>)> callback, - mojom::NodeDataStatus status, - const std::vector<NodeData>& node_data); - - // Called once the model has been run on all frames. - void OnRunAnnotateDomModel( - const std::vector<std::vector<GlobalBackendNodeId>>& all_nodes); - - void OnResolveNodeForAnnotateDom( - content::GlobalRenderFrameHostId host_id, - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<dom::ResolveNodeResult> result); - - const raw_ptr<content::WebContents> web_contents_; - const raw_ptr<DevtoolsClient> devtools_client_; - const raw_ptr<AnnotateDomModelService> annotate_dom_model_service_; - const Selector selector_; - Callback callback_; - - // Elements gathered through all frames. Unused if the |selector_| does not - // contain |SemanticInformation|. - std::vector<GlobalBackendNodeId> semantic_node_results_; - std::vector<mojom::NodeDataStatus> node_data_frame_status_; - - base::WeakPtrFactory<SemanticElementFinder> weak_ptr_factory_{this}; - }; - - class CssElementFinder : public ElementFinderBase { - public: - CssElementFinder(content::WebContents* web_contents, - DevtoolsClient* devtools_client, - const UserData* user_data, - const ResultType result_type, - const Selector& selector); - ~CssElementFinder() override; - - CssElementFinder(const CssElementFinder&) = delete; - CssElementFinder& operator=(const CssElementFinder&) = delete; - - void Start(const ElementFinderResult& start_element, - Callback callback) override; - - ElementFinderInfoProto GetLogInfo() const override; - - // Returns the backend node id of the result if the proto contains - // |semantic_information|, or 0. - int GetBackendNodeId() const override; - - private: - // Returns the given status and no element. This expects an error status. - void GiveUpWithError(const ClientStatus& status); - - // Found a valid result. - void ResultFound(const std::string& object_id); - - // Builds a result from the current state of the finder and returns it with - // an ok status. - void BuildAndSendResult(const std::string& object_id); - - // Call |callback_| with the |status| and |result|. - void SendResult(const ClientStatus& status, - const ElementFinderResult& result); - - // Figures out what to do next given the current state. - // - // Most background operations in this worker end by updating the state and - // calling ExecuteNextTask() again either directly or through Report*(). - void ExecuteNextTask(); - - // Prepare a batch of |n| tasks that are sent at the same time to compute - // one or more matching elements. - // - // After calling this, Report*(i, ...) should be called *exactly once* for - // all 0 <= i < n to report the tasks results. - // - // Once all tasks reported their result, the object ID of all matching - // elements will be added to |current_matches_| and ExecuteNextTask() will - // be called. - void PrepareBatchTasks(int n); - - // Report that task with ID |task_id| didn't match any element. - void ReportNoMatchingElement(size_t task_id); - - // Report that task with ID |task_id| matched a single element with ID - // |object_id|. - void ReportMatchingElement(size_t task_id, const std::string& object_id); - - // Report that task with ID |task_id| matched multiple elements that are - // stored in the JS array with ID |object_id|. - void ReportMatchingElementsArray(size_t task_id, - const std::string& array_object_id); - void ReportMatchingElementsArrayRecursive( - size_t task_id, - const std::string& array_object_id, - std::unique_ptr<std::vector<std::string>> acc, - int index); - void OnReportMatchingElementsArrayRecursive( - size_t task_id, - const std::string& array_object_id, - std::unique_ptr<std::vector<std::string>> acc, - int index, - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<runtime::CallFunctionOnResult> result); - - // If all batch tasks reported their result, add all tasks results to - // |current_matches_| then call ExecuteNextTask(). - void MaybeFinalizeBatchTasks(); - - // Make sure there's exactly one match, set it |object_id_out| then return - // true. - // - // If there are too many or too few matches, this function sends an error - // and returns false. - // - // If this returns true, continue processing. If this returns false, return - // from ExecuteNextTask(). ExecuteNextTask() will be called again once the - // required data is available. - bool ConsumeOneMatchOrFail(std::string& object_id_out); - - // Make sure there's at least |index + 1| matches, take the one at that - // index and put it in |object_id_out|, then return true. - // - // If there are not enough matches, send an error response and return false. - bool ConsumeMatchAtOrFail(size_t index, std::string& object_id_out); - - // Make sure there's at least one match and move them all into - // |matches_out|. - // - // If there are no matches, send an error response and return false. - // If there are not enough matches yet, fetch them in the background and - // return false. This calls ExecuteNextTask() once matches have been - // fetched. - // - // If this returns true, continue processing. If this returns false, return - // from ExecuteNextTask(). ExecuteNextTask() will be called again once the - // required data is available. - bool ConsumeAllMatchesOrFail(std::vector<std::string>& matches_out); - - // Make sure there's at least one match and move them all into a single - // array. - // - // If there are no matches, call SendResult() and return false. - // - // If there are matches, return false directly and move the matches into - // an JS array in the background. ExecuteNextTask() is called again - // once the background tasks have executed, and calling this will return - // true and write the JS array id to |array_object_id_out|. - bool ConsumeMatchArrayOrFail(std::string& array_object_id_out); - - void OnConsumeMatchArray( - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<runtime::CallFunctionOnResult> result); - - // Gets a document element from the current frame and us it as root for the - // rest of the tasks, then call ExecuteNextTask(). - void GetDocumentElement(); - void OnGetDocumentElement(const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<runtime::EvaluateResult> result); - - // Handle Javascript filters - void ApplyJsFilters(const JsFilterBuilder& builder, - const std::vector<std::string>& object_ids); - void OnApplyJsFilters( - size_t task_id, - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<runtime::CallFunctionOnResult> result); - - // Handle PSEUDO_TYPE - void ResolvePseudoElement(PseudoType pseudo_type, - const std::vector<std::string>& object_ids); - void OnDescribeNodeForPseudoElement( - dom::PseudoType pseudo_type, - size_t task_id, - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<dom::DescribeNodeResult> result); - void OnResolveNodeForPseudoElement( - size_t task_id, - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<dom::ResolveNodeResult> result); - - // Handle ENTER_FRAME - void EnterFrame(const std::string& object_id); - void OnDescribeNodeForFrame( - const std::string& object_id, - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<dom::DescribeNodeResult> result); - void OnResolveNode(const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<dom::ResolveNodeResult> result); - - // Fill |current_matches_js_array_| with the values in |current_matches_| - // starting from |index|, then clear |current_matches_| and call - // ExecuteNextTask(). - void MoveMatchesToJSArrayRecursive(size_t index); - - void OnMoveMatchesToJSArrayRecursive( - size_t index, - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<runtime::CallFunctionOnResult> result); - - void OnDescribeNodeForId( - const std::string& object_id, - const DevtoolsClient::ReplyStatus& reply_status, - std::unique_ptr<dom::DescribeNodeResult> node_result); - - const raw_ptr<content::WebContents> web_contents_; - const raw_ptr<DevtoolsClient> devtools_client_; - const raw_ptr<const UserData> user_data_; - const ResultType result_type_; - const Selector selector_; - Callback callback_; - - // The modified selector to use going forward. This is guaranteed to have - // resolved any filters that need a data lookup. - SelectorProto selector_proto_; - - // The index of the next filter to process, in selector__proto_.filters. - int next_filter_index_ = 0; - - // Getting the document failed. Used for error reporting. - bool get_document_failed_ = false; - - // The currently worked on filters are starting at this index.. - int current_filter_index_range_start_ = -1; - - // Pointer to the current frame - raw_ptr<content::RenderFrameHost> current_frame_ = nullptr; - - // The frame id to use to execute devtools Javascript calls within the - // context of the frame. Might be empty if no frame id needs to be - // specified. - std::string current_frame_id_; - - // Object IDs of the current set matching elements. Cleared once it's used - // to query or filter. - std::vector<std::string> current_matches_; - - // Object ID of the JavaScript array of the currently matching elements. In - // practice, this is used by ConsumeMatchArrayOrFail() to convert - // |current_matches_| to a JavaScript array. - std::string current_matches_js_array_; - - // True if current_matches are pseudo-elements. - bool matching_pseudo_elements_ = false; - - // The result of the background tasks. |tasks_results_[i]| contains the - // elements matched by task i, or nullptr if the task is still running. - std::vector<std::unique_ptr<std::vector<std::string>>> tasks_results_; - - std::vector<JsObjectIdentifier> frame_stack_; - - // The backend node id of the result. Only gets assigned if required, when - // this will be used for a comparison with the result of a semantic run. - absl::optional<int> backend_node_id_; - - // The client status of the last run. - ClientStatus client_status_; - - // Finder for the target of the current proximity filter. - std::unique_ptr<ElementFinder> proximity_target_filter_; - - base::WeakPtrFactory<CssElementFinder> weak_ptr_factory_{this}; - }; - // Updates |log_info_| and calls |callback_| with the |status| and |result|. void SendResult(const ClientStatus& status, std::unique_ptr<ElementFinderResult> result); @@ -478,7 +64,7 @@ class ElementFinder : public WebControllerWorker { // Adds a runner to the list and starts it from the |start_element|. void AddAndStartRunner(const ElementFinderResult& start_element, - std::unique_ptr<ElementFinderBase> runner); + std::unique_ptr<BaseElementFinder> runner); void OnResult(size_t index, const ClientStatus& status, std::unique_ptr<ElementFinderResult> result); @@ -489,10 +75,10 @@ class ElementFinder : public WebControllerWorker { const raw_ptr<ProcessedActionStatusDetailsProto> log_info_; const raw_ptr<AnnotateDomModelService> annotate_dom_model_service_; const Selector selector_; - const ResultType result_type_; + const ElementFinderResultType result_type_; Callback callback_; - std::vector<std::unique_ptr<ElementFinderBase>> runners_; + std::vector<std::unique_ptr<BaseElementFinder>> runners_; std::vector<std::pair<ClientStatus, std::unique_ptr<ElementFinderResult>>> results_; size_t num_results_ = 0; diff --git a/chromium/components/autofill_assistant/browser/web/element_finder_result.cc b/chromium/components/autofill_assistant/browser/web/element_finder_result.cc new file mode 100644 index 00000000000..e97f6b68836 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/web/element_finder_result.cc @@ -0,0 +1,19 @@ +// Copyright 2022 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_assistant/browser/web/element_finder_result.h" + +namespace autofill_assistant { + +ElementFinderResult::ElementFinderResult() = default; + +ElementFinderResult::~ElementFinderResult() = default; + +ElementFinderResult::ElementFinderResult(const ElementFinderResult&) = default; + +ElementFinderResult ElementFinderResult::EmptyResult() { + return ElementFinderResult(); +} + +} // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/web/element_finder_result.h b/chromium/components/autofill_assistant/browser/web/element_finder_result.h new file mode 100644 index 00000000000..ad44f251ac2 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/web/element_finder_result.h @@ -0,0 +1,101 @@ +// Copyright 2022 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_ASSISTANT_BROWSER_WEB_ELEMENT_FINDER_RESULT_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_WEB_ELEMENT_FINDER_RESULT_H_ + +#include <string> +#include <vector> + +#include "components/autofill_assistant/browser/web/element.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace content { +class RenderFrameHost; +struct GlobalRenderFrameHostId; +} // namespace content + +namespace autofill_assistant { + +// ElementFinderResult is the fully resolved element that can be used without +// limitations. This means that |render_frame_host()| has been found and is not +// nullptr. +class ElementFinderResult { + public: + ElementFinderResult(); + ~ElementFinderResult(); + ElementFinderResult(const ElementFinderResult&); + + // Create an instance that is deemed to be empty. This can be used for + // optional Elements (e.g. optional an frame). + static ElementFinderResult EmptyResult(); + + const DomObjectFrameStack& dom_object() const { return dom_object_; } + + content::RenderFrameHost* render_frame_host() const { + if (!render_frame_id_) { + return nullptr; + } + return content::RenderFrameHost::FromID(*render_frame_id_); + } + + const std::string& object_id() const { + return dom_object_.object_data.object_id; + } + + absl::optional<int> backend_node_id() const { + return dom_object_.object_data.backend_node_id; + } + + const std::string& node_frame_id() const { + return dom_object_.object_data.node_frame_id; + } + + const std::vector<JsObjectIdentifier>& frame_stack() const { + return dom_object_.frame_stack; + } + + bool IsEmpty() const { + return object_id().empty() && node_frame_id().empty(); + } + + // Deprecated. Use SetRenderFrameHostGlobalId instead. + void SetRenderFrameHost(content::RenderFrameHost* render_frame_host) { + if (!render_frame_host) { + return; + } + render_frame_id_ = render_frame_host->GetGlobalId(); + } + + void SetRenderFrameHostGlobalId( + content::GlobalRenderFrameHostId render_frame_id) { + render_frame_id_ = render_frame_id; + } + + void SetObjectId(const std::string& object_id) { + dom_object_.object_data.object_id = object_id; + } + + void SetBackendNodeId(absl::optional<int> backend_node_id) { + dom_object_.object_data.backend_node_id = backend_node_id; + } + + void SetNodeFrameId(const std::string& node_frame_id) { + dom_object_.object_data.node_frame_id = node_frame_id; + } + + void SetFrameStack(const std::vector<JsObjectIdentifier>& frame_stack) { + dom_object_.frame_stack = frame_stack; + } + + private: + DomObjectFrameStack dom_object_; + + // The id of the render frame host that contains the element. + absl::optional<content::GlobalRenderFrameHostId> render_frame_id_; +}; + +} // namespace autofill_assistant + +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_WEB_ELEMENT_FINDER_RESULT_H_ diff --git a/chromium/components/autofill_assistant/browser/web/element_finder_result_type.h b/chromium/components/autofill_assistant/browser/web/element_finder_result_type.h new file mode 100644 index 00000000000..a506ccff05a --- /dev/null +++ b/chromium/components/autofill_assistant/browser/web/element_finder_result_type.h @@ -0,0 +1,32 @@ +// Copyright 2022 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_ASSISTANT_BROWSER_WEB_ELEMENT_FINDER_RESULT_TYPE_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_WEB_ELEMENT_FINDER_RESULT_TYPE_H_ + +namespace autofill_assistant { + +enum class ElementFinderResultType { + // ElementFinderResult.object_id contains the object ID of the single node + // that matched. + // If there are no matches, status is ELEMENT_RESOLUTION_FAILED. If there + // are more than one matches, status is TOO_MANY_ELEMENTS. + kExactlyOneMatch = 0, + + // ElementFinderResult.object_id contains the object ID of one of the nodes + // that matched. + // If there are no matches, status is ELEMENT_RESOLUTION_FAILED. + kAnyMatch, + + // ElementFinderResult.object_id contains the object ID of an array + // containing all the + // nodes + // that matched. If there are no matches, status is + // ELEMENT_RESOLUTION_FAILED. + kMatchArray, +}; + +} // namespace autofill_assistant + +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_WEB_ELEMENT_FINDER_RESULT_H_ diff --git a/chromium/components/autofill_assistant/browser/web/element_rect_getter.cc b/chromium/components/autofill_assistant/browser/web/element_rect_getter.cc index d3fd70f883f..84e2b6a82f6 100644 --- a/chromium/components/autofill_assistant/browser/web/element_rect_getter.cc +++ b/chromium/components/autofill_assistant/browser/web/element_rect_getter.cc @@ -7,10 +7,11 @@ #include "base/callback.h" #include "base/logging.h" #include "base/values.h" +#include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/devtools/devtools/domains/types_runtime.h" #include "components/autofill_assistant/browser/devtools/devtools_client.h" #include "components/autofill_assistant/browser/rectf.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "components/autofill_assistant/browser/web/web_controller_util.h" namespace autofill_assistant { diff --git a/chromium/components/autofill_assistant/browser/web/element_rect_getter.h b/chromium/components/autofill_assistant/browser/web/element_rect_getter.h index 033e0adcd52..37ff9afb0dc 100644 --- a/chromium/components/autofill_assistant/browser/web/element_rect_getter.h +++ b/chromium/components/autofill_assistant/browser/web/element_rect_getter.h @@ -10,10 +10,11 @@ #include "base/memory/weak_ptr.h" #include "components/autofill_assistant/browser/devtools/devtools_client.h" #include "components/autofill_assistant/browser/rectf.h" -#include "components/autofill_assistant/browser/web/element_finder.h" #include "components/autofill_assistant/browser/web/web_controller_worker.h" namespace autofill_assistant { +class ClientStatus; +class ElementFinderResult; // Worker class to get an element's bounding rectangle in viewport coordinates. // This returns the global coordinates of the element rect, summing up (and diff --git a/chromium/components/autofill_assistant/browser/web/element_store.cc b/chromium/components/autofill_assistant/browser/web/element_store.cc index 5f7540ecd8d..3b81a75ca83 100644 --- a/chromium/components/autofill_assistant/browser/web/element_store.cc +++ b/chromium/components/autofill_assistant/browser/web/element_store.cc @@ -6,7 +6,7 @@ #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/web/element.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents.h" @@ -37,6 +37,7 @@ ClientStatus ElementStore::RestoreElement( const DomObjectFrameStack& object, ElementFinderResult* out_element) const { out_element->SetObjectId(object.object_data.object_id); + out_element->SetBackendNodeId(object.object_data.backend_node_id); out_element->SetNodeFrameId(object.object_data.node_frame_id); out_element->SetFrameStack(object.frame_stack); auto* frame = FindCorrespondingRenderFrameHost( diff --git a/chromium/components/autofill_assistant/browser/web/element_store.h b/chromium/components/autofill_assistant/browser/web/element_store.h index 74a90c53ffd..fe52dd0cecc 100644 --- a/chromium/components/autofill_assistant/browser/web/element_store.h +++ b/chromium/components/autofill_assistant/browser/web/element_store.h @@ -9,13 +9,13 @@ #include "base/memory/raw_ptr.h" #include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/web/element.h" -#include "components/autofill_assistant/browser/web/element_finder.h" namespace content { class WebContents; } // namespace content namespace autofill_assistant { +class ElementFinderResult; // Temporary store for elements resolved from a |Selector| by the // |ElementFinder|. This store only holds a shallow copy of the element, diff --git a/chromium/components/autofill_assistant/browser/web/element_store_unittest.cc b/chromium/components/autofill_assistant/browser/web/element_store_unittest.cc index 95309045734..bca656e9147 100644 --- a/chromium/components/autofill_assistant/browser/web/element_store_unittest.cc +++ b/chromium/components/autofill_assistant/browser/web/element_store_unittest.cc @@ -8,7 +8,7 @@ #include "base/test/mock_callback.h" #include "components/autofill_assistant/browser/actions/action_test_utils.h" #include "components/autofill_assistant/browser/client_status.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" #include "content/public/test/browser_task_environment.h" #include "content/public/test/test_browser_context.h" #include "content/public/test/test_renderer_host.h" @@ -18,6 +18,9 @@ namespace autofill_assistant { namespace { +using ::testing::IsEmpty; +using ::testing::Not; + class ElementStoreTest : public testing::Test { public: void SetUp() override { @@ -31,8 +34,9 @@ class ElementStoreTest : public testing::Test { const std::string& object_id) { auto element = std::make_unique<ElementFinderResult>(); element->SetObjectId(object_id); - element->SetNodeFrameId( - web_contents_->GetMainFrame()->GetDevToolsFrameToken().ToString()); + element->SetNodeFrameId(web_contents_->GetPrimaryMainFrame() + ->GetDevToolsFrameToken() + .ToString()); return element; } @@ -60,12 +64,15 @@ TEST_F(ElementStoreTest, AddElementToStore) { TEST_F(ElementStoreTest, GetElementFromStore) { auto element = CreateElement("1"); + element->SetBackendNodeId(1); AddElement("1", std::move(element)); ElementFinderResult result; EXPECT_EQ(ACTION_APPLIED, element_store_->GetElement("1", &result).proto_status()); EXPECT_EQ("1", result.object_id()); + EXPECT_EQ(1, *result.backend_node_id()); + EXPECT_THAT(result.node_frame_id(), Not(IsEmpty())); } TEST_F(ElementStoreTest, GetElementFromStoreWithBadFrameHost) { @@ -87,7 +94,7 @@ TEST_F(ElementStoreTest, GetElementFromStoreWithNoFrameId) { ElementFinderResult result; EXPECT_EQ(ACTION_APPLIED, element_store_->GetElement("1", &result).proto_status()); - EXPECT_EQ(web_contents_->GetMainFrame(), result.render_frame_host()); + EXPECT_EQ(web_contents_->GetPrimaryMainFrame(), result.render_frame_host()); } TEST_F(ElementStoreTest, AddElementToStoreOverwrites) { diff --git a/chromium/components/autofill_assistant/browser/web/fake_element_store.cc b/chromium/components/autofill_assistant/browser/web/fake_element_store.cc index 7fc1400b75c..8568a797d03 100644 --- a/chromium/components/autofill_assistant/browser/web/fake_element_store.cc +++ b/chromium/components/autofill_assistant/browser/web/fake_element_store.cc @@ -27,7 +27,7 @@ ClientStatus FakeElementStore::GetElement( out_element->SetNodeFrameId(it->second.object_data.node_frame_id); out_element->SetFrameStack(it->second.frame_stack); if (web_contents_ != nullptr) { - out_element->SetRenderFrameHost(web_contents_->GetMainFrame()); + out_element->SetRenderFrameHost(web_contents_->GetPrimaryMainFrame()); } return OkClientStatus(); } diff --git a/chromium/components/autofill_assistant/browser/web/js_filter_builder.cc b/chromium/components/autofill_assistant/browser/web/js_filter_builder.cc index 3be18b997e1..ce9bf589c9b 100644 --- a/chromium/components/autofill_assistant/browser/web/js_filter_builder.cc +++ b/chromium/components/autofill_assistant/browser/web/js_filter_builder.cc @@ -157,6 +157,12 @@ bool JsFilterBuilder::AddFilter(const SelectorProto::Filter& filter) { return true; } + case SelectorProto::Filter::kParent: + AddLine("elements = elements.flatMap((e) => {"); + AddLine(" return e.parentElement ? [e.parentElement] : [];"); + AddLine("});"); + return true; + case SelectorProto::Filter::kEnterFrame: case SelectorProto::Filter::kPseudoType: case SelectorProto::Filter::FILTER_NOT_SET: @@ -179,8 +185,8 @@ std::string JsFilterBuilder::AddRegexpInstance(const TextFilter& filter) { void JsFilterBuilder::AddRegexpFilter(const TextFilter& filter, const std::string& property) { std::string re_var = AddRegexpInstance(filter); - AddLine({"elements = elements.filter((e) => ", re_var, ".test(e.", property, - "));"}); + AddLine({"elements = elements.filter((e) => ", re_var, ".test(e[", + AddArgument(property), "]));"}); } std::string JsFilterBuilder::DeclareVariable() { diff --git a/chromium/components/autofill_assistant/browser/web/mock_autofill_assistant_agent.cc b/chromium/components/autofill_assistant/browser/web/mock_autofill_assistant_agent.cc new file mode 100644 index 00000000000..93594192da2 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/web/mock_autofill_assistant_agent.cc @@ -0,0 +1,37 @@ +// Copyright 2022 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_assistant/browser/web/mock_autofill_assistant_agent.h" + +#include "base/test/bind.h" +#include "content/public/browser/render_frame_host.h" +#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" + +namespace autofill_assistant { + +MockAutofillAssistantAgent::MockAutofillAssistantAgent() = default; +MockAutofillAssistantAgent::~MockAutofillAssistantAgent() = default; + +void MockAutofillAssistantAgent::BindPendingReceiver( + mojo::ScopedInterfaceEndpointHandle handle) { + receivers_.Add(this, + mojo::PendingAssociatedReceiver<mojom::AutofillAssistantAgent>( + std::move(handle))); +} + +// static +void MockAutofillAssistantAgent::RegisterForAllFrames( + content::WebContents* web_contents, + MockAutofillAssistantAgent* agent) { + web_contents->GetPrimaryMainFrame()->ForEachRenderFrameHost( + base::BindLambdaForTesting([agent](content::RenderFrameHost* host) { + host->GetRemoteAssociatedInterfaces()->OverrideBinderForTesting( + mojom::AutofillAssistantAgent::Name_, + base::BindRepeating( + &MockAutofillAssistantAgent::BindPendingReceiver, + base::Unretained(agent))); + })); +} + +} // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/web/mock_autofill_assistant_agent.h b/chromium/components/autofill_assistant/browser/web/mock_autofill_assistant_agent.h new file mode 100644 index 00000000000..4b3fca573c4 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/web/mock_autofill_assistant_agent.h @@ -0,0 +1,51 @@ +// Copyright 2022 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_ASSISTANT_BROWSER_WEB_MOCK_AUTOFILL_ASSISTANT_AGENT_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_WEB_MOCK_AUTOFILL_ASSISTANT_AGENT_H_ + +#include "base/callback.h" +#include "base/time/time.h" +#include "components/autofill_assistant/content/common/autofill_assistant_agent.mojom.h" +#include "content/public/browser/web_contents.h" +#include "mojo/public/cpp/bindings/associated_receiver_set.h" +#include "mojo/public/cpp/bindings/pending_associated_receiver.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace autofill_assistant { + +class MockAutofillAssistantAgent : public mojom::AutofillAssistantAgent { + public: + MockAutofillAssistantAgent(); + ~MockAutofillAssistantAgent() override; + + void BindPendingReceiver(mojo::ScopedInterfaceEndpointHandle handle); + static void RegisterForAllFrames(content::WebContents* web_contents, + MockAutofillAssistantAgent* agent); + + MOCK_METHOD(void, + GetSemanticNodes, + (int32_t role, + int32_t objective, + bool ignore_objective, + base::TimeDelta model_timeout, + base::OnceCallback<void(mojom::NodeDataStatus, + const std::vector<NodeData>&)> callback), + (override)); + MOCK_METHOD(void, + SetElementValue, + (int32_t backend_node_id, + const std::u16string& value, + bool send_events, + base::OnceCallback<void(bool)> callback), + (override)); + + private: + mojo::AssociatedReceiverSet<mojom::AutofillAssistantAgent> receivers_; +}; + +} // namespace autofill_assistant + +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_WEB_MOCK_AUTOFILL_ASSISTANT_AGENT_H_ diff --git a/chromium/components/autofill_assistant/browser/web/mock_web_controller.h b/chromium/components/autofill_assistant/browser/web/mock_web_controller.h index 00ecba63032..30280060b96 100644 --- a/chromium/components/autofill_assistant/browser/web/mock_web_controller.h +++ b/chromium/components/autofill_assistant/browser/web/mock_web_controller.h @@ -19,6 +19,7 @@ #include "testing/gmock/include/gmock/gmock.h" namespace autofill_assistant { +class ElementFinderResult; struct RectF; class MockWebController : public WebController { diff --git a/chromium/components/autofill_assistant/browser/web/selector_observer.cc b/chromium/components/autofill_assistant/browser/web/selector_observer.cc index a6a38e69c9e..1b12eaf2f41 100644 --- a/chromium/components/autofill_assistant/browser/web/selector_observer.cc +++ b/chromium/components/autofill_assistant/browser/web/selector_observer.cc @@ -61,23 +61,31 @@ SelectorObserver::RequestedElement::~RequestedElement() = default; SelectorObserver::RequestedElement::RequestedElement(const RequestedElement&) = default; +SelectorObserver::Settings::Settings(const base::TimeDelta& max_wait_time, + const base::TimeDelta& min_check_interval, + const base::TimeDelta& extra_timeout, + const base::TimeDelta& debounce_interval) + : max_wait_time(max_wait_time), + min_check_interval(min_check_interval), + extra_timeout(extra_timeout), + debounce_interval(debounce_interval) {} +SelectorObserver::Settings::~Settings() = default; +SelectorObserver::Settings::Settings(const Settings&) = default; + SelectorObserver::SelectorObserver( const std::vector<ObservableSelector>& selectors, - base::TimeDelta max_wait_time, - base::TimeDelta periodic_check_interval, - base::TimeDelta extra_timeout, + const Settings& settings, content::WebContents* web_contents, DevtoolsClient* devtools_client, const UserData* user_data, Callback update_callback) - : periodic_check_interval_(periodic_check_interval), - extra_timeout_(extra_timeout), + : settings_(settings), devtools_client_(devtools_client), web_contents_(web_contents), user_data_(user_data), update_callback_(update_callback) { const DomRoot root(/* frame_id = */ "", DomRoot::kUseMainDoc); - wait_time_remaining_ms_[root] = max_wait_time.InMilliseconds(); + wait_time_remaining_ms_[root] = settings.max_wait_time.InMilliseconds(); for (auto& selector : selectors) { selectors_.emplace(std::make_pair(selector.selector_id, selector)); // Every selector starts in the root frame @@ -106,7 +114,7 @@ ClientStatus SelectorObserver::Start(base::OnceClosure finished_callback) { ResolveObjectIdAndInjectFrame(root, 0); timeout_timer_ = std::make_unique<base::OneShotTimer>(); - timeout_timer_->Start(FROM_HERE, MaxTimeRemaining() + extra_timeout_, + timeout_timer_->Start(FROM_HERE, MaxTimeRemaining() + settings_.extra_timeout, base::BindOnce(&SelectorObserver::OnHardTimeout, weak_ptr_factory_.GetWeakPtr())); @@ -814,15 +822,21 @@ std::string SelectorObserver::BuildExpression(const DomRoot& dom_root) const { snippet.AddLine("(function selectorObserver() {"); snippet.AddLine( {"const pollInterval = ", - base::NumberToString(periodic_check_interval_.InMilliseconds()), ";"}); + base::NumberToString(settings_.min_check_interval.InMilliseconds()), + ";"}); int max_wait_time = wait_time_remaining_ms_.at(dom_root); - snippet.AddLine({"const maxRuntime = ", - base::NumberToString(base::saturated_cast<int>( - (base::Milliseconds(max_wait_time) + extra_timeout_) - .InMilliseconds())), - ";"}); + snippet.AddLine( + {"const maxRuntime = ", + base::NumberToString(base::saturated_cast<int>( + (base::Milliseconds(max_wait_time) + settings_.extra_timeout) + .InMilliseconds())), + ";"}); snippet.AddLine( {"const maxWaitTime = ", base::NumberToString(max_wait_time), ";"}); + snippet.AddLine( + {"const debounceInterval = ", + base::NumberToString(settings_.debounce_interval.InMilliseconds()), + ";"}); snippet.AddLine("const selectors = ["); size_t depth = frame_depth_.at(dom_root); diff --git a/chromium/components/autofill_assistant/browser/web/selector_observer.h b/chromium/components/autofill_assistant/browser/web/selector_observer.h index e89f7b9d2ce..0ffb89b63af 100644 --- a/chromium/components/autofill_assistant/browser/web/selector_observer.h +++ b/chromium/components/autofill_assistant/browser/web/selector_observer.h @@ -10,6 +10,7 @@ #include "base/callback.h" #include "base/containers/flat_map.h" +#include "base/memory/raw_ptr.h" #include "base/strings/strcat.h" #include "base/time/time.h" #include "base/timer/timer.h" @@ -60,6 +61,7 @@ class SelectorObserver : public WebControllerWorker { // If true, match will fail if more that one element matches the selector. bool strict; }; + // An update to the match status of a selector. struct Update { Update(); @@ -73,6 +75,7 @@ class SelectorObserver : public WebControllerWorker { // fetch the element later. int element_id; }; + struct RequestedElement { RequestedElement(const SelectorId& selector_id, int element_id); ~RequestedElement(); @@ -85,18 +88,37 @@ class SelectorObserver : public WebControllerWorker { // end. int element_id; }; + + // Settings to configure the selector observer. + struct Settings { + Settings(const base::TimeDelta& max_wait_time, + const base::TimeDelta& min_check_interval, + const base::TimeDelta& extra_timeout, + const base::TimeDelta& debounce_interval); + ~Settings(); + Settings(const Settings&); + // Maximum amount of time it will wait for an element. + const base::TimeDelta max_wait_time; + // Selector checks will run at least this often, even if no DOM changes are + // detected. + const base::TimeDelta min_check_interval; + // Extra wait time before assuming something has failed and giving up. + const base::TimeDelta extra_timeout; + // Wait until no DOM changes are received for this amount of time to check + // the selectors. An interval of 0 effectively disables debouncing. + const base::TimeDelta debounce_interval; + }; + using Callback = base::RepeatingCallback< void(const ClientStatus&, const std::vector<Update>&, SelectorObserver*)>; // |content::WebContents| and |DevtoolsClient| need to outlive this instance. // |UserData| needs to exist until Start() is called. explicit SelectorObserver(const std::vector<ObservableSelector>& selectors, - base::TimeDelta max_wait_time, - base::TimeDelta periodic_check_interval, - base::TimeDelta extra_timeout, - content::WebContents*, - DevtoolsClient*, - const UserData*, + const Settings& settings, + content::WebContents* web_contents, + DevtoolsClient* devtools_client, + const UserData* user_data, Callback update_callback); ~SelectorObserver() override; @@ -143,16 +165,14 @@ class SelectorObserver : public WebControllerWorker { ERROR_STATE = 4, }; State state_ = State::INITIALIZED; - const base::TimeDelta periodic_check_interval_; - const base::TimeDelta extra_timeout_; - base::TimeDelta max_wait_time_; + const Settings settings_; base::TimeTicks started_; std::unique_ptr<base::OneShotTimer> timeout_timer_; base::flat_map<SelectorId, ObservableSelector> selectors_; - DevtoolsClient* devtools_client_; - content::WebContents* web_contents_; - const UserData* user_data_; + raw_ptr<DevtoolsClient> devtools_client_; + raw_ptr<content::WebContents> web_contents_; + raw_ptr<const UserData> user_data_; Callback update_callback_; base::OnceClosure finished_callback_; diff --git a/chromium/components/autofill_assistant/browser/web/selector_observer_script.h b/chromium/components/autofill_assistant/browser/web/selector_observer_script.h index f4207e521ba..05e4e97f514 100644 --- a/chromium/components/autofill_assistant/browser/web/selector_observer_script.h +++ b/chromium/components/autofill_assistant/browser/web/selector_observer_script.h @@ -13,7 +13,7 @@ namespace selector_observer_script { // (1) selector_id -> element // (2) selector_id -> element // (3) element_id -> element -// (4) uses setTimeout so that initialization isn't blocked checking the +// (4) uses queueMicrotask so that initialization isn't blocked checking the // selectors. // (5) In case c++ doesn't call terminate() // (7) A result is serializable by value. Unserializable elements are saved in @@ -75,14 +75,15 @@ constexpr char kWaitForChangeScript[] = R"eof( return runTime - count * avgCheckTime; }; - const onChange = () => { + const checkSelectors = () => { if (startTime == null) { startTime = now(); } checkCount += 1; const start = now(); - if (pollingTid) clearTimeout(pollingTid); - pollingTid = setTimeout(onChange, pollInterval); + clearTimeout(pollingTid); + clearTimeout(debounceTid); + pollingTid = setTimeout(checkSelectors, pollInterval); for (const selector of selectors) { const node = runSelector(selector); @@ -108,6 +109,15 @@ constexpr char kWaitForChangeScript[] = R"eof( } }; + let debounceTid = 0; + const onChange = () => { + clearTimeout(debounceTid); + debounceTid = setTimeout(checkSelectors, debounceInterval); + }; + + // (4) + queueMicrotask(checkSelectors); + const config = { attributes: true, childList: true, @@ -121,8 +131,6 @@ constexpr char kWaitForChangeScript[] = R"eof( if (pollingTid) clearTimeout(pollingTid); clearTimeout(disconnectTid); }; - // (4) - setTimeout(onChange, 0); // (5) const disconnectTid = setTimeout(terminate, maxRuntime); @@ -176,7 +184,7 @@ constexpr char kWaitForChangeScript[] = R"eof( selectors.push(selector); } }); - onChange(); + checkSelectors(); }, getElements(elementIds) { const result = {}; diff --git a/chromium/components/autofill_assistant/browser/web/semantic_element_finder.cc b/chromium/components/autofill_assistant/browser/web/semantic_element_finder.cc new file mode 100644 index 00000000000..5ed6bf88d1c --- /dev/null +++ b/chromium/components/autofill_assistant/browser/web/semantic_element_finder.cc @@ -0,0 +1,246 @@ +// Copyright 2022 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_assistant/browser/web/semantic_element_finder.h" + +#include <utility> + +#include "base/barrier_callback.h" +#include "base/time/time.h" +#include "components/autofill_assistant/browser/devtools/devtools_client.h" +#include "components/autofill_assistant/browser/service.pb.h" +#include "components/autofill_assistant/browser/user_data.h" +#include "components/autofill_assistant/browser/user_data_util.h" +#include "components/autofill_assistant/browser/web/element.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" +#include "components/autofill_assistant/browser/web/js_filter_builder.h" +#include "components/autofill_assistant/browser/web/web_controller_util.h" +#include "components/autofill_assistant/content/browser/content_autofill_assistant_driver.h" +#include "components/autofill_assistant/content/common/autofill_assistant_agent.mojom.h" +#include "content/public/browser/global_routing_id.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/web_contents.h" + +namespace autofill_assistant { + +namespace { + +void AddHostToList(std::vector<content::GlobalRenderFrameHostId>& host_ids, + content::RenderFrameHost* host) { + host_ids.push_back(host->GetGlobalId()); +} + +ElementFinderInfoProto::SemanticInferenceStatus +NodeDataStatusToSemanticInferenceStatus( + mojom::NodeDataStatus node_data_status) { + switch (node_data_status) { + case mojom::NodeDataStatus::kSuccess: + return ElementFinderInfoProto::SUCCESS; + case mojom::NodeDataStatus::kUnexpectedError: + return ElementFinderInfoProto::UNEXPECTED_ERROR; + case mojom::NodeDataStatus::kInitializationError: + return ElementFinderInfoProto::INITIALIZATION_ERROR; + case mojom::NodeDataStatus::kModelLoadError: + return ElementFinderInfoProto::MODEL_LOAD_ERROR; + case mojom::NodeDataStatus::kModelLoadTimeout: + return ElementFinderInfoProto::MODEL_LOAD_TIMEOUT; + } +} + +} // namespace + +SemanticElementFinder::SemanticElementFinder( + content::WebContents* web_contents, + DevtoolsClient* devtools_client, + AnnotateDomModelService* annotate_dom_model_service, + const Selector& selector) + : web_contents_(web_contents), + devtools_client_(devtools_client), + annotate_dom_model_service_(annotate_dom_model_service), + selector_(selector) { + DCHECK(annotate_dom_model_service_); +} + +SemanticElementFinder::~SemanticElementFinder() = default; + +void SemanticElementFinder::GiveUpWithError(const ClientStatus& status) { + DCHECK(!status.ok()); + if (!callback_) { + return; + } + + SendResult(status, ElementFinderResult::EmptyResult()); +} + +void SemanticElementFinder::ResultFound( + content::RenderFrameHost* render_frame_host, + const std::string& object_id, + int backend_node_id) { + if (!callback_) { + return; + } + + ElementFinderResult result; + result.SetRenderFrameHost(render_frame_host); + result.SetObjectId(object_id); + result.SetBackendNodeId(backend_node_id); + + SendResult(OkClientStatus(), result); +} + +void SemanticElementFinder::SendResult(const ClientStatus& status, + const ElementFinderResult& result) { + DCHECK(callback_); + std::move(callback_).Run(status, + std::make_unique<ElementFinderResult>(result)); +} + +void SemanticElementFinder::Start(const ElementFinderResult& start_element, + BaseElementFinder::Callback callback) { + callback_ = std::move(callback); + + auto* start_frame = start_element.render_frame_host(); + if (!start_frame) { + start_frame = web_contents_->GetPrimaryMainFrame(); + } + RunAnnotateDomModel(start_frame); +} + +ElementFinderInfoProto SemanticElementFinder::GetLogInfo() const { + DCHECK(!callback_); // Run after finish. + + ElementFinderInfoProto info; + DCHECK(selector_.proto.has_semantic_information()); + for (auto node_data_status : node_data_frame_status_) { + info.mutable_semantic_inference_result()->add_status_per_frame( + NodeDataStatusToSemanticInferenceStatus(node_data_status)); + } + for (const auto& semantic_node_result : semantic_node_results_) { + auto* predicted_element = + info.mutable_semantic_inference_result()->add_predicted_elements(); + predicted_element->set_backend_node_id( + semantic_node_result.backend_node_id()); + *predicted_element->mutable_semantic_information() = + selector_.proto.semantic_information(); + // TODO(b/217160707): For the ignore_objective case this is not correct + // and the inferred objective should be returned from the Agent and used + // here. + } + + return info; +} + +int SemanticElementFinder::GetBackendNodeId() const { + if (semantic_node_results_.empty()) { + return 0; + } + return semantic_node_results_[0].backend_node_id(); +} + +void SemanticElementFinder::RunAnnotateDomModel( + content::RenderFrameHost* start_frame) { + std::vector<content::GlobalRenderFrameHostId> host_ids; + start_frame->ForEachRenderFrameHost( + base::BindRepeating(&AddHostToList, std::ref(host_ids))); + const auto run_on_frame = + base::BarrierCallback<std::vector<GlobalBackendNodeId>>( + host_ids.size(), + base::BindOnce(&SemanticElementFinder::OnRunAnnotateDomModel, + weak_ptr_factory_.GetWeakPtr())); + for (const auto& host_id : host_ids) { + RunAnnotateDomModelOnFrame(host_id, run_on_frame); + } +} + +void SemanticElementFinder::RunAnnotateDomModelOnFrame( + const content::GlobalRenderFrameHostId& host_id, + base::OnceCallback<void(std::vector<GlobalBackendNodeId>)> callback) { + content::RenderFrameHost* render_frame_host = + content::RenderFrameHost::FromID(host_id); + if (!render_frame_host) { + std::move(callback).Run(std::vector<GlobalBackendNodeId>()); + return; + } + + auto* driver = ContentAutofillAssistantDriver::GetOrCreateForRenderFrameHost( + render_frame_host, annotate_dom_model_service_); + if (!driver) { + NOTREACHED(); + std::move(callback).Run(std::vector<GlobalBackendNodeId>()); + return; + } + + driver->GetAutofillAssistantAgent()->GetSemanticNodes( + selector_.proto.semantic_information().semantic_role(), + selector_.proto.semantic_information().objective(), + selector_.proto.semantic_information().ignore_objective(), + base::Milliseconds( + selector_.proto.semantic_information().model_timeout_ms()), + base::BindOnce(&SemanticElementFinder::OnRunAnnotateDomModelOnFrame, + weak_ptr_factory_.GetWeakPtr(), host_id, + std::move(callback))); +} + +void SemanticElementFinder::OnRunAnnotateDomModelOnFrame( + const content::GlobalRenderFrameHostId& host_id, + base::OnceCallback<void(std::vector<GlobalBackendNodeId>)> callback, + mojom::NodeDataStatus status, + const std::vector<NodeData>& node_data) { + node_data_frame_status_.emplace_back(status); + + std::vector<GlobalBackendNodeId> node_ids; + for (const auto& node : node_data) { + node_ids.emplace_back(GlobalBackendNodeId(host_id, node.backend_node_id)); + } + std::move(callback).Run(node_ids); +} + +void SemanticElementFinder::OnRunAnnotateDomModel( + const std::vector<std::vector<GlobalBackendNodeId>>& all_nodes) { + for (const auto& node_ids : all_nodes) { + semantic_node_results_.insert(semantic_node_results_.end(), + node_ids.begin(), node_ids.end()); + } + + // For now we only support finding a single element. + // TODO(b/224746702): Emit multiple ResolveNode calls for the case where the + // result type is not ResultType::kExactlyOneMatch. + if (semantic_node_results_.size() > 1) { + VLOG(1) << __func__ << " Got " << semantic_node_results_.size() + << " matches for " << selector_ << ", when only 1 was expected."; + GiveUpWithError(ClientStatus(TOO_MANY_ELEMENTS)); + return; + } + if (semantic_node_results_.empty()) { + GiveUpWithError(ClientStatus(ELEMENT_RESOLUTION_FAILED)); + return; + } + + // We need to set the empty string for the frame id. The expectation is that + // backend node ids are global and devtools is able to resolve the node + // without an explicit frame id. + devtools_client_->GetDOM()->ResolveNode( + dom::ResolveNodeParams::Builder() + .SetBackendNodeId(semantic_node_results_[0].backend_node_id()) + .Build(), + /* current_frame_id= */ std::string(), + base::BindOnce(&SemanticElementFinder::OnResolveNodeForAnnotateDom, + weak_ptr_factory_.GetWeakPtr(), + semantic_node_results_[0])); +} + +void SemanticElementFinder::OnResolveNodeForAnnotateDom( + GlobalBackendNodeId node, + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<dom::ResolveNodeResult> result) { + if (result && result->GetObject() && result->GetObject()->HasObjectId()) { + ResultFound(content::RenderFrameHost::FromID(node.host_id()), + result->GetObject()->GetObjectId(), node.backend_node_id()); + return; + } + SendResult(ClientStatus(ELEMENT_RESOLUTION_FAILED), + ElementFinderResult::EmptyResult()); +} + +} // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/web/semantic_element_finder.h b/chromium/components/autofill_assistant/browser/web/semantic_element_finder.h new file mode 100644 index 00000000000..00a6854395b --- /dev/null +++ b/chromium/components/autofill_assistant/browser/web/semantic_element_finder.h @@ -0,0 +1,105 @@ +// Copyright 2022 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_ASSISTANT_BROWSER_WEB_SEMANTIC_ELEMENT_FINDER_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_WEB_SEMANTIC_ELEMENT_FINDER_H_ + +#include <string> +#include <vector> + +#include "base/callback_forward.h" +#include "base/memory/raw_ptr.h" +#include "base/memory/weak_ptr.h" +#include "components/autofill_assistant/browser/client_status.h" +#include "components/autofill_assistant/browser/devtools/devtools/domains/types_dom.h" +#include "components/autofill_assistant/browser/devtools/devtools_client.h" +#include "components/autofill_assistant/browser/selector.h" +#include "components/autofill_assistant/browser/web/base_element_finder.h" +#include "components/autofill_assistant/browser/web/element.h" +#include "components/autofill_assistant/content/browser/annotate_dom_model_service.h" +#include "components/autofill_assistant/content/common/autofill_assistant_types.mojom.h" +#include "components/autofill_assistant/content/common/node_data.h" + +namespace content { +class WebContents; +class RenderFrameHost; +struct GlobalRenderFrameHostId; +} // namespace content + +namespace autofill_assistant { +class DevtoolsClient; +class ElementFinderResult; + +class SemanticElementFinder : public BaseElementFinder { + public: + SemanticElementFinder(content::WebContents* web_contents, + DevtoolsClient* devtools_client, + AnnotateDomModelService* annotate_dom_model_service, + const Selector& selector); + ~SemanticElementFinder() override; + + SemanticElementFinder(const SemanticElementFinder&) = delete; + SemanticElementFinder& operator=(const SemanticElementFinder&) = delete; + + void Start(const ElementFinderResult& start_element, + BaseElementFinder::Callback callback) override; + + ElementFinderInfoProto GetLogInfo() const override; + + // Returns the backend node id of the first result (if any), or 0. + int GetBackendNodeId() const override; + + private: + // Returns the given status and no element. This expects an error status. + void GiveUpWithError(const ClientStatus& status); + + // Builds a result from the |render_frame_host|, the |object_id| and the + // |backend_node_id| returns it withan ok status. + void ResultFound(content::RenderFrameHost* render_frame_host, + const std::string& object_id, + int backend_node_id); + + // Call |callback_| with the |status| and |result|. + void SendResult(const ClientStatus& status, + const ElementFinderResult& result); + + // Run the model annotation on all frames for the current |start_frame|. + void RunAnnotateDomModel(content::RenderFrameHost* start_frame); + + // Runs the model on the frame identified by |host_id|. + void RunAnnotateDomModelOnFrame( + const content::GlobalRenderFrameHostId& host_id, + base::OnceCallback<void(std::vector<GlobalBackendNodeId>)> callback); + void OnRunAnnotateDomModelOnFrame( + const content::GlobalRenderFrameHostId& host_id, + base::OnceCallback<void(std::vector<GlobalBackendNodeId>)> callback, + mojom::NodeDataStatus status, + const std::vector<NodeData>& node_data); + + // Called once the model has been run on all frames. + void OnRunAnnotateDomModel( + const std::vector<std::vector<GlobalBackendNodeId>>& all_nodes); + + void OnResolveNodeForAnnotateDom( + GlobalBackendNodeId node, + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<dom::ResolveNodeResult> result); + + const raw_ptr<content::WebContents> web_contents_; + const raw_ptr<DevtoolsClient> devtools_client_; + const raw_ptr<AnnotateDomModelService> annotate_dom_model_service_; + const Selector selector_; + BaseElementFinder::Callback callback_; + + // Elements gathered through all frames. Unused if the |selector_| does not + // contain |SemanticInformation|. + std::vector<GlobalBackendNodeId> semantic_node_results_; + std::vector<mojom::NodeDataStatus> node_data_frame_status_; + + base::WeakPtrFactory<SemanticElementFinder> weak_ptr_factory_{this}; +}; + +} // namespace autofill_assistant + +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_WEB_SEMANTIC_ELEMENT_FINDER_H_ diff --git a/chromium/components/autofill_assistant/browser/web/semantic_element_finder_browsertest.cc b/chromium/components/autofill_assistant/browser/web/semantic_element_finder_browsertest.cc new file mode 100644 index 00000000000..1b564d8072d --- /dev/null +++ b/chromium/components/autofill_assistant/browser/web/semantic_element_finder_browsertest.cc @@ -0,0 +1,470 @@ +// Copyright 2022 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_assistant/browser/web/web_controller.h" + +#include <memory> +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/callback_helpers.h" +#include "base/run_loop.h" +#include "base/task/single_thread_task_runner.h" +#include "base/test/bind.h" +#include "base/test/gmock_callback_support.h" +#include "base/test/mock_callback.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "components/autofill_assistant/browser/actions/wait_for_dom_action.h" +#include "components/autofill_assistant/browser/base_browsertest.h" +#include "components/autofill_assistant/browser/client_status.h" +#include "components/autofill_assistant/browser/fake_script_executor_ui_delegate.h" +#include "components/autofill_assistant/browser/mock_script_executor_delegate.h" +#include "components/autofill_assistant/browser/model.pb.h" +#include "components/autofill_assistant/browser/script.h" +#include "components/autofill_assistant/browser/script_executor.h" +#include "components/autofill_assistant/browser/selector.h" +#include "components/autofill_assistant/browser/service.pb.h" +#include "components/autofill_assistant/browser/service/mock_service.h" +#include "components/autofill_assistant/browser/trigger_context.h" +#include "components/autofill_assistant/browser/user_data.h" +#include "components/autofill_assistant/browser/web/element.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" +#include "components/autofill_assistant/browser/web/element_finder_result_type.h" +#include "components/autofill_assistant/browser/web/element_store.h" +#include "components/autofill_assistant/browser/web/mock_autofill_assistant_agent.h" +#include "components/autofill_assistant/content/common/autofill_assistant_types.mojom.h" +#include "components/autofill_assistant/content/common/node_data.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/test/browser_test.h" +#include "content/public/test/browser_test_utils.h" +#include "content/public/test/content_browser_test_utils.h" +#include "content/shell/browser/shell.h" +#include "mojo/public/cpp/bindings/associated_receiver_set.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace autofill_assistant { +namespace { + +using ::base::test::RunOnceCallback; +using ::testing::_; +using ::testing::IsEmpty; +using ::testing::Return; +using ::testing::WithArgs; + +} // namespace + +class SemanticElementFinderBrowserTest + : public autofill_assistant::BaseBrowserTest, + public content::WebContentsObserver { + public: + SemanticElementFinderBrowserTest() {} + + SemanticElementFinderBrowserTest(const SemanticElementFinderBrowserTest&) = + delete; + SemanticElementFinderBrowserTest& operator=( + const SemanticElementFinderBrowserTest&) = delete; + + ~SemanticElementFinderBrowserTest() override {} + + void SetUpOnMainThread() override { + BaseBrowserTest::SetUpOnMainThread(); + + MockAutofillAssistantAgent::RegisterForAllFrames( + shell()->web_contents(), &autofill_assistant_agent_); + + annotate_dom_model_service_ = std::make_unique<AnnotateDomModelService>( + /* opt_guide= */ nullptr, /* background_task_runner= */ nullptr); + web_controller_ = WebController::CreateForWebContents( + shell()->web_contents(), &user_data_, &log_info_, + annotate_dom_model_service_.get(), + /* enable_full_stack_traces= */ true); + + Observe(shell()->web_contents()); + } + + void FindElement(const Selector& selector, + ClientStatus* status_out, + ElementFinderResult* result_out) { + base::RunLoop run_loop; + web_controller_->FindElement( + selector, /* strict_mode= */ true, + base::BindOnce(&SemanticElementFinderBrowserTest::OnFindElement, + base::Unretained(this), run_loop.QuitClosure(), + base::Unretained(status_out), + base::Unretained(result_out))); + run_loop.Run(); + } + + void OnFindElement(base::OnceClosure done_callback, + ClientStatus* status_out, + ElementFinderResult* result_out, + const ClientStatus& status, + std::unique_ptr<ElementFinderResult> result) { + ASSERT_TRUE(result); + std::move(done_callback).Run(); + if (status_out) + *status_out = status; + if (result_out) + *result_out = *result; + } + + void RunStrictElementCheck(const Selector& selector, bool expected_result) { + ClientStatus status; + ElementFinderResult ignored_element; + FindElement(selector, &status, &ignored_element); + EXPECT_EQ(expected_result, status.ok()) + << "selector: " << selector << " status: " << expected_result; + } + + void FindElementExpectEmptyResult(const Selector& selector) { + ClientStatus status; + ElementFinderResult element; + FindElement(selector, &status, &element); + EXPECT_EQ(ELEMENT_RESOLUTION_FAILED, status.proto_status()); + EXPECT_THAT(element.object_id(), IsEmpty()); + } + + void OnScriptFinished(base::OnceClosure done_callback, + const ScriptExecutor::Result& result) { + std::move(done_callback).Run(); + } + + ClientStatus RunWaitForDom( + const ActionProto& wait_for_dom_action, + bool use_observers, + base::OnceCallback<void(ScriptExecutor*)> run_expectations) { + MockScriptExecutorDelegate mock_script_executor_delegate; + ON_CALL(mock_script_executor_delegate, GetWebController) + .WillByDefault(Return(web_controller_.get())); + TriggerContext trigger_context; + if (use_observers) { + trigger_context.SetScriptParameters(std::make_unique<ScriptParameters>( + base::flat_map<std::string, std::string>{ + {"ENABLE_OBSERVER_WAIT_FOR_DOM", "true"}})); + } + + MockService mock_service; + ActionsResponseProto actions_response; + *actions_response.add_actions() = wait_for_dom_action; + std::string serialized_actions_response; + actions_response.SerializeToString(&serialized_actions_response); + EXPECT_CALL(mock_service, GetActions) + .WillOnce(RunOnceCallback<5>(200, serialized_actions_response, + ServiceRequestSender::ResponseInfo{})); + + std::vector<ProcessedActionProto> captured_processed_actions; + EXPECT_CALL(mock_service, GetNextActions) + .WillOnce(WithArgs<3, 6>( + [&captured_processed_actions]( + const std::vector<ProcessedActionProto>& processed_actions, + ServiceRequestSender::ResponseCallback callback) { + captured_processed_actions = processed_actions; + + // Send empty response to stop the script executor. + std::move(callback).Run(200, std::string(), + ServiceRequestSender::ResponseInfo{}); + })); + ON_CALL(mock_script_executor_delegate, GetTriggerContext()) + .WillByDefault(Return(&trigger_context)); + ON_CALL(mock_script_executor_delegate, GetService()) + .WillByDefault(Return(&mock_service)); + GURL test_script_url("https://example.com"); + ON_CALL(mock_script_executor_delegate, GetScriptURL()) + .WillByDefault(testing::ReturnRef(test_script_url)); + std::vector<std::unique_ptr<Script>> ordered_interrupts; + FakeScriptExecutorUiDelegate fake_script_executor_ui_delegate; + UserData fake_user_data; + ScriptExecutor script_executor( + /* script_path= */ std::string(), + /* additional_context= */ std::make_unique<TriggerContext>(), + /* global_payload= */ std::string(), + /* script_payload= */ std::string(), + /* listener= */ nullptr, &ordered_interrupts, + &mock_script_executor_delegate, &fake_script_executor_ui_delegate); + base::RunLoop run_loop; + script_executor.Run( + &fake_user_data, + base::BindOnce(&SemanticElementFinderBrowserTest::OnScriptFinished, + base::Unretained(this), run_loop.QuitClosure())); + run_loop.Run(); + std::move(run_expectations).Run(&script_executor); + + CHECK_EQ(captured_processed_actions.size(), 1u); + return ClientStatus(captured_processed_actions[0].status()); + } + + int GetBackendNodeId(Selector selector, ClientStatus* status_out) { + std::unique_ptr<ElementFinderResult> element_result; + int backend_node_id = -1; + + base::RunLoop run_loop_1; + web_controller_->FindElement( + selector, true, + base::BindLambdaForTesting( + [&](const ClientStatus& status, + std::unique_ptr<ElementFinderResult> result) { + element_result = std::move(result); + *status_out = status; + run_loop_1.Quit(); + })); + run_loop_1.Run(); + if (!status_out->ok()) { + return backend_node_id; + } + + // Second part in sequence, lookup backend node id. + base::RunLoop run_loop_2; + web_controller_->GetBackendNodeId( + *element_result, + base::BindLambdaForTesting([&](const ClientStatus& status, int id) { + *status_out = status; + backend_node_id = id; + run_loop_2.Quit(); + })); + run_loop_2.Run(); + + log_info_.Clear(); + return backend_node_id; + } + + protected: + std::unique_ptr<WebController> web_controller_; + UserData user_data_; + ProcessedActionStatusDetailsProto log_info_; + MockAutofillAssistantAgent autofill_assistant_agent_; + std::unique_ptr<AnnotateDomModelService> annotate_dom_model_service_; +}; + +IN_PROC_BROWSER_TEST_F(SemanticElementFinderBrowserTest, + WaitForDomForSemanticElement) { + // This element is unique. + SelectorProto baseline_selector = ToSelectorProto("#select"); + + ClientStatus element_status; + int backend_node_id = + GetBackendNodeId(Selector(baseline_selector), &element_status); + EXPECT_TRUE(element_status.ok()); + + NodeData node_data; + node_data.backend_node_id = backend_node_id; + EXPECT_CALL(autofill_assistant_agent_, + GetSemanticNodes(1, 2, false, base::Milliseconds(5000), _)) + .WillOnce(RunOnceCallback<4>(mojom::NodeDataStatus::kSuccess, + std::vector<NodeData>{node_data})) + // Capture any other frames. + .WillRepeatedly(RunOnceCallback<4>( + mojom::NodeDataStatus::kUnexpectedError, std::vector<NodeData>())); + + ActionProto action_proto; + auto* wait_for_dom = action_proto.mutable_wait_for_dom(); + auto* condition = wait_for_dom->mutable_wait_condition(); + condition->mutable_client_id()->set_identifier("e"); + condition->set_require_unique_element(true); + auto* semantic_information = + condition->mutable_match()->mutable_semantic_information(); + semantic_information->set_semantic_role(1); + semantic_information->set_objective(2); + + base::MockCallback<base::OnceCallback<void(ScriptExecutor*)>> + run_expectations; + EXPECT_CALL(run_expectations, Run(_)) + .WillOnce([](ScriptExecutor* script_executor) { + EXPECT_TRUE(script_executor->GetElementStore()->HasElement("e")); + }); + ClientStatus status = RunWaitForDom(action_proto, /* use_observers= */ false, + run_expectations.Get()); + EXPECT_EQ(status.proto_status(), ACTION_APPLIED); +} + +IN_PROC_BROWSER_TEST_F(SemanticElementFinderBrowserTest, + ElementExistenceCheckWithSemanticModel) { + ClientStatus status; + int backend_node_id = GetBackendNodeId(Selector({"#button"}), &status); + EXPECT_TRUE(status.ok()); + + NodeData node_data; + node_data.backend_node_id = backend_node_id; + EXPECT_CALL(autofill_assistant_agent_, + GetSemanticNodes(1, 2, false, base::Milliseconds(5000), _)) + .WillOnce(RunOnceCallback<4>(mojom::NodeDataStatus::kSuccess, + std::vector<NodeData>{node_data})) + // Capture any other frames. + .WillRepeatedly(RunOnceCallback<4>( + mojom::NodeDataStatus::kUnexpectedError, std::vector<NodeData>())); + + // We pretend that the button is the correct element. + SelectorProto proto; + auto* semantic_information = proto.mutable_semantic_information(); + semantic_information->set_semantic_role(1); + semantic_information->set_objective(2); + RunStrictElementCheck(Selector(proto), true); + + ASSERT_EQ(log_info_.element_finder_info().size(), 1); + const auto& result = + log_info_.element_finder_info(0).semantic_inference_result(); + ASSERT_EQ(1, result.predicted_elements().size()); + EXPECT_EQ(backend_node_id, result.predicted_elements(0).backend_node_id()); + EXPECT_THAT( + 1, result.predicted_elements(0).semantic_information().semantic_role()); + EXPECT_THAT(2, + result.predicted_elements(0).semantic_information().objective()); +} + +IN_PROC_BROWSER_TEST_F(SemanticElementFinderBrowserTest, + ElementExistenceCheckWithSemanticModelOOPIF) { + ClientStatus status; + int backend_node_id = + GetBackendNodeId(Selector({"#iframeExternal", "#button"}), &status); + EXPECT_TRUE(status.ok()); + + NodeData node_data; + node_data.backend_node_id = backend_node_id; + EXPECT_CALL(autofill_assistant_agent_, + GetSemanticNodes(1, 2, false, base::Milliseconds(5000), _)) + .WillOnce(RunOnceCallback<4>(mojom::NodeDataStatus::kSuccess, + std::vector<NodeData>{node_data})) + // Capture any other frames. + .WillRepeatedly(RunOnceCallback<4>( + mojom::NodeDataStatus::kUnexpectedError, std::vector<NodeData>())); + + // We pretend that the button is the correct element. + SelectorProto proto; + auto* semantic_information = proto.mutable_semantic_information(); + semantic_information->set_semantic_role(1); + semantic_information->set_objective(2); + RunStrictElementCheck(Selector(proto), true); + + ASSERT_EQ(log_info_.element_finder_info().size(), 1); + const auto& result = + log_info_.element_finder_info(0).semantic_inference_result(); + ASSERT_EQ(1, result.predicted_elements().size()); + EXPECT_EQ(backend_node_id, result.predicted_elements(0).backend_node_id()); + EXPECT_THAT( + 1, result.predicted_elements(0).semantic_information().semantic_role()); + EXPECT_THAT(2, + result.predicted_elements(0).semantic_information().objective()); +} + +IN_PROC_BROWSER_TEST_F(SemanticElementFinderBrowserTest, + ElementExistenceCheckWithSemanticModelNotFound) { + // All frames return an empty list as a result. + EXPECT_CALL(autofill_assistant_agent_, + GetSemanticNodes(1, 2, false, base::Milliseconds(5000), _)) + .WillRepeatedly(RunOnceCallback<4>(mojom::NodeDataStatus::kSuccess, + std::vector<NodeData>{})); + + SelectorProto proto; + auto* semantic_information = proto.mutable_semantic_information(); + semantic_information->set_semantic_role(1); + semantic_information->set_objective(2); + FindElementExpectEmptyResult(Selector(proto)); +} + +IN_PROC_BROWSER_TEST_F(SemanticElementFinderBrowserTest, + ElementExistenceCheckWithSemanticMultipleFound) { + SelectorProto proto; + auto* semantic_information = proto.mutable_semantic_information(); + semantic_information->set_semantic_role(1); + semantic_information->set_objective(2); + + NodeData node_data; + node_data.backend_node_id = 5; + NodeData node_data_other; + node_data_other.backend_node_id = 13; + EXPECT_CALL(autofill_assistant_agent_, + GetSemanticNodes(1, 2, false, base::Milliseconds(5000), _)) + .WillOnce(RunOnceCallback<4>(mojom::NodeDataStatus::kSuccess, + std::vector<NodeData>{node_data})) + .WillOnce(RunOnceCallback<4>(mojom::NodeDataStatus::kSuccess, + std::vector<NodeData>{node_data_other})) + // Capture any other frames. + .WillRepeatedly(RunOnceCallback<4>( + mojom::NodeDataStatus::kUnexpectedError, std::vector<NodeData>())); + + // Two elements are found in different frames. + ClientStatus status; + FindElement(Selector(proto), &status, nullptr); + EXPECT_EQ(TOO_MANY_ELEMENTS, status.proto_status()); +} + +IN_PROC_BROWSER_TEST_F( + SemanticElementFinderBrowserTest, + ElementExistenceCheckWithSemanticModelUsesIgnoreObjective) { + NodeData node_data; + node_data.backend_node_id = 5; + EXPECT_CALL(autofill_assistant_agent_, + GetSemanticNodes(1, 2, true, base::Milliseconds(5000), _)) + .WillOnce(RunOnceCallback<4>(mojom::NodeDataStatus::kSuccess, + std::vector<NodeData>{node_data})) + .WillRepeatedly(RunOnceCallback<4>( + mojom::NodeDataStatus::kUnexpectedError, std::vector<NodeData>())); + + SelectorProto proto; + auto* semantic_information = proto.mutable_semantic_information(); + semantic_information->set_semantic_role(1); + semantic_information->set_objective(2); + // All we want is this to be propagated to the GetSemanticNodes call as + // configured in the previous expectation. + semantic_information->set_ignore_objective(true); + + ClientStatus ignore_status; + FindElement(Selector(proto), &ignore_status, nullptr); + + // TODO(b/217160707): For now we expect the originally passed in semantic info + // to be logged instead of the objective inferred by the model. + ASSERT_EQ(log_info_.element_finder_info().size(), 1); + const auto& result = + log_info_.element_finder_info(0).semantic_inference_result(); + ASSERT_EQ(1, result.predicted_elements().size()); + EXPECT_EQ(5, result.predicted_elements(0).backend_node_id()); + EXPECT_THAT( + 1, result.predicted_elements(0).semantic_information().semantic_role()); + EXPECT_THAT(2, + result.predicted_elements(0).semantic_information().objective()); +} + +IN_PROC_BROWSER_TEST_F(SemanticElementFinderBrowserTest, + SemanticAndCssComparison) { + ClientStatus status; + int backend_node_id = GetBackendNodeId(Selector({"#button"}), &status); + EXPECT_TRUE(status.ok()); + + NodeData node_data; + node_data.backend_node_id = backend_node_id; + EXPECT_CALL(autofill_assistant_agent_, + GetSemanticNodes(1, 2, false, base::Milliseconds(5000), _)) + .WillOnce(RunOnceCallback<4>(mojom::NodeDataStatus::kSuccess, + std::vector<NodeData>{node_data})) + // Capture any other frames. + .WillRepeatedly(RunOnceCallback<4>( + mojom::NodeDataStatus::kUnexpectedError, std::vector<NodeData>())); + + // We pretend that the button is the correct element. + SelectorProto proto = ToSelectorProto("#button"); + auto* semantic_information = proto.mutable_semantic_information(); + semantic_information->set_semantic_role(1); + semantic_information->set_objective(2); + semantic_information->set_check_matches_css_element(true); + RunStrictElementCheck(Selector(proto), true); + + ASSERT_EQ(log_info_.element_finder_info().size(), 1); + const auto& result = + log_info_.element_finder_info(0).semantic_inference_result(); + ASSERT_EQ(1, result.predicted_elements().size()); + EXPECT_EQ(backend_node_id, result.predicted_elements(0).backend_node_id()); + EXPECT_THAT( + 1, result.predicted_elements(0).semantic_information().semantic_role()); + EXPECT_THAT(2, + result.predicted_elements(0).semantic_information().objective()); + EXPECT_TRUE(result.predicted_elements(0).matches_css_element()); +} + +} // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/web/web_controller.cc b/chromium/components/autofill_assistant/browser/web/web_controller.cc index ffae5c272c1..3a06cd59bb7 100644 --- a/chromium/components/autofill_assistant/browser/web/web_controller.cc +++ b/chromium/components/autofill_assistant/browser/web/web_controller.cc @@ -31,8 +31,12 @@ #include "components/autofill_assistant/browser/string_conversions_util.h" #include "components/autofill_assistant/browser/user_data_util.h" #include "components/autofill_assistant/browser/web/element.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" +#include "components/autofill_assistant/browser/web/element_finder_result_type.h" #include "components/autofill_assistant/browser/web/selector_observer.h" #include "components/autofill_assistant/browser/web/web_controller_util.h" +#include "components/autofill_assistant/content/browser/content_autofill_assistant_driver.h" +#include "components/autofill_assistant/content/common/autofill_assistant_agent.mojom.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/render_frame_host.h" @@ -875,21 +879,21 @@ void WebController::FindElement(const Selector& selector, ElementFinder::Callback callback) { RunElementFinder(/* start_element= */ ElementFinderResult::EmptyResult(), selector, - strict_mode ? ElementFinder::ResultType::kExactlyOneMatch - : ElementFinder::ResultType::kAnyMatch, + strict_mode ? ElementFinderResultType::kExactlyOneMatch + : ElementFinderResultType::kAnyMatch, std::move(callback)); } void WebController::FindAllElements(const Selector& selector, ElementFinder::Callback callback) { RunElementFinder(/* start_element= */ ElementFinderResult::EmptyResult(), - selector, ElementFinder::ResultType::kMatchArray, + selector, ElementFinderResultType::kMatchArray, std::move(callback)); } void WebController::RunElementFinder(const ElementFinderResult& start_element, const Selector& selector, - ElementFinder::ResultType result_type, + ElementFinderResultType result_type, ElementFinder::Callback callback) { auto finder = std::make_unique<ElementFinder>( web_contents_, devtools_client_.get(), user_data_, log_info_, @@ -915,13 +919,11 @@ void WebController::OnFindElementResult( ClientStatus WebController::ObserveSelectors( const std::vector<SelectorObserver::ObservableSelector>& selectors, - base::TimeDelta timeout_ms, - base::TimeDelta periodic_check_interval, - base::TimeDelta extra_timeout, + const SelectorObserver::Settings& settings, SelectorObserver::Callback callback) { auto observer = std::make_unique<SelectorObserver>( - selectors, timeout_ms, periodic_check_interval, extra_timeout, - web_contents_, devtools_client_.get(), user_data_, std::move(callback)); + selectors, settings, web_contents_, devtools_client_.get(), user_data_, + std::move(callback)); auto* ptr = observer.get(); pending_workers_.emplace_back(std::move(observer)); return ptr->Start(base::BindOnce(&WebController::OnSelectorObserverFinished, @@ -1000,11 +1002,28 @@ void WebController::GetElementFormAndFieldData( ContentAutofillDriver* driver, const autofill::FormData&, const autofill::FormFieldData&)> callback) { - GetBackendNodeId( - element, - base::BindOnce(&WebController::OnGetBackendNodeIdForFormAndFieldData, - weak_ptr_factory_.GetWeakPtr(), element, - std::move(callback))); + if (!element.backend_node_id()) { + DVLOG(1) << __func__ + << "No backend node id on element intended for native execution."; + std::move(callback).Run(UnexpectedErrorStatus(__FILE__, __LINE__), nullptr, + autofill::FormData(), autofill::FormFieldData()); + return; + } + + ContentAutofillDriver* driver = + ContentAutofillDriver::GetForRenderFrameHost(element.render_frame_host()); + if (driver == nullptr) { + DVLOG(1) << __func__ << " Failed to get the autofill driver."; + std::move(callback).Run(UnexpectedErrorStatus(__FILE__, __LINE__), nullptr, + autofill::FormData(), autofill::FormFieldData()); + return; + } + + driver->GetAutofillAgent()->GetElementFormAndFieldDataForDevToolsNodeId( + *element.backend_node_id(), + base::BindOnce(&WebController::OnGetFormAndFieldData, + weak_ptr_factory_.GetWeakPtr(), std::move(callback), + driver)); } void WebController::GetBackendNodeId( @@ -1034,35 +1053,6 @@ void WebController::OnGetBackendNodeId( result->GetNode()->GetBackendNodeId()); } -void WebController::OnGetBackendNodeIdForFormAndFieldData( - const ElementFinderResult& element, - base::OnceCallback<void(const ClientStatus&, - ContentAutofillDriver* driver, - const autofill::FormData&, - const autofill::FormFieldData&)> callback, - const ClientStatus& node_status, - const int backend_node_id) { - if (!node_status.ok()) { - std::move(callback).Run(node_status, nullptr, autofill::FormData(), - autofill::FormFieldData()); - return; - } - - ContentAutofillDriver* driver = - ContentAutofillDriver::GetForRenderFrameHost(element.render_frame_host()); - if (driver == nullptr) { - DVLOG(1) << __func__ << " Failed to get the autofill driver."; - std::move(callback).Run(UnexpectedErrorStatus(__FILE__, __LINE__), nullptr, - autofill::FormData(), autofill::FormFieldData()); - return; - } - - driver->GetAutofillAgent()->GetElementFormAndFieldDataForDevToolsNodeId( - backend_node_id, base::BindOnce(&WebController::OnGetFormAndFieldData, - weak_ptr_factory_.GetWeakPtr(), - std::move(callback), driver)); -} - void WebController::OnGetFormAndFieldData( base::OnceCallback<void(const ClientStatus&, ContentAutofillDriver* driver, @@ -1635,6 +1625,39 @@ void WebController::ExecuteJS( WebControllerErrorInfoProto::EXECUTE_JS, std::move(callback)); } +void WebController::SetNativeValue( + const std::string& value, + const ElementFinderResult& element, + base::OnceCallback<void(const ClientStatus&)> callback) { + if (!element.backend_node_id()) { + DVLOG(1) << __func__ + << "No backend node id on element intended for native execution."; + std::move(callback).Run(UnexpectedErrorStatus(__FILE__, __LINE__)); + return; + } + + auto* render_frame_host = element.render_frame_host(); + DCHECK(render_frame_host); + auto* driver = ContentAutofillAssistantDriver::GetOrCreateForRenderFrameHost( + render_frame_host, annotate_dom_model_service_); + if (!driver) { + std::move(callback).Run(UnexpectedErrorStatus(__FILE__, __LINE__)); + return; + } + driver->GetAutofillAssistantAgent()->SetElementValue( + *element.backend_node_id(), base::UTF8ToUTF16(value), + /* send_events= */ true, + base::BindOnce(&WebController::OnSetElementValue, + weak_ptr_factory_.GetWeakPtr(), std::move(callback))); +} + +void WebController::OnSetElementValue( + base::OnceCallback<void(const ClientStatus&)> callback, + bool success) const { + std::move(callback).Run(success ? OkClientStatus() + : UnexpectedErrorStatus(__FILE__, __LINE__)); +} + base::WeakPtr<WebController> WebController::GetWeakPtr() const { return weak_ptr_factory_.GetWeakPtr(); } diff --git a/chromium/components/autofill_assistant/browser/web/web_controller.h b/chromium/components/autofill_assistant/browser/web/web_controller.h index 88c75f4597a..7914f7e5add 100644 --- a/chromium/components/autofill_assistant/browser/web/web_controller.h +++ b/chromium/components/autofill_assistant/browser/web/web_controller.h @@ -52,6 +52,8 @@ class WebContents; } // namespace content namespace autofill_assistant { +class ElementFinderResult; +enum class ElementFinderResultType; // Controller to interact with the web pages. // @@ -106,7 +108,7 @@ class WebController { // |start_element|. Returns results or errors based on the |result_type|. virtual void RunElementFinder(const ElementFinderResult& start_element, const Selector& selector, - ElementFinder::ResultType result_type, + ElementFinderResultType result_type, ElementFinder::Callback callback); // Find all elements matching |selector|. If there are no matches, the status @@ -116,9 +118,7 @@ class WebController { virtual ClientStatus ObserveSelectors( const std::vector<SelectorObserver::ObservableSelector>& selectors, - base::TimeDelta timeout_ms, - base::TimeDelta periodic_check_interval, - base::TimeDelta extra_timeout, + const SelectorObserver::Settings& settings, SelectorObserver::Callback callback); // Scroll the |element| into view. |animation| defines the transition @@ -385,11 +385,17 @@ class WebController { const ElementFinderResult& element, base::OnceCallback<void(const ClientStatus&)> callback); + virtual void SetNativeValue( + const std::string& value, + const ElementFinderResult& element, + base::OnceCallback<void(const ClientStatus&)> callback); + virtual base::WeakPtr<WebController> GetWeakPtr() const; private: - friend class WebControllerBrowserTest; friend class BatchElementCheckerBrowserTest; + friend class SemanticElementFinderBrowserTest; + friend class WebControllerBrowserTest; void OnJavaScriptResult( base::OnceCallback<void(const ClientStatus&)> callback, @@ -463,14 +469,6 @@ class WebController { base::OnceCallback<void(const ClientStatus&, int)> callback, const DevtoolsClient::ReplyStatus& reply_status, std::unique_ptr<dom::DescribeNodeResult> result); - void OnGetBackendNodeIdForFormAndFieldData( - const ElementFinderResult& element, - base::OnceCallback<void(const ClientStatus&, - autofill::ContentAutofillDriver* driver, - const autofill::FormData&, - const autofill::FormFieldData&)> callback, - const ClientStatus& node_status, - int backend_node_id); void OnGetFormAndFieldData( base::OnceCallback<void(const ClientStatus&, autofill::ContentAutofillDriver* driver, @@ -532,6 +530,8 @@ class WebController { void OnDispatchJsEvent(base::OnceCallback<void(const ClientStatus&)> callback, const DevtoolsClient::ReplyStatus& reply_status, std::unique_ptr<runtime::EvaluateResult> result) const; + void OnSetElementValue(base::OnceCallback<void(const ClientStatus&)> callback, + bool success) const; // Weak pointer is fine here since it must outlive this web controller, which // is guaranteed by the owner of this object. diff --git a/chromium/components/autofill_assistant/browser/web/web_controller_browsertest.cc b/chromium/components/autofill_assistant/browser/web/web_controller_browsertest.cc index 0bce668ea90..aed1001cd56 100644 --- a/chromium/components/autofill_assistant/browser/web/web_controller_browsertest.cc +++ b/chromium/components/autofill_assistant/browser/web/web_controller_browsertest.cc @@ -13,7 +13,6 @@ #include "base/bind.h" #include "base/callback.h" -#include "base/callback_forward.h" #include "base/callback_helpers.h" #include "base/containers/checked_range.h" #include "base/location.h" @@ -57,11 +56,10 @@ #include "components/autofill_assistant/browser/user_model.h" #include "components/autofill_assistant/browser/web/element.h" #include "components/autofill_assistant/browser/web/element_action_util.h" -#include "components/autofill_assistant/browser/web/element_finder.h" +#include "components/autofill_assistant/browser/web/element_finder_result.h" +#include "components/autofill_assistant/browser/web/element_finder_result_type.h" #include "components/autofill_assistant/browser/web/element_store.h" -#include "components/autofill_assistant/content/common/autofill_assistant_agent.mojom.h" -#include "components/autofill_assistant/content/common/autofill_assistant_types.mojom.h" -#include "components/autofill_assistant/content/common/node_data.h" +#include "components/autofill_assistant/browser/web/mock_autofill_assistant_agent.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_observer.h" @@ -69,12 +67,8 @@ #include "content/public/test/browser_test_utils.h" #include "content/public/test/content_browser_test_utils.h" #include "content/shell/browser/shell.h" -#include "mojo/public/cpp/bindings/associated_receiver_set.h" -#include "net/test/embedded_test_server/embedded_test_server.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" -#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" -#include "third_party/blink/public/common/switches.h" #include "url/gurl.h" #include "url/url_constants.h" @@ -86,34 +80,8 @@ using ::testing::_; using ::testing::AnyOf; using ::testing::IsEmpty; using ::testing::Return; -using ::testing::SaveArg; using ::testing::WithArgs; -class MockAutofillAssistantAgent : public mojom::AutofillAssistantAgent { - public: - MockAutofillAssistantAgent() = default; - ~MockAutofillAssistantAgent() override = default; - - void BindPendingReceiver(mojo::ScopedInterfaceEndpointHandle handle) { - receivers_.Add( - this, mojo::PendingAssociatedReceiver<mojom::AutofillAssistantAgent>( - std::move(handle))); - } - - MOCK_METHOD(void, - GetSemanticNodes, - (int32_t role, - int32_t objective, - bool ignore_objective, - base::TimeDelta model_timeout, - base::OnceCallback<void(mojom::NodeDataStatus, - const std::vector<NodeData>&)> callback), - (override)); - - private: - mojo::AssociatedReceiverSet<mojom::AutofillAssistantAgent> receivers_; -}; - } // namespace class WebControllerBrowserTest : public autofill_assistant::BaseBrowserTest, @@ -129,22 +97,13 @@ class WebControllerBrowserTest : public autofill_assistant::BaseBrowserTest, void SetUpOnMainThread() override { BaseBrowserTest::SetUpOnMainThread(); - // Register the same agent on all frames, such that the callback can be - // mocked. - shell()->web_contents()->GetMainFrame()->ForEachRenderFrameHost( - base::BindLambdaForTesting([this](content::RenderFrameHost* host) { - host->GetRemoteAssociatedInterfaces()->OverrideBinderForTesting( - mojom::AutofillAssistantAgent::Name_, - base::BindRepeating( - &MockAutofillAssistantAgent::BindPendingReceiver, - base::Unretained(&autofill_assistant_agent_))); - })); + MockAutofillAssistantAgent::RegisterForAllFrames( + shell()->web_contents(), &autofill_assistant_agent_); - annotate_dom_model_service_ = std::make_unique<AnnotateDomModelService>( - /* opt_guide= */ nullptr, /* background_task_runner= */ nullptr); web_controller_ = WebController::CreateForWebContents( shell()->web_contents(), &user_data_, &log_info_, - annotate_dom_model_service_.get(), /*enable_full_stack_traces= */ true); + /* annotate_dom_model_service= */ nullptr, + /*enable_full_stack_traces= */ true); Observe(shell()->web_contents()); } @@ -601,11 +560,11 @@ class WebControllerBrowserTest : public autofill_assistant::BaseBrowserTest, void CheckFindElementResult(const ElementFinderResult& result, bool is_main_frame) { if (is_main_frame) { - EXPECT_EQ(shell()->web_contents()->GetMainFrame(), + EXPECT_EQ(shell()->web_contents()->GetPrimaryMainFrame(), result.render_frame_host()); EXPECT_EQ(result.frame_stack().size(), 0u); } else { - EXPECT_NE(shell()->web_contents()->GetMainFrame(), + EXPECT_NE(shell()->web_contents()->GetPrimaryMainFrame(), result.render_frame_host()); EXPECT_GE(result.frame_stack().size(), 1u); } @@ -983,38 +942,21 @@ document.getElementById("overlay_in_frame").style.visibility='hidden'; return ClientStatus(captured_processed_actions[0].status()); } - int GetBackendNodeId(Selector selector, ClientStatus* status_out) { - std::unique_ptr<ElementFinderResult> element_result; - int backend_node_id = -1; - - base::RunLoop run_loop_1; - web_controller_->FindElement( - selector, true, - base::BindLambdaForTesting( - [&](const ClientStatus& status, - std::unique_ptr<ElementFinderResult> result) { - element_result = std::move(result); - *status_out = status; - run_loop_1.Quit(); - })); - run_loop_1.Run(); - if (!status_out->ok()) { - return backend_node_id; - } + ClientStatus GetBackendNodeId(const ElementFinderResult& element, + int* backend_node_id) { + ClientStatus result_status; - // Second part in sequence, lookup backend node id. - base::RunLoop run_loop_2; + base::RunLoop run_loop; web_controller_->GetBackendNodeId( - *element_result, + element, base::BindLambdaForTesting([&](const ClientStatus& status, int id) { - *status_out = status; - backend_node_id = id; - run_loop_2.Quit(); + result_status = status; + *backend_node_id = id; + run_loop.Quit(); })); - run_loop_2.Run(); + run_loop.Run(); - log_info_.Clear(); - return backend_node_id; + return result_status; } protected: @@ -1023,7 +965,6 @@ document.getElementById("overlay_in_frame").style.visibility='hidden'; UserModel user_model_; ProcessedActionStatusDetailsProto log_info_; MockAutofillAssistantAgent autofill_assistant_agent_; - std::unique_ptr<AnnotateDomModelService> annotate_dom_model_service_; }; IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ElementExistenceCheck) { @@ -2761,7 +2702,7 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, // This makes the devtools action fail. ElementFinderResult element; element.SetNodeFrameId("doesnotexist"); - element.SetRenderFrameHost(web_contents()->GetMainFrame()); + element.SetRenderFrameHost(web_contents()->GetPrimaryMainFrame()); EXPECT_EQ(ELEMENT_POSITION_NOT_FOUND, WaitUntilElementIsStable(element, 10, base::Milliseconds(100)) @@ -3151,46 +3092,6 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, EXPECT_EQ(status.proto_status(), ACTION_APPLIED); } -IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, WaitForDomForSemanticElement) { - // This element is unique. - SelectorProto baseline_selector = ToSelectorProto("#select"); - - ClientStatus element_status; - int backend_node_id = - GetBackendNodeId(Selector(baseline_selector), &element_status); - EXPECT_TRUE(element_status.ok()); - - NodeData node_data; - node_data.backend_node_id = backend_node_id; - EXPECT_CALL(autofill_assistant_agent_, - GetSemanticNodes(1, 2, false, base::Milliseconds(5000), _)) - .WillOnce(RunOnceCallback<4>(mojom::NodeDataStatus::kSuccess, - std::vector<NodeData>{node_data})) - // Capture any other frames. - .WillRepeatedly(RunOnceCallback<4>( - mojom::NodeDataStatus::kUnexpectedError, std::vector<NodeData>())); - - ActionProto action_proto; - auto* wait_for_dom = action_proto.mutable_wait_for_dom(); - auto* condition = wait_for_dom->mutable_wait_condition(); - condition->mutable_client_id()->set_identifier("e"); - condition->set_require_unique_element(true); - auto* semantic_information = - condition->mutable_match()->mutable_semantic_information(); - semantic_information->set_semantic_role(1); - semantic_information->set_objective(2); - - base::MockCallback<base::OnceCallback<void(ScriptExecutor*)>> - run_expectations; - EXPECT_CALL(run_expectations, Run(_)) - .WillOnce([](ScriptExecutor* script_executor) { - EXPECT_TRUE(script_executor->GetElementStore()->HasElement("e")); - }); - ClientStatus status = RunWaitForDom(action_proto, /* use_observers= */ false, - run_expectations.Get()); - EXPECT_EQ(status.proto_status(), ACTION_APPLIED); -} - IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, FindElementError) { ClientStatus element_status; ElementFinderResult element; @@ -3224,7 +3125,7 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, base::RunLoop button_run_loop; web_controller_->RunElementFinder( frame_element, Selector({"#shadowsection", "#shadowbutton"}), - ElementFinder::ResultType::kExactlyOneMatch, + ElementFinderResultType::kExactlyOneMatch, base::BindOnce(&WebControllerBrowserTest::OnFindElement, base::Unretained(this), button_run_loop.QuitClosure(), &button_status, &button_element)); @@ -3262,7 +3163,7 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, RunElementFinderFromOOPIF) { base::RunLoop button_run_loop; web_controller_->RunElementFinder( fake_frame_element, Selector({"#button"}), - ElementFinder::ResultType::kExactlyOneMatch, + ElementFinderResultType::kExactlyOneMatch, base::BindOnce(&WebControllerBrowserTest::OnFindElement, base::Unretained(this), button_run_loop.QuitClosure(), &button_status, &button_element)); @@ -3447,186 +3348,100 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ExecuteJSWithException) { testing::ElementsAre(18, 12, 10)); } -IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, - ElementExistenceCheckWithSemanticModel) { - ClientStatus status; - int backend_node_id = GetBackendNodeId(Selector({"#button"}), &status); - EXPECT_TRUE(status.ok()); - - NodeData node_data; - node_data.backend_node_id = backend_node_id; - EXPECT_CALL(autofill_assistant_agent_, - GetSemanticNodes(1, 2, false, base::Milliseconds(5000), _)) - .WillOnce(RunOnceCallback<4>(mojom::NodeDataStatus::kSuccess, - std::vector<NodeData>{node_data})) - // Capture any other frames. - .WillRepeatedly(RunOnceCallback<4>( - mojom::NodeDataStatus::kUnexpectedError, std::vector<NodeData>())); - - // We pretend that the button is the correct element. +IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ParentFilter) { SelectorProto proto; - auto* semantic_information = proto.mutable_semantic_information(); - semantic_information->set_semantic_role(1); - semantic_information->set_objective(2); - RunStrictElementCheck(Selector(proto), true); - - ASSERT_EQ(log_info_.element_finder_info().size(), 1); - const auto& result = - log_info_.element_finder_info(0).semantic_inference_result(); - ASSERT_EQ(1, result.predicted_elements().size()); - EXPECT_EQ(backend_node_id, result.predicted_elements(0).backend_node_id()); - EXPECT_THAT( - 1, result.predicted_elements(0).semantic_information().semantic_role()); - EXPECT_THAT(2, - result.predicted_elements(0).semantic_information().objective()); -} + proto.add_filters()->set_css_selector("#select option:checked"); + proto.add_filters()->mutable_parent(); -IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, - ElementExistenceCheckWithSemanticModelOOPIF) { - ClientStatus status; - int backend_node_id = - GetBackendNodeId(Selector({"#iframeExternal", "#button"}), &status); - EXPECT_TRUE(status.ok()); + std::string element_tag; + EXPECT_EQ(ACTION_APPLIED, + GetElementTag(Selector(proto), &element_tag).proto_status()); + EXPECT_EQ("SELECT", element_tag); - NodeData node_data; - node_data.backend_node_id = backend_node_id; - EXPECT_CALL(autofill_assistant_agent_, - GetSemanticNodes(1, 2, false, base::Milliseconds(5000), _)) - .WillOnce(RunOnceCallback<4>(mojom::NodeDataStatus::kSuccess, - std::vector<NodeData>{node_data})) - // Capture any other frames. - .WillRepeatedly(RunOnceCallback<4>( - mojom::NodeDataStatus::kUnexpectedError, std::vector<NodeData>())); - - // We pretend that the button is the correct element. - SelectorProto proto; - auto* semantic_information = proto.mutable_semantic_information(); - semantic_information->set_semantic_role(1); - semantic_information->set_objective(2); - RunStrictElementCheck(Selector(proto), true); + SelectorProto failing_proto; + failing_proto.add_filters()->set_css_selector("body"); + failing_proto.add_filters()->mutable_parent(); // document + failing_proto.add_filters()->mutable_parent(); // Nothing - ASSERT_EQ(log_info_.element_finder_info().size(), 1); - const auto& result = - log_info_.element_finder_info(0).semantic_inference_result(); - ASSERT_EQ(1, result.predicted_elements().size()); - EXPECT_EQ(backend_node_id, result.predicted_elements(0).backend_node_id()); - EXPECT_THAT( - 1, result.predicted_elements(0).semantic_information().semantic_role()); - EXPECT_THAT(2, - result.predicted_elements(0).semantic_information().objective()); + ClientStatus failing_status; + ElementFinderResult ignored_element; + FindElement(Selector(failing_proto), &failing_status, &ignored_element); + EXPECT_EQ(ELEMENT_RESOLUTION_FAILED, failing_status.proto_status()); } -IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, - ElementExistenceCheckWithSemanticModelNotFound) { - // All frames return an empty list as a result. - EXPECT_CALL(autofill_assistant_agent_, - GetSemanticNodes(1, 2, false, base::Milliseconds(5000), _)) - .WillRepeatedly(RunOnceCallback<4>(mojom::NodeDataStatus::kSuccess, - std::vector<NodeData>{})); +IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, WebpageZoom) { + double initial_width = + content::EvalJs( + shell(), + R"(document.querySelector("#select").getBoundingClientRect().width)") + .ExtractDouble(); + EXPECT_GT(initial_width, 0); - SelectorProto proto; - auto* semantic_information = proto.mutable_semantic_information(); - semantic_information->set_semantic_role(1); - semantic_information->set_objective(2); - FindElementExpectEmptyResult(Selector(proto)); -} + ClientStatus body_status; + ElementFinderResult body; + FindElement(Selector({"body"}), &body_status, &body); + EXPECT_EQ(ACTION_APPLIED, body_status.proto_status()); -IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, - ElementExistenceCheckWithSemanticMultipleFound) { - SelectorProto proto; - auto* semantic_information = proto.mutable_semantic_information(); - semantic_information->set_semantic_role(1); - semantic_information->set_objective(2); - - NodeData node_data; - node_data.backend_node_id = 5; - NodeData node_data_other; - node_data_other.backend_node_id = 13; - EXPECT_CALL(autofill_assistant_agent_, - GetSemanticNodes(1, 2, false, base::Milliseconds(5000), _)) - .WillOnce(RunOnceCallback<4>(mojom::NodeDataStatus::kSuccess, - std::vector<NodeData>{node_data})) - .WillOnce(RunOnceCallback<4>(mojom::NodeDataStatus::kSuccess, - std::vector<NodeData>{node_data_other})) - // Capture any other frames. - .WillRepeatedly(RunOnceCallback<4>( - mojom::NodeDataStatus::kUnexpectedError, std::vector<NodeData>())); - - // Two elements are found in different frames. - ClientStatus status; - FindElement(Selector(proto), &status, nullptr); - EXPECT_EQ(TOO_MANY_ELEMENTS, status.proto_status()); -} + ClientStatus zoom_status; + base::RunLoop zoom_run_loop; + web_controller_->ExecuteJS( + "this.style.webkitTransform = 'scale(2)'", body, + base::BindOnce(&WebControllerBrowserTest::OnClientStatus, + base::Unretained(this), zoom_run_loop.QuitClosure(), + &zoom_status)); + zoom_run_loop.Run(); + EXPECT_EQ(ACTION_APPLIED, zoom_status.proto_status()); -IN_PROC_BROWSER_TEST_F( - WebControllerBrowserTest, - ElementExistenceCheckWithSemanticModelUsesIgnoreObjective) { - NodeData node_data; - node_data.backend_node_id = 5; - EXPECT_CALL(autofill_assistant_agent_, - GetSemanticNodes(1, 2, true, base::Milliseconds(5000), _)) - .WillOnce(RunOnceCallback<4>(mojom::NodeDataStatus::kSuccess, - std::vector<NodeData>{node_data})) - .WillRepeatedly(RunOnceCallback<4>( - mojom::NodeDataStatus::kUnexpectedError, std::vector<NodeData>())); + double after_zoom_width = + content::EvalJs( + shell(), + R"(document.querySelector("#select").getBoundingClientRect().width)") + .ExtractDouble(); + EXPECT_NEAR(after_zoom_width, initial_width * 2, 1); - SelectorProto proto; - auto* semantic_information = proto.mutable_semantic_information(); - semantic_information->set_semantic_role(1); - semantic_information->set_objective(2); - // All we want is this to be propagated to the GetSemanticNodes call as - // configured in the previous expectation. - semantic_information->set_ignore_objective(true); - - ClientStatus ignore_status; - FindElement(Selector(proto), &ignore_status, nullptr); - - // TODO(b/217160707): For now we expect the originally passed in semantic info - // to be logged instead of the objective inferred by the model. - ASSERT_EQ(log_info_.element_finder_info().size(), 1); - const auto& result = - log_info_.element_finder_info(0).semantic_inference_result(); - ASSERT_EQ(1, result.predicted_elements().size()); - EXPECT_EQ(5, result.predicted_elements(0).backend_node_id()); - EXPECT_THAT( - 1, result.predicted_elements(0).semantic_information().semantic_role()); - EXPECT_THAT(2, - result.predicted_elements(0).semantic_information().objective()); + ClientStatus reset_status; + base::RunLoop reset_run_loop; + web_controller_->ExecuteJS( + "this.style.webkitTransform = 'scale(1)'", body, + base::BindOnce(&WebControllerBrowserTest::OnClientStatus, + base::Unretained(this), reset_run_loop.QuitClosure(), + &reset_status)); + reset_run_loop.Run(); + EXPECT_EQ(ACTION_APPLIED, reset_status.proto_status()); + + double after_reset_width = + content::EvalJs( + shell(), + R"(document.querySelector("#select").getBoundingClientRect().width)") + .ExtractDouble(); + EXPECT_NEAR(after_reset_width, initial_width, 1); } -IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SemanticAndCssComparison) { - ClientStatus status; - int backend_node_id = GetBackendNodeId(Selector({"#button"}), &status); - EXPECT_TRUE(status.ok()); +IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SetFieldValueThroughNative) { + ClientStatus element_status; + ElementFinderResult input; + FindElement(Selector({"#input1"}), &element_status, &input); + ASSERT_EQ(ACTION_APPLIED, element_status.proto_status()); - NodeData node_data; - node_data.backend_node_id = backend_node_id; + int backend_node_id; + ASSERT_EQ(ACTION_APPLIED, + GetBackendNodeId(input, &backend_node_id).proto_status()); + std::u16string expected_value = u"native"; EXPECT_CALL(autofill_assistant_agent_, - GetSemanticNodes(1, 2, false, base::Milliseconds(5000), _)) - .WillOnce(RunOnceCallback<4>(mojom::NodeDataStatus::kSuccess, - std::vector<NodeData>{node_data})) - // Capture any other frames. - .WillRepeatedly(RunOnceCallback<4>( - mojom::NodeDataStatus::kUnexpectedError, std::vector<NodeData>())); - - // We pretend that the button is the correct element. - SelectorProto proto = ToSelectorProto("#button"); - auto* semantic_information = proto.mutable_semantic_information(); - semantic_information->set_semantic_role(1); - semantic_information->set_objective(2); - semantic_information->set_check_matches_css_element(true); - RunStrictElementCheck(Selector(proto), true); + SetElementValue(backend_node_id, expected_value, + /* send_events= */ true, _)) + .WillOnce(RunOnceCallback<3>(true)); - ASSERT_EQ(log_info_.element_finder_info().size(), 1); - const auto& result = - log_info_.element_finder_info(0).semantic_inference_result(); - ASSERT_EQ(1, result.predicted_elements().size()); - EXPECT_EQ(backend_node_id, result.predicted_elements(0).backend_node_id()); - EXPECT_THAT( - 1, result.predicted_elements(0).semantic_information().semantic_role()); - EXPECT_THAT(2, - result.predicted_elements(0).semantic_information().objective()); - EXPECT_TRUE(result.predicted_elements(0).matches_css_element()); + ClientStatus fill_status; + base::RunLoop run_loop; + web_controller_->SetNativeValue( + "native", input, + base::BindOnce(&WebControllerBrowserTest::OnClientStatus, + base::Unretained(this), run_loop.QuitClosure(), + &fill_status)); + run_loop.Run(); + + EXPECT_EQ(ACTION_APPLIED, fill_status.proto_status()); } } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/web/web_controller_util.cc b/chromium/components/autofill_assistant/browser/web/web_controller_util.cc index fa7a2f41399..dfafce4d5db 100644 --- a/chromium/components/autofill_assistant/browser/web/web_controller_util.cc +++ b/chromium/components/autofill_assistant/browser/web/web_controller_util.cc @@ -6,6 +6,7 @@ #include "base/logging.h" #include "components/autofill_assistant/browser/devtools/devtools/domains/types_runtime.h" +#include "components/autofill_assistant/browser/js_flow_util.h" #include "components/autofill_assistant/browser/service.pb.h" // Necessary to avoid a type collision while building for Windows. @@ -16,27 +17,38 @@ namespace autofill_assistant { namespace { + template <typename S> void AddStackEntry(const S& s, - const int js_line_offset, + const std::string& devtools_source_url, + const JsLineOffsets& js_line_offsets, UnexpectedErrorInfoProto* info) { - const int line_number = s.GetLineNumber() - js_line_offset; - DCHECK(line_number >= 0) - << "Line number " << s.GetLineNumber() - << " pointing into the offset included in the stack."; + int line_number = s.GetLineNumber(); + if (js_line_offsets.contains(devtools_source_url)) { + const int line_offset = js_line_offsets.at(devtools_source_url); + line_number -= line_offset; + DCHECK(line_number >= 0) + << "Line number (" << s.GetLineNumber() + << ") pointing into the offset (" << line_offset + << ") for devtools source url (" << devtools_source_url + << ") included in the stack."; + } + + info->add_js_exception_locations( + js_flow_util::GetExceptionLocation(devtools_source_url)); info->add_js_exception_line_numbers(line_number); info->add_js_exception_column_numbers(s.GetColumnNumber()); } void AddStackEntries(const runtime::ExceptionDetails* exception, - const int js_line_offset, + const JsLineOffsets& js_line_offsets, const int num_stack_entries_to_drop, UnexpectedErrorInfoProto* info) { if (!exception->HasStackTrace()) { - AddStackEntry(*exception, js_line_offset, info); + AddStackEntry(*exception, exception->HasUrl() ? exception->GetUrl() : "", + js_line_offsets, info); return; } - const std::vector<std::unique_ptr<runtime::CallFrame>>& frames = *exception->GetStackTrace()->GetCallFrames(); const int num_stack_entries = static_cast<int>(frames.size()); @@ -47,7 +59,8 @@ void AddStackEntries(const runtime::ExceptionDetails* exception, std::max(num_stack_entries - num_stack_entries_to_drop, 1); for (int i = 0; i < num_frames_to_use; i++) { - AddStackEntry(*frames[i], js_line_offset, info); + const auto& frame = *frames[i]; + AddStackEntry(frame, frame.GetUrl(), js_line_offsets, info); } } } // namespace @@ -78,7 +91,7 @@ ClientStatus JavaScriptErrorStatus( const std::string& file, const int line, const runtime::ExceptionDetails* exception, - const int js_line_offset, + const JsLineOffsets& js_line_offsets, const int num_stack_entries_to_drop) { ClientStatus status = UnexpectedDevtoolsErrorStatus(reply_status, file, line); status.set_proto_status(UNEXPECTED_JS_ERROR); @@ -90,7 +103,7 @@ ClientStatus JavaScriptErrorStatus( if (exception->HasException() && exception->GetException()->HasClassName()) { info->set_js_exception_classname(exception->GetException()->GetClassName()); } - AddStackEntries(exception, js_line_offset, num_stack_entries_to_drop, info); + AddStackEntries(exception, js_line_offsets, num_stack_entries_to_drop, info); return status; } diff --git a/chromium/components/autofill_assistant/browser/web/web_controller_util.h b/chromium/components/autofill_assistant/browser/web/web_controller_util.h index 5e543af2bb6..7d13446bd97 100644 --- a/chromium/components/autofill_assistant/browser/web/web_controller_util.h +++ b/chromium/components/autofill_assistant/browser/web/web_controller_util.h @@ -30,14 +30,18 @@ ClientStatus UnexpectedDevtoolsErrorStatus( const std::string& file, int line); +// Map from devtools source url to js line offset. See js_flow_util for details +// on devtools source urls. +using JsLineOffsets = base::flat_map<std::string, int>; + // Builds a ClientStatus appropriate for a JavaScript error. ClientStatus JavaScriptErrorStatus( const DevtoolsClient::ReplyStatus& reply_status, const std::string& file, int line, const runtime::ExceptionDetails* exception, - int js_line_offset = 0, - int num_stack_entries_to_drop = 0); + const JsLineOffsets& js_line_offsets = {}, + const int num_stack_entries_to_drop = 0); // Makes sure that the given EvaluateResult exists, is successful and contains a // result. @@ -47,18 +51,18 @@ ClientStatus CheckJavaScriptResult( T* result, const char* file, int line, - int js_line_offset = 0, + const JsLineOffsets& js_line_offsets = {}, int num_stack_entries_to_drop = 0) { if (!result) return JavaScriptErrorStatus(reply_status, file, line, nullptr, - js_line_offset, num_stack_entries_to_drop); + js_line_offsets, num_stack_entries_to_drop); if (result->HasExceptionDetails()) return JavaScriptErrorStatus(reply_status, file, line, - result->GetExceptionDetails(), js_line_offset, + result->GetExceptionDetails(), js_line_offsets, num_stack_entries_to_drop); if (!result->GetResult()) return JavaScriptErrorStatus(reply_status, file, line, nullptr, - js_line_offset, num_stack_entries_to_drop); + js_line_offsets, num_stack_entries_to_drop); return OkClientStatus(); } diff --git a/chromium/components/autofill_assistant/browser/website_login_manager_impl.cc b/chromium/components/autofill_assistant/browser/website_login_manager_impl.cc index f3f5b48ccab..c74f7c2fa5d 100644 --- a/chromium/components/autofill_assistant/browser/website_login_manager_impl.cc +++ b/chromium/components/autofill_assistant/browser/website_login_manager_impl.cc @@ -465,7 +465,8 @@ absl::optional<std::string> WebsiteLoginManagerImpl::GeneratePassword( // TODO(crbug.com/1043132): Add support for non-main frames. If another // frame has a different origin than the main frame, passwords-related // features may not work. - auto* driver = factory->GetDriverForFrame(web_contents_->GetMainFrame()); + auto* driver = + factory->GetDriverForFrame(web_contents_->GetPrimaryMainFrame()); if (!driver) { return absl::nullopt; } diff --git a/chromium/components/autofill_assistant/content/browser/BUILD.gn b/chromium/components/autofill_assistant/content/browser/BUILD.gn index 592b8ebfcfc..17df1b0bc2c 100644 --- a/chromium/components/autofill_assistant/content/browser/BUILD.gn +++ b/chromium/components/autofill_assistant/content/browser/BUILD.gn @@ -15,6 +15,7 @@ static_library("browser") { deps = [ "//base", "//components/autofill_assistant/content/common:mojo_interfaces", + "//components/autofill_assistant/content/common/proto:proto", "//components/keyed_service/core", "//components/optimization_guide/core", "//components/optimization_guide/proto:optimization_guide_proto", diff --git a/chromium/components/autofill_assistant/content/browser/annotate_dom_model_service.cc b/chromium/components/autofill_assistant/content/browser/annotate_dom_model_service.cc index cc501a400ab..37efddba4f7 100644 --- a/chromium/components/autofill_assistant/content/browser/annotate_dom_model_service.cc +++ b/chromium/components/autofill_assistant/content/browser/annotate_dom_model_service.cc @@ -59,12 +59,11 @@ void AnnotateDomModelService::Shutdown() { // This and the optimization guide are keyed services, currently optimization // guide is a BrowserContextKeyedService, it will be cleaned first so removing // the observer should not be performed. - if (annotate_dom_model_file_) { + if (model_file_) { // If the model file is already loaded, it should be closed on a // background thread. background_task_runner_->PostTask( - FROM_HERE, - base::BindOnce(&CloseModelFile, std::move(*annotate_dom_model_file_))); + FROM_HERE, base::BindOnce(&CloseModelFile, std::move(*model_file_))); } for (auto& pending_request : pending_model_requests_) { // Clear any pending requests, no model file is acceptable as |Shutdown| is @@ -85,23 +84,24 @@ void AnnotateDomModelService::OnModelUpdated( background_task_runner_->PostTaskAndReplyWithResult( FROM_HERE, base::BindOnce(&LoadModelFile, model_info.GetModelFilePath()), base::BindOnce(&AnnotateDomModelService::OnModelFileLoaded, - weak_ptr_factory_.GetWeakPtr())); + weak_ptr_factory_.GetWeakPtr(), model_info.GetVersion())); } -void AnnotateDomModelService::OnModelFileLoaded(base::File model_file) { +void AnnotateDomModelService::OnModelFileLoaded(int64_t model_version, + base::File model_file) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (!model_file.IsValid()) { return; } - if (annotate_dom_model_file_) { + if (model_file_) { // If the model file is already loaded, it should be closed on a background // thread. background_task_runner_->PostTask( - FROM_HERE, - base::BindOnce(&CloseModelFile, std::move(*annotate_dom_model_file_))); + FROM_HERE, base::BindOnce(&CloseModelFile, std::move(*model_file_))); } - annotate_dom_model_file_ = std::move(model_file); + model_file_ = std::move(model_file); + model_version_ = model_version; for (auto& pending_request : pending_model_requests_) { if (!pending_request) { continue; @@ -111,18 +111,31 @@ void AnnotateDomModelService::OnModelFileLoaded(base::File model_file) { pending_model_requests_.clear(); } -absl::optional<base::File> AnnotateDomModelService::GetModelFile() { - if (!annotate_dom_model_file_) { +absl::optional<base::File> AnnotateDomModelService::GetModelFile() const { + if (!model_file_) { return absl::nullopt; } // The model must be valid at this point. - DCHECK(annotate_dom_model_file_->IsValid()); - return annotate_dom_model_file_->Duplicate(); + DCHECK(model_file_->IsValid()); + return model_file_->Duplicate(); +} + +absl::optional<int64_t> AnnotateDomModelService::GetModelVersion() const { + return model_version_; +} + +std::string AnnotateDomModelService::GetOverridesPolicy() const { + return overrides_policy_binary_proto_; +} + +bool AnnotateDomModelService::SetOverridesPolicy( + SemanticSelectorPolicy policy) { + return policy.SerializeToString(&overrides_policy_binary_proto_); } void AnnotateDomModelService::NotifyOnModelFileAvailable( NotifyModelAvailableCallback callback) { - DCHECK(!annotate_dom_model_file_); + DCHECK(!model_file_); if (pending_model_requests_.size() < kMaxPendingRequestsAllowed) { pending_model_requests_.emplace_back(std::move(callback)); return; @@ -131,7 +144,7 @@ void AnnotateDomModelService::NotifyOnModelFileAvailable( } void AnnotateDomModelService::SetModelFileForTest(base::File model_file) { - annotate_dom_model_file_ = std::move(model_file); + model_file_ = std::move(model_file); for (auto& pending_request : pending_model_requests_) { std::move(pending_request).Run(true); } diff --git a/chromium/components/autofill_assistant/content/browser/annotate_dom_model_service.h b/chromium/components/autofill_assistant/content/browser/annotate_dom_model_service.h index ba4d0b0a902..85908084152 100644 --- a/chromium/components/autofill_assistant/content/browser/annotate_dom_model_service.h +++ b/chromium/components/autofill_assistant/content/browser/annotate_dom_model_service.h @@ -13,6 +13,7 @@ #include "base/files/file_path.h" #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" +#include "components/autofill_assistant/content/common/proto/semantic_feature_overrides.pb.h" #include "components/keyed_service/core/keyed_service.h" #include "components/optimization_guide/core/optimization_target_model_observer.h" #include "third_party/abseil-cpp/absl/types/optional.h" @@ -45,10 +46,20 @@ class AnnotateDomModelService optimization_guide::proto::OptimizationTarget optimization_target, const optimization_guide::ModelInfo& model_info) override; - // Returns the annotate dom model file, should only be called when the model - // file is already available. See the |NotifyOnModelFileAvailable| for an - // asynchronous notification of the model being available. - absl::optional<base::File> GetModelFile(); + // Returns the annotate dom model file. If this returns a nullopt, see the + // |NotifyOnModelFileAvailable| for an asynchronous notification of the model + // being available. + absl::optional<base::File> GetModelFile() const; + + // Returns the model of the version. If this returns a nullopt, see the + // |NotifyOnModelFileAvailable| for an asynchronous notification of the model + // being available. + absl::optional<int64_t> GetModelVersion() const; + + // Returns the overrides policy as a serialized binary proto representation + // that will be passed to renderer processes. + std::string GetOverridesPolicy() const; + virtual bool SetOverridesPolicy(SemanticSelectorPolicy policy); // If the model file is not available, requestors can ask to be notified, via // |callback|. This enables a two-step approach to relabily get the model file @@ -61,7 +72,7 @@ class AnnotateDomModelService void SetModelFileForTest(base::File model_file); private: - void OnModelFileLoaded(base::File model_file); + void OnModelFileLoaded(int64_t model_version, base::File model_file); // Optimization Guide Service that provides model files for this service. raw_ptr<optimization_guide::OptimizationGuideModelProvider> opt_guide_ = @@ -70,7 +81,12 @@ class AnnotateDomModelService // The file that contains the annotate DOM model. Available when the // file path has been provided by the Optimization Guide and has been // successfully loaded. - absl::optional<base::File> annotate_dom_model_file_; + absl::optional<base::File> model_file_; + // The version of the current model. + absl::optional<int64_t> model_version_; + + // A serialized binary representation of a SemanticSelectorPolicy proto. + std::string overrides_policy_binary_proto_; // The set of callbacks associated with requests for the language detection // model. diff --git a/chromium/components/autofill_assistant/content/browser/content_autofill_assistant_driver.cc b/chromium/components/autofill_assistant/content/browser/content_autofill_assistant_driver.cc index 79e42a96194..99bd750b1e2 100644 --- a/chromium/components/autofill_assistant/content/browser/content_autofill_assistant_driver.cc +++ b/chromium/components/autofill_assistant/content/browser/content_autofill_assistant_driver.cc @@ -7,6 +7,7 @@ #include "base/files/file.h" #include "base/guid.h" #include "base/location.h" +#include "components/autofill_assistant/content/common/proto/semantic_feature_overrides.pb.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" @@ -49,7 +50,7 @@ ContentAutofillAssistantDriver::GetOrCreateForRenderFrameHost( ContentAutofillAssistantDriver* driver = ContentAutofillAssistantDriver::GetOrCreateForCurrentDocument( render_frame_host); - if (driver) { + if (driver && annotate_dom_model_service) { driver->SetAnnotateDomModelService(annotate_dom_model_service); } return driver; @@ -77,13 +78,15 @@ void ContentAutofillAssistantDriver::GetAnnotateDomModel( GetAnnotateDomModelCallback callback) { if (!annotate_dom_model_service_) { NOTREACHED() << "No model service"; - std::move(callback).Run(mojom::ModelStatus::kUnexpectedError, base::File()); + std::move(callback).Run(mojom::ModelStatus::kUnexpectedError, base::File(), + GetOverridesPolicy()); return; } absl::optional<base::File> file = annotate_dom_model_service_->GetModelFile(); if (file) { - std::move(callback).Run(mojom::ModelStatus::kSuccess, *std::move(file)); + std::move(callback).Run(mojom::ModelStatus::kSuccess, *std::move(file), + GetOverridesPolicy()); return; } @@ -129,7 +132,8 @@ void ContentAutofillAssistantDriver::RunCallback( } DCHECK(it->second->callback_); - std::move(it->second->callback_).Run(model_status, std::move(model_file)); + std::move(it->second->callback_) + .Run(model_status, std::move(model_file), GetOverridesPolicy()); pending_calls_.erase(it); } @@ -139,4 +143,9 @@ void ContentAutofillAssistantDriver::SetAnnotateDomModelService( annotate_dom_model_service_ = annotate_dom_model_service; } +std::string ContentAutofillAssistantDriver::GetOverridesPolicy() const { + DCHECK(annotate_dom_model_service_); + return annotate_dom_model_service_->GetOverridesPolicy(); +} + } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/content/browser/content_autofill_assistant_driver.h b/chromium/components/autofill_assistant/content/browser/content_autofill_assistant_driver.h index 69b81025489..c9bc30a327d 100644 --- a/chromium/components/autofill_assistant/content/browser/content_autofill_assistant_driver.h +++ b/chromium/components/autofill_assistant/content/browser/content_autofill_assistant_driver.h @@ -68,6 +68,7 @@ class ContentAutofillAssistantDriver void RunCallback(const std::string& guid, mojom::ModelStatus model_status, base::File model_file); + std::string GetOverridesPolicy() const; raw_ptr<AnnotateDomModelService> annotate_dom_model_service_ = nullptr; diff --git a/chromium/components/autofill_assistant/content/browser/content_autofill_assistant_driver_unittest.cc b/chromium/components/autofill_assistant/content/browser/content_autofill_assistant_driver_unittest.cc index d83ac8b134b..743e19041e8 100644 --- a/chromium/components/autofill_assistant/content/browser/content_autofill_assistant_driver_unittest.cc +++ b/chromium/components/autofill_assistant/content/browser/content_autofill_assistant_driver_unittest.cc @@ -48,8 +48,8 @@ class ContentAutofillAssistantDriverTest : public testing::Test { &browser_context_, nullptr); // Constructor of ContentAutofillAssistantDriver is private, cannot use // std::make_unique. - driver_ = base::WrapUnique( - new ContentAutofillAssistantDriver(web_contents_->GetMainFrame())); + driver_ = base::WrapUnique(new ContentAutofillAssistantDriver( + web_contents_->GetPrimaryMainFrame())); driver_->SetAnnotateDomModelService(annotate_dom_model_service_.get()); } @@ -67,29 +67,29 @@ class ContentAutofillAssistantDriverTest : public testing::Test { std::unique_ptr<ContentAutofillAssistantDriver> driver_; std::unique_ptr<AnnotateDomModelService> annotate_dom_model_service_; base::File model_file_; + + base::MockCallback<base::OnceCallback< + void(mojom::ModelStatus, base::File, const std::string&)>> + callback_; }; TEST_F(ContentAutofillAssistantDriverTest, GetLoadedModelFromService) { // Model has been loaded before. annotate_dom_model_service_->SetModelFileForTest(model_file_.Duplicate()); - base::MockCallback<base::OnceCallback<void(mojom::ModelStatus, base::File)>> - callback; - EXPECT_CALL(callback, Run(mojom::ModelStatus::kSuccess, _)); + EXPECT_CALL(callback_, Run(mojom::ModelStatus::kSuccess, _, _)); driver_->GetAnnotateDomModel(/* timeout= */ base::Milliseconds(1000), - callback.Get()); + callback_.Get()); EXPECT_FALSE(HasPendingCallbacks()); } TEST_F(ContentAutofillAssistantDriverTest, GetModelFromServiceAfterLoading) { - base::MockCallback<base::OnceCallback<void(mojom::ModelStatus, base::File)>> - callback; - EXPECT_CALL(callback, Run(mojom::ModelStatus::kSuccess, _)); + EXPECT_CALL(callback_, Run(mojom::ModelStatus::kSuccess, _, _)); driver_->GetAnnotateDomModel(/* timeout= */ base::Milliseconds(1000), - callback.Get()); + callback_.Get()); // Model loaded after being requested. annotate_dom_model_service_->SetModelFileForTest(model_file_.Duplicate()); @@ -98,12 +98,10 @@ TEST_F(ContentAutofillAssistantDriverTest, GetModelFromServiceAfterLoading) { } TEST_F(ContentAutofillAssistantDriverTest, GetModelTimesOut) { - base::MockCallback<base::OnceCallback<void(mojom::ModelStatus, base::File)>> - callback; - EXPECT_CALL(callback, Run(mojom::ModelStatus::kTimeout, _)); + EXPECT_CALL(callback_, Run(mojom::ModelStatus::kTimeout, _, _)); driver_->GetAnnotateDomModel(/* timeout= */ base::Milliseconds(1000), - callback.Get()); + callback_.Get()); // Model does not get loaded. task_environment_.FastForwardBy(base::Seconds(2)); @@ -112,16 +110,14 @@ TEST_F(ContentAutofillAssistantDriverTest, GetModelTimesOut) { } TEST_F(ContentAutofillAssistantDriverTest, MultipleParallelCalls) { - base::MockCallback<base::OnceCallback<void(mojom::ModelStatus, base::File)>> - callback; - EXPECT_CALL(callback, Run(mojom::ModelStatus::kTimeout, _)).Times(3); + EXPECT_CALL(callback_, Run(mojom::ModelStatus::kTimeout, _, _)).Times(3); driver_->GetAnnotateDomModel(/* timeout= */ base::Milliseconds(1000), - callback.Get()); + callback_.Get()); driver_->GetAnnotateDomModel(/* timeout= */ base::Milliseconds(1000), - callback.Get()); + callback_.Get()); driver_->GetAnnotateDomModel(/* timeout= */ base::Milliseconds(1000), - callback.Get()); + callback_.Get()); // Model does not get loaded. task_environment_.FastForwardBy(base::Seconds(2)); @@ -129,4 +125,16 @@ TEST_F(ContentAutofillAssistantDriverTest, MultipleParallelCalls) { EXPECT_FALSE(HasPendingCallbacks()); } +TEST_F(ContentAutofillAssistantDriverTest, EmptyOverrides) { + EXPECT_CALL(callback_, Run(mojom::ModelStatus::kSuccess, _, std::string())); + + driver_->GetAnnotateDomModel(/* timeout= */ base::Milliseconds(1000), + callback_.Get()); + + // Model loaded after being requested. + annotate_dom_model_service_->SetModelFileForTest(model_file_.Duplicate()); + + EXPECT_FALSE(HasPendingCallbacks()); +} + } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/content/common/BUILD.gn b/chromium/components/autofill_assistant/content/common/BUILD.gn index 4f4f1bbb842..63cf9526a79 100644 --- a/chromium/components/autofill_assistant/content/common/BUILD.gn +++ b/chromium/components/autofill_assistant/content/common/BUILD.gn @@ -8,6 +8,8 @@ static_library("common") { sources = [ "node_data.cc", "node_data.h", + "switches.cc", + "switches.h", ] } diff --git a/chromium/components/autofill_assistant/content/common/autofill_assistant_agent.mojom b/chromium/components/autofill_assistant/content/common/autofill_assistant_agent.mojom index ff86b3937c9..9c1c9160afc 100644 --- a/chromium/components/autofill_assistant/content/common/autofill_assistant_agent.mojom +++ b/chromium/components/autofill_assistant/content/common/autofill_assistant_agent.mojom @@ -5,6 +5,7 @@ module autofill_assistant.mojom; import "components/autofill_assistant/content/common/autofill_assistant_types.mojom"; +import "mojo/public/mojom/base/string16.mojom"; import "mojo/public/mojom/base/time.mojom"; // There is one instance of this interface per render frame in the renderer @@ -16,4 +17,12 @@ interface AutofillAssistantAgent { mojo_base.mojom.TimeDelta timeout) => (NodeDataStatus status, array<autofill_assistant.mojom.NodeData> nodes); + + // Set the value of a web element. The target needs to be a form control + // element, otherwise the call fails. This works for input, textarea and + // select. For a select element it finds the option with a value matching + // the given parameter (exactly) and makes that option the current selection. + SetElementValue(int32 backend_node_id, mojo_base.mojom.String16 value, + bool send_events) + => (bool success); }; diff --git a/chromium/components/autofill_assistant/content/common/autofill_assistant_driver.mojom b/chromium/components/autofill_assistant/content/common/autofill_assistant_driver.mojom index 703883b0cf3..b69d97aa4ab 100644 --- a/chromium/components/autofill_assistant/content/common/autofill_assistant_driver.mojom +++ b/chromium/components/autofill_assistant/content/common/autofill_assistant_driver.mojom @@ -5,6 +5,7 @@ module autofill_assistant.mojom; import "components/autofill_assistant/content/common/autofill_assistant_types.mojom"; +import "mojo/public/mojom/base/byte_string.mojom"; import "mojo/public/mojom/base/read_only_file.mojom"; import "mojo/public/mojom/base/time.mojom"; @@ -14,5 +15,6 @@ interface AutofillAssistantDriver { // Request that the annotate DOM model is being loaded and returned for use // by the AutofillAssistantAgent. GetAnnotateDomModel(mojo_base.mojom.TimeDelta timeout) - => (ModelStatus status, mojo_base.mojom.ReadOnlyFile? model_file); + => (ModelStatus status, mojo_base.mojom.ReadOnlyFile? model_file, + mojo_base.mojom.ByteString overrides_policy); }; diff --git a/chromium/components/autofill_assistant/content/common/switches.cc b/chromium/components/autofill_assistant/content/common/switches.cc new file mode 100644 index 00000000000..35256c49dcf --- /dev/null +++ b/chromium/components/autofill_assistant/content/common/switches.cc @@ -0,0 +1,13 @@ +// Copyright 2022 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_assistant/content/common/switches.h" + +namespace autofill_assistant::switches { + +// Enables annotating DOM debugging when set. +const char kAutofillAssistantDebugAnnotateDom[] = + "autofill-assistant-debug-annotate-dom"; + +} // namespace autofill_assistant::switches diff --git a/chromium/components/autofill_assistant/content/common/switches.h b/chromium/components/autofill_assistant/content/common/switches.h new file mode 100644 index 00000000000..80e8ce6d425 --- /dev/null +++ b/chromium/components/autofill_assistant/content/common/switches.h @@ -0,0 +1,15 @@ +// Copyright 2022 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_ASSISTANT_CONTENT_COMMON_SWITCHES_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_CONTENT_COMMON_SWITCHES_H_ + +namespace autofill_assistant::switches { + +// All switches in alphabetical order. +extern const char kAutofillAssistantDebugAnnotateDom[]; + +} // namespace autofill_assistant::switches + +#endif // COMPONENTS_AUTOFILL_ASSISTANT_CONTENT_COMMON_SWITCHES_H_ diff --git a/chromium/components/autofill_assistant/content/renderer/BUILD.gn b/chromium/components/autofill_assistant/content/renderer/BUILD.gn index 3138bb95fb8..b2173f11e4e 100644 --- a/chromium/components/autofill_assistant/content/renderer/BUILD.gn +++ b/chromium/components/autofill_assistant/content/renderer/BUILD.gn @@ -20,6 +20,7 @@ static_library("renderer") { "//base", "//components/autofill_assistant/content/common:common", "//components/autofill_assistant/content/common:mojo_interfaces", + "//components/autofill_assistant/content/common/proto:proto", "//components/optimization_guide:machine_learning_tflite_buildflags", "//content/public/common:common", "//content/public/renderer:renderer", @@ -73,6 +74,7 @@ source_set("browser_tests") { "//base", "//base/test:test_support", "//components/autofill_assistant/content/common:mojo_interfaces", + "//components/autofill_assistant/content/common/proto:proto", "//content/public/browser", "//content/public/renderer", "//content/test:test_support", diff --git a/chromium/components/autofill_assistant/content/renderer/DEPS b/chromium/components/autofill_assistant/content/renderer/DEPS index a28ced1664c..9218c077f71 100644 --- a/chromium/components/autofill_assistant/content/renderer/DEPS +++ b/chromium/components/autofill_assistant/content/renderer/DEPS @@ -10,6 +10,7 @@ include_rules = [ "+third_party/blink/public/common", "+third_party/blink/public/platform", "+third_party/blink/public/web", + "+third_party/protobuf/src/google/protobuf/repeated_field.h", "+third_party/tflite", "+third_party/tflite_support", ] diff --git a/chromium/components/autofill_assistant/content/renderer/autofill_assistant_agent.cc b/chromium/components/autofill_assistant/content/renderer/autofill_assistant_agent.cc index f7b2cb06fe5..cdf09d94e7d 100644 --- a/chromium/components/autofill_assistant/content/renderer/autofill_assistant_agent.cc +++ b/chromium/components/autofill_assistant/content/renderer/autofill_assistant_agent.cc @@ -4,18 +4,101 @@ #include "components/autofill_assistant/content/renderer/autofill_assistant_agent.h" +#include <ostream> + +#include "base/command_line.h" +#include "components/autofill_assistant/content/common/switches.h" #include "components/optimization_guide/machine_learning_tflite_buildflags.h" #include "content/public/renderer/render_frame.h" #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" +#include "third_party/blink/public/platform/web_string.h" #include "third_party/blink/public/platform/web_vector.h" #include "third_party/blink/public/web/modules/autofill_assistant/node_signals.h" +#include "third_party/blink/public/web/web_element.h" +#include "third_party/blink/public/web/web_form_control_element.h" #include "third_party/blink/public/web/web_local_frame.h" +#include "third_party/protobuf/src/google/protobuf/repeated_field.h" #if BUILDFLAG(BUILD_WITH_TFLITE_LIB) +#include "components/autofill_assistant/content/common/proto/semantic_feature_overrides.pb.h" #include "components/autofill_assistant/content/renderer/autofill_assistant_model_executor.h" #endif // BUILDFLAG(BUILD_WITH_TFLITE_LIB) namespace autofill_assistant { +namespace { + +#if BUILDFLAG(BUILD_WITH_TFLITE_LIB) + +using OverridesMap = AutofillAssistantModelExecutor::OverridesMap; +using SparseVector = AutofillAssistantModelExecutor::SparseVector; + +std::string NodeSignalsToDebugString( + const blink::AutofillAssistantNodeSignals& node_signals) { + std::ostringstream out; + + out << "AutofillAssistantNodeSignals {\n" + << "\tbackend_node_id: " << node_signals.backend_node_id + << "\n\tnode_features {"; + for (const auto& text : node_signals.node_features.text) { + out << "\n\t\ttext: " << text.Utf16(); + } + out << "\n\t\taria: " << node_signals.node_features.aria.Utf16() + << "\n\t\thtml_tag: " << node_signals.node_features.html_tag.Utf16() + << "\n\t\ttype: " << node_signals.node_features.type.Utf16() + << "\n\t\tinvisible_attributes: " + << node_signals.node_features.invisible_attributes.Utf16() + << "\n\t}\n\tlabel_features {"; + for (const auto& text : node_signals.label_features.text) { + out << "\n\t\ttext: " << text.Utf16(); + } + out << "\n\t}\n\tcontext_features {"; + for (const auto& header_text : node_signals.context_features.header_text) { + out << "\n\t\theader_text: " << header_text.Utf16(); + } + out << "\n\t\tform_type: " << node_signals.context_features.form_type.Utf16() + << "\n\t}\n}"; + + return out.str(); +} + +SparseVector KeyCoordinatesToSparseVector( + const ::google::protobuf::RepeatedPtrField<SparseEncoding>& + key_coordinates) { + SparseVector sparse_vector; + for (const auto& coordinate : key_coordinates) { + sparse_vector.emplace_back( + std::make_pair(std::make_pair(coordinate.feature_concatenation_index(), + coordinate.vocabulary_index()), + coordinate.number_of_occurrences())); + } + return sparse_vector; +} + +absl::optional<OverridesMap> ParseOverridesPolicyToMap( + std::string overrides_policy) { + SemanticSelectorPolicy policy; + if (!policy.ParseFromString( + std::string(overrides_policy.begin(), overrides_policy.end()))) { + return absl::nullopt; + } + if (policy.bag_of_words().data_point_map().empty()) { + return absl::nullopt; + } + OverridesMap overrides_map; + for (const auto& data_point : policy.bag_of_words().data_point_map()) { + if (data_point.key_coordinate().empty()) { + continue; + } + const auto& value = data_point.value(); + overrides_map[KeyCoordinatesToSparseVector(data_point.key_coordinate())] = + std::make_pair(value.semantic_role(), value.objective()); + } + return overrides_map; +} + +#endif // BUILDFLAG(BUILD_WITH_TFLITE_LIB) + +} // namespace AutofillAssistantAgent::AutofillAssistantAgent( content::RenderFrame* render_frame, @@ -52,6 +135,7 @@ void AutofillAssistantAgent::GetSemanticNodes( GetSemanticNodesCallback callback) { blink::WebLocalFrame* frame = render_frame()->GetWebFrame(); if (!frame) { + VLOG(1) << "Failed to get semantic nodes, no frame."; std::move(callback).Run(mojom::NodeDataStatus::kUnexpectedError, std::vector<NodeData>()); return; @@ -66,7 +150,8 @@ void AutofillAssistantAgent::GetSemanticNodes( void AutofillAssistantAgent::GetAnnotateDomModel( base::TimeDelta model_timeout, - base::OnceCallback<void(mojom::ModelStatus, base::File)> callback) { + base::OnceCallback<void(mojom::ModelStatus, base::File, const std::string&)> + callback) { GetDriver().GetAnnotateDomModel(model_timeout, std::move(callback)); } @@ -77,14 +162,16 @@ mojom::AutofillAssistantDriver& AutofillAssistantAgent::GetDriver() { return *driver_; } -void AutofillAssistantAgent::OnGetModelFile(base::Time start_time, - blink::WebLocalFrame* frame, - int32_t role, - int32_t objective, - bool ignore_objective, - GetSemanticNodesCallback callback, - mojom::ModelStatus model_status, - base::File model) { +void AutofillAssistantAgent::OnGetModelFile( + base::Time start_time, + blink::WebLocalFrame* frame, + int32_t role, + int32_t objective, + bool ignore_objective, + GetSemanticNodesCallback callback, + mojom::ModelStatus model_status, + base::File model, + const std::string& overrides_policy) { std::vector<NodeData> nodes; switch (model_status) { case mojom::ModelStatus::kSuccess: @@ -109,7 +196,8 @@ void AutofillAssistantAgent::OnGetModelFile(base::Time start_time, << (on_node_signals - on_get_model_file).InMilliseconds() << "ms"; #if BUILDFLAG(BUILD_WITH_TFLITE_LIB) - AutofillAssistantModelExecutor model_executor; + AutofillAssistantModelExecutor model_executor( + ParseOverridesPolicyToMap(std::move(overrides_policy))); if (!model_executor.InitializeModelFromFile(std::move(model))) { std::move(callback).Run(mojom::NodeDataStatus::kInitializationError, nodes); return; @@ -123,9 +211,16 @@ void AutofillAssistantAgent::OnGetModelFile(base::Time start_time, for (const auto& node_signal : node_signals) { auto result = model_executor.ExecuteModelWithInput(node_signal); - DVLOG(3) << "Annotated node with result: role: " << result->first - << " and objective: " << result->second - << " (or ignore: " << ignore_objective << ")"; + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kAutofillAssistantDebugAnnotateDom)) { + VLOG(3) << NodeSignalsToDebugString(node_signal); + if (result) { + VLOG(3) << "Result { role: " << result->first + << ", objective: " << result->second + << (ignore_objective ? " (ignored)" : "") << " }"; + } + } + if (result && result->first == role && (result->second == objective || ignore_objective)) { NodeData node_data; @@ -145,4 +240,30 @@ void AutofillAssistantAgent::OnGetModelFile(base::Time start_time, std::move(callback).Run(mojom::NodeDataStatus::kSuccess, nodes); } +void AutofillAssistantAgent::SetElementValue(const int32_t backend_node_id, + const std::u16string& value, + bool send_events, + SetElementValueCallback callback) { + blink::WebLocalFrame* frame = render_frame()->GetWebFrame(); + if (!frame) { + VLOG(1) << "Failed to set Element value, no frame."; + std::move(callback).Run(false); + return; + } + + blink::WebElement target_element = + frame->GetDocument().GetElementByDevToolsNodeId(backend_node_id); + if (target_element.IsNull() || !target_element.IsFormControlElement()) { + VLOG(3) << "Failed to set Element value, invalid target."; + std::move(callback).Run(false); + return; + } + + blink::WebFormControlElement target_form_control_element = + target_element.To<blink::WebFormControlElement>(); + target_form_control_element.SetValue(blink::WebString::FromUTF16(value), + send_events); + std::move(callback).Run(true); +} + } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/content/renderer/autofill_assistant_agent.h b/chromium/components/autofill_assistant/content/renderer/autofill_assistant_agent.h index b546af66e3f..41773f1c171 100644 --- a/chromium/components/autofill_assistant/content/renderer/autofill_assistant_agent.h +++ b/chromium/components/autofill_assistant/content/renderer/autofill_assistant_agent.h @@ -47,6 +47,10 @@ class AutofillAssistantAgent : public content::RenderFrameObserver, bool ignore_objective, base::TimeDelta model_timeout, GetSemanticNodesCallback callback) override; + void SetElementValue(int32_t backend_node_id, + const std::u16string& value, + bool send_events, + SetElementValueCallback callback) override; private: // content::RenderFrameObserver: @@ -54,7 +58,8 @@ class AutofillAssistantAgent : public content::RenderFrameObserver, void GetAnnotateDomModel( base::TimeDelta model_timeout, - base::OnceCallback<void(mojom::ModelStatus, base::File)> callback); + base::OnceCallback< + void(mojom::ModelStatus, base::File, const std::string&)> callback); mojom::AutofillAssistantDriver& GetDriver(); @@ -65,7 +70,8 @@ class AutofillAssistantAgent : public content::RenderFrameObserver, bool ignore_objective, GetSemanticNodesCallback callback, mojom::ModelStatus model_status, - base::File model); + base::File model, + const std::string& overrides_policy); mojo::AssociatedRemote<mojom::AutofillAssistantDriver> driver_; diff --git a/chromium/components/autofill_assistant/content/renderer/autofill_assistant_agent_browsertest.cc b/chromium/components/autofill_assistant/content/renderer/autofill_assistant_agent_browsertest.cc index 36900340e42..690a33abd36 100644 --- a/chromium/components/autofill_assistant/content/renderer/autofill_assistant_agent_browsertest.cc +++ b/chromium/components/autofill_assistant/content/renderer/autofill_assistant_agent_browsertest.cc @@ -13,12 +13,17 @@ #include "base/test/mock_callback.h" #include "components/autofill_assistant/content/common/autofill_assistant_agent.mojom.h" #include "components/autofill_assistant/content/common/autofill_assistant_driver.mojom.h" +#include "components/autofill_assistant/content/common/proto/semantic_feature_overrides.pb.h" #include "content/public/renderer/render_frame.h" #include "content/public/test/render_view_test.h" #include "mojo/public/cpp/bindings/associated_receiver_set.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" +#include "third_party/blink/public/web/web_document.h" +#include "third_party/blink/public/web/web_element.h" +#include "third_party/blink/public/web/web_form_control_element.h" +#include "third_party/blink/public/web/web_local_frame.h" namespace autofill_assistant { namespace { @@ -27,6 +32,9 @@ using ::base::test::RunOnceCallback; using ::testing::_; using ::testing::SizeIs; +constexpr int kDummySemanticRole = 9999; +constexpr int kDummyObjective = 1111; + class MockAutofillAssistantDriver : public mojom::AutofillAssistantDriver { public: void BindPendingReceiver(mojo::ScopedInterfaceEndpointHandle handle) { @@ -39,7 +47,8 @@ class MockAutofillAssistantDriver : public mojom::AutofillAssistantDriver { void, GetAnnotateDomModel, (base::TimeDelta timeout, - base::OnceCallback<void(mojom::ModelStatus, base::File)> callback), + base::OnceCallback< + void(mojom::ModelStatus, base::File, const std::string&)> callback), (override)); private: @@ -92,7 +101,7 @@ class AutofillAssistantAgentBrowserTest : public content::RenderViewTest { TEST_F(AutofillAssistantAgentBrowserTest, GetSemanticNodes) { EXPECT_CALL(autofill_assistant_driver_, GetAnnotateDomModel) .WillOnce(RunOnceCallback<1>(mojom::ModelStatus::kSuccess, - model_file_.Duplicate())); + model_file_.Duplicate(), std::string())); base::MockCallback<base::OnceCallback<void(mojom::NodeDataStatus, const std::vector<NodeData>&)>> @@ -117,7 +126,8 @@ TEST_F(AutofillAssistantAgentBrowserTest, GetSemanticNodes) { TEST_F(AutofillAssistantAgentBrowserTest, GetSemanticNodesModelTimeout) { // Do not reply to the model call. EXPECT_CALL(autofill_assistant_driver_, GetAnnotateDomModel) - .WillOnce(RunOnceCallback<1>(mojom::ModelStatus::kTimeout, base::File())); + .WillOnce(RunOnceCallback<1>(mojom::ModelStatus::kTimeout, base::File(), + std::string())); base::MockCallback<base::OnceCallback<void(mojom::NodeDataStatus, const std::vector<NodeData>&)>> @@ -143,7 +153,7 @@ TEST_F(AutofillAssistantAgentBrowserTest, GetSemanticNodesModelError) { // Do not reply to the model call. EXPECT_CALL(autofill_assistant_driver_, GetAnnotateDomModel) .WillOnce(RunOnceCallback<1>(mojom::ModelStatus::kUnexpectedError, - base::File())); + base::File(), std::string())); base::MockCallback<base::OnceCallback<void(mojom::NodeDataStatus, const std::vector<NodeData>&)>> @@ -168,7 +178,7 @@ TEST_F(AutofillAssistantAgentBrowserTest, GetSemanticNodesModelError) { TEST_F(AutofillAssistantAgentBrowserTest, GetSemanticNodesIgnoreObjective) { EXPECT_CALL(autofill_assistant_driver_, GetAnnotateDomModel) .WillOnce(RunOnceCallback<1>(mojom::ModelStatus::kSuccess, - model_file_.Duplicate())); + model_file_.Duplicate(), std::string())); LoadHTML(R"( <div> @@ -189,5 +199,114 @@ TEST_F(AutofillAssistantAgentBrowserTest, GetSemanticNodesIgnoreObjective) { base::RunLoop().RunUntilIdle(); } +TEST_F(AutofillAssistantAgentBrowserTest, Overrides) { + SemanticSelectorPolicy policy_proto; + auto* single_override = + policy_proto.mutable_bag_of_words()->add_data_point_map(); + + auto* coordinate = single_override->add_key_coordinate(); + coordinate->set_feature_concatenation_index(0); + // Vocabulary entry "input" + coordinate->set_vocabulary_index(1); + coordinate->set_number_of_occurrences(1); + + auto* coordinate2 = single_override->add_key_coordinate(); + coordinate2->set_feature_concatenation_index(3); + // Vocabulary entry "street" + coordinate2->set_vocabulary_index(862); + coordinate2->set_number_of_occurrences(1); + + auto* value = single_override->mutable_value(); + value->set_objective(kDummyObjective); + value->set_semantic_role(kDummySemanticRole); + + std::string policy; + ASSERT_TRUE(policy_proto.SerializeToString(&policy)); + + EXPECT_CALL(autofill_assistant_driver_, GetAnnotateDomModel) + .WillOnce(RunOnceCallback<1>(mojom::ModelStatus::kSuccess, + model_file_.Duplicate(), policy)); + + LoadHTML(R"( + <div> + <label for="street">street</label><input id="street"> + </div>)"); + + base::MockCallback<base::OnceCallback<void(mojom::NodeDataStatus, + const std::vector<NodeData>&)>> + callback; + EXPECT_CALL(callback, Run(mojom::NodeDataStatus::kSuccess, SizeIs(1))); + + autofill_assistant_agent_->GetSemanticNodes( + kDummySemanticRole, kDummyObjective, + /* ignore_objective= */ false, + /* model_timeout= */ base::Milliseconds(1000), callback.Get()); + base::RunLoop().RunUntilIdle(); +} + +TEST_F(AutofillAssistantAgentBrowserTest, SetElementValueForInput) { + LoadHTML(R"(<input id="id">)"); + + base::MockCallback<base::OnceCallback<void(bool)>> callback; + EXPECT_CALL(callback, Run(true)); + + const auto web_element = GetMainRenderFrame() + ->GetWebFrame() + ->GetDocument() + .GetElementById(blink::WebString::FromUTF8("id")) + .To<blink::WebFormControlElement>(); + + autofill_assistant_agent_->SetElementValue( + web_element.GetDevToolsNodeIdForTest(), u"value", + /* send_events= */ true, callback.Get()); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(web_element.Value(), "value"); +} + +TEST_F(AutofillAssistantAgentBrowserTest, SetElementValueForSelect) { + LoadHTML(R"( + <select id="id"> + <option value="dog">Dog</option> + <option value="cat">Cat</option> + </select>)"); + + base::MockCallback<base::OnceCallback<void(bool)>> callback; + EXPECT_CALL(callback, Run(true)); + + const auto web_element = GetMainRenderFrame() + ->GetWebFrame() + ->GetDocument() + .GetElementById(blink::WebString::FromUTF8("id")) + .To<blink::WebFormControlElement>(); + + EXPECT_EQ(web_element.Value(), "dog"); + + autofill_assistant_agent_->SetElementValue( + web_element.GetDevToolsNodeIdForTest(), u"cat", + /* send_events= */ true, callback.Get()); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(web_element.Value(), "cat"); +} + +TEST_F(AutofillAssistantAgentBrowserTest, + SetElementValueFailsForNonFormControl) { + LoadHTML(R"( + <div id="id"></div>)"); + + base::MockCallback<base::OnceCallback<void(bool)>> callback; + EXPECT_CALL(callback, Run(false)); + + const auto web_element = + GetMainRenderFrame()->GetWebFrame()->GetDocument().GetElementById( + blink::WebString::FromUTF8("id")); + + autofill_assistant_agent_->SetElementValue( + web_element.GetDevToolsNodeIdForTest(), u"value", + /* send_events= */ true, callback.Get()); + base::RunLoop().RunUntilIdle(); +} + } // namespace } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/content/renderer/autofill_assistant_model_executor.cc b/chromium/components/autofill_assistant/content/renderer/autofill_assistant_model_executor.cc index 4e499ae64e8..df2c4a8b992 100644 --- a/chromium/components/autofill_assistant/content/renderer/autofill_assistant_model_executor.cc +++ b/chromium/components/autofill_assistant/content/renderer/autofill_assistant_model_executor.cc @@ -4,9 +4,13 @@ #include "components/autofill_assistant/content/renderer/autofill_assistant_model_executor.h" +#include <ostream> + +#include "base/command_line.h" #include "base/i18n/case_conversion.h" #include "base/no_destructor.h" #include "base/strings/utf_string_conversions.h" +#include "components/autofill_assistant/content/common/switches.h" #include "components/optimization_guide/core/execution_status.h" #include "components/optimization_guide/core/tflite_op_resolver.h" #include "third_party/abseil-cpp/absl/status/status.h" @@ -17,8 +21,43 @@ #include "third_party/tflite_support/src/tensorflow_lite_support/metadata/cc/metadata_extractor.h" namespace autofill_assistant { +namespace { + +std::string SparseVectorToDebugString( + AutofillAssistantModelExecutor::SparseVector sparse_vector) { + std::ostringstream out; + out << "Sparse vector representation:\n"; + for (const auto& entry : sparse_vector) { + out << " [idx: [" << entry.first.first << ", " << entry.first.second + << "], count: " << entry.second << "]"; + } + return out.str(); +} + +void DenseEncode( + const AutofillAssistantModelExecutor::SparseVector& sparse_vector, + std::vector<std::vector<float>>& inputs) { + for (const auto& entry : sparse_vector) { + const auto& coordinates = entry.first; + if (static_cast<size_t>(coordinates.first) >= inputs.size()) { + NOTREACHED(); + continue; + } + if (static_cast<size_t>(coordinates.second) >= + inputs[coordinates.first].size()) { + NOTREACHED(); + continue; + } + inputs[coordinates.first][coordinates.second] = entry.second; + } +} + +} // namespace + +AutofillAssistantModelExecutor::AutofillAssistantModelExecutor( + absl::optional<OverridesMap> overrides) + : overrides_(std::move(overrides)) {} -AutofillAssistantModelExecutor::AutofillAssistantModelExecutor() = default; AutofillAssistantModelExecutor::~AutofillAssistantModelExecutor() = default; bool AutofillAssistantModelExecutor::InitializeModelFromFile( @@ -121,6 +160,18 @@ bool AutofillAssistantModelExecutor::Preprocess( NOTREACHED() << "Input tensors mismatch."; return false; } + + SparseVector sparse_vector = TokenizeSignalsToSparseVector(node_signals); + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kAutofillAssistantDebugAnnotateDom)) { + VLOG(3) << SparseVectorToDebugString(sparse_vector); + } + + if (overrides_ && overrides_->contains(sparse_vector)) { + overrides_result_ = (*overrides_)[sparse_vector]; + return true; + } + std::vector<std::vector<float>> inputs; for (const auto* input_tensor : input_tensors) { tflite::RuntimeShape shape = tflite::GetTensorShape(input_tensor); @@ -129,27 +180,7 @@ bool AutofillAssistantModelExecutor::Preprocess( } inputs.emplace_back(std::vector(shape.Dims(1), 0.0f)); } - - DCHECK(tags_tokenizer_); - Tokenize(node_signals.node_features.html_tag.Utf16(), tags_tokenizer_.get(), - &inputs[0]); - DCHECK(types_tokenizer_); - Tokenize(node_signals.node_features.type.Utf16(), types_tokenizer_.get(), - &inputs[1]); - DCHECK(text_tokenizer_); - Tokenize(node_signals.node_features.invisible_attributes.Utf16(), - text_tokenizer_.get(), &inputs[2]); - for (const auto& text : node_signals.node_features.text) { - Tokenize(text.Utf16(), text_tokenizer_.get(), &inputs[2]); - } - for (const auto& text : node_signals.label_features.text) { - Tokenize(text.Utf16(), text_tokenizer_.get(), &inputs[3]); - } - for (const auto& text : node_signals.context_features.header_text) { - Tokenize(text.Utf16(), text_tokenizer_.get(), &inputs[4]); - } - Tokenize(node_signals.context_features.form_type.Utf16(), - text_tokenizer_.get(), &inputs[4]); + DenseEncode(sparse_vector, inputs); for (size_t i = 0; i < inputs.size(); ++i) { absl::Status tensor_status = @@ -163,7 +194,7 @@ bool AutofillAssistantModelExecutor::Preprocess( bool AutofillAssistantModelExecutor::GetIndexOfBestRole( const std::vector<float>& output_role, - size_t* index_of_best_role) { + size_t* index_of_best_role) const { if (output_role.size() < static_cast<size_t>( model_metadata_.output().semantic_role().classes_size())) { @@ -182,7 +213,7 @@ bool AutofillAssistantModelExecutor::GetIndexOfBestRole( bool AutofillAssistantModelExecutor::GetBlockIndex( const std::vector<float>& output_role, size_t index_of_best_role, - int* block_index) { + int* block_index) const { if (index_of_best_role >= static_cast<size_t>(model_metadata_.output() .semantic_role() @@ -198,7 +229,7 @@ bool AutofillAssistantModelExecutor::GetBlockIndex( bool AutofillAssistantModelExecutor::GetObjective( const std::vector<float>& output_objective, int block_index, - int* objective) { + int* objective) const { if (block_index + 1 >= model_metadata_.output().objective().blocks_size()) { NOTREACHED(); return false; @@ -207,8 +238,8 @@ bool AutofillAssistantModelExecutor::GetObjective( model_metadata_.output().objective().blocks(block_index); auto block_end = output_objective.begin() + model_metadata_.output().objective().blocks(block_index + 1); - size_t index_of_best_objective = - std::distance(block_start, std::max_element(block_start, block_end)); + size_t index_of_best_objective = std::distance( + output_objective.begin(), std::max_element(block_start, block_end)); if (index_of_best_objective >= static_cast<size_t>( model_metadata_.output().objective().classes_size())) { @@ -223,6 +254,18 @@ bool AutofillAssistantModelExecutor::GetObjective( absl::optional<std::pair<int, int>> AutofillAssistantModelExecutor::Postprocess( const std::vector<const TfLiteTensor*>& output_tensors) { + // Check if we have an override for this execution and return that instead. + if (overrides_result_) { + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kAutofillAssistantDebugAnnotateDom)) { + VLOG(3) << "Found override, using (role: " << overrides_result_->first + << ", objective: " << overrides_result_->second << ")"; + } + // Cleanup the result in case this executor is reused. + std::pair<int, int> result = *overrides_result_; + overrides_result_.reset(); + return result; + } if (output_tensors.size() < 2u) { NOTREACHED() << "Output Tensors mismatch."; return absl::nullopt; @@ -244,6 +287,12 @@ absl::optional<std::pair<int, int>> AutofillAssistantModelExecutor::Postprocess( if (!GetIndexOfBestRole(output_role, &index_of_best_role)) { return absl::nullopt; } + if (index_of_best_role >= + static_cast<size_t>( + model_metadata_.output().semantic_role().classes_size())) { + NOTREACHED(); + return absl::nullopt; + } int semantic_role = model_metadata_.output().semantic_role().classes(index_of_best_role); if (semantic_role == 0) { @@ -265,19 +314,52 @@ absl::optional<std::pair<int, int>> AutofillAssistantModelExecutor::Postprocess( void AutofillAssistantModelExecutor::Tokenize( const std::u16string& input, tflite::support::text::tokenizer::RegexTokenizer* tokenizer, - std::vector<float>* output) { + const int feature_index, + SparseMap& output_map) { auto result = tokenizer->Tokenize(base::UTF16ToUTF8(base::i18n::ToUpper(input))); for (const auto& token : result.subwords) { int index; if (tokenizer->LookupId(token, &index)) { - if (static_cast<size_t>(index) >= output->size()) { - NOTREACHED(); - continue; - } - ++output->at(index); + output_map[std::make_pair(feature_index, index)]++; } } } +AutofillAssistantModelExecutor::SparseVector +AutofillAssistantModelExecutor::TokenizeSignalsToSparseVector( + const blink::AutofillAssistantNodeSignals& node_signals) { + SparseMap sparse_map; + + DCHECK(tags_tokenizer_); + Tokenize(node_signals.node_features.html_tag.Utf16(), tags_tokenizer_.get(), + /* feature_index= */ 0, sparse_map); + DCHECK(types_tokenizer_); + Tokenize(node_signals.node_features.type.Utf16(), types_tokenizer_.get(), + /* feature_index= */ 1, sparse_map); + DCHECK(text_tokenizer_); + Tokenize(node_signals.node_features.invisible_attributes.Utf16(), + text_tokenizer_.get(), /* feature_index= */ 2, sparse_map); + for (const auto& text : node_signals.node_features.text) { + Tokenize(text.Utf16(), text_tokenizer_.get(), /* feature_index= */ 2, + sparse_map); + } + for (const auto& text : node_signals.label_features.text) { + Tokenize(text.Utf16(), text_tokenizer_.get(), /* feature_index= */ 3, + sparse_map); + } + for (const auto& text : node_signals.context_features.header_text) { + Tokenize(text.Utf16(), text_tokenizer_.get(), /* feature_index= */ 4, + sparse_map); + } + Tokenize(node_signals.context_features.form_type.Utf16(), + text_tokenizer_.get(), /* feature_index= */ 4, sparse_map); + + SparseVector sparse_vector; + for (const auto& entry : sparse_map) { + sparse_vector.emplace_back(entry); + } + return sparse_vector; +} + } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/content/renderer/autofill_assistant_model_executor.h b/chromium/components/autofill_assistant/content/renderer/autofill_assistant_model_executor.h index f067934a534..d234c6f3c48 100644 --- a/chromium/components/autofill_assistant/content/renderer/autofill_assistant_model_executor.h +++ b/chromium/components/autofill_assistant/content/renderer/autofill_assistant_model_executor.h @@ -33,8 +33,12 @@ class AutofillAssistantModelExecutor using ExecutionTask = optimization_guide::GenericModelExecutionTask< std::pair<int, int>, const blink::AutofillAssistantNodeSignals&>; + using SparseVector = std::vector<std::pair<std::pair<int, int>, int>>; + using SparseMap = base::flat_map<std::pair<int, int>, int>; + using OverridesMap = base::flat_map<SparseVector, std::pair<int, int>>; - AutofillAssistantModelExecutor(); + explicit AutofillAssistantModelExecutor( + absl::optional<OverridesMap> policy = absl::nullopt); ~AutofillAssistantModelExecutor() override; AutofillAssistantModelExecutor(const AutofillAssistantModelExecutor&) = @@ -70,21 +74,25 @@ class AutofillAssistantModelExecutor void BuildExecutionTask( std::unique_ptr<tflite::task::core::TfLiteEngine> tflite_engine); - // Tokenize the |input| and count words into the |output| vector. The |output| - // can be reused for all relevant inputs for a signal. + // Tokenize the |input| and count words into the |output_map|. The same + // |output_map| should be reused for all relevant inputs for a signal. void Tokenize(const std::u16string& input, tflite::support::text::tokenizer::RegexTokenizer* tokenizer, - std::vector<float>* output); + const int feature_index, + SparseMap& output_map); + + SparseVector TokenizeSignalsToSparseVector( + const blink::AutofillAssistantNodeSignals& node_signals); // Helper functions for post processing based on |model_metadata_|. bool GetIndexOfBestRole(const std::vector<float>& output_role, - size_t* index_of_best_role); + size_t* index_of_best_role) const; bool GetBlockIndex(const std::vector<float>& output_role, size_t index_of_best_role, - int* block_index); + int* block_index) const; bool GetObjective(const std::vector<float>& output_objective, int block_index, - int* objective); + int* objective) const; // Tokenizer for HTML tag. std::unique_ptr<tflite::support::text::tokenizer::RegexTokenizer> @@ -102,6 +110,12 @@ class AutofillAssistantModelExecutor base::MemoryMappedFile model_file_; // Model Metadata for handling input/output. ModelMetadata model_metadata_; + // Data regarding business logic for model execution. + // Set if there is an override for this model execution. + // Sparse encoding of a feature vector table. + // The format is: overrides_[vector] = (semantic_role, objective) + absl::optional<OverridesMap> overrides_; + absl::optional<std::pair<int, int>> overrides_result_; }; } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/content/renderer/autofill_assistant_model_executor_unittest.cc b/chromium/components/autofill_assistant/content/renderer/autofill_assistant_model_executor_unittest.cc index 2f5df620f25..71a5c269564 100644 --- a/chromium/components/autofill_assistant/content/renderer/autofill_assistant_model_executor_unittest.cc +++ b/chromium/components/autofill_assistant/content/renderer/autofill_assistant_model_executor_unittest.cc @@ -24,6 +24,12 @@ namespace autofill_assistant { namespace { +using OverridesMap = AutofillAssistantModelExecutor::OverridesMap; +using SparseVector = AutofillAssistantModelExecutor::SparseVector; + +constexpr int kDummyObjective = 9999; +constexpr int kDummySemanticRole = 1111; + class AutofillAssistantModelExecutorTest : public testing::Test { public: AutofillAssistantModelExecutorTest() { @@ -36,6 +42,20 @@ class AutofillAssistantModelExecutorTest : public testing::Test { ~AutofillAssistantModelExecutorTest() override = default; protected: + OverridesMap CreateOverrides() { + OverridesMap map; + SparseVector vector; + // First, create a feature vector of the feature "street" that is found + // twice on the website for the "second" feature index. + vector.push_back(std::make_pair( + std::make_pair(/* feature_index = */ 2, /* feature= */ 862), + /* count= */ 2)); + // Add an override with a dummy objetive and semantic role for that feature + // vector. + map[vector] = std::make_pair(kDummyObjective, kDummySemanticRole); + return map; + } + base::File model_file_; AutofillAssistantModelExecutor model_executor_; @@ -86,5 +106,71 @@ TEST_F(AutofillAssistantModelExecutorTest, ExecuteWithLoadedModel) { EXPECT_EQ(result->second, 7 /* FILL_DELIVERY_ADDRESS */); } +TEST_F(AutofillAssistantModelExecutorTest, OverridesMatch) { + AutofillAssistantModelExecutor model_executor = + AutofillAssistantModelExecutor(CreateOverrides()); + + ASSERT_TRUE(model_executor.InitializeModelFromFile(model_file_.Duplicate())); + + blink::AutofillAssistantNodeSignals node_signals; + node_signals.node_features.invisible_attributes = + blink::WebString::FromUTF8("street"); + node_signals.node_features.text.push_back( + blink::WebString::FromUTF8("street")); + + auto result = model_executor.ExecuteModelWithInput(node_signals); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->first, 9999); + EXPECT_EQ(result->second, 1111); +} + +TEST_F(AutofillAssistantModelExecutorTest, OverridesNoMatch) { + AutofillAssistantModelExecutor model_executor = + AutofillAssistantModelExecutor(CreateOverrides()); + + ASSERT_TRUE(model_executor.InitializeModelFromFile(model_file_.Duplicate())); + + blink::AutofillAssistantNodeSignals node_signals; + node_signals.node_features.text.push_back( + blink::WebString::FromUTF8("street")); + + auto result = model_executor.ExecuteModelWithInput(node_signals); + ASSERT_TRUE(result.has_value()); + EXPECT_NE(result->first, 9999); + EXPECT_NE(result->second, 1111); +} + +TEST_F(AutofillAssistantModelExecutorTest, OverridesResultNotReused) { + AutofillAssistantModelExecutor model_executor = + AutofillAssistantModelExecutor(CreateOverrides()); + + ASSERT_TRUE(model_executor.InitializeModelFromFile(model_file_.Duplicate())); + { + blink::AutofillAssistantNodeSignals node_signals; + node_signals.node_features.invisible_attributes = + blink::WebString::FromUTF8("street"); + node_signals.node_features.text.push_back( + blink::WebString::FromUTF8("street")); + + auto result = model_executor.ExecuteModelWithInput(node_signals); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->first, 9999); + EXPECT_EQ(result->second, 1111); + } + + // We expect the internal overrides result from the previous execution to have + // been cleared. + { + blink::AutofillAssistantNodeSignals node_signals; + node_signals.node_features.text.push_back( + blink::WebString::FromUTF8("unknown")); + + auto result = model_executor.ExecuteModelWithInput(node_signals); + ASSERT_TRUE(result.has_value()); + EXPECT_NE(result->first, 9999); + EXPECT_NE(result->second, 1111); + } +} + } // namespace } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/guided_browsing/OWNERS b/chromium/components/autofill_assistant/guided_browsing/OWNERS new file mode 100644 index 00000000000..ed892cb7288 --- /dev/null +++ b/chromium/components/autofill_assistant/guided_browsing/OWNERS @@ -0,0 +1,2 @@ +# Please keep these in alphabetical order +jainshashank@google.com diff --git a/chromium/components/autofill_assistant/guided_browsing/android/BUILD.gn b/chromium/components/autofill_assistant/guided_browsing/android/BUILD.gn new file mode 100644 index 00000000000..8e3d580f887 --- /dev/null +++ b/chromium/components/autofill_assistant/guided_browsing/android/BUILD.gn @@ -0,0 +1,50 @@ +# Copyright 2022 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") + +android_library("java") { + resources_package = + "org.chromium.components.autofill_assistant.guided_browsing" + + deps = [ + ":java_resources", + "$google_play_services_package:google_play_services_vision_common_java", + "$google_play_services_package:google_play_services_vision_java", + "//base:base_java", + "//content/public/android:content_java", + "//third_party/androidx:androidx_appcompat_appcompat_java", + "//ui/android:ui_java", + ] + + sources = [ + "java/src/org/chromium/components/autofill_assistant/guided_browsing/LayoutUtils.java", + "java/src/org/chromium/components/autofill_assistant/guided_browsing/qr_code/AssistantQrCodeController.java", + "java/src/org/chromium/components/autofill_assistant/guided_browsing/qr_code/AssistantQrCodeDelegate.java", + "java/src/org/chromium/components/autofill_assistant/guided_browsing/qr_code/camera_scan/AssistantQrCodeCameraCallbacks.java", + "java/src/org/chromium/components/autofill_assistant/guided_browsing/qr_code/camera_scan/AssistantQrCodeCameraPreview.java", + "java/src/org/chromium/components/autofill_assistant/guided_browsing/qr_code/camera_scan/AssistantQrCodeCameraPreviewOverlay.java", + "java/src/org/chromium/components/autofill_assistant/guided_browsing/qr_code/camera_scan/AssistantQrCodeCameraScanBinder.java", + "java/src/org/chromium/components/autofill_assistant/guided_browsing/qr_code/camera_scan/AssistantQrCodeCameraScanCoordinator.java", + "java/src/org/chromium/components/autofill_assistant/guided_browsing/qr_code/camera_scan/AssistantQrCodeCameraScanDialog.java", + "java/src/org/chromium/components/autofill_assistant/guided_browsing/qr_code/camera_scan/AssistantQrCodeCameraScanModel.java", + "java/src/org/chromium/components/autofill_assistant/guided_browsing/qr_code/camera_scan/AssistantQrCodeCameraScanView.java", + "java/src/org/chromium/components/autofill_assistant/guided_browsing/qr_code/utils/AssistantQrCodePermissionUtils.java", + ] + + annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ] +} + +android_resources("java_resources") { + sources = [ + "java/res/layout/autofill_assistant_qr_code_camera_scan_dialog.xml", + "java/res/layout/autofill_assistant_qr_code_camera_scan_permission_layout.xml", + "java/res/values-v17/dimens.xml", + ] + deps = [ + "//components/browser_ui/strings/android:browser_ui_strings_grd", + "//components/browser_ui/styles/android:java_resources", + "//components/browser_ui/widget/android:java_resources", + ] +} diff --git a/chromium/components/autofill_assistant/guided_browsing/android/DEPS b/chromium/components/autofill_assistant/guided_browsing/android/DEPS new file mode 100644 index 00000000000..83e44bc3998 --- /dev/null +++ b/chromium/components/autofill_assistant/guided_browsing/android/DEPS @@ -0,0 +1,7 @@ +include_rules = [ + "+components/browser_ui/styles/android", + "+components/browser_ui/widget/android", + "+components/browser_ui/strings/android", + "+content/public/android", + "+ui/android", +] |