summaryrefslogtreecommitdiff
path: root/chromium/components/autofill_assistant
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-12 14:27:29 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-13 09:35:20 +0000
commitc30a6232df03e1efbd9f3b226777b07e087a1122 (patch)
treee992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/components/autofill_assistant
parent7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff)
downloadqtwebengine-chromium-c30a6232df03e1efbd9f3b226777b07e087a1122.tar.gz
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/components/autofill_assistant')
-rw-r--r--chromium/components/autofill_assistant/browser/BUILD.gn10
-rw-r--r--chromium/components/autofill_assistant/browser/actions/action_delegate.h28
-rw-r--r--chromium/components/autofill_assistant/browser/actions/collect_user_data_action.cc123
-rw-r--r--chromium/components/autofill_assistant/browser/actions/collect_user_data_action_unittest.cc55
-rw-r--r--chromium/components/autofill_assistant/browser/actions/fallback_handler/fallback_data.cc73
-rw-r--r--chromium/components/autofill_assistant/browser/actions/fallback_handler/fallback_data.h40
-rw-r--r--chromium/components/autofill_assistant/browser/actions/fallback_handler/required_field.cc41
-rw-r--r--chromium/components/autofill_assistant/browser/actions/fallback_handler/required_field.h21
-rw-r--r--chromium/components/autofill_assistant/browser/actions/fallback_handler/required_fields_fallback_handler.cc135
-rw-r--r--chromium/components/autofill_assistant/browser/actions/fallback_handler/required_fields_fallback_handler.h33
-rw-r--r--chromium/components/autofill_assistant/browser/actions/fallback_handler/required_fields_fallback_handler_unittest.cc479
-rw-r--r--chromium/components/autofill_assistant/browser/actions/generate_password_for_form_field_action_unittest.cc5
-rw-r--r--chromium/components/autofill_assistant/browser/actions/mock_action_delegate.h49
-rw-r--r--chromium/components/autofill_assistant/browser/actions/navigate_action.cc7
-rw-r--r--chromium/components/autofill_assistant/browser/actions/prompt_action.cc4
-rw-r--r--chromium/components/autofill_assistant/browser/actions/prompt_action_unittest.cc30
-rw-r--r--chromium/components/autofill_assistant/browser/actions/set_attribute_action.cc1
-rw-r--r--chromium/components/autofill_assistant/browser/actions/set_form_field_value_action.cc1
-rw-r--r--chromium/components/autofill_assistant/browser/actions/set_form_field_value_action_unittest.cc5
-rw-r--r--chromium/components/autofill_assistant/browser/actions/show_generic_ui_action.cc147
-rw-r--r--chromium/components/autofill_assistant/browser/actions/show_generic_ui_action.h26
-rw-r--r--chromium/components/autofill_assistant/browser/actions/show_generic_ui_action_unittest.cc117
-rw-r--r--chromium/components/autofill_assistant/browser/actions/show_progress_bar_action.cc45
-rw-r--r--chromium/components/autofill_assistant/browser/actions/show_progress_bar_action.h5
-rw-r--r--chromium/components/autofill_assistant/browser/actions/show_progress_bar_action_unittest.cc166
-rw-r--r--chromium/components/autofill_assistant/browser/actions/use_address_action.cc164
-rw-r--r--chromium/components/autofill_assistant/browser/actions/use_address_action.h23
-rw-r--r--chromium/components/autofill_assistant/browser/actions/use_address_action_unittest.cc229
-rw-r--r--chromium/components/autofill_assistant/browser/actions/use_credit_card_action.cc145
-rw-r--r--chromium/components/autofill_assistant/browser/actions/use_credit_card_action.h17
-rw-r--r--chromium/components/autofill_assistant/browser/actions/use_credit_card_action_unittest.cc348
-rw-r--r--chromium/components/autofill_assistant/browser/actions/wait_for_document_action_unittest.cc4
-rw-r--r--chromium/components/autofill_assistant/browser/actions/wait_for_dom_action_unittest.cc22
-rw-r--r--chromium/components/autofill_assistant/browser/basic_interactions.cc127
-rw-r--r--chromium/components/autofill_assistant/browser/basic_interactions.h32
-rw-r--r--chromium/components/autofill_assistant/browser/basic_interactions_unittest.cc129
-rw-r--r--chromium/components/autofill_assistant/browser/client.h3
-rw-r--r--chromium/components/autofill_assistant/browser/client_settings.h2
-rw-r--r--chromium/components/autofill_assistant/browser/client_status.cc3
-rw-r--r--chromium/components/autofill_assistant/browser/controller.cc151
-rw-r--r--chromium/components/autofill_assistant/browser/controller.h29
-rw-r--r--chromium/components/autofill_assistant/browser/controller_observer.h10
-rw-r--r--chromium/components/autofill_assistant/browser/controller_unittest.cc123
-rw-r--r--chromium/components/autofill_assistant/browser/devtools/devtools_api/domain_type_conversions_h.template2
-rw-r--r--chromium/components/autofill_assistant/browser/devtools/devtools_client.cc4
-rw-r--r--chromium/components/autofill_assistant/browser/element_area.cc3
-rw-r--r--chromium/components/autofill_assistant/browser/element_area_unittest.cc42
-rw-r--r--chromium/components/autofill_assistant/browser/element_precondition_unittest.cc121
-rw-r--r--chromium/components/autofill_assistant/browser/event_handler.cc66
-rw-r--r--chromium/components/autofill_assistant/browser/event_handler.h4
-rw-r--r--chromium/components/autofill_assistant/browser/fake_script_executor_delegate.cc17
-rw-r--r--chromium/components/autofill_assistant/browser/fake_script_executor_delegate.h13
-rw-r--r--chromium/components/autofill_assistant/browser/features.cc8
-rw-r--r--chromium/components/autofill_assistant/browser/features.h2
-rw-r--r--chromium/components/autofill_assistant/browser/field_formatter.cc128
-rw-r--r--chromium/components/autofill_assistant/browser/field_formatter.h37
-rw-r--r--chromium/components/autofill_assistant/browser/field_formatter_unittest.cc209
-rw-r--r--chromium/components/autofill_assistant/browser/generic_ui.proto81
-rw-r--r--chromium/components/autofill_assistant/browser/generic_ui_java_generated_enums.h20
-rw-r--r--chromium/components/autofill_assistant/browser/metrics.h20
-rw-r--r--chromium/components/autofill_assistant/browser/mock_client.h1
-rw-r--r--chromium/components/autofill_assistant/browser/mock_controller_observer.h5
-rw-r--r--chromium/components/autofill_assistant/browser/model.proto12
-rw-r--r--chromium/components/autofill_assistant/browser/radio_button_controller.cc44
-rw-r--r--chromium/components/autofill_assistant/browser/radio_button_controller.h49
-rw-r--r--chromium/components/autofill_assistant/browser/radio_button_controller_unittest.cc77
-rw-r--r--chromium/components/autofill_assistant/browser/script_executor.cc52
-rw-r--r--chromium/components/autofill_assistant/browser/script_executor.h16
-rw-r--r--chromium/components/autofill_assistant/browser/script_executor_delegate.h15
-rw-r--r--chromium/components/autofill_assistant/browser/script_executor_unittest.cc109
-rw-r--r--chromium/components/autofill_assistant/browser/script_precondition.cc1
-rw-r--r--chromium/components/autofill_assistant/browser/script_precondition_unittest.cc7
-rw-r--r--chromium/components/autofill_assistant/browser/script_tracker_unittest.cc13
-rw-r--r--chromium/components/autofill_assistant/browser/selector.cc409
-rw-r--r--chromium/components/autofill_assistant/browser/selector.h105
-rw-r--r--chromium/components/autofill_assistant/browser/selector_unittest.cc181
-rw-r--r--chromium/components/autofill_assistant/browser/service.proto392
-rw-r--r--chromium/components/autofill_assistant/browser/service_impl.cc11
-rw-r--r--chromium/components/autofill_assistant/browser/service_impl.h8
-rw-r--r--chromium/components/autofill_assistant/browser/service_impl_unittest.cc21
-rw-r--r--chromium/components/autofill_assistant/browser/trigger_context.cc20
-rw-r--r--chromium/components/autofill_assistant/browser/trigger_context.h16
-rw-r--r--chromium/components/autofill_assistant/browser/trigger_context_unittest.cc50
-rw-r--r--chromium/components/autofill_assistant/browser/ui_delegate.h13
-rw-r--r--chromium/components/autofill_assistant/browser/user_data_util.cc101
-rw-r--r--chromium/components/autofill_assistant/browser/user_data_util.h12
-rw-r--r--chromium/components/autofill_assistant/browser/user_data_util_unittest.cc294
-rw-r--r--chromium/components/autofill_assistant/browser/user_model.cc36
-rw-r--r--chromium/components/autofill_assistant/browser/user_model.h18
-rw-r--r--chromium/components/autofill_assistant/browser/user_model_unittest.cc71
-rw-r--r--chromium/components/autofill_assistant/browser/value_util.cc31
-rw-r--r--chromium/components/autofill_assistant/browser/value_util.h6
-rw-r--r--chromium/components/autofill_assistant/browser/value_util_unittest.cc37
-rw-r--r--chromium/components/autofill_assistant/browser/view_layout.proto21
-rw-r--r--chromium/components/autofill_assistant/browser/web/element_finder.cc929
-rw-r--r--chromium/components/autofill_assistant/browser/web/element_finder.h286
-rw-r--r--chromium/components/autofill_assistant/browser/web/element_position_getter.cc9
-rw-r--r--chromium/components/autofill_assistant/browser/web/element_rect_getter.cc1
-rw-r--r--chromium/components/autofill_assistant/browser/web/web_controller.cc38
-rw-r--r--chromium/components/autofill_assistant/browser/web/web_controller_browsertest.cc759
-rw-r--r--chromium/components/autofill_assistant/browser/website_login_manager_impl.cc17
101 files changed, 5968 insertions, 2309 deletions
diff --git a/chromium/components/autofill_assistant/browser/BUILD.gn b/chromium/components/autofill_assistant/browser/BUILD.gn
index 5d059ccea71..803c0e3f8bc 100644
--- a/chromium/components/autofill_assistant/browser/BUILD.gn
+++ b/chromium/components/autofill_assistant/browser/BUILD.gn
@@ -32,8 +32,6 @@ jumbo_static_library("browser") {
"actions/configure_bottom_sheet_action.h",
"actions/expect_navigation_action.cc",
"actions/expect_navigation_action.h",
- "actions/fallback_handler/fallback_data.cc",
- "actions/fallback_handler/fallback_data.h",
"actions/fallback_handler/required_field.cc",
"actions/fallback_handler/required_field.h",
"actions/fallback_handler/required_fields_fallback_handler.cc",
@@ -115,6 +113,8 @@ jumbo_static_library("browser") {
"event_handler.h",
"features.cc",
"features.h",
+ "field_formatter.cc",
+ "field_formatter.h",
"generic_ui_java_generated_enums.h",
"info_box.cc",
"info_box.h",
@@ -123,6 +123,8 @@ jumbo_static_library("browser") {
"overlay_state.h",
"protocol_utils.cc",
"protocol_utils.h",
+ "radio_button_controller.cc",
+ "radio_button_controller.h",
"rectf.cc",
"rectf.h",
"retry_timer.cc",
@@ -190,6 +192,7 @@ jumbo_static_library("browser") {
"//components/autofill_assistant/browser/devtools",
"//components/google/core/common:common",
"//components/password_manager/core/browser:browser",
+ "//components/password_manager/core/browser/form_parsing:form_parsing",
"//components/signin/public/identity_manager",
"//components/strings:components_strings_grit",
"//components/version_info",
@@ -248,6 +251,7 @@ source_set("unit_tests") {
"actions/set_form_field_value_action_unittest.cc",
"actions/show_details_action_unittest.cc",
"actions/show_generic_ui_action_unittest.cc",
+ "actions/show_progress_bar_action_unittest.cc",
"actions/tell_action_unittest.cc",
"actions/use_address_action_unittest.cc",
"actions/use_credit_card_action_unittest.cc",
@@ -260,7 +264,9 @@ source_set("unit_tests") {
"element_area_unittest.cc",
"element_precondition_unittest.cc",
"event_handler_unittest.cc",
+ "field_formatter_unittest.cc",
"protocol_utils_unittest.cc",
+ "radio_button_controller_unittest.cc",
"retry_timer_unittest.cc",
"script_executor_unittest.cc",
"script_precondition_unittest.cc",
diff --git a/chromium/components/autofill_assistant/browser/actions/action_delegate.h b/chromium/components/autofill_assistant/browser/actions/action_delegate.h
index 2998d21d193..2bb9e13af86 100644
--- a/chromium/components/autofill_assistant/browser/actions/action_delegate.h
+++ b/chromium/components/autofill_assistant/browser/actions/action_delegate.h
@@ -134,17 +134,14 @@ class ActionDelegate {
base::OnceCallback<void(UserData*, UserData::FieldChange*)>
write_callback) = 0;
- // Executes |write_callback| on the currently stored user_model.
- virtual void WriteUserModel(
- base::OnceCallback<void(UserModel*)> write_callback) = 0;
-
using GetFullCardCallback =
base::OnceCallback<void(std::unique_ptr<autofill::CreditCard> card,
const base::string16& cvc)>;
- // Asks for the full card information for the selected card. Might require the
+ // Asks for the full card information for |credit_card|. Might require the
// user entering CVC.
- virtual void GetFullCard(GetFullCardCallback callback) = 0;
+ virtual void GetFullCard(const autofill::CreditCard* credit_card,
+ GetFullCardCallback callback) = 0;
// Fill the address form given by |selector| with the given address
// |profile|. |profile| cannot be nullptr.
@@ -316,9 +313,17 @@ class ActionDelegate {
// Show the progress bar and set it at |progress|%.
virtual void SetProgress(int progress) = 0;
+ // Show the progress bar and set the |active_step| to active.
+ virtual void SetProgressActiveStep(int active_step) = 0;
+
// Shows the progress bar when |visible| is true. Hides it when false.
virtual void SetProgressVisible(bool visible) = 0;
+ // Sets a new step progress bar configuration.
+ virtual void SetStepProgressBarConfiguration(
+ const ShowProgressBarProto::StepProgressBarConfiguration&
+ configuration) = 0;
+
// Set the viewport mode.
virtual void SetViewportMode(ViewportMode mode) = 0;
@@ -363,14 +368,19 @@ class ActionDelegate {
// Gets the user data.
virtual const UserData* GetUserData() const = 0;
+ // Access to the user model.
+ virtual UserModel* GetUserModel() = 0;
+
// Show |generic_ui| to the user and call |end_action_callback| when done.
// Note that this callback needs to be tied to one or multiple interactions
// specified in |generic_ui|, as otherwise it will never be called.
+ // |view_inflation_finished_callback| should be called immediately after
+ // view inflation, with a status indicating whether view inflation succeeded.
virtual void SetGenericUi(
std::unique_ptr<GenericUserInterfaceProto> generic_ui,
- base::OnceCallback<void(bool,
- ProcessedActionStatusProto,
- const UserModel*)> end_action_callback) = 0;
+ base::OnceCallback<void(const ClientStatus&)> end_action_callback,
+ base::OnceCallback<void(const ClientStatus&)>
+ view_inflation_finished_callback) = 0;
// Clears the generic UI. This will remove all corresponding views from the
// view hierarchy and remove all corresponding interactions. Note that
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 f8b2add2361..43dc6724748 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
@@ -85,8 +85,6 @@ bool OnlyLoginRequested(
collect_user_data_options.additional_appended_sections.end(),
find_additional_input_sections) !=
collect_user_data_options.additional_appended_sections.end();
- LOG(ERROR) << "HAS_INPUT_SECTIONS: " << has_input_sections;
-
return !has_input_sections && !collect_user_data_options.request_payer_name &&
!collect_user_data_options.request_payer_email &&
!collect_user_data_options.request_payer_phone &&
@@ -98,107 +96,6 @@ bool OnlyLoginRequested(
.has_value();
}
-bool IsCompleteContact(
- const autofill::AutofillProfile* profile,
- const CollectUserDataOptions& collect_user_data_options) {
- if (!collect_user_data_options.request_payer_name &&
- !collect_user_data_options.request_payer_email &&
- !collect_user_data_options.request_payer_phone) {
- return true;
- }
-
- if (!profile) {
- return false;
- }
-
- if (collect_user_data_options.request_payer_name &&
- !profile->HasInfo(autofill::NAME_FULL)) {
- return false;
- }
-
- if (collect_user_data_options.request_payer_email &&
- !profile->HasInfo(autofill::EMAIL_ADDRESS)) {
- return false;
- }
-
- if (collect_user_data_options.request_payer_phone &&
- !profile->HasInfo(autofill::PHONE_HOME_WHOLE_NUMBER)) {
- return false;
- }
- return true;
-}
-
-bool IsCompleteAddress(const autofill::AutofillProfile* profile,
- bool require_postal_code) {
- if (!profile) {
- return false;
- }
- // We use a hard coded locale here since it's not used in the autofill:: code
- // anyway. I.e. creating this profile ends up in FormGroup::GetInfoImpl, which
- // simply ignores the app_locale.
- auto address_data =
- autofill::i18n::CreateAddressDataFromAutofillProfile(*profile, "en-US");
- if (!autofill::addressinput::HasAllRequiredFields(*address_data)) {
- return false;
- }
-
- if (require_postal_code && address_data->postal_code.empty()) {
- return false;
- }
-
- return true;
-}
-
-bool IsCompleteShippingAddress(
- const autofill::AutofillProfile* profile,
- const CollectUserDataOptions& collect_user_data_options) {
- return !collect_user_data_options.request_shipping ||
- IsCompleteAddress(profile, /* require_postal_code = */ false);
-}
-
-bool IsCompleteCreditCard(
- const autofill::CreditCard* credit_card,
- const autofill::AutofillProfile* billing_profile,
- const CollectUserDataOptions& collect_user_data_options) {
- if (!collect_user_data_options.request_payment_method) {
- return true;
- }
-
- if (!credit_card || !billing_profile) {
- return false;
- }
-
- if (!IsCompleteAddress(
- billing_profile,
- collect_user_data_options.require_billing_postal_code)) {
- return false;
- }
-
- if (credit_card->record_type() != autofill::CreditCard::MASKED_SERVER_CARD &&
- !credit_card->HasValidCardNumber()) {
- // Can't check validity of masked server card numbers because they are
- // incomplete until decrypted.
- return false;
- }
-
- if (!credit_card->HasValidExpirationDate() ||
- credit_card->billing_address_id().empty()) {
- return false;
- }
-
- std::string basic_card_network =
- autofill::data_util::GetPaymentRequestData(credit_card->network())
- .basic_card_issuer_network;
- if (!collect_user_data_options.supported_basic_card_networks.empty() &&
- std::find(collect_user_data_options.supported_basic_card_networks.begin(),
- collect_user_data_options.supported_basic_card_networks.end(),
- basic_card_network) ==
- collect_user_data_options.supported_basic_card_networks.end()) {
- return false;
- }
- return true;
-}
-
bool IsValidLoginChoice(
const std::string& choice_identifier,
const CollectUserDataOptions& collect_user_data_options) {
@@ -535,7 +432,6 @@ void CollectUserDataAction::InternalProcessAction(
ProcessActionCallback callback) {
callback_ = std::move(callback);
if (!CreateOptionsFromProto()) {
- LOG(ERROR) << "INVALID";
EndAction(ClientStatus(INVALID_ACTION));
return;
}
@@ -975,17 +871,16 @@ bool CollectUserDataAction::CreateOptionsFromProto() {
collect_user_data.additional_model_identifier_to_check();
}
- // TODO(crbug.com/806868): Maybe we could refactor this to make the confirm
- // chip and direct_action part of the additional_actions.
- std::string confirm_text = collect_user_data.confirm_button_text();
- if (confirm_text.empty()) {
- confirm_text =
- l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_PAYMENT_INFO_CONFIRM);
+ auto* confirm_chip =
+ collect_user_data_options_->confirm_action.mutable_chip();
+ if (collect_user_data.has_confirm_chip()) {
+ *confirm_chip = collect_user_data.confirm_chip();
+ } else {
+ confirm_chip->set_text(
+ l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_PAYMENT_INFO_CONFIRM));
+ confirm_chip->set_type(HIGHLIGHTED_ACTION);
}
- collect_user_data_options_->confirm_action.mutable_chip()->set_text(
- confirm_text);
- collect_user_data_options_->confirm_action.mutable_chip()->set_type(
- HIGHLIGHTED_ACTION);
+
*collect_user_data_options_->confirm_action.mutable_direct_action() =
collect_user_data.confirm_direct_action();
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 42f3aec6dd2..894d0f751fe 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
@@ -2286,5 +2286,60 @@ TEST_F(CollectUserDataActionTest, LinkClickWritesPartialUserData) {
action.ProcessAction(callback_.Get());
}
+TEST_F(CollectUserDataActionTest, ConfirmButtonChip) {
+ ActionProto action_proto;
+ auto* collect_user_data_proto = action_proto.mutable_collect_user_data();
+ collect_user_data_proto->set_request_terms_and_conditions(false);
+ auto* confirm_chip = collect_user_data_proto->mutable_confirm_chip();
+ confirm_chip->set_text("Custom text");
+ confirm_chip->set_icon(ICON_CLEAR);
+ confirm_chip->set_sticky(true);
+
+ ON_CALL(mock_action_delegate_, CollectUserData(_))
+ .WillByDefault(
+ Invoke([this](CollectUserDataOptions* collect_user_data_options) {
+ EXPECT_EQ(collect_user_data_options->confirm_action.chip().text(),
+ "Custom text");
+ EXPECT_EQ(collect_user_data_options->confirm_action.chip().icon(),
+ ICON_CLEAR);
+ EXPECT_EQ(collect_user_data_options->confirm_action.chip().sticky(),
+ true);
+ user_data_.succeed_ = true;
+ std::move(collect_user_data_options->confirm_callback)
+ .Run(&user_data_, &user_model_);
+ }));
+
+ EXPECT_CALL(
+ callback_,
+ Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED))));
+
+ CollectUserDataAction action(&mock_action_delegate_, action_proto);
+ action.ProcessAction(callback_.Get());
+}
+
+TEST_F(CollectUserDataActionTest, ConfirmButtonFallbackText) {
+ ActionProto action_proto;
+ auto* collect_user_data_proto = action_proto.mutable_collect_user_data();
+ collect_user_data_proto->set_request_terms_and_conditions(false);
+
+ ON_CALL(mock_action_delegate_, CollectUserData(_))
+ .WillByDefault(
+ Invoke([this](CollectUserDataOptions* collect_user_data_options) {
+ EXPECT_EQ(collect_user_data_options->confirm_action.chip().text(),
+ l10n_util::GetStringUTF8(
+ IDS_AUTOFILL_ASSISTANT_PAYMENT_INFO_CONFIRM));
+ user_data_.succeed_ = true;
+ std::move(collect_user_data_options->confirm_callback)
+ .Run(&user_data_, &user_model_);
+ }));
+
+ EXPECT_CALL(
+ callback_,
+ Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED))));
+
+ CollectUserDataAction action(&mock_action_delegate_, action_proto);
+ action.ProcessAction(callback_.Get());
+}
+
} // namespace
} // namespace autofill_assistant
diff --git a/chromium/components/autofill_assistant/browser/actions/fallback_handler/fallback_data.cc b/chromium/components/autofill_assistant/browser/actions/fallback_handler/fallback_data.cc
deleted file mode 100644
index f0e08b1e03d..00000000000
--- a/chromium/components/autofill_assistant/browser/actions/fallback_handler/fallback_data.cc
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/autofill_assistant/browser/actions/fallback_handler/fallback_data.h"
-
-#include "base/optional.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/strings/utf_string_conversions.h"
-#include "components/autofill/core/browser/autofill_type.h"
-#include "components/autofill/core/browser/data_model/form_group.h"
-#include "third_party/re2/src/re2/re2.h"
-#include "third_party/re2/src/re2/stringpiece.h"
-
-namespace autofill_assistant {
-namespace {
-
-// Matches a single $ followed by an opening {, then an inner key and a closing
-// }.
-const char kKeyPattern[] = R"re(\$\{([^}]+)\})re";
-
-} // namespace
-
-FallbackData::FallbackData() = default;
-
-FallbackData::~FallbackData() = default;
-
-void FallbackData::AddFormGroup(const autofill::FormGroup& form_group) {
- autofill::ServerFieldTypeSet available_fields;
- form_group.GetNonEmptyTypes("en-US", &available_fields);
- for (const auto& field : available_fields) {
- field_values.emplace(static_cast<int>(field),
- base::UTF16ToUTF8(form_group.GetInfo(
- autofill::AutofillType(field), "en-US")));
- }
-}
-
-base::Optional<std::string> FallbackData::GetValue(int key) {
- auto it = field_values.find(key);
- if (it != field_values.end() && !it->second.empty()) {
- return it->second;
- }
- return base::nullopt;
-}
-
-base::Optional<std::string> FallbackData::GetValueByExpression(
- const std::string& value_expression) {
- int key;
- if (base::StringToInt(value_expression, &key)) {
- return GetValue(key);
- }
-
- std::string out = value_expression;
-
- re2::StringPiece input(value_expression);
- while (re2::RE2::FindAndConsume(&input, kKeyPattern, &key)) {
- auto rewrite_value = GetValue(key);
- if (!rewrite_value.has_value()) {
- DVLOG(3) << "No value for " << key << " in " << value_expression;
- return base::nullopt;
- }
-
- re2::RE2::Replace(&out, kKeyPattern,
- re2::StringPiece(rewrite_value.value()));
- }
-
- if (out.empty()) {
- return base::nullopt;
- }
- return out;
-}
-
-} // namespace autofill_assistant
diff --git a/chromium/components/autofill_assistant/browser/actions/fallback_handler/fallback_data.h b/chromium/components/autofill_assistant/browser/actions/fallback_handler/fallback_data.h
deleted file mode 100644
index 5deb5111876..00000000000
--- a/chromium/components/autofill_assistant/browser/actions/fallback_handler/fallback_data.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_FALLBACK_HANDLER_FALLBACK_DATA_H_
-#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_FALLBACK_HANDLER_FALLBACK_DATA_H_
-
-#include <map>
-
-#include "base/optional.h"
-#include "components/autofill/core/browser/data_model/form_group.h"
-
-namespace autofill_assistant {
-
-// Data necessary for filling in the fallback fields. This is kept in a
-// separate struct to make sure we don't keep it for longer than strictly
-// necessary.
-struct FallbackData {
- public:
- FallbackData();
- ~FallbackData();
-
- FallbackData(const FallbackData&) = delete;
- FallbackData& operator=(const FallbackData&) = delete;
-
- // The key of the map. Should be either an entry of field_types.h or an
- // enum of Use*Action::AutofillAssistantCustomField.
- std::map<int, std::string> field_values;
-
- void AddFormGroup(const autofill::FormGroup& form_group);
-
- base::Optional<std::string> GetValueByExpression(
- const std::string& value_expression);
-
- private:
- base::Optional<std::string> GetValue(int key);
-};
-
-} // namespace autofill_assistant
-#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_FALLBACK_HANDLER_FALLBACK_DATA_H_
diff --git a/chromium/components/autofill_assistant/browser/actions/fallback_handler/required_field.cc b/chromium/components/autofill_assistant/browser/actions/fallback_handler/required_field.cc
index d22104117e8..1a12d5b8696 100644
--- a/chromium/components/autofill_assistant/browser/actions/fallback_handler/required_field.cc
+++ b/chromium/components/autofill_assistant/browser/actions/fallback_handler/required_field.cc
@@ -12,40 +12,13 @@ RequiredField::~RequiredField() = default;
RequiredField::RequiredField(const RequiredField& copy) = default;
-void RequiredField::FromProto(
- const UseAddressProto::RequiredField& required_field_proto) {
- value_expression = required_field_proto.value_expression();
- selector = Selector(required_field_proto.element());
- fill_strategy = required_field_proto.fill_strategy();
- select_strategy = required_field_proto.select_strategy();
- delay_in_millisecond = required_field_proto.delay_in_millisecond();
- if (required_field_proto.has_option_element_to_click()) {
- fallback_click_element =
- Selector(required_field_proto.option_element_to_click());
- click_type = required_field_proto.click_type();
- }
- forced = required_field_proto.forced();
-}
-
-void RequiredField::FromProto(
- const UseCreditCardProto::RequiredField& required_field_proto) {
- value_expression = required_field_proto.value_expression();
- selector = Selector(required_field_proto.element());
- fill_strategy = required_field_proto.fill_strategy();
- select_strategy = required_field_proto.select_strategy();
- delay_in_millisecond = required_field_proto.delay_in_millisecond();
- if (required_field_proto.has_option_element_to_click()) {
- fallback_click_element =
- Selector(required_field_proto.option_element_to_click());
- click_type = required_field_proto.click_type();
- }
- forced = required_field_proto.forced();
-}
-
-bool RequiredField::ShouldFallback(bool has_fallback_data) const {
- return (status == EMPTY && !fallback_click_element.has_value()) ||
- (forced && has_fallback_data) ||
- (fallback_click_element.has_value() && has_fallback_data);
+bool RequiredField::ShouldFallback(bool apply_fallback) const {
+ return (status == EMPTY && !value_expression.empty() &&
+ !fallback_click_element.has_value()) ||
+ (status != EMPTY && value_expression.empty() &&
+ !fallback_click_element.has_value()) ||
+ (forced && apply_fallback) ||
+ (fallback_click_element.has_value() && apply_fallback);
}
} // namespace autofill_assistant
diff --git a/chromium/components/autofill_assistant/browser/actions/fallback_handler/required_field.h b/chromium/components/autofill_assistant/browser/actions/fallback_handler/required_field.h
index 97026813ae4..f5713c2ff03 100644
--- a/chromium/components/autofill_assistant/browser/actions/fallback_handler/required_field.h
+++ b/chromium/components/autofill_assistant/browser/actions/fallback_handler/required_field.h
@@ -16,12 +16,25 @@ struct RequiredField {
public:
enum FieldValueStatus { UNKNOWN, EMPTY, NOT_EMPTY };
- RequiredField();
~RequiredField();
+ RequiredField();
RequiredField(const RequiredField& copy);
- void FromProto(const UseAddressProto::RequiredField& proto);
- void FromProto(const UseCreditCardProto::RequiredField& proto);
+ template <typename T>
+ void FromProto(const T& required_field_proto) {
+ selector = Selector(required_field_proto.element());
+ value_expression = required_field_proto.value_expression();
+ forced = required_field_proto.forced();
+ fill_strategy = required_field_proto.fill_strategy();
+ delay_in_millisecond = required_field_proto.delay_in_millisecond();
+ select_strategy = required_field_proto.select_strategy();
+
+ if (required_field_proto.has_option_element_to_click()) {
+ fallback_click_element =
+ Selector(required_field_proto.option_element_to_click());
+ click_type = required_field_proto.click_type();
+ }
+ }
// The selector of the field that must be filled.
Selector selector;
@@ -60,7 +73,7 @@ struct RequiredField {
ClickType click_type = ClickType::NOT_SET;
// Returns true if fallback is required for this field.
- bool ShouldFallback(bool has_fallback_data) const;
+ bool ShouldFallback(bool apply_fallback) const;
};
} // 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 bf8eeaab150..1b127e12dd1 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
@@ -13,6 +13,7 @@
#include "components/autofill_assistant/browser/actions/action_delegate.h"
#include "components/autofill_assistant/browser/batch_element_checker.h"
#include "components/autofill_assistant/browser/client_status.h"
+#include "components/autofill_assistant/browser/field_formatter.h"
namespace autofill_assistant {
namespace {
@@ -22,11 +23,12 @@ const char kSelectElementTag[] = "SELECT";
AutofillErrorInfoProto::AutofillFieldError* AddAutofillError(
const RequiredField& required_field,
ClientStatus* client_status) {
+ client_status->set_proto_status(
+ ProcessedActionStatusProto::AUTOFILL_INCOMPLETE);
auto* field_error = client_status->mutable_details()
->mutable_autofill_error_info()
->add_autofill_field_error();
- *field_error->mutable_field() =
- required_field.selector.ToElementReferenceProto();
+ *field_error->mutable_field() = required_field.selector.proto;
field_error->set_value_expression(required_field.value_expression);
return field_error;
}
@@ -45,25 +47,35 @@ void FillStatusDetailsWithError(const RequiredField& required_field,
field_error->set_status(error_status);
}
-} // namespace
+void FillStatusDetailsWithEmptyField(const RequiredField& required_field,
+ ClientStatus* client_status) {
+ auto* field_error = AddAutofillError(required_field, client_status);
+ field_error->set_empty_after_fallback(true);
+}
-RequiredFieldsFallbackHandler::RequiredFieldsFallbackHandler(
- const std::vector<RequiredField>& required_fields,
- ActionDelegate* action_delegate) {
- required_fields_.assign(required_fields.begin(), required_fields.end());
- action_delegate_ = action_delegate;
+void FillStatusDetailsWithNotClearedField(const RequiredField& required_field,
+ ClientStatus* client_status) {
+ auto* field_error = AddAutofillError(required_field, client_status);
+ field_error->set_filled_after_clear(true);
}
+} // namespace
+
RequiredFieldsFallbackHandler::~RequiredFieldsFallbackHandler() = default;
+RequiredFieldsFallbackHandler::RequiredFieldsFallbackHandler(
+ const std::vector<RequiredField>& required_fields,
+ const std::map<std::string, std::string>& fallback_values,
+ ActionDelegate* delegate)
+ : required_fields_(required_fields),
+ fallback_values_(fallback_values),
+ action_delegate_(delegate) {}
+
void RequiredFieldsFallbackHandler::CheckAndFallbackRequiredFields(
const ClientStatus& initial_autofill_status,
- std::unique_ptr<FallbackData> fallback_data,
base::OnceCallback<void(const ClientStatus&,
const base::Optional<ClientStatus>&)>
status_update_callback) {
- DCHECK(fallback_data != nullptr);
-
client_status_ = initial_autofill_status;
status_update_callback_ = std::move(status_update_callback);
@@ -78,18 +90,18 @@ void RequiredFieldsFallbackHandler::CheckAndFallbackRequiredFields(
return;
}
- CheckAllRequiredFields(std::move(fallback_data));
+ CheckAllRequiredFields(/* apply_fallback = */ true);
}
void RequiredFieldsFallbackHandler::CheckAllRequiredFields(
- std::unique_ptr<FallbackData> fallback_data) {
+ bool apply_fallback) {
DCHECK(!batch_element_checker_);
batch_element_checker_ = std::make_unique<BatchElementChecker>();
for (size_t i = 0; i < required_fields_.size(); i++) {
- // First run (with fallback data) we skip checking forced fields, since
- // we overwrite them anyway. Second run (without fallback data) forced
- // fields should be checked.
- if (required_fields_[i].forced && fallback_data != nullptr) {
+ // First run (with fallback) we skip checking forced fields, since we
+ // overwrite them anyway. Second run (without fallback) forced fields should
+ // be checked.
+ if (required_fields_[i].forced && apply_fallback) {
continue;
}
@@ -109,7 +121,7 @@ void RequiredFieldsFallbackHandler::CheckAllRequiredFields(
batch_element_checker_->AddAllDoneCallback(
base::BindOnce(&RequiredFieldsFallbackHandler::OnCheckRequiredFieldsDone,
- weak_ptr_factory_.GetWeakPtr(), std::move(fallback_data)));
+ weak_ptr_factory_.GetWeakPtr(), apply_fallback));
action_delegate_->RunElementChecks(batch_element_checker_.get());
}
@@ -122,15 +134,26 @@ void RequiredFieldsFallbackHandler::OnGetRequiredFieldValue(
}
void RequiredFieldsFallbackHandler::OnCheckRequiredFieldsDone(
- std::unique_ptr<FallbackData> fallback_data) {
+ bool apply_fallback) {
batch_element_checker_.reset();
// We process all fields with an empty value in order to perform the fallback
// on all those fields, if any.
bool should_fallback = false;
for (const RequiredField& required_field : required_fields_) {
- if (required_field.ShouldFallback(fallback_data != nullptr)) {
+ if (required_field.ShouldFallback(apply_fallback)) {
should_fallback = true;
+ if (!apply_fallback) {
+ if (required_field.value_expression.empty()) {
+ VLOG(1) << "Field was filled after attempting to clear it: "
+ << required_field.selector;
+ FillStatusDetailsWithNotClearedField(required_field, &client_status_);
+ } else {
+ VLOG(1) << "Field was empty after applying fallback: "
+ << required_field.selector;
+ FillStatusDetailsWithEmptyField(required_field, &client_status_);
+ }
+ }
break;
}
}
@@ -141,7 +164,7 @@ void RequiredFieldsFallbackHandler::OnCheckRequiredFieldsDone(
return;
}
- if (fallback_data == nullptr) {
+ if (!apply_fallback) {
// Validation failed and we don't want to try the fallback.
std::move(status_update_callback_)
.Run(ClientStatus(AUTOFILL_INCOMPLETE), client_status_);
@@ -152,14 +175,19 @@ void RequiredFieldsFallbackHandler::OnCheckRequiredFieldsDone(
// immediately.
bool has_fallbacks = false;
for (const RequiredField& required_field : required_fields_) {
- if (!required_field.ShouldFallback(/* has_fallback_data= */ true)) {
+ if (!required_field.ShouldFallback(/* apply_fallback= */ true)) {
continue;
}
- if (fallback_data->GetValueByExpression(required_field.value_expression)
- .has_value()) {
+ if (required_field.value_expression.empty()) {
+ has_fallbacks = true;
+ } else if (field_formatter::FormatString(required_field.value_expression,
+ fallback_values_)
+ .has_value()) {
has_fallbacks = true;
} else {
+ VLOG(3) << "Field has no fallback data: " << required_field.selector
+ << " " << required_field.value_expression;
FillStatusDetailsWithMissingFallbackData(required_field, &client_status_);
}
}
@@ -170,16 +198,15 @@ void RequiredFieldsFallbackHandler::OnCheckRequiredFieldsDone(
}
// Set the fallback values and check again.
- SetFallbackFieldValuesSequentially(0, std::move(fallback_data));
+ SetFallbackFieldValuesSequentially(0);
}
void RequiredFieldsFallbackHandler::SetFallbackFieldValuesSequentially(
- size_t required_fields_index,
- std::unique_ptr<FallbackData> fallback_data) {
+ size_t required_fields_index) {
// Skip non-empty fields.
while (required_fields_index < required_fields_.size() &&
!required_fields_[required_fields_index].ShouldFallback(
- fallback_data != nullptr)) {
+ /* apply_fallback= */ true)) {
required_fields_index++;
}
@@ -188,18 +215,29 @@ void RequiredFieldsFallbackHandler::SetFallbackFieldValuesSequentially(
if (required_fields_index >= required_fields_.size()) {
DCHECK_EQ(required_fields_index, required_fields_.size());
- return CheckAllRequiredFields(/* fallback_data= */ nullptr);
+ CheckAllRequiredFields(/* apply_fallback= */ false);
+ return;
}
- // Set the next field to its fallback value.
+ // Treat the next field.
const RequiredField& required_field = required_fields_[required_fields_index];
- auto fallback_value =
- fallback_data->GetValueByExpression(required_field.value_expression);
+
+ if (required_field.value_expression.empty()) {
+ action_delegate_->SetFieldValue(
+ required_field.selector, "", required_field.fill_strategy,
+ required_field.delay_in_millisecond,
+ base::BindOnce(&RequiredFieldsFallbackHandler::OnSetFallbackFieldValue,
+ weak_ptr_factory_.GetWeakPtr(), required_fields_index));
+ return;
+ }
+
+ auto fallback_value = field_formatter::FormatString(
+ required_field.value_expression, fallback_values_);
if (!fallback_value.has_value()) {
VLOG(3) << "No fallback for " << required_field.selector;
// If there is no fallback value, we skip this failed field.
- return SetFallbackFieldValuesSequentially(++required_fields_index,
- std::move(fallback_data));
+ SetFallbackFieldValuesSequentially(++required_fields_index);
+ return;
}
if (required_field.fallback_click_element.has_value()) {
@@ -214,7 +252,7 @@ void RequiredFieldsFallbackHandler::SetFallbackFieldValuesSequentially(
base::BindOnce(
&RequiredFieldsFallbackHandler::OnClickOrTapFallbackElement,
weak_ptr_factory_.GetWeakPtr(), fallback_value.value(),
- required_fields_index, std::move(fallback_data)));
+ required_fields_index));
return;
}
@@ -223,13 +261,12 @@ void RequiredFieldsFallbackHandler::SetFallbackFieldValuesSequentially(
required_field.selector,
base::BindOnce(&RequiredFieldsFallbackHandler::OnGetFallbackFieldTag,
weak_ptr_factory_.GetWeakPtr(), fallback_value.value(),
- required_fields_index, std::move(fallback_data)));
+ required_fields_index));
}
void RequiredFieldsFallbackHandler::OnGetFallbackFieldTag(
const std::string& value,
size_t required_fields_index,
- std::unique_ptr<FallbackData> fallback_data,
const ClientStatus& element_tag_status,
const std::string& element_tag) {
if (!element_tag_status.ok()) {
@@ -252,8 +289,7 @@ void RequiredFieldsFallbackHandler::OnGetFallbackFieldTag(
action_delegate_->SelectOption(
required_field.selector, value, select_strategy,
base::BindOnce(&RequiredFieldsFallbackHandler::OnSetFallbackFieldValue,
- weak_ptr_factory_.GetWeakPtr(), required_fields_index,
- std::move(fallback_data)));
+ weak_ptr_factory_.GetWeakPtr(), required_fields_index));
return;
}
@@ -261,14 +297,12 @@ void RequiredFieldsFallbackHandler::OnGetFallbackFieldTag(
required_field.selector, value, required_field.fill_strategy,
required_field.delay_in_millisecond,
base::BindOnce(&RequiredFieldsFallbackHandler::OnSetFallbackFieldValue,
- weak_ptr_factory_.GetWeakPtr(), required_fields_index,
- std::move(fallback_data)));
+ weak_ptr_factory_.GetWeakPtr(), required_fields_index));
}
void RequiredFieldsFallbackHandler::OnClickOrTapFallbackElement(
const std::string& value,
size_t required_fields_index,
- std::unique_ptr<FallbackData> fallback_data,
const ClientStatus& element_click_status) {
const RequiredField& required_field = required_fields_[required_fields_index];
if (!element_click_status.ok()) {
@@ -283,21 +317,20 @@ void RequiredFieldsFallbackHandler::OnClickOrTapFallbackElement(
DCHECK(required_field.fallback_click_element.has_value());
Selector value_selector = required_field.fallback_click_element.value();
- value_selector.inner_text_pattern = value;
- value_selector.must_be_visible = true;
+ value_selector.MatchingInnerText(value).MustBeVisible();
DVLOG(3) << "Finding option for " << required_field.selector;
action_delegate_->ShortWaitForElement(
value_selector,
base::BindOnce(&RequiredFieldsFallbackHandler::OnShortWaitForElement,
weak_ptr_factory_.GetWeakPtr(), value_selector,
- required_fields_index, std::move(fallback_data)));
+ required_fields_index));
}
void RequiredFieldsFallbackHandler::OnShortWaitForElement(
const Selector& selector_to_click,
size_t required_fields_index,
- std::unique_ptr<FallbackData> fallback_data,
+
const ClientStatus& find_element_status) {
const RequiredField& required_field = required_fields_[required_fields_index];
if (!find_element_status.ok()) {
@@ -319,15 +352,16 @@ void RequiredFieldsFallbackHandler::OnShortWaitForElement(
action_delegate_->ClickOrTapElement(
selector_to_click, click_type,
base::BindOnce(&RequiredFieldsFallbackHandler::OnSetFallbackFieldValue,
- weak_ptr_factory_.GetWeakPtr(), required_fields_index,
- std::move(fallback_data)));
+ weak_ptr_factory_.GetWeakPtr(), required_fields_index));
}
void RequiredFieldsFallbackHandler::OnSetFallbackFieldValue(
size_t required_fields_index,
- std::unique_ptr<FallbackData> fallback_data,
const ClientStatus& set_field_status) {
if (!set_field_status.ok()) {
+ VLOG(1) << "Error setting value for required_field: "
+ << required_fields_[required_fields_index].selector << " "
+ << set_field_status;
FillStatusDetailsWithError(required_fields_[required_fields_index],
set_field_status.proto_status(),
&client_status_);
@@ -338,8 +372,7 @@ void RequiredFieldsFallbackHandler::OnSetFallbackFieldValue(
return;
}
- SetFallbackFieldValuesSequentially(++required_fields_index,
- std::move(fallback_data));
+ SetFallbackFieldValuesSequentially(++required_fields_index);
}
} // namespace autofill_assistant
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 9707612510d..23637adc736 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
@@ -16,20 +16,21 @@
#include "base/optional.h"
#include "components/autofill/core/browser/data_model/autofill_profile.h"
#include "components/autofill_assistant/browser/actions/action.h"
-#include "components/autofill_assistant/browser/actions/fallback_handler/fallback_data.h"
#include "components/autofill_assistant/browser/actions/fallback_handler/required_field.h"
#include "components/autofill_assistant/browser/batch_element_checker.h"
namespace autofill_assistant {
class ClientStatus;
-// A handler for required fields and fallback values, used for example in
-// UseAddressAction.
+// A handler for required fields and fallback values, used by UseAddressAction
+// and UseCreditCardAction.
class RequiredFieldsFallbackHandler {
public:
explicit RequiredFieldsFallbackHandler(
const std::vector<RequiredField>& required_fields,
+ const std::map<std::string, std::string>& fallback_values,
ActionDelegate* delegate);
+
~RequiredFieldsFallbackHandler();
// Check if there are required fields. If so, verify them and fallback if
@@ -37,23 +38,20 @@ class RequiredFieldsFallbackHandler {
// action.
void CheckAndFallbackRequiredFields(
const ClientStatus& initial_autofill_status,
- std::unique_ptr<FallbackData> fallback_data,
base::OnceCallback<void(const ClientStatus&,
const base::Optional<ClientStatus>&)>
status_update_callback);
private:
// Check whether all required fields have a non-empty value. If it is the
- // case, update the status to success. If it's not and |fallback_data|
- // is null, update the status to failure. If |fallback_data| is non-null, use
- // it to attempt to fill the failed fields without Autofill.
- void CheckAllRequiredFields(std::unique_ptr<FallbackData> fallback_data);
+ // case, update the status to success. If it's not and |apply_fallback|
+ // is false, update the status to failure. If |apply_fallback| is true,
+ // attempt to fill the failed fields without Autofill using fallback values.
+ void CheckAllRequiredFields(bool apply_fallback);
// Triggers the check for a specific field.
- void CheckRequiredFieldsSequentially(
- bool allow_fallback,
- size_t required_fields_index,
- std::unique_ptr<FallbackData> fallback_data);
+ void CheckRequiredFieldsSequentially(bool allow_fallback,
+ size_t required_fields_index);
// Updates the status of the required field.
void OnGetRequiredFieldValue(size_t required_fields_index,
@@ -61,40 +59,35 @@ class RequiredFieldsFallbackHandler {
const std::string& value);
// Called when all required fields have been checked.
- void OnCheckRequiredFieldsDone(std::unique_ptr<FallbackData> fallback_data);
+ void OnCheckRequiredFieldsDone(bool apply_fallback);
// Sets fallback field values for empty fields.
- void SetFallbackFieldValuesSequentially(
- size_t required_fields_index,
- std::unique_ptr<FallbackData> fallback_data);
+ void SetFallbackFieldValuesSequentially(size_t required_fields_index);
// Called after retrieving tag name from a field.
void OnGetFallbackFieldTag(const std::string& value,
size_t required_fields_index,
- std::unique_ptr<FallbackData> fallback_data,
const ClientStatus& element_tag_status,
const std::string& element_tag);
// Called after clicking a fallback element.
void OnClickOrTapFallbackElement(const std::string& value,
size_t required_fields_index,
- std::unique_ptr<FallbackData> fallback_data,
const ClientStatus& element_click_status);
// Called after waiting for option element to appear before clicking it.
void OnShortWaitForElement(const Selector& selector_to_click,
size_t required_fields_index,
- std::unique_ptr<FallbackData> fallback_data,
const ClientStatus& find_element_status);
// Called after trying to set form values without Autofill in case of
// fallback after failed validation.
void OnSetFallbackFieldValue(size_t required_fields_index,
- std::unique_ptr<FallbackData> fallback_data,
const ClientStatus& status);
ClientStatus client_status_;
std::vector<RequiredField> required_fields_;
+ std::map<std::string, std::string> fallback_values_;
base::OnceCallback<void(const ClientStatus&,
const base::Optional<ClientStatus>&)>
status_update_callback_;
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 62c9c65a8e4..863135de8b9 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
@@ -12,10 +12,10 @@
#include "base/test/mock_callback.h"
#include "components/autofill/core/browser/autofill_test_utils.h"
#include "components/autofill/core/browser/data_model/autofill_profile.h"
-#include "components/autofill_assistant/browser/actions/fallback_handler/fallback_data.h"
#include "components/autofill_assistant/browser/actions/fallback_handler/required_field.h"
#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/mock_web_controller.h"
#include "testing/gmock/include/gmock/gmock.h"
@@ -57,12 +57,9 @@ class RequiredFieldsFallbackHandlerTest : public testing::Test {
TEST_F(RequiredFieldsFallbackHandlerTest,
AutofillFailureExitsEarlyForEmptyRequiredFields) {
- std::vector<RequiredField> fields;
-
- auto fallback_data = std::make_unique<FallbackData>();
-
- RequiredFieldsFallbackHandler fallback_handler(fields,
- &mock_action_delegate_);
+ RequiredFieldsFallbackHandler fallback_handler(
+ /* required_fields = */ {},
+ /* fallback_values = */ {}, &mock_action_delegate_);
base::OnceCallback<void(const ClientStatus&,
const base::Optional<ClientStatus>&)>
@@ -74,30 +71,30 @@ TEST_F(RequiredFieldsFallbackHandlerTest,
});
fallback_handler.CheckAndFallbackRequiredFields(
- ClientStatus(OTHER_ACTION_STATUS), std::move(fallback_data),
- std::move(callback));
+ ClientStatus(OTHER_ACTION_STATUS), std::move(callback));
}
TEST_F(RequiredFieldsFallbackHandlerTest,
- AddsMissingOrEmptyFallbackFieldToError) {
+ AddsMissingOrEmptyFallbackValuesToError) {
ON_CALL(mock_web_controller_, OnGetFieldValue(_, _))
.WillByDefault(RunOnceCallback<1>(OkClientStatus(), ""));
- std::vector<RequiredField> fields;
- fields.emplace_back(CreateRequiredField("51", {"#card_name"}));
- fields.emplace_back(CreateRequiredField("52", {"#card_number"}));
- fields.emplace_back(CreateRequiredField("-3", {"#card_network"}));
+ std::vector<RequiredField> required_fields = {
+ CreateRequiredField("${51}", {"#card_name"}),
+ CreateRequiredField("${52}", {"#card_number"}),
+ CreateRequiredField("${-3}", {"#card_network"})};
- auto fallback_data = std::make_unique<FallbackData>();
- fallback_data->field_values.emplace(
- static_cast<int>(autofill::ServerFieldType::CREDIT_CARD_NAME_FULL),
+ std::map<std::string, std::string> fallback_values;
+ fallback_values.emplace(
+ base::NumberToString(
+ static_cast<int>(autofill::ServerFieldType::CREDIT_CARD_NAME_FULL)),
"John Doe");
- fallback_data->field_values.emplace(
- static_cast<int>(UseCreditCardProto::RequiredField::CREDIT_CARD_NETWORK),
- "");
+ fallback_values.emplace(base::NumberToString(static_cast<int>(
+ AutofillFormatProto::CREDIT_CARD_NETWORK)),
+ "");
- RequiredFieldsFallbackHandler fallback_handler(fields,
- &mock_action_delegate_);
+ RequiredFieldsFallbackHandler fallback_handler(
+ required_fields, fallback_values, &mock_action_delegate_);
base::OnceCallback<void(const ClientStatus&,
const base::Optional<ClientStatus>&)>
@@ -106,17 +103,19 @@ TEST_F(RequiredFieldsFallbackHandlerTest,
const base::Optional<ClientStatus>& detail_status) {
EXPECT_EQ(status.proto_status(), AUTOFILL_INCOMPLETE);
ASSERT_TRUE(detail_status.has_value());
+ ASSERT_EQ(detail_status.value().proto_status(),
+ AUTOFILL_INCOMPLETE);
ASSERT_EQ(detail_status.value()
.details()
.autofill_error_info()
.autofill_field_error_size(),
- 2);
+ 3);
EXPECT_EQ(detail_status.value()
.details()
.autofill_error_info()
.autofill_field_error(0)
.value_expression(),
- "52");
+ "${52}");
EXPECT_TRUE(detail_status.value()
.details()
.autofill_error_info()
@@ -127,16 +126,27 @@ TEST_F(RequiredFieldsFallbackHandlerTest,
.autofill_error_info()
.autofill_field_error(1)
.value_expression(),
- "-3");
+ "${-3}");
EXPECT_TRUE(detail_status.value()
.details()
.autofill_error_info()
.autofill_field_error(1)
.no_fallback_value());
+ EXPECT_EQ(detail_status.value()
+ .details()
+ .autofill_error_info()
+ .autofill_field_error(2)
+ .value_expression(),
+ "${51}");
+ EXPECT_TRUE(detail_status.value()
+ .details()
+ .autofill_error_info()
+ .autofill_field_error(2)
+ .empty_after_fallback());
});
- fallback_handler.CheckAndFallbackRequiredFields(
- OkClientStatus(), std::move(fallback_data), std::move(callback));
+ fallback_handler.CheckAndFallbackRequiredFields(OkClientStatus(),
+ std::move(callback));
}
TEST_F(RequiredFieldsFallbackHandlerTest, AddsFirstFieldFillingError) {
@@ -145,20 +155,21 @@ TEST_F(RequiredFieldsFallbackHandlerTest, AddsFirstFieldFillingError) {
ON_CALL(mock_action_delegate_, OnSetFieldValue(_, _, _))
.WillByDefault(RunOnceCallback<2>(ClientStatus(OTHER_ACTION_STATUS)));
- std::vector<RequiredField> fields;
- fields.emplace_back(CreateRequiredField("51", {"#card_name"}));
- fields.emplace_back(CreateRequiredField("52", {"#card_number"}));
+ std::vector<RequiredField> required_fields = {
+ CreateRequiredField("${51}", {"#card_name"}),
+ CreateRequiredField("${52}", {"#card_number"})};
- auto fallback_data = std::make_unique<FallbackData>();
- fallback_data->field_values.emplace(
- static_cast<int>(autofill::ServerFieldType::CREDIT_CARD_NAME_FULL),
+ std::map<std::string, std::string> fallback_values;
+ fallback_values.emplace(
+ base::NumberToString(
+ static_cast<int>(autofill::ServerFieldType::CREDIT_CARD_NAME_FULL)),
"John Doe");
- fallback_data->field_values.emplace(
- static_cast<int>(autofill::ServerFieldType::CREDIT_CARD_NUMBER),
- "4111111111111111");
+ fallback_values.emplace(base::NumberToString(static_cast<int>(
+ autofill::ServerFieldType::CREDIT_CARD_NUMBER)),
+ "4111111111111111");
- RequiredFieldsFallbackHandler fallback_handler(fields,
- &mock_action_delegate_);
+ RequiredFieldsFallbackHandler fallback_handler(
+ required_fields, fallback_values, &mock_action_delegate_);
base::OnceCallback<void(const ClientStatus&,
const base::Optional<ClientStatus>&)>
@@ -167,6 +178,8 @@ TEST_F(RequiredFieldsFallbackHandlerTest, AddsFirstFieldFillingError) {
const base::Optional<ClientStatus>& detail_status) {
EXPECT_EQ(status.proto_status(), AUTOFILL_INCOMPLETE);
ASSERT_TRUE(detail_status.has_value());
+ ASSERT_EQ(detail_status.value().proto_status(),
+ AUTOFILL_INCOMPLETE);
ASSERT_EQ(detail_status.value()
.details()
.autofill_error_info()
@@ -177,7 +190,7 @@ TEST_F(RequiredFieldsFallbackHandlerTest, AddsFirstFieldFillingError) {
.autofill_error_info()
.autofill_field_error(0)
.value_expression(),
- "51");
+ "${51}");
EXPECT_EQ(detail_status.value()
.details()
.autofill_error_info()
@@ -186,8 +199,60 @@ TEST_F(RequiredFieldsFallbackHandlerTest, AddsFirstFieldFillingError) {
OTHER_ACTION_STATUS);
});
- fallback_handler.CheckAndFallbackRequiredFields(
- OkClientStatus(), std::move(fallback_data), std::move(callback));
+ fallback_handler.CheckAndFallbackRequiredFields(OkClientStatus(),
+ std::move(callback));
+}
+
+TEST_F(RequiredFieldsFallbackHandlerTest,
+ AddsFirstEmptyFieldAfterFillingToError) {
+ ON_CALL(mock_web_controller_, OnGetFieldValue(_, _))
+ .WillByDefault(RunOnceCallback<1>(OkClientStatus(), ""));
+
+ std::vector<RequiredField> required_fields = {
+ CreateRequiredField("${51}", {"#card_name"}),
+ CreateRequiredField("${52}", {"#card_number"})};
+
+ std::map<std::string, std::string> fallback_values;
+ fallback_values.emplace(
+ base::NumberToString(
+ static_cast<int>(autofill::ServerFieldType::CREDIT_CARD_NAME_FULL)),
+ "John Doe");
+ fallback_values.emplace(base::NumberToString(static_cast<int>(
+ autofill::ServerFieldType::CREDIT_CARD_NUMBER)),
+ "4111111111111111");
+
+ RequiredFieldsFallbackHandler fallback_handler(
+ required_fields, fallback_values, &mock_action_delegate_);
+
+ base::OnceCallback<void(const ClientStatus&,
+ const base::Optional<ClientStatus>&)>
+ callback =
+ base::BindOnce([](const ClientStatus& status,
+ const base::Optional<ClientStatus>& detail_status) {
+ EXPECT_EQ(status.proto_status(), AUTOFILL_INCOMPLETE);
+ ASSERT_TRUE(detail_status.has_value());
+ ASSERT_EQ(detail_status.value().proto_status(),
+ AUTOFILL_INCOMPLETE);
+ ASSERT_EQ(detail_status.value()
+ .details()
+ .autofill_error_info()
+ .autofill_field_error_size(),
+ 1);
+ EXPECT_EQ(detail_status.value()
+ .details()
+ .autofill_error_info()
+ .autofill_field_error(0)
+ .value_expression(),
+ "${51}");
+ EXPECT_TRUE(detail_status.value()
+ .details()
+ .autofill_error_info()
+ .autofill_field_error(0)
+ .empty_after_fallback());
+ });
+
+ fallback_handler.CheckAndFallbackRequiredFields(OkClientStatus(),
+ std::move(callback));
}
TEST_F(RequiredFieldsFallbackHandlerTest, DoesNotFallbackIfFieldsAreFilled) {
@@ -195,12 +260,10 @@ TEST_F(RequiredFieldsFallbackHandlerTest, DoesNotFallbackIfFieldsAreFilled) {
.WillByDefault(RunOnceCallback<1>(OkClientStatus(), "value"));
EXPECT_CALL(mock_action_delegate_, OnSetFieldValue(_, _, _)).Times(0);
- std::vector<RequiredField> fields;
- fields.emplace_back(CreateRequiredField("51", {"#card_name"}));
-
- auto fallback_data = std::make_unique<FallbackData>();
+ std::vector<RequiredField> required_fields = {
+ CreateRequiredField("${51}", {"#card_name"})};
- RequiredFieldsFallbackHandler fallback_handler(fields,
+ RequiredFieldsFallbackHandler fallback_handler(required_fields, {},
&mock_action_delegate_);
base::OnceCallback<void(const ClientStatus&,
@@ -211,8 +274,8 @@ TEST_F(RequiredFieldsFallbackHandlerTest, DoesNotFallbackIfFieldsAreFilled) {
EXPECT_EQ(status.proto_status(), ACTION_APPLIED);
});
- fallback_handler.CheckAndFallbackRequiredFields(
- OkClientStatus(), std::move(fallback_data), std::move(callback));
+ fallback_handler.CheckAndFallbackRequiredFields(OkClientStatus(),
+ std::move(callback));
}
TEST_F(RequiredFieldsFallbackHandlerTest, FillsEmptyRequiredField) {
@@ -226,16 +289,17 @@ TEST_F(RequiredFieldsFallbackHandlerTest, FillsEmptyRequiredField) {
.After(set_value)
.WillOnce(RunOnceCallback<1>(OkClientStatus(), "John Doe"));
- std::vector<RequiredField> fields;
- fields.emplace_back(CreateRequiredField("51", {"#card_name"}));
+ std::vector<RequiredField> required_fields = {
+ CreateRequiredField("${51}", {"#card_name"})};
- auto fallback_data = std::make_unique<FallbackData>();
- fallback_data->field_values.emplace(
- static_cast<int>(autofill::ServerFieldType::CREDIT_CARD_NAME_FULL),
+ std::map<std::string, std::string> fallback_values;
+ fallback_values.emplace(
+ base::NumberToString(
+ static_cast<int>(autofill::ServerFieldType::CREDIT_CARD_NAME_FULL)),
"John Doe");
- RequiredFieldsFallbackHandler fallback_handler(fields,
- &mock_action_delegate_);
+ RequiredFieldsFallbackHandler fallback_handler(
+ required_fields, fallback_values, &mock_action_delegate_);
base::OnceCallback<void(const ClientStatus&,
const base::Optional<ClientStatus>&)>
@@ -245,8 +309,8 @@ TEST_F(RequiredFieldsFallbackHandlerTest, FillsEmptyRequiredField) {
EXPECT_EQ(status.proto_status(), ACTION_APPLIED);
});
- fallback_handler.CheckAndFallbackRequiredFields(
- OkClientStatus(), std::move(fallback_data), std::move(callback));
+ fallback_handler.CheckAndFallbackRequiredFields(OkClientStatus(),
+ std::move(callback));
}
TEST_F(RequiredFieldsFallbackHandlerTest, FallsBackForForcedFilledField) {
@@ -256,18 +320,18 @@ TEST_F(RequiredFieldsFallbackHandlerTest, FallsBackForForcedFilledField) {
OnSetFieldValue(Eq(Selector({"#card_name"})), "John Doe", _))
.WillOnce(RunOnceCallback<2>(OkClientStatus()));
- std::vector<RequiredField> fields;
- auto field = CreateRequiredField("51", {"#card_name"});
- field.forced = true;
- fields.emplace_back(field);
+ std::vector<RequiredField> required_fields = {
+ CreateRequiredField("${51}", {"#card_name"})};
+ required_fields[0].forced = true;
- auto fallback_data = std::make_unique<FallbackData>();
- fallback_data->field_values.emplace(
- static_cast<int>(autofill::ServerFieldType::CREDIT_CARD_NAME_FULL),
+ std::map<std::string, std::string> fallback_values;
+ fallback_values.emplace(
+ base::NumberToString(
+ static_cast<int>(autofill::ServerFieldType::CREDIT_CARD_NAME_FULL)),
"John Doe");
- RequiredFieldsFallbackHandler fallback_handler(fields,
- &mock_action_delegate_);
+ RequiredFieldsFallbackHandler fallback_handler(
+ required_fields, fallback_values, &mock_action_delegate_);
base::OnceCallback<void(const ClientStatus&,
const base::Optional<ClientStatus>&)>
@@ -277,8 +341,8 @@ TEST_F(RequiredFieldsFallbackHandlerTest, FallsBackForForcedFilledField) {
EXPECT_EQ(status.proto_status(), ACTION_APPLIED);
});
- fallback_handler.CheckAndFallbackRequiredFields(
- OkClientStatus(), std::move(fallback_data), std::move(callback));
+ fallback_handler.CheckAndFallbackRequiredFields(OkClientStatus(),
+ std::move(callback));
}
TEST_F(RequiredFieldsFallbackHandlerTest, FailsIfForcedFieldDidNotGetFilled) {
@@ -286,14 +350,11 @@ TEST_F(RequiredFieldsFallbackHandlerTest, FailsIfForcedFieldDidNotGetFilled) {
.WillByDefault(RunOnceCallback<1>(OkClientStatus(), "value"));
EXPECT_CALL(mock_action_delegate_, OnSetFieldValue(_, _, _)).Times(0);
- std::vector<RequiredField> fields;
- auto field = CreateRequiredField("51", {"#card_name"});
- field.forced = true;
- fields.emplace_back(field);
+ std::vector<RequiredField> required_fields = {
+ CreateRequiredField("${51}", {"#card_name"})};
+ required_fields[0].forced = true;
- auto fallback_data = std::make_unique<FallbackData>();
-
- RequiredFieldsFallbackHandler fallback_handler(fields,
+ RequiredFieldsFallbackHandler fallback_handler(required_fields, {},
&mock_action_delegate_);
base::OnceCallback<void(const ClientStatus&,
@@ -303,6 +364,8 @@ TEST_F(RequiredFieldsFallbackHandlerTest, FailsIfForcedFieldDidNotGetFilled) {
const base::Optional<ClientStatus>& detail_status) {
EXPECT_EQ(status.proto_status(), AUTOFILL_INCOMPLETE);
ASSERT_TRUE(detail_status.has_value());
+ ASSERT_EQ(detail_status.value().proto_status(),
+ AUTOFILL_INCOMPLETE);
ASSERT_EQ(detail_status.value()
.details()
.autofill_error_info()
@@ -313,7 +376,7 @@ TEST_F(RequiredFieldsFallbackHandlerTest, FailsIfForcedFieldDidNotGetFilled) {
.autofill_error_info()
.autofill_field_error(0)
.value_expression(),
- "51");
+ "${51}");
EXPECT_TRUE(detail_status.value()
.details()
.autofill_error_info()
@@ -321,8 +384,8 @@ TEST_F(RequiredFieldsFallbackHandlerTest, FailsIfForcedFieldDidNotGetFilled) {
.no_fallback_value());
});
- fallback_handler.CheckAndFallbackRequiredFields(
- OkClientStatus(), std::move(fallback_data), std::move(callback));
+ fallback_handler.CheckAndFallbackRequiredFields(OkClientStatus(),
+ std::move(callback));
}
TEST_F(RequiredFieldsFallbackHandlerTest, FillsFieldWithPattern) {
@@ -336,18 +399,21 @@ TEST_F(RequiredFieldsFallbackHandlerTest, FillsFieldWithPattern) {
.After(set_value)
.WillOnce(RunOnceCallback<1>(OkClientStatus(), "not empty"));
- std::vector<RequiredField> fields;
- fields.emplace_back(CreateRequiredField("${53}/${55}", {"#card_expiry"}));
-
- auto fallback_data = std::make_unique<FallbackData>();
- fallback_data->field_values.emplace(
- static_cast<int>(autofill::ServerFieldType::CREDIT_CARD_EXP_MONTH), "08");
- fallback_data->field_values.emplace(
- static_cast<int>(autofill::ServerFieldType::CREDIT_CARD_EXP_4_DIGIT_YEAR),
+ std::vector<RequiredField> required_fields = {
+ CreateRequiredField("${53}/${55}", {"#card_expiry"})};
+
+ std::map<std::string, std::string> fallback_values;
+ fallback_values.emplace(
+ base::NumberToString(
+ static_cast<int>(autofill::ServerFieldType::CREDIT_CARD_EXP_MONTH)),
+ "08");
+ fallback_values.emplace(
+ base::NumberToString(static_cast<int>(
+ autofill::ServerFieldType::CREDIT_CARD_EXP_4_DIGIT_YEAR)),
"2050");
- RequiredFieldsFallbackHandler fallback_handler(fields,
- &mock_action_delegate_);
+ RequiredFieldsFallbackHandler fallback_handler(
+ required_fields, fallback_values, &mock_action_delegate_);
base::OnceCallback<void(const ClientStatus&,
const base::Optional<ClientStatus>&)>
@@ -357,8 +423,8 @@ TEST_F(RequiredFieldsFallbackHandlerTest, FillsFieldWithPattern) {
EXPECT_EQ(status.proto_status(), ACTION_APPLIED);
});
- fallback_handler.CheckAndFallbackRequiredFields(
- OkClientStatus(), std::move(fallback_data), std::move(callback));
+ fallback_handler.CheckAndFallbackRequiredFields(OkClientStatus(),
+ std::move(callback));
}
TEST_F(RequiredFieldsFallbackHandlerTest,
@@ -368,17 +434,17 @@ TEST_F(RequiredFieldsFallbackHandlerTest,
.WillRepeatedly(RunOnceCallback<1>(OkClientStatus(), ""));
EXPECT_CALL(mock_action_delegate_, OnSetFieldValue(_, _, _)).Times(0);
- std::vector<RequiredField> fields;
- fields.emplace_back(CreateRequiredField("53", {"#card_expiry"}));
- fields.emplace_back(CreateRequiredField("-3", {"#card_network"}));
+ std::vector<RequiredField> required_fields = {
+ CreateRequiredField("${53}", {"#card_expiry"}),
+ CreateRequiredField("${-3}", {"#card_network"})};
- auto fallback_data = std::make_unique<FallbackData>();
- fallback_data->field_values.emplace(
- static_cast<int>(UseCreditCardProto::RequiredField::CREDIT_CARD_NETWORK),
- "");
+ std::map<std::string, std::string> fallback_values;
+ fallback_values.emplace(base::NumberToString(static_cast<int>(
+ AutofillFormatProto::CREDIT_CARD_NETWORK)),
+ "");
- RequiredFieldsFallbackHandler fallback_handler(fields,
- &mock_action_delegate_);
+ RequiredFieldsFallbackHandler fallback_handler(
+ required_fields, fallback_values, &mock_action_delegate_);
base::OnceCallback<void(const ClientStatus&,
const base::Optional<ClientStatus>&)>
@@ -387,6 +453,8 @@ TEST_F(RequiredFieldsFallbackHandlerTest,
const base::Optional<ClientStatus>& detail_status) {
EXPECT_EQ(status.proto_status(), AUTOFILL_INCOMPLETE);
ASSERT_TRUE(detail_status.has_value());
+ ASSERT_EQ(detail_status.value().proto_status(),
+ AUTOFILL_INCOMPLETE);
ASSERT_EQ(detail_status.value()
.details()
.autofill_error_info()
@@ -397,7 +465,7 @@ TEST_F(RequiredFieldsFallbackHandlerTest,
.autofill_error_info()
.autofill_field_error(0)
.value_expression(),
- "53");
+ "${53}");
EXPECT_TRUE(detail_status.value()
.details()
.autofill_error_info()
@@ -408,7 +476,7 @@ TEST_F(RequiredFieldsFallbackHandlerTest,
.autofill_error_info()
.autofill_field_error(1)
.value_expression(),
- "-3");
+ "${-3}");
EXPECT_TRUE(detail_status.value()
.details()
.autofill_error_info()
@@ -416,114 +484,8 @@ TEST_F(RequiredFieldsFallbackHandlerTest,
.no_fallback_value());
});
- fallback_handler.CheckAndFallbackRequiredFields(
- OkClientStatus(), std::move(fallback_data), std::move(callback));
-}
-
-TEST_F(RequiredFieldsFallbackHandlerTest, IgnoresNonIntegerKeys) {
- EXPECT_CALL(mock_web_controller_, OnGetFieldValue(_, _))
- .WillOnce(RunOnceCallback<1>(OkClientStatus(), ""));
- Expectation set_value =
- EXPECT_CALL(mock_action_delegate_,
- OnSetFieldValue(Eq(Selector({"#card_expiry"})), "${KEY}", _))
- .WillOnce(RunOnceCallback<2>(OkClientStatus()));
- EXPECT_CALL(mock_web_controller_, OnGetFieldValue(_, _))
- .After(set_value)
- .WillOnce(RunOnceCallback<1>(OkClientStatus(), "not empty"));
-
- std::vector<RequiredField> fields;
- fields.emplace_back(CreateRequiredField("${KEY}", {"#card_expiry"}));
-
- auto fallback_data = std::make_unique<FallbackData>();
-
- RequiredFieldsFallbackHandler fallback_handler(fields,
- &mock_action_delegate_);
-
- base::OnceCallback<void(const ClientStatus&,
- const base::Optional<ClientStatus>&)>
- callback =
- base::BindOnce([](const ClientStatus& status,
- const base::Optional<ClientStatus>& detail_status) {
- EXPECT_EQ(status.proto_status(), ACTION_APPLIED);
- });
-
- fallback_handler.CheckAndFallbackRequiredFields(
- OkClientStatus(), std::move(fallback_data), std::move(callback));
-}
-
-TEST_F(RequiredFieldsFallbackHandlerTest, AddsAllProfileFields) {
- std::unordered_map<int, std::string> expected_values = {
- {3, "Alpha"},
- {4, "Beta"},
- {5, "Gamma"},
- {6, "B"},
- {7, "Alpha Beta Gamma"},
- {9, "alpha@google.com"},
- {10, "1234567"},
- {11, "79"},
- {12, "41"},
- {13, "0791234567"},
- {14, "+41791234567"},
- {30, "Brandschenkestrasse 110"},
- {31, "Google Building 110"},
- {33, "Zurich"},
- {34, "Canton Zurich"},
- {35, "8002"},
- {36, "Switzerland"},
- {60, "Google"},
- {77, "Brandschenkestrasse 110\nGoogle Building 110"}};
-
- auto profile = std::make_unique<autofill::AutofillProfile>(
- base::GenerateGUID(), autofill::test::kEmptyOrigin);
- autofill::test::SetProfileInfo(
- profile.get(), "Alpha", "Beta", "Gamma", "alpha@google.com", "Google",
- "Brandschenkestrasse 110", "Google Building 110", "Zurich",
- "Canton Zurich", "8002", "CH", "+41791234567");
- FallbackData fallback_data;
- fallback_data.AddFormGroup(*profile);
-
- for (auto iter = expected_values.begin(); iter != expected_values.end();
- ++iter) {
- ASSERT_TRUE(
- fallback_data.GetValueByExpression(base::NumberToString(iter->first))
- .has_value());
- EXPECT_EQ(
- fallback_data.GetValueByExpression(base::NumberToString(iter->first))
- .value(),
- iter->second);
- }
-}
-
-TEST_F(RequiredFieldsFallbackHandlerTest, AddsAllCreditCardFields) {
- std::unordered_map<int, std::string> expected_values = {
- {51, "Alpha Beta Gamma"},
- {52, "4111111111111111"},
- {53, "08"},
- {54, "50"},
- {55, "2050"},
- {56, "08/50"},
- {57, "08/2050"},
- {58, "Visa"},
- {91, "Alpha"},
- {92, "Gamma"}};
-
- auto card = std::make_unique<autofill::CreditCard>(
- base::GenerateGUID(), autofill::test::kEmptyOrigin);
- autofill::test::SetCreditCardInfo(card.get(), "Alpha Beta Gamma",
- "4111111111111111", "8", "2050", "");
- FallbackData fallback_data;
- fallback_data.AddFormGroup(*card);
-
- for (auto iter = expected_values.begin(); iter != expected_values.end();
- ++iter) {
- ASSERT_TRUE(
- fallback_data.GetValueByExpression(base::NumberToString(iter->first))
- .has_value());
- EXPECT_EQ(
- fallback_data.GetValueByExpression(base::NumberToString(iter->first))
- .value(),
- iter->second);
- }
+ fallback_handler.CheckAndFallbackRequiredFields(OkClientStatus(),
+ std::move(callback));
}
TEST_F(RequiredFieldsFallbackHandlerTest, ClicksOnCustomDropdown) {
@@ -534,8 +496,8 @@ TEST_F(RequiredFieldsFallbackHandlerTest, ClicksOnCustomDropdown) {
ClickOrTapElement(Eq(Selector({"#card_expiry"})), ClickType::TAP, _))
.WillOnce(RunOnceCallback<2>(OkClientStatus()));
Selector expected_selector({".option"});
- expected_selector.must_be_visible = true;
- expected_selector.inner_text_pattern = "08";
+ expected_selector.MatchingInnerText("08");
+ expected_selector.MustBeVisible();
EXPECT_CALL(mock_action_delegate_,
OnShortWaitForElement(Eq(expected_selector), _))
.WillOnce(RunOnceCallback<1>(OkClientStatus()));
@@ -543,17 +505,18 @@ TEST_F(RequiredFieldsFallbackHandlerTest, ClicksOnCustomDropdown) {
ClickOrTapElement(Eq(expected_selector), ClickType::TAP, _))
.WillOnce(RunOnceCallback<2>(OkClientStatus()));
- std::vector<RequiredField> fields;
- RequiredField required_field = CreateRequiredField("53", {"#card_expiry"});
- required_field.fallback_click_element = Selector({".option"});
- fields.emplace_back(required_field);
+ std::vector<RequiredField> required_fields = {
+ CreateRequiredField("${53}", {"#card_expiry"})};
+ required_fields[0].fallback_click_element = Selector({".option"});
- auto fallback_data = std::make_unique<FallbackData>();
- fallback_data->field_values.emplace(
- static_cast<int>(autofill::ServerFieldType::CREDIT_CARD_EXP_MONTH), "08");
+ std::map<std::string, std::string> fallback_values;
+ fallback_values.emplace(
+ base::NumberToString(
+ static_cast<int>(autofill::ServerFieldType::CREDIT_CARD_EXP_MONTH)),
+ "08");
- RequiredFieldsFallbackHandler fallback_handler(fields,
- &mock_action_delegate_);
+ RequiredFieldsFallbackHandler fallback_handler(
+ required_fields, fallback_values, &mock_action_delegate_);
base::OnceCallback<void(const ClientStatus&,
const base::Optional<ClientStatus>&)>
@@ -563,8 +526,8 @@ TEST_F(RequiredFieldsFallbackHandlerTest, ClicksOnCustomDropdown) {
EXPECT_EQ(status.proto_status(), ACTION_APPLIED);
});
- fallback_handler.CheckAndFallbackRequiredFields(
- OkClientStatus(), std::move(fallback_data), std::move(callback));
+ fallback_handler.CheckAndFallbackRequiredFields(OkClientStatus(),
+ std::move(callback));
}
TEST_F(RequiredFieldsFallbackHandlerTest, CustomDropdownClicksStopOnError) {
@@ -575,8 +538,8 @@ TEST_F(RequiredFieldsFallbackHandlerTest, CustomDropdownClicksStopOnError) {
ClickOrTapElement(Eq(Selector({"#card_expiry"})), ClickType::TAP, _))
.WillOnce(RunOnceCallback<2>(OkClientStatus()));
Selector expected_selector({".option"});
- expected_selector.must_be_visible = true;
- expected_selector.inner_text_pattern = "08";
+ expected_selector.MatchingInnerText("08");
+ expected_selector.MustBeVisible();
EXPECT_CALL(mock_action_delegate_,
OnShortWaitForElement(Eq(expected_selector), _))
.WillOnce(RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED)));
@@ -584,17 +547,18 @@ TEST_F(RequiredFieldsFallbackHandlerTest, CustomDropdownClicksStopOnError) {
ClickOrTapElement(Eq(expected_selector), _, _))
.Times(0);
- std::vector<RequiredField> fields;
- RequiredField required_field = CreateRequiredField("53", {"#card_expiry"});
- required_field.fallback_click_element = Selector({".option"});
- fields.emplace_back(required_field);
+ std::vector<RequiredField> required_fields = {
+ CreateRequiredField("${53}", {"#card_expiry"})};
+ required_fields[0].fallback_click_element = Selector({".option"});
- auto fallback_data = std::make_unique<FallbackData>();
- fallback_data->field_values.emplace(
- static_cast<int>(autofill::ServerFieldType::CREDIT_CARD_EXP_MONTH), "08");
+ std::map<std::string, std::string> fallback_values;
+ fallback_values.emplace(
+ base::NumberToString(
+ static_cast<int>(autofill::ServerFieldType::CREDIT_CARD_EXP_MONTH)),
+ "08");
- RequiredFieldsFallbackHandler fallback_handler(fields,
- &mock_action_delegate_);
+ RequiredFieldsFallbackHandler fallback_handler(
+ required_fields, fallback_values, &mock_action_delegate_);
base::OnceCallback<void(const ClientStatus&,
const base::Optional<ClientStatus>&)>
@@ -604,8 +568,53 @@ TEST_F(RequiredFieldsFallbackHandlerTest, CustomDropdownClicksStopOnError) {
EXPECT_EQ(status.proto_status(), AUTOFILL_INCOMPLETE);
});
- fallback_handler.CheckAndFallbackRequiredFields(
- OkClientStatus(), std::move(fallback_data), std::move(callback));
+ fallback_handler.CheckAndFallbackRequiredFields(OkClientStatus(),
+ std::move(callback));
+}
+
+TEST_F(RequiredFieldsFallbackHandlerTest, ClearsFilledFields) {
+ Selector full_field_selector({"#full_field"});
+ Selector empty_field_selector({"#empty_field"});
+ EXPECT_CALL(mock_web_controller_, OnGetFieldValue(full_field_selector, _))
+ .WillOnce(RunOnceCallback<1>(OkClientStatus(), "value"));
+ EXPECT_CALL(mock_web_controller_, OnGetFieldValue(empty_field_selector, _))
+ .Times(0);
+
+ Expectation clear_full_value =
+ EXPECT_CALL(mock_action_delegate_,
+ OnSetFieldValue(full_field_selector, "", _))
+ .WillOnce(RunOnceCallback<2>(OkClientStatus()));
+ EXPECT_CALL(mock_web_controller_, OnGetFieldValue(full_field_selector, _))
+ .After(clear_full_value)
+ .WillOnce(RunOnceCallback<1>(OkClientStatus(), ""));
+ Expectation clear_empty_value =
+ EXPECT_CALL(mock_action_delegate_,
+ OnSetFieldValue(empty_field_selector, "", _))
+ .WillOnce(RunOnceCallback<2>(OkClientStatus()));
+ EXPECT_CALL(mock_web_controller_, OnGetFieldValue(empty_field_selector, _))
+ .After(clear_empty_value)
+ .WillOnce(RunOnceCallback<1>(OkClientStatus(), ""));
+
+ auto non_forced_field = CreateRequiredField("", {"#full_field"});
+ auto forced_field = CreateRequiredField("", {"#empty_field"});
+ forced_field.forced = true;
+ std::vector<RequiredField> required_fields = {non_forced_field, forced_field};
+
+ std::map<std::string, std::string> fallback_values;
+
+ RequiredFieldsFallbackHandler fallback_handler(
+ required_fields, fallback_values, &mock_action_delegate_);
+
+ base::OnceCallback<void(const ClientStatus&,
+ const base::Optional<ClientStatus>&)>
+ callback =
+ base::BindOnce([](const ClientStatus& status,
+ const base::Optional<ClientStatus>& detail_status) {
+ EXPECT_EQ(status.proto_status(), ACTION_APPLIED);
+ });
+
+ fallback_handler.CheckAndFallbackRequiredFields(OkClientStatus(),
+ std::move(callback));
}
} // namespace
diff --git a/chromium/components/autofill_assistant/browser/actions/generate_password_for_form_field_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/generate_password_for_form_field_action_unittest.cc
index dacb0c3fc80..df974c8433e 100644
--- a/chromium/components/autofill_assistant/browser/actions/generate_password_for_form_field_action_unittest.cc
+++ b/chromium/components/autofill_assistant/browser/actions/generate_password_for_form_field_action_unittest.cc
@@ -62,9 +62,8 @@ class GeneratePasswordForFormFieldActionTest : public testing::Test {
TEST_F(GeneratePasswordForFormFieldActionTest, GeneratedPassword) {
GeneratePasswordForFormFieldProto* generate_password_proto =
proto_.mutable_generate_password_for_form_field();
- generate_password_proto->mutable_element()->add_selectors(kFakeSelector);
- generate_password_proto->mutable_element()->set_visibility_requirement(
- MUST_BE_VISIBLE);
+ *generate_password_proto->mutable_element() =
+ Selector({kFakeSelector}).MustBeVisible().proto;
generate_password_proto->set_memory_key(kMemoryKeyForGeneratedPassword);
Selector fake_selector = Selector({kFakeSelector}).MustBeVisible();
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 22d50591d0d..808d29d18a7 100644
--- a/chromium/components/autofill_assistant/browser/actions/mock_action_delegate.h
+++ b/chromium/components/autofill_assistant/browser/actions/mock_action_delegate.h
@@ -135,26 +135,18 @@ class MockActionDelegate : public ActionDelegate {
MOCK_METHOD1(
WriteUserData,
void(base::OnceCallback<void(UserData*, UserData::FieldChange*)>));
- MOCK_METHOD1(WriteUserModel, void(base::OnceCallback<void(UserModel*)>));
-
- MOCK_METHOD1(OnGetFullCard,
- void(base::OnceCallback<void(const autofill::CreditCard& card,
- const base::string16& cvc)>&));
-
- void GetFullCard(GetFullCardCallback callback) override {
- // A local variable is necessary to allow OnGetFullCard to get a reference.
- base::OnceCallback<void(const autofill::CreditCard& card,
- const base::string16& cvc)>
- transformed_callback = base::BindOnce(
- [](GetFullCardCallback real_callback,
- const autofill::CreditCard& card, const base::string16& cvc) {
- std::move(real_callback)
- .Run(std::make_unique<autofill::CreditCard>(card), cvc);
- },
- std::move(callback));
- OnGetFullCard(transformed_callback);
+
+ void GetFullCard(const autofill::CreditCard* credit_card,
+ ActionDelegate::GetFullCardCallback callback) override {
+ OnGetFullCard(credit_card, callback);
}
+ MOCK_METHOD2(
+ OnGetFullCard,
+ void(const autofill::CreditCard* credit_card,
+ base::OnceCallback<void(std::unique_ptr<autofill::CreditCard> card,
+ const base::string16& cvc)>& callback));
+
void GetFieldValue(const Selector& selector,
base::OnceCallback<void(const ClientStatus&,
const std::string&)> callback) {
@@ -238,7 +230,11 @@ class MockActionDelegate : public ActionDelegate {
MOCK_METHOD1(SetInfoBox, void(const InfoBox& info_box));
MOCK_METHOD0(ClearInfoBox, void());
MOCK_METHOD1(SetProgress, void(int progress));
+ MOCK_METHOD1(SetProgressActiveStep, void(int active_step));
MOCK_METHOD1(SetProgressVisible, void(bool visible));
+ MOCK_METHOD1(SetStepProgressBarConfiguration,
+ void(const ShowProgressBarProto::StepProgressBarConfiguration&
+ configuration));
MOCK_METHOD1(SetUserActions,
void(std::unique_ptr<std::vector<UserAction>> user_action));
MOCK_METHOD1(SetViewportMode, void(ViewportMode mode));
@@ -294,19 +290,20 @@ class MockActionDelegate : public ActionDelegate {
MOCK_METHOD0(RequireUI, void());
MOCK_METHOD0(SetExpandSheetForPromptAction, bool());
- MOCK_METHOD2(
+ MOCK_METHOD3(
OnSetGenericUi,
void(std::unique_ptr<GenericUserInterfaceProto> generic_ui,
- base::OnceCallback<void(bool,
- ProcessedActionStatusProto,
- const UserModel*)>& end_action_callback));
+ base::OnceCallback<void(const ClientStatus&)>& end_action_callback,
+ base::OnceCallback<void(const ClientStatus&)>&
+ view_inflation_finished_callback));
void SetGenericUi(
std::unique_ptr<GenericUserInterfaceProto> generic_ui,
- base::OnceCallback<void(bool,
- ProcessedActionStatusProto,
- const UserModel*)> end_action_callback) override {
- OnSetGenericUi(std::move(generic_ui), end_action_callback);
+ base::OnceCallback<void(const ClientStatus&)> end_action_callback,
+ base::OnceCallback<void(const ClientStatus&)>
+ view_inflation_finished_callback) override {
+ OnSetGenericUi(std::move(generic_ui), end_action_callback,
+ view_inflation_finished_callback);
}
MOCK_METHOD0(ClearGenericUi, void());
diff --git a/chromium/components/autofill_assistant/browser/actions/navigate_action.cc b/chromium/components/autofill_assistant/browser/actions/navigate_action.cc
index 684c694dd34..21773c5dca2 100644
--- a/chromium/components/autofill_assistant/browser/actions/navigate_action.cc
+++ b/chromium/components/autofill_assistant/browser/actions/navigate_action.cc
@@ -26,16 +26,20 @@ void NavigateAction::InternalProcessAction(ProcessActionCallback callback) {
// We know to expect navigation to happen, since we're about to cause it. This
// allows scripts to put wait_for_navigation just after navigate, if needed,
// without having to add an expect_navigation first.
- delegate_->ExpectNavigation();
+ // The navigations are not declared as renderer initiated, they can cause
+ // failures. Setting the navigation to expected will prevent this from
+ // happening.
auto& proto = proto_.navigate();
if (!proto.url().empty()) {
+ delegate_->ExpectNavigation();
GURL url(proto_.navigate().url());
delegate_->LoadURL(url);
UpdateProcessedAction(ACTION_APPLIED);
} else if (proto.go_backward()) {
auto& controller = delegate_->GetWebContents()->GetController();
if (controller.CanGoBack()) {
+ delegate_->ExpectNavigation();
controller.GoBack();
UpdateProcessedAction(ACTION_APPLIED);
} else {
@@ -44,6 +48,7 @@ void NavigateAction::InternalProcessAction(ProcessActionCallback callback) {
} else if (proto.go_forward()) {
auto& controller = delegate_->GetWebContents()->GetController();
if (controller.CanGoForward()) {
+ delegate_->ExpectNavigation();
controller.GoForward();
UpdateProcessedAction(ACTION_APPLIED);
} else {
diff --git a/chromium/components/autofill_assistant/browser/actions/prompt_action.cc b/chromium/components/autofill_assistant/browser/actions/prompt_action.cc
index c6f3fde6429..9f1cd9cd746 100644
--- a/chromium/components/autofill_assistant/browser/actions/prompt_action.cc
+++ b/chromium/components/autofill_assistant/browser/actions/prompt_action.cc
@@ -221,8 +221,8 @@ void PromptAction::OnSuggestionChosen(int choice_index) {
}
DCHECK(choice_index >= 0 && choice_index <= proto_.prompt().choices_size());
- *processed_action_proto_->mutable_prompt_choice() =
- proto_.prompt().choices(choice_index);
+ processed_action_proto_->mutable_prompt_choice()->set_server_payload(
+ proto_.prompt().choices(choice_index).server_payload());
EndAction(ClientStatus(ACTION_APPLIED));
}
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 d5455c5bdce..afb7d7a6336 100644
--- a/chromium/components/autofill_assistant/browser/actions/prompt_action_unittest.cc
+++ b/chromium/components/autofill_assistant/browser/actions/prompt_action_unittest.cc
@@ -172,9 +172,9 @@ TEST_F(PromptActionTest, SelectButtons) {
Run(Pointee(AllOf(
Property(&ProcessedActionProto::status, ACTION_APPLIED),
Property(&ProcessedActionProto::prompt_choice,
- Property(&PromptProto::Choice::navigation_ended, false)),
+ Property(&PromptProto::Result::navigation_ended, false)),
Property(&ProcessedActionProto::prompt_choice,
- Property(&PromptProto::Choice::server_payload, "ok"))))));
+ Property(&PromptProto::Result::server_payload, "ok"))))));
EXPECT_TRUE((*user_actions_)[0].HasCallback());
(*user_actions_)[0].Call(TriggerContext::CreateEmpty());
}
@@ -209,7 +209,8 @@ TEST_F(PromptActionTest, ShowOnlyIfElementExists) {
ok_proto->mutable_chip()->set_text("Ok");
ok_proto->mutable_chip()->set_type(HIGHLIGHTED_ACTION);
ok_proto->set_server_payload("ok");
- ok_proto->mutable_show_only_when()->mutable_match()->add_selectors("element");
+ *ok_proto->mutable_show_only_when()->mutable_match() =
+ ToSelectorProto("element");
PromptAction action(&mock_action_delegate_, proto_);
action.ProcessAction(callback_.Get());
@@ -235,7 +236,8 @@ TEST_F(PromptActionTest, DisabledUnlessElementExists) {
ok_proto->mutable_chip()->set_type(HIGHLIGHTED_ACTION);
ok_proto->set_server_payload("ok");
ok_proto->set_allow_disabling(true);
- ok_proto->mutable_show_only_when()->mutable_match()->add_selectors("element");
+ *ok_proto->mutable_show_only_when()->mutable_match() =
+ ToSelectorProto("element");
PromptAction action(&mock_action_delegate_, proto_);
action.ProcessAction(callback_.Get());
@@ -259,8 +261,8 @@ TEST_F(PromptActionTest, DisabledUnlessElementExists) {
TEST_F(PromptActionTest, AutoSelectWhenElementExists) {
auto* choice_proto = prompt_proto_->add_choices();
choice_proto->set_server_payload("auto-select");
- choice_proto->mutable_auto_select_when()->mutable_match()->add_selectors(
- "element");
+ *choice_proto->mutable_auto_select_when()->mutable_match() =
+ ToSelectorProto("element");
PromptAction action(&mock_action_delegate_, proto_);
action.ProcessAction(callback_.Get());
@@ -275,7 +277,7 @@ TEST_F(PromptActionTest, AutoSelectWhenElementExists) {
callback_,
Run(Pointee(AllOf(Property(&ProcessedActionProto::status, ACTION_APPLIED),
Property(&ProcessedActionProto::prompt_choice,
- Property(&PromptProto::Choice::server_payload,
+ Property(&PromptProto::Result::server_payload,
"auto-select"))))));
task_env_.FastForwardBy(base::TimeDelta::FromSeconds(1));
}
@@ -288,8 +290,8 @@ TEST_F(PromptActionTest, AutoSelectWithButton) {
auto* choice_proto = prompt_proto_->add_choices();
choice_proto->set_server_payload("auto-select");
- choice_proto->mutable_auto_select_when()->mutable_match()->add_selectors(
- "element");
+ *choice_proto->mutable_auto_select_when()->mutable_match() =
+ ToSelectorProto("element");
PromptAction action(&mock_action_delegate_, proto_);
action.ProcessAction(callback_.Get());
@@ -303,7 +305,7 @@ TEST_F(PromptActionTest, AutoSelectWithButton) {
callback_,
Run(Pointee(AllOf(Property(&ProcessedActionProto::status, ACTION_APPLIED),
Property(&ProcessedActionProto::prompt_choice,
- Property(&PromptProto::Choice::server_payload,
+ Property(&PromptProto::Result::server_payload,
"auto-select"))))));
task_env_.FastForwardBy(base::TimeDelta::FromSeconds(1));
}
@@ -401,8 +403,8 @@ TEST_F(PromptActionTest, ForwardInterruptFailure) {
prompt_proto_->set_allow_interrupt(true);
auto* choice_proto = prompt_proto_->add_choices();
choice_proto->set_server_payload("auto-select");
- choice_proto->mutable_auto_select_when()->mutable_match()->add_selectors(
- "element");
+ *choice_proto->mutable_auto_select_when()->mutable_match() =
+ ToSelectorProto("element");
PromptAction action(&mock_action_delegate_, proto_);
action.ProcessAction(callback_.Get());
@@ -419,7 +421,7 @@ TEST_F(PromptActionTest, ForwardInterruptFailure) {
Pointee(Property(&ProcessedActionProto::status, INTERRUPT_FAILED)),
Pointee(
Property(&ProcessedActionProto::prompt_choice,
- Property(&PromptProto::Choice::server_payload, ""))))));
+ Property(&PromptProto::Result::server_payload, ""))))));
ASSERT_TRUE(fake_wait_for_dom_done_);
std::move(fake_wait_for_dom_done_).Run(ClientStatus(INTERRUPT_FAILED));
}
@@ -446,7 +448,7 @@ TEST_F(PromptActionTest, EndActionOnNavigation) {
Run(Pointee(AllOf(
Property(&ProcessedActionProto::status, ACTION_APPLIED),
Property(&ProcessedActionProto::prompt_choice,
- Property(&PromptProto::Choice::navigation_ended, true))))));
+ Property(&PromptProto::Result::navigation_ended, true))))));
action.ProcessAction(callback_.Get());
}
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 a1a8ceb4088..b49213d0b5e 100644
--- a/chromium/components/autofill_assistant/browser/actions/set_attribute_action.cc
+++ b/chromium/components/autofill_assistant/browser/actions/set_attribute_action.cc
@@ -16,7 +16,6 @@ namespace autofill_assistant {
SetAttributeAction::SetAttributeAction(ActionDelegate* delegate,
const ActionProto& proto)
: Action(delegate, proto) {
- DCHECK_GT(proto_.set_attribute().element().selectors_size(), 0);
DCHECK_GT(proto_.set_attribute().attribute_size(), 0);
}
diff --git a/chromium/components/autofill_assistant/browser/actions/set_form_field_value_action.cc b/chromium/components/autofill_assistant/browser/actions/set_form_field_value_action.cc
index c628e837b8f..4e2464531d5 100644
--- a/chromium/components/autofill_assistant/browser/actions/set_form_field_value_action.cc
+++ b/chromium/components/autofill_assistant/browser/actions/set_form_field_value_action.cc
@@ -40,7 +40,6 @@ SetFormFieldValueAction::SetFormFieldValueAction(ActionDelegate* delegate,
const ActionProto& proto)
: Action(delegate, proto) {
DCHECK(proto_.has_set_form_value());
- DCHECK_GT(proto_.set_form_value().element().selectors_size(), 0);
DCHECK_GT(proto_.set_form_value().value_size(), 0);
}
diff --git a/chromium/components/autofill_assistant/browser/actions/set_form_field_value_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/set_form_field_value_action_unittest.cc
index a397b7d5182..d90f43e9cfc 100644
--- a/chromium/components/autofill_assistant/browser/actions/set_form_field_value_action_unittest.cc
+++ b/chromium/components/autofill_assistant/browser/actions/set_form_field_value_action_unittest.cc
@@ -36,9 +36,8 @@ class SetFormFieldValueActionTest : public testing::Test {
public:
void SetUp() override {
set_form_field_proto_ = proto_.mutable_set_form_value();
- set_form_field_proto_->mutable_element()->add_selectors(kFakeSelector);
- set_form_field_proto_->mutable_element()->set_visibility_requirement(
- MUST_BE_VISIBLE);
+ *set_form_field_proto_->mutable_element() =
+ Selector({kFakeSelector}).MustBeVisible().proto;
ON_CALL(mock_action_delegate_, GetUserData)
.WillByDefault(Return(&user_data_));
ON_CALL(mock_action_delegate_, WriteUserData)
diff --git a/chromium/components/autofill_assistant/browser/actions/show_generic_ui_action.cc b/chromium/components/autofill_assistant/browser/actions/show_generic_ui_action.cc
index 1d239707e18..6f66a26ded7 100644
--- a/chromium/components/autofill_assistant/browser/actions/show_generic_ui_action.cc
+++ b/chromium/components/autofill_assistant/browser/actions/show_generic_ui_action.cc
@@ -53,8 +53,8 @@ void WriteProfilesToUserModel(
void WriteLoginOptionsToUserModel(
const ShowGenericUiProto::RequestLoginOptions& proto,
- std::vector<WebsiteLoginManager::Login> logins,
- UserModel* user_model) {
+ UserModel* user_model,
+ std::vector<WebsiteLoginManager::Login> logins) {
DCHECK(user_model);
ValueProto model_value;
model_value.set_is_client_side_only(true);
@@ -83,7 +83,6 @@ void WriteLoginOptionsToUserModel(
}
user_model->SetValue(proto.model_identifier(), model_value);
}
-
} // namespace
ShowGenericUiAction::ShowGenericUiAction(ActionDelegate* delegate,
@@ -107,17 +106,34 @@ void ShowGenericUiAction::InternalProcessAction(
/* force_notifications = */ false);
if (!temp_model.GetValues(proto_.show_generic_ui().output_model_identifiers())
.has_value()) {
- EndAction(false, INVALID_ACTION, nullptr);
+ EndAction(ClientStatus(INVALID_ACTION));
return;
}
+ for (const auto& element_check :
+ proto_.show_generic_ui().periodic_element_checks().element_checks()) {
+ if (element_check.model_identifier().empty()) {
+ VLOG(1) << "Invalid action: ElementCheck with empty model_identifier";
+ EndAction(ClientStatus(INVALID_ACTION));
+ return;
+ }
+ }
delegate_->Prompt(/* user_actions = */ nullptr,
/* disable_force_expand_sheet = */ false);
delegate_->SetGenericUi(
std::make_unique<GenericUserInterfaceProto>(
proto_.show_generic_ui().generic_user_interface()),
- base::BindOnce(&ShowGenericUiAction::EndAction,
+ base::BindOnce(&ShowGenericUiAction::OnEndActionInteraction,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::BindOnce(&ShowGenericUiAction::OnViewInflationFinished,
weak_ptr_factory_.GetWeakPtr()));
+}
+
+void ShowGenericUiAction::OnViewInflationFinished(const ClientStatus& status) {
+ if (!status.ok()) {
+ EndAction(status);
+ return;
+ }
// Note: it is important to write autofill profiles etc. to the model AFTER
// the UI has been inflated, otherwise the UI won't get change notifications
@@ -133,31 +149,108 @@ void ShowGenericUiAction::InternalProcessAction(
}) != login_options.end()) {
delegate_->GetWebsiteLoginManager()->GetLoginsForUrl(
delegate_->GetWebContents()->GetLastCommittedURL(),
- base::BindOnce(&ShowGenericUiAction::OnGetLogins,
- weak_ptr_factory_.GetWeakPtr(),
- proto_.show_generic_ui().request_login_options()));
+ base::BindOnce(&WriteLoginOptionsToUserModel,
+ proto_.show_generic_ui().request_login_options(),
+ delegate_->GetUserModel()));
} else {
- delegate_->WriteUserModel(base::BindOnce(
- &WriteLoginOptionsToUserModel,
+ WriteLoginOptionsToUserModel(
proto_.show_generic_ui().request_login_options(),
- /* logins = */ std::vector<WebsiteLoginManager::Login>()));
+ delegate_->GetUserModel(),
+ /* logins = */ std::vector<WebsiteLoginManager::Login>());
}
}
delegate_->GetPersonalDataManager()->AddObserver(this);
OnPersonalDataChanged();
+ for (const auto& element_check :
+ proto_.show_generic_ui().periodic_element_checks().element_checks()) {
+ preconditions_.emplace_back(std::make_unique<ElementPrecondition>(
+ element_check.element_condition()));
+ }
+ if (std::any_of(
+ preconditions_.begin(), preconditions_.end(),
+ [&](const auto& precondition) { return !precondition->empty(); })) {
+ has_pending_wait_for_dom_ = true;
+ delegate_->WaitForDom(
+ base::TimeDelta::Max(), false,
+ base::BindRepeating(&ShowGenericUiAction::RegisterChecks,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::BindOnce(&ShowGenericUiAction::OnDoneWaitForDom,
+ weak_ptr_factory_.GetWeakPtr()));
+ }
}
-void ShowGenericUiAction::EndAction(bool view_inflation_successful,
- ProcessedActionStatusProto status,
- const UserModel* user_model) {
+void ShowGenericUiAction::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 < preconditions_.size(); i++) {
+ preconditions_[i]->Check(
+ checker, base::BindOnce(&ShowGenericUiAction::OnPreconditionResult,
+ weak_ptr_factory_.GetWeakPtr(), i));
+ }
+ // Let WaitForDom know we're still waiting for elements.
+ checker->AddAllDoneCallback(base::BindOnce(
+ &ShowGenericUiAction::OnElementChecksDone, weak_ptr_factory_.GetWeakPtr(),
+ std::move(wait_for_dom_callback)));
+}
+
+void ShowGenericUiAction::OnPreconditionResult(
+ size_t precondition_index,
+ const ClientStatus& status,
+ const std::vector<std::string>& ignored_payloads) {
+ delegate_->GetUserModel()->SetValue(proto_.show_generic_ui()
+ .periodic_element_checks()
+ .element_checks(precondition_index)
+ .model_identifier(),
+ SimpleValue(status.ok()));
+}
+
+void ShowGenericUiAction::OnElementChecksDone(
+ base::OnceCallback<void(const ClientStatus&)> wait_for_dom_callback) {
+ // Calling wait_for_dom_callback with successful status is a way of asking the
+ // WaitForDom to end gracefully and call OnDoneWaitForDom with the status.
+ // Note that it is possible for WaitForDom to decide not to call
+ // OnDoneWaitForDom, if an interrupt triggers at the same time, so we cannot
+ // cancel the prompt and choose the suggestion just yet.
+ if (should_end_action_) {
+ std::move(wait_for_dom_callback).Run(OkClientStatus());
+ return;
+ }
+ // Let WaitForDom know we're still waiting for an element.
+ std::move(wait_for_dom_callback).Run(ClientStatus(ELEMENT_RESOLUTION_FAILED));
+}
+
+void ShowGenericUiAction::OnDoneWaitForDom(const ClientStatus& status) {
+ if (!callback_) {
+ return;
+ }
+ EndAction(status);
+}
+
+void ShowGenericUiAction::OnEndActionInteraction(const ClientStatus& status) {
+ // If WaitForDom was called, we end the action the next time the callback
+ // is called in order to end WaitForDom gracefully.
+ if (has_pending_wait_for_dom_) {
+ should_end_action_ = true;
+ return;
+ }
+ EndAction(status);
+}
+
+void ShowGenericUiAction::EndAction(const ClientStatus& status) {
delegate_->ClearGenericUi();
delegate_->CleanUpAfterPrompt();
UpdateProcessedAction(status);
- if (view_inflation_successful) {
- DCHECK(user_model);
+ if (status.ok()) {
const auto& output_model_identifiers =
proto_.show_generic_ui().output_model_identifiers();
- auto values = user_model->GetValues(output_model_identifiers);
+ auto values =
+ delegate_->GetUserModel()->GetValues(output_model_identifiers);
// This should always be the case since there is no way to erase a value
// from the model.
DCHECK(values.has_value());
@@ -185,9 +278,9 @@ void ShowGenericUiAction::OnPersonalDataChanged() {
profiles->emplace_back(
std::make_unique<autofill::AutofillProfile>(*profile));
}
- delegate_->WriteUserModel(
- base::BindOnce(&WriteProfilesToUserModel, std::move(profiles),
- proto_.show_generic_ui().request_profiles()));
+ WriteProfilesToUserModel(std::move(profiles),
+ proto_.show_generic_ui().request_profiles(),
+ delegate_->GetUserModel());
}
if (proto_.show_generic_ui().has_request_credit_cards()) {
@@ -198,18 +291,10 @@ void ShowGenericUiAction::OnPersonalDataChanged() {
credit_cards->emplace_back(
std::make_unique<autofill::CreditCard>(*credit_card));
}
- delegate_->WriteUserModel(
- base::BindOnce(&WriteCreditCardsToUserModel, std::move(credit_cards),
- proto_.show_generic_ui().request_credit_cards()));
+ WriteCreditCardsToUserModel(std::move(credit_cards),
+ proto_.show_generic_ui().request_credit_cards(),
+ delegate_->GetUserModel());
}
}
-void ShowGenericUiAction::OnGetLogins(
- const ShowGenericUiProto::RequestLoginOptions& proto,
- std::vector<WebsiteLoginManager::Login> logins) {
- LOG(ERROR) << "Retrieved " << logins.size() << " logins";
- delegate_->WriteUserModel(
- base::BindOnce(&WriteLoginOptionsToUserModel, proto, logins));
-}
-
} // namespace autofill_assistant
diff --git a/chromium/components/autofill_assistant/browser/actions/show_generic_ui_action.h b/chromium/components/autofill_assistant/browser/actions/show_generic_ui_action.h
index 091191ade32..5cdca28c679 100644
--- a/chromium/components/autofill_assistant/browser/actions/show_generic_ui_action.h
+++ b/chromium/components/autofill_assistant/browser/actions/show_generic_ui_action.h
@@ -11,10 +11,10 @@
#include "components/autofill/core/browser/personal_data_manager.h"
#include "components/autofill/core/browser/personal_data_manager_observer.h"
#include "components/autofill_assistant/browser/actions/action.h"
+#include "components/autofill_assistant/browser/element_precondition.h"
#include "components/autofill_assistant/browser/website_login_manager.h"
namespace autofill_assistant {
-class UserModel;
// Action to show generic UI in the sheet.
class ShowGenericUiAction : public Action,
@@ -31,16 +31,28 @@ class ShowGenericUiAction : public Action,
// Overrides Action:
void InternalProcessAction(ProcessActionCallback callback) override;
- void EndAction(bool view_inflation_successful,
- ProcessedActionStatusProto status,
- const UserModel* user_model);
+ void RegisterChecks(
+ BatchElementChecker* checker,
+ base::OnceCallback<void(const ClientStatus&)> wait_for_dom_callback);
+ void OnPreconditionResult(size_t choice_index,
+ const ClientStatus& status,
+ const std::vector<std::string>& ignored_payloads);
+ void OnElementChecksDone(
+ base::OnceCallback<void(const ClientStatus&)> wait_for_dom_callback);
+ void OnDoneWaitForDom(const ClientStatus& status);
+ // If there is an active WaitForDom this method ends it before calling
+ // EndAction, otherwise it just calls EndAction.
+ void OnEndActionInteraction(const ClientStatus& status);
+ void EndAction(const ClientStatus& status);
+
+ void OnViewInflationFinished(const ClientStatus& status);
// From autofill::PersonalDataManagerObserver.
void OnPersonalDataChanged() override;
- void OnGetLogins(const ShowGenericUiProto::RequestLoginOptions& proto,
- std::vector<WebsiteLoginManager::Login> logins);
-
+ bool has_pending_wait_for_dom_ = false;
+ bool should_end_action_ = false;
+ std::vector<std::unique_ptr<ElementPrecondition>> preconditions_;
ProcessActionCallback callback_;
base::WeakPtrFactory<ShowGenericUiAction> weak_ptr_factory_{this};
};
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 8464d22ef0b..d0405c87cff 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
@@ -43,21 +43,20 @@ class ShowGenericUiActionTest : public content::RenderViewHostTestHarness {
void SetUp() override {
RenderViewHostTestHarness::SetUp();
- ON_CALL(mock_action_delegate_, OnSetGenericUi(_, _))
- .WillByDefault(Invoke(
- [this](std::unique_ptr<GenericUserInterfaceProto> generic_ui,
- base::OnceCallback<void(bool, ProcessedActionStatusProto,
- const UserModel*)>&
- end_action_callback) {
- std::move(end_action_callback)
- .Run(true, UNKNOWN_ACTION_STATUS, &user_model_);
- }));
- ON_CALL(mock_action_delegate_, ClearGenericUi()).WillByDefault(Return());
- ON_CALL(mock_action_delegate_, WriteUserModel(_))
+ ON_CALL(mock_action_delegate_, OnSetGenericUi(_, _, _))
.WillByDefault(
- Invoke([this](base::OnceCallback<void(UserModel*)> write_callback) {
- std::move(write_callback).Run(&user_model_);
+ Invoke([&](std::unique_ptr<GenericUserInterfaceProto> generic_ui,
+ base::OnceCallback<void(const ClientStatus&)>&
+ end_action_callback,
+ base::OnceCallback<void(const ClientStatus&)>&
+ view_inflation_finished_callback) {
+ std::move(view_inflation_finished_callback)
+ .Run(ClientStatus(ACTION_APPLIED));
+ std::move(end_action_callback).Run(ClientStatus(ACTION_APPLIED));
}));
+ ON_CALL(mock_action_delegate_, ClearGenericUi()).WillByDefault(Return());
+ ON_CALL(mock_action_delegate_, GetUserModel())
+ .WillByDefault(Return(&user_model_));
ON_CALL(mock_action_delegate_, GetPersonalDataManager)
.WillByDefault(Return(&mock_personal_data_manager_));
ON_CALL(mock_action_delegate_, GetWebsiteLoginManager)
@@ -92,15 +91,16 @@ class ShowGenericUiActionTest : public content::RenderViewHostTestHarness {
ShowGenericUiProto proto_;
};
-TEST_F(ShowGenericUiActionTest, SmokeTest) {
- ON_CALL(mock_action_delegate_, OnSetGenericUi(_, _))
- .WillByDefault(Invoke(
- [this](
- std::unique_ptr<GenericUserInterfaceProto> generic_ui,
- base::OnceCallback<void(bool, ProcessedActionStatusProto,
- const UserModel*)>& end_action_callback) {
- std::move(end_action_callback)
- .Run(false, INVALID_ACTION, &user_model_);
+TEST_F(ShowGenericUiActionTest, FailedViewInflationEndsAction) {
+ ON_CALL(mock_action_delegate_, OnSetGenericUi(_, _, _))
+ .WillByDefault(
+ Invoke([&](std::unique_ptr<GenericUserInterfaceProto> generic_ui,
+ base::OnceCallback<void(const ClientStatus&)>&
+ end_action_callback,
+ base::OnceCallback<void(const ClientStatus&)>&
+ view_inflation_finished_callback) {
+ std::move(view_inflation_finished_callback)
+ .Run(ClientStatus(INVALID_ACTION));
}));
EXPECT_CALL(mock_action_delegate_, ClearGenericUi()).Times(1);
@@ -112,19 +112,9 @@ TEST_F(ShowGenericUiActionTest, SmokeTest) {
}
TEST_F(ShowGenericUiActionTest, GoesIntoPromptState) {
- ON_CALL(mock_action_delegate_, OnSetGenericUi(_, _))
- .WillByDefault(Invoke(
- [this](
- std::unique_ptr<GenericUserInterfaceProto> generic_ui,
- base::OnceCallback<void(bool, ProcessedActionStatusProto,
- const UserModel*)>& end_action_callback) {
- std::move(end_action_callback)
- .Run(true, ACTION_APPLIED, &user_model_);
- }));
-
InSequence seq;
EXPECT_CALL(mock_action_delegate_, Prompt(_, _, _, _)).Times(1);
- EXPECT_CALL(mock_action_delegate_, OnSetGenericUi(_, _)).Times(1);
+ EXPECT_CALL(mock_action_delegate_, OnSetGenericUi(_, _, _)).Times(1);
EXPECT_CALL(mock_action_delegate_, ClearGenericUi()).Times(1);
EXPECT_CALL(mock_action_delegate_, CleanUpAfterPrompt()).Times(1);
EXPECT_CALL(
@@ -140,16 +130,6 @@ TEST_F(ShowGenericUiActionTest, EmptyOutputModel) {
input_value->set_identifier("input");
*input_value->mutable_value() = SimpleValue(std::string("InputValue"));
- ON_CALL(mock_action_delegate_, OnSetGenericUi(_, _))
- .WillByDefault(Invoke(
- [this](
- std::unique_ptr<GenericUserInterfaceProto> generic_ui,
- base::OnceCallback<void(bool, ProcessedActionStatusProto,
- const UserModel*)>& end_action_callback) {
- std::move(end_action_callback)
- .Run(true, ACTION_APPLIED, &user_model_);
- }));
-
EXPECT_CALL(mock_action_delegate_, ClearGenericUi()).Times(1);
EXPECT_CALL(
callback_,
@@ -174,15 +154,17 @@ TEST_F(ShowGenericUiActionTest, NonEmptyOutputModel) {
proto_.add_output_model_identifiers("value_2");
- ON_CALL(mock_action_delegate_, OnSetGenericUi(_, _))
- .WillByDefault(Invoke(
- [this](
- std::unique_ptr<GenericUserInterfaceProto> generic_ui,
- base::OnceCallback<void(bool, ProcessedActionStatusProto,
- const UserModel*)>& end_action_callback) {
+ ON_CALL(mock_action_delegate_, OnSetGenericUi(_, _, _))
+ .WillByDefault(
+ Invoke([this](std::unique_ptr<GenericUserInterfaceProto> generic_ui,
+ base::OnceCallback<void(const ClientStatus&)>&
+ end_action_callback,
+ base::OnceCallback<void(const ClientStatus&)>&
+ view_inflation_finished_callback) {
+ std::move(view_inflation_finished_callback)
+ .Run(ClientStatus(ACTION_APPLIED));
user_model_.SetValue("value_2", SimpleValue(std::string("change")));
- std::move(end_action_callback)
- .Run(true, ACTION_APPLIED, &user_model_);
+ std::move(end_action_callback).Run(ClientStatus(ACTION_APPLIED));
}));
EXPECT_CALL(mock_action_delegate_, ClearGenericUi()).Times(1);
@@ -215,7 +197,7 @@ TEST_F(ShowGenericUiActionTest, OutputModelNotSubsetOfInputModel) {
proto_.add_output_model_identifiers("value_2");
proto_.add_output_model_identifiers("value_3");
- EXPECT_CALL(mock_action_delegate_, OnSetGenericUi(_, _)).Times(0);
+ EXPECT_CALL(mock_action_delegate_, OnSetGenericUi(_, _, _)).Times(0);
EXPECT_CALL(mock_action_delegate_, ClearGenericUi()).Times(1);
EXPECT_CALL(
callback_,
@@ -242,16 +224,6 @@ TEST_F(ShowGenericUiActionTest, ClientOnlyValuesDoNotLeaveDevice) {
proto_.add_output_model_identifiers("regular_value");
proto_.add_output_model_identifiers("sensitive_value");
- ON_CALL(mock_action_delegate_, OnSetGenericUi(_, _))
- .WillByDefault(Invoke(
- [this](
- std::unique_ptr<GenericUserInterfaceProto> generic_ui,
- base::OnceCallback<void(bool, ProcessedActionStatusProto,
- const UserModel*)>& end_action_callback) {
- std::move(end_action_callback)
- .Run(true, ACTION_APPLIED, &user_model_);
- }));
-
EXPECT_CALL(
callback_,
Run(Pointee(AllOf(
@@ -462,5 +434,26 @@ TEST_F(ShowGenericUiActionTest, RequestLogins) {
EXPECT_EQ(*user_model_.GetValue("login_options"), expected_value);
}
+TEST_F(ShowGenericUiActionTest, ElementPreconditionMissesIdentifier) {
+ auto* element_check =
+ proto_.mutable_periodic_element_checks()->add_element_checks();
+ element_check->mutable_element_condition()
+ ->mutable_match()
+ ->add_filters()
+ ->set_css_selector("selector");
+
+ EXPECT_CALL(mock_action_delegate_, OnSetGenericUi(_, _, _)).Times(0);
+ EXPECT_CALL(mock_action_delegate_, ClearGenericUi()).Times(1);
+ EXPECT_CALL(
+ callback_,
+ Run(Pointee(AllOf(
+ Property(&ProcessedActionProto::status, INVALID_ACTION),
+ Property(&ProcessedActionProto::show_generic_ui_result,
+ Property(&ShowGenericUiProto::Result::model,
+ Property(&ModelProto::values, SizeIs(0))))))));
+
+ Run();
+}
+
} // namespace
} // namespace autofill_assistant
diff --git a/chromium/components/autofill_assistant/browser/actions/show_progress_bar_action.cc b/chromium/components/autofill_assistant/browser/actions/show_progress_bar_action.cc
index e23d511cc44..d99c9298f61 100644
--- a/chromium/components/autofill_assistant/browser/actions/show_progress_bar_action.cc
+++ b/chromium/components/autofill_assistant/browser/actions/show_progress_bar_action.cc
@@ -20,7 +20,7 @@ ShowProgressBarAction::ShowProgressBarAction(ActionDelegate* delegate,
DCHECK(proto_.has_show_progress_bar());
}
-ShowProgressBarAction::~ShowProgressBarAction() {}
+ShowProgressBarAction::~ShowProgressBarAction() = default;
void ShowProgressBarAction::InternalProcessAction(
ProcessActionCallback callback) {
@@ -29,14 +29,49 @@ void ShowProgressBarAction::InternalProcessAction(
// use tell instead.
delegate_->SetStatusMessage(proto_.show_progress_bar().message());
}
- int progress =
- base::ClampToRange(proto_.show_progress_bar().progress(), 0, 100);
- delegate_->SetProgress(progress);
+
if (proto_.show_progress_bar().has_hide()) {
delegate_->SetProgressVisible(!proto_.show_progress_bar().hide());
}
+ if (proto_.show_progress_bar().has_step_progress_bar_configuration()) {
+ const auto& configuration =
+ proto_.show_progress_bar().step_progress_bar_configuration();
+ if (!configuration.step_icons().empty() &&
+ configuration.step_icons().size() < 2) {
+ EndAction(std::move(callback), INVALID_ACTION);
+ return;
+ }
+ delegate_->SetStepProgressBarConfiguration(configuration);
+ }
+
+ switch (proto_.show_progress_bar().progress_indicator_case()) {
+ case ShowProgressBarProto::ProgressIndicatorCase::kProgress: {
+ int progress =
+ base::ClampToRange(proto_.show_progress_bar().progress(), 0, 100);
+ delegate_->SetProgress(progress);
+ break;
+ }
+ case ShowProgressBarProto::ProgressIndicatorCase::kActiveStep: {
+ int active_step = proto_.show_progress_bar().active_step();
+ if (active_step < 0) {
+ EndAction(std::move(callback), INVALID_ACTION);
+ return;
+ }
+
+ delegate_->SetProgressActiveStep(active_step);
+ break;
+ }
+ default:
+ // Ignore.
+ break;
+ }
+
+ EndAction(std::move(callback), ACTION_APPLIED);
+}
- UpdateProcessedAction(ACTION_APPLIED);
+void ShowProgressBarAction::EndAction(ProcessActionCallback callback,
+ ProcessedActionStatusProto status) {
+ UpdateProcessedAction(status);
std::move(callback).Run(std::move(processed_action_proto_));
}
diff --git a/chromium/components/autofill_assistant/browser/actions/show_progress_bar_action.h b/chromium/components/autofill_assistant/browser/actions/show_progress_bar_action.h
index e6832adfa31..395fca951b1 100644
--- a/chromium/components/autofill_assistant/browser/actions/show_progress_bar_action.h
+++ b/chromium/components/autofill_assistant/browser/actions/show_progress_bar_action.h
@@ -8,7 +8,7 @@
#include "components/autofill_assistant/browser/actions/action.h"
#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
+#include "components/autofill_assistant/browser/service.pb.h"
namespace autofill_assistant {
// An action to show the current progress.
@@ -22,6 +22,9 @@ class ShowProgressBarAction : public Action {
// Overrides Action:
void InternalProcessAction(ProcessActionCallback callback) override;
+ void EndAction(ProcessActionCallback callback,
+ ProcessedActionStatusProto status);
+
DISALLOW_COPY_AND_ASSIGN(ShowProgressBarAction);
};
diff --git a/chromium/components/autofill_assistant/browser/actions/show_progress_bar_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/show_progress_bar_action_unittest.cc
new file mode 100644
index 00000000000..571a9c99577
--- /dev/null
+++ b/chromium/components/autofill_assistant/browser/actions/show_progress_bar_action_unittest.cc
@@ -0,0 +1,166 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill_assistant/browser/actions/show_progress_bar_action.h"
+
+#include "base/test/gmock_callback_support.h"
+#include "base/test/mock_callback.h"
+#include "components/autofill_assistant/browser/actions/mock_action_delegate.h"
+#include "components/autofill_assistant/browser/service.pb.h"
+#include "components/autofill_assistant/browser/view_layout.pb.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace autofill_assistant {
+namespace {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Property;
+using ::testing::Return;
+using ::testing::StrEq;
+
+class ShowProgressBarActionTest : public testing::Test {
+ public:
+ ShowProgressBarActionTest() {}
+
+ void SetUp() override {}
+
+ protected:
+ void Run() {
+ ActionProto action_proto;
+ *action_proto.mutable_show_progress_bar() = proto_;
+ ShowProgressBarAction action(&mock_action_delegate_, action_proto);
+ action.ProcessAction(callback_.Get());
+ }
+
+ MockActionDelegate mock_action_delegate_;
+ base::MockCallback<Action::ProcessActionCallback> callback_;
+ ShowProgressBarProto proto_;
+};
+
+TEST_F(ShowProgressBarActionTest, EmptyProgressBarDoesNothing) {
+ EXPECT_CALL(mock_action_delegate_, SetStatusMessage(_)).Times(0);
+ EXPECT_CALL(mock_action_delegate_, SetProgressVisible(_)).Times(0);
+ EXPECT_CALL(mock_action_delegate_, SetStepProgressBarConfiguration(_))
+ .Times(0);
+ EXPECT_CALL(mock_action_delegate_, SetProgress(_)).Times(0);
+ EXPECT_CALL(mock_action_delegate_, SetProgressActiveStep(_)).Times(0);
+ EXPECT_CALL(
+ callback_,
+ Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED))));
+ Run();
+}
+
+TEST_F(ShowProgressBarActionTest, SpecifiedMessageGetsSet) {
+ proto_.set_message("Message");
+
+ EXPECT_CALL(mock_action_delegate_, SetStatusMessage("Message"));
+ EXPECT_CALL(
+ callback_,
+ Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED))));
+ Run();
+}
+
+TEST_F(ShowProgressBarActionTest, HidesProgressBar) {
+ proto_.set_hide(true);
+
+ EXPECT_CALL(mock_action_delegate_, SetProgressVisible(false));
+ EXPECT_CALL(
+ callback_,
+ Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED))));
+ Run();
+}
+
+TEST_F(ShowProgressBarActionTest, ShowsProgressBar) {
+ proto_.set_hide(false);
+
+ EXPECT_CALL(mock_action_delegate_, SetProgressVisible(true));
+ EXPECT_CALL(
+ callback_,
+ Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED))));
+ Run();
+}
+
+TEST_F(ShowProgressBarActionTest, FewerThanTwoStepsProgressBarFailsAction) {
+ auto* config = proto_.mutable_step_progress_bar_configuration();
+ config->set_use_step_progress_bar(true);
+ config->add_step_icons()->set_icon(
+ DrawableProto::PROGRESSBAR_DEFAULT_INITIAL_STEP);
+
+ EXPECT_CALL(mock_action_delegate_, SetStepProgressBarConfiguration(_))
+ .Times(0);
+ EXPECT_CALL(
+ callback_,
+ Run(Pointee(Property(&ProcessedActionProto::status, INVALID_ACTION))));
+ Run();
+}
+
+TEST_F(ShowProgressBarActionTest, UpdateStepProgressBarConfiguration) {
+ auto* config = proto_.mutable_step_progress_bar_configuration();
+ config->set_use_step_progress_bar(true);
+ config->add_step_icons()->set_icon(
+ DrawableProto::PROGRESSBAR_DEFAULT_INITIAL_STEP);
+ config->add_step_icons()->set_icon(
+ DrawableProto::PROGRESSBAR_DEFAULT_FINAL_STEP);
+
+ EXPECT_CALL(mock_action_delegate_, SetStepProgressBarConfiguration(_));
+ EXPECT_CALL(
+ callback_,
+ Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED))));
+ Run();
+}
+
+TEST_F(ShowProgressBarActionTest, DeactivateStepProgressBar) {
+ auto* config = proto_.mutable_step_progress_bar_configuration();
+ config->set_use_step_progress_bar(false);
+
+ EXPECT_CALL(mock_action_delegate_, SetStepProgressBarConfiguration(_));
+ EXPECT_CALL(
+ callback_,
+ Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED))));
+ Run();
+}
+
+TEST_F(ShowProgressBarActionTest, SetProgress) {
+ proto_.set_progress(50);
+
+ EXPECT_CALL(mock_action_delegate_, SetProgress(50));
+ EXPECT_CALL(
+ callback_,
+ Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED))));
+ Run();
+}
+
+TEST_F(ShowProgressBarActionTest, ClampsProgress) {
+ proto_.set_progress(150);
+
+ EXPECT_CALL(mock_action_delegate_, SetProgress(100));
+ EXPECT_CALL(
+ callback_,
+ Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED))));
+ Run();
+}
+
+TEST_F(ShowProgressBarActionTest, SetActiveStep) {
+ proto_.set_active_step(2);
+
+ EXPECT_CALL(mock_action_delegate_, SetProgressActiveStep(2));
+ EXPECT_CALL(
+ callback_,
+ Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED))));
+ Run();
+}
+
+TEST_F(ShowProgressBarActionTest, OutOfBoundsActiveStepFailsAction) {
+ proto_.set_active_step(-1);
+
+ EXPECT_CALL(mock_action_delegate_, SetProgressActiveStep(_)).Times(0);
+ EXPECT_CALL(
+ callback_,
+ Run(Pointee(Property(&ProcessedActionProto::status, INVALID_ACTION))));
+ Run();
+}
+
+} // namespace
+} // namespace autofill_assistant
diff --git a/chromium/components/autofill_assistant/browser/actions/use_address_action.cc b/chromium/components/autofill_assistant/browser/actions/use_address_action.cc
index ad2c1078bf2..938cab87309 100644
--- a/chromium/components/autofill_assistant/browser/actions/use_address_action.cc
+++ b/chromium/components/autofill_assistant/browser/actions/use_address_action.cc
@@ -12,12 +12,13 @@
#include "base/optional.h"
#include "base/time/time.h"
#include "components/autofill/core/browser/autofill_data_util.h"
-#include "components/autofill/core/browser/data_model/autofill_profile.h"
#include "components/autofill_assistant/browser/actions/action_delegate.h"
-#include "components/autofill_assistant/browser/actions/fallback_handler/fallback_data.h"
#include "components/autofill_assistant/browser/actions/fallback_handler/required_field.h"
#include "components/autofill_assistant/browser/actions/fallback_handler/required_fields_fallback_handler.h"
#include "components/autofill_assistant/browser/client_status.h"
+#include "components/autofill_assistant/browser/field_formatter.h"
+#include "components/autofill_assistant/browser/user_model.h"
+#include "components/autofill_assistant/browser/value_util.h"
namespace autofill_assistant {
@@ -25,23 +26,6 @@ UseAddressAction::UseAddressAction(ActionDelegate* delegate,
const ActionProto& proto)
: Action(delegate, proto) {
DCHECK(proto.has_use_address());
- prompt_ = proto.use_address().prompt();
- name_ = proto.use_address().name();
- std::vector<RequiredField> required_fields;
- for (const auto& required_field_proto :
- proto_.use_address().required_fields()) {
- if (required_field_proto.value_expression().empty()) {
- DVLOG(3) << "No fallback filling information provided, skipping field";
- continue;
- }
-
- required_fields.emplace_back();
- required_fields.back().FromProto(required_field_proto);
- }
-
- required_fields_fallback_handler_ =
- std::make_unique<RequiredFieldsFallbackHandler>(required_fields,
- delegate);
selector_ = Selector(proto.use_address().form_field_element());
selector_.MustBeVisible();
}
@@ -65,19 +49,68 @@ void UseAddressAction::InternalProcessAction(
}
// Ensure data already selected in a previous action.
- auto* user_data = delegate_->GetUserData();
- if (!user_data->has_selected_address(name_)) {
- auto* error_info = processed_action_proto_->mutable_status_details()
- ->mutable_autofill_error_info();
- error_info->set_address_key_requested(name_);
- error_info->set_client_memory_address_key_names(
- user_data->GetAllAddressKeyNames());
- error_info->set_address_pointee_was_null(
- !user_data->has_selected_address(name_) ||
- !user_data->selected_address(name_));
- EndAction(ClientStatus(PRECONDITION_FAILED));
- return;
+ switch (proto_.use_address().address_source_case()) {
+ case UseAddressProto::kName: {
+ if (proto_.use_address().name().empty()) {
+ VLOG(1) << "UseAddress failed: |name| specified but empty";
+ EndAction(ClientStatus(INVALID_ACTION));
+ return;
+ }
+ auto* profile = delegate_->GetUserData()->selected_address(
+ proto_.use_address().name());
+ if (profile == nullptr) {
+ auto* error_info = processed_action_proto_->mutable_status_details()
+ ->mutable_autofill_error_info();
+ error_info->set_address_key_requested(proto_.use_address().name());
+ error_info->set_client_memory_address_key_names(
+ delegate_->GetUserData()->GetAllAddressKeyNames());
+ error_info->set_address_pointee_was_null(true);
+ VLOG(1) << "UseAddress failed: no profile found under "
+ << proto_.use_address().name();
+ EndAction(ClientStatus(PRECONDITION_FAILED));
+ return;
+ }
+ profile_ = std::make_unique<autofill::AutofillProfile>(*profile);
+ break;
+ }
+ case UseAddressProto::kModelIdentifier: {
+ if (proto_.use_address().model_identifier().empty()) {
+ VLOG(1) << "UseAddress failed: |model_identifier| set but empty";
+ EndAction(ClientStatus(INVALID_ACTION));
+ return;
+ }
+ auto profile_value = delegate_->GetUserModel()->GetValue(
+ proto_.use_address().model_identifier());
+ if (!profile_value.has_value()) {
+ VLOG(1) << "UseAddress failed: "
+ << proto_.use_address().model_identifier()
+ << " not found in user model";
+ EndAction(ClientStatus(PRECONDITION_FAILED));
+ return;
+ }
+ if (profile_value->profiles().values().size() != 1) {
+ VLOG(1) << "UseAddress failed: expected single profile for "
+ << proto_.use_address().model_identifier() << ", but got "
+ << *profile_value;
+ EndAction(ClientStatus(PRECONDITION_FAILED));
+ return;
+ }
+ auto* profile = delegate_->GetUserModel()->GetProfile(
+ profile_value->profiles().values(0).guid());
+ if (profile == nullptr) {
+ VLOG(1) << "UseAddress failed: profile not found for guid "
+ << *profile_value;
+ EndAction(ClientStatus(PRECONDITION_FAILED));
+ return;
+ }
+ profile_ = std::make_unique<autofill::AutofillProfile>(*profile);
+ break;
+ }
+ case UseAddressProto::ADDRESS_SOURCE_NOT_SET:
+ EndAction(ClientStatus(INVALID_ACTION));
+ return;
}
+ DCHECK(profile_ != nullptr);
FillFormWithData();
}
@@ -94,21 +127,12 @@ void UseAddressAction::EndAction(
}
void UseAddressAction::FillFormWithData() {
- if (proto_.use_address().skip_autofill()) {
- VLOG(3) << "Retrieving address from client memory under '" << name_ << "'.";
- const autofill::AutofillProfile* profile =
- delegate_->GetUserData()->selected_address(name_);
- DCHECK(profile);
- auto fallback_data = CreateFallbackData(*profile);
-
- required_fields_fallback_handler_->CheckAndFallbackRequiredFields(
- OkClientStatus(), std::move(fallback_data),
- base::BindOnce(&UseAddressAction::EndAction,
- weak_ptr_factory_.GetWeakPtr()));
+ if (selector_.empty()) {
+ DCHECK(proto_.use_address().skip_autofill());
+ OnWaitForElement(OkClientStatus());
return;
}
- DCHECK(!selector_.empty());
delegate_->ShortWaitForElement(
selector_, base::BindOnce(&UseAddressAction::OnWaitForElement,
weak_ptr_factory_.GetWeakPtr()));
@@ -116,36 +140,46 @@ void UseAddressAction::FillFormWithData() {
void UseAddressAction::OnWaitForElement(const ClientStatus& element_status) {
if (!element_status.ok()) {
- EndAction(ClientStatus(element_status.proto_status()));
+ EndAction(element_status);
return;
}
- VLOG(3) << "Retrieving address from client memory under '" << name_ << "'.";
- const autofill::AutofillProfile* profile =
- delegate_->GetUserData()->selected_address(name_);
- DCHECK(profile);
- auto fallback_data = CreateFallbackData(*profile);
+ if (proto_.use_address().skip_autofill()) {
+ ExecuteFallback(OkClientStatus());
+ return;
+ }
- delegate_->FillAddressForm(
- profile, selector_,
- base::BindOnce(&UseAddressAction::OnFormFilled,
- weak_ptr_factory_.GetWeakPtr(), std::move(fallback_data)));
+ DCHECK(!selector_.empty());
+ DCHECK(profile_ != nullptr);
+ delegate_->FillAddressForm(profile_.get(), selector_,
+ base::BindOnce(&UseAddressAction::ExecuteFallback,
+ weak_ptr_factory_.GetWeakPtr()));
}
-void UseAddressAction::OnFormFilled(std::unique_ptr<FallbackData> fallback_data,
- const ClientStatus& status) {
- required_fields_fallback_handler_->CheckAndFallbackRequiredFields(
- status, std::move(fallback_data),
- base::BindOnce(&UseAddressAction::EndAction,
- weak_ptr_factory_.GetWeakPtr()));
-}
+void UseAddressAction::ExecuteFallback(const ClientStatus& status) {
+ DCHECK(profile_ != nullptr);
+ std::vector<RequiredField> required_fields;
+ for (const auto& required_field_proto :
+ proto_.use_address().required_fields()) {
+ if (!required_field_proto.has_value_expression()) {
+ continue;
+ }
+
+ RequiredField required_field;
+ required_field.FromProto(required_field_proto);
+ required_fields.emplace_back(required_field);
+ }
-std::unique_ptr<FallbackData> UseAddressAction::CreateFallbackData(
- const autofill::AutofillProfile& profile) {
- auto fallback_data = std::make_unique<FallbackData>();
+ DCHECK(fallback_handler_ == nullptr);
+ fallback_handler_ = std::make_unique<RequiredFieldsFallbackHandler>(
+ required_fields,
+ field_formatter::CreateAutofillMappings(*profile_,
+ /* locale = */ "en-US"),
+ delegate_);
- fallback_data->AddFormGroup(profile);
- return fallback_data;
+ fallback_handler_->CheckAndFallbackRequiredFields(
+ status, base::BindOnce(&UseAddressAction::EndAction,
+ weak_ptr_factory_.GetWeakPtr()));
}
} // namespace autofill_assistant
diff --git a/chromium/components/autofill_assistant/browser/actions/use_address_action.h b/chromium/components/autofill_assistant/browser/actions/use_address_action.h
index 6366e7420fb..01c5c53c705 100644
--- a/chromium/components/autofill_assistant/browser/actions/use_address_action.h
+++ b/chromium/components/autofill_assistant/browser/actions/use_address_action.h
@@ -13,8 +13,8 @@
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
+#include "components/autofill/core/browser/data_model/autofill_profile.h"
#include "components/autofill_assistant/browser/actions/action.h"
-#include "components/autofill_assistant/browser/actions/fallback_handler/fallback_data.h"
#include "components/autofill_assistant/browser/actions/fallback_handler/required_fields_fallback_handler.h"
namespace autofill {
@@ -38,27 +38,20 @@ class UseAddressAction : public Action {
const base::Optional<ClientStatus>& optional_details_status =
base::nullopt);
- // Fill the form using data in client memory. Return whether filling succeeded
- // or not through OnFormFilled.
+ // Fill the form using |profile_|. Return whether filling succeeded or not
+ // through OnFormFilled.
void FillFormWithData();
void OnWaitForElement(const ClientStatus& element_status);
// Called when the address has been filled.
- void OnFormFilled(std::unique_ptr<FallbackData> fallback_data,
- const ClientStatus& status);
+ void ExecuteFallback(const ClientStatus& status);
- // Create fallback data.
- std::unique_ptr<FallbackData> CreateFallbackData(
- const autofill::AutofillProfile& profile);
-
- // Usage of the autofilled address.
- std::string name_;
- std::string prompt_;
+ // Note: |fallback_handler_| must be a member, because checking for fallbacks
+ // is asynchronous and the existence of the handler must be ensured.
+ std::unique_ptr<RequiredFieldsFallbackHandler> fallback_handler_;
+ std::unique_ptr<autofill::AutofillProfile> profile_;
Selector selector_;
- std::unique_ptr<RequiredFieldsFallbackHandler>
- required_fields_fallback_handler_;
-
ProcessActionCallback process_action_callback_;
base::WeakPtrFactory<UseAddressAction> weak_ptr_factory_{this};
diff --git a/chromium/components/autofill_assistant/browser/actions/use_address_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/use_address_action_unittest.cc
index 5017c774a1d..3827d03443c 100644
--- a/chromium/components/autofill_assistant/browser/actions/use_address_action_unittest.cc
+++ b/chromium/components/autofill_assistant/browser/actions/use_address_action_unittest.cc
@@ -7,6 +7,7 @@
#include <utility>
#include "base/guid.h"
+#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/gmock_callback_support.h"
@@ -16,12 +17,20 @@
#include "components/autofill/core/browser/data_model/autofill_profile.h"
#include "components/autofill_assistant/browser/actions/mock_action_delegate.h"
#include "components/autofill_assistant/browser/mock_personal_data_manager.h"
+#include "components/autofill_assistant/browser/user_model.h"
#include "components/autofill_assistant/browser/web/mock_web_controller.h"
#include "components/autofill_assistant/browser/web/web_controller_util.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace autofill_assistant {
namespace {
+const char kAddressName[] = "billing";
+const char kFakeSelector[] = "#selector";
+const char kFirstName[] = "FirstName";
+const char kLastName[] = "LastName";
+const char kEmail[] = "foobar@gmail.com";
+const char kPhoneNumber[] = "+41791234567";
+const char kModelIdentifier[] = "identifier";
using ::base::test::RunOnceCallback;
using ::testing::_;
@@ -30,25 +39,31 @@ using ::testing::Expectation;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::NotNull;
+using ::testing::Pointee;
using ::testing::Return;
using ::testing::SaveArgPointee;
class UseAddressActionTest : public testing::Test {
public:
void SetUp() override {
- // Build two identical autofill profiles. One for the memory, one for the
- // mock.
- auto autofill_profile = std::make_unique<autofill::AutofillProfile>(
- base::GenerateGUID(), autofill::test::kEmptyOrigin);
- autofill::test::SetProfileInfo(autofill_profile.get(), kFirstName, "",
- kLastName, kEmail, "", "", "", "", "", "",
- "", kPhoneNumber);
- user_data_.selected_addresses_[kAddressName] = std::move(autofill_profile);
-
- ON_CALL(mock_personal_data_manager_, GetProfileByGUID)
- .WillByDefault(Return(&autofill_profile_));
+ autofill::test::SetProfileInfo(&profile_, kFirstName, "", kLastName, kEmail,
+ "", "", "", "", "", "", "", kPhoneNumber);
+ // Store copies of |profile_| in |user_data_| and |user_model_|.
+ user_data_.selected_addresses_[kAddressName] =
+ std::make_unique<autofill::AutofillProfile>(profile_);
+ auto profiles = std::make_unique<
+ std::vector<std::unique_ptr<autofill::AutofillProfile>>>();
+ profiles->emplace_back(
+ std::make_unique<autofill::AutofillProfile>(profile_));
+ user_model_.SetAutofillProfiles(std::move(profiles));
+ ValueProto profile_value;
+ profile_value.mutable_profiles()->add_values()->set_guid(profile_.guid());
+ user_model_.SetValue(kModelIdentifier, profile_value);
+
ON_CALL(mock_action_delegate_, GetUserData)
.WillByDefault(Return(&user_data_));
+ ON_CALL(mock_action_delegate_, GetUserModel)
+ .WillByDefault(Return(&user_model_));
ON_CALL(mock_action_delegate_, GetPersonalDataManager)
.WillByDefault(Return(&mock_personal_data_manager_));
ON_CALL(mock_action_delegate_, RunElementChecks)
@@ -60,19 +75,11 @@ class UseAddressActionTest : public testing::Test {
}
protected:
- const char* const kAddressName = "billing";
- const char* const kFakeSelector = "#selector";
- const char* const kSelectionPrompt = "prompt";
- const char* const kFirstName = "FirstName";
- const char* const kLastName = "LastName";
- const char* const kEmail = "foobar@gmail.com";
- const char* const kPhoneNumber = "+41791234567";
-
ActionProto CreateUseAddressAction() {
ActionProto action;
UseAddressProto* use_address = action.mutable_use_address();
use_address->set_name(kAddressName);
- use_address->mutable_form_field_element()->add_selectors(kFakeSelector);
+ *use_address->mutable_form_field_element() = ToSelectorProto(kFakeSelector);
return action;
}
@@ -81,7 +88,7 @@ class UseAddressActionTest : public testing::Test {
std::string selector) {
auto* required_field = action->mutable_use_address()->add_required_fields();
required_field->set_value_expression(value_expression);
- required_field->mutable_element()->add_selectors(selector);
+ *required_field->mutable_element() = ToSelectorProto(selector);
return required_field;
}
@@ -98,8 +105,9 @@ class UseAddressActionTest : public testing::Test {
MockActionDelegate mock_action_delegate_;
MockWebController mock_web_controller_;
UserData user_data_;
-
- autofill::AutofillProfile autofill_profile_;
+ UserModel user_model_;
+ autofill::AutofillProfile profile_ = {base::GenerateGUID(),
+ autofill::test::kEmptyOrigin};
};
#if !defined(OS_ANDROID)
@@ -111,37 +119,97 @@ TEST_F(UseAddressActionTest, MAYBE_FillManually) {
InSequence seq;
ActionProto action_proto = CreateUseAddressAction();
- action_proto.mutable_use_address()->set_prompt(kSelectionPrompt);
EXPECT_EQ(ProcessedActionStatusProto::MANUAL_FALLBACK,
ProcessAction(action_proto));
}
-TEST_F(UseAddressActionTest, NoSelectedAddress) {
- InSequence seq;
+TEST_F(UseAddressActionTest, InvalidActionNoSelectorSet) {
+ ActionProto action;
+ action.mutable_use_address();
+ EXPECT_EQ(ProcessedActionStatusProto::INVALID_ACTION, ProcessAction(action));
+}
- ActionProto action_proto = CreateUseAddressAction();
- action_proto.mutable_use_address()->set_prompt(kSelectionPrompt);
+TEST_F(UseAddressActionTest, InvalidActionNameSetButEmpty) {
+ ActionProto action;
+ UseAddressProto* use_address = action.mutable_use_address();
+ *use_address->mutable_form_field_element() = ToSelectorProto(kFakeSelector);
+ use_address->set_name("");
+ EXPECT_EQ(ProcessedActionStatusProto::INVALID_ACTION, ProcessAction(action));
+}
- user_data_.selected_addresses_[kAddressName] = nullptr;
+TEST_F(UseAddressActionTest, InvalidActionSkipAutofillWithoutRequiredFields) {
+ ActionProto action;
+ UseAddressProto* use_address = action.mutable_use_address();
+ use_address->set_name(kAddressName);
+ use_address->set_skip_autofill(true);
+ EXPECT_EQ(ProcessedActionStatusProto::INVALID_ACTION, ProcessAction(action));
+}
+TEST_F(UseAddressActionTest, PreconditionFailedNoProfileForName) {
+ ActionProto action;
+ UseAddressProto* use_address = action.mutable_use_address();
+ *use_address->mutable_form_field_element() = ToSelectorProto(kFakeSelector);
+ use_address->set_name("invalid");
EXPECT_EQ(ProcessedActionStatusProto::PRECONDITION_FAILED,
- ProcessAction(action_proto));
+ ProcessAction(action));
}
-TEST_F(UseAddressActionTest, InvalidActionSkipAutofillWithoutRequiredFields) {
+TEST_F(UseAddressActionTest, ResolveProfileByNameSucceeds) {
+ ON_CALL(mock_action_delegate_,
+ OnShortWaitForElement(Selector({kFakeSelector}).MustBeVisible(), _))
+ .WillByDefault(RunOnceCallback<1>(OkClientStatus()));
+ ON_CALL(mock_web_controller_, OnGetFieldValue(_, _))
+ .WillByDefault(RunOnceCallback<1>(OkClientStatus(), "not empty"));
+
ActionProto action;
UseAddressProto* use_address = action.mutable_use_address();
+ *use_address->mutable_form_field_element() = ToSelectorProto(kFakeSelector);
use_address->set_name(kAddressName);
- use_address->set_skip_autofill(true);
+ EXPECT_CALL(mock_action_delegate_,
+ OnFillAddressForm(Pointee(Eq(profile_)), _, _))
+ .WillOnce(RunOnceCallback<2>(OkClientStatus()));
+ EXPECT_EQ(ProcessedActionStatusProto::ACTION_APPLIED, ProcessAction(action));
+}
+
+TEST_F(UseAddressActionTest, InvalidActionModelIdentifierSetButEmpty) {
+ ActionProto action;
+ UseAddressProto* use_address = action.mutable_use_address();
+ *use_address->mutable_form_field_element() = ToSelectorProto(kFakeSelector);
+ use_address->set_model_identifier("");
EXPECT_EQ(ProcessedActionStatusProto::INVALID_ACTION, ProcessAction(action));
}
+TEST_F(UseAddressActionTest, PreconditionFailedNoProfileForModelIdentifier) {
+ ActionProto action;
+ UseAddressProto* use_address = action.mutable_use_address();
+ *use_address->mutable_form_field_element() = ToSelectorProto(kFakeSelector);
+ use_address->set_model_identifier("invalid");
+ EXPECT_EQ(ProcessedActionStatusProto::PRECONDITION_FAILED,
+ ProcessAction(action));
+}
+
+TEST_F(UseAddressActionTest, ResolveProfileByModelIdentifierSucceeds) {
+ ON_CALL(mock_action_delegate_,
+ OnShortWaitForElement(Selector({kFakeSelector}).MustBeVisible(), _))
+ .WillByDefault(RunOnceCallback<1>(OkClientStatus()));
+ ON_CALL(mock_web_controller_, OnGetFieldValue(_, _))
+ .WillByDefault(RunOnceCallback<1>(OkClientStatus(), "not empty"));
+
+ ActionProto action;
+ UseAddressProto* use_address = action.mutable_use_address();
+ *use_address->mutable_form_field_element() = ToSelectorProto(kFakeSelector);
+ use_address->set_model_identifier(kModelIdentifier);
+ EXPECT_CALL(mock_action_delegate_,
+ OnFillAddressForm(Pointee(Eq(profile_)), _, _))
+ .WillOnce(RunOnceCallback<2>(OkClientStatus()));
+ EXPECT_EQ(ProcessedActionStatusProto::ACTION_APPLIED, ProcessAction(action));
+}
+
TEST_F(UseAddressActionTest, PreconditionFailedPopulatesUnexpectedErrorInfo) {
InSequence seq;
ActionProto action_proto = CreateUseAddressAction();
- action_proto.mutable_use_address()->set_prompt(kSelectionPrompt);
user_data_.selected_addresses_[kAddressName] = nullptr;
user_data_.selected_addresses_["one_more"] = nullptr;
@@ -185,16 +253,22 @@ TEST_F(UseAddressActionTest, ValidationSucceeds) {
ActionProto action_proto = CreateUseAddressAction();
AddRequiredField(&action_proto,
- base::NumberToString(
- static_cast<int>(autofill::ServerFieldType::NAME_FIRST)),
+ base::StrCat({"${",
+ base::NumberToString(static_cast<int>(
+ autofill::ServerFieldType::NAME_FIRST)),
+ "}"}),
"#first_name");
AddRequiredField(&action_proto,
- base::NumberToString(
- static_cast<int>(autofill::ServerFieldType::NAME_LAST)),
+ base::StrCat({"${",
+ base::NumberToString(static_cast<int>(
+ autofill::ServerFieldType::NAME_LAST)),
+ "}"}),
"#last_name");
AddRequiredField(&action_proto,
- base::NumberToString(static_cast<int>(
- autofill::ServerFieldType::EMAIL_ADDRESS)),
+ base::StrCat({"${",
+ base::NumberToString(static_cast<int>(
+ autofill::ServerFieldType::EMAIL_ADDRESS)),
+ "}"}),
"#email");
// Autofill succeeds.
@@ -217,16 +291,22 @@ TEST_F(UseAddressActionTest, FallbackFails) {
ActionProto action_proto = CreateUseAddressAction();
AddRequiredField(&action_proto,
- base::NumberToString(
- static_cast<int>(autofill::ServerFieldType::NAME_FIRST)),
+ base::StrCat({"${",
+ base::NumberToString(static_cast<int>(
+ autofill::ServerFieldType::NAME_FIRST)),
+ "}"}),
"#first_name");
AddRequiredField(&action_proto,
- base::NumberToString(
- static_cast<int>(autofill::ServerFieldType::NAME_LAST)),
+ base::StrCat({"${",
+ base::NumberToString(static_cast<int>(
+ autofill::ServerFieldType::NAME_LAST)),
+ "}"}),
"#last_name");
AddRequiredField(&action_proto,
- base::NumberToString(static_cast<int>(
- autofill::ServerFieldType::EMAIL_ADDRESS)),
+ base::StrCat({"${",
+ base::NumberToString(static_cast<int>(
+ autofill::ServerFieldType::EMAIL_ADDRESS)),
+ "}"}),
"#email");
// Autofill succeeds.
@@ -251,8 +331,23 @@ TEST_F(UseAddressActionTest, FallbackFails) {
OnSetFieldValue(Eq(Selector({"#first_name"})), kFirstName, _))
.WillOnce(RunOnceCallback<2>(ClientStatus(OTHER_ACTION_STATUS)));
- EXPECT_EQ(ProcessedActionStatusProto::AUTOFILL_INCOMPLETE,
- ProcessAction(action_proto));
+ ProcessedActionProto processed_action;
+ EXPECT_CALL(callback_, Run(_)).WillOnce(SaveArgPointee<0>(&processed_action));
+
+ UseAddressAction action(&mock_action_delegate_, action_proto);
+ action.ProcessAction(callback_.Get());
+
+ EXPECT_EQ(processed_action.status(),
+ ProcessedActionStatusProto::AUTOFILL_INCOMPLETE);
+ EXPECT_TRUE(processed_action.has_status_details());
+ EXPECT_EQ(processed_action.status_details()
+ .autofill_error_info()
+ .autofill_field_error_size(),
+ 1);
+ EXPECT_EQ(OTHER_ACTION_STATUS, processed_action.status_details()
+ .autofill_error_info()
+ .autofill_field_error(0)
+ .status());
}
TEST_F(UseAddressActionTest, FallbackSucceeds) {
@@ -261,16 +356,22 @@ TEST_F(UseAddressActionTest, FallbackSucceeds) {
ActionProto action_proto = CreateUseAddressAction();
AddRequiredField(&action_proto,
- base::NumberToString(
- static_cast<int>(autofill::ServerFieldType::NAME_FIRST)),
+ base::StrCat({"${",
+ base::NumberToString(static_cast<int>(
+ autofill::ServerFieldType::NAME_FIRST)),
+ "}"}),
"#first_name");
AddRequiredField(&action_proto,
- base::NumberToString(
- static_cast<int>(autofill::ServerFieldType::NAME_LAST)),
+ base::StrCat({"${",
+ base::NumberToString(static_cast<int>(
+ autofill::ServerFieldType::NAME_LAST)),
+ "}"}),
"#last_name");
AddRequiredField(&action_proto,
- base::NumberToString(static_cast<int>(
- autofill::ServerFieldType::EMAIL_ADDRESS)),
+ base::StrCat({"${",
+ base::NumberToString(static_cast<int>(
+ autofill::ServerFieldType::EMAIL_ADDRESS)),
+ "}"}),
"#email");
// Autofill succeeds.
@@ -331,8 +432,10 @@ TEST_F(UseAddressActionTest,
ActionProto action_proto = CreateUseAddressAction();
AddRequiredField(&action_proto,
- base::NumberToString(
- static_cast<int>(autofill::ServerFieldType::NAME_FIRST)),
+ base::StrCat({"${",
+ base::NumberToString(static_cast<int>(
+ autofill::ServerFieldType::NAME_FIRST)),
+ "}"}),
"#first_name");
EXPECT_CALL(mock_action_delegate_,
@@ -411,8 +514,10 @@ TEST_F(UseAddressActionTest, ForcedFallbackWithKeystrokes) {
ActionProto action_proto = CreateUseAddressAction();
auto* name_required = AddRequiredField(
&action_proto,
- base::NumberToString(
- static_cast<int>(autofill::ServerFieldType::NAME_FIRST)),
+ base::StrCat({"${",
+ base::NumberToString(static_cast<int>(
+ autofill::ServerFieldType::NAME_FIRST)),
+ "}"}),
"#first_name");
name_required->set_forced(true);
name_required->set_fill_strategy(SIMULATE_KEY_PRESSES);
@@ -428,7 +533,8 @@ TEST_F(UseAddressActionTest, ForcedFallbackWithKeystrokes) {
ON_CALL(mock_web_controller_, OnGetFieldValue(_, _))
.WillByDefault(RunOnceCallback<1>(OkClientStatus(), "not empty"));
- // But we still want the first name filled, with simulated keypresses.
+ // But we still want the first name filled, with
+ // simulated keypresses.
EXPECT_CALL(mock_action_delegate_, OnSetFieldValue(Selector({"#first_name"}),
kFirstName, true, 1000, _))
.WillOnce(RunOnceCallback<4>(OkClientStatus()));
@@ -444,11 +550,14 @@ TEST_F(UseAddressActionTest, SkippingAutofill) {
ActionProto action_proto;
action_proto.mutable_use_address()->set_name(kAddressName);
AddRequiredField(&action_proto,
- base::NumberToString(
- static_cast<int>(autofill::ServerFieldType::NAME_FIRST)),
+ base::StrCat({"${",
+ base::NumberToString(static_cast<int>(
+ autofill::ServerFieldType::NAME_FIRST)),
+ "}"}),
"#first_name");
action_proto.mutable_use_address()->set_skip_autofill(true);
+ EXPECT_CALL(mock_action_delegate_, OnShortWaitForElement(_, _)).Times(0);
EXPECT_CALL(mock_action_delegate_, OnFillAddressForm(_, _, _)).Times(0);
// First validation fails.
diff --git a/chromium/components/autofill_assistant/browser/actions/use_credit_card_action.cc b/chromium/components/autofill_assistant/browser/actions/use_credit_card_action.cc
index 655533eddeb..717f02e68a2 100644
--- a/chromium/components/autofill_assistant/browser/actions/use_credit_card_action.cc
+++ b/chromium/components/autofill_assistant/browser/actions/use_credit_card_action.cc
@@ -4,12 +4,14 @@
#include "components/autofill_assistant/browser/actions/use_credit_card_action.h"
+#include <map>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/optional.h"
+#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
@@ -17,10 +19,11 @@
#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/actions/action_delegate.h"
-#include "components/autofill_assistant/browser/actions/fallback_handler/fallback_data.h"
#include "components/autofill_assistant/browser/actions/fallback_handler/required_field.h"
#include "components/autofill_assistant/browser/actions/fallback_handler/required_fields_fallback_handler.h"
#include "components/autofill_assistant/browser/client_status.h"
+#include "components/autofill_assistant/browser/field_formatter.h"
+#include "components/autofill_assistant/browser/user_model.h"
namespace autofill_assistant {
@@ -28,21 +31,6 @@ UseCreditCardAction::UseCreditCardAction(ActionDelegate* delegate,
const ActionProto& proto)
: Action(delegate, proto) {
DCHECK(proto.has_use_card());
- prompt_ = proto.use_card().prompt();
- std::vector<RequiredField> required_fields;
- for (const auto& required_field_proto : proto_.use_card().required_fields()) {
- if (required_field_proto.value_expression().empty()) {
- DVLOG(3) << "No fallback filling information provided, skipping field";
- continue;
- }
-
- required_fields.emplace_back();
- required_fields.back().FromProto(required_field_proto);
- }
-
- required_fields_fallback_handler_ =
- std::make_unique<RequiredFieldsFallbackHandler>(required_fields,
- delegate);
selector_ = Selector(proto.use_card().form_field_element());
selector_.MustBeVisible();
}
@@ -66,11 +54,45 @@ void UseCreditCardAction::InternalProcessAction(
}
// Ensure data already selected in a previous action.
- auto* user_data = delegate_->GetUserData();
- if (user_data->selected_card_.get() == nullptr) {
- EndAction(ClientStatus(PRECONDITION_FAILED));
- return;
+ if (proto_.use_card().has_model_identifier()) {
+ if (proto_.use_card().model_identifier().empty()) {
+ VLOG(1) << "UseCreditCard failed: |model_identifier| set but empty";
+ EndAction(ClientStatus(INVALID_ACTION));
+ return;
+ }
+ auto credit_card_value = delegate_->GetUserModel()->GetValue(
+ proto_.use_card().model_identifier());
+ if (!credit_card_value.has_value()) {
+ VLOG(1) << "UseCreditCard failed: "
+ << proto_.use_card().model_identifier()
+ << " not found in user model";
+ EndAction(ClientStatus(PRECONDITION_FAILED));
+ return;
+ }
+ if (credit_card_value->credit_cards().values().size() != 1) {
+ VLOG(1) << "UseCreditCard failed: expected single card for "
+ << proto_.use_card().model_identifier() << ", but got "
+ << *credit_card_value;
+ }
+ auto* credit_card = delegate_->GetUserModel()->GetCreditCard(
+ credit_card_value->credit_cards().values(0).guid());
+ if (credit_card == nullptr) {
+ VLOG(1) << "UseCreditCard failed: card not found for guid "
+ << *credit_card_value;
+ EndAction(ClientStatus(PRECONDITION_FAILED));
+ return;
+ }
+ credit_card_ = std::make_unique<autofill::CreditCard>(*credit_card);
+ } else {
+ auto* credit_card = delegate_->GetUserData()->selected_card_.get();
+ if (credit_card == nullptr) {
+ VLOG(1) << "UseCreditCard failed: card not found in user_data";
+ EndAction(ClientStatus(PRECONDITION_FAILED));
+ return;
+ }
+ credit_card_ = std::make_unique<autofill::CreditCard>(*credit_card);
}
+ DCHECK(credit_card_ != nullptr);
FillFormWithData();
}
@@ -87,13 +109,12 @@ void UseCreditCardAction::EndAction(
}
void UseCreditCardAction::FillFormWithData() {
- if (proto_.use_card().skip_autofill()) {
- delegate_->GetFullCard(base::BindOnce(&UseCreditCardAction::OnGetFullCard,
- weak_ptr_factory_.GetWeakPtr()));
+ if (selector_.empty()) {
+ DCHECK(proto_.use_card().skip_autofill());
+ OnWaitForElement(OkClientStatus());
return;
}
- DCHECK(!selector_.empty());
delegate_->ShortWaitForElement(
selector_, base::BindOnce(&UseCreditCardAction::OnWaitForElement,
weak_ptr_factory_.GetWeakPtr()));
@@ -105,7 +126,9 @@ void UseCreditCardAction::OnWaitForElement(const ClientStatus& element_status) {
return;
}
- delegate_->GetFullCard(base::BindOnce(&UseCreditCardAction::OnGetFullCard,
+ DCHECK(credit_card_ != nullptr);
+ delegate_->GetFullCard(credit_card_.get(),
+ base::BindOnce(&UseCreditCardAction::OnGetFullCard,
weak_ptr_factory_.GetWeakPtr()));
}
@@ -117,49 +140,49 @@ void UseCreditCardAction::OnGetFullCard(
return;
}
- auto fallback_data = CreateFallbackData(cvc, *card);
+ std::vector<RequiredField> required_fields;
+ for (const auto& required_field_proto : proto_.use_card().required_fields()) {
+ if (!required_field_proto.has_value_expression()) {
+ continue;
+ }
+
+ RequiredField required_field;
+ required_field.FromProto(required_field_proto);
+ required_fields.emplace_back(required_field);
+ }
+
+ std::map<std::string, std::string> fallback_values =
+ field_formatter::CreateAutofillMappings(*card,
+ /* locale = */ "en-US");
+ fallback_values.emplace(
+ base::NumberToString(
+ static_cast<int>(AutofillFormatProto::CREDIT_CARD_VERIFICATION_CODE)),
+ base::UTF16ToUTF8(cvc));
+ fallback_values.emplace(
+ base::NumberToString(
+ static_cast<int>(AutofillFormatProto::CREDIT_CARD_RAW_NUMBER)),
+ base::UTF16ToUTF8(card->GetRawInfo(autofill::CREDIT_CARD_NUMBER)));
+
+ DCHECK(fallback_handler_ == nullptr);
+ fallback_handler_ = std::make_unique<RequiredFieldsFallbackHandler>(
+ required_fields, fallback_values, delegate_);
if (proto_.use_card().skip_autofill()) {
- required_fields_fallback_handler_->CheckAndFallbackRequiredFields(
- OkClientStatus(), std::move(fallback_data),
- base::BindOnce(&UseCreditCardAction::EndAction,
- weak_ptr_factory_.GetWeakPtr()));
+ ExecuteFallback(OkClientStatus());
return;
}
- delegate_->FillCardForm(
- std::move(card), cvc, selector_,
- base::BindOnce(&UseCreditCardAction::OnFormFilled,
- weak_ptr_factory_.GetWeakPtr(), std::move(fallback_data)));
+ DCHECK(!selector_.empty());
+ delegate_->FillCardForm(std::move(card), cvc, selector_,
+ base::BindOnce(&UseCreditCardAction::ExecuteFallback,
+ weak_ptr_factory_.GetWeakPtr()));
}
-void UseCreditCardAction::OnFormFilled(
- std::unique_ptr<FallbackData> fallback_data,
- const ClientStatus& status) {
- required_fields_fallback_handler_->CheckAndFallbackRequiredFields(
- status, std::move(fallback_data),
- base::BindOnce(&UseCreditCardAction::EndAction,
- weak_ptr_factory_.GetWeakPtr()));
+void UseCreditCardAction::ExecuteFallback(const ClientStatus& status) {
+ DCHECK(fallback_handler_ != nullptr);
+ fallback_handler_->CheckAndFallbackRequiredFields(
+ status, base::BindOnce(&UseCreditCardAction::EndAction,
+ weak_ptr_factory_.GetWeakPtr()));
}
-std::unique_ptr<FallbackData> UseCreditCardAction::CreateFallbackData(
- const base::string16& cvc,
- const autofill::CreditCard& card) {
- auto fallback_data = std::make_unique<FallbackData>();
-
- fallback_data->field_values.emplace(
- static_cast<int>(
- UseCreditCardProto::RequiredField::CREDIT_CARD_VERIFICATION_CODE),
- base::UTF16ToUTF8(cvc));
- fallback_data->field_values.emplace(
- (int)UseCreditCardProto::RequiredField::CREDIT_CARD_RAW_NUMBER,
- base::UTF16ToUTF8(card.GetRawInfo(autofill::CREDIT_CARD_NUMBER)));
- fallback_data->field_values.emplace(
- static_cast<int>(UseCreditCardProto::RequiredField::CREDIT_CARD_NETWORK),
- autofill::data_util::GetPaymentRequestData(card.network())
- .basic_card_issuer_network);
-
- fallback_data->AddFormGroup(card);
- return fallback_data;
-}
} // namespace autofill_assistant
diff --git a/chromium/components/autofill_assistant/browser/actions/use_credit_card_action.h b/chromium/components/autofill_assistant/browser/actions/use_credit_card_action.h
index 99191230fec..7bf36ff9eda 100644
--- a/chromium/components/autofill_assistant/browser/actions/use_credit_card_action.h
+++ b/chromium/components/autofill_assistant/browser/actions/use_credit_card_action.h
@@ -14,7 +14,6 @@
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "components/autofill_assistant/browser/actions/action.h"
-#include "components/autofill_assistant/browser/actions/fallback_handler/fallback_data.h"
#include "components/autofill_assistant/browser/actions/fallback_handler/required_fields_fallback_handler.h"
namespace autofill {
@@ -49,20 +48,14 @@ class UseCreditCardAction : public Action {
const base::string16& cvc);
// Called when the form credit card has been filled.
- void OnFormFilled(std::unique_ptr<FallbackData> fallback_data,
- const ClientStatus& status);
+ void ExecuteFallback(const ClientStatus& status);
- // Create fallback data.
- std::unique_ptr<FallbackData> CreateFallbackData(
- const base::string16& cvc,
- const autofill::CreditCard& card);
-
- std::string prompt_;
+ // Note: |fallback_handler_| must be a member, because checking for fallbacks
+ // is asynchronous and the existence of the handler must be ensured.
+ std::unique_ptr<RequiredFieldsFallbackHandler> fallback_handler_;
+ std::unique_ptr<autofill::CreditCard> credit_card_;
Selector selector_;
- std::unique_ptr<RequiredFieldsFallbackHandler>
- required_fields_fallback_handler_;
-
ProcessActionCallback process_action_callback_;
base::WeakPtrFactory<UseCreditCardAction> weak_ptr_factory_{this};
diff --git a/chromium/components/autofill_assistant/browser/actions/use_credit_card_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/use_credit_card_action_unittest.cc
index 7d737b8b98b..a81d311d961 100644
--- a/chromium/components/autofill_assistant/browser/actions/use_credit_card_action_unittest.cc
+++ b/chromium/components/autofill_assistant/browser/actions/use_credit_card_action_unittest.cc
@@ -7,45 +7,63 @@
#include <utility>
#include "base/guid.h"
+#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "components/autofill/core/browser/autofill_test_utils.h"
-#include "components/autofill/core/browser/data_model/autofill_profile.h"
#include "components/autofill_assistant/browser/actions/mock_action_delegate.h"
#include "components/autofill_assistant/browser/mock_personal_data_manager.h"
+#include "components/autofill_assistant/browser/user_model.h"
#include "components/autofill_assistant/browser/web/mock_web_controller.h"
#include "components/autofill_assistant/browser/web/web_controller_util.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace autofill_assistant {
namespace {
+const char kFakeSelector[] = "#selector";
+const char kFakeCvc[] = "123";
+const char kModelIdentifier[] = "identifier";
+const char kCardName[] = "Adam West";
+const char kCardNumber[] = "4111111111111111";
+const char kExpirationMonth[] = "9";
+const char kExpirationYear[] = "2050";
using ::base::test::RunOnceCallback;
using ::testing::_;
using ::testing::Eq;
using ::testing::Expectation;
+using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::NotNull;
+using ::testing::Pointee;
using ::testing::Return;
using ::testing::SaveArgPointee;
class UseCreditCardActionTest : public testing::Test {
public:
void SetUp() override {
- // Build two identical autofill profiles. One for the memory, one for the
- // mock.
- auto autofill_profile = std::make_unique<autofill::AutofillProfile>(
- base::GenerateGUID(), autofill::test::kEmptyOrigin);
- autofill::test::SetProfileInfo(autofill_profile.get(), kFirstName, "",
- kLastName, kEmail, "", "", "", "", "", "",
- "", "");
- user_data_.selected_addresses_[kAddressName] = std::move(autofill_profile);
- ON_CALL(mock_personal_data_manager_, GetProfileByGUID)
- .WillByDefault(Return(&autofill_profile_));
+ autofill::test::SetCreditCardInfo(&credit_card_, kCardName, kCardNumber,
+ kExpirationMonth, kExpirationYear,
+ /* billing_address_id= */ "");
+
+ // Store copies of |credit_card_| in |user_data_| and |user_model_|.
+ user_data_.selected_card_ =
+ std::make_unique<autofill::CreditCard>(credit_card_);
+ auto cards =
+ std::make_unique<std::vector<std::unique_ptr<autofill::CreditCard>>>();
+ cards->emplace_back(std::make_unique<autofill::CreditCard>(credit_card_));
+ user_model_.SetAutofillCreditCards(std::move(cards));
+ ValueProto card_value;
+ card_value.mutable_credit_cards()->add_values()->set_guid(
+ credit_card_.guid());
+ user_model_.SetValue(kModelIdentifier, card_value);
+
ON_CALL(mock_action_delegate_, GetUserData)
.WillByDefault(Return(&user_data_));
+ ON_CALL(mock_action_delegate_, GetUserModel)
+ .WillByDefault(Return(&user_model_));
ON_CALL(mock_action_delegate_, GetPersonalDataManager)
.WillByDefault(Return(&mock_personal_data_manager_));
ON_CALL(mock_action_delegate_, RunElementChecks)
@@ -54,20 +72,23 @@ class UseCreditCardActionTest : public testing::Test {
}));
ON_CALL(mock_action_delegate_, OnShortWaitForElement(_, _))
.WillByDefault(RunOnceCallback<1>(OkClientStatus()));
+ ON_CALL(mock_action_delegate_, OnGetFullCard)
+ .WillByDefault(Invoke([](const autofill::CreditCard* credit_card,
+ base::OnceCallback<void(
+ std::unique_ptr<autofill::CreditCard> card,
+ const base::string16& cvc)>& callback) {
+ std::move(callback).Run(
+ credit_card ? std::make_unique<autofill::CreditCard>(*credit_card)
+ : nullptr,
+ base::UTF8ToUTF16(kFakeCvc));
+ }));
}
protected:
- const char* const kAddressName = "billing";
- const char* const kFakeSelector = "#selector";
- const char* const kSelectionPrompt = "prompt";
- const char* const kFirstName = "FirstName";
- const char* const kLastName = "LastName";
- const char* const kEmail = "foobar@gmail.com";
-
ActionProto CreateUseCreditCardAction() {
ActionProto action;
- action.mutable_use_card()->mutable_form_field_element()->add_selectors(
- kFakeSelector);
+ *action.mutable_use_card()->mutable_form_field_element() =
+ ToSelectorProto(kFakeSelector);
return action;
}
@@ -77,14 +98,14 @@ class UseCreditCardActionTest : public testing::Test {
std::string selector) {
auto* required_field = action->mutable_use_card()->add_required_fields();
required_field->set_value_expression(value_expression);
- required_field->mutable_element()->add_selectors(selector);
+ *required_field->mutable_element() = ToSelectorProto(selector);
return required_field;
}
ActionProto CreateUseCardAction() {
ActionProto action;
UseCreditCardProto* use_card = action.mutable_use_card();
- use_card->mutable_form_field_element()->add_selectors(kFakeSelector);
+ *use_card->mutable_form_field_element() = ToSelectorProto(kFakeSelector);
return action;
}
@@ -101,14 +122,15 @@ class UseCreditCardActionTest : public testing::Test {
MockActionDelegate mock_action_delegate_;
MockWebController mock_web_controller_;
UserData user_data_;
-
- autofill::AutofillProfile autofill_profile_;
+ UserModel user_model_;
+ autofill::CreditCard credit_card_ = {base::GenerateGUID(),
+ autofill::test::kEmptyOrigin};
};
-TEST_F(UseCreditCardActionTest, FillCreditCardNoCardSelected) {
- ActionProto action = CreateUseCreditCardAction();
- EXPECT_EQ(ProcessedActionStatusProto::PRECONDITION_FAILED,
- ProcessAction(action));
+TEST_F(UseCreditCardActionTest, InvalidActionNoSelectorSet) {
+ ActionProto action;
+ action.mutable_use_card();
+ EXPECT_EQ(ProcessedActionStatusProto::INVALID_ACTION, ProcessAction(action));
}
TEST_F(UseCreditCardActionTest,
@@ -119,16 +141,74 @@ TEST_F(UseCreditCardActionTest,
EXPECT_EQ(ProcessedActionStatusProto::INVALID_ACTION, ProcessAction(action));
}
+TEST_F(UseCreditCardActionTest, PreconditionFailedNoCreditCardInUserData) {
+ ActionProto action;
+ auto* use_card = action.mutable_use_card();
+ *use_card->mutable_form_field_element() = ToSelectorProto(kFakeSelector);
+ user_data_.selected_card_.reset();
+ EXPECT_EQ(ProcessedActionStatusProto::PRECONDITION_FAILED,
+ ProcessAction(action));
+}
+
+TEST_F(UseCreditCardActionTest, CreditCardInUserDataSucceeds) {
+ ON_CALL(mock_action_delegate_,
+ OnShortWaitForElement(Selector({kFakeSelector}).MustBeVisible(), _))
+ .WillByDefault(RunOnceCallback<1>(OkClientStatus()));
+ ON_CALL(mock_web_controller_, OnGetFieldValue(_, _))
+ .WillByDefault(RunOnceCallback<1>(OkClientStatus(), "not empty"));
+ ActionProto action;
+ auto* use_card = action.mutable_use_card();
+ *use_card->mutable_form_field_element() = ToSelectorProto(kFakeSelector);
+ EXPECT_CALL(
+ mock_action_delegate_,
+ OnFillCardForm(Pointee(Eq(credit_card_)), base::UTF8ToUTF16(kFakeCvc),
+ Selector({kFakeSelector}).MustBeVisible(), _))
+ .WillOnce(RunOnceCallback<3>(OkClientStatus()));
+ EXPECT_EQ(ProcessedActionStatusProto::ACTION_APPLIED, ProcessAction(action));
+}
+
+TEST_F(UseCreditCardActionTest, InvalidActionModelIdentifierSetButEmpty) {
+ ActionProto action;
+ auto* use_card = action.mutable_use_card();
+ *use_card->mutable_form_field_element() = ToSelectorProto(kFakeSelector);
+ use_card->set_model_identifier("");
+ EXPECT_EQ(ProcessedActionStatusProto::INVALID_ACTION, ProcessAction(action));
+}
+
+TEST_F(UseCreditCardActionTest,
+ PreconditionFailedNoCreditCardForModelIdentifier) {
+ ActionProto action;
+ auto* use_card = action.mutable_use_card();
+ *use_card->mutable_form_field_element() = ToSelectorProto(kFakeSelector);
+ use_card->set_model_identifier("invalid");
+ EXPECT_EQ(ProcessedActionStatusProto::PRECONDITION_FAILED,
+ ProcessAction(action));
+}
+
+TEST_F(UseCreditCardActionTest, CreditCardInUserModelSucceeds) {
+ ON_CALL(mock_action_delegate_,
+ OnShortWaitForElement(Selector({kFakeSelector}).MustBeVisible(), _))
+ .WillByDefault(RunOnceCallback<1>(OkClientStatus()));
+ ON_CALL(mock_web_controller_, OnGetFieldValue(_, _))
+ .WillByDefault(RunOnceCallback<1>(OkClientStatus(), "not empty"));
+ ActionProto action;
+ auto* use_card = action.mutable_use_card();
+ *use_card->mutable_form_field_element() = ToSelectorProto(kFakeSelector);
+ use_card->set_model_identifier(kModelIdentifier);
+ EXPECT_CALL(
+ mock_action_delegate_,
+ OnFillCardForm(Pointee(Eq(credit_card_)), base::UTF8ToUTF16(kFakeCvc),
+ Selector({kFakeSelector}).MustBeVisible(), _))
+ .WillOnce(RunOnceCallback<3>(OkClientStatus()));
+ EXPECT_EQ(ProcessedActionStatusProto::ACTION_APPLIED, ProcessAction(action));
+}
+
TEST_F(UseCreditCardActionTest, FillCreditCard) {
ActionProto action = CreateUseCreditCardAction();
- autofill::CreditCard credit_card;
- user_data_.selected_card_ =
- std::make_unique<autofill::CreditCard>(credit_card);
- EXPECT_CALL(mock_action_delegate_, OnGetFullCard(_))
- .WillOnce(RunOnceCallback<0>(credit_card, base::UTF8ToUTF16("123")));
+ user_data_.selected_card_ = std::make_unique<autofill::CreditCard>();
EXPECT_CALL(mock_action_delegate_,
- OnFillCardForm(_, base::UTF8ToUTF16("123"),
+ OnFillCardForm(_, base::UTF8ToUTF16(kFakeCvc),
Selector({kFakeSelector}).MustBeVisible(), _))
.WillOnce(RunOnceCallback<3>(OkClientStatus()));
@@ -141,23 +221,18 @@ TEST_F(UseCreditCardActionTest, FillCreditCardRequiredFieldsFilled) {
.WillByDefault(RunOnceCallback<1>(OkClientStatus(), "not empty"));
ActionProto action = CreateUseCreditCardAction();
- AddRequiredField(
- &action,
- base::NumberToString(static_cast<int>(
- UseCreditCardProto::RequiredField::CREDIT_CARD_VERIFICATION_CODE)),
- "#cvc");
+ AddRequiredField(&action,
+ base::NumberToString(static_cast<int>(
+ AutofillFormatProto::CREDIT_CARD_VERIFICATION_CODE)),
+ "#cvc");
AddRequiredField(&action,
base::NumberToString(static_cast<int>(
autofill::ServerFieldType::CREDIT_CARD_EXP_MONTH)),
"#expmonth");
- autofill::CreditCard credit_card;
- user_data_.selected_card_ =
- std::make_unique<autofill::CreditCard>(credit_card);
- EXPECT_CALL(mock_action_delegate_, OnGetFullCard(_))
- .WillOnce(RunOnceCallback<0>(credit_card, base::UTF8ToUTF16("123")));
+ user_data_.selected_card_ = std::make_unique<autofill::CreditCard>();
EXPECT_CALL(mock_action_delegate_,
- OnFillCardForm(_, base::UTF8ToUTF16("123"),
+ OnFillCardForm(_, base::UTF8ToUTF16(kFakeCvc),
Selector({kFakeSelector}).MustBeVisible(), _))
.WillOnce(RunOnceCallback<3>(OkClientStatus()));
@@ -171,34 +246,53 @@ TEST_F(UseCreditCardActionTest, FillCreditCardWithFallback) {
ActionProto action = CreateUseCreditCardAction();
AddRequiredField(
&action,
- base::NumberToString(static_cast<int>(
- UseCreditCardProto::RequiredField::CREDIT_CARD_VERIFICATION_CODE)),
+ base::StrCat({"${",
+ base::NumberToString(static_cast<int>(
+ AutofillFormatProto::CREDIT_CARD_VERIFICATION_CODE)),
+ "}"}),
"#cvc");
- AddRequiredField(&action,
- base::NumberToString(static_cast<int>(
- autofill::ServerFieldType::CREDIT_CARD_EXP_MONTH)),
- "#expmonth");
AddRequiredField(
&action,
- base::NumberToString(static_cast<int>(
- autofill::ServerFieldType::CREDIT_CARD_EXP_2_DIGIT_YEAR)),
+ base::StrCat({"${",
+ base::NumberToString(static_cast<int>(
+ autofill::ServerFieldType::CREDIT_CARD_EXP_MONTH)),
+ "}"}),
+ "#expmonth");
+ AddRequiredField(
+ &action,
+ base::StrCat(
+ {"${",
+ base::NumberToString(static_cast<int>(
+ autofill::ServerFieldType::CREDIT_CARD_EXP_2_DIGIT_YEAR)),
+ "}"}),
"#expyear2");
AddRequiredField(
&action,
- base::NumberToString(static_cast<int>(
- autofill::ServerFieldType::CREDIT_CARD_EXP_4_DIGIT_YEAR)),
+ base::StrCat(
+ {"${",
+ base::NumberToString(static_cast<int>(
+ autofill::ServerFieldType::CREDIT_CARD_EXP_4_DIGIT_YEAR)),
+ "}"}),
"#expyear4");
+ AddRequiredField(
+ &action,
+ base::StrCat({"${",
+ base::NumberToString(static_cast<int>(
+ autofill::ServerFieldType::CREDIT_CARD_NAME_FULL)),
+ "}"}),
+ "#card_name");
+ AddRequiredField(
+ &action,
+ base::StrCat({"${",
+ base::NumberToString(static_cast<int>(
+ autofill::ServerFieldType::CREDIT_CARD_NUMBER)),
+ "}"}),
+ "#card_number");
AddRequiredField(&action,
- base::NumberToString(static_cast<int>(
- autofill::ServerFieldType::CREDIT_CARD_NAME_FULL)),
- "#card_name");
- AddRequiredField(&action,
- base::NumberToString(static_cast<int>(
- autofill::ServerFieldType::CREDIT_CARD_NUMBER)),
- "#card_number");
- AddRequiredField(&action,
- base::NumberToString(static_cast<int>(
- UseCreditCardProto::RequiredField::CREDIT_CARD_NETWORK)),
+ base::StrCat({"${",
+ base::NumberToString(static_cast<int>(
+ AutofillFormatProto::CREDIT_CARD_NETWORK)),
+ "}"}),
"#network");
// First validation fails.
@@ -222,7 +316,7 @@ TEST_F(UseCreditCardActionTest, FillCreditCardWithFallback) {
// Expect fields to be filled
Expectation set_cvc =
EXPECT_CALL(mock_action_delegate_,
- OnSetFieldValue(Selector({"#cvc"}), "123", _))
+ OnSetFieldValue(Selector({"#cvc"}), kFakeCvc, _))
.WillOnce(RunOnceCallback<2>(OkClientStatus()));
Expectation set_expmonth =
EXPECT_CALL(mock_action_delegate_,
@@ -230,15 +324,15 @@ TEST_F(UseCreditCardActionTest, FillCreditCardWithFallback) {
.WillOnce(RunOnceCallback<2>(OkClientStatus()));
Expectation set_expyear2 =
EXPECT_CALL(mock_action_delegate_,
- OnSetFieldValue(Selector({"#expyear2"}), "24", _))
+ OnSetFieldValue(Selector({"#expyear2"}), "50", _))
.WillOnce(RunOnceCallback<2>(OkClientStatus()));
Expectation set_expyear4 =
EXPECT_CALL(mock_action_delegate_,
- OnSetFieldValue(Selector({"#expyear4"}), "2024", _))
+ OnSetFieldValue(Selector({"#expyear4"}), "2050", _))
.WillOnce(RunOnceCallback<2>(OkClientStatus()));
Expectation set_cardholder_name =
EXPECT_CALL(mock_action_delegate_,
- OnSetFieldValue(Selector({"#card_name"}), "Jon Doe", _))
+ OnSetFieldValue(Selector({"#card_name"}), "Adam West", _))
.WillOnce(RunOnceCallback<2>(OkClientStatus()));
Expectation set_card_number =
EXPECT_CALL(
@@ -275,19 +369,8 @@ TEST_F(UseCreditCardActionTest, FillCreditCardWithFallback) {
.After(set_card_network)
.WillOnce(RunOnceCallback<1>(OkClientStatus(), "not empty"));
- autofill::CreditCard credit_card;
- credit_card.SetExpirationMonth(9);
- credit_card.SetExpirationYear(2024);
- credit_card.SetRawInfo(autofill::CREDIT_CARD_NAME_FULL,
- base::UTF8ToUTF16("Jon Doe"));
- credit_card.SetRawInfo(autofill::CREDIT_CARD_NUMBER,
- base::UTF8ToUTF16("4111111111111111"));
- user_data_.selected_card_ =
- std::make_unique<autofill::CreditCard>(credit_card);
- EXPECT_CALL(mock_action_delegate_, OnGetFullCard(_))
- .WillOnce(RunOnceCallback<0>(credit_card, base::UTF8ToUTF16("123")));
EXPECT_CALL(mock_action_delegate_,
- OnFillCardForm(_, base::UTF8ToUTF16("123"),
+ OnFillCardForm(_, base::UTF8ToUTF16(kFakeCvc),
Selector({kFakeSelector}).MustBeVisible(), _))
.WillOnce(RunOnceCallback<3>(OkClientStatus()));
@@ -301,8 +384,10 @@ TEST_F(UseCreditCardActionTest, ForcedFallbackWithKeystrokes) {
ActionProto action = CreateUseCreditCardAction();
auto* cvc_required = AddRequiredField(
&action,
- base::NumberToString(static_cast<int>(
- UseCreditCardProto::RequiredField::CREDIT_CARD_VERIFICATION_CODE)),
+ base::StrCat({"${",
+ base::NumberToString(static_cast<int>(
+ AutofillFormatProto::CREDIT_CARD_VERIFICATION_CODE)),
+ "}"}),
"#cvc");
cvc_required->set_forced(true);
cvc_required->set_fill_strategy(SIMULATE_KEY_PRESSES);
@@ -314,16 +399,12 @@ TEST_F(UseCreditCardActionTest, ForcedFallbackWithKeystrokes) {
// But we still want the CVC filled, with simulated keypresses.
EXPECT_CALL(mock_action_delegate_,
- OnSetFieldValue(Selector({"#cvc"}), "123", true, 1000, _))
+ OnSetFieldValue(Selector({"#cvc"}), kFakeCvc, true, 1000, _))
.WillOnce(RunOnceCallback<4>(OkClientStatus()));
- autofill::CreditCard credit_card;
- user_data_.selected_card_ =
- std::make_unique<autofill::CreditCard>(credit_card);
- EXPECT_CALL(mock_action_delegate_, OnGetFullCard(_))
- .WillOnce(RunOnceCallback<0>(credit_card, base::UTF8ToUTF16("123")));
+ user_data_.selected_card_ = std::make_unique<autofill::CreditCard>();
EXPECT_CALL(mock_action_delegate_,
- OnFillCardForm(_, base::UTF8ToUTF16("123"),
+ OnFillCardForm(_, base::UTF8ToUTF16(kFakeCvc),
Selector({kFakeSelector}).MustBeVisible(), _))
.WillOnce(RunOnceCallback<3>(OkClientStatus()));
@@ -334,19 +415,17 @@ TEST_F(UseCreditCardActionTest, SkippingAutofill) {
ON_CALL(mock_action_delegate_, GetElementTag(_, _))
.WillByDefault(RunOnceCallback<1>(OkClientStatus(), "INPUT"));
- ActionProto action = CreateUseCreditCardAction();
+ ActionProto action;
AddRequiredField(
&action,
- base::NumberToString(static_cast<int>(
- UseCreditCardProto::RequiredField::CREDIT_CARD_VERIFICATION_CODE)),
+ base::StrCat({"${",
+ base::NumberToString(static_cast<int>(
+ AutofillFormatProto::CREDIT_CARD_VERIFICATION_CODE)),
+ "}"}),
"#cvc");
action.mutable_use_card()->set_skip_autofill(true);
- autofill::CreditCard credit_card;
- user_data_.selected_card_ =
- std::make_unique<autofill::CreditCard>(credit_card);
- EXPECT_CALL(mock_action_delegate_, OnGetFullCard(_))
- .WillOnce(RunOnceCallback<0>(credit_card, base::UTF8ToUTF16("123")));
+ EXPECT_CALL(mock_action_delegate_, OnShortWaitForElement(_, _)).Times(0);
EXPECT_CALL(mock_action_delegate_, OnFillCardForm(_, _, _, _)).Times(0);
// First validation fails.
@@ -355,7 +434,7 @@ TEST_F(UseCreditCardActionTest, SkippingAutofill) {
// Fill cvc.
Expectation set_cvc =
EXPECT_CALL(mock_action_delegate_,
- OnSetFieldValue(Selector({"#cvc"}), "123", _))
+ OnSetFieldValue(Selector({"#cvc"}), kFakeCvc, _))
.WillOnce(RunOnceCallback<2>(OkClientStatus()));
// Second validation succeeds.
EXPECT_CALL(mock_web_controller_, OnGetFieldValue(Selector({"#cvc"}), _))
@@ -368,13 +447,9 @@ TEST_F(UseCreditCardActionTest, SkippingAutofill) {
TEST_F(UseCreditCardActionTest, AutofillFailureWithoutRequiredFieldsIsFatal) {
ActionProto action_proto = CreateUseCreditCardAction();
- autofill::CreditCard credit_card;
- user_data_.selected_card_ =
- std::make_unique<autofill::CreditCard>(credit_card);
- EXPECT_CALL(mock_action_delegate_, OnGetFullCard(_))
- .WillOnce(RunOnceCallback<0>(credit_card, base::UTF8ToUTF16("123")));
+ user_data_.selected_card_ = std::make_unique<autofill::CreditCard>();
EXPECT_CALL(mock_action_delegate_,
- OnFillCardForm(_, base::UTF8ToUTF16("123"),
+ OnFillCardForm(_, base::UTF8ToUTF16(kFakeCvc),
Selector({kFakeSelector}).MustBeVisible(), _))
.WillOnce(RunOnceCallback<3>(ClientStatus(OTHER_ACTION_STATUS)));
@@ -397,17 +472,15 @@ TEST_F(UseCreditCardActionTest,
ActionProto action_proto = CreateUseCreditCardAction();
AddRequiredField(
&action_proto,
- base::NumberToString(static_cast<int>(
- UseCreditCardProto::RequiredField::CREDIT_CARD_VERIFICATION_CODE)),
+ base::StrCat({"${",
+ base::NumberToString(static_cast<int>(
+ AutofillFormatProto::CREDIT_CARD_VERIFICATION_CODE)),
+ "}"}),
"#cvc");
- autofill::CreditCard credit_card;
- user_data_.selected_card_ =
- std::make_unique<autofill::CreditCard>(credit_card);
- EXPECT_CALL(mock_action_delegate_, OnGetFullCard(_))
- .WillOnce(RunOnceCallback<0>(credit_card, base::UTF8ToUTF16("123")));
+ user_data_.selected_card_ = std::make_unique<autofill::CreditCard>();
EXPECT_CALL(mock_action_delegate_,
- OnFillCardForm(_, base::UTF8ToUTF16("123"),
+ OnFillCardForm(_, base::UTF8ToUTF16(kFakeCvc),
Selector({kFakeSelector}).MustBeVisible(), _))
.WillOnce(RunOnceCallback<3>(
FillAutofillErrorStatus(ClientStatus(OTHER_ACTION_STATUS))));
@@ -418,7 +491,7 @@ TEST_F(UseCreditCardActionTest,
// Fill CVC.
Expectation set_cvc =
EXPECT_CALL(mock_action_delegate_,
- OnSetFieldValue(Selector({"#cvc"}), "123", _))
+ OnSetFieldValue(Selector({"#cvc"}), kFakeCvc, _))
.WillOnce(RunOnceCallback<2>(OkClientStatus()));
// Second validation succeeds.
EXPECT_CALL(mock_web_controller_, OnGetFieldValue(Selector({"#cvc"}), _))
@@ -447,19 +520,8 @@ TEST_F(UseCreditCardActionTest, FallbackForCardExpirationSucceeds) {
AddRequiredField(&action_proto, "${53} - ${55}", "#expiration_date");
// Autofill succeeds.
- autofill::CreditCard credit_card;
- credit_card.SetExpirationMonth(9);
- credit_card.SetExpirationYear(2050);
- credit_card.SetRawInfo(autofill::CREDIT_CARD_NAME_FULL,
- base::UTF8ToUTF16("Jon Doe"));
- credit_card.SetRawInfo(autofill::CREDIT_CARD_NUMBER,
- base::UTF8ToUTF16("4111111111111111"));
- user_data_.selected_card_ =
- std::make_unique<autofill::CreditCard>(credit_card);
- EXPECT_CALL(mock_action_delegate_, OnGetFullCard(_))
- .WillOnce(RunOnceCallback<0>(credit_card, base::UTF8ToUTF16("123")));
EXPECT_CALL(mock_action_delegate_,
- OnFillCardForm(_, base::UTF8ToUTF16("123"),
+ OnFillCardForm(_, base::UTF8ToUTF16(kFakeCvc),
Selector({kFakeSelector}).MustBeVisible(), _))
.WillOnce(RunOnceCallback<3>(OkClientStatus()));
@@ -489,22 +551,11 @@ TEST_F(UseCreditCardActionTest, FallbackFails) {
.WillByDefault(RunOnceCallback<1>(OkClientStatus(), "INPUT"));
ActionProto action_proto = CreateUseCreditCardAction();
- AddRequiredField(&action_proto, "57", "#expiration_date");
+ AddRequiredField(&action_proto, "${57}", "#expiration_date");
// Autofill succeeds.
- autofill::CreditCard credit_card;
- credit_card.SetExpirationMonth(9);
- credit_card.SetExpirationYear(2050);
- credit_card.SetRawInfo(autofill::CREDIT_CARD_NAME_FULL,
- base::UTF8ToUTF16("Jon Doe"));
- credit_card.SetRawInfo(autofill::CREDIT_CARD_NUMBER,
- base::UTF8ToUTF16("4111111111111111"));
- user_data_.selected_card_ =
- std::make_unique<autofill::CreditCard>(credit_card);
- EXPECT_CALL(mock_action_delegate_, OnGetFullCard(_))
- .WillOnce(RunOnceCallback<0>(credit_card, base::UTF8ToUTF16("123")));
EXPECT_CALL(mock_action_delegate_,
- OnFillCardForm(_, base::UTF8ToUTF16("123"),
+ OnFillCardForm(_, base::UTF8ToUTF16(kFakeCvc),
Selector({kFakeSelector}).MustBeVisible(), _))
.WillOnce(RunOnceCallback<3>(OkClientStatus()));
@@ -518,8 +569,23 @@ TEST_F(UseCreditCardActionTest, FallbackFails) {
OnSetFieldValue(Eq(Selector({"#expiration_date"})), "09/2050", _))
.WillOnce(RunOnceCallback<2>(ClientStatus(OTHER_ACTION_STATUS)));
- EXPECT_EQ(ProcessedActionStatusProto::AUTOFILL_INCOMPLETE,
- ProcessAction(action_proto));
+ ProcessedActionProto processed_action;
+ EXPECT_CALL(callback_, Run(_)).WillOnce(SaveArgPointee<0>(&processed_action));
+
+ UseCreditCardAction action(&mock_action_delegate_, action_proto);
+ action.ProcessAction(callback_.Get());
+
+ EXPECT_EQ(processed_action.status(),
+ ProcessedActionStatusProto::AUTOFILL_INCOMPLETE);
+ EXPECT_TRUE(processed_action.has_status_details());
+ EXPECT_EQ(processed_action.status_details()
+ .autofill_error_info()
+ .autofill_field_error_size(),
+ 1);
+ EXPECT_EQ(OTHER_ACTION_STATUS, processed_action.status_details()
+ .autofill_error_info()
+ .autofill_field_error(0)
+ .status());
}
} // namespace
diff --git a/chromium/components/autofill_assistant/browser/actions/wait_for_document_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/wait_for_document_action_unittest.cc
index 7cf07a0faad..3ae75c0a587 100644
--- a/chromium/components/autofill_assistant/browser/actions/wait_for_document_action_unittest.cc
+++ b/chromium/components/autofill_assistant/browser/actions/wait_for_document_action_unittest.cc
@@ -192,7 +192,7 @@ TEST_F(WaitForDocumentActionTest, CheckDocumentInFrame) {
.WillOnce(RunOnceCallback<1>(OkClientStatus(), DOCUMENT_COMPLETE));
proto_.set_timeout_ms(0);
- proto_.mutable_frame()->add_selectors("#frame");
+ *proto_.mutable_frame() = ToSelectorProto("#frame");
Run();
EXPECT_EQ(ACTION_APPLIED, processed_action_.status());
}
@@ -203,7 +203,7 @@ TEST_F(WaitForDocumentActionTest, CheckFrameElementNotFound) {
RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED)));
proto_.set_timeout_ms(0);
- proto_.mutable_frame()->add_selectors("#frame");
+ *proto_.mutable_frame() = ToSelectorProto("#frame");
Run();
EXPECT_EQ(ELEMENT_RESOLUTION_FAILED, processed_action_.status());
}
diff --git a/chromium/components/autofill_assistant/browser/actions/wait_for_dom_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/wait_for_dom_action_unittest.cc
index ecedb4531a9..4f496d18a68 100644
--- a/chromium/components/autofill_assistant/browser/actions/wait_for_dom_action_unittest.cc
+++ b/chromium/components/autofill_assistant/browser/actions/wait_for_dom_action_unittest.cc
@@ -102,7 +102,8 @@ TEST_F(WaitForDomActionTest, ConditionMet) {
EXPECT_CALL(mock_web_controller_, OnElementCheck(Selector({"#element"}), _))
.WillRepeatedly(RunOnceCallback<1>(OkClientStatus()));
- proto_.mutable_wait_condition()->mutable_match()->add_selectors("#element");
+ *proto_.mutable_wait_condition()->mutable_match() =
+ ToSelectorProto("#element");
EXPECT_CALL(
callback_,
Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED))));
@@ -110,7 +111,8 @@ TEST_F(WaitForDomActionTest, ConditionMet) {
}
TEST_F(WaitForDomActionTest, ConditionNotMet) {
- proto_.mutable_wait_condition()->mutable_match()->add_selectors("#element");
+ *proto_.mutable_wait_condition()->mutable_match() =
+ ToSelectorProto("#element");
EXPECT_CALL(callback_, Run(Pointee(Property(&ProcessedActionProto::status,
ELEMENT_RESOLUTION_FAILED))));
Run();
@@ -128,25 +130,21 @@ TEST_F(WaitForDomActionTest, ReportMatchesToServer) {
auto* any_of = proto_.mutable_wait_condition()->mutable_any_of();
auto* condition1 = any_of->add_conditions();
- condition1->mutable_match()->add_selectors("#element1");
+ *condition1->mutable_match() = ToSelectorProto("#element1");
condition1->set_payload("1");
auto* condition2 = any_of->add_conditions();
- condition2->mutable_none_of()
- ->add_conditions()
- ->mutable_match()
- ->add_selectors("#element2");
+ *condition2->mutable_none_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("#element2");
condition2->set_payload("2");
auto* condition3 = any_of->add_conditions();
- condition3->mutable_match()->add_selectors("#element3");
+ *condition3->mutable_match() = ToSelectorProto("#element3");
condition3->set_payload("3");
auto* condition4 = any_of->add_conditions();
- condition4->mutable_none_of()
- ->add_conditions()
- ->mutable_match()
- ->add_selectors("#element4");
+ *condition4->mutable_none_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("#element4");
condition4->set_payload("4");
// Condition 1 and 2 are met, conditions 3 and 4 are not.
diff --git a/chromium/components/autofill_assistant/browser/basic_interactions.cc b/chromium/components/autofill_assistant/browser/basic_interactions.cc
index 149429ede1d..e11808288fb 100644
--- a/chromium/components/autofill_assistant/browser/basic_interactions.cc
+++ b/chromium/components/autofill_assistant/browser/basic_interactions.cc
@@ -9,6 +9,7 @@
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "components/autofill/core/browser/autofill_data_util.h"
+#include "components/autofill_assistant/browser/field_formatter.h"
#include "components/autofill_assistant/browser/script_executor_delegate.h"
#include "components/autofill_assistant/browser/trigger_context.h"
#include "components/autofill_assistant/browser/user_model.h"
@@ -103,10 +104,8 @@ bool ValueToString(UserModel* user_model,
switch (value->kind_case()) {
case ValueProto::kUserActions:
case ValueProto::kLoginOptions:
- case ValueProto::kCreditCards:
- case ValueProto::kProfiles:
case ValueProto::kCreditCardResponse:
- case ValueProto::kLoginOptionResponse:
+ case ValueProto::kServerPayload:
DVLOG(2) << "Error evaluating " << __func__
<< ": does not support values of type " << value->kind_case();
return false;
@@ -156,12 +155,59 @@ bool ValueToString(UserModel* user_model,
time, proto.date_format().date_format().c_str())));
break;
}
+ case ValueProto::kCreditCards: {
+ if (proto.autofill_format().pattern().empty()) {
+ DVLOG(2) << "Error evaluating " << __func__ << ": pattern not set";
+ return false;
+ }
+ auto* credit_card =
+ user_model->GetCreditCard(value->credit_cards().values(i).guid());
+ if (!credit_card) {
+ DVLOG(2) << "Error evaluating " << __func__
+ << ": credit card not found";
+ return false;
+ }
+ auto formatted_string = field_formatter::FormatString(
+ proto.autofill_format().pattern(),
+ field_formatter::CreateAutofillMappings(
+ *credit_card, proto.autofill_format().locale()));
+ if (!formatted_string.has_value()) {
+ DVLOG(2) << "Error evaluating " << __func__
+ << ": error formatting pattern '"
+ << proto.autofill_format().pattern() << "'";
+ return false;
+ }
+ result.mutable_strings()->add_values(*formatted_string);
+ break;
+ }
+ case ValueProto::kProfiles: {
+ if (proto.autofill_format().pattern().empty()) {
+ DVLOG(2) << "Error evaluating " << __func__ << ": pattern not set";
+ return false;
+ }
+ auto* profile =
+ user_model->GetProfile(value->profiles().values(i).guid());
+ if (!profile) {
+ DVLOG(2) << "Error evaluating " << __func__ << ": profile not found";
+ return false;
+ }
+ auto formatted_string = field_formatter::FormatString(
+ proto.autofill_format().pattern(),
+ field_formatter::CreateAutofillMappings(
+ *profile, proto.autofill_format().locale()));
+ if (!formatted_string.has_value()) {
+ DVLOG(2) << "Error evaluating " << __func__
+ << ": error formatting pattern '"
+ << proto.autofill_format().pattern() << "'";
+ return false;
+ }
+ result.mutable_strings()->add_values(*formatted_string);
+ break;
+ }
case ValueProto::kUserActions:
case ValueProto::kLoginOptions:
- case ValueProto::kCreditCards:
- case ValueProto::kProfiles:
case ValueProto::kCreditCardResponse:
- case ValueProto::kLoginOptionResponse:
+ case ValueProto::kServerPayload:
case ValueProto::KIND_NOT_SET:
NOTREACHED();
return false;
@@ -326,8 +372,7 @@ bool CreateLoginOptionResponse(UserModel* user_model,
// The result is intentionally not client_side_only, irrespective of input.
ValueProto result;
- result.mutable_login_option_response()->set_payload(
- value->login_options().values(0).payload());
+ result.set_server_payload(value->login_options().values(0).payload());
user_model->SetValue(result_model_identifier, result);
return true;
}
@@ -515,28 +560,46 @@ bool BasicInteractions::ToggleUserAction(const ToggleUserActionProto& proto) {
return true;
}
-bool BasicInteractions::EndAction(bool view_inflation_successful,
- const EndActionProto& proto) {
+bool BasicInteractions::EndAction(const ClientStatus& status) {
if (!end_action_callback_) {
DVLOG(2) << "Failed to EndAction: no callback set";
return false;
}
- std::move(end_action_callback_)
- .Run(view_inflation_successful, proto.status(),
- delegate_->GetUserModel());
+
+ // It is possible for the action to end before view inflation was finished.
+ // In that case, the action can end directly and does not need to receive this
+ // callback.
+ view_inflation_finished_callback_.Reset();
+ std::move(end_action_callback_).Run(status);
return true;
}
-void BasicInteractions::ClearEndActionCallback() {
+bool BasicInteractions::NotifyViewInflationFinished(
+ const ClientStatus& status) {
+ if (!view_inflation_finished_callback_) {
+ return false;
+ }
+ std::move(view_inflation_finished_callback_).Run(status);
+ return true;
+}
+
+void BasicInteractions::ClearCallbacks() {
end_action_callback_.Reset();
+ view_inflation_finished_callback_.Reset();
}
void BasicInteractions::SetEndActionCallback(
- base::OnceCallback<void(bool, ProcessedActionStatusProto, const UserModel*)>
- end_action_callback) {
+ base::OnceCallback<void(const ClientStatus&)> end_action_callback) {
end_action_callback_ = std::move(end_action_callback);
}
+void BasicInteractions::SetViewInflationFinishedCallback(
+ base::OnceCallback<void(const ClientStatus&)>
+ view_inflation_finished_callback) {
+ view_inflation_finished_callback_ =
+ std::move(view_inflation_finished_callback);
+}
+
bool BasicInteractions::RunConditionalCallback(
const std::string& condition_identifier,
base::RepeatingCallback<void()> callback) {
@@ -559,36 +622,4 @@ bool BasicInteractions::RunConditionalCallback(
return true;
}
-bool BasicInteractions::UpdateRadioButtonGroup(
- const std::vector<std::string>& model_identifiers,
- const std::string& selected_model_identifier) {
- auto selected_iterator =
- std::find(model_identifiers.begin(), model_identifiers.end(),
- selected_model_identifier);
- if (selected_iterator == model_identifiers.end()) {
- return false;
- }
-
- auto values = delegate_->GetUserModel()->GetValues(model_identifiers);
- if (!values.has_value()) {
- return false;
- }
-
- if (!AreAllValuesOfType(*values, ValueProto::kBooleans)) {
- return false;
- }
-
- if (!AreAllValuesOfSize(*values, 1)) {
- return false;
- }
-
- for (const auto& model_identifier : model_identifiers) {
- if (model_identifier == selected_model_identifier) {
- continue;
- }
- delegate_->GetUserModel()->SetValue(model_identifier, SimpleValue(false));
- }
- return true;
-}
-
} // namespace autofill_assistant
diff --git a/chromium/components/autofill_assistant/browser/basic_interactions.h b/chromium/components/autofill_assistant/browser/basic_interactions.h
index 0a265fad541..69e256e15db 100644
--- a/chromium/components/autofill_assistant/browser/basic_interactions.h
+++ b/chromium/components/autofill_assistant/browser/basic_interactions.h
@@ -7,11 +7,11 @@
#include "base/bind_helpers.h"
#include "base/memory/weak_ptr.h"
+#include "components/autofill_assistant/browser/client_status.h"
#include "components/autofill_assistant/browser/generic_ui.pb.h"
namespace autofill_assistant {
class ScriptExecutorDelegate;
-class UserModel;
// Provides basic interactions for use by the generic UI framework. These
// methods are intended to be bound to by the corresponding interaction
@@ -43,16 +43,23 @@ class BasicInteractions {
bool ToggleUserAction(const ToggleUserActionProto& proto);
// Ends the current action. Can only be called during a ShowGenericUiAction.
- bool EndAction(bool view_inflation_successful, const EndActionProto& proto);
+ bool EndAction(const ClientStatus& status);
+
+ // Runs |view_inflation_finished_callback_| to notify its owner that view
+ // inflation has finished. Can only be called during a ShowGenericUiAction.
+ bool NotifyViewInflationFinished(const ClientStatus& status);
// Sets the callback to end the current ShowGenericUiAction.
void SetEndActionCallback(
- base::OnceCallback<void(bool,
- ProcessedActionStatusProto,
- const UserModel*)> end_action_callback);
+ base::OnceCallback<void(const ClientStatus&)> end_action_callback);
+
+ // Sets the callback to indicate whether view inflation was successful or not.
+ void SetViewInflationFinishedCallback(
+ base::OnceCallback<void(const ClientStatus&)>
+ view_inflation_finished_callback);
- // Clears the |end_action_callback_|.
- void ClearEndActionCallback();
+ // Clears all callbacks associated with the current ShowGenericUi action.
+ void ClearCallbacks();
// Runs |callback| if |condition_identifier| points to a single boolean set to
// 'true'. Returns true on success (i.e., condition was evaluated
@@ -60,17 +67,12 @@ class BasicInteractions {
bool RunConditionalCallback(const std::string& condition_identifier,
base::RepeatingCallback<void()> callback);
- // Disables all radio buttons in |model_identifiers| except
- // |selected_model_identifier|. Fails if one or more model identifiers are not
- // found.
- bool UpdateRadioButtonGroup(const std::vector<std::string>& model_identifiers,
- const std::string& selected_model_identifier);
-
private:
ScriptExecutorDelegate* delegate_;
// Only valid during a ShowGenericUiAction.
- base::OnceCallback<void(bool, ProcessedActionStatusProto, const UserModel*)>
- end_action_callback_;
+ base::OnceCallback<void(const ClientStatus&)> end_action_callback_;
+ base::OnceCallback<void(const ClientStatus&)>
+ view_inflation_finished_callback_;
base::WeakPtrFactory<BasicInteractions> weak_ptr_factory_{this};
};
diff --git a/chromium/components/autofill_assistant/browser/basic_interactions_unittest.cc b/chromium/components/autofill_assistant/browser/basic_interactions_unittest.cc
index fb1316eba9e..1eb98bdb3cc 100644
--- a/chromium/components/autofill_assistant/browser/basic_interactions_unittest.cc
+++ b/chromium/components/autofill_assistant/browser/basic_interactions_unittest.cc
@@ -16,6 +16,7 @@
namespace autofill_assistant {
+using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::InSequence;
@@ -219,6 +220,91 @@ TEST_F(BasicInteractionsTest, ComputeValueToString) {
/* is_client_side_only = */ true));
}
+ // Credit cards
+ autofill::CreditCard credit_card_a(base::GenerateGUID(),
+ "https://www.example.com");
+ autofill::test::SetCreditCardInfo(&credit_card_a, "Marion Mitchell",
+ "4111 1111 1111 1111", "01", "2050", "");
+ autofill::CreditCard credit_card_b(base::GenerateGUID(),
+ "https://www.example.com");
+ autofill::test::SetCreditCardInfo(&credit_card_b, "John Doe",
+ "4111 1111 1111 2222", "02", "2051", "");
+ auto credit_cards =
+ std::make_unique<std::vector<std::unique_ptr<autofill::CreditCard>>>();
+ credit_cards->emplace_back(
+ std::make_unique<autofill::CreditCard>(credit_card_a));
+ credit_cards->emplace_back(
+ std::make_unique<autofill::CreditCard>(credit_card_b));
+ user_model_.SetAutofillCreditCards(std::move(credit_cards));
+ ValueProto credit_cards_value;
+ credit_cards_value.mutable_credit_cards()->add_values()->set_guid(
+ credit_card_a.guid());
+ credit_cards_value.mutable_credit_cards()->add_values()->set_guid(
+ credit_card_b.guid());
+ user_model_.SetValue("value", credit_cards_value);
+ // Formatting credit cards fails if pattern or locale are not set.
+ proto.mutable_to_string()->mutable_autofill_format()->set_locale("en-US");
+ EXPECT_FALSE(basic_interactions_.ComputeValue(proto));
+ // {name} {network} **** {last-4-digits} ({month/year})
+ proto.mutable_to_string()->mutable_autofill_format()->set_pattern(
+ "${51}. ${-5} **** ${-4} (${53}/${54})");
+ EXPECT_TRUE(basic_interactions_.ComputeValue(proto));
+ ValueProto expected_result;
+ expected_result.mutable_strings()->add_values(
+ "Marion Mitchell. Visa **** 1111 (01/50)");
+ expected_result.mutable_strings()->add_values(
+ "John Doe. Visa **** 2222 (02/51)");
+ EXPECT_EQ(*user_model_.GetValue("output"), expected_result);
+
+ // Profiles
+ autofill::AutofillProfile profile_a(base::GenerateGUID(),
+ "https://www.example.com");
+ autofill::test::SetProfileInfo(
+ &profile_a, "Marion", "Mitchell", "Morrison", "marion@me.xyz", "Fox",
+ "123 Zoo St.", "unit 5", "Hollywood", "CA", "91601", "US", "16505678910");
+ autofill::AutofillProfile profile_b(base::GenerateGUID(),
+ "https://www.example.com");
+ autofill::test::SetProfileInfo(&profile_b, "John", "", "Doe",
+ "editor@gmail.com", "", "203 Barfield Lane",
+ "apt A", "Mountain View", "CA", "94043", "US",
+ "+12345678901");
+ auto profiles = std::make_unique<
+ std::vector<std::unique_ptr<autofill::AutofillProfile>>>();
+ profiles->emplace_back(
+ std::make_unique<autofill::AutofillProfile>(profile_a));
+ profiles->emplace_back(
+ std::make_unique<autofill::AutofillProfile>(profile_b));
+ user_model_.SetAutofillProfiles(std::move(profiles));
+ ValueProto profiles_value;
+ profiles_value.mutable_profiles()->add_values()->set_guid(profile_a.guid());
+ profiles_value.mutable_profiles()->add_values()->set_guid(profile_b.guid());
+ user_model_.SetValue("value", profiles_value);
+ // Formatting profiles fails if pattern is not set.
+ EXPECT_FALSE(basic_interactions_.ComputeValue(proto));
+ // {name_full}, {address_line_1} {address_line_2} {zip code} {city} {country}
+ proto.mutable_to_string()->mutable_autofill_format()->set_pattern(
+ "${7} ${30} ${31} ${35} ${33} ${36}");
+ EXPECT_TRUE(basic_interactions_.ComputeValue(proto));
+ expected_result.Clear();
+ expected_result.mutable_strings()->add_values(
+ "Marion Mitchell Morrison 123 Zoo St. unit 5 91601 Hollywood United "
+ "States");
+ expected_result.mutable_strings()->add_values(
+ "John Doe 203 Barfield Lane apt A 94043 Mountain View United States");
+ EXPECT_EQ(*user_model_.GetValue("output"), expected_result);
+
+ // Different locale.
+ proto.mutable_to_string()->mutable_autofill_format()->set_locale("de-DE");
+ EXPECT_TRUE(basic_interactions_.ComputeValue(proto));
+ expected_result.Clear();
+ expected_result.mutable_strings()->add_values(
+ "Marion Mitchell Morrison 123 Zoo St. unit 5 91601 Hollywood Vereinigte "
+ "Staaten");
+ expected_result.mutable_strings()->add_values(
+ "John Doe 203 Barfield Lane apt A 94043 Mountain View Vereinigte "
+ "Staaten");
+ EXPECT_EQ(*user_model_.GetValue("output"), expected_result);
+
// Empty value fails.
user_model_.SetValue("value", ValueProto());
EXPECT_FALSE(basic_interactions_.ComputeValue(proto));
@@ -229,7 +315,7 @@ TEST_F(BasicInteractionsTest, ComputeValueToString) {
multi_value.mutable_booleans()->add_values(false);
user_model_.SetValue("value", multi_value);
EXPECT_TRUE(basic_interactions_.ComputeValue(proto));
- ValueProto expected_result;
+ expected_result.Clear();
expected_result.mutable_strings()->add_values("true");
expected_result.mutable_strings()->add_values("false");
EXPECT_EQ(*user_model_.GetValue("output"), expected_result);
@@ -294,20 +380,42 @@ TEST_F(BasicInteractionsTest, ComputeValueIntegerSum) {
TEST_F(BasicInteractionsTest, EndActionWithoutCallbackFails) {
EndActionProto proto;
- EXPECT_FALSE(basic_interactions_.EndAction(true, proto));
+ EXPECT_FALSE(basic_interactions_.EndAction(ClientStatus(INVALID_ACTION)));
}
TEST_F(BasicInteractionsTest, EndActionWithCallbackSucceeds) {
- base::MockCallback<base::OnceCallback<void(bool, ProcessedActionStatusProto,
- const UserModel*)>>
- callback;
+ base::MockCallback<base::OnceCallback<void(const ClientStatus&)>> callback;
basic_interactions_.SetEndActionCallback(callback.Get());
- EndActionProto proto;
- proto.set_status(ACTION_APPLIED);
+ EXPECT_CALL(callback,
+ Run(Property(&ClientStatus::proto_status, ACTION_APPLIED)));
+ EXPECT_TRUE(basic_interactions_.EndAction(ClientStatus(ACTION_APPLIED)));
+}
+
+TEST_F(BasicInteractionsTest, NotifyViewInflationFinishedRunsCallback) {
+ base::MockCallback<base::OnceCallback<void(const ClientStatus&)>> callback;
+ basic_interactions_.SetViewInflationFinishedCallback(callback.Get());
+
+ EXPECT_CALL(callback,
+ Run(Property(&ClientStatus::proto_status, ACTION_APPLIED)));
+ basic_interactions_.NotifyViewInflationFinished(ClientStatus(ACTION_APPLIED));
+}
- EXPECT_CALL(callback, Run(true, ACTION_APPLIED, &user_model_));
- EXPECT_TRUE(basic_interactions_.EndAction(true, proto));
+TEST_F(BasicInteractionsTest, EndActionResetsViewInflationCallback) {
+ base::MockCallback<base::OnceCallback<void(const ClientStatus&)>>
+ view_inflation_finished_callback;
+ base::MockCallback<base::OnceCallback<void(const ClientStatus&)>>
+ end_action_callback;
+ basic_interactions_.SetViewInflationFinishedCallback(
+ view_inflation_finished_callback.Get());
+ basic_interactions_.SetEndActionCallback(end_action_callback.Get());
+
+ EXPECT_CALL(end_action_callback,
+ Run(Property(&ClientStatus::proto_status, INVALID_ACTION)));
+ EXPECT_CALL(view_inflation_finished_callback, Run(_)).Times(0);
+ EXPECT_TRUE(basic_interactions_.EndAction(ClientStatus(INVALID_ACTION)));
+ EXPECT_FALSE(basic_interactions_.NotifyViewInflationFinished(
+ ClientStatus(ACTION_APPLIED)));
}
TEST_F(BasicInteractionsTest, ComputeValueCompare) {
@@ -493,8 +601,7 @@ TEST_F(BasicInteractionsTest, ComputeValueCreateLoginOptionResponse) {
// LoginOptionResponseProto is allowed to extract the payload from
// client-only values.
ValueProto expected_response_value;
- expected_response_value.mutable_login_option_response()->set_payload(
- "payload");
+ expected_response_value.set_server_payload("payload");
expected_response_value.set_is_client_side_only(false);
EXPECT_EQ(user_model_.GetValue("result"), expected_response_value);
}
diff --git a/chromium/components/autofill_assistant/browser/client.h b/chromium/components/autofill_assistant/browser/client.h
index 2b44e689581..bbcba879b07 100644
--- a/chromium/components/autofill_assistant/browser/client.h
+++ b/chromium/components/autofill_assistant/browser/client.h
@@ -76,6 +76,9 @@ class Client {
// Returns details about the device.
virtual DeviceContext GetDeviceContext() const = 0;
+ // Returns whether a11y (talkback and touch exploration) is enabled or not.
+ virtual bool IsAccessibilityEnabled() const = 0;
+
// Returns current WebContents.
virtual content::WebContents* GetWebContents() const = 0;
diff --git a/chromium/components/autofill_assistant/browser/client_settings.h b/chromium/components/autofill_assistant/browser/client_settings.h
index 94674066739..330aac1a2d8 100644
--- a/chromium/components/autofill_assistant/browser/client_settings.h
+++ b/chromium/components/autofill_assistant/browser/client_settings.h
@@ -82,7 +82,7 @@ struct ClientSettings {
base::Optional<OverlayImageProto> overlay_image;
// Optional settings intended for integration tests.
- base::Optional<ClientSettingsProto_IntegrationTestSettings>
+ base::Optional<ClientSettingsProto::IntegrationTestSettings>
integration_test_settings;
void UpdateFromProto(const ClientSettingsProto& proto);
diff --git a/chromium/components/autofill_assistant/browser/client_status.cc b/chromium/components/autofill_assistant/browser/client_status.cc
index d99fa0dfb91..c3b4ad9cd54 100644
--- a/chromium/components/autofill_assistant/browser/client_status.cc
+++ b/chromium/components/autofill_assistant/browser/client_status.cc
@@ -119,6 +119,9 @@ std::ostream& operator<<(std::ostream& out,
case ProcessedActionStatusProto::AUTOFILL_INCOMPLETE:
out << "AUTOFILL_INCOMPLETE";
break;
+ case ProcessedActionStatusProto::TOO_MANY_CANDIDATES:
+ out << "TOO_MANY_CANDIDATES";
+ break;
// Intentionally no default case to make compilation fail if a new value
// was added to the enum but not to this list.
diff --git a/chromium/components/autofill_assistant/browser/controller.cc b/chromium/components/autofill_assistant/browser/controller.cc
index f980f810704..db966cfd225 100644
--- a/chromium/components/autofill_assistant/browser/controller.cc
+++ b/chromium/components/autofill_assistant/browser/controller.cc
@@ -12,7 +12,6 @@
#include "base/no_destructor.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
-#include "base/task/post_task.h"
#include "base/time/tick_clock.h"
#include "base/values.h"
#include "components/autofill_assistant/browser/actions/collect_user_data_action.h"
@@ -23,6 +22,7 @@
#include "components/autofill_assistant/browser/service_impl.h"
#include "components/autofill_assistant/browser/trigger_context.h"
#include "components/autofill_assistant/browser/user_data.h"
+#include "components/autofill_assistant/browser/view_layout.pb.h"
#include "components/google/core/common/google_util.h"
#include "components/password_manager/core/browser/password_manager_client.h"
#include "components/strings/grit/components_strings.h"
@@ -43,14 +43,16 @@ namespace {
static constexpr int kAutostartInitialProgress = 5;
// Parameter that allows setting the color of the overlay.
-static const char* const kOverlayColorParameterName = "OVERLAY_COLORS";
+const char kOverlayColorParameterName[] = "OVERLAY_COLORS";
// Parameter that contains the current session username. Should be synced with
// |SESSION_USERNAME_PARAMETER| from
// .../password_manager/PasswordChangeLauncher.java
// TODO(b/151401974): Eliminate duplicate parameter definitions.
-static const char* const kPasswordChangeUsernameParameterName =
- "PASSWORD_CHANGE_USERNAME";
+const char kPasswordChangeUsernameParameterName[] = "PASSWORD_CHANGE_USERNAME";
+
+// Experiment for toggling the new progress bar.
+const char kProgressBarExperiment[] = "4400697";
// Returns true if the state requires a UI to be shown.
//
@@ -241,6 +243,15 @@ int Controller::GetProgress() const {
return progress_;
}
+base::Optional<int> Controller::GetProgressActiveStep() const {
+ return progress_active_step_;
+}
+
+base::Optional<ShowProgressBarProto::StepProgressBarConfiguration>
+Controller::GetStepProgressBarConfiguration() const {
+ return step_progress_bar_configuration_;
+}
+
void Controller::SetInfoBox(const InfoBox& info_box) {
if (!info_box_) {
info_box_ = std::make_unique<InfoBox>();
@@ -273,6 +284,18 @@ void Controller::SetProgress(int progress) {
}
}
+void Controller::SetProgressActiveStep(int active_step) {
+ // Step can only increase.
+ if (progress_active_step_ >= active_step) {
+ return;
+ }
+
+ progress_active_step_ = active_step;
+ for (ControllerObserver& observer : observers_) {
+ observer.OnProgressActiveStepChanged(active_step);
+ }
+}
+
void Controller::SetProgressVisible(bool visible) {
if (progress_visible_ == visible)
return;
@@ -287,6 +310,38 @@ bool Controller::GetProgressVisible() const {
return progress_visible_;
}
+void Controller::SetStepProgressBarConfiguration(
+ const ShowProgressBarProto::StepProgressBarConfiguration& configuration) {
+ step_progress_bar_configuration_ = configuration;
+ if (!configuration.step_icons().empty() &&
+ progress_active_step_.has_value() &&
+ configuration.step_icons().size() < *progress_active_step_) {
+ progress_active_step_ = configuration.step_icons().size();
+ }
+ for (ControllerObserver& observer : observers_) {
+ observer.OnStepProgressBarConfigurationChanged(configuration);
+ if (progress_active_step_.has_value()) {
+ observer.OnProgressActiveStepChanged(*progress_active_step_);
+ }
+ observer.OnProgressBarErrorStateChanged(progress_bar_error_state_);
+ }
+}
+
+void Controller::SetProgressBarErrorState(bool error) {
+ if (progress_bar_error_state_ == error) {
+ return;
+ }
+
+ progress_bar_error_state_ = error;
+ for (ControllerObserver& observer : observers_) {
+ observer.OnProgressBarErrorStateChanged(error);
+ }
+}
+
+bool Controller::GetProgressBarErrorState() const {
+ return progress_bar_error_state_;
+}
+
const std::vector<UserAction>& Controller::GetUserActions() const {
static const base::NoDestructor<std::vector<UserAction>> no_user_actions_;
return user_actions_ ? *user_actions_ : *no_user_actions_;
@@ -321,10 +376,13 @@ void Controller::RequireUI() {
void Controller::SetGenericUi(
std::unique_ptr<GenericUserInterfaceProto> generic_ui,
- base::OnceCallback<void(bool, ProcessedActionStatusProto, const UserModel*)>
- end_action_callback) {
+ base::OnceCallback<void(const ClientStatus&)> end_action_callback,
+ base::OnceCallback<void(const ClientStatus&)>
+ view_inflation_finished_callback) {
generic_user_interface_ = std::move(generic_ui);
basic_interactions_.SetEndActionCallback(std::move(end_action_callback));
+ basic_interactions_.SetViewInflationFinishedCallback(
+ std::move(view_inflation_finished_callback));
for (ControllerObserver& observer : observers_) {
observer.OnGenericUserInterfaceChanged(generic_user_interface_.get());
}
@@ -332,7 +390,7 @@ void Controller::SetGenericUi(
void Controller::ClearGenericUi() {
generic_user_interface_.reset();
- basic_interactions_.ClearEndActionCallback();
+ basic_interactions_.ClearCallbacks();
for (ControllerObserver& observer : observers_) {
observer.OnGenericUserInterfaceChanged(nullptr);
}
@@ -699,10 +757,11 @@ void Controller::StartPeriodicScriptChecks() {
if (periodic_script_check_scheduled_)
return;
periodic_script_check_scheduled_ = true;
- base::PostDelayedTask(FROM_HERE, {content::BrowserThread::UI},
- base::BindOnce(&Controller::OnPeriodicScriptCheck,
- weak_ptr_factory_.GetWeakPtr()),
- settings_.periodic_script_check_interval);
+ content::GetUIThreadTaskRunner({})->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&Controller::OnPeriodicScriptCheck,
+ weak_ptr_factory_.GetWeakPtr()),
+ settings_.periodic_script_check_interval);
}
void Controller::StopPeriodicScriptChecks() {
@@ -732,10 +791,11 @@ void Controller::OnPeriodicScriptCheck() {
}
script_tracker()->CheckScripts();
- base::PostDelayedTask(FROM_HERE, {content::BrowserThread::UI},
- base::BindOnce(&Controller::OnPeriodicScriptCheck,
- weak_ptr_factory_.GetWeakPtr()),
- settings_.periodic_script_check_interval);
+ content::GetUIThreadTaskRunner({})->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&Controller::OnPeriodicScriptCheck,
+ weak_ptr_factory_.GetWeakPtr()),
+ settings_.periodic_script_check_interval);
}
void Controller::OnGetScripts(const GURL& url,
@@ -980,6 +1040,12 @@ void Controller::InitFromParameters() {
user_data_->selected_login_.emplace(web_contents()->GetLastCommittedURL(),
*password_change_username);
}
+
+ if (trigger_context_->HasExperimentId(kProgressBarExperiment)) {
+ ShowProgressBarProto::StepProgressBarConfiguration mock_configuration;
+ mock_configuration.set_use_step_progress_bar(true);
+ SetStepProgressBarConfiguration(mock_configuration);
+ }
}
void Controller::Track(std::unique_ptr<TriggerContext> trigger_context,
@@ -1043,7 +1109,12 @@ void Controller::ShowFirstMessageAndStart() {
SetStatusMessage(
l10n_util::GetStringFUTF8(IDS_AUTOFILL_ASSISTANT_LOADING,
base::UTF8ToUTF16(GetCurrentURL().host())));
- SetProgress(kAutostartInitialProgress);
+ if (step_progress_bar_configuration_.has_value() &&
+ step_progress_bar_configuration_->use_step_progress_bar()) {
+ SetProgressActiveStep(0);
+ } else {
+ SetProgress(kAutostartInitialProgress);
+ }
EnterState(AutofillAssistantState::STARTING);
}
@@ -1051,6 +1122,10 @@ AutofillAssistantState Controller::GetState() {
return state_;
}
+int64_t Controller::GetErrorCausingNavigationId() const {
+ return error_causing_navigation_id_;
+}
+
void Controller::OnScriptSelected(const ScriptHandle& handle,
std::unique_ptr<TriggerContext> context) {
ExecuteScript(handle.path, handle.start_message, handle.needs_ui,
@@ -1435,6 +1510,7 @@ void Controller::OnScriptError(const std::string& error_message,
RequireUI();
SetStatusMessage(error_message);
+ SetProgressBarErrorState(true);
EnterStoppedState();
if (tracking_) {
@@ -1454,6 +1530,7 @@ void Controller::OnFatalError(const std::string& error_message,
return;
SetStatusMessage(error_message);
+ SetProgressBarErrorState(true);
EnterStoppedState();
// If we haven't managed to check the set of scripts yet at this point, we
@@ -1588,10 +1665,6 @@ void Controller::OnRunnableScriptsChanged(
SetUserActions(std::move(user_actions));
}
-void Controller::DidAttachInterstitialPage() {
- client_->Shutdown(Metrics::DropOutReason::INTERSTITIAL_PAGE);
-}
-
void Controller::DidFinishLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url) {
// validated_url might not be the page URL. Ignore it and always check the
@@ -1599,6 +1672,10 @@ void Controller::DidFinishLoad(content::RenderFrameHost* render_frame_host,
OnUrlChange();
}
+void Controller::ExpectNavigation() {
+ expect_navigation_ = true;
+}
+
void Controller::DidStartNavigation(
content::NavigationHandle* navigation_handle) {
if (!navigation_handle->IsInMainFrame() ||
@@ -1611,6 +1688,12 @@ void Controller::DidStartNavigation(
ReportNavigationStateChanged();
}
+ // The navigation is expected, do not check for errors below.
+ if (expect_navigation_) {
+ expect_navigation_ = false;
+ return;
+ }
+
// The following types of navigations are allowed for the main frame, when
// in PROMPT state:
// - first-time URL load
@@ -1627,19 +1710,38 @@ void Controller::DidStartNavigation(
// Everything else, such as going back to a previous page, or refreshing the
// page is considered an end condition. If going back to a previous page is
// required, consider using the BROWSE state instead.
- // Note that BROWSE state end conditions are in DidFinishNavigation, in order
- // to be able to properly evaluate the committed url.
if (state_ == AutofillAssistantState::PROMPT &&
web_contents()->GetLastCommittedURL().is_valid() &&
!navigation_handle->WasServerRedirect() &&
!navigation_handle->IsRendererInitiated()) {
+ error_causing_navigation_id_ = navigation_handle->GetNavigationId();
OnScriptError(l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_GIVE_UP),
Metrics::DropOutReason::NAVIGATION);
+ return;
}
+
+ if (base::FeatureList::IsEnabled(
+ features::kAutofillAssistantBreakOnRunningNavigation)) {
+ // When in RUNNING state, all renderer initiated navigation is allowed,
+ // user initiated navigation will cause an error.
+ if (state_ == AutofillAssistantState::RUNNING &&
+ !navigation_handle->WasServerRedirect() &&
+ !navigation_handle->IsRendererInitiated()) {
+ error_causing_navigation_id_ = navigation_handle->GetNavigationId();
+ OnScriptError(l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_GIVE_UP),
+ Metrics::DropOutReason::NAVIGATION_WHILE_RUNNING);
+ return;
+ }
+ }
+
+ // Note that BROWSE state end conditions are in DidFinishNavigation, in order
+ // to be able to properly evaluate the committed url.
}
void Controller::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
+ // TODO(b/159871774): Rethink how we handle navigation events. The early
+ // return here may prevent us from updating |navigating_to_new_document_|.
if (!navigation_handle->IsInMainFrame() ||
navigation_handle->IsSameDocument() ||
!navigation_handle->HasCommitted() || !IsNavigatingToNewDocument()) {
@@ -1758,11 +1860,6 @@ void Controller::WriteUserData(
}
}
-void Controller::WriteUserModel(
- base::OnceCallback<void(UserModel*)> write_callback) {
- std::move(write_callback).Run(&user_model_);
-}
-
ElementArea* Controller::touchable_element_area() {
if (!touchable_element_area_) {
touchable_element_area_ = std::make_unique<ElementArea>(this);
diff --git a/chromium/components/autofill_assistant/browser/controller.h b/chromium/components/autofill_assistant/browser/controller.h
index 998f8f84e7d..e35235d7bd4 100644
--- a/chromium/components/autofill_assistant/browser/controller.h
+++ b/chromium/components/autofill_assistant/browser/controller.h
@@ -119,7 +119,11 @@ class Controller : public ScriptExecutorDelegate,
void SetInfoBox(const InfoBox& info_box) override;
void ClearInfoBox() override;
void SetProgress(int progress) override;
+ void SetProgressActiveStep(int active_step) override;
void SetProgressVisible(bool visible) override;
+ void SetStepProgressBarConfiguration(
+ const ShowProgressBarProto::StepProgressBarConfiguration& configuration)
+ override;
void SetUserActions(
std::unique_ptr<std::vector<UserAction>> user_actions) override;
void SetViewportMode(ViewportMode mode) override;
@@ -130,13 +134,14 @@ class Controller : public ScriptExecutorDelegate,
std::unique_ptr<FormProto> form,
base::RepeatingCallback<void(const FormProto::Result*)> changed_callback,
base::OnceCallback<void(const ClientStatus&)> cancel_callback) override;
+ void ExpectNavigation() override;
bool IsNavigatingToNewDocument() override;
bool HasNavigationError() override;
void SetGenericUi(
std::unique_ptr<GenericUserInterfaceProto> generic_ui,
- base::OnceCallback<void(bool,
- ProcessedActionStatusProto,
- const UserModel*)> end_action_callback) override;
+ base::OnceCallback<void(const ClientStatus&)> end_action_callback,
+ base::OnceCallback<void(const ClientStatus&)>
+ view_inflation_finished_callback) override;
void ClearGenericUi() override;
// Show the UI if it's not already shown. This is only meaningful while in
@@ -153,18 +158,21 @@ class Controller : public ScriptExecutorDelegate,
void SetCollectUserDataOptions(CollectUserDataOptions* options) override;
void WriteUserData(
base::OnceCallback<void(UserData*, UserData::FieldChange*)>) override;
- void WriteUserModel(
- base::OnceCallback<void(UserModel*)> write_callback) override;
void OnScriptError(const std::string& error_message,
Metrics::DropOutReason reason);
// Overrides autofill_assistant::UiDelegate:
AutofillAssistantState GetState() override;
+ int64_t GetErrorCausingNavigationId() const override;
void OnUserInteractionInsideTouchableArea() override;
const Details* GetDetails() const override;
const InfoBox* GetInfoBox() const override;
int GetProgress() const override;
+ base::Optional<int> GetProgressActiveStep() const override;
bool GetProgressVisible() const override;
+ bool GetProgressBarErrorState() const override;
+ base::Optional<ShowProgressBarProto::StepProgressBarConfiguration>
+ GetStepProgressBarConfiguration() const override;
const std::vector<UserAction>& GetUserActions() const override;
bool PerformUserActionWithContext(
int index,
@@ -279,7 +287,6 @@ class Controller : public ScriptExecutorDelegate,
const std::vector<ScriptHandle>& runnable_scripts) override;
// Overrides content::WebContentsObserver:
- void DidAttachInterstitialPage() override;
void DidFinishLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url) override;
void DidStartNavigation(
@@ -311,6 +318,8 @@ class Controller : public ScriptExecutorDelegate,
// Clear out visible state and enter the stopped state.
void EnterStoppedState();
+ void SetProgressBarErrorState(bool error);
+
ElementArea* touchable_element_area();
ScriptTracker* script_tracker();
bool allow_autostart() { return state_ == AutofillAssistantState::STARTING; }
@@ -327,6 +336,7 @@ class Controller : public ScriptExecutorDelegate,
std::unique_ptr<TriggerContext> trigger_context_;
AutofillAssistantState state_ = AutofillAssistantState::INACTIVE;
+ int64_t error_causing_navigation_id_ = -1;
// The URL passed to Start(). Used only as long as there's no committed URL.
// Note that this is the deeplink passed by a caller.
@@ -373,9 +383,13 @@ class Controller : public ScriptExecutorDelegate,
// Current progress.
int progress_ = 0;
+ base::Optional<int> progress_active_step_;
// Current visibility of the progress bar. It is initially visible.
bool progress_visible_ = true;
+ bool progress_bar_error_state_ = false;
+ base::Optional<ShowProgressBarProto::StepProgressBarConfiguration>
+ step_progress_bar_configuration_;
// Current set of user actions. May be null, but never empty.
std::unique_ptr<std::vector<UserAction>> user_actions_;
@@ -411,6 +425,9 @@ class Controller : public ScriptExecutorDelegate,
bool navigation_error_ = false;
base::ObserverList<NavigationListener> navigation_listeners_;
+ // The next DidStartNavigation will not cause an error.
+ bool expect_navigation_ = false;
+
// 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 6cc7d6a6cd4..eeb525f47fe 100644
--- a/chromium/components/autofill_assistant/browser/controller_observer.h
+++ b/chromium/components/autofill_assistant/browser/controller_observer.h
@@ -67,10 +67,20 @@ class ControllerObserver : public base::CheckedObserver {
// percentage.
virtual void OnProgressChanged(int progress) = 0;
+ // Called when the currently active progress step has changed.
+ virtual void OnProgressActiveStepChanged(int active_step) = 0;
+
// Called when the current progress bar visibility has changed. If |visible|
// is true, then the bar is now shown.
virtual void OnProgressVisibilityChanged(bool visible) = 0;
+ virtual void OnStepProgressBarConfigurationChanged(
+ const ShowProgressBarProto::StepProgressBarConfiguration&
+ configuration) = 0;
+
+ // Called when the progress bar error state changes.
+ virtual void OnProgressBarErrorStateChanged(bool error) = 0;
+
// Updates the area of the visible viewport that is accessible when the
// overlay state is OverlayState::PARTIAL.
//
diff --git a/chromium/components/autofill_assistant/browser/controller_unittest.cc b/chromium/components/autofill_assistant/browser/controller_unittest.cc
index eb8dbeed3c6..9b16df336cf 100644
--- a/chromium/components/autofill_assistant/browser/controller_unittest.cc
+++ b/chromium/components/autofill_assistant/browser/controller_unittest.cc
@@ -416,12 +416,11 @@ TEST_F(ControllerTest, NoRelevantScripts) {
TEST_F(ControllerTest, NoRelevantScriptYet) {
SupportsScriptResponseProto script_response;
- AddRunnableScript(&script_response, "no_match_yet")
- ->mutable_presentation()
- ->mutable_precondition()
- ->mutable_element_condition()
- ->mutable_match()
- ->add_selectors("#element");
+ *AddRunnableScript(&script_response, "no_match_yet")
+ ->mutable_presentation()
+ ->mutable_precondition()
+ ->mutable_element_condition()
+ ->mutable_match() = ToSelectorProto("#element");
SetNextScriptResponse(script_response);
Start("http://a.example.com/path");
@@ -714,12 +713,11 @@ TEST_F(ControllerTest, AttachUIWhenContentsFocused) {
TEST_F(ControllerTest, KeepCheckingForElement) {
SupportsScriptResponseProto script_response;
- AddRunnableScript(&script_response, "no_match_yet")
- ->mutable_presentation()
- ->mutable_precondition()
- ->mutable_element_condition()
- ->mutable_match()
- ->add_selectors("#element");
+ *AddRunnableScript(&script_response, "no_match_yet")
+ ->mutable_presentation()
+ ->mutable_precondition()
+ ->mutable_element_condition()
+ ->mutable_match() = ToSelectorProto("#element");
SetNextScriptResponse(script_response);
Start("http://a.example.com/path");
@@ -743,12 +741,11 @@ TEST_F(ControllerTest, ScriptTimeoutError) {
// Wait for #element to show up for will_never_match. After 25s, execute the
// script on_timeout_error.
SupportsScriptResponseProto script_response;
- AddRunnableScript(&script_response, "will_never_match")
- ->mutable_presentation()
- ->mutable_precondition()
- ->mutable_element_condition()
- ->mutable_match()
- ->add_selectors("#element");
+ *AddRunnableScript(&script_response, "will_never_match")
+ ->mutable_presentation()
+ ->mutable_precondition()
+ ->mutable_element_condition()
+ ->mutable_match() = ToSelectorProto("#element");
script_response.mutable_script_timeout_error()->set_timeout_ms(30000);
script_response.mutable_script_timeout_error()->set_script_path(
"on_timeout_error");
@@ -777,12 +774,11 @@ TEST_F(ControllerTest, ScriptTimeoutWarning) {
// Wait for #element to show up for will_never_match. After 10s, execute the
// script on_timeout_error.
SupportsScriptResponseProto script_response;
- AddRunnableScript(&script_response, "will_never_match")
- ->mutable_presentation()
- ->mutable_precondition()
- ->mutable_element_condition()
- ->mutable_match()
- ->add_selectors("#element");
+ *AddRunnableScript(&script_response, "will_never_match")
+ ->mutable_presentation()
+ ->mutable_precondition()
+ ->mutable_element_condition()
+ ->mutable_match() = ToSelectorProto("#element");
script_response.mutable_script_timeout_error()->set_timeout_ms(4000);
script_response.mutable_script_timeout_error()->set_script_path(
"on_timeout_error");
@@ -1249,12 +1245,11 @@ TEST_F(ControllerTest, TrackReportsNoScripts) {
TEST_F(ControllerTest, TrackReportsNoScriptsForNow) {
SupportsScriptResponseProto script_response;
- AddRunnableScript(&script_response, "no_match_yet")
- ->mutable_presentation()
- ->mutable_precondition()
- ->mutable_element_condition()
- ->mutable_match()
- ->add_selectors("#element");
+ *AddRunnableScript(&script_response, "no_match_yet")
+ ->mutable_presentation()
+ ->mutable_precondition()
+ ->mutable_element_condition()
+ ->mutable_match() = ToSelectorProto("#element");
SetNextScriptResponse(script_response);
SetLastCommittedUrl(GURL("http://example.com/"));
@@ -1574,10 +1569,23 @@ TEST_F(ControllerTest, UnexpectedNavigationDuringPromptAction) {
EXPECT_CALL(mock_observer_, OnStatusMessageChanged(testing::Not(never_shown)))
.Times(testing::AnyNumber());
- EXPECT_CALL(mock_client_, Shutdown(Metrics::DropOutReason::NAVIGATION));
+ // Renderer (Document) initiated navigation is allowed.
+ EXPECT_CALL(mock_client_, Shutdown(_)).Times(0);
+ content::NavigationSimulator::NavigateAndCommitFromDocument(
+ GURL("http://a.example.com/page"), web_contents()->GetMainFrame());
+ EXPECT_EQ(AutofillAssistantState::PROMPT, controller_->GetState());
+
+ // Expected browser initiated navigation is allowed.
+ EXPECT_CALL(mock_client_, Shutdown(_)).Times(0);
+ controller_->ExpectNavigation();
content::NavigationSimulator::NavigateAndCommitFromBrowser(
- web_contents(), GURL("http://example.com/otherpage"));
+ web_contents(), GURL("http://b.example.com/page"));
+ EXPECT_EQ(AutofillAssistantState::PROMPT, controller_->GetState());
+ // Unexpected browser initiated navigation will cause an error.
+ EXPECT_CALL(mock_client_, Shutdown(Metrics::DropOutReason::NAVIGATION));
+ content::NavigationSimulator::NavigateAndCommitFromBrowser(
+ web_contents(), GURL("http://c.example.com/page"));
EXPECT_EQ(AutofillAssistantState::STOPPED, controller_->GetState());
// Full history of state transitions.
@@ -1587,6 +1595,55 @@ TEST_F(ControllerTest, UnexpectedNavigationDuringPromptAction) {
AutofillAssistantState::STOPPED));
}
+TEST_F(ControllerTest, UnexpectedNavigationInRunningState) {
+ SupportsScriptResponseProto script_response;
+ AddRunnableScript(&script_response, "autostart")
+ ->mutable_presentation()
+ ->set_autostart(true);
+ SetNextScriptResponse(script_response);
+
+ ActionsResponseProto autostart_script;
+ auto* wait_for_dom = autostart_script.add_actions()->mutable_wait_for_dom();
+ wait_for_dom->set_timeout_ms(10000);
+ wait_for_dom->mutable_wait_condition()
+ ->mutable_match()
+ ->add_filters()
+ ->set_css_selector("#some-element");
+ SetupActionsForScript("autostart", autostart_script);
+
+ Start();
+ EXPECT_EQ(AutofillAssistantState::RUNNING, controller_->GetState());
+
+ // Document (not user) initiated navigation while in RUNNING state:
+ // The controller keeps going.
+ EXPECT_CALL(mock_client_, Shutdown(_)).Times(0);
+ content::NavigationSimulator::NavigateAndCommitFromDocument(
+ GURL("http://a.example.com/page"), web_contents()->GetMainFrame());
+ EXPECT_EQ(AutofillAssistantState::RUNNING, controller_->GetState());
+
+ // Expected browser initiated navigation while in RUNNING state:
+ // The controller keeps going.
+ EXPECT_CALL(mock_client_, Shutdown(_)).Times(0);
+ controller_->ExpectNavigation();
+ content::NavigationSimulator::NavigateAndCommitFromBrowser(
+ web_contents(), GURL("http://b.example.com/page"));
+ EXPECT_EQ(AutofillAssistantState::RUNNING, controller_->GetState());
+
+ // Unexpected browser initiated navigation while in RUNNING state:
+ // The controller stops the scripts, shows an error and shuts down.
+ EXPECT_CALL(mock_client_,
+ Shutdown(Metrics::DropOutReason::NAVIGATION_WHILE_RUNNING));
+ EXPECT_CALL(mock_observer_, OnStatusMessageChanged(_));
+ content::NavigationSimulator::NavigateAndCommitFromBrowser(
+ web_contents(), GURL("http://c.example.com/page"));
+ EXPECT_EQ(AutofillAssistantState::STOPPED, controller_->GetState());
+
+ // Full history of state transitions.
+ EXPECT_THAT(states_, ElementsAre(AutofillAssistantState::STARTING,
+ AutofillAssistantState::RUNNING,
+ AutofillAssistantState::STOPPED));
+}
+
TEST_F(ControllerTest, NavigationToGooglePropertyDestroysUI) {
SupportsScriptResponseProto script_response;
AddRunnableScript(&script_response, "autostart")
@@ -2271,7 +2328,7 @@ TEST_F(ControllerTest, SetGenericUi) {
}
controller_->SetGenericUi(
std::make_unique<GenericUserInterfaceProto>(GenericUserInterfaceProto()),
- base::DoNothing());
+ base::DoNothing(), base::DoNothing());
controller_->ClearGenericUi();
}
diff --git a/chromium/components/autofill_assistant/browser/devtools/devtools_api/domain_type_conversions_h.template b/chromium/components/autofill_assistant/browser/devtools/devtools_api/domain_type_conversions_h.template
index bbc506c3ed8..681e555e16d 100644
--- a/chromium/components/autofill_assistant/browser/devtools/devtools_api/domain_type_conversions_h.template
+++ b/chromium/components/autofill_assistant/browser/devtools/devtools_api/domain_type_conversions_h.template
@@ -9,7 +9,7 @@
#ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_DEVTOOLS_DEVTOOLS_INTERNAL_TYPE_CONVERSIONS_{{domain.domain | camelcase_to_hacker_style | upper}}_H_
#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_DEVTOOLS_DEVTOOLS_INTERNAL_TYPE_CONVERSIONS_{{domain.domain | camelcase_to_hacker_style | upper}}_H_
-#include "base/logging.h"
+#include "base/notreached.h"
#include "components/autofill_assistant/browser/devtools/devtools/domains/types_{{domain.domain | camelcase_to_hacker_style}}.h"
#include "components/autofill_assistant/browser/devtools/value_conversions.h"
diff --git a/chromium/components/autofill_assistant/browser/devtools/devtools_client.cc b/chromium/components/autofill_assistant/browser/devtools/devtools_client.cc
index 3c213373bba..8f7af508b55 100644
--- a/chromium/components/autofill_assistant/browser/devtools/devtools_client.cc
+++ b/chromium/components/autofill_assistant/browser/devtools/devtools_client.cc
@@ -13,7 +13,6 @@
#include "base/callback_forward.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
-#include "base/task/post_task.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
@@ -30,8 +29,7 @@ DevtoolsClient::DevtoolsClient(
renderer_crashed_(false),
next_message_id_(0),
frame_tracker_(this) {
- browser_main_thread_ =
- base::CreateSingleThreadTaskRunner({content::BrowserThread::UI});
+ browser_main_thread_ = content::GetUIThreadTaskRunner({});
agent_host_->AttachClient(this);
frame_tracker_.Start();
}
diff --git a/chromium/components/autofill_assistant/browser/element_area.cc b/chromium/components/autofill_assistant/browser/element_area.cc
index 6136a6e62e7..0b513492506 100644
--- a/chromium/components/autofill_assistant/browser/element_area.cc
+++ b/chromium/components/autofill_assistant/browser/element_area.cc
@@ -50,6 +50,9 @@ void ElementArea::Clear() {
void ElementArea::SetFromProto(const ElementAreaProto& proto) {
rectangles_.clear();
+ last_visual_viewport_ = RectF();
+ last_rectangles_.clear();
+
AddRectangles(proto.touchable(), /* restricted= */ false);
AddRectangles(proto.restricted(), /* restricted= */ true);
diff --git a/chromium/components/autofill_assistant/browser/element_area_unittest.cc b/chromium/components/autofill_assistant/browser/element_area_unittest.cc
index 5da55e62e68..3ccde28d42f 100644
--- a/chromium/components/autofill_assistant/browser/element_area_unittest.cc
+++ b/chromium/components/autofill_assistant/browser/element_area_unittest.cc
@@ -86,7 +86,7 @@ class ElementAreaTest : public testing::Test {
void SetElement(const std::string& selector, bool restricted) {
ElementAreaProto area;
auto* rectangle = restricted ? area.add_restricted() : area.add_touchable();
- rectangle->add_elements()->add_selectors(selector);
+ *rectangle->add_elements() = ToSelectorProto(selector);
element_area_.SetFromProto(area);
}
@@ -164,6 +164,17 @@ TEST_F(ElementAreaTest, CallOnUpdate) {
EXPECT_THAT(reported_area_, ElementsAre(MatchingRectF(25, 25, 75, 75)));
}
+TEST_F(ElementAreaTest, CallOnUpdateAfterSetFromProto) {
+ EXPECT_CALL(mock_web_controller_,
+ OnGetElementPosition(Eq(Selector({"#found"}).MustBeVisible()), _))
+ .WillRepeatedly(RunOnceCallback<1>(true, RectF(25, 25, 75, 75)));
+
+ SetElement("#found");
+ EXPECT_EQ(on_update_call_count_, 1);
+ SetElement("#found");
+ EXPECT_EQ(on_update_call_count_, 2);
+}
+
TEST_F(ElementAreaTest, DontCallOnUpdateWhenViewportMissing) {
// Swallowing calls to OnGetVisualViewport guarantees that the viewport
// position will never be known.
@@ -203,8 +214,9 @@ TEST_F(ElementAreaTest, TwoRectangles) {
.WillOnce(RunOnceCallback<1>(true, RectF(25, 25, 100, 100)));
ElementAreaProto area_proto;
- area_proto.add_touchable()->add_elements()->add_selectors("#top_left");
- area_proto.add_touchable()->add_elements()->add_selectors("#bottom_right");
+ *area_proto.add_touchable()->add_elements() = ToSelectorProto("#top_left");
+ *area_proto.add_touchable()->add_elements() =
+ ToSelectorProto("#bottom_right");
element_area_.SetFromProto(area_proto);
std::vector<RectF> rectangles;
@@ -225,8 +237,8 @@ TEST_F(ElementAreaTest, OneRectangleTwoElements) {
ElementAreaProto area_proto;
auto* rectangle_proto = area_proto.add_touchable();
- rectangle_proto->add_elements()->add_selectors("#element1");
- rectangle_proto->add_elements()->add_selectors("#element2");
+ *rectangle_proto->add_elements() = ToSelectorProto("#element1");
+ *rectangle_proto->add_elements() = ToSelectorProto("#element2");
element_area_.SetFromProto(area_proto);
std::vector<RectF> rectangles;
@@ -249,8 +261,8 @@ TEST_F(ElementAreaTest, DoNotReportIncompleteRectangles) {
ElementAreaProto area_proto;
auto* rectangle_proto = area_proto.add_touchable();
- rectangle_proto->add_elements()->add_selectors("#element1");
- rectangle_proto->add_elements()->add_selectors("#element2");
+ *rectangle_proto->add_elements() = ToSelectorProto("#element1");
+ *rectangle_proto->add_elements() = ToSelectorProto("#element2");
element_area_.SetFromProto(area_proto);
EXPECT_THAT(reported_area_, IsEmpty());
@@ -280,10 +292,10 @@ TEST_F(ElementAreaTest, OneRectangleFourElements) {
ElementAreaProto area_proto;
auto* rectangle_proto = area_proto.add_touchable();
- rectangle_proto->add_elements()->add_selectors("#element1");
- rectangle_proto->add_elements()->add_selectors("#element2");
- rectangle_proto->add_elements()->add_selectors("#element3");
- rectangle_proto->add_elements()->add_selectors("#element4");
+ *rectangle_proto->add_elements() = ToSelectorProto("#element1");
+ *rectangle_proto->add_elements() = ToSelectorProto("#element2");
+ *rectangle_proto->add_elements() = ToSelectorProto("#element3");
+ *rectangle_proto->add_elements() = ToSelectorProto("#element4");
element_area_.SetFromProto(area_proto);
std::vector<RectF> rectangles;
@@ -303,8 +315,8 @@ TEST_F(ElementAreaTest, OneRectangleMissingElementsReported) {
ElementAreaProto area_proto;
auto* rectangle_proto = area_proto.add_touchable();
- rectangle_proto->add_elements()->add_selectors("#element1");
- rectangle_proto->add_elements()->add_selectors("#element2");
+ *rectangle_proto->add_elements() = ToSelectorProto("#element1");
+ *rectangle_proto->add_elements() = ToSelectorProto("#element2");
element_area_.SetFromProto(area_proto);
std::vector<RectF> rectangles;
@@ -328,8 +340,8 @@ TEST_F(ElementAreaTest, FullWidthRectangle) {
ElementAreaProto area_proto;
auto* rectangle_proto = area_proto.add_touchable();
- rectangle_proto->add_elements()->add_selectors("#element1");
- rectangle_proto->add_elements()->add_selectors("#element2");
+ *rectangle_proto->add_elements() = ToSelectorProto("#element1");
+ *rectangle_proto->add_elements() = ToSelectorProto("#element2");
rectangle_proto->set_full_width(true);
element_area_.SetFromProto(area_proto);
diff --git a/chromium/components/autofill_assistant/browser/element_precondition_unittest.cc b/chromium/components/autofill_assistant/browser/element_precondition_unittest.cc
index 7756d8ccdb1..b9f1058c92a 100644
--- a/chromium/components/autofill_assistant/browser/element_precondition_unittest.cc
+++ b/chromium/components/autofill_assistant/browser/element_precondition_unittest.cc
@@ -67,7 +67,7 @@ TEST_F(ElementPreconditionTest, Empty) {
}
TEST_F(ElementPreconditionTest, NonEmpty) {
- condition_.mutable_match()->add_selectors("exists");
+ *condition_.mutable_match() = ToSelectorProto("exists");
EXPECT_FALSE(ElementPrecondition(condition_).empty());
}
@@ -87,15 +87,15 @@ TEST_F(ElementPreconditionTest, EmptySelector) {
}
TEST_F(ElementPreconditionTest, ElementExists) {
- condition_.mutable_match()->add_selectors("exists");
+ *condition_.mutable_match() = ToSelectorProto("exists");
EXPECT_CALL(mock_callback_,
Run(Property(&ClientStatus::proto_status, ACTION_APPLIED), _));
Check(mock_callback_.Get());
}
-TEST_F(ElementPreconditionTest, ElementDoesNotExist) {
- condition_.mutable_match()->add_selectors("does_not_exist");
+TEST_F(ElementPreconditionTest, ElementDoes_Not_Exist) {
+ *condition_.mutable_match() = ToSelectorProto("does_not_exist");
EXPECT_CALL(
mock_callback_,
@@ -113,10 +113,10 @@ TEST_F(ElementPreconditionTest, AnyOf_Empty) {
}
TEST_F(ElementPreconditionTest, AnyOf_NoneMatch) {
- condition_.mutable_any_of()->add_conditions()->mutable_match()->add_selectors(
- "does_not_exist");
- condition_.mutable_any_of()->add_conditions()->mutable_match()->add_selectors(
- "does_not_exist_either");
+ *condition_.mutable_any_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("does_not_exist");
+ *condition_.mutable_any_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("does_not_exist_either");
EXPECT_CALL(
mock_callback_,
@@ -125,10 +125,10 @@ TEST_F(ElementPreconditionTest, AnyOf_NoneMatch) {
}
TEST_F(ElementPreconditionTest, AnyOf_SomeMatch) {
- condition_.mutable_any_of()->add_conditions()->mutable_match()->add_selectors(
- "exists");
- condition_.mutable_any_of()->add_conditions()->mutable_match()->add_selectors(
- "does_not_exist");
+ *condition_.mutable_any_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("exists");
+ *condition_.mutable_any_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("does_not_exist");
EXPECT_CALL(mock_callback_,
Run(Property(&ClientStatus::proto_status, ACTION_APPLIED), _));
@@ -136,10 +136,10 @@ TEST_F(ElementPreconditionTest, AnyOf_SomeMatch) {
}
TEST_F(ElementPreconditionTest, AnyOf_AllMatch) {
- condition_.mutable_any_of()->add_conditions()->mutable_match()->add_selectors(
- "exists");
- condition_.mutable_any_of()->add_conditions()->mutable_match()->add_selectors(
- "exists_too");
+ *condition_.mutable_any_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("exists");
+ *condition_.mutable_any_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("exists_too");
EXPECT_CALL(mock_callback_,
Run(Property(&ClientStatus::proto_status, ACTION_APPLIED), _));
@@ -155,10 +155,10 @@ TEST_F(ElementPreconditionTest, AllOf_Empty) {
}
TEST_F(ElementPreconditionTest, AllOf_NoneMatch) {
- condition_.mutable_all_of()->add_conditions()->mutable_match()->add_selectors(
- "does_not_exist");
- condition_.mutable_all_of()->add_conditions()->mutable_match()->add_selectors(
- "does_not_exist_either");
+ *condition_.mutable_all_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("does_not_exist");
+ *condition_.mutable_all_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("does_not_exist_either");
EXPECT_CALL(
mock_callback_,
@@ -167,10 +167,10 @@ TEST_F(ElementPreconditionTest, AllOf_NoneMatch) {
}
TEST_F(ElementPreconditionTest, AllOf_SomeMatch) {
- condition_.mutable_all_of()->add_conditions()->mutable_match()->add_selectors(
- "exists");
- condition_.mutable_all_of()->add_conditions()->mutable_match()->add_selectors(
- "does_not_exist");
+ *condition_.mutable_all_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("exists");
+ *condition_.mutable_all_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("does_not_exist");
EXPECT_CALL(
mock_callback_,
@@ -179,10 +179,10 @@ TEST_F(ElementPreconditionTest, AllOf_SomeMatch) {
}
TEST_F(ElementPreconditionTest, AllOf_AllMatch) {
- condition_.mutable_all_of()->add_conditions()->mutable_match()->add_selectors(
- "exists");
- condition_.mutable_all_of()->add_conditions()->mutable_match()->add_selectors(
- "exists_too");
+ *condition_.mutable_all_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("exists");
+ *condition_.mutable_all_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("exists_too");
EXPECT_CALL(mock_callback_,
Run(Property(&ClientStatus::proto_status, ACTION_APPLIED), _));
@@ -198,14 +198,10 @@ TEST_F(ElementPreconditionTest, NoneOf_Empty) {
}
TEST_F(ElementPreconditionTest, NoneOf_NoneMatch) {
- condition_.mutable_none_of()
- ->add_conditions()
- ->mutable_match()
- ->add_selectors("does_not_exist");
- condition_.mutable_none_of()
- ->add_conditions()
- ->mutable_match()
- ->add_selectors("does_not_exist_either");
+ *condition_.mutable_none_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("does_not_exist");
+ *condition_.mutable_none_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("does_not_exist_either");
EXPECT_CALL(mock_callback_,
Run(Property(&ClientStatus::proto_status, ACTION_APPLIED), _));
@@ -213,14 +209,10 @@ TEST_F(ElementPreconditionTest, NoneOf_NoneMatch) {
}
TEST_F(ElementPreconditionTest, NoneOf_SomeMatch) {
- condition_.mutable_none_of()
- ->add_conditions()
- ->mutable_match()
- ->add_selectors("exists");
- condition_.mutable_none_of()
- ->add_conditions()
- ->mutable_match()
- ->add_selectors("does_not_exist");
+ *condition_.mutable_none_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("exists");
+ *condition_.mutable_none_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("does_not_exist");
EXPECT_CALL(
mock_callback_,
@@ -229,14 +221,10 @@ TEST_F(ElementPreconditionTest, NoneOf_SomeMatch) {
}
TEST_F(ElementPreconditionTest, NoneOf_AllMatch) {
- condition_.mutable_none_of()
- ->add_conditions()
- ->mutable_match()
- ->add_selectors("exists");
- condition_.mutable_none_of()
- ->add_conditions()
- ->mutable_match()
- ->add_selectors("exists_too");
+ *condition_.mutable_none_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("exists");
+ *condition_.mutable_none_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("exists_too");
EXPECT_CALL(
mock_callback_,
@@ -246,11 +234,11 @@ TEST_F(ElementPreconditionTest, NoneOf_AllMatch) {
TEST_F(ElementPreconditionTest, Payload_ConditionMet) {
auto* exists = condition_.mutable_any_of()->add_conditions();
- exists->mutable_match()->add_selectors("exists");
+ *exists->mutable_match() = ToSelectorProto("exists");
exists->set_payload("exists");
auto* exists_too = condition_.mutable_any_of()->add_conditions();
- exists_too->mutable_match()->add_selectors("exists_too");
+ *exists_too->mutable_match() = ToSelectorProto("exists_too");
exists_too->set_payload("exists_too");
condition_.set_payload("any_of");
@@ -263,11 +251,11 @@ TEST_F(ElementPreconditionTest, Payload_ConditionMet) {
TEST_F(ElementPreconditionTest, Payload_ConditionNotMet) {
auto* exists = condition_.mutable_none_of()->add_conditions();
- exists->mutable_match()->add_selectors("exists");
+ *exists->mutable_match() = ToSelectorProto("exists");
exists->set_payload("exists");
auto* exists_too = condition_.mutable_none_of()->add_conditions();
- exists_too->mutable_match()->add_selectors("exists_too");
+ *exists_too->mutable_match() = ToSelectorProto("exists_too");
exists_too->set_payload("exists_too");
condition_.set_payload("none_of");
@@ -279,27 +267,28 @@ TEST_F(ElementPreconditionTest, Payload_ConditionNotMet) {
}
TEST_F(ElementPreconditionTest, Complex) {
- condition_.mutable_all_of()->add_conditions()->mutable_match()->add_selectors(
- "exists");
- condition_.mutable_all_of()->add_conditions()->mutable_match()->add_selectors(
- "exists_too");
+ *condition_.mutable_all_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("exists");
+ *condition_.mutable_all_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("exists_too");
auto* none_of = condition_.mutable_all_of()->add_conditions();
none_of->set_payload("none_of");
auto* does_not_exist_in_none_of =
none_of->mutable_none_of()->add_conditions();
- does_not_exist_in_none_of->mutable_match()->add_selectors("does_not_exist");
+ *does_not_exist_in_none_of->mutable_match() =
+ ToSelectorProto("does_not_exist");
does_not_exist_in_none_of->set_payload("does_not_exist in none_of");
- none_of->mutable_none_of()->add_conditions()->mutable_match()->add_selectors(
- "does_not_exist_either");
+ *none_of->mutable_none_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("does_not_exist_either");
auto* any_of = condition_.mutable_all_of()->add_conditions();
any_of->set_payload("any_of");
auto* exists_in_any_of = any_of->mutable_any_of()->add_conditions();
- exists_in_any_of->mutable_match()->add_selectors("exists");
+ *exists_in_any_of->mutable_match() = ToSelectorProto("exists");
exists_in_any_of->set_payload("exists in any_of");
- any_of->mutable_any_of()->add_conditions()->mutable_match()->add_selectors(
- "does_not_exist");
+ *any_of->mutable_any_of()->add_conditions()->mutable_match() =
+ ToSelectorProto("does_not_exist");
EXPECT_CALL(mock_callback_,
Run(Property(&ClientStatus::proto_status, ACTION_APPLIED),
diff --git a/chromium/components/autofill_assistant/browser/event_handler.cc b/chromium/components/autofill_assistant/browser/event_handler.cc
index 886f8f96c4c..543d22d176b 100644
--- a/chromium/components/autofill_assistant/browser/event_handler.cc
+++ b/chromium/components/autofill_assistant/browser/event_handler.cc
@@ -3,6 +3,9 @@
// found in the LICENSE file.
#include "components/autofill_assistant/browser/event_handler.h"
+#include "base/strings/string_number_conversions.h"
+
+#include "base/logging.h"
namespace autofill_assistant {
@@ -24,6 +27,66 @@ void EventHandler::RemoveObserver(const Observer* observer) {
observers_.RemoveObserver(observer);
}
+// static
+base::Optional<EventHandler::EventKey> EventHandler::CreateEventKeyFromProto(
+ const EventProto& proto) {
+ switch (proto.kind_case()) {
+ case EventProto::kOnValueChanged:
+ if (proto.on_value_changed().model_identifier().empty()) {
+ VLOG(1) << "Invalid OnValueChangedEventProto: no model_identifier "
+ "specified";
+ return base::nullopt;
+ }
+ return base::Optional<EventHandler::EventKey>(
+ {proto.kind_case(), proto.on_value_changed().model_identifier()});
+ case EventProto::kOnViewClicked:
+ if (proto.on_view_clicked().view_identifier().empty()) {
+ VLOG(1) << "Invalid OnViewClickedEventProto: no view_identifier "
+ "specified";
+ return base::nullopt;
+ }
+ return base::Optional<EventHandler::EventKey>(
+ {proto.kind_case(), proto.on_view_clicked().view_identifier()});
+ case EventProto::kOnUserActionCalled:
+ if (proto.on_user_action_called().user_action_identifier().empty()) {
+ VLOG(1) << "Invalid OnUserActionCalled: no user_action_identifier "
+ "specified";
+ return base::nullopt;
+ }
+ return base::Optional<EventHandler::EventKey>(
+ {proto.kind_case(),
+ proto.on_user_action_called().user_action_identifier()});
+ case EventProto::kOnTextLinkClicked:
+ if (!proto.on_text_link_clicked().has_text_link()) {
+ VLOG(1) << "Invalid OnTextLinkClickedProto: no text_link specified";
+ return base::nullopt;
+ }
+ return base::Optional<EventHandler::EventKey>(
+ {proto.kind_case(),
+ base::NumberToString(proto.on_text_link_clicked().text_link())});
+ case EventProto::kOnPopupDismissed:
+ if (proto.on_popup_dismissed().popup_identifier().empty()) {
+ VLOG(1)
+ << "Invalid OnPopupDismissedProto: no popup_identifier specified";
+ return base::nullopt;
+ }
+ return base::Optional<EventHandler::EventKey>(
+ {proto.kind_case(), proto.on_popup_dismissed().popup_identifier()});
+ case EventProto::kOnViewContainerCleared:
+ if (proto.on_view_container_cleared().view_identifier().empty()) {
+ VLOG(1) << "Invalid OnViewContainerClearedProto: no view_identifier "
+ "specified";
+ return base::nullopt;
+ }
+ return base::Optional<EventHandler::EventKey>(
+ {proto.kind_case(),
+ proto.on_view_container_cleared().view_identifier()});
+ case EventProto::KIND_NOT_SET:
+ VLOG(1) << "Error creating event: kind not set";
+ return base::nullopt;
+ }
+}
+
std::ostream& operator<<(std::ostream& out,
const EventProto::KindCase& event_case) {
#ifdef NDEBUG
@@ -46,6 +109,9 @@ std::ostream& operator<<(std::ostream& out,
case EventProto::kOnPopupDismissed:
out << "kOnPopupDismissed";
break;
+ case EventProto::kOnViewContainerCleared:
+ out << "kOnViewContainerCleared";
+ break;
case EventProto::KIND_NOT_SET:
break;
}
diff --git a/chromium/components/autofill_assistant/browser/event_handler.h b/chromium/components/autofill_assistant/browser/event_handler.h
index e4718433008..92ac4b96344 100644
--- a/chromium/components/autofill_assistant/browser/event_handler.h
+++ b/chromium/components/autofill_assistant/browser/event_handler.h
@@ -10,6 +10,7 @@
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
+#include "base/optional.h"
#include "components/autofill_assistant/browser/service.pb.h"
#include "components/autofill_assistant/browser/user_model.h"
@@ -37,6 +38,9 @@ class EventHandler {
void DispatchEvent(const EventKey& key);
+ static base::Optional<EventKey> CreateEventKeyFromProto(
+ const EventProto& proto);
+
void AddObserver(Observer* observer);
void RemoveObserver(const Observer* observer);
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 3c8b0febdc0..616f918d7e9 100644
--- a/chromium/components/autofill_assistant/browser/fake_script_executor_delegate.cc
+++ b/chromium/components/autofill_assistant/browser/fake_script_executor_delegate.cc
@@ -103,8 +103,13 @@ void FakeScriptExecutorDelegate::ClearInfoBox() {
void FakeScriptExecutorDelegate::SetProgress(int progress) {}
+void FakeScriptExecutorDelegate::SetProgressActiveStep(int active_step) {}
+
void FakeScriptExecutorDelegate::SetProgressVisible(bool visible) {}
+void FakeScriptExecutorDelegate::SetStepProgressBarConfiguration(
+ const ShowProgressBarProto::StepProgressBarConfiguration& configuration) {}
+
void FakeScriptExecutorDelegate::SetUserActions(
std::unique_ptr<std::vector<UserAction>> user_actions) {
user_actions_ = std::move(user_actions);
@@ -126,11 +131,6 @@ void FakeScriptExecutorDelegate::WriteUserData(
std::move(write_callback).Run(payment_request_info_.get(), &field_change);
}
-void FakeScriptExecutorDelegate::WriteUserModel(
- base::OnceCallback<void(UserModel*)> write_callback) {
- std::move(write_callback).Run(user_model_);
-}
-
void FakeScriptExecutorDelegate::SetViewportMode(ViewportMode mode) {
viewport_mode_ = mode;
}
@@ -158,6 +158,8 @@ void FakeScriptExecutorDelegate::CollapseBottomSheet() {
expand_or_collapse_value_ = false;
}
+void FakeScriptExecutorDelegate::ExpectNavigation() {}
+
bool FakeScriptExecutorDelegate::HasNavigationError() {
return navigation_error_;
}
@@ -204,8 +206,9 @@ EventHandler* FakeScriptExecutorDelegate::GetEventHandler() {
void FakeScriptExecutorDelegate::SetGenericUi(
std::unique_ptr<GenericUserInterfaceProto> generic_ui,
- base::OnceCallback<void(bool, ProcessedActionStatusProto, const UserModel*)>
- end_action_callback) {}
+ base::OnceCallback<void(const ClientStatus&)> end_action_callback,
+ base::OnceCallback<void(const ClientStatus&)>
+ view_inflation_finished_callback) {}
void FakeScriptExecutorDelegate::ClearGenericUi() {}
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 4594334faac..3948c00cc2a 100644
--- a/chromium/components/autofill_assistant/browser/fake_script_executor_delegate.h
+++ b/chromium/components/autofill_assistant/browser/fake_script_executor_delegate.h
@@ -46,14 +46,16 @@ class FakeScriptExecutorDelegate : public ScriptExecutorDelegate {
void SetInfoBox(const InfoBox& info_box) override;
void ClearInfoBox() override;
void SetProgress(int progress) override;
+ void SetProgressActiveStep(int active_step) override;
void SetProgressVisible(bool visible) override;
+ void SetStepProgressBarConfiguration(
+ const ShowProgressBarProto::StepProgressBarConfiguration& configuration)
+ override;
void SetUserActions(
std::unique_ptr<std::vector<UserAction>> user_actions) override;
void SetCollectUserDataOptions(CollectUserDataOptions* options) override;
void WriteUserData(
base::OnceCallback<void(UserData*, UserData::FieldChange*)>) override;
- void WriteUserModel(
- base::OnceCallback<void(UserModel*)> write_callback) override;
void SetViewportMode(ViewportMode mode) override;
ViewportMode GetViewportMode() override;
void SetPeekMode(ConfigureBottomSheetProto::PeekMode peek_mode) override;
@@ -66,6 +68,7 @@ class FakeScriptExecutorDelegate : public ScriptExecutorDelegate {
base::OnceCallback<void(const ClientStatus&)> cancel_callback) override;
UserModel* GetUserModel() override;
EventHandler* GetEventHandler() override;
+ void ExpectNavigation() override;
bool HasNavigationError() override;
bool IsNavigatingToNewDocument() override;
void RequireUI() override;
@@ -76,9 +79,9 @@ class FakeScriptExecutorDelegate : public ScriptExecutorDelegate {
void SetGenericUi(
std::unique_ptr<GenericUserInterfaceProto> generic_ui,
- base::OnceCallback<void(bool,
- ProcessedActionStatusProto,
- const UserModel*)> end_action_callback) override;
+ base::OnceCallback<void(const ClientStatus&)> end_action_callback,
+ base::OnceCallback<void(const ClientStatus&)>
+ view_inflation_finished_callback) override;
void ClearGenericUi() override;
ClientSettings* GetMutableSettings() { return &client_settings_; }
diff --git a/chromium/components/autofill_assistant/browser/features.cc b/chromium/components/autofill_assistant/browser/features.cc
index 943ab632ad6..6f42c54b476 100644
--- a/chromium/components/autofill_assistant/browser/features.cc
+++ b/chromium/components/autofill_assistant/browser/features.cc
@@ -12,6 +12,14 @@ namespace features {
const base::Feature kAutofillAssistant{"AutofillAssistant",
base::FEATURE_ENABLED_BY_DEFAULT};
+// Guard for the end condition when a non-renderer-initiated navigation occurs
+// while the AutofillAssistant is in RUNNING state.
+// TODO(b/159309621): Remove this if the end condition shows no unwanted side
+// effects.
+const base::Feature kAutofillAssistantBreakOnRunningNavigation{
+ "AutofillAssistantBreakOnRunningNavigation",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
// Controls whether to enable Assistant Autofill in a normal Chrome tab.
const base::Feature kAutofillAssistantChromeEntry{
"AutofillAssistantChromeEntry", base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/chromium/components/autofill_assistant/browser/features.h b/chromium/components/autofill_assistant/browser/features.h
index 689c466b23c..0478342a905 100644
--- a/chromium/components/autofill_assistant/browser/features.h
+++ b/chromium/components/autofill_assistant/browser/features.h
@@ -11,8 +11,10 @@ struct Feature;
namespace autofill_assistant {
namespace features {
+
// All features in alphabetical order.
extern const base::Feature kAutofillAssistant;
+extern const base::Feature kAutofillAssistantBreakOnRunningNavigation;
extern const base::Feature kAutofillAssistantChromeEntry;
extern const base::Feature kAutofillAssistantDirectActions;
diff --git a/chromium/components/autofill_assistant/browser/field_formatter.cc b/chromium/components/autofill_assistant/browser/field_formatter.cc
new file mode 100644
index 00000000000..8045c89530e
--- /dev/null
+++ b/chromium/components/autofill_assistant/browser/field_formatter.cc
@@ -0,0 +1,128 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill_assistant/browser/field_formatter.h"
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/autofill/core/browser/autofill_data_util.h"
+#include "components/autofill/core/browser/autofill_type.h"
+#include "components/autofill/core/browser/field_types.h"
+#include "components/autofill/core/browser/geo/state_names.h"
+#include "components/autofill_assistant/browser/generic_ui.pb.h"
+#include "third_party/re2/src/re2/re2.h"
+#include "third_party/re2/src/re2/stringpiece.h"
+
+namespace {
+// Regex to find placeholders of the form ${key}, where key is an arbitrary
+// string that does not contain curly braces.
+const char kPlaceholderExtractor[] = R"re(\$\{([^{}]+)\})re";
+
+base::Optional<std::string> GetFieldValue(
+ const std::map<std::string, std::string>& mappings,
+ const std::string& key) {
+ auto it = mappings.find(key);
+ if (it == mappings.end()) {
+ return base::nullopt;
+ }
+ return it->second;
+}
+
+std::map<std::string, std::string> CreateFormGroupMappings(
+ const autofill::FormGroup& form_group,
+ const std::string& locale) {
+ std::map<std::string, std::string> mappings;
+ autofill::ServerFieldTypeSet available_fields;
+ form_group.GetNonEmptyTypes(locale, &available_fields);
+ for (const auto& field : available_fields) {
+ mappings.emplace(base::NumberToString(static_cast<int>(field)),
+ base::UTF16ToUTF8(form_group.GetInfo(
+ autofill::AutofillType(field), locale)));
+ }
+ return mappings;
+}
+
+} // namespace
+
+namespace autofill_assistant {
+namespace field_formatter {
+
+base::Optional<std::string> FormatString(
+ const std::string& pattern,
+ const std::map<std::string, std::string>& mappings) {
+ if (pattern.empty()) {
+ return std::string();
+ }
+
+ std::string key;
+ std::string out = pattern;
+ re2::StringPiece input(pattern);
+ while (re2::RE2::FindAndConsume(&input, kPlaceholderExtractor, &key)) {
+ auto rewrite_value = GetFieldValue(mappings, key);
+ if (!rewrite_value.has_value()) {
+ VLOG(2) << "No value for " << key << " in " << pattern;
+ return base::nullopt;
+ }
+
+ re2::RE2::Replace(&out, kPlaceholderExtractor,
+ re2::StringPiece(rewrite_value.value()));
+ }
+
+ return out;
+}
+
+template <>
+std::map<std::string, std::string>
+CreateAutofillMappings<autofill::AutofillProfile>(
+ const autofill::AutofillProfile& profile,
+ const std::string& locale) {
+ auto mappings = CreateFormGroupMappings(profile, locale);
+
+ auto state = profile.GetInfo(
+ autofill::AutofillType(autofill::ADDRESS_HOME_STATE), locale);
+ if (!state.empty()) {
+ // TODO(b/159309560): Capitalize first letter of the state name.
+ auto state_name =
+ base::UTF16ToUTF8(autofill::state_names::GetNameForAbbreviation(state));
+ if (!state_name.empty()) {
+ mappings[base::NumberToString(static_cast<int>(
+ AutofillFormatProto::ADDRESS_HOME_STATE_NAME))] = state_name;
+ }
+ }
+
+ return mappings;
+}
+
+template <>
+std::map<std::string, std::string> CreateAutofillMappings<autofill::CreditCard>(
+ const autofill::CreditCard& credit_card,
+ const std::string& locale) {
+ auto mappings = CreateFormGroupMappings(credit_card, locale);
+
+ auto network = std::string(
+ autofill::data_util::GetPaymentRequestData(credit_card.network())
+ .basic_card_issuer_network);
+ if (!network.empty()) {
+ mappings[base::NumberToString(
+ static_cast<int>(AutofillFormatProto::CREDIT_CARD_NETWORK))] = network;
+ }
+ auto network_for_display = base::UTF16ToUTF8(credit_card.NetworkForDisplay());
+ if (!network_for_display.empty()) {
+ mappings[base::NumberToString(static_cast<int>(
+ AutofillFormatProto::CREDIT_CARD_NETWORK_FOR_DISPLAY))] =
+ network_for_display;
+ }
+ auto last_four_digits = base::UTF16ToUTF8(credit_card.LastFourDigits());
+ if (!last_four_digits.empty()) {
+ mappings[base::NumberToString(static_cast<int>(
+ AutofillFormatProto::CREDIT_CARD_NUMBER_LAST_FOUR_DIGITS))] =
+ last_four_digits;
+ }
+
+ return mappings;
+}
+
+} // namespace field_formatter
+} // namespace autofill_assistant
diff --git a/chromium/components/autofill_assistant/browser/field_formatter.h b/chromium/components/autofill_assistant/browser/field_formatter.h
new file mode 100644
index 00000000000..ce934e9132a
--- /dev/null
+++ b/chromium/components/autofill_assistant/browser/field_formatter.h
@@ -0,0 +1,37 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_FIELD_FORMATTER_H_
+#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_FIELD_FORMATTER_H_
+
+#include <map>
+#include <string>
+#include "base/optional.h"
+#include "components/autofill/core/browser/data_model/autofill_profile.h"
+#include "components/autofill/core/browser/data_model/credit_card.h"
+
+namespace autofill_assistant {
+namespace field_formatter {
+
+// Replaces all placeholder occurrences of the form ${key} in |input| with the
+// corresponding value in |mappings|, where |key| is an arbitrary string that
+// does not contain curly braces. Fails if any of the found placeholders is not
+// in |mappings|.
+base::Optional<std::string> FormatString(
+ const std::string& input,
+ const std::map<std::string, std::string>& mappings);
+
+// Creates a lookup map for all non-empty autofill and custom
+// AutofillFormatProto::AutofillAssistantCustomField field types in
+// |autofill_data_model|.
+// |locale| should be a locale string such as "en-US".
+template <typename T>
+std::map<std::string, std::string> CreateAutofillMappings(
+ const T& autofill_data_model,
+ const std::string& locale);
+
+} // namespace field_formatter
+} // namespace autofill_assistant
+
+#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_FIELD_FORMATTER_H_
diff --git a/chromium/components/autofill_assistant/browser/field_formatter_unittest.cc b/chromium/components/autofill_assistant/browser/field_formatter_unittest.cc
new file mode 100644
index 00000000000..f7bda3bdfbf
--- /dev/null
+++ b/chromium/components/autofill_assistant/browser/field_formatter_unittest.cc
@@ -0,0 +1,209 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill_assistant/browser/field_formatter.h"
+
+#include "base/guid.h"
+#include "base/strings/string_number_conversions.h"
+#include "components/autofill/core/browser/autofill_test_utils.h"
+#include "components/autofill/core/browser/data_model/autofill_profile.h"
+#include "components/autofill/core/browser/data_model/credit_card.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace autofill_assistant {
+namespace field_formatter {
+namespace {
+const char kFakeUrl[] = "https://www.example.com";
+
+using ::testing::_;
+using ::testing::Eq;
+using ::testing::UnorderedElementsAreArray;
+
+TEST(FieldFormatterTest, FormatString) {
+ std::map<std::string, std::string> mappings = {
+ {"keyA", "valueA"}, {"keyB", "valueB"}, {"keyC", "valueC"}};
+
+ EXPECT_EQ(*FormatString("", mappings), "");
+ EXPECT_EQ(*FormatString("input", mappings), "input");
+ EXPECT_EQ(*FormatString("prefix ${keyA}", mappings), "prefix valueA");
+ EXPECT_EQ(*FormatString("prefix ${keyA}${keyB}${keyC} suffix", mappings),
+ "prefix valueAvalueBvalueC suffix");
+ EXPECT_EQ(*FormatString("keyA = ${keyA}", mappings), "keyA = valueA");
+ EXPECT_EQ(FormatString("${keyD}", mappings), base::nullopt);
+ EXPECT_EQ(FormatString("${keyA}${keyD}", mappings), base::nullopt);
+}
+
+TEST(FieldFormatterTest, AutofillProfile) {
+ autofill::AutofillProfile profile(base::GenerateGUID(), kFakeUrl);
+ autofill::test::SetProfileInfo(
+ &profile, "John", "", "Doe", "editor@gmail.com", "", "203 Barfield Lane",
+ "", "Mountain View", "CA", "94043", "US", "+12345678901");
+
+ // NAME_FIRST NAME_LAST
+ EXPECT_EQ(
+ *FormatString("${3} ${5}", CreateAutofillMappings(profile, "en-US")),
+ "John Doe");
+
+ // PHONE_HOME_COUNTRY_CODE, PHONE_HOME_CITY_CODE, PHONE_HOME_NUMBER
+ EXPECT_EQ(*FormatString("(+${12}) (${11}) ${10}",
+ CreateAutofillMappings(profile, "en-US")),
+ "(+1) (234) 5678901");
+
+ // ADDRESS_HOME_STATE, ADDRESS_HOME_STATE_NAME
+ EXPECT_EQ(
+ *FormatString("${34} - ${-6}", CreateAutofillMappings(profile, "en-US")),
+ "CA - california");
+
+ // Unknown state.
+ autofill::AutofillProfile unknown_state_profile(base::GenerateGUID(),
+ kFakeUrl);
+ autofill::test::SetProfileInfo(&unknown_state_profile, "John", "", "Doe", "",
+ "", "", "", "", "XY", "", "US", "");
+ EXPECT_EQ(FormatString("${34}", CreateAutofillMappings(unknown_state_profile,
+ "en-US")),
+ "XY");
+ EXPECT_EQ(FormatString("${-6}", CreateAutofillMappings(unknown_state_profile,
+ "en-US")),
+ base::nullopt);
+
+ // UNKNOWN_TYPE
+ EXPECT_EQ(FormatString("${1}", CreateAutofillMappings(profile, "en-US")),
+ base::nullopt);
+}
+
+TEST(FieldFormatterTest, CreditCard) {
+ autofill::CreditCard credit_card(base::GenerateGUID(), kFakeUrl);
+ autofill::test::SetCreditCardInfo(&credit_card, "John Doe",
+ "4111 1111 1111 1111", "01", "2050", "");
+
+ // CREDIT_CARD_NAME_FULL
+ EXPECT_EQ(
+ *FormatString("${51}", CreateAutofillMappings(credit_card, "en-US")),
+ "John Doe");
+
+ // CREDIT_CARD_NUMBER
+ EXPECT_EQ(
+ *FormatString("${52}", CreateAutofillMappings(credit_card, "en-US")),
+ "4111111111111111");
+
+ // CREDIT_CARD_NUMBER_LAST_FOUR_DIGITS
+ EXPECT_EQ(
+ *FormatString("**** ${-4}", CreateAutofillMappings(credit_card, "en-US")),
+ "**** 1111");
+
+ // CREDIT_CARD_EXP_MONTH, CREDIT_CARD_EXP_2_DIGIT_YEAR
+ EXPECT_EQ(*FormatString("${53}/${54}",
+ CreateAutofillMappings(credit_card, "en-US")),
+ "01/50");
+
+ // CREDIT_CARD_NETWORK, CREDIT_CARD_NETWORK_FOR_DISPLAY
+ EXPECT_EQ(*FormatString("${-2} ${-5}",
+ CreateAutofillMappings(credit_card, "en-US")),
+ "visa Visa");
+}
+
+TEST(FieldFormatterTest, SpecialCases) {
+ autofill::AutofillProfile profile(base::GenerateGUID(), kFakeUrl);
+ autofill::test::SetProfileInfo(
+ &profile, "John", "", "Doe", "editor@gmail.com", "", "203 Barfield Lane",
+ "", "Mountain View", "CA", "94043", "US", "+12345678901");
+
+ EXPECT_EQ(*FormatString("", CreateAutofillMappings(profile, "en-US")),
+ std::string());
+ EXPECT_EQ(*FormatString("${3}", CreateAutofillMappings(profile, "en-US")),
+ "John");
+ EXPECT_EQ(FormatString("${-1}", CreateAutofillMappings(profile, "en-US")),
+ base::nullopt);
+ EXPECT_EQ(
+ FormatString(
+ "${" + base::NumberToString(autofill::MAX_VALID_FIELD_TYPE) + "}",
+ CreateAutofillMappings(profile, "en-US")),
+ base::nullopt);
+
+ // Second {} is not prefixed with $.
+ EXPECT_EQ(
+ *FormatString("${3} {10}", CreateAutofillMappings(profile, "en-US")),
+ "John {10}");
+}
+
+TEST(FieldFormatterTest, DifferentLocales) {
+ autofill::AutofillProfile profile(base::GenerateGUID(), kFakeUrl);
+ autofill::test::SetProfileInfo(
+ &profile, "John", "", "Doe", "editor@gmail.com", "", "203 Barfield Lane",
+ "", "Mountain View", "CA", "94043", "US", "+12345678901");
+ auto mappings = CreateAutofillMappings(profile, "de-DE");
+
+ // 36 == ADDRESS_HOME_COUNTRY
+ EXPECT_EQ(*FormatString("${36}", CreateAutofillMappings(profile, "en-US")),
+ "United States");
+ EXPECT_EQ(*FormatString("${36}", CreateAutofillMappings(profile, "de-DE")),
+ "Vereinigte Staaten");
+
+ // Invalid locales default to "en-US".
+ EXPECT_EQ(*FormatString("${36}", CreateAutofillMappings(profile, "")),
+ "United States");
+ EXPECT_EQ(*FormatString("${36}", CreateAutofillMappings(profile, "invalid")),
+ "United States");
+}
+
+TEST(FieldFormatterTest, AddsAllProfileFields) {
+ std::map<std::string, std::string> expected_values = {
+ {"3", "Alpha"},
+ {"4", "Beta"},
+ {"5", "Gamma"},
+ {"6", "B"},
+ {"7", "Alpha Beta Gamma"},
+ {"9", "alpha@google.com"},
+ {"10", "1234567"},
+ {"11", "79"},
+ {"12", "41"},
+ {"13", "0791234567"},
+ {"14", "+41791234567"},
+ {"30", "Brandschenkestrasse 110"},
+ {"31", "Google Building 110"},
+ {"33", "Zurich"},
+ {"34", "Canton Zurich"},
+ {"35", "8002"},
+ {"36", "Switzerland"},
+ {"60", "Google"},
+ {"77", "Brandschenkestrasse 110\nGoogle Building 110"}};
+
+ autofill::AutofillProfile profile(base::GenerateGUID(), kFakeUrl);
+ autofill::test::SetProfileInfo(
+ &profile, "Alpha", "Beta", "Gamma", "alpha@google.com", "Google",
+ "Brandschenkestrasse 110", "Google Building 110", "Zurich",
+ "Canton Zurich", "8002", "CH", "+41791234567");
+
+ EXPECT_THAT(CreateAutofillMappings(profile, "en-US"),
+ UnorderedElementsAreArray(expected_values));
+}
+
+TEST(FieldFormatterTest, AddsAllCreditCardFields) {
+ std::map<std::string, std::string> expected_values = {
+ {"-5", "Visa"},
+ {"-4", "1111"},
+ {"-2", "visa"},
+ {"51", "Alpha Beta Gamma"},
+ {"52", "4111111111111111"},
+ {"53", "08"},
+ {"54", "50"},
+ {"55", "2050"},
+ {"56", "08/50"},
+ {"57", "08/2050"},
+ {"58", "Visa"},
+ {"91", "Alpha"},
+ {"92", "Gamma"}};
+
+ autofill::CreditCard credit_card(base::GenerateGUID(), kFakeUrl);
+ autofill::test::SetCreditCardInfo(&credit_card, "Alpha Beta Gamma",
+ "4111111111111111", "8", "2050", "");
+
+ EXPECT_THAT(CreateAutofillMappings(credit_card, "en-US"),
+ UnorderedElementsAreArray(expected_values));
+}
+
+} // namespace
+} // namespace field_formatter
+} // namespace autofill_assistant
diff --git a/chromium/components/autofill_assistant/browser/generic_ui.proto b/chromium/components/autofill_assistant/browser/generic_ui.proto
index d87228400a7..e4031ba99c7 100644
--- a/chromium/components/autofill_assistant/browser/generic_ui.proto
+++ b/chromium/components/autofill_assistant/browser/generic_ui.proto
@@ -49,6 +49,9 @@ message CallbackProto {
SetViewVisibilityProto set_view_visibility = 11;
SetViewEnabledProto set_view_enabled = 12;
ShowGenericUiPopupProto show_generic_popup = 13;
+ CreateNestedGenericUiProto create_nested_ui = 14;
+ ClearViewContainerProto clear_view_container = 15;
+ ForEachProto for_each = 16;
}
// Optional model identifier pointing to a single boolean. If set, the
// callback will only be invoked if the condition is true.
@@ -62,6 +65,7 @@ message EventProto {
OnUserActionCalled on_user_action_called = 3;
OnTextLinkClickedProto on_text_link_clicked = 4;
OnPopupDismissedProto on_popup_dismissed = 5;
+ OnViewContainerClearedProto on_view_container_cleared = 6;
}
}
@@ -96,6 +100,13 @@ message OnPopupDismissedProto {
optional string popup_identifier = 1;
}
+// Event which is called whenever |view_identifier| was cleared with a
+// |ClearViewContainer| interaction.
+message OnViewContainerClearedProto {
+ // The view container that was cleared.
+ optional string view_identifier = 1;
+}
+
// Callback that writes the specified value to |model_identifier|.
message SetModelValueProto {
// The model identifier to write to.
@@ -158,7 +169,10 @@ message ToStringProto {
optional ValueReferenceProto value = 3;
// Optional format options.
- oneof format_options { DateFormatProto date_format = 2; }
+ oneof format_options {
+ DateFormatProto date_format = 2;
+ AutofillFormatProto autofill_format = 4;
+ }
reserved 1;
}
@@ -168,6 +182,32 @@ message DateFormatProto {
optional string date_format = 1;
}
+// Format options for autofill profiles and credit cards.
+message AutofillFormatProto {
+ // Fields we add that are not mapped to field_types.h. The values must be
+ // negative to avoid overlap with field_types.h.
+ // Note: CREDIT_CARD_VERIFICATION_CODE and CREDIT_CARD_RAW_NUMBER are only
+ // available in UseCreditCardAction, as they require the CVC dialog to be
+ // shown.
+ enum AutofillAssistantCustomField {
+ UNDEFINED = 0;
+ CREDIT_CARD_VERIFICATION_CODE = -1;
+ CREDIT_CARD_NETWORK = -2;
+ CREDIT_CARD_RAW_NUMBER = -3;
+ CREDIT_CARD_NUMBER_LAST_FOUR_DIGITS = -4;
+ CREDIT_CARD_NETWORK_FOR_DISPLAY = -5;
+ // Currently only US states are supported. The state name is in lower case.
+ ADDRESS_HOME_STATE_NAME = -6;
+ }
+ // The format string to use. May contain one or multiple "${key}"
+ // placeholders, where the key is an integer corresponding to
+ // entries from field_types.h or AutofillAssistantCustomField.
+ optional string pattern = 1;
+ // The locale to use when stringifying data. Invalid locales will
+ // automatically default to "en-US".
+ optional string locale = 2;
+}
+
// A comparison of two values in the form |value_a| <mode> |value_b|. EQUAL is
// supported for all values. All other comparison modes are only supported for
// single integers, strings, and dates.
@@ -331,3 +371,42 @@ message SetViewEnabledProto {
// Whether the view should be enabled or not. Must point to a single boolean.
optional ValueReferenceProto enabled = 2;
}
+
+// Creates new UI and interactions, i.e., a nested instance and attaches it to
+// |parent_view_identifier|. Nested UI instances have their own view layout and
+// interactions, but share the same model with their parent. All interactions
+// between ancestor and descendant UIs must pass through the user model, as
+// neither can directly reference views or interactions of the other.
+//
+// Use |ClearViewContainerProto| on a nested UI's view parent to detach and
+// destroy the nested UI.
+message CreateNestedGenericUiProto {
+ // Identifier for the newly created instance.
+ optional string generic_ui_identifier = 1;
+ // The UI to create.
+ optional GenericUserInterfaceProto generic_ui = 2;
+ // The view identifier of the parent to attach (=append) this UI to.
+ optional string parent_view_identifier = 3;
+}
+
+// Removes all views from the target view container. Nested UI instances
+// attached to |view_identifier| will be destroyed.
+message ClearViewContainerProto {
+ // The view container to clear.
+ optional string view_identifier = 1;
+}
+
+// Invokes |callbacks| for each item in the loop value. Automatically replaces
+// instances of "${i}" in model and view identifiers with the loop counter.
+message ForEachProto {
+ // The loop counter, usually "i", "j", etc. Callbacks may use this counter
+ // in view and model identifiers by using "${i}"" placeholders, e.g.,
+ // "profiles[${i}]" or "my_view_${i}".
+ optional string loop_counter = 1;
+
+ // The value list to loop over.
+ optional string loop_value_model_identifier = 2;
+
+ // The callbacks to invoke for every iteration of the loop.
+ repeated CallbackProto callbacks = 3;
+}
diff --git a/chromium/components/autofill_assistant/browser/generic_ui_java_generated_enums.h b/chromium/components/autofill_assistant/browser/generic_ui_java_generated_enums.h
index 29539e5368a..23d29a1a570 100644
--- a/chromium/components/autofill_assistant/browser/generic_ui_java_generated_enums.h
+++ b/chromium/components/autofill_assistant/browser/generic_ui_java_generated_enums.h
@@ -19,6 +19,26 @@ enum class VerticalExpanderChevronStyle {
NEVER = 2,
};
+// GENERATED_JAVA_ENUM_PACKAGE: (
+// org.chromium.chrome.browser.autofill_assistant.drawable)
+// GENERATED_JAVA_CLASS_NAME_OVERRIDE: AssistantDrawableIcon
+enum class DrawableIcon {
+ DRAWABLE_ICON_UNDEFINED = 0,
+ PROGRESSBAR_DEFAULT_INITIAL_STEP = 1,
+ PROGRESSBAR_DEFAULT_DATA_COLLECTION = 2,
+ PROGRESSBAR_DEFAULT_PAYMENT = 3,
+ PROGRESSBAR_DEFAULT_FINAL_STEP = 4,
+ SITTING_PERSON = 5,
+ TICKET_STUB = 6,
+ SHOPPING_BASKET = 7,
+ FAST_FOOD = 8,
+ LOCAL_DINING = 9,
+ COGWHEEL = 10,
+ KEY = 11,
+ CAR = 12,
+ GROCERY = 13,
+};
+
} // namespace autofill_assistant
#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_GENERIC_UI_JAVA_GENERATED_ENUMS_H_
diff --git a/chromium/components/autofill_assistant/browser/metrics.h b/chromium/components/autofill_assistant/browser/metrics.h
index 8eabecca360..ff8a84c225a 100644
--- a/chromium/components/autofill_assistant/browser/metrics.h
+++ b/chromium/components/autofill_assistant/browser/metrics.h
@@ -45,8 +45,11 @@ class Metrics {
NO_INITIAL_SCRIPTS = 19,
DFM_INSTALL_FAILED = 20,
DOMAIN_CHANGE_DURING_BROWSE_MODE = 21,
+ BACK_BUTTON_CLICKED = 22,
+ ONBOARDING_BACK_BUTTON_CLICKED = 23,
+ NAVIGATION_WHILE_RUNNING = 24,
- kMaxValue = DOMAIN_CHANGE_DURING_BROWSE_MODE
+ kMaxValue = NAVIGATION_WHILE_RUNNING
};
// The different ways that autofill assistant can stop.
@@ -64,8 +67,9 @@ class Metrics {
OB_NOT_SHOWN = 1,
OB_ACCEPTED = 2,
OB_CANCELLED = 3,
+ OB_NO_ANSWER = 4,
- kMaxValue = OB_CANCELLED
+ kMaxValue = OB_NO_ANSWER
};
// The different ways for payment request to succeed or fail, broken down by
@@ -222,6 +226,15 @@ class Metrics {
case DropOutReason::DOMAIN_CHANGE_DURING_BROWSE_MODE:
out << "DOMAIN_CHANGE_DURING_BROWSE_MODE";
break;
+ case DropOutReason::BACK_BUTTON_CLICKED:
+ out << "BACK_BUTTON_CLICKED";
+ break;
+ case DropOutReason::ONBOARDING_BACK_BUTTON_CLICKED:
+ out << "ONBOARDING_BACK_BUTTON_CLICKED";
+ break;
+ case DropOutReason::NAVIGATION_WHILE_RUNNING:
+ out << "NAVIGATION_WHILE_RUNNING";
+ break;
// Do not add default case to force compilation error for new values.
}
return out;
@@ -249,6 +262,9 @@ class Metrics {
case OnBoarding::OB_CANCELLED:
out << "OB_CANCELLED";
break;
+ case OnBoarding::OB_NO_ANSWER:
+ out << "OB_NO_ANSWER";
+ break;
// Do not add default case to force compilation error for new values.
}
return out;
diff --git a/chromium/components/autofill_assistant/browser/mock_client.h b/chromium/components/autofill_assistant/browser/mock_client.h
index b96f0c72eb9..a83fe7ab273 100644
--- a/chromium/components/autofill_assistant/browser/mock_client.h
+++ b/chromium/components/autofill_assistant/browser/mock_client.h
@@ -25,6 +25,7 @@ class MockClient : public Client {
MOCK_CONST_METHOD0(GetLocale, std::string());
MOCK_CONST_METHOD0(GetCountryCode, std::string());
MOCK_CONST_METHOD0(GetDeviceContext, DeviceContext());
+ MOCK_CONST_METHOD0(IsAccessibilityEnabled, bool());
MOCK_CONST_METHOD0(GetEmailAddressForAccessTokenAccount, std::string());
MOCK_CONST_METHOD0(GetChromeSignedInEmailAddress, std::string());
MOCK_CONST_METHOD0(GetWebContents, content::WebContents*());
diff --git a/chromium/components/autofill_assistant/browser/mock_controller_observer.h b/chromium/components/autofill_assistant/browser/mock_controller_observer.h
index be6ae4d2d51..8cd6fc4f8d7 100644
--- a/chromium/components/autofill_assistant/browser/mock_controller_observer.h
+++ b/chromium/components/autofill_assistant/browser/mock_controller_observer.h
@@ -37,7 +37,12 @@ class MockControllerObserver : public ControllerObserver {
MOCK_METHOD1(OnDetailsChanged, void(const Details* details));
MOCK_METHOD1(OnInfoBoxChanged, void(const InfoBox* info_box));
MOCK_METHOD1(OnProgressChanged, void(int progress));
+ MOCK_METHOD1(OnProgressActiveStepChanged, void(int active_step));
MOCK_METHOD1(OnProgressVisibilityChanged, void(bool visible));
+ MOCK_METHOD1(OnStepProgressBarConfigurationChanged,
+ void(const ShowProgressBarProto::StepProgressBarConfiguration&
+ configuration));
+ MOCK_METHOD1(OnProgressBarErrorStateChanged, void(bool error));
MOCK_METHOD3(OnTouchableAreaChanged,
void(const RectF&,
const std::vector<RectF>& touchable_areas,
diff --git a/chromium/components/autofill_assistant/browser/model.proto b/chromium/components/autofill_assistant/browser/model.proto
index 607825be75d..7e3547ed105 100644
--- a/chromium/components/autofill_assistant/browser/model.proto
+++ b/chromium/components/autofill_assistant/browser/model.proto
@@ -33,13 +33,15 @@ message ValueProto {
ProfileList profiles = 8;
LoginOptionList login_options = 9;
CreditCardResponseProto credit_card_response = 10;
- LoginOptionResponseProto login_option_response = 11;
+ bytes server_payload = 12;
}
// If set to true, this value will not be sent to the backend. This flag is
// mostly used internally, to prevent certain values from leaving the device.
// Note that values derived from client_side_only values will inherit this
// property.
optional bool is_client_side_only = 6;
+
+ reserved 11;
}
message ValueReferenceProto {
@@ -192,6 +194,10 @@ enum ProcessedActionStatusProto {
// ProcessedActionStatusProto.AutofillErrorInfoProto contains more details.
AUTOFILL_INCOMPLETE = 24;
+ // Evaluating a selector required considering too many candidates. Please
+ // generate more specific selectors. See SelectorProto::ProximityFilter.
+ TOO_MANY_CANDIDATES = 25;
+
reserved 15, 23;
}
@@ -337,7 +343,3 @@ message LoginOptionProto {
optional string sublabel = 2;
optional bytes payload = 3;
}
-
-message LoginOptionResponseProto {
- optional bytes payload = 1;
-}
diff --git a/chromium/components/autofill_assistant/browser/radio_button_controller.cc b/chromium/components/autofill_assistant/browser/radio_button_controller.cc
new file mode 100644
index 00000000000..b905bb39e7a
--- /dev/null
+++ b/chromium/components/autofill_assistant/browser/radio_button_controller.cc
@@ -0,0 +1,44 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill_assistant/browser/radio_button_controller.h"
+
+namespace autofill_assistant {
+
+RadioButtonController::RadioButtonController(UserModel* user_model)
+ : user_model_(user_model) {}
+RadioButtonController::~RadioButtonController() = default;
+
+base::WeakPtr<RadioButtonController> RadioButtonController::GetWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+}
+
+void RadioButtonController::AddRadioButtonToGroup(
+ const std::string& radio_group,
+ const std::string& model_identifier) {
+ radio_groups_[radio_group].insert(model_identifier);
+}
+
+bool RadioButtonController::UpdateRadioButtonGroup(
+ const std::string& radio_group,
+ const std::string& selected_model_identifier) {
+ auto radio_group_it = radio_groups_.find(radio_group);
+ if (radio_group_it == radio_groups_.end()) {
+ return false;
+ }
+
+ if (radio_group_it->second.find(selected_model_identifier) ==
+ radio_group_it->second.end()) {
+ return false;
+ }
+
+ for (const auto& model_identifier : radio_group_it->second) {
+ user_model_->SetValue(
+ model_identifier,
+ SimpleValue(model_identifier == selected_model_identifier));
+ }
+ return true;
+}
+
+} // namespace autofill_assistant
diff --git a/chromium/components/autofill_assistant/browser/radio_button_controller.h b/chromium/components/autofill_assistant/browser/radio_button_controller.h
new file mode 100644
index 00000000000..b9287ea91dd
--- /dev/null
+++ b/chromium/components/autofill_assistant/browser/radio_button_controller.h
@@ -0,0 +1,49 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_RADIO_BUTTON_CONTROLLER_H_
+#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_RADIO_BUTTON_CONTROLLER_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "components/autofill_assistant/browser/user_model.h"
+
+namespace autofill_assistant {
+
+class RadioButtonController {
+ public:
+ RadioButtonController(UserModel* user_model);
+ ~RadioButtonController();
+ RadioButtonController(const RadioButtonController&) = delete;
+ RadioButtonController& operator=(const RadioButtonController&) = delete;
+
+ base::WeakPtr<RadioButtonController> GetWeakPtr();
+
+ // Adds |model_identifier| to the list of model identifiers belonging to
+ // |radio_group|.
+ void AddRadioButtonToGroup(const std::string& radio_group,
+ const std::string& model_identifier);
+
+ // Ensures that only |selected_model_identifier| is set to true in
+ // |radio_group|.
+ bool UpdateRadioButtonGroup(const std::string& radio_group,
+ const std::string& selected_model_identifier);
+
+ private:
+ // Maps radiogroup identifiers to the list of corresponding model identifiers.
+ std::map<std::string, std::set<std::string>> radio_groups_;
+
+ private:
+ friend class RadioButtonControllerTest;
+
+ UserModel* user_model_;
+ base::WeakPtrFactory<RadioButtonController> weak_ptr_factory_{this};
+};
+
+} // namespace autofill_assistant
+
+#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_RADIO_BUTTON_CONTROLLER_H_
diff --git a/chromium/components/autofill_assistant/browser/radio_button_controller_unittest.cc b/chromium/components/autofill_assistant/browser/radio_button_controller_unittest.cc
new file mode 100644
index 00000000000..5de656a82c4
--- /dev/null
+++ b/chromium/components/autofill_assistant/browser/radio_button_controller_unittest.cc
@@ -0,0 +1,77 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill_assistant/browser/radio_button_controller.h"
+#include "components/autofill_assistant/browser/user_model.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace autofill_assistant {
+
+using ::testing::Pair;
+using ::testing::SizeIs;
+using ::testing::UnorderedElementsAre;
+
+class RadioButtonControllerTest : public testing::Test {
+ protected:
+ RadioButtonControllerTest() : controller_(&user_model_) {}
+ ~RadioButtonControllerTest() override {}
+
+ std::map<std::string, std::set<std::string>> GetRadioGroups() {
+ return controller_.radio_groups_;
+ }
+
+ UserModel user_model_;
+ RadioButtonController controller_;
+};
+
+TEST_F(RadioButtonControllerTest, AddRadioButtonToGroup) {
+ EXPECT_THAT(GetRadioGroups(), SizeIs(0));
+ controller_.AddRadioButtonToGroup("group_1", "id_1");
+ EXPECT_THAT(GetRadioGroups(), UnorderedElementsAre(Pair(
+ "group_1", std::set<std::string>{"id_1"})));
+
+ controller_.AddRadioButtonToGroup("group_1", "id_1");
+ EXPECT_THAT(GetRadioGroups(), UnorderedElementsAre(Pair(
+ "group_1", std::set<std::string>{"id_1"})));
+
+ controller_.AddRadioButtonToGroup("group_1", "id_2");
+ EXPECT_THAT(GetRadioGroups(),
+ UnorderedElementsAre(
+ Pair("group_1", std::set<std::string>{"id_1", "id_2"})));
+
+ controller_.AddRadioButtonToGroup("group_2", "id_3");
+ EXPECT_THAT(GetRadioGroups(),
+ UnorderedElementsAre(
+ Pair("group_1", std::set<std::string>{"id_1", "id_2"}),
+ Pair("group_2", std::set<std::string>{"id_3"})));
+}
+
+TEST_F(RadioButtonControllerTest, UpdateRadioButtonGroup) {
+ controller_.AddRadioButtonToGroup("group_1", "id_1");
+ controller_.AddRadioButtonToGroup("group_1", "id_2");
+ controller_.AddRadioButtonToGroup("group_1", "id_3");
+ controller_.AddRadioButtonToGroup("group_2", "id_4");
+ controller_.AddRadioButtonToGroup("group_2", "id_5");
+
+ EXPECT_FALSE(controller_.UpdateRadioButtonGroup("does_not_exist", "id_1"));
+
+ EXPECT_TRUE(controller_.UpdateRadioButtonGroup("group_1", "id_1"));
+ EXPECT_EQ(user_model_.GetValue("id_1"), SimpleValue(true));
+ EXPECT_EQ(user_model_.GetValue("id_2"), SimpleValue(false));
+ EXPECT_EQ(user_model_.GetValue("id_3"), SimpleValue(false));
+
+ EXPECT_FALSE(controller_.UpdateRadioButtonGroup("group_1", "does_not_exist"));
+ EXPECT_EQ(user_model_.GetValue("id_1"), SimpleValue(true));
+ EXPECT_EQ(user_model_.GetValue("id_2"), SimpleValue(false));
+ EXPECT_EQ(user_model_.GetValue("id_3"), SimpleValue(false));
+
+ EXPECT_TRUE(controller_.UpdateRadioButtonGroup("group_2", "id_5"));
+ EXPECT_EQ(user_model_.GetValue("id_1"), SimpleValue(true));
+ EXPECT_EQ(user_model_.GetValue("id_2"), SimpleValue(false));
+ EXPECT_EQ(user_model_.GetValue("id_3"), SimpleValue(false));
+ EXPECT_EQ(user_model_.GetValue("id_4"), SimpleValue(false));
+ EXPECT_EQ(user_model_.GetValue("id_5"), SimpleValue(true));
+}
+
+} // namespace autofill_assistant
diff --git a/chromium/components/autofill_assistant/browser/script_executor.cc b/chromium/components/autofill_assistant/browser/script_executor.cc
index a0c76ad340e..5125c48d81b 100644
--- a/chromium/components/autofill_assistant/browser/script_executor.cc
+++ b/chromium/components/autofill_assistant/browser/script_executor.cc
@@ -104,7 +104,12 @@ void ScriptExecutor::Run(const UserData* user_data,
callback_ = std::move(callback);
DCHECK(delegate_->GetService());
+#ifdef NDEBUG
+ VLOG(2) << "GetActions for (redacted)";
+#else
VLOG(2) << "GetActions for " << delegate_->GetCurrentURL().host();
+#endif
+
delegate_->GetService()->GetActions(
script_path_, delegate_->GetScriptURL(),
MergedTriggerContext(
@@ -119,6 +124,10 @@ const UserData* ScriptExecutor::GetUserData() const {
return user_data_;
}
+UserModel* ScriptExecutor::GetUserModel() {
+ return delegate_->GetUserModel();
+}
+
void ScriptExecutor::OnNavigationStateChanged() {
NavigationInfoProto& navigation_info = current_action_data_.navigation_info;
if (delegate_->IsNavigatingToNewDocument()) {
@@ -150,7 +159,9 @@ void ScriptExecutor::OnNavigationStateChanged() {
std::move(on_expected_navigation_done_)
.Run(!delegate_->HasNavigationError());
}
- break;
+ // Early return since current_action_data_ is no longer valid at this
+ // point.
+ return;
case ExpectedNavigationStep::DONE:
// nothing to do
@@ -242,11 +253,6 @@ void ScriptExecutor::WriteUserData(
delegate_->WriteUserData(std::move(write_callback));
}
-void ScriptExecutor::WriteUserModel(
- base::OnceCallback<void(UserModel*)> write_callback) {
- delegate_->WriteUserModel(std::move(write_callback));
-}
-
void ScriptExecutor::OnGetUserData(
base::OnceCallback<void(UserData*, const UserModel*)> callback,
UserData* user_data,
@@ -274,8 +280,11 @@ void ScriptExecutor::OnTermsAndConditionsLinkClicked(
std::move(callback).Run(link, user_data, user_model);
}
-void ScriptExecutor::GetFullCard(GetFullCardCallback callback) {
- DCHECK(GetUserData()->selected_card_.get());
+void ScriptExecutor::GetFullCard(
+ const autofill::CreditCard* credit_card,
+ base::OnceCallback<void(std::unique_ptr<autofill::CreditCard> card,
+ const base::string16& cvc)> callback) {
+ DCHECK(credit_card);
// User might be asked to provide the cvc.
delegate_->EnterState(AutofillAssistantState::MODAL_DIALOG);
@@ -284,7 +293,7 @@ void ScriptExecutor::GetFullCard(GetFullCardCallback callback) {
// so as to unit test it.
(new SelfDeleteFullCardRequester())
->GetFullCard(
- GetWebContents(), GetUserData()->selected_card_.get(),
+ GetWebContents(), credit_card,
base::BindOnce(&ScriptExecutor::OnGetFullCard,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
@@ -422,10 +431,19 @@ void ScriptExecutor::SetProgress(int progress) {
delegate_->SetProgress(progress);
}
+void ScriptExecutor::SetProgressActiveStep(int active_step) {
+ delegate_->SetProgressActiveStep(active_step);
+}
+
void ScriptExecutor::SetProgressVisible(bool visible) {
delegate_->SetProgressVisible(visible);
}
+void ScriptExecutor::SetStepProgressBarConfiguration(
+ const ShowProgressBarProto::StepProgressBarConfiguration& configuration) {
+ delegate_->SetStepProgressBarConfiguration(configuration);
+}
+
void ScriptExecutor::GetFieldValue(
const Selector& selector,
base::OnceCallback<void(const ClientStatus&, const std::string&)>
@@ -478,6 +496,9 @@ void ScriptExecutor::GetElementTag(
}
void ScriptExecutor::ExpectNavigation() {
+ // TODO(b/160948417): Clean this up such that the logic is not required in
+ // both |ScriptExecutor| and |Controller|.
+ delegate_->ExpectNavigation();
expected_navigation_step_ = ExpectedNavigationStep::EXPECTED;
}
@@ -622,10 +643,11 @@ void ScriptExecutor::RequireUI() {
void ScriptExecutor::SetGenericUi(
std::unique_ptr<GenericUserInterfaceProto> generic_ui,
- base::OnceCallback<void(bool, ProcessedActionStatusProto, const UserModel*)>
- end_action_callback) {
- delegate_->SetGenericUi(std::move(generic_ui),
- std::move(end_action_callback));
+ base::OnceCallback<void(const ClientStatus&)> end_action_callback,
+ base::OnceCallback<void(const ClientStatus&)>
+ view_inflation_finished_callback) {
+ delegate_->SetGenericUi(std::move(generic_ui), std::move(end_action_callback),
+ std::move(view_inflation_finished_callback));
}
void ScriptExecutor::ClearGenericUi() {
@@ -771,6 +793,7 @@ void ScriptExecutor::OnProcessedAction(
processed_action.set_direct_action(current_action_data_.direct_action);
*processed_action.mutable_navigation_info() =
current_action_data_.navigation_info;
+
if (processed_action.status() != ProcessedActionStatusProto::ACTION_APPLIED) {
if (delegate_->HasNavigationError()) {
// Overwrite the original error, as the root cause is most likely a
@@ -848,9 +871,6 @@ ScriptExecutor::WaitForDomOperation::~WaitForDomOperation() {
void ScriptExecutor::WaitForDomOperation::Run() {
delegate_->AddListener(this);
- if (delegate_->IsNavigatingToNewDocument())
- return; // start paused
-
Start();
}
diff --git a/chromium/components/autofill_assistant/browser/script_executor.h b/chromium/components/autofill_assistant/browser/script_executor.h
index ea4c6f4b6a7..42fe159a5fc 100644
--- a/chromium/components/autofill_assistant/browser/script_executor.h
+++ b/chromium/components/autofill_assistant/browser/script_executor.h
@@ -95,6 +95,7 @@ class ScriptExecutor : public ActionDelegate,
void Run(const UserData* user_data, RunScriptCallback callback);
const UserData* GetUserData() const override;
+ UserModel* GetUserModel() override;
// Override ScriptExecutorDelegate::Listener
void OnNavigationStateChanged() override;
@@ -123,9 +124,8 @@ class ScriptExecutor : public ActionDelegate,
CollectUserDataOptions* collect_user_data_options) override;
void WriteUserData(
base::OnceCallback<void(UserData*, UserData::FieldChange*)>) override;
- void WriteUserModel(
- base::OnceCallback<void(UserModel*)> write_callback) override;
- void GetFullCard(GetFullCardCallback callback) override;
+ void GetFullCard(const autofill::CreditCard* credit_card,
+ GetFullCardCallback callback) override;
void Prompt(std::unique_ptr<std::vector<UserAction>> user_actions,
bool disable_force_expand_sheet,
base::OnceCallback<void()> end_on_navigation_callback,
@@ -214,7 +214,11 @@ class ScriptExecutor : public ActionDelegate,
void ClearInfoBox() override;
void SetInfoBox(const InfoBox& info_box) override;
void SetProgress(int progress) override;
+ void SetProgressActiveStep(int active_step) override;
void SetProgressVisible(bool visible) override;
+ void SetStepProgressBarConfiguration(
+ const ShowProgressBarProto::StepProgressBarConfiguration& configuration)
+ override;
void SetViewportMode(ViewportMode mode) override;
ViewportMode GetViewportMode() override;
void SetPeekMode(ConfigureBottomSheetProto::PeekMode peek_mode) override;
@@ -231,9 +235,9 @@ class ScriptExecutor : public ActionDelegate,
void RequireUI() override;
void SetGenericUi(
std::unique_ptr<GenericUserInterfaceProto> generic_ui,
- base::OnceCallback<void(bool,
- ProcessedActionStatusProto,
- const UserModel*)> end_action_callback) override;
+ base::OnceCallback<void(const ClientStatus&)> end_action_callback,
+ base::OnceCallback<void(const ClientStatus&)>
+ view_inflation_finished_callback) override;
void ClearGenericUi() override;
private:
diff --git a/chromium/components/autofill_assistant/browser/script_executor_delegate.h b/chromium/components/autofill_assistant/browser/script_executor_delegate.h
index 0cf42b6253b..7b646ed3b3b 100644
--- a/chromium/components/autofill_assistant/browser/script_executor_delegate.h
+++ b/chromium/components/autofill_assistant/browser/script_executor_delegate.h
@@ -78,10 +78,12 @@ class ScriptExecutorDelegate {
virtual void WriteUserData(
base::OnceCallback<void(UserData*, UserData::FieldChange*)>
write_callback) = 0;
- virtual void WriteUserModel(
- base::OnceCallback<void(UserModel*)> write_callback) = 0;
virtual void SetProgress(int progress) = 0;
+ virtual void SetProgressActiveStep(int active_step) = 0;
virtual void SetProgressVisible(bool visible) = 0;
+ virtual void SetStepProgressBarConfiguration(
+ const ShowProgressBarProto::StepProgressBarConfiguration&
+ configuration) = 0;
virtual void SetUserActions(
std::unique_ptr<std::vector<UserAction>> user_action) = 0;
virtual ViewportMode GetViewportMode() = 0;
@@ -102,6 +104,9 @@ class ScriptExecutorDelegate {
SetTouchableElementArea(ElementAreaProto::default_instance());
}
+ // The next navigation is expected and will not cause an error.
+ virtual void ExpectNavigation() = 0;
+
// Returns true if a new document is being fetched for the main frame.
//
// Navigation ends once a response, with its associated URL has been
@@ -147,9 +152,9 @@ class ScriptExecutorDelegate {
// Sets the generic UI to show to the user.
virtual void SetGenericUi(
std::unique_ptr<GenericUserInterfaceProto> generic_ui,
- base::OnceCallback<void(bool,
- ProcessedActionStatusProto,
- const UserModel*)> end_action_callback) = 0;
+ base::OnceCallback<void(const ClientStatus&)> end_action_callback,
+ base::OnceCallback<void(const ClientStatus&)>
+ view_inflation_finished_callback) = 0;
// Clears the generic UI.
virtual void ClearGenericUi() = 0;
diff --git a/chromium/components/autofill_assistant/browser/script_executor_unittest.cc b/chromium/components/autofill_assistant/browser/script_executor_unittest.cc
index 6a7eca439e7..571d73aadc9 100644
--- a/chromium/components/autofill_assistant/browser/script_executor_unittest.cc
+++ b/chromium/components/autofill_assistant/browser/script_executor_unittest.cc
@@ -105,8 +105,8 @@ class ScriptExecutorTest : public testing::Test,
interruptible.set_global_payload("main script global payload");
interruptible.set_script_payload("main script payload");
auto* wait_action = interruptible.add_actions()->mutable_wait_for_dom();
- wait_action->mutable_wait_condition()->mutable_match()->add_selectors(
- element);
+ *wait_action->mutable_wait_condition()->mutable_match() =
+ ToSelectorProto(element);
wait_action->set_allow_interrupt(true);
interruptible.add_actions()->mutable_tell()->set_message(path);
EXPECT_CALL(mock_service_, OnGetActions(StrEq(path), _, _, _, _, _))
@@ -136,9 +136,8 @@ class ScriptExecutorTest : public testing::Test,
auto interrupt = std::make_unique<Script>();
interrupt->handle.path = path;
ScriptPreconditionProto interrupt_preconditions;
- interrupt_preconditions.mutable_element_condition()
- ->mutable_match()
- ->add_selectors(trigger);
+ *interrupt_preconditions.mutable_element_condition()->mutable_match() =
+ ToSelectorProto(trigger);
interrupt->precondition =
ScriptPrecondition::FromProto(path, interrupt_preconditions);
@@ -207,10 +206,8 @@ TEST_F(ScriptExecutorTest, ForwardParameters) {
TEST_F(ScriptExecutorTest, RunOneActionReportAndReturn) {
ActionsResponseProto actions_response;
- actions_response.add_actions()
- ->mutable_click()
- ->mutable_element_to_click()
- ->add_selectors("will fail");
+ *actions_response.add_actions()->mutable_click()->mutable_element_to_click() =
+ ToSelectorProto("will fail");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(true, Serialize(actions_response)));
@@ -295,10 +292,9 @@ TEST_F(ScriptExecutorTest, InterruptActionListOnError) {
ActionsResponseProto initial_actions_response;
initial_actions_response.add_actions()->mutable_tell()->set_message(
"will pass");
- initial_actions_response.add_actions()
- ->mutable_click()
- ->mutable_element_to_click()
- ->add_selectors("will fail");
+ *initial_actions_response.add_actions()
+ ->mutable_click()
+ ->mutable_element_to_click() = ToSelectorProto("will fail");
initial_actions_response.add_actions()->mutable_tell()->set_message(
"never run");
@@ -496,8 +492,8 @@ TEST_F(ScriptExecutorTest, ForwardLastPayloadOnError) {
TEST_F(ScriptExecutorTest, WaitForDomWaitUntil) {
ActionsResponseProto actions_response;
auto* wait_for_dom = actions_response.add_actions()->mutable_wait_for_dom();
- wait_for_dom->mutable_wait_condition()->mutable_match()->add_selectors(
- "element");
+ *wait_for_dom->mutable_wait_condition()->mutable_match() =
+ ToSelectorProto("element");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(true, Serialize(actions_response)));
@@ -599,8 +595,8 @@ TEST_F(ScriptExecutorTest, RunSameInterruptMultipleTimes) {
ActionsResponseProto interruptible;
for (int i = 0; i < 3; i++) {
auto* wait_action = interruptible.add_actions()->mutable_wait_for_dom();
- wait_action->mutable_wait_condition()->mutable_match()->add_selectors(
- "element");
+ *wait_action->mutable_wait_condition()->mutable_match() =
+ ToSelectorProto("element");
wait_action->set_allow_interrupt(true);
}
EXPECT_CALL(mock_service_, OnGetActions(StrEq("script_path"), _, _, _, _, _))
@@ -703,8 +699,8 @@ TEST_F(ScriptExecutorTest, DoNotRunInterruptIfNotInterruptible) {
// The main script has a wait_for_dom, but it is not interruptible.
ActionsResponseProto interruptible;
auto* wait_action = interruptible.add_actions()->mutable_wait_for_dom();
- wait_action->mutable_wait_condition()->mutable_match()->add_selectors(
- "element");
+ *wait_action->mutable_wait_condition()->mutable_match() =
+ ToSelectorProto("element");
// allow_interrupt is not set
EXPECT_CALL(mock_service_, OnGetActions(StrEq(kScriptPath), _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(true, Serialize(interruptible)));
@@ -796,10 +792,8 @@ TEST_F(ScriptExecutorTest, RunInterruptDuringPrompt) {
ActionsResponseProto interruptible;
auto* prompt_action = interruptible.add_actions()->mutable_prompt();
prompt_action->set_allow_interrupt(true);
- prompt_action->add_choices()
- ->mutable_auto_select_when()
- ->mutable_match()
- ->add_selectors("end_prompt");
+ *prompt_action->add_choices()->mutable_auto_select_when()->mutable_match() =
+ ToSelectorProto("end_prompt");
interruptible.add_actions()->mutable_tell()->set_message("done");
EXPECT_CALL(mock_service_, OnGetActions(kScriptPath, _, _, _, _, _))
.WillRepeatedly(RunOnceCallback<5>(true, Serialize(interruptible)));
@@ -876,10 +870,8 @@ TEST_F(ScriptExecutorTest, RunInterruptMultipleTimesDuringPrompt) {
ActionsResponseProto interruptible;
auto* prompt_action = interruptible.add_actions()->mutable_prompt();
prompt_action->set_allow_interrupt(true);
- prompt_action->add_choices()
- ->mutable_auto_select_when()
- ->mutable_match()
- ->add_selectors("end_prompt");
+ *prompt_action->add_choices()->mutable_auto_select_when()->mutable_match() =
+ ToSelectorProto("end_prompt");
EXPECT_CALL(mock_service_, OnGetActions(kScriptPath, _, _, _, _, _))
.WillRepeatedly(RunOnceCallback<5>(true, Serialize(interruptible)));
@@ -1041,8 +1033,8 @@ TEST_F(ScriptExecutorTest, RestorePreInterruptStatusMessage) {
interruptible.add_actions()->mutable_tell()->set_message(
"pre-interrupt status");
auto* wait_action = interruptible.add_actions()->mutable_wait_for_dom();
- wait_action->mutable_wait_condition()->mutable_match()->add_selectors(
- "element");
+ *wait_action->mutable_wait_condition()->mutable_match() =
+ ToSelectorProto("element");
wait_action->set_allow_interrupt(true);
EXPECT_CALL(mock_service_, OnGetActions(kScriptPath, _, _, _, _, _))
.WillRepeatedly(RunOnceCallback<5>(true, Serialize(interruptible)));
@@ -1070,8 +1062,8 @@ TEST_F(ScriptExecutorTest, KeepStatusMessageWhenNotInterrupted) {
interruptible.add_actions()->mutable_tell()->set_message(
"pre-interrupt status");
auto* wait_action = interruptible.add_actions()->mutable_wait_for_dom();
- wait_action->mutable_wait_condition()->mutable_match()->add_selectors(
- "element");
+ *wait_action->mutable_wait_condition()->mutable_match() =
+ ToSelectorProto("element");
wait_action->set_allow_interrupt(true);
EXPECT_CALL(mock_service_, OnGetActions(kScriptPath, _, _, _, _, _))
.WillRepeatedly(RunOnceCallback<5>(true, Serialize(interruptible)));
@@ -1091,8 +1083,8 @@ TEST_F(ScriptExecutorTest, PauseWaitForDomWhileNavigating) {
ActionsResponseProto actions_response;
auto* wait_for_dom = actions_response.add_actions()->mutable_wait_for_dom();
wait_for_dom->set_timeout_ms(2000);
- wait_for_dom->mutable_wait_condition()->mutable_match()->add_selectors(
- "element");
+ *wait_for_dom->mutable_wait_condition()->mutable_match() =
+ ToSelectorProto("element");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(true, Serialize(actions_response)));
@@ -1130,8 +1122,8 @@ TEST_F(ScriptExecutorTest, StartWaitForDomWhileNavigating) {
ActionsResponseProto actions_response;
auto* wait_for_dom = actions_response.add_actions()->mutable_wait_for_dom();
wait_for_dom->set_timeout_ms(2000);
- wait_for_dom->mutable_wait_condition()->mutable_match()->add_selectors(
- "element");
+ *wait_for_dom->mutable_wait_condition()->mutable_match() =
+ ToSelectorProto("element");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(true, Serialize(actions_response)));
@@ -1140,27 +1132,25 @@ TEST_F(ScriptExecutorTest, StartWaitForDomWhileNavigating) {
.WillOnce(DoAll(SaveArg<3>(&processed_actions_capture),
RunOnceCallback<4>(true, "")));
- // Navigation starts before WaitForDom even starts, so the operation starts in
- // a paused state.
+ // Navigation starts before WaitForDom starts. WaitForDom does not wait and
+ // completes successfully.
delegate_.UpdateNavigationState(/* navigating= */ true, /* error= */ false);
-
+ EXPECT_CALL(executor_callback_, Run(_));
executor_->Run(&user_data_, executor_callback_.Get());
- // The end of navigation un-pauses WaitForDom, which then succeeds
- // immediately.
- EXPECT_CALL(executor_callback_, Run(_));
+ // Navigation finishes after the WaitForDom has finished.
delegate_.UpdateNavigationState(/* navigating= */ false, /* error= */ false);
ASSERT_EQ(1u, processed_actions_capture.size());
EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[0].status());
+ EXPECT_FALSE(processed_actions_capture[0].navigation_info().started());
+ EXPECT_FALSE(processed_actions_capture[0].navigation_info().ended());
}
TEST_F(ScriptExecutorTest, ReportErrorAsNavigationError) {
ActionsResponseProto actions_response;
- actions_response.add_actions()
- ->mutable_click()
- ->mutable_element_to_click()
- ->add_selectors("will fail");
+ *actions_response.add_actions()->mutable_click()->mutable_element_to_click() =
+ ToSelectorProto("will fail");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(true, Serialize(actions_response)));
@@ -1241,8 +1231,8 @@ TEST_F(ScriptExecutorTest, ReportNavigationErrors) {
TEST_F(ScriptExecutorTest, ReportNavigationEnd) {
ActionsResponseProto actions_response;
auto* wait_for_dom = actions_response.add_actions()->mutable_wait_for_dom();
- wait_for_dom->mutable_wait_condition()->mutable_match()->add_selectors(
- "element");
+ *wait_for_dom->mutable_wait_condition()->mutable_match() =
+ ToSelectorProto("element");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(true, Serialize(actions_response)));
@@ -1251,20 +1241,19 @@ TEST_F(ScriptExecutorTest, ReportNavigationEnd) {
.WillOnce(DoAll(SaveArg<3>(&processed_actions_capture),
RunOnceCallback<4>(true, "")));
- // Navigation starts, before the script is run.
- delegate_.UpdateNavigationState(/* navigating= */ true, /* error= */ false);
- EXPECT_CALL(executor_callback_, Run(_));
- executor_->Run(&user_data_, executor_callback_.Get());
-
- // WaitForDom waits for navigation to end, then checks for the element, which
- // fails.
+ // WaitForDom does NOT wait for navigation to end, it immediately checks for
+ // the element, which fails.
EXPECT_CALL(mock_web_controller_,
OnElementCheck(Eq(Selector({"element"})), _))
.WillOnce(RunOnceCallback<1>(ClientStatus()));
+
+ // Navigation starts before the script is run.
+ delegate_.UpdateNavigationState(/* navigating= */ true, /* error= */ false);
+ EXPECT_CALL(executor_callback_, Run(_));
+ executor_->Run(&user_data_, executor_callback_.Get());
delegate_.UpdateNavigationState(/* navigating= */ false, /* error= */ false);
- // Checking for the element succeeds on the second try. Waiting avoids
- // depending on the order at which the listeners are called.
+ // Checking for the element succeeds on the second try.
EXPECT_CALL(mock_web_controller_,
OnElementCheck(Eq(Selector({"element"})), _))
.WillOnce(RunOnceCallback<1>(OkClientStatus()));
@@ -1279,8 +1268,8 @@ TEST_F(ScriptExecutorTest, ReportNavigationEnd) {
TEST_F(ScriptExecutorTest, ReportUnexpectedNavigationStart) {
ActionsResponseProto actions_response;
auto* wait_for_dom = actions_response.add_actions()->mutable_wait_for_dom();
- wait_for_dom->mutable_wait_condition()->mutable_match()->add_selectors(
- "element");
+ *wait_for_dom->mutable_wait_condition()->mutable_match() =
+ ToSelectorProto("element");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(true, Serialize(actions_response)));
@@ -1314,8 +1303,8 @@ TEST_F(ScriptExecutorTest, ReportExpectedNavigationStart) {
ActionsResponseProto actions_response;
actions_response.add_actions()->mutable_expect_navigation();
auto* wait_for_dom = actions_response.add_actions()->mutable_wait_for_dom();
- wait_for_dom->mutable_wait_condition()->mutable_match()->add_selectors(
- "element");
+ *wait_for_dom->mutable_wait_condition()->mutable_match() =
+ ToSelectorProto("element");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(true, Serialize(actions_response)));
diff --git a/chromium/components/autofill_assistant/browser/script_precondition.cc b/chromium/components/autofill_assistant/browser/script_precondition.cc
index f12db0b8da6..8dcab16380b 100644
--- a/chromium/components/autofill_assistant/browser/script_precondition.cc
+++ b/chromium/components/autofill_assistant/browser/script_precondition.cc
@@ -7,6 +7,7 @@
#include <utility>
#include "base/bind.h"
+#include "base/logging.h"
#include "base/strings/strcat.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/autofill_assistant/browser/batch_element_checker.h"
diff --git a/chromium/components/autofill_assistant/browser/script_precondition_unittest.cc b/chromium/components/autofill_assistant/browser/script_precondition_unittest.cc
index 00f6e8c890b..2bfd4611c38 100644
--- a/chromium/components/autofill_assistant/browser/script_precondition_unittest.cc
+++ b/chromium/components/autofill_assistant/browser/script_precondition_unittest.cc
@@ -295,7 +295,8 @@ TEST_F(ScriptPreconditionTest, MultipleConditions) {
ScriptPreconditionProto proto;
proto.add_domain("http://match.example.com");
proto.add_path_pattern("/path");
- proto.mutable_element_condition()->mutable_match()->add_selectors("exists");
+ *proto.mutable_element_condition()->mutable_match() =
+ ToSelectorProto("exists");
// Domain and path don't match.
EXPECT_FALSE(Check(proto));
@@ -303,8 +304,8 @@ TEST_F(ScriptPreconditionTest, MultipleConditions) {
SetUrl("http://match.example.com/path");
EXPECT_TRUE(Check(proto)) << "Domain, path and selector must match.";
- proto.mutable_element_condition()->mutable_match()->set_selectors(
- 0, "does_not_exist");
+ *proto.mutable_element_condition()->mutable_match() =
+ ToSelectorProto("does_not_exist");
EXPECT_FALSE(Check(proto)) << "Element can not match.";
}
diff --git a/chromium/components/autofill_assistant/browser/script_tracker_unittest.cc b/chromium/components/autofill_assistant/browser/script_tracker_unittest.cc
index 8b745a8eff8..90d8c7afd61 100644
--- a/chromium/components/autofill_assistant/browser/script_tracker_unittest.cc
+++ b/chromium/components/autofill_assistant/browser/script_tracker_unittest.cc
@@ -89,11 +89,10 @@ class ScriptTrackerTest : public testing::Test, public ScriptTracker::Listener {
script->set_path(path);
script->mutable_presentation()->mutable_chip()->set_text(name);
if (!selector.empty()) {
- script->mutable_presentation()
- ->mutable_precondition()
- ->mutable_element_condition()
- ->mutable_match()
- ->add_selectors(selector);
+ *script->mutable_presentation()
+ ->mutable_precondition()
+ ->mutable_element_condition()
+ ->mutable_match() = ToSelectorProto(selector);
}
ScriptStatusMatchProto dont_run_twice_precondition;
dont_run_twice_precondition.set_script(path);
@@ -375,8 +374,8 @@ TEST_F(ScriptTrackerTest, UpdateInterruptList) {
ActionsResponseProto actions_response;
auto* wait_for_dom = actions_response.add_actions()->mutable_wait_for_dom();
- wait_for_dom->mutable_wait_condition()->mutable_match()->add_selectors(
- "exists");
+ *wait_for_dom->mutable_wait_condition()->mutable_match() =
+ ToSelectorProto("exists");
wait_for_dom->set_allow_interrupt(true);
SupportedScriptProto* interrupt_proto =
diff --git a/chromium/components/autofill_assistant/browser/selector.cc b/chromium/components/autofill_assistant/browser/selector.cc
index 99bfce41813..e460622cc42 100644
--- a/chromium/components/autofill_assistant/browser/selector.cc
+++ b/chromium/components/autofill_assistant/browser/selector.cc
@@ -4,150 +4,361 @@
#include "components/autofill_assistant/browser/selector.h"
+#include "base/logging.h"
+#include "base/notreached.h"
#include "base/strings/string_util.h"
namespace autofill_assistant {
-Selector::Selector() {}
+// Comparison operations are in the autofill_assistant scope, even though
+// they're not shared outside of this module, for them to be visible to
+// std::make_tuple and std::lexicographical_compare.
-Selector::Selector(const ElementReferenceProto& proto) {
- for (const auto& selector : proto.selectors()) {
- selectors.emplace_back(selector);
- }
- must_be_visible = proto.visibility_requirement() == MUST_BE_VISIBLE;
- inner_text_pattern = proto.inner_text_pattern();
- value_pattern = proto.value_pattern();
- pseudo_type = proto.pseudo_type();
+bool operator<(const SelectorProto::TextFilter& a,
+ const SelectorProto::TextFilter& b) {
+ return std::make_tuple(a.re2(), a.case_sensitive()) <
+ std::make_tuple(b.re2(), b.case_sensitive());
}
-ElementReferenceProto Selector::ToElementReferenceProto() const {
- ElementReferenceProto proto;
- for (const auto& selector : selectors) {
- proto.add_selectors(selector);
+// Used by operator<(RepeatedPtrField<Filter>, RepeatedPtrField<Filter>)
+bool operator<(const SelectorProto::Filter& a, const SelectorProto::Filter& b);
+
+bool operator<(
+ const google::protobuf::RepeatedPtrField<SelectorProto::Filter>& a,
+ const google::protobuf::RepeatedPtrField<SelectorProto::Filter>& b) {
+ return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end());
+}
+
+bool operator<(const SelectorProto::Filter& a, const SelectorProto::Filter& b) {
+ if (a.filter_case() < b.filter_case()) {
+ return true;
}
- if (must_be_visible) {
- proto.set_visibility_requirement(MUST_BE_VISIBLE);
+ if (a.filter_case() != b.filter_case()) {
+ return false;
}
- proto.set_inner_text_pattern(inner_text_pattern);
- proto.set_value_pattern(value_pattern);
- proto.set_pseudo_type(pseudo_type);
- return proto;
-}
+ switch (a.filter_case()) {
+ case SelectorProto::Filter::kCssSelector:
+ return a.css_selector() < b.css_selector();
-Selector::Selector(std::vector<std::string> s)
- : selectors(s), pseudo_type(PseudoType::UNDEFINED) {}
-Selector::Selector(std::vector<std::string> s, PseudoType p)
- : selectors(s), pseudo_type(p) {}
-Selector::~Selector() = default;
+ case SelectorProto::Filter::kInnerText:
+ return a.inner_text() < b.inner_text();
-Selector::Selector(Selector&& other) = default;
-Selector::Selector(const Selector& other) = default;
-Selector& Selector::operator=(const Selector& other) = default;
-Selector& Selector::operator=(Selector&& other) = default;
+ case SelectorProto::Filter::kValue:
+ return a.value() < b.value();
-bool Selector::operator<(const Selector& other) const {
- return std::tie(selectors, inner_text_pattern, value_pattern, must_be_visible,
- pseudo_type) <
- std::tie(other.selectors, other.inner_text_pattern,
- other.value_pattern, other.must_be_visible,
- other.pseudo_type);
+ case SelectorProto::Filter::kPseudoType:
+ return a.pseudo_type() < b.pseudo_type();
+
+ case SelectorProto::Filter::kPseudoElementContent:
+ return std::make_tuple(a.pseudo_element_content().pseudo_type(),
+ a.pseudo_element_content().content()) <
+ std::make_tuple(b.pseudo_element_content().pseudo_type(),
+ b.pseudo_element_content().content());
+
+ case SelectorProto::Filter::kBoundingBox:
+ case SelectorProto::Filter::kEnterFrame:
+ case SelectorProto::Filter::kPickOne:
+ case SelectorProto::Filter::kLabelled:
+ return false;
+
+ case SelectorProto::Filter::kClosest: {
+ return std::make_tuple(a.closest().target(), a.closest().in_alignment(),
+ a.closest().relative_position()) <
+ std::make_tuple(b.closest().target(), b.closest().in_alignment(),
+ b.closest().relative_position());
+ }
+
+ case SelectorProto::Filter::FILTER_NOT_SET:
+ return false;
+ }
+ return false;
}
-bool Selector::operator==(const Selector& other) const {
- return selectors == other.selectors &&
- inner_text_pattern == other.inner_text_pattern &&
- value_pattern == other.value_pattern &&
- must_be_visible == other.must_be_visible &&
- pseudo_type == other.pseudo_type;
+SelectorProto ToSelectorProto(const std::string& s) {
+ return ToSelectorProto(std::vector<std::string>{s});
}
-bool Selector::empty() const {
- return this->selectors.empty();
+SelectorProto ToSelectorProto(const std::vector<std::string>& s) {
+ SelectorProto proto;
+ if (!s.empty()) {
+ for (size_t i = 0; i < s.size(); i++) {
+ if (i > 0) {
+ proto.add_filters()->mutable_pick_one();
+ proto.add_filters()->mutable_enter_frame();
+ }
+ proto.add_filters()->set_css_selector(s[i]);
+ }
+ }
+ return proto;
}
-std::ostream& operator<<(std::ostream& out, PseudoType pseudo_type) {
-#ifdef NDEBUG
- return out << static_cast<int>(pseudo_type);
-#else
+std::string PseudoTypeName(PseudoType pseudo_type) {
switch (pseudo_type) {
case UNDEFINED:
- out << "UNDEFINED";
- break;
+ return "undefined";
case FIRST_LINE:
- out << "FIRST_LINE";
- break;
+ return "first-line";
case FIRST_LETTER:
- out << "FIRST_LETTER";
- break;
+ return "first-letter";
case BEFORE:
- out << "BEFORE";
- break;
+ return "before";
case AFTER:
- out << "AFTER";
- break;
+ return "after";
case BACKDROP:
- out << "BACKDROP";
- break;
+ return "backdrop";
case SELECTION:
- out << "SELECTION";
- break;
+ return "selection";
case FIRST_LINE_INHERITED:
- out << "FIRST_LINE_INHERITED";
- break;
+ return "first-line-inherited";
case SCROLLBAR:
- out << "SCROLLBAR";
- break;
+ return "scrollbar";
case SCROLLBAR_THUMB:
- out << "SCROLLBAR_THUMB";
- break;
+ return "scrollbar-thumb";
case SCROLLBAR_BUTTON:
- out << "SCROLLBAR_BUTTON";
- break;
+ return "scrollbar-button";
case SCROLLBAR_TRACK:
- out << "SCROLLBAR_TRACK";
- break;
+ return "scrollbar-track";
case SCROLLBAR_TRACK_PIECE:
- out << "SCROLLBAR_TRACK_PIECE";
- break;
+ return "scrollbar-track-piece";
case SCROLLBAR_CORNER:
- out << "SCROLLBAR_CORNER";
- break;
+ return "scrollbar-corner";
case RESIZER:
- out << "RESIZER";
- break;
+ return "resizer";
case INPUT_LIST_BUTTON:
- out << "INPUT_LIST_BUTTON";
- break;
+ return "input-list-button";
// Intentionally no default case to make compilation fail if a new value
// was added to the enum but not to this list.
}
- return out;
-#endif
}
-std::ostream& operator<<(std::ostream& out, const Selector& selector) {
-#ifdef NDEBUG
- out << selector.selectors.size() << " element(s)";
- return out;
-#else
- out << "elements=[" << base::JoinString(selector.selectors, ",") << "]";
- if (!selector.inner_text_pattern.empty()) {
- out << " innerText =~ /";
- out << selector.inner_text_pattern;
- out << "/";
+
+Selector::Selector() {}
+
+Selector::Selector(const SelectorProto& selector_proto)
+ : proto(selector_proto) {}
+
+Selector::~Selector() = default;
+
+Selector::Selector(Selector&& other) = default;
+Selector::Selector(const Selector& other) = default;
+Selector& Selector::operator=(const Selector& other) = default;
+Selector& Selector::operator=(Selector&& other) = default;
+
+bool Selector::operator<(const Selector& other) const {
+ return proto.filters() < other.proto.filters();
+}
+
+bool Selector::operator==(const Selector& other) const {
+ return !(*this < other) && !(other < *this);
+}
+
+Selector& Selector::MustBeVisible() {
+ int filter_count = proto.filters().size();
+ if (filter_count == 0 || proto.filters(filter_count - 1).has_bounding_box()) {
+ // Avoids adding duplicate visibility requirements in the common case.
+ return *this;
+ }
+ proto.add_filters()->mutable_bounding_box();
+ return *this;
+}
+
+bool Selector::empty() const {
+ bool has_css_selector = false;
+ for (const SelectorProto::Filter& filter : proto.filters()) {
+ switch (filter.filter_case()) {
+ case SelectorProto::Filter::FILTER_NOT_SET:
+ // There must not be any unknown or invalid filters.
+ return true;
+
+ case SelectorProto::Filter::kCssSelector:
+ // There must be at least one CSS selector, since it's the only
+ // way we have of expanding the set of matches.
+ has_css_selector = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+ return !has_css_selector;
+}
+
+base::Optional<std::string> Selector::ExtractSingleCssSelectorForAutofill()
+ const {
+ int last_enter_frame_index = -1;
+ for (int i = proto.filters().size() - 1; i >= 0; i--) {
+ if (proto.filters(i).filter_case() == SelectorProto::Filter::kEnterFrame) {
+ last_enter_frame_index = i;
+ break;
+ }
+ }
+ std::string css_selector;
+ for (int i = last_enter_frame_index + 1; i < proto.filters().size(); i++) {
+ const SelectorProto::Filter& filter = proto.filters(i);
+ switch (filter.filter_case()) {
+ case SelectorProto::Filter::kCssSelector:
+ if (css_selector.empty()) {
+ css_selector = filter.css_selector();
+ } else {
+ VLOG(1) << __func__
+ << " Selector with multiple CSS selectors not supported for "
+ "autofill: "
+ << *this;
+ return base::nullopt;
+ }
+ break;
+
+ case SelectorProto::Filter::kBoundingBox:
+ case SelectorProto::Filter::kPickOne:
+ // Ignore these; they're not relevant for the autofill use-case
+ break;
+
+ case SelectorProto::Filter::kInnerText:
+ case SelectorProto::Filter::kValue:
+ case SelectorProto::Filter::kPseudoType:
+ case SelectorProto::Filter::kPseudoElementContent:
+ case SelectorProto::Filter::kLabelled:
+ case SelectorProto::Filter::kClosest:
+ VLOG(1) << __func__
+ << " Selector feature not supported by autofill: " << *this;
+ return base::nullopt;
+
+ case SelectorProto::Filter::FILTER_NOT_SET:
+ VLOG(1) << __func__ << " Unknown filter type in: " << *this;
+ return base::nullopt;
+
+ case SelectorProto::Filter::kEnterFrame:
+ // This cannot possibly happen, since the iteration started after the
+ // last enter_frame.
+ NOTREACHED();
+ break;
+ }
}
- if (!selector.value_pattern.empty()) {
- out << " value =~ /";
- out << selector.value_pattern;
- out << "/";
+ if (css_selector.empty()) {
+ VLOG(1) << __func__
+ << " Selector without CSS selector not supported by autofill: "
+ << *this;
+ return base::nullopt;
}
- if (selector.must_be_visible) {
- out << " must_be_visible";
+ return css_selector;
+}
+
+std::ostream& operator<<(std::ostream& out, const Selector& selector) {
+ return out << selector.proto;
+}
+
+#ifndef NDEBUG
+namespace {
+
+// Debug output for pseudo types.
+std::ostream& operator<<(std::ostream& out, PseudoType pseudo_type) {
+ return out << PseudoTypeName(pseudo_type);
+}
+
+std::ostream& operator<<(std::ostream& out,
+ const SelectorProto::TextFilter& c) {
+ out << "/" << c.re2() << "/";
+ if (c.case_sensitive()) {
+ out << "i";
}
- if (selector.pseudo_type != PseudoType::UNDEFINED) {
- out << "::" << selector.pseudo_type;
+ return out;
+}
+
+std::ostream& operator<<(
+ std::ostream& out,
+ const google::protobuf::RepeatedPtrField<SelectorProto::Filter>& filters) {
+ out << "[";
+ std::string separator = "";
+ for (const SelectorProto::Filter& filter : filters) {
+ out << separator << filter;
+ separator = " ";
}
+ out << "]";
+ return out;
+}
+} // namespace
+#endif // NDEBUG
+
+std::ostream& operator<<(std::ostream& out, const SelectorProto& selector) {
+#ifdef NDEBUG
+ out << selector.filters().size() << " filter(s)";
+#else
+ out << selector.filters();
+#endif // NDEBUG
return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const SelectorProto::Filter& f) {
+#ifdef NDEBUG
+ // DEBUG output not available.
+ return out << "filter case=" << f.filter_case();
+#else
+ switch (f.filter_case()) {
+ case SelectorProto::Filter::kEnterFrame:
+ out << "/";
+ return out;
+
+ case SelectorProto::Filter::kCssSelector:
+ out << f.css_selector();
+ return out;
+
+ case SelectorProto::Filter::kInnerText:
+ out << "innerText~=" << f.inner_text();
+ return out;
+
+ case SelectorProto::Filter::kValue:
+ out << "value~=" << f.value();
+ return out;
+
+ case SelectorProto::Filter::kPseudoType:
+ out << "::" << f.pseudo_type();
+ return out;
+
+ case SelectorProto::Filter::kPseudoElementContent:
+ out << "::" << f.pseudo_element_content().pseudo_type()
+ << "~=" << f.pseudo_element_content().content();
+ return out;
+
+ case SelectorProto::Filter::kBoundingBox:
+ out << "bounding_box";
+ return out;
+
+ case SelectorProto::Filter::kPickOne:
+ out << "pick_one";
+ return out;
+
+ case SelectorProto::Filter::kLabelled:
+ out << "labelled";
+ return out;
+
+ case SelectorProto::Filter::kClosest:
+ out << "closest to " << f.closest().target();
+ switch (f.closest().relative_position()) {
+ case SelectorProto::ProximityFilter::UNSPECIFIED_POSITION:
+ break;
+ case SelectorProto::ProximityFilter::ABOVE:
+ out << " above";
+ break;
+ case SelectorProto::ProximityFilter::BELOW:
+ out << " below";
+ break;
+ case SelectorProto::ProximityFilter::RIGHT:
+ out << " right";
+ break;
+ case SelectorProto::ProximityFilter::LEFT:
+ out << " left";
+ break;
+ }
+ if (f.closest().in_alignment()) {
+ out << " in alignment";
+ }
+ return out;
+
+ case SelectorProto::Filter::FILTER_NOT_SET:
+ // Either unset or set to an unsupported value. Let's assume the worse.
+ out << "INVALID";
+ return out;
+ }
#endif // NDEBUG
}
diff --git a/chromium/components/autofill_assistant/browser/selector.h b/chromium/components/autofill_assistant/browser/selector.h
index 1a174e0c166..c79849f3b71 100644
--- a/chromium/components/autofill_assistant/browser/selector.h
+++ b/chromium/components/autofill_assistant/browser/selector.h
@@ -10,42 +10,33 @@
#include <vector>
#include "base/macros.h"
+#include "base/optional.h"
+#include "components/autofill_assistant/browser/client_status.h"
#include "components/autofill_assistant/browser/service.pb.h"
namespace autofill_assistant {
-// A structure to represent a CSS selector.
-struct Selector {
- // A sequence of CSS selectors. Any non-final CSS selector is expected to
- // arrive at a frame or an iframe, i.e. an element that contains another
- // document.
- std::vector<std::string> selectors;
-
- // If true, only match visible elements. Visible elements are elements that
- // have a box model. The box model is not checked at all, so an element with a
- // zero size bounding box is considered visible.
- bool must_be_visible = false;
-
- // If non-empty, this must be a regular expression that matches the inner text
- // of the element(s) matching selectors.
- std::string inner_text_pattern;
+// Convenience functions for creating SelectorProtos.
+SelectorProto ToSelectorProto(const std::string& s);
+SelectorProto ToSelectorProto(const std::vector<std::string>& s);
- // If non-empty, this must be a regular expression that matches the value
- // of the element(s) matching selectors.
- std::string value_pattern;
+// Returns the CSS name of a pseudo-type, without "::" prefix.
+std::string PseudoTypeName(PseudoType pseudoType);
- // An optional pseudo type. This pseudo type is associated to the final
- // element matched by |selectors|, which means that we currently don't handle
- // matching an element inside a pseudo element.
- PseudoType pseudo_type = PseudoType::UNDEFINED;
+// Convenience wrapper around a SelectorProto that makes it simpler to work with
+// selectors.
+//
+// Selectors are comparables, can be used as std::map key or std::set elements
+// and converted to string with operator<<.
+struct Selector {
+ SelectorProto proto;
Selector();
- explicit Selector(const ElementReferenceProto& element);
- explicit Selector(std::vector<std::string> s);
- Selector(std::vector<std::string> s, PseudoType p);
~Selector();
- ElementReferenceProto ToElementReferenceProto() const;
+ explicit Selector(const SelectorProto& proto);
+ explicit Selector(const std::vector<std::string>& s)
+ : Selector(ToSelectorProto(s)) {}
Selector(Selector&& other);
Selector(const Selector& other);
@@ -56,30 +47,70 @@ struct Selector {
bool operator==(const Selector& other) const;
// Convenience function to update the visible field in a fluent style.
- Selector& MustBeVisible() {
- must_be_visible = true;
- return *this;
- }
-
- // The output operator. The actual selectors are only available in debug
- // builds.
- friend std::ostream& operator<<(std::ostream& out, const Selector& selector);
+ Selector& MustBeVisible();
- // Checks whether this selector is empty.
+ // Checks whether this selector is empty or invalid.
bool empty() const;
// Convenience function to set inner_text_pattern in a fluent style.
Selector& MatchingInnerText(const std::string& pattern) {
- inner_text_pattern = pattern;
+ return MatchingInnerText(pattern, false);
+ }
+
+ // Convenience function to set inner_text_pattern matching with case
+ // sensitivity.
+ Selector& MatchingInnerText(const std::string& pattern, bool case_sensitive) {
+ auto* text_filter = proto.add_filters()->mutable_inner_text();
+ text_filter->set_re2(pattern);
+ text_filter->set_case_sensitive(case_sensitive);
return *this;
}
// Convenience function to set inner_text_pattern in a fluent style.
Selector& MatchingValue(const std::string& pattern) {
- value_pattern = pattern;
+ return MatchingValue(pattern, false);
+ }
+
+ // Convenience function to set value_pattern matchinng with case sensitivity.
+ Selector& MatchingValue(const std::string& pattern, bool case_sensitive) {
+ auto* text_filter = proto.add_filters()->mutable_value();
+ text_filter->set_re2(pattern);
+ text_filter->set_case_sensitive(case_sensitive);
return *this;
}
+
+ Selector& SetPseudoType(PseudoType pseudo_type) {
+ proto.add_filters()->set_pseudo_type(pseudo_type);
+ return *this;
+ }
+
+ // Returns a single CSS selector pointing to the element from the last frame,
+ // to pass to autofill.
+ //
+ // This call returns nothing if the selector contains unsupported filters,
+ // such as innerText or pseudo-element filters.
+ //
+ // AutofillAgent::GetElementFormAndFieldData takes a single CSS selector that
+ // identifies the form. This means that form elements for autofill are limited
+ // to one single CSS selector and no further filtering. TODO(b/155264465):
+ // have ElementFinder specify the element it has found in a format that
+ // Autofill can recognise.
+ base::Optional<std::string> ExtractSingleCssSelectorForAutofill() const;
};
+
+// Debug output operator for selectors. The output is only useful in
+// debug builds.
+std::ostream& operator<<(std::ostream& out, const Selector& selector);
+
+// Debug output for selector protos. The output is only useful in
+// debug builds.
+std::ostream& operator<<(std::ostream& out, const SelectorProto& proto);
+
+// Debug output for selector filter protos. The output is only useful in
+// debug builds.
+std::ostream& operator<<(std::ostream& out,
+ const SelectorProto::Filter& filter);
+
} // namespace autofill_assistant
#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SELECTOR_H_
diff --git a/chromium/components/autofill_assistant/browser/selector_unittest.cc b/chromium/components/autofill_assistant/browser/selector_unittest.cc
index 462bcc3db29..08639296f6b 100644
--- a/chromium/components/autofill_assistant/browser/selector_unittest.cc
+++ b/chromium/components/autofill_assistant/browser/selector_unittest.cc
@@ -8,74 +8,169 @@
#include "components/autofill_assistant/browser/service.pb.h"
#include "testing/gmock/include/gmock/gmock.h"
+using ::testing::UnorderedElementsAre;
+
namespace autofill_assistant {
namespace {
-TEST(SelectorTest, FromProto) {
- ElementReferenceProto proto;
- proto.add_selectors("a");
- proto.add_selectors("b");
- proto.set_inner_text_pattern("c");
- proto.set_value_pattern("d");
- proto.set_visibility_requirement(MUST_BE_VISIBLE);
- proto.set_pseudo_type(PseudoType::BEFORE);
-
- Selector selector(proto);
- EXPECT_THAT(selector.selectors, testing::ElementsAre("a", "b"));
- EXPECT_TRUE(selector.must_be_visible);
- EXPECT_EQ("c", selector.inner_text_pattern);
- EXPECT_EQ("d", selector.value_pattern);
- EXPECT_EQ(PseudoType::BEFORE, selector.pseudo_type);
+TEST(SelectorTest, Constructor_Simple) {
+ Selector selector({"#test"});
+ ASSERT_EQ(1, selector.proto.filters().size());
+ EXPECT_EQ("#test", selector.proto.filters(0).css_selector());
}
-TEST(SelectorTest, ToProto) {
- Selector selector;
- selector.selectors.emplace_back("a");
- selector.selectors.emplace_back("b");
- selector.inner_text_pattern = "c";
- selector.value_pattern = "d";
- selector.must_be_visible = true;
- selector.pseudo_type = PseudoType::BEFORE;
-
- ElementReferenceProto proto = selector.ToElementReferenceProto();
- EXPECT_THAT(proto.selectors(), testing::ElementsAre("a", "b"));
- EXPECT_EQ("c", proto.inner_text_pattern());
- EXPECT_EQ("d", proto.value_pattern());
- EXPECT_EQ(MUST_BE_VISIBLE, proto.visibility_requirement());
- EXPECT_EQ(PseudoType::BEFORE, proto.pseudo_type());
+TEST(SelectorTest, Constructor_WithIframe) {
+ Selector selector({"#frame", "#test"});
+ ASSERT_EQ(4, selector.proto.filters().size());
+ EXPECT_EQ("#frame", selector.proto.filters(0).css_selector());
+ EXPECT_EQ(selector.proto.filters(1).filter_case(),
+ SelectorProto::Filter::kPickOne);
+ EXPECT_EQ(selector.proto.filters(2).filter_case(),
+ SelectorProto::Filter::kEnterFrame);
+ EXPECT_EQ("#test", selector.proto.filters(3).css_selector());
+}
+
+TEST(SelectorTest, FromProto) {
+ SelectorProto proto;
+ proto.add_filters()->set_css_selector("#test");
+
+ EXPECT_EQ(Selector({"#test"}), Selector(proto));
}
TEST(SelectorTest, Comparison) {
+ // Note that comparison tests cover < indirectly through ==, since a == b is
+ // defined as !(a < b) && !(b < a). This makes sense, as what matters is that
+ // there is an order but what the order is doesn't matter.
+
EXPECT_FALSE(Selector({"a"}) == Selector({"b"}));
- EXPECT_LT(Selector({"a"}), Selector({"b"}));
EXPECT_TRUE(Selector({"a"}) == Selector({"a"}));
+}
- EXPECT_FALSE(Selector({"a"}, PseudoType::BEFORE) ==
- Selector({"a"}, PseudoType::AFTER));
- EXPECT_LT(Selector({"a"}, PseudoType::BEFORE),
- Selector({"a"}, PseudoType::AFTER));
- EXPECT_LT(Selector({"a"}, PseudoType::BEFORE), Selector({"b"}));
- EXPECT_TRUE(Selector({"a"}, PseudoType::BEFORE) ==
- Selector({"a"}, PseudoType::BEFORE));
+TEST(SelectorTest, SelectorInSet) {
+ std::set<Selector> selectors;
+ selectors.insert(Selector({"a"}));
+ selectors.insert(Selector({"a"}));
+ selectors.insert(Selector({"b"}));
+ selectors.insert(Selector({"c"}));
+ EXPECT_THAT(selectors, UnorderedElementsAre(Selector({"a"}), Selector({"b"}),
+ Selector({"c"})));
+}
+TEST(SelectorTest, Comparison_PseudoType) {
+ EXPECT_FALSE(Selector({"a"}).SetPseudoType(PseudoType::BEFORE) ==
+ Selector({"a"}).SetPseudoType(PseudoType::AFTER));
+ EXPECT_FALSE(Selector({"a"}).SetPseudoType(PseudoType::BEFORE) ==
+ Selector({"a"}).SetPseudoType(PseudoType::AFTER));
+ EXPECT_FALSE(Selector({"b"}) ==
+ Selector({"a"}).SetPseudoType(PseudoType::BEFORE));
+ EXPECT_FALSE(Selector({"b"}) ==
+ Selector({"a"}).SetPseudoType(PseudoType::BEFORE));
+ EXPECT_TRUE(Selector({"a"}).SetPseudoType(PseudoType::BEFORE) ==
+ Selector({"a"}).SetPseudoType(PseudoType::BEFORE));
+}
+
+TEST(SelectorTest, Comparison_Visibility) {
EXPECT_FALSE(Selector({"a"}) == Selector({"a"}).MustBeVisible());
- EXPECT_LT(Selector({"a"}), Selector({"a"}).MustBeVisible());
EXPECT_TRUE(Selector({"a"}).MustBeVisible() ==
Selector({"a"}).MustBeVisible());
+}
+TEST(SelectorTest, Comparison_InnerText) {
EXPECT_FALSE(Selector({"a"}).MatchingInnerText("a") ==
Selector({"a"}).MatchingInnerText("b"));
- EXPECT_LT(Selector({"a"}).MatchingInnerText("a"),
- Selector({"a"}).MatchingInnerText("b"));
EXPECT_TRUE(Selector({"a"}).MatchingInnerText("a") ==
Selector({"a"}).MatchingInnerText("a"));
+ EXPECT_FALSE(Selector({"a"}).MatchingInnerText("a", false) ==
+ Selector({"a"}).MatchingInnerText("a", true));
+ EXPECT_TRUE(Selector({"a"}).MatchingInnerText("a", true) ==
+ Selector({"a"}).MatchingInnerText("a", true));
+}
+
+TEST(SelectorTest, Comparison_Value) {
EXPECT_FALSE(Selector({"a"}).MatchingValue("a") ==
Selector({"a"}).MatchingValue("b"));
- EXPECT_LT(Selector({"a"}).MatchingValue("a"),
- Selector({"a"}).MatchingValue("b"));
EXPECT_TRUE(Selector({"a"}).MatchingValue("a") ==
Selector({"a"}).MatchingValue("a"));
+
+ EXPECT_FALSE(Selector({"a"}).MatchingValue("a", false) ==
+ Selector({"a"}).MatchingValue("a", true));
+ EXPECT_TRUE(Selector({"a"}).MatchingValue("a", true) ==
+ Selector({"a"}).MatchingValue("a", true));
+}
+
+TEST(SelectorTest, Comparison_Proximity) {
+ SelectorProto selector;
+ selector.add_filters()->set_css_selector("button");
+ auto* closest_to_button = selector.add_filters()->mutable_closest();
+ closest_to_button->mutable_target()->Add()->set_css_selector("#label1");
+
+ EXPECT_TRUE(Selector(selector) == Selector(selector));
+
+ // Different relative positions
+ SelectorProto left = selector;
+ left.mutable_filters(0)->mutable_closest()->set_relative_position(
+ SelectorProto::ProximityFilter::LEFT);
+
+ SelectorProto right = selector;
+ right.mutable_filters(0)->mutable_closest()->set_relative_position(
+ SelectorProto::ProximityFilter::RIGHT);
+
+ EXPECT_TRUE(Selector(right) == Selector(right));
+ EXPECT_TRUE(Selector(left) == Selector(left));
+ EXPECT_FALSE(Selector(left) == Selector(right));
+
+ // Different alignment
+ SelectorProto aligned = selector;
+ selector.mutable_filters(0)->mutable_closest()->set_in_alignment(true);
+ EXPECT_TRUE(Selector(aligned) == Selector(aligned));
+ EXPECT_FALSE(Selector(selector) == Selector(aligned));
+
+ // Different targets
+ SelectorProto label2 = selector;
+ label2.mutable_filters(0)
+ ->mutable_closest()
+ ->mutable_target()
+ ->Add()
+ ->set_css_selector("#label2");
+
+ EXPECT_TRUE(Selector(label2) == Selector(label2));
+ EXPECT_FALSE(Selector(selector) == Selector(label2));
+}
+
+TEST(SelectorTest, Comparison_Frames) {
+ Selector ab({"a", "b"});
+ EXPECT_EQ(ab, ab);
+
+ Selector cb({"c", "b"});
+ EXPECT_TRUE(cb == cb);
+ EXPECT_FALSE(ab == cb);
+
+ Selector b({"b"});
+ EXPECT_TRUE(b == b);
+ EXPECT_FALSE(ab == b);
+}
+
+TEST(SelectorTest, Comparison_MultipleFilters) {
+ Selector abcdef;
+ abcdef.proto.add_filters()->set_css_selector("abc");
+ abcdef.proto.add_filters()->set_css_selector("def");
+
+ Selector abcdef2;
+ abcdef2.proto.add_filters()->set_css_selector("abc");
+ abcdef2.proto.add_filters()->set_css_selector("def");
+ EXPECT_EQ(abcdef, abcdef2);
+
+ Selector defabc;
+ defabc.proto.add_filters()->set_css_selector("def");
+ defabc.proto.add_filters()->set_css_selector("abc");
+ EXPECT_TRUE(defabc == defabc);
+ EXPECT_FALSE(abcdef == defabc);
+
+ Selector abc;
+ abc.proto.add_filters()->set_css_selector("abc");
+ EXPECT_TRUE(abc == abc);
+ EXPECT_FALSE(abcdef == abc);
}
} // namespace
diff --git a/chromium/components/autofill_assistant/browser/service.proto b/chromium/components/autofill_assistant/browser/service.proto
index de7306a466b..61d392f405c 100644
--- a/chromium/components/autofill_assistant/browser/service.proto
+++ b/chromium/components/autofill_assistant/browser/service.proto
@@ -79,6 +79,20 @@ message ClientContextProto {
}
// Whether the account from the intent and the chrome account match.
optional AccountsMatchingStatus accounts_matching_status = 12;
+
+ // Whether a11y (talkback and touch exploration) is enabled or not.
+ optional bool accessibility_enabled = 13;
+
+ enum SignedIntoChromeStatus {
+ UNDEFINED = 0;
+
+ // There is no account signed into Chrome.
+ NOT_SIGNED_IN = 1;
+
+ // An account is signed into Chrome.
+ SIGNED_IN = 2;
+ }
+ optional SignedIntoChromeStatus signed_into_chrome_status = 14;
}
// Get the list of scripts that can potentially be run on a url.
@@ -528,7 +542,7 @@ message ProcessedActionProto {
optional ProcessedActionStatusDetailsProto status_details = 19;
oneof result_data {
- PromptProto.Choice prompt_choice = 5;
+ PromptProto.Result prompt_choice = 5;
string html_source = 12;
// Should be set as a result of CollectUserDataAction.
CollectUserDataResultProto collect_user_data_result = 15;
@@ -638,7 +652,7 @@ message UnexpectedErrorInfoProto {
message AutofillErrorInfoProto {
message AutofillFieldError {
// The field the error occurred for.
- optional ElementReferenceProto field = 1;
+ optional SelectorProto field = 1;
// The value expression associated with the field that caused the error.
optional string value_expression = 5;
@@ -649,6 +663,14 @@ message AutofillErrorInfoProto {
// The status of the action.
ProcessedActionStatusProto status = 4;
+
+ // The field was required and expected to be filled during the fallback
+ // flow but was empty in the end.
+ bool empty_after_fallback = 6;
+
+ // The field was expected to be cleared during the fallback flow but
+ // still had a value in the end.
+ bool filled_after_clear = 7;
}
reserved 2;
@@ -694,65 +716,187 @@ enum PseudoType {
INPUT_LIST_BUTTON = 15;
}
-// A reference to an unique element on the page, possibly nested in frames.
-message ElementReferenceProto {
- // A sequence of CSS selectors. Any non-final CSS selector is expected to
- // arrive at a frame or an iframe, i.e. an element that contains another
- // document.
- // APIs are free to reject element references that do not refer to unique
- // elements (i.e. resolve to more than one element on the page).
- repeated string selectors = 2;
+// A reference to one or more elements on the page, possibly nested in frames.
+message SelectorProto {
+ // Filters for the element on the page. Filter are applied sequentially, using
+ // the output of the previous filter as input. The root of these filters is
+ // the main frame's document.
+ repeated Filter filters = 9;
+
+ // A filter that starts with one or more elements and returns one on more
+ // elements. Filters are meant to be applied sequentially.
+ message Filter {
+ oneof filter {
+ // Enter the document of an iframe or shadow root. The next filters apply
+ // to the document inside of the iframe(s) or on the shadow element.
+ //
+ // Fails if there are more than one match.
+ EmptyFilter enter_frame = 1;
+
+ // Evaluate the given CSS selector on all start elements and use
+ // the result as end elements.
+ string css_selector = 2;
+
+ // Check the inner text of all start elements, using the Javascript
+ // innerText property. Keep only the element whose innerText match the
+ // given regular expression.
+ TextFilter inner_text = 3;
+
+ // Check the value of all start elements, using the Javascript value
+ // property. Keep only the element whose value property match the given
+ // regular expression.
+ TextFilter value = 4;
+
+ // Select the pseudo-element of the given type associated with the current
+ // elements.
+ PseudoType pseudo_type = 5;
+
+ // Only keep elements that have a box model, even if it is empty.
+ //
+ // This is the equivalent of the old MUST_BE_VISIBLE flag. It's been
+ // renamed as having a bounding box is not enough to imply visibility.
+ EmptyFilter bounding_box = 6;
+
+ // Pick just one match and continue. Ignore all other matches.
+ //
+ // For backward-compatibility with older element references, pick_one can
+ // be put before enter_frame.
+ EmptyFilter pick_one = 7;
+
+ // Only keep elements that have a pseudo-element with the given content.
+ //
+ // This only works with BEFORE and AFTER.
+ //
+ // Note that this just filters out elements. It doesn't select the
+ // pseudo-element; use pseudo_type for that.
+ PseudoElementContent pseudo_element_content = 8;
+
+ // Go from label to the labelled control. Only works starting with current
+ // elements that are LABEL.
+ //
+ // For example if we have:
+ // <label for="someid">First Name</label>...<input id="someid" ...>
+ // then labelled, goes from the label to the form element.
+ //
+ // So, the form element can be accessed as "label~=/FirstName/ labelled".
+ // This is especially useful in situations where someid can change.
+ //
+ // The same selector also works in the case where the element is inside of
+ // the label, so we don't need to worry which implementation is used when
+ // building the selector:
+ // <label>First Name <input ...></label>
+ EmptyFilter labelled = 9;
+
+ // Filter elements by their position on the page, relative to a given
+ // target element.
+ //
+ // The distance between two elements is the shortest euclidean distance
+ // between their borders. The distance between two overlapping elements is
+ // always 0. If there are multiple elements at exactly the same distance,
+ // an arbitrary one is returned.
+ ProximityFilter closest = 10;
+ }
+ }
- // If non-empty, only take into accounts the elements matched by selector
- // whose inner text matches the given JavaScript regex. This does a search,
- // not a full match. Add ^ and $ as necessary.
- //
- // This is used to filter the elements matching the last selector, before
- // trying to get the pseudo_type, if any was set.
- optional string inner_text_pattern = 4;
+ // A way of filtering elements by their text.
+ message TextFilter {
+ // Javascript RE2 regular expression to apply to the text. This is evaluated
+ // with Regexp.test, so it's a "find" and will be satisfied whenever the
+ // text contains at least one substring that matches the given regular
+ // expression.
+ optional string re2 = 1;
- // If non-empty, only take into accounts the elements matched by selector
- // whose value matches the given JavaScript regex. This does a search,
- // not a full match. Add ^ and $ as necessary.
- //
- // This is used to filter the elements matching the last selector, before
- // trying to get the pseudo_type, if any was set.
- optional string value_pattern = 6;
+ // If true, the regular expression is case-sensitive.
+ optional bool case_sensitive = 2;
+ }
- // Specifies whether the matched element(s) must be visible.
- //
- // Visibility applies to the element matched in selectors; the pseudo type is
- // checked afterwards.
- optional VisibilityRequirement visibility_requirement = 5;
+ // A way of filtering elements by their pseudo-element content.
+ message PseudoElementContent {
+ optional PseudoType pseudo_type = 1;
+ optional TextFilter content = 2;
+ }
- // An optional pseudo type. This pseudo type is associated to the final
- // element matched, which means that we currently don't handle matching an
- // element inside a pseudo element.
- optional PseudoType pseudo_type = 3;
-}
+ // Filter elements by their position on the page, relative to a given target
+ // element.
+ message ProximityFilter {
+ // From the set of potential matches, choose the one closest to the given
+ // target. The target filters are evaluated relative to the current frame
+ // and must select an element in the current frame.
+ //
+ // If there is no target the whole selector matches nothing.
+ //
+ // This element cannot include enter_frame filters.
+ repeated Filter target = 1;
+
+ // If true, the element and targets must be aligned either
+ // horizontally or vertically.
+ //
+ // This is usually what we want, as elements close, but in diagonal position
+ // relative to each other are usually not considered part of the same group.
+ optional bool in_alignment = 3;
+
+ // Require the target and element to have a specific relative position.
+ //
+ // If unspecified, the target and element be in any position relative to
+ // each other.
+ //
+ // If necessary, this can be combined with in_alignment, so in_aligment=true
+ // relative_position=LEFT requires the element to be strictly to the left or
+ // target.
+ optional RelativePosition relative_position = 4;
+
+ enum RelativePosition {
+ // Unspecified relative position.
+ UNSPECIFIED_POSITION = 0;
-enum VisibilityRequirement {
- UNSPECIFIED_VISIBILITY = 0;
+ // Element is above target.
+ ABOVE = 1;
+
+ // Element is below target.
+ BELOW = 2;
+
+ // Element is left of target.
+ LEFT = 3;
+
+ // Element is right of target.
+ RIGHT = 4;
+ }
+
+ // Maximum number of pairs the client is allowed to compare.
+ //
+ // If there are too many pairs to compare, the client bails out and returns
+ // the status TOO_MANY_CANDIDATES to the server.
+ //
+ // The maximum number of pairs is limited, to avoid clients being slowed
+ // down by overly expensive selectors, as the current algorithm is not
+ // optimized for large number of pairs. Authors of selectors must take care
+ // to keep the number of pairs reasonable.
+ //
+ // For example, avoid looking for "a div near label X". This will be too
+ // slow to process. Look instead for "a button near label X" or "a clickable
+ // div near label X".
+ //
+ // This setting must not be exposed to scripts. It must not be increased
+ // just to allow that one slow selector. This is a value that must be
+ // maintained by the team responsible for keeping clients running properly.
+ optional int32 max_pairs = 5 [default = 50];
+ }
- // Element must be visible. Visible elements are elements that have a box
- // model. The box model is not checked at all, so an element with a zero size
- // bounding box is considered visible.
- MUST_BE_VISIBLE = 1;
+ message EmptyFilter {}
- // It's enough for the element to exist in the DOM tree for it to match.
- MUST_EXIST = 2;
+ reserved 1 to 8;
}
// Contain all arguments to perform a click.
message ClickProto {
- optional ElementReferenceProto element_to_click = 1;
+ optional SelectorProto element_to_click = 1;
optional ClickType click_type = 2;
}
// Contain all arguments to perform a select option action.
message SelectOptionProto {
// The drop down element on which to select an option.
- optional ElementReferenceProto element = 2;
+ optional SelectorProto element = 2;
// Value of the option to use.
optional string selected_option = 3;
@@ -788,7 +932,7 @@ message FocusElementProto {
}
}
// Element to focus on.
- optional ElementReferenceProto element = 1;
+ optional SelectorProto element = 1;
// Optional title to show in the status bar.
optional string title = 2;
@@ -797,7 +941,7 @@ message FocusElementProto {
//
// Deprecated: use touchable_element_area instead. Ignored if
// touchable_element_area is non-empty.
- repeated ElementReferenceProto deprecated_touchable_elements = 5;
+ repeated SelectorProto deprecated_touchable_elements = 5;
// Restrict interaction to a series of rectangular areas.
optional ElementAreaProto touchable_element_area = 6;
@@ -813,7 +957,7 @@ message ElementAreaProto {
//
// The rectangle is the smallest rectangle that includes all listed elements.
message Rectangle {
- repeated ElementReferenceProto elements = 1;
+ repeated SelectorProto elements = 1;
// If true, the width of the rectangle always corresponds to the width of
// the screen.
@@ -833,13 +977,9 @@ message UseAddressProto {
// Message used to indicate what form fields should be filled with what
// information coming from the address.
message RequiredField {
- // Fields we add that are not mapped to field_types.h. The values must be
- // negative not to overlap with the source.
- enum AutofillAssistantCustomField { UNDEFINED = 0; }
-
// A string containing either a single integer key or multiple "${key}"
// placeholders, where the key is an integer corresponding to entries from
- // field_types.h or RequiredField::AutofillAssistantCustomField.
+ // field_types.h or AutofillFormatProto::AutofillAssistantCustomField.
// Example:
// * "3" -> First name.
// * "${3}" -> First name.
@@ -847,9 +987,10 @@ message UseAddressProto {
// e.g., (+41) (79) (1234567)
// Note that the set of actually available fields are outside of our
// control and are retrieved automatically from the provided profile.
+ // An value expression set to an empty string will clear the field.
optional string value_expression = 6;
- optional ElementReferenceProto element = 2;
+ optional SelectorProto element = 2;
// The strategy used to execute filling the value.
optional KeyboardValueFillStrategy fill_strategy = 7;
@@ -870,22 +1011,21 @@ message UseAddressProto {
// selector must match a generic option, an |inner_text_pattern| will be
// added to this element reference to match a single option.
// Both clicks use the same |click_type|.
- optional ElementReferenceProto option_element_to_click = 9;
+ optional SelectorProto option_element_to_click = 9;
optional ClickType click_type = 10;
reserved 1, 3;
}
- // An optional name to allow to handle multiple addresses selection (for
- // instance a billing and a delivery address).
- optional string name = 1;
-
- // An optional message to show to the user when asking to select an address.
- // TODO(crbug.com/806868): Make the prompt a required field.
- optional string prompt = 2;
+ oneof address_source {
+ // The client memory key from which to retrieve the address.
+ string name = 1;
+ // The client model identifier from which to retrieve the address.
+ string model_identifier = 9;
+ }
// Reference to an element in the form that should be filled.
- optional ElementReferenceProto form_field_element = 4;
+ optional SelectorProto form_field_element = 4;
// An optional list of fields that should be filled by this action.
repeated RequiredField required_fields = 6;
@@ -894,7 +1034,7 @@ message UseAddressProto {
// |required_fields|.
optional bool skip_autofill = 10;
- reserved 7, 8, 9;
+ reserved 2, 7, 8;
}
// Fill a form with a credit card if there is one stored in client memory,
@@ -903,27 +1043,19 @@ message UseCreditCardProto {
// Message used to indicate what form fields should be filled with what
// information.
message RequiredField {
- // Fields we add that are not mapped to field_types.h. The values must be
- // negative not to overlap with the source.
- enum AutofillAssistantCustomField {
- UNDEFINED = 0;
- CREDIT_CARD_VERIFICATION_CODE = -1;
- CREDIT_CARD_NETWORK = -2;
- CREDIT_CARD_RAW_NUMBER = -3;
- }
-
// A string containing either a single integer key or multiple "${key}"
// placeholders, where the key is an integer corresponding to entries from
- // field_types.h or RequiredField::AutofillAssistantCustomField.
+ // field_types.h or AutofillFormatProto::AutofillAssistantCustomField.
// Example:
// * "51" -> Full name.
// * "${51}" -> Full Name.
// * "${53}/${55}" -> expiration month / expiration year
// Note that the set of actually available fields are outside of our
// control and are retrieved automatically from the provided credit card.
+ // An value expression set to an empty string will clear the field.
optional string value_expression = 6;
- optional ElementReferenceProto element = 2;
+ optional SelectorProto element = 2;
// The strategy used to execute filling the value.
optional KeyboardValueFillStrategy fill_strategy = 7;
@@ -944,18 +1076,18 @@ message UseCreditCardProto {
// selector must match a generic option, an |inner_text_pattern| will be
// added to this element reference to match a single option.
// Both clicks use the same |click_type|.
- optional ElementReferenceProto option_element_to_click = 9;
+ optional SelectorProto option_element_to_click = 9;
optional ClickType click_type = 10;
reserved 1, 3;
}
- // An optional message to show to the user when asking to select a card.
- // TODO(crbug.com/806868): Make the prompt a required field.
- optional string prompt = 1;
+ // The client model identifier from which to retrieve the credit card.
+ // If not specified, will use the card stored in client memory instead.
+ optional string model_identifier = 4;
// A reference to the card number field in the form that should be filled.
- optional ElementReferenceProto form_field_element = 3;
+ optional SelectorProto form_field_element = 3;
repeated RequiredField required_fields = 7;
@@ -963,7 +1095,7 @@ message UseCreditCardProto {
// |required_fields|.
optional bool skip_autofill = 8;
- reserved 4, 5, 6;
+ reserved 1, 5, 6;
}
// Ask Chrome to wait for an element in the DOM. This can be used to only
@@ -1004,7 +1136,7 @@ message ElementConditionProto {
// The condition matches if the given element exists. An empty
// ElementReference never match.
- ElementReferenceProto match = 4;
+ SelectorProto match = 4;
}
// A payload that identifies this condition. WaitForDom puts this payload
@@ -1021,7 +1153,7 @@ message ElementConditionsProto {
message UploadDomProto {
// The element that should be a root of uploaded DOM. If empty then the whole
// page is returned.
- optional ElementReferenceProto tree_root = 1;
+ optional SelectorProto tree_root = 1;
}
// Shows the progress bar.
@@ -1029,19 +1161,47 @@ message ShowProgressBarProto {
// Message to show on the progress bar while loading.
optional string message = 3;
- // Value between 0 and 100 indicating the current progress. Values above 100
- // will be capped to 100, values below 0 will be capped to 0 by the client.
- // NOTE: Setting |progress| to 100 is an equivalent of setting |done| to true.
- optional int32 progress = 6;
+ oneof progress_indicator {
+ // Value between 0 and 100 indicating the current progress. Values above 100
+ // will be capped to 100, values below 0 will be capped to 0 by the client.
+ int32 progress = 6;
+
+ // Value between 0 and |N - 1| (where N is the size of the initial
+ // |step_icons|) indicating the current state the flow is in,
+ // marking all previous steps as active. Setting the value to 0 will mark
+ // only the first step as active, up to |N - 1|, which marks everything up
+ // to the final step as active.
+ // If this is used over |progress| the step progress bar will be used.
+ int32 active_step = 8;
+ }
// Hide the progress bar in the UI.
optional bool hide = 7;
+
+ // The configuration of the step progress bar. Only has an impact if the new
+ // progress bar is being used. This configuration should only be sent once in
+ // the initial call of the progress bar, as each appearance may cause the
+ // progress bar being rerendered. The previous configuration will be carried
+ // over in each new progress |ShowProgressBarAction|.
+ message StepProgressBarConfiguration {
+ // This boolean should always be true, unless you wish to switch to the
+ // old progress bar during the flow. This is not recommended!
+ optional bool use_step_progress_bar = 1;
+
+ // Set the icons for the new progress bar. The size of the |step_icons|
+ // gives the length of the progress bar. As such an empty list is not
+ // supported. The list needs to be at least 2 items long.
+ repeated DrawableProto step_icons = 2;
+ }
+ optional StepProgressBarConfiguration step_progress_bar_configuration = 9;
+
+ reserved 1, 2, 4, 5;
}
// Contain all arguments to perform a highlight element action.
message HighlightElementProto {
// The element to highlight.
- optional ElementReferenceProto element = 1;
+ optional SelectorProto element = 1;
}
// Controls the browser navigation.
@@ -1114,7 +1274,7 @@ message WaitForDocumentProto {
// If specified, check the document in the given frame, instead
// of the main document.
- optional ElementReferenceProto frame = 2;
+ optional SelectorProto frame = 2;
// The minimum ready state needed to satisfy the requirement.
optional DocumentReadyState min_ready_state = 3
@@ -1185,6 +1345,17 @@ message ShowGenericUiProto {
// manager) will be provided and auto-updated in the specified
// |model_identifier|.
optional RequestLoginOptions request_login_options = 5;
+
+ message PeriodicElementChecks {
+ message ElementCheck {
+ // The element condition to be checked during the action.
+ optional ElementConditionProto element_condition = 1;
+ // The model identifier to write the result to.
+ optional string model_identifier = 2;
+ }
+ repeated ElementCheck element_checks = 1;
+ }
+ optional PeriodicElementChecks periodic_element_checks = 6;
}
// Allow choosing one or more possibility. If FocusElement was called just
@@ -1222,12 +1393,7 @@ message PromptProto {
// be transmitted as-is by the client without interpreting.
optional bytes server_payload = 5;
- // This field is only used when crafting a response Choice for the server
- // when the |end_on_navigation| option is set. It means there was a
- // navigation event while in prompt mode that ended the action.
- optional bool navigation_ended = 17;
-
- reserved 4, 6, 8, 13, 14;
+ reserved 4, 6, 8, 13, 14, 17;
}
repeated Choice choices = 4;
@@ -1245,13 +1411,28 @@ message PromptProto {
optional bool browse_mode = 7;
// When set to true, end prompt on navigation events happening during a prompt
- // action. The result sent back to the server will contain a Choice marked as
- // |navigation_ended|.
+ // action. The result sent back to the server in
+ // ProcessedActionProto.prompt_choice will have |navigation_ended| set to
+ // true.
optional bool end_on_navigation = 8;
// A list of domains and subdomains to allow users to navigate to when in
// browse mode.
repeated string browse_domains_whitelist = 9;
+
+ // Result to pass to ProcessedActionProto.
+ message Result {
+ // This field is only used when crafting a response Choice for the server
+ // when the |end_on_navigation| option is set. It means there was a
+ // navigation event while in prompt mode that ended the action.
+ optional bool navigation_ended = 2;
+
+ // Server payload originally found in Choice.server_payload. This should be
+ // transmitted as-is by the client without interpreting.
+ optional bytes server_payload = 5;
+
+ reserved 1;
+ }
}
message ContactDetailsProto {
@@ -1436,7 +1617,7 @@ message PopupListSectionProto {
// Asks to provide the data used by UseAddressAction and
// UseCreditCardAction.
-// Next: 33
+// Next: 34
message CollectUserDataProto {
enum TermsAndConditionsState {
// No choice has been made yet.
@@ -1465,8 +1646,11 @@ message CollectUserDataProto {
repeated string supported_basic_card_networks = 6;
// Contact details that should be gathered.
optional ContactDetailsProto contact_details = 5;
- // Override for the text of the confirm button.
- optional string confirm_button_text = 7;
+ // Optional specification for the confirm button (defaults to standard confirm
+ // chip if not specified).
+ optional ChipProto confirm_chip = 33;
+ // Optionally allows confirming through the given direct actions.
+ optional DirectActionProto confirm_direct_action = 10;
// The initial state of the terms & conditions choice.
optional TermsAndConditionsState terms_and_conditions_state = 8;
// When 'false', hide the terms and conditions box in the UI.
@@ -1488,8 +1672,6 @@ message CollectUserDataProto {
// Privacy notice telling users that autofill assistant will send personal
// data to a third party’s website.
optional string privacy_notice_text = 21;
- // Optionally allows confiriming through the given direct actions.
- optional DirectActionProto confirm_direct_action = 10;
// Additional actions available to the user. This can be used for instance to
// display a "Show terms" button that will navigate the user to the terms and
// conditions page when clicked.
@@ -1536,7 +1718,7 @@ message CollectUserDataProto {
// address, the selected credit card should also be cleared!
repeated string clear_previous_profile_selection = 30;
- reserved 26;
+ reserved 7, 26;
}
// Stop Autofill Assistant.
@@ -1668,7 +1850,7 @@ message SetFormFieldValueProto {
}
// A reference to the form element whose value should be set.
- optional ElementReferenceProto element = 1;
+ optional SelectorProto element = 1;
// The value to set.
repeated KeyPress value = 2;
@@ -1688,7 +1870,7 @@ message SetFormFieldValueProto {
// login details need to be available in the client memory for this action.
message GeneratePasswordForFormFieldProto {
// A reference to the form element for which to generate a password.
- optional ElementReferenceProto element = 1;
+ optional SelectorProto element = 1;
// The client memory key to store the generated password.
optional string memory_key = 2;
}
@@ -1703,7 +1885,7 @@ message SaveGeneratedPasswordProto {
// Set an element attribute to a specific value.
message SetAttributeProto {
// A reference to the form element whose attribute should be set.
- optional ElementReferenceProto element = 1;
+ optional SelectorProto element = 1;
// The attribute to set, e.g. ["style", "position"] to change
// element.style.position.
diff --git a/chromium/components/autofill_assistant/browser/service_impl.cc b/chromium/components/autofill_assistant/browser/service_impl.cc
index 0298031062c..45364efb3c7 100644
--- a/chromium/components/autofill_assistant/browser/service_impl.cc
+++ b/chromium/components/autofill_assistant/browser/service_impl.cc
@@ -133,6 +133,7 @@ void ServiceImpl::GetScriptsForUrl(const GURL& url,
ResponseCallback callback) {
DCHECK(url.is_valid());
+ UpdateMutableClientContextFields();
SendRequest(AddLoader(
script_server_url_,
ProtocolUtils::CreateGetScriptsRequest(
@@ -148,6 +149,7 @@ void ServiceImpl::GetActions(const std::string& script_path,
ResponseCallback callback) {
DCHECK(!script_path.empty());
+ UpdateMutableClientContextFields();
SendRequest(
AddLoader(script_action_server_url_,
ProtocolUtils::CreateInitialScriptActionsRequest(
@@ -162,6 +164,7 @@ void ServiceImpl::GetNextActions(
const std::string& previous_script_payload,
const std::vector<ProcessedActionProto>& processed_actions,
ResponseCallback callback) {
+ UpdateMutableClientContextFields();
SendRequest(AddLoader(
script_action_server_url_,
ProtocolUtils::CreateNextScriptActionsRequest(
@@ -335,4 +338,12 @@ ClientContextProto ServiceImpl::CreateClientContext(
return context;
}
+void ServiceImpl::UpdateMutableClientContextFields() {
+ client_context_.set_accessibility_enabled(client_->IsAccessibilityEnabled());
+ client_context_.set_signed_into_chrome_status(
+ client_->GetChromeSignedInEmailAddress().empty()
+ ? ClientContextProto::NOT_SIGNED_IN
+ : ClientContextProto::SIGNED_IN);
+}
+
} // namespace autofill_assistant
diff --git a/chromium/components/autofill_assistant/browser/service_impl.h b/chromium/components/autofill_assistant/browser/service_impl.h
index 57aa4b2c5d1..df95f2f6c3f 100644
--- a/chromium/components/autofill_assistant/browser/service_impl.h
+++ b/chromium/components/autofill_assistant/browser/service_impl.h
@@ -30,6 +30,7 @@ class Client;
// Native autofill assistant service which communicates with the server to get
// scripts and client actions.
+// TODO(b/158998456): Add unit tests.
class ServiceImpl : public Service {
public:
// Convenience method for creating a service from a client.
@@ -110,6 +111,9 @@ class ServiceImpl : public Service {
const std::string& locale,
const std::string& country_code,
const DeviceContext& device_context);
+ // Updates the subset of |client_context_| fields that may change during a
+ // flow.
+ void UpdateMutableClientContextFields();
content::BrowserContext* context_;
GURL script_server_url_;
@@ -136,12 +140,14 @@ class ServiceImpl : public Service {
// The client context is cached here to avoid having to recreate it for
// every message.
- const ClientContextProto client_context_;
+ ClientContextProto client_context_;
const Client* client_;
base::WeakPtrFactory<ServiceImpl> weak_ptr_factory_;
+ FRIEND_TEST_ALL_PREFIXES(ServiceImplTestSignedInStatus, SetsSignedInStatus);
+
DISALLOW_COPY_AND_ASSIGN(ServiceImpl);
};
diff --git a/chromium/components/autofill_assistant/browser/service_impl_unittest.cc b/chromium/components/autofill_assistant/browser/service_impl_unittest.cc
index 6def5e906eb..ce97f931f7f 100644
--- a/chromium/components/autofill_assistant/browser/service_impl_unittest.cc
+++ b/chromium/components/autofill_assistant/browser/service_impl_unittest.cc
@@ -38,4 +38,25 @@ TEST_F(ServiceImplTest, CreatesValidHashFromEmail) {
"2c8fa87717fab622bb5cc4d18135fe30dae339efd274b450022d361be92b48c3");
}
+class ServiceImplTestSignedInStatus
+ : public ServiceImplTest,
+ public testing::WithParamInterface<
+ std::pair<std::string, ClientContextProto::SignedIntoChromeStatus>> {
+};
+
+INSTANTIATE_TEST_SUITE_P(
+ ParametrizedTests,
+ ServiceImplTestSignedInStatus,
+ testing::Values(std::make_pair("", ClientContextProto::NOT_SIGNED_IN),
+ std::make_pair("bob@email.com",
+ ClientContextProto::SIGNED_IN)));
+
+TEST_P(ServiceImplTestSignedInStatus, SetsSignedInStatus) {
+ ON_CALL(mock_client_, GetChromeSignedInEmailAddress)
+ .WillByDefault(Return(GetParam().first));
+ service_impl_->UpdateMutableClientContextFields();
+ EXPECT_EQ(service_impl_->client_context_.signed_into_chrome_status(),
+ GetParam().second);
+}
+
} // namespace autofill_assistant
diff --git a/chromium/components/autofill_assistant/browser/trigger_context.cc b/chromium/components/autofill_assistant/browser/trigger_context.cc
index f3241f2e0c7..ab59be54a4c 100644
--- a/chromium/components/autofill_assistant/browser/trigger_context.cc
+++ b/chromium/components/autofill_assistant/browser/trigger_context.cc
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "components/autofill_assistant/browser/trigger_context.h"
+#include "base/strings/string_split.h"
namespace autofill_assistant {
@@ -59,6 +60,15 @@ std::string TriggerContextImpl::experiment_ids() const {
return experiment_ids_;
}
+bool TriggerContextImpl::HasExperimentId(
+ const std::string& experiment_id) const {
+ std::vector<std::string> experiments = base::SplitString(
+ experiment_ids_, ",", base::WhitespaceHandling::TRIM_WHITESPACE,
+ base::SplitResult::SPLIT_WANT_NONEMPTY);
+ return std::find(experiments.begin(), experiments.end(), experiment_id) !=
+ experiments.end();
+}
+
bool TriggerContextImpl::is_cct() const {
return cct_;
}
@@ -113,6 +123,16 @@ std::string MergedTriggerContext::experiment_ids() const {
return experiment_ids;
}
+bool MergedTriggerContext::HasExperimentId(
+ const std::string& experiment_id) const {
+ for (const TriggerContext* context : contexts_) {
+ if (context->HasExperimentId(experiment_id)) {
+ return true;
+ }
+ }
+ return false;
+}
+
bool MergedTriggerContext::is_cct() const {
for (const TriggerContext* context : contexts_) {
if (context->is_cct())
diff --git a/chromium/components/autofill_assistant/browser/trigger_context.h b/chromium/components/autofill_assistant/browser/trigger_context.h
index 2b62cd3e75b..735a1b5c782 100644
--- a/chromium/components/autofill_assistant/browser/trigger_context.h
+++ b/chromium/components/autofill_assistant/browser/trigger_context.h
@@ -48,6 +48,9 @@ class TriggerContext {
// Returns a comma-separated set of experiment ids.
virtual std::string experiment_ids() const = 0;
+ // Returns whether an experiment is contained in |experiment_ids|.
+ virtual bool HasExperimentId(const std::string& experiment_id) const = 0;
+
// Returns true if we're in a Chrome Custom Tab created for Autofill
// Assistant, originally created through AutofillAssistantFacade.start(), in
// Java.
@@ -75,12 +78,6 @@ class TriggerContextImpl : public TriggerContext {
~TriggerContextImpl() override;
- // Implements TriggerContext:
- void AddParameters(google::protobuf::RepeatedPtrField<ScriptParameterProto>*
- dest) const override;
- base::Optional<std::string> GetParameter(
- const std::string& name) const override;
-
void SetCCT(bool value) { cct_ = value; }
void SetOnboardingShown(bool value) { onboarding_shown_ = value; }
void SetDirectAction(bool value) { direct_action_ = value; }
@@ -88,7 +85,13 @@ class TriggerContextImpl : public TriggerContext {
caller_account_hash_ = value;
}
+ // Implements TriggerContext:
+ void AddParameters(google::protobuf::RepeatedPtrField<ScriptParameterProto>*
+ dest) const override;
+ base::Optional<std::string> GetParameter(
+ const std::string& name) const override;
std::string experiment_ids() const override;
+ bool HasExperimentId(const std::string& experiment_id) const override;
bool is_cct() const override;
bool is_onboarding_shown() const override;
bool is_direct_action() const override;
@@ -125,6 +128,7 @@ class MergedTriggerContext : public TriggerContext {
base::Optional<std::string> GetParameter(
const std::string& name) const override;
std::string experiment_ids() const override;
+ bool HasExperimentId(const std::string& experiment_id) const override;
bool is_cct() const override;
bool is_onboarding_shown() const override;
bool is_direct_action() const override;
diff --git a/chromium/components/autofill_assistant/browser/trigger_context_unittest.cc b/chromium/components/autofill_assistant/browser/trigger_context_unittest.cc
index 38b36831e5d..2268ae16dfe 100644
--- a/chromium/components/autofill_assistant/browser/trigger_context_unittest.cc
+++ b/chromium/components/autofill_assistant/browser/trigger_context_unittest.cc
@@ -80,7 +80,7 @@ TEST(TriggerContextTest, Merge) {
EXPECT_EQ("exp1,exp2", merged->experiment_ids());
}
-TEST(TriggerContextText, CCT) {
+TEST(TriggerContextTest, CCT) {
TriggerContextImpl context;
EXPECT_FALSE(context.is_cct());
@@ -88,7 +88,7 @@ TEST(TriggerContextText, CCT) {
EXPECT_TRUE(context.is_cct());
}
-TEST(TriggerContextText, MergeCCT) {
+TEST(TriggerContextTest, MergeCCT) {
auto empty = TriggerContext::CreateEmpty();
auto all_empty = TriggerContext::Merge({empty.get(), empty.get()});
@@ -124,7 +124,7 @@ TEST(TriggerContextTest, MergeOnboardingShown) {
EXPECT_TRUE(one_with_onboarding->is_onboarding_shown());
}
-TEST(TriggerContextText, DirectAction) {
+TEST(TriggerContextTest, DirectAction) {
TriggerContextImpl context;
EXPECT_FALSE(context.is_direct_action());
@@ -132,7 +132,7 @@ TEST(TriggerContextText, DirectAction) {
EXPECT_TRUE(context.is_direct_action());
}
-TEST(TriggerContextText, MergeDirectAction) {
+TEST(TriggerContextTest, MergeDirectAction) {
auto empty = TriggerContext::CreateEmpty();
auto all_empty = TriggerContext::Merge({empty.get(), empty.get()});
@@ -146,7 +146,7 @@ TEST(TriggerContextText, MergeDirectAction) {
EXPECT_TRUE(one_direct_action->is_direct_action());
}
-TEST(TriggerContextText, MergeAccountsMatchingStatusTest) {
+TEST(TriggerContextTest, MergeAccountsMatchingStatusTest) {
auto empty = TriggerContext::CreateEmpty();
auto all_empty = TriggerContext::Merge({empty.get(), empty.get()});
@@ -161,5 +161,45 @@ TEST(TriggerContextText, MergeAccountsMatchingStatusTest) {
"accountsha");
}
+TEST(TriggerContextTest, HasExperimentId) {
+ std::map<std::string, std::string> parameters;
+
+ auto context = TriggerContext::Create(parameters, "1,2,3");
+ ASSERT_TRUE(context);
+
+ EXPECT_TRUE(context->HasExperimentId("2"));
+ EXPECT_FALSE(context->HasExperimentId("4"));
+
+ auto other_context = TriggerContext::Create(parameters, "4,5,6");
+ ASSERT_TRUE(other_context);
+
+ EXPECT_TRUE(other_context->HasExperimentId("4"));
+ EXPECT_FALSE(other_context->HasExperimentId("2"));
+
+ auto merged = TriggerContext::Merge({context.get(), other_context.get()});
+ ASSERT_TRUE(merged);
+
+ EXPECT_TRUE(merged->HasExperimentId("2"));
+ EXPECT_TRUE(merged->HasExperimentId("4"));
+ EXPECT_FALSE(merged->HasExperimentId("7"));
+
+ // Double commas should not allow empty element to match.
+ auto double_comma = TriggerContext::Create(parameters, "1,,2");
+ EXPECT_TRUE(double_comma->HasExperimentId("2"));
+ EXPECT_FALSE(double_comma->HasExperimentId(""));
+
+ // Empty context should not aloow empty element to match.
+ auto empty = TriggerContext::Create(parameters, "");
+ EXPECT_FALSE(empty->HasExperimentId(""));
+
+ // Lone comma does not create empty elements.
+ auto lone_comma = TriggerContext::Create(parameters, ",");
+ EXPECT_FALSE(lone_comma->HasExperimentId(""));
+
+ // Single element should match.
+ auto single_element = TriggerContext::Create(parameters, "1");
+ EXPECT_TRUE(single_element->HasExperimentId("1"));
+}
+
} // namespace
} // namespace autofill_assistant
diff --git a/chromium/components/autofill_assistant/browser/ui_delegate.h b/chromium/components/autofill_assistant/browser/ui_delegate.h
index afb7ea6c1ee..6c12d9a38e0 100644
--- a/chromium/components/autofill_assistant/browser/ui_delegate.h
+++ b/chromium/components/autofill_assistant/browser/ui_delegate.h
@@ -42,6 +42,9 @@ class UiDelegate {
// Returns the current state of the controller.
virtual AutofillAssistantState GetState() = 0;
+ // Returns the last navigation id that caused an error.
+ virtual int64_t GetErrorCausingNavigationId() const = 0;
+
// Called when user interaction within the allowed touchable area was
// detected. This should cause rerun of preconditions check.
virtual void OnUserInteractionInsideTouchableArea() = 0;
@@ -65,9 +68,19 @@ class UiDelegate {
// Returns the current progress; a percentage.
virtual int GetProgress() const = 0;
+ // Returns the currently active progress step.
+ virtual base::Optional<int> GetProgressActiveStep() const = 0;
+
// Returns whether the progress bar is visible.
virtual bool GetProgressVisible() const = 0;
+ // Returns the current configuration of the step progress bar.
+ virtual base::Optional<ShowProgressBarProto::StepProgressBarConfiguration>
+ GetStepProgressBarConfiguration() const = 0;
+
+ // Returns whether the progress bar should show an error state.
+ virtual bool GetProgressBarErrorState() const = 0;
+
// Returns the current set of user actions.
virtual const std::vector<UserAction>& GetUserActions() const = 0;
diff --git a/chromium/components/autofill_assistant/browser/user_data_util.cc b/chromium/components/autofill_assistant/browser/user_data_util.cc
index dbaabf36417..1b7da34007f 100644
--- a/chromium/components/autofill_assistant/browser/user_data_util.cc
+++ b/chromium/components/autofill_assistant/browser/user_data_util.cc
@@ -133,6 +133,26 @@ bool CompletenessComparePaymentInstruments(
return complete_fields_a > complete_fields_b;
}
+bool IsCompleteAddress(const autofill::AutofillProfile* profile,
+ bool require_postal_code) {
+ if (!profile) {
+ return false;
+ }
+ // We use a hard coded locale here since we are only interested in whether
+ // fields are empty or not.
+ auto address_data =
+ autofill::i18n::CreateAddressDataFromAutofillProfile(*profile, "en-US");
+ if (!autofill::addressinput::HasAllRequiredFields(*address_data)) {
+ return false;
+ }
+
+ if (require_postal_code && address_data->postal_code.empty()) {
+ return false;
+ }
+
+ return true;
+}
+
} // namespace
std::vector<int> SortContactsByCompleteness(
@@ -252,4 +272,85 @@ bool CompareContactDetails(
return true;
}
+bool IsCompleteContact(
+ const autofill::AutofillProfile* profile,
+ const CollectUserDataOptions& collect_user_data_options) {
+ if (!collect_user_data_options.request_payer_name &&
+ !collect_user_data_options.request_payer_email &&
+ !collect_user_data_options.request_payer_phone) {
+ return true;
+ }
+
+ if (!profile) {
+ return false;
+ }
+
+ if (collect_user_data_options.request_payer_name &&
+ !profile->HasInfo(autofill::NAME_FULL)) {
+ return false;
+ }
+
+ if (collect_user_data_options.request_payer_email &&
+ !profile->HasInfo(autofill::EMAIL_ADDRESS)) {
+ return false;
+ }
+
+ if (collect_user_data_options.request_payer_phone &&
+ !profile->HasInfo(autofill::PHONE_HOME_WHOLE_NUMBER)) {
+ return false;
+ }
+ return true;
+}
+
+bool IsCompleteShippingAddress(
+ const autofill::AutofillProfile* profile,
+ const CollectUserDataOptions& collect_user_data_options) {
+ return !collect_user_data_options.request_shipping ||
+ IsCompleteAddress(profile, /* require_postal_code = */ false);
+}
+
+bool IsCompleteCreditCard(
+ const autofill::CreditCard* credit_card,
+ const autofill::AutofillProfile* billing_profile,
+ const CollectUserDataOptions& collect_user_data_options) {
+ if (!collect_user_data_options.request_payment_method) {
+ return true;
+ }
+
+ if (!credit_card || !billing_profile ||
+ credit_card->billing_address_id().empty()) {
+ return false;
+ }
+
+ if (!IsCompleteAddress(
+ billing_profile,
+ collect_user_data_options.require_billing_postal_code)) {
+ return false;
+ }
+
+ if (credit_card->record_type() != autofill::CreditCard::MASKED_SERVER_CARD &&
+ !credit_card->HasValidCardNumber()) {
+ // Can't check validity of masked server card numbers because they are
+ // incomplete until decrypted.
+ return false;
+ }
+
+ if (!credit_card->HasValidExpirationDate()) {
+ return false;
+ }
+
+ std::string basic_card_network =
+ autofill::data_util::GetPaymentRequestData(credit_card->network())
+ .basic_card_issuer_network;
+ if (!collect_user_data_options.supported_basic_card_networks.empty() &&
+ std::find(collect_user_data_options.supported_basic_card_networks.begin(),
+ collect_user_data_options.supported_basic_card_networks.end(),
+ basic_card_network) ==
+ collect_user_data_options.supported_basic_card_networks.end()) {
+ return false;
+ }
+
+ return true;
+}
+
} // 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 58ac0df9715..e5fa363aa72 100644
--- a/chromium/components/autofill_assistant/browser/user_data_util.h
+++ b/chromium/components/autofill_assistant/browser/user_data_util.h
@@ -62,6 +62,18 @@ bool CompareContactDetails(
const autofill::AutofillProfile* a,
const autofill::AutofillProfile* b);
+bool IsCompleteContact(const autofill::AutofillProfile* profile,
+ const CollectUserDataOptions& collect_user_data_options);
+
+bool IsCompleteShippingAddress(
+ const autofill::AutofillProfile* profile,
+ const CollectUserDataOptions& collect_user_data_options);
+
+bool IsCompleteCreditCard(
+ const autofill::CreditCard* credit_card,
+ const autofill::AutofillProfile* billing_profile,
+ const CollectUserDataOptions& collect_user_data_options);
+
} // namespace autofill_assistant
#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_USER_DATA_UTIL_H_
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 ef4fb966ca3..0cf04fd4560 100644
--- a/chromium/components/autofill_assistant/browser/user_data_util_unittest.cc
+++ b/chromium/components/autofill_assistant/browser/user_data_util_unittest.cc
@@ -33,7 +33,7 @@ TEST(UserDataUtilTest, SortsCompleteContactsAlphabetically) {
autofill::test::SetProfileInfo(profile_unicode.get(),
"\xC3\x85"
"dam",
- "", "West", "aedam.west@gmail.com", "", "", "",
+ "", "West", "adam.west@gmail.com", "", "", "",
"", "", "", "", "");
// Specify profiles in reverse order to force sorting.
@@ -375,13 +375,13 @@ TEST(UserDataUtilTest, GetDefaultSelectionForCompletePaymentInstruments) {
}
TEST(UserDataUtilTest, CompareContactDetailsMatch) {
- auto profile_a = std::make_unique<autofill::AutofillProfile>();
- autofill::test::SetProfileInfo(profile_a.get(), "Adam", "", "West",
+ autofill::AutofillProfile profile_a;
+ autofill::test::SetProfileInfo(&profile_a, "Adam", "", "West",
"adam.west@gmail.com", "", "", "", "", "", "",
"", "+41");
- auto profile_b = std::make_unique<autofill::AutofillProfile>();
- autofill::test::SetProfileInfo(profile_b.get(), "Adam", "", "West",
+ autofill::AutofillProfile profile_b;
+ autofill::test::SetProfileInfo(&profile_b, "Adam", "", "West",
"adam.west@gmail.com", "", "", "", "", "", "",
"", "+41");
@@ -390,84 +390,79 @@ TEST(UserDataUtilTest, CompareContactDetailsMatch) {
options.request_payer_email = true;
options.request_payer_phone = true;
- EXPECT_TRUE(CompareContactDetails(options, profile_a.get(), profile_b.get()));
+ EXPECT_TRUE(CompareContactDetails(options, &profile_a, &profile_b));
}
TEST(UserDataUtilTest, CompareContactDetailsMismatchForNoChecks) {
- auto profile_a = std::make_unique<autofill::AutofillProfile>();
- autofill::test::SetProfileInfo(profile_a.get(), "Adam", "", "West",
+ autofill::AutofillProfile profile_a;
+ autofill::test::SetProfileInfo(&profile_a, "Adam", "", "West",
"adam.west@gmail.com", "", "", "", "", "", "",
"", "+41");
- auto profile_b = std::make_unique<autofill::AutofillProfile>();
- autofill::test::SetProfileInfo(profile_b.get(), "Adam", "", "West",
+ autofill::AutofillProfile profile_b;
+ autofill::test::SetProfileInfo(&profile_b, "Adam", "", "West",
"adam.west@gmail.com", "", "", "", "", "", "",
"", "+41");
CollectUserDataOptions options;
- EXPECT_FALSE(
- CompareContactDetails(options, profile_a.get(), profile_b.get()));
+ EXPECT_FALSE(CompareContactDetails(options, &profile_a, &profile_b));
}
TEST(UserDataUtilTest, CompareContactDetailsMismatches) {
- auto profile_truth = std::make_unique<autofill::AutofillProfile>();
- autofill::test::SetProfileInfo(profile_truth.get(), "Adam", "", "West",
+ autofill::AutofillProfile profile_truth;
+ autofill::test::SetProfileInfo(&profile_truth, "Adam", "", "West",
"adam.west@gmail.com", "", "", "", "", "", "",
"", "+41");
- auto profile_mismatching_name = std::make_unique<autofill::AutofillProfile>();
- autofill::test::SetProfileInfo(profile_mismatching_name.get(), "Berta", "",
- "West", "adam.west@gmail.com", "", "", "", "",
- "", "", "", "+41");
+ autofill::AutofillProfile profile_mismatching_name;
+ autofill::test::SetProfileInfo(&profile_mismatching_name, "Berta", "", "West",
+ "adam.west@gmail.com", "", "", "", "", "", "",
+ "", "+41");
- auto profile_mismatching_email =
- std::make_unique<autofill::AutofillProfile>();
- autofill::test::SetProfileInfo(profile_mismatching_email.get(), "Adam", "",
- "West", "berta.west@gmail.com", "", "", "", "",
- "", "", "", "+41");
+ autofill::AutofillProfile profile_mismatching_email;
+ autofill::test::SetProfileInfo(&profile_mismatching_email, "Adam", "", "West",
+ "berta.west@gmail.com", "", "", "", "", "", "",
+ "", "+41");
- auto profile_mismatching_phone =
- std::make_unique<autofill::AutofillProfile>();
- autofill::test::SetProfileInfo(profile_mismatching_name.get(), "Adam", "",
- "West", "adam.west@gmail.com", "", "", "", "",
- "", "", "", "+44");
+ autofill::AutofillProfile profile_mismatching_phone;
+ autofill::test::SetProfileInfo(&profile_mismatching_name, "Adam", "", "West",
+ "adam.west@gmail.com", "", "", "", "", "", "",
+ "", "+44");
CollectUserDataOptions options;
options.request_payer_name = true;
options.request_payer_email = true;
options.request_payer_phone = true;
- EXPECT_FALSE(CompareContactDetails(options, profile_truth.get(),
- profile_mismatching_name.get()));
- EXPECT_FALSE(CompareContactDetails(options, profile_truth.get(),
- profile_mismatching_email.get()));
- EXPECT_FALSE(CompareContactDetails(options, profile_truth.get(),
- profile_mismatching_phone.get()));
+ EXPECT_FALSE(CompareContactDetails(options, &profile_truth,
+ &profile_mismatching_name));
+ EXPECT_FALSE(CompareContactDetails(options, &profile_truth,
+ &profile_mismatching_email));
+ EXPECT_FALSE(CompareContactDetails(options, &profile_truth,
+ &profile_mismatching_phone));
}
TEST(UserDataUtilTest, CompareContactDetailsMatchesForUnqueriedFields) {
- auto profile_truth = std::make_unique<autofill::AutofillProfile>();
- autofill::test::SetProfileInfo(profile_truth.get(), "Adam", "", "West",
+ autofill::AutofillProfile profile_truth;
+ autofill::test::SetProfileInfo(&profile_truth, "Adam", "", "West",
"adam.west@gmail.com", "", "", "", "", "", "",
"", "+41");
- auto profile_mismatching_name = std::make_unique<autofill::AutofillProfile>();
- autofill::test::SetProfileInfo(profile_mismatching_name.get(), "Berta", "",
- "West", "adam.west@gmail.com", "", "", "", "",
- "", "", "", "+41");
+ autofill::AutofillProfile profile_mismatching_name;
+ autofill::test::SetProfileInfo(&profile_mismatching_name, "Berta", "", "West",
+ "adam.west@gmail.com", "", "", "", "", "", "",
+ "", "+41");
- auto profile_mismatching_email =
- std::make_unique<autofill::AutofillProfile>();
- autofill::test::SetProfileInfo(profile_mismatching_email.get(), "Adam", "",
- "West", "berta.west@gmail.com", "", "", "", "",
- "", "", "", "+41");
+ autofill::AutofillProfile profile_mismatching_email;
+ autofill::test::SetProfileInfo(&profile_mismatching_email, "Adam", "", "West",
+ "berta.west@gmail.com", "", "", "", "", "", "",
+ "", "+41");
- auto profile_mismatching_phone =
- std::make_unique<autofill::AutofillProfile>();
- autofill::test::SetProfileInfo(profile_mismatching_phone.get(), "Adam", "",
- "West", "adam.west@gmail.com", "", "", "", "",
- "", "", "", "+44");
+ autofill::AutofillProfile profile_mismatching_phone;
+ autofill::test::SetProfileInfo(&profile_mismatching_phone, "Adam", "", "West",
+ "adam.west@gmail.com", "", "", "", "", "", "",
+ "", "+44");
CollectUserDataOptions options_no_check_name;
options_no_check_name.request_payer_email = true;
@@ -481,12 +476,199 @@ TEST(UserDataUtilTest, CompareContactDetailsMatchesForUnqueriedFields) {
options_no_check_phone.request_payer_name = true;
options_no_check_phone.request_payer_email = true;
- EXPECT_TRUE(CompareContactDetails(options_no_check_name, profile_truth.get(),
- profile_mismatching_name.get()));
- EXPECT_TRUE(CompareContactDetails(options_no_check_email, profile_truth.get(),
- profile_mismatching_email.get()));
- EXPECT_TRUE(CompareContactDetails(options_no_check_phone, profile_truth.get(),
- profile_mismatching_phone.get()));
+ EXPECT_TRUE(CompareContactDetails(options_no_check_name, &profile_truth,
+ &profile_mismatching_name));
+ EXPECT_TRUE(CompareContactDetails(options_no_check_email, &profile_truth,
+ &profile_mismatching_email));
+ EXPECT_TRUE(CompareContactDetails(options_no_check_phone, &profile_truth,
+ &profile_mismatching_phone));
+}
+
+TEST(UserDataUtilTest, ContactCompletenessNotRequired) {
+ CollectUserDataOptions not_required_options;
+ not_required_options.request_payer_name = false;
+ not_required_options.request_payer_email = false;
+ not_required_options.request_payer_phone = false;
+
+ EXPECT_TRUE(IsCompleteContact(nullptr, not_required_options));
+}
+
+TEST(UserDataUtilTest, ContactCompletenessRequireName) {
+ autofill::AutofillProfile contact;
+ CollectUserDataOptions require_name_options;
+ require_name_options.request_payer_name = true;
+
+ EXPECT_FALSE(IsCompleteContact(nullptr, require_name_options));
+ autofill::test::SetProfileInfo(&contact, /* first_name= */ "",
+ /* middle_name= */ "",
+ /* last_name= */ "", "adam.west@gmail.com", "",
+ "", "", "", "", "", "", "+41");
+ EXPECT_FALSE(IsCompleteContact(&contact, require_name_options));
+ autofill::test::SetProfileInfo(&contact, "John", /* middle_name= */ "", "Doe",
+ "", "", "", "", "", "", "", "", "");
+ EXPECT_TRUE(IsCompleteContact(&contact, require_name_options));
+}
+
+TEST(UserDataUtilTest, ContactCompletenessRequireEmail) {
+ autofill::AutofillProfile contact;
+ CollectUserDataOptions require_email_options;
+ require_email_options.request_payer_email = true;
+
+ EXPECT_FALSE(IsCompleteContact(nullptr, require_email_options));
+ autofill::test::SetProfileInfo(&contact, "John", "", "Doe",
+ /* email= */ "", "", "", "", "", "", "", "",
+ "+41");
+ EXPECT_FALSE(IsCompleteContact(&contact, require_email_options));
+ autofill::test::SetProfileInfo(&contact, "John", "", "Doe",
+ "john.doe@gmail.com", "", "", "", "", "", "",
+ "", "+41");
+ EXPECT_TRUE(IsCompleteContact(&contact, require_email_options));
+}
+
+TEST(UserDataUtilTest, ContactCompletenessRequirePhone) {
+ autofill::AutofillProfile contact;
+ CollectUserDataOptions require_phone_options;
+ require_phone_options.request_payer_phone = true;
+
+ EXPECT_FALSE(IsCompleteContact(nullptr, require_phone_options));
+ autofill::test::SetProfileInfo(&contact, "John", "", "Doe",
+ "john.doe@gmail.com", "", "", "", "", "", "",
+ "",
+ /* phone= */ "");
+ EXPECT_FALSE(IsCompleteContact(&contact, require_phone_options));
+ autofill::test::SetProfileInfo(&contact, "", "", "", "", "", "", "", "", "",
+ "", "", "+41");
+ EXPECT_TRUE(IsCompleteContact(&contact, require_phone_options));
+}
+
+TEST(UserDataUtilTest, CompleteShippingAddressNotRequired) {
+ CollectUserDataOptions not_required_options;
+ not_required_options.request_shipping = false;
+
+ EXPECT_TRUE(IsCompleteShippingAddress(nullptr, not_required_options));
+}
+
+TEST(UserDataUtilTest, CompleteShippingAddressRequired) {
+ autofill::AutofillProfile address;
+ CollectUserDataOptions require_shipping_options;
+ require_shipping_options.request_shipping = true;
+
+ autofill::test::SetProfileInfo(&address, "John", "", "Doe",
+ "john.doe@gmail.com", "", /* address1= */ "",
+ /* address2= */ "", /* city= */ "",
+ /* state= */ "", /* zip_code= */ "",
+ /* country= */ "", "+41");
+ EXPECT_FALSE(IsCompleteShippingAddress(&address, require_shipping_options));
+ autofill::test::SetProfileInfo(&address, "John", "", "Doe",
+ /* email= */ "", "", "Brandschenkestrasse 110",
+ "", "Zurich", "Zurich", "8002", "CH",
+ /* phone= */ "");
+ EXPECT_TRUE(IsCompleteShippingAddress(&address, require_shipping_options));
+}
+
+TEST(UserDataUtilTest, CompleteCreditCardNotRequired) {
+ CollectUserDataOptions not_required_options;
+ not_required_options.request_payment_method = false;
+
+ EXPECT_TRUE(IsCompleteCreditCard(nullptr, nullptr, not_required_options));
+}
+
+TEST(UserDataUtilTest, CompleteCreditCardZipNotRequired) {
+ CollectUserDataOptions payment_options;
+ payment_options.request_payment_method = true;
+ payment_options.require_billing_postal_code = false;
+
+ autofill::AutofillProfile address;
+ autofill::CreditCard card;
+ autofill::test::SetCreditCardInfo(&card, "Adam West", "4111111111111111", "1",
+ "2050",
+ /* billing_address_id= */ "id");
+
+ EXPECT_FALSE(IsCompleteCreditCard(nullptr, nullptr, payment_options));
+ EXPECT_FALSE(IsCompleteCreditCard(&card, nullptr, payment_options));
+ EXPECT_FALSE(IsCompleteCreditCard(&card, &address, payment_options));
+ // UK addresses do not require a zip code, they are complete without it.
+ autofill::test::SetProfileInfo(&address, "John", "", "Doe",
+ /* email= */ "", "", "Baker Street 221b", "",
+ "London", /* state= */ "",
+ /* zipcode= */ "", "UK", /* phone= */ "");
+ EXPECT_TRUE(IsCompleteCreditCard(&card, &address, payment_options));
+ // CH addresses require a zip code to be complete. This check outranks the
+ // |require_billing_postal_code| flag.
+ autofill::test::SetProfileInfo(&address, "John", "", "Doe",
+ /* email= */ "", "", "Brandschenkestrasse 110",
+ "", "Zurich", "Zurich",
+ /* zipcode= */ "", "CH", /* phone= */ "");
+ EXPECT_FALSE(IsCompleteCreditCard(&card, &address, payment_options));
+}
+
+TEST(UserDataUtilTest, CompleteCreditCardZipRequired) {
+ CollectUserDataOptions payment_options;
+ payment_options.request_payment_method = true;
+ payment_options.require_billing_postal_code = true;
+
+ autofill::AutofillProfile address;
+ autofill::CreditCard card;
+ autofill::test::SetCreditCardInfo(&card, "Adam West", "4111111111111111", "1",
+ "2050",
+ /* billing_address_id= */ "id");
+
+ EXPECT_FALSE(IsCompleteCreditCard(nullptr, nullptr, payment_options));
+ EXPECT_FALSE(IsCompleteCreditCard(&card, nullptr, payment_options));
+ EXPECT_FALSE(IsCompleteCreditCard(&card, &address, payment_options));
+ autofill::test::SetProfileInfo(&address, "John", "", "Doe",
+ /* email= */ "", "", "Baker Street 221b", "",
+ "London", /* state= */ "",
+ /* zipcode= */ "", "UK", /* phone= */ "");
+ EXPECT_FALSE(IsCompleteCreditCard(&card, &address, payment_options));
+ autofill::test::SetProfileInfo(&address, "John", "", "Doe",
+ /* email= */ "", "", "Baker Street 221b", "",
+ "London", /* state= */ "", "WC2N 5DU", "UK",
+ /* phone= */ "");
+ EXPECT_TRUE(IsCompleteCreditCard(&card, &address, payment_options));
+}
+
+TEST(UserDataUtilTest, CompleteExpiredCreditCard) {
+ CollectUserDataOptions payment_options;
+ payment_options.request_payment_method = true;
+
+ autofill::AutofillProfile address;
+ autofill::test::SetProfileInfo(
+ &address, "John", "", "Doe", "john.doe@gmail.com", "",
+ "Brandschenkestrasse 110", "", "Zurich", "Zurich", "8002", "CH", "+41");
+ autofill::CreditCard card;
+
+ autofill::test::SetCreditCardInfo(&card, "Adam West", "4111111111111111", "1",
+ "2000",
+ /* billing_address_id= */ "id");
+ EXPECT_FALSE(IsCompleteCreditCard(&card, &address, payment_options));
+ autofill::test::SetCreditCardInfo(&card, "Adam West", "4111111111111111", "1",
+ "2050",
+ /* billing_address_id= */ "id");
+ EXPECT_TRUE(IsCompleteCreditCard(&card, &address, payment_options));
+}
+
+TEST(UserDataUtilTest, CompleteCreditCardWithBadNetwork) {
+ autofill::AutofillProfile address;
+ autofill::test::SetProfileInfo(
+ &address, "John", "", "Doe", "john.doe@gmail.com", "",
+ "Brandschenkestrasse 110", "", "Zurich", "Zurich", "8002", "CH", "+41");
+ autofill::CreditCard card;
+ autofill::test::SetCreditCardInfo(&card, "Adam West", "4111111111111111", "1",
+ "2050",
+ /* billing_address_id= */ "id");
+
+ CollectUserDataOptions payment_options_mastercard;
+ payment_options_mastercard.request_payment_method = true;
+ payment_options_mastercard.supported_basic_card_networks.emplace_back(
+ "mastercard");
+ EXPECT_FALSE(
+ IsCompleteCreditCard(&card, &address, payment_options_mastercard));
+
+ CollectUserDataOptions payment_options_visa;
+ payment_options_visa.request_payment_method = true;
+ payment_options_visa.supported_basic_card_networks.emplace_back("visa");
+ EXPECT_TRUE(IsCompleteCreditCard(&card, &address, payment_options_visa));
}
} // namespace
diff --git a/chromium/components/autofill_assistant/browser/user_model.cc b/chromium/components/autofill_assistant/browser/user_model.cc
index 2130602b4e1..020f1b27a7b 100644
--- a/chromium/components/autofill_assistant/browser/user_model.cc
+++ b/chromium/components/autofill_assistant/browser/user_model.cc
@@ -3,7 +3,9 @@
// found in the LICENSE file.
#include "components/autofill_assistant/browser/user_model.h"
+#include "components/autofill_assistant/browser/field_formatter.h"
+#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "third_party/re2/src/re2/re2.h"
@@ -55,9 +57,17 @@ base::WeakPtr<UserModel> UserModel::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
-void UserModel::SetValue(const std::string& identifier,
+void UserModel::SetValue(const std::string& input,
const ValueProto& value,
bool force_notification) {
+ // Replace all placeholders in the input.
+ auto formatted_identifier =
+ field_formatter::FormatString(input, identifier_placeholders_);
+ if (!formatted_identifier.has_value()) {
+ VLOG(2) << "Error setting value: placeholder not found for " << input;
+ return;
+ }
+ std::string identifier = *formatted_identifier;
auto result = values_.emplace(identifier, value);
if (!force_notification && !result.second && result.first->second == value &&
value.is_client_side_only() ==
@@ -72,8 +82,14 @@ void UserModel::SetValue(const std::string& identifier,
}
}
-base::Optional<ValueProto> UserModel::GetValue(
- const std::string& identifier) const {
+base::Optional<ValueProto> UserModel::GetValue(const std::string& input) const {
+ // Replace all placeholders in the input.
+ auto formatted_identifier =
+ field_formatter::FormatString(input, identifier_placeholders_);
+ if (!formatted_identifier.has_value()) {
+ return base::nullopt;
+ }
+ std::string identifier = *formatted_identifier;
auto it = values_.find(identifier);
if (it != values_.end()) {
return it->second;
@@ -145,6 +161,20 @@ void UserModel::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
+void UserModel::AddIdentifierPlaceholders(
+ const std::map<std::string, std::string> placeholders) {
+ for (const auto& placeholder : placeholders) {
+ identifier_placeholders_[placeholder.first] = placeholder.second;
+ }
+}
+
+void UserModel::RemoveIdentifierPlaceholders(
+ const std::map<std::string, std::string> placeholders) {
+ for (const auto& placeholder : placeholders) {
+ identifier_placeholders_.erase(placeholder.first);
+ }
+}
+
void UserModel::SetAutofillCreditCards(
std::unique_ptr<std::vector<std::unique_ptr<autofill::CreditCard>>>
credit_cards) {
diff --git a/chromium/components/autofill_assistant/browser/user_model.h b/chromium/components/autofill_assistant/browser/user_model.h
index e0b4189a42e..266dee6b7d7 100644
--- a/chromium/components/autofill_assistant/browser/user_model.h
+++ b/chromium/components/autofill_assistant/browser/user_model.h
@@ -48,8 +48,10 @@ class UserModel {
bool force_notification = false);
// Returns the value for |identifier| or nullopt if there is no such value.
- // Also supports the array operator to retrieve a specific element of a list,
- // e.g., "identifier[0]" to get the first item.
+ // - Placeholders in |identifier| of the form ${key} are automatically
+ // replaced (see |AddIdentifierPlaceholders|).
+ // - Also supports the array operator to retrieve
+ // a specific element of a list, e.g., "identifier[0]" to get the first item.
base::Optional<ValueProto> GetValue(const std::string& identifier) const;
// Returns the value for |reference| or nullopt if there is no such value.
@@ -72,6 +74,17 @@ class UserModel {
return values;
}
+ // Adds a set of placeholders (overwrite if necessary). When looking up values
+ // by identifier, all occurrences of ${key} are automatically replaced by
+ // their value. Example: the current set of placeholders contains "i" -> "1".
+ // Looking up the value "value[${i}]" will now actually lookup "value[1]".
+ void AddIdentifierPlaceholders(
+ const std::map<std::string, std::string> placeholders);
+
+ // Removes a set of placeholders.
+ void RemoveIdentifierPlaceholders(
+ const std::map<std::string, std::string> placeholders);
+
// Replaces the set of available autofill credit cards.
void SetAutofillCreditCards(
std::unique_ptr<std::vector<std::unique_ptr<autofill::CreditCard>>>
@@ -105,6 +118,7 @@ class UserModel {
friend class UserModelTest;
std::map<std::string, ValueProto> values_;
+ std::map<std::string, std::string> identifier_placeholders_;
std::map<std::string, std::unique_ptr<autofill::CreditCard>> credit_cards_;
std::map<std::string, std::unique_ptr<autofill::AutofillProfile>> profiles_;
base::ObserverList<Observer> observers_;
diff --git a/chromium/components/autofill_assistant/browser/user_model_unittest.cc b/chromium/components/autofill_assistant/browser/user_model_unittest.cc
index 799d606e023..a1d772965bc 100644
--- a/chromium/components/autofill_assistant/browser/user_model_unittest.cc
+++ b/chromium/components/autofill_assistant/browser/user_model_unittest.cc
@@ -366,4 +366,75 @@ TEST_F(UserModelTest, ClientSideOnlyNotifications) {
EXPECT_TRUE(GetValues().at("identifier").is_client_side_only());
}
+TEST_F(UserModelTest, GetValueWithPlaceholders) {
+ ValueProto value;
+ value.mutable_strings()->add_values("a");
+ value.mutable_strings()->add_values("b");
+ value.mutable_strings()->add_values("c");
+ model_.SetValue("multi_value", value);
+ model_.SetValue("single_value_0", SimpleValue(std::string("d")));
+ model_.SetValue("single_value_1", SimpleValue(std::string("e")));
+ model_.SetValue("single_value_2", SimpleValue(std::string("f")));
+
+ EXPECT_EQ(model_.GetValue("multi_value[${i}]"), base::nullopt);
+ EXPECT_EQ(model_.GetValue("single_value_i"), base::nullopt);
+ model_.AddIdentifierPlaceholders({{"i", "0"}});
+ EXPECT_EQ(model_.GetValue("multi_value[${i}]"),
+ SimpleValue(std::string("a")));
+ EXPECT_EQ(model_.GetValue("single_value_${i}"),
+ SimpleValue(std::string("d")));
+
+ // Add placeholder.
+ model_.AddIdentifierPlaceholders({{"j", "1"}});
+ EXPECT_EQ(model_.GetValue("multi_value[${j}]"),
+ SimpleValue(std::string("b")));
+ EXPECT_EQ(model_.GetValue("single_value_${j}"),
+ SimpleValue(std::string("e")));
+ EXPECT_EQ(model_.GetValue("single_value_${j}[${i}]"),
+ SimpleValue(std::string("e")));
+
+ // Overwrite placeholder.
+ model_.AddIdentifierPlaceholders({{"i", "2"}});
+ EXPECT_EQ(model_.GetValue("multi_value[${i}]"),
+ SimpleValue(std::string("c")));
+ EXPECT_EQ(model_.GetValue("single_value_${i}"),
+ SimpleValue(std::string("f")));
+ EXPECT_EQ(model_.GetValue("single_value_${j}[${i}]"), base::nullopt);
+ // Remove placeholder (the value does not matter, it's just about the key).
+ model_.RemoveIdentifierPlaceholders({{"i", "123"}});
+ EXPECT_EQ(model_.GetValue("multi_value[${i}]"), base::nullopt);
+ EXPECT_EQ(model_.GetValue("single_value_${i}"), base::nullopt);
+ EXPECT_EQ(model_.GetValue("single_value_${j}"),
+ SimpleValue(std::string("e")));
+}
+
+TEST_F(UserModelTest, SetValueWithPlaceholders) {
+ ValueProto value;
+ value.mutable_strings()->add_values("a");
+ value.mutable_strings()->add_values("b");
+ value.mutable_strings()->add_values("c");
+ model_.SetValue("value_${i}", value);
+ EXPECT_EQ(model_.GetValue("value_${i}"), base::nullopt);
+
+ model_.AddIdentifierPlaceholders({{"i", "0"}});
+ model_.SetValue("value_${i}", value);
+ EXPECT_EQ(model_.GetValue("value_0"), value);
+ EXPECT_EQ(model_.GetValue("value_${i}"), value);
+
+ model_.RemoveIdentifierPlaceholders({{"i", "0"}});
+ EXPECT_EQ(model_.GetValue("value_0"), value);
+ EXPECT_EQ(model_.GetValue("value_${i}"), base::nullopt);
+
+ model_.AddIdentifierPlaceholders({{"i", "0"}});
+ model_.AddIdentifierPlaceholders({{"j", "1"}});
+ model_.SetValue("value_${i}_${j}", value);
+ EXPECT_EQ(model_.GetValue("value_0_1"), value);
+ EXPECT_EQ(model_.GetValue("value_${i}_${j}"), value);
+
+ model_.RemoveIdentifierPlaceholders({{"j", "1"}});
+ EXPECT_EQ(model_.GetValue("value_${i}_${j}"), base::nullopt);
+ model_.SetValue("value_${i}", value);
+ EXPECT_EQ(model_.GetValue("value_${i}"), value);
+}
+
} // namespace autofill_assistant
diff --git a/chromium/components/autofill_assistant/browser/value_util.cc b/chromium/components/autofill_assistant/browser/value_util.cc
index 24b0175e166..453d6da6918 100644
--- a/chromium/components/autofill_assistant/browser/value_util.cc
+++ b/chromium/components/autofill_assistant/browser/value_util.cc
@@ -65,8 +65,8 @@ bool operator==(const ValueProto& value_a, const ValueProto& value_b) {
value_b.login_options().values();
case ValueProto::kCreditCardResponse:
return value_a.credit_card_response() == value_b.credit_card_response();
- case ValueProto::kLoginOptionResponse:
- return value_a.login_option_response() == value_b.login_option_response();
+ case ValueProto::kServerPayload:
+ return value_a.server_payload() == value_b.server_payload();
case ValueProto::KIND_NOT_SET:
return true;
}
@@ -100,7 +100,7 @@ bool operator<(const ValueProto& value_a, const ValueProto& value_b) {
case ValueProto::kProfiles:
case ValueProto::kLoginOptions:
case ValueProto::kCreditCardResponse:
- case ValueProto::kLoginOptionResponse:
+ case ValueProto::kServerPayload:
case ValueProto::KIND_NOT_SET:
NOTREACHED();
return false;
@@ -183,11 +183,6 @@ bool operator==(const CreditCardResponseProto& value_a,
return value_a.network() == value_b.network();
}
-bool operator==(const LoginOptionResponseProto& value_a,
- const LoginOptionResponseProto& value_b) {
- return value_a.payload() == value_b.payload();
-}
-
// Intended for debugging. Writes a string representation of |values| to |out|.
template <typename T>
std::ostream& WriteRepeatedField(std::ostream& out, const T& values) {
@@ -247,12 +242,6 @@ std::ostream& operator<<(std::ostream& out,
return out;
}
-std::ostream& operator<<(std::ostream& out,
- const LoginOptionResponseProto& value) {
- out << value.payload();
- return out;
-}
-
// Intended for debugging. Writes a string representation of |value| to |out|.
std::ostream& operator<<(std::ostream& out, const ValueProto& value) {
switch (value.kind_case()) {
@@ -283,8 +272,8 @@ std::ostream& operator<<(std::ostream& out, const ValueProto& value) {
case ValueProto::kCreditCardResponse:
out << value.credit_card_response();
break;
- case ValueProto::kLoginOptionResponse:
- out << value.login_option_response();
+ case ValueProto::kServerPayload:
+ out << value.server_payload();
break;
case ValueProto::KIND_NOT_SET:
break;
@@ -413,7 +402,7 @@ int GetValueSize(const ValueProto& value) {
return value.login_options().values().size();
case ValueProto::kCreditCardResponse:
return 1;
- case ValueProto::kLoginOptionResponse:
+ case ValueProto::kServerPayload:
return 1;
case ValueProto::KIND_NOT_SET:
return 0;
@@ -464,12 +453,10 @@ base::Optional<ValueProto> GetNthValue(const ValueProto& value, int index) {
return nth_value;
case ValueProto::kCreditCardResponse:
DCHECK(index == 0);
- nth_value = value;
- return nth_value;
- case ValueProto::kLoginOptionResponse:
+ return value;
+ case ValueProto::kServerPayload:
DCHECK(index == 0);
- nth_value = value;
- return nth_value;
+ return value;
case ValueProto::KIND_NOT_SET:
return base::nullopt;
}
diff --git a/chromium/components/autofill_assistant/browser/value_util.h b/chromium/components/autofill_assistant/browser/value_util.h
index 9d44a6c86a1..dae11fcae48 100644
--- a/chromium/components/autofill_assistant/browser/value_util.h
+++ b/chromium/components/autofill_assistant/browser/value_util.h
@@ -54,10 +54,6 @@ bool operator==(const LoginOptionProto& value_a,
bool operator==(const CreditCardResponseProto& value_a,
const CreditCardResponseProto& value_b);
-// Custom comparison operator for |LoginOptionResponseProto|.
-bool operator==(const LoginOptionResponseProto& value_a,
- const LoginOptionResponseProto& value_b);
-
// Intended for debugging.
std::ostream& operator<<(std::ostream& out, const ValueProto& value);
std::ostream& operator<<(std::ostream& out,
@@ -72,8 +68,6 @@ std::ostream& operator<<(std::ostream& out, const AutofillProfileProto& value);
std::ostream& operator<<(std::ostream& out, const LoginOptionProto& value);
std::ostream& operator<<(std::ostream& out,
const CreditCardResponseProto& value);
-std::ostream& operator<<(std::ostream& out,
- const LoginOptionResponseProto& value);
// Convenience constructors.
ValueProto SimpleValue(bool value, bool is_client_side_only = false);
diff --git a/chromium/components/autofill_assistant/browser/value_util_unittest.cc b/chromium/components/autofill_assistant/browser/value_util_unittest.cc
index b33a2d23fd6..996b2561743 100644
--- a/chromium/components/autofill_assistant/browser/value_util_unittest.cc
+++ b/chromium/components/autofill_assistant/browser/value_util_unittest.cc
@@ -59,23 +59,20 @@ TEST_F(ValueUtilTest, DifferentTypesComparison) {
ValueProto value_c = CreateIntValue();
ValueProto value_d = CreateBoolValue();
ValueProto value_e = SimpleValue(CreateDateProto(2020, 8, 30));
-
- EXPECT_FALSE(value_a == value_b);
- EXPECT_FALSE(value_a == value_c);
- EXPECT_FALSE(value_a == value_d);
- EXPECT_FALSE(value_a == value_e);
- EXPECT_FALSE(value_b == value_c);
- EXPECT_FALSE(value_b == value_d);
- EXPECT_FALSE(value_b == value_e);
- EXPECT_FALSE(value_c == value_d);
- EXPECT_FALSE(value_c == value_e);
- EXPECT_FALSE(value_d == value_e);
-
- EXPECT_TRUE(value_a == value_a);
- EXPECT_TRUE(value_b == value_b);
- EXPECT_TRUE(value_c == value_c);
- EXPECT_TRUE(value_d == value_d);
- EXPECT_TRUE(value_e == value_e);
+ ValueProto value_f;
+ value_f.set_server_payload("payload");
+
+ std::vector<ValueProto> values = {value_a, value_b, value_c,
+ value_d, value_e, value_f};
+ for (size_t i = 0; i < values.size(); ++i) {
+ for (size_t j = 0; j < values.size(); ++j) {
+ if (j == i) {
+ EXPECT_TRUE(values[i] == values[j]);
+ continue;
+ }
+ EXPECT_FALSE(values[i] == values[j]);
+ }
+ }
}
TEST_F(ValueUtilTest, EmptyValueComparison) {
@@ -222,13 +219,13 @@ TEST_F(ValueUtilTest, CreditCardResponseComparison) {
EXPECT_FALSE(value_a == value_b);
}
-TEST_F(ValueUtilTest, LoginOptionResponseComparison) {
+TEST_F(ValueUtilTest, ServerPayloadComparison) {
ValueProto value_a;
- value_a.mutable_login_option_response()->set_payload("payload");
+ value_a.set_server_payload("payload");
ValueProto value_b = value_a;
EXPECT_TRUE(value_a == value_b);
- value_b.mutable_login_option_response()->set_payload("different");
+ value_b.set_server_payload("different");
EXPECT_FALSE(value_a == value_b);
}
diff --git a/chromium/components/autofill_assistant/browser/view_layout.proto b/chromium/components/autofill_assistant/browser/view_layout.proto
index 28ac4d5090e..fd0f6a10f8b 100644
--- a/chromium/components/autofill_assistant/browser/view_layout.proto
+++ b/chromium/components/autofill_assistant/browser/view_layout.proto
@@ -74,6 +74,23 @@ message BitmapDrawableProto {
// A drawable for use in backgrounds or in image views.
message DrawableProto {
+ enum Icon {
+ DRAWABLE_ICON_UNDEFINED = 0;
+ PROGRESSBAR_DEFAULT_INITIAL_STEP = 1;
+ PROGRESSBAR_DEFAULT_DATA_COLLECTION = 2;
+ PROGRESSBAR_DEFAULT_PAYMENT = 3;
+ PROGRESSBAR_DEFAULT_FINAL_STEP = 4;
+ SITTING_PERSON = 5;
+ TICKET_STUB = 6;
+ SHOPPING_BASKET = 7;
+ FAST_FOOD = 8;
+ LOCAL_DINING = 9;
+ COGWHEEL = 10;
+ KEY = 11;
+ CAR = 12;
+ GROCERY = 13;
+ }
+
oneof drawable {
// The resource identifier of a drawable.
string resource_identifier = 1;
@@ -81,6 +98,10 @@ message DrawableProto {
BitmapDrawableProto bitmap = 2;
// A shape, e.g., a rounded rectangle.
ShapeDrawableProto shape = 3;
+ // An icon from a predefined set of known icons.
+ Icon icon = 4;
+ // A Base64 encoded image string.
+ bytes base64 = 5;
}
}
diff --git a/chromium/components/autofill_assistant/browser/web/element_finder.cc b/chromium/components/autofill_assistant/browser/web/element_finder.cc
index 3cff6bc1b18..add03320a31 100644
--- a/chromium/components/autofill_assistant/browser/web/element_finder.cc
+++ b/chromium/components/autofill_assistant/browser/web/element_finder.cc
@@ -14,52 +14,10 @@ namespace autofill_assistant {
namespace {
// Javascript code to get document root element.
-const char* const kGetDocumentElement =
- R"(
- (function() {
- return document.documentElement;
- }())
- )";
-
-// Javascript code to query an elements for a selector, either the first
-// (non-strict) or a single (strict) element.
-//
-// Returns undefined if no elements are found, TOO_MANY_ELEMENTS (18) if too
-// many elements were found and strict mode is enabled.
-const char* const kQuerySelector =
- R"(function (selector, strictMode) {
- var found = this.querySelectorAll(selector);
- if(found.length == 0) return undefined;
- if(found.length > 1 && strictMode) return 18;
- return found[0];
- })";
-
-// Javascript code to query a visible elements for a selector, either the first
-// (non-strict) or a single (strict) visible element.q
-//
-// Returns undefined if no elements are found, TOO_MANY_ELEMENTS (18) if too
-// many elements were found and strict mode is enabled.
-const char* const kQuerySelectorWithConditions =
- R"(function (selector, strict, visible, inner_text_str, value_str) {
- var found = this.querySelectorAll(selector);
- var found_index = -1;
- var inner_text_re = inner_text_str ? RegExp(inner_text_str) : undefined;
- var value_re = value_str ? RegExp(value_str) : undefined;
- var match = function(e) {
- if (visible && e.getClientRects().length == 0) return false;
- if (inner_text_re && !inner_text_re.test(e.innerText)) return false;
- if (value_re && !value_re.test(e.value)) return false;
- return true;
- };
- for (let i = 0; i < found.length; i++) {
- if (match(found[i])) {
- if (found_index != -1) return 18;
- found_index = i;
- if (!strict) break;
- }
- }
- return found_index == -1 ? undefined : found[found_index];
- })";
+const char kGetDocumentElement[] =
+ "(function() { return document.documentElement; }())";
+
+const char kGetArrayElement[] = "function(index) { return this[index]; }";
bool ConvertPseudoType(const PseudoType pseudo_type,
dom::PseudoType* pseudo_type_output) {
@@ -116,25 +74,175 @@ bool ConvertPseudoType(const PseudoType pseudo_type,
}
} // namespace
+ElementFinder::JsFilterBuilder::JsFilterBuilder() = default;
+ElementFinder::JsFilterBuilder::~JsFilterBuilder() = default;
+
+std::vector<std::unique_ptr<runtime::CallArgument>>
+ElementFinder::JsFilterBuilder::BuildArgumentList() const {
+ auto str_array_arg = std::make_unique<base::Value>(base::Value::Type::LIST);
+ for (const std::string& str : arguments_) {
+ str_array_arg->Append(str);
+ }
+ std::vector<std::unique_ptr<runtime::CallArgument>> arguments;
+ arguments.emplace_back(runtime::CallArgument::Builder()
+ .SetValue(std::move(str_array_arg))
+ .Build());
+ return arguments;
+}
+
+// clang-format off
+std::string ElementFinder::JsFilterBuilder::BuildFunction() const {
+ return base::StrCat({
+ R"(
+ function(args) {
+ let elements = [this];
+ )",
+ base::JoinString(lines_, "\n"),
+ R"(
+ if (elements.length == 0) return null;
+ if (elements.length == 1) { return elements[0] }
+ return elements;
+ })"
+ });
+}
+// clang-format on
+
+bool ElementFinder::JsFilterBuilder::AddFilter(
+ const SelectorProto::Filter& filter) {
+ switch (filter.filter_case()) {
+ case SelectorProto::Filter::kCssSelector:
+ // clang-format off
+ AddLine({
+ "elements = elements.flatMap((e) => Array.from(e.querySelectorAll(",
+ AddArgument(filter.css_selector()),
+ ")));"
+ });
+
+ // Elements are temporarily put into a set to get rid of duplicates, which
+ // are likely when using inner text before CSS selector filters. We must
+ // not return duplicates as they cause incorrect TOO_MANY_ELEMENTS errors.
+ AddLine(R"(if (elements.length > 1) {
+ elements = Array.from(new Set(elements));
+ })");
+ // clang-format on
+ return true;
+
+ case SelectorProto::Filter::kInnerText:
+ AddRegexpFilter(filter.inner_text(), "innerText");
+ return true;
+
+ case SelectorProto::Filter::kValue:
+ AddRegexpFilter(filter.value(), "value");
+ return true;
+
+ case SelectorProto::Filter::kBoundingBox:
+ AddLine(
+ "elements = elements.filter((e) => e.getClientRects().length > 0);");
+ return true;
+
+ case SelectorProto::Filter::kPseudoElementContent: {
+ // When a content is set, window.getComputedStyle().content contains a
+ // double-quoted string with the content, unquoted here by JSON.parse().
+ std::string re_var =
+ AddRegexpInstance(filter.pseudo_element_content().content());
+ std::string pseudo_type =
+ PseudoTypeName(filter.pseudo_element_content().pseudo_type());
+
+ AddLine("elements = elements.filter((e) => {");
+ AddLine({" const s = window.getComputedStyle(e, '", pseudo_type, "');"});
+ AddLine(" if (!s || !s.content || !s.content.startsWith('\"')) {");
+ AddLine(" return false;");
+ AddLine(" }");
+ AddLine({" return ", re_var, ".test(JSON.parse(s.content));"});
+ AddLine("});");
+ return true;
+ }
+
+ case SelectorProto::Filter::kLabelled:
+ AddLine(R"(elements = elements.flatMap((e) => {
+ if (e.tagName != 'LABEL') return [];
+ let element = null;
+ const id = e.getAttribute('for');
+ if (id) {
+ element = document.getElementById(id)
+ }
+ if (!element) {
+ element = e.querySelector(
+ 'button,input,keygen,meter,output,progress,select,textarea');
+ }
+ if (element) return [element];
+ return [];
+});
+)");
+ // The selector above for the case where there's no "for" corresponds to
+ // the list of labelable elements listed on "W3C's HTML5: Edition for Web
+ // Authors":
+ // https://www.w3.org/TR/2011/WD-html5-author-20110809/forms.html#category-label
+ return true;
+
+ case SelectorProto::Filter::kEnterFrame:
+ case SelectorProto::Filter::kPseudoType:
+ case SelectorProto::Filter::kPickOne:
+ case SelectorProto::Filter::kClosest:
+ case SelectorProto::Filter::FILTER_NOT_SET:
+ return false;
+ }
+}
+
+std::string ElementFinder::JsFilterBuilder::AddRegexpInstance(
+ const SelectorProto::TextFilter& filter) {
+ std::string re_flags = filter.case_sensitive() ? "" : "i";
+ std::string re_var = DeclareVariable();
+ AddLine({"const ", re_var, " = RegExp(", AddArgument(filter.re2()), ", '",
+ re_flags, "');"});
+ return re_var;
+}
+
+void ElementFinder::JsFilterBuilder::AddRegexpFilter(
+ const SelectorProto::TextFilter& filter,
+ const std::string& property) {
+ std::string re_var = AddRegexpInstance(filter);
+ AddLine({"elements = elements.filter((e) => ", re_var, ".test(e.", property,
+ "));"});
+}
+
+std::string ElementFinder::JsFilterBuilder::DeclareVariable() {
+ return base::StrCat({"v", base::NumberToString(variable_counter_++)});
+}
+
+std::string ElementFinder::JsFilterBuilder::AddArgument(
+ const std::string& value) {
+ int index = arguments_.size();
+ arguments_.emplace_back(value);
+ return base::StrCat({"args[", base::NumberToString(index), "]"});
+}
+
ElementFinder::Result::Result() = default;
ElementFinder::Result::~Result() = default;
-ElementFinder::Result::Result(const Result& to_copy) = default;
+ElementFinder::Result::Result(const Result&) = default;
ElementFinder::ElementFinder(content::WebContents* web_contents,
DevtoolsClient* devtools_client,
const Selector& selector,
- bool strict)
+ ResultType result_type)
: web_contents_(web_contents),
devtools_client_(devtools_client),
selector_(selector),
- strict_(strict),
- element_result_(std::make_unique<Result>()) {}
+ result_type_(result_type) {}
ElementFinder::~ElementFinder() = default;
void ElementFinder::Start(Callback callback) {
+ StartInternal(std::move(callback), web_contents_->GetMainFrame(),
+ /* frame_id= */ "", /* document_object_id= */ "");
+}
+
+void ElementFinder::StartInternal(Callback callback,
+ content::RenderFrameHost* frame,
+ const std::string& frame_id,
+ const std::string& document_object_id) {
callback_ = std::move(callback);
if (selector_.empty()) {
@@ -142,22 +250,287 @@ void ElementFinder::Start(Callback callback) {
return;
}
- element_result_->container_frame_selector_index = 0;
- element_result_->container_frame_host = web_contents_->GetMainFrame();
- devtools_client_->GetRuntime()->Evaluate(
- std::string(kGetDocumentElement), /* node_frame_id= */ std::string(),
- base::BindOnce(&ElementFinder::OnGetDocumentElement,
- weak_ptr_factory_.GetWeakPtr(), 0));
+ current_frame_ = frame;
+ current_frame_id_ = frame_id;
+ current_frame_root_ = document_object_id;
+ if (current_frame_root_.empty()) {
+ GetDocumentElement();
+ } else {
+ current_matches_.emplace_back(current_frame_root_);
+ ExecuteNextTask();
+ }
}
void ElementFinder::SendResult(const ClientStatus& status) {
- DCHECK(callback_);
- DCHECK(element_result_);
- std::move(callback_).Run(status, std::move(element_result_));
+ if (!callback_)
+ return;
+
+ std::move(callback_).Run(status, std::make_unique<Result>());
+}
+
+void ElementFinder::SendSuccessResult(const std::string& object_id) {
+ if (!callback_)
+ return;
+
+ // Fill in result and return
+ std::unique_ptr<Result> result =
+ std::make_unique<Result>(BuildResult(object_id));
+ result->frame_stack = frame_stack_;
+ std::move(callback_).Run(OkClientStatus(), std::move(result));
+}
+
+ElementFinder::Result ElementFinder::BuildResult(const std::string& object_id) {
+ Result result;
+ result.container_frame_host = current_frame_;
+ result.object_id = object_id;
+ result.node_frame_id = current_frame_id_;
+ return result;
+}
+
+void ElementFinder::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 (!ConsumeAnyMatchOrFail(object_id)) {
+ return;
+ }
+ break;
+
+ case ResultType::kMatchArray:
+ if (!ConsumeMatchArrayOrFail(object_id)) {
+ return;
+ }
+ break;
+ }
+ SendSuccessResult(object_id);
+ return;
+ }
+
+ 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::kPickOne: {
+ std::string object_id;
+ if (!ConsumeAnyMatchOrFail(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::kBoundingBox:
+ case SelectorProto::Filter::kPseudoElementContent:
+ case SelectorProto::Filter::kLabelled: {
+ 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::kClosest: {
+ std::string array_object_id;
+ if (!ConsumeMatchArrayOrFail(array_object_id))
+ return;
+
+ ApplyProximityFilter(next_filter_index_++, array_object_id);
+ return;
+ }
+
+ case SelectorProto::Filter::FILTER_NOT_SET:
+ VLOG(1) << __func__ << " Unset or unknown filter in " << filter << " in "
+ << selector_;
+ SendResult(ClientStatus(INVALID_SELECTOR));
+ return;
+ }
+}
+
+bool ElementFinder::ConsumeOneMatchOrFail(std::string& object_id_out) {
+ // This logic relies on JsFilterBuilder::BuildFunction guaranteeing that
+ // arrays contain at least 2 elements to avoid having to fetch all matching
+ // elements in the common case where we just want to know whether there is at
+ // least one match.
+
+ if (!current_match_arrays_.empty()) {
+ VLOG(1) << __func__ << " Got " << current_match_arrays_.size()
+ << " arrays of 2 or more matches for " << selector_
+ << ", when only 1 match was expected.";
+ SendResult(ClientStatus(TOO_MANY_ELEMENTS));
+ return false;
+ }
+ if (current_matches_.size() > 1) {
+ VLOG(1) << __func__ << " Got " << current_matches_.size() << " matches for "
+ << selector_ << ", when only 1 was expected.";
+ SendResult(ClientStatus(TOO_MANY_ELEMENTS));
+ return false;
+ }
+ if (current_matches_.empty()) {
+ SendResult(ClientStatus(ELEMENT_RESOLUTION_FAILED));
+ return false;
+ }
+
+ object_id_out = current_matches_[0];
+ current_matches_.clear();
+ return true;
+}
+
+bool ElementFinder::ConsumeAnyMatchOrFail(std::string& object_id_out) {
+ // This logic relies on ApplyJsFilters guaranteeing that arrays contain at
+ // least 2 elements to avoid having to fetch all matching elements in the
+ // common case where we just want one match.
+
+ if (current_matches_.size() > 0) {
+ object_id_out = current_matches_[0];
+ current_matches_.clear();
+ current_match_arrays_.clear();
+ return true;
+ }
+ if (!current_match_arrays_.empty()) {
+ std::string array_object_id = current_match_arrays_[0];
+ current_match_arrays_.clear();
+ ResolveMatchArrays({array_object_id}, /* max_count= */ 1);
+ return false; // Caller should call again to check
+ }
+ SendResult(ClientStatus(ELEMENT_RESOLUTION_FAILED));
+ return false;
+}
+
+bool ElementFinder::ConsumeAllMatchesOrFail(
+ std::vector<std::string>& matches_out) {
+ if (!current_match_arrays_.empty()) {
+ std::vector<std::string> array_object_ids =
+ std::move(current_match_arrays_);
+ ResolveMatchArrays(array_object_ids, /* max_count= */ -1);
+ return false; // Caller should call again to check
+ }
+ if (!current_matches_.empty()) {
+ matches_out = std::move(current_matches_);
+ current_matches_.clear();
+ return true;
+ }
+ SendResult(ClientStatus(ELEMENT_RESOLUTION_FAILED));
+ return false;
+}
+
+bool ElementFinder::ConsumeMatchArrayOrFail(std::string& array_object_id) {
+ if (current_matches_.empty() && current_match_arrays_.empty()) {
+ SendResult(ClientStatus(ELEMENT_RESOLUTION_FAILED));
+ return false;
+ }
+
+ if (current_matches_.empty() && current_match_arrays_.size() == 1) {
+ array_object_id = current_match_arrays_[0];
+ current_match_arrays_.clear();
+ return true;
+ }
+
+ std::vector<std::unique_ptr<runtime::CallArgument>> arguments;
+ std::string object_id; // Will be "this" in Javascript.
+ std::string function;
+ if (current_match_arrays_.size() > 1) {
+ object_id = current_match_arrays_.back();
+ current_match_arrays_.pop_back();
+ // Merge both arrays into current_match_arrays_[0]
+ function = "function(dest) { dest.push(...this); }";
+ AddRuntimeCallArgumentObjectId(current_match_arrays_[0], &arguments);
+ } else if (!current_matches_.empty()) {
+ object_id = current_matches_.back();
+ current_matches_.pop_back();
+ if (current_match_arrays_.empty()) {
+ // 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_match_arrays_[0], &arguments);
+ }
+ }
+ devtools_client_->GetRuntime()->CallFunctionOn(
+ runtime::CallFunctionOnParams::Builder()
+ .SetObjectId(object_id)
+ .SetArguments(std::move(arguments))
+ .SetFunctionDeclaration(function)
+ .Build(),
+ current_frame_id_,
+ base::BindOnce(&ElementFinder::OnConsumeMatchArray,
+ weak_ptr_factory_.GetWeakPtr()));
+ return false;
+}
+
+void ElementFinder::OnConsumeMatchArray(
+ 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_;
+ SendResult(status);
+ return;
+ }
+ if (current_match_arrays_.empty()) {
+ std::string returned_object_id;
+ if (SafeGetObjectId(result->GetResult(), &returned_object_id)) {
+ current_match_arrays_.push_back(returned_object_id);
+ }
+ }
+ ExecuteNextTask();
+}
+
+void ElementFinder::GetDocumentElement() {
+ devtools_client_->GetRuntime()->Evaluate(
+ std::string(kGetDocumentElement), current_frame_id_,
+ base::BindOnce(&ElementFinder::OnGetDocumentElement,
+ weak_ptr_factory_.GetWeakPtr()));
}
void ElementFinder::OnGetDocumentElement(
- size_t index,
const DevtoolsClient::ReplyStatus& reply_status,
std::unique_ptr<runtime::EvaluateResult> result) {
ClientStatus status =
@@ -174,46 +547,32 @@ void ElementFinder::OnGetDocumentElement(
return;
}
- RecursiveFindElement(object_id, index);
+ current_frame_root_ = object_id;
+ // Use the node as root for the rest of the evaluation.
+ current_matches_.emplace_back(object_id);
+
+ DecrementResponseCountAndContinue();
}
-void ElementFinder::RecursiveFindElement(const std::string& object_id,
- size_t index) {
- std::vector<std::unique_ptr<runtime::CallArgument>> arguments;
- AddRuntimeCallArgument(selector_.selectors[index], &arguments);
- // For finding intermediate elements, strict mode would be more appropriate,
- // as long as the logic does not support more than one intermediate match.
- //
- // TODO(b/129387787): first, add logging to figure out whether it matters and
- // decide between strict mode and full support for multiple matching
- // intermeditate elements.
- AddRuntimeCallArgument(strict_, &arguments);
- std::string function;
- if (index == (selector_.selectors.size() - 1)) {
- if (selector_.must_be_visible || !selector_.inner_text_pattern.empty() ||
- !selector_.value_pattern.empty()) {
- function.assign(kQuerySelectorWithConditions);
- AddRuntimeCallArgument(selector_.must_be_visible, &arguments);
- AddRuntimeCallArgument(selector_.inner_text_pattern, &arguments);
- AddRuntimeCallArgument(selector_.value_pattern, &arguments);
- }
- }
- if (function.empty()) {
- function.assign(kQuerySelector);
+void ElementFinder::ApplyJsFilters(const JsFilterBuilder& builder,
+ const std::vector<std::string>& object_ids) {
+ DCHECK(!object_ids.empty()); // Guaranteed by ExecuteNextTask()
+ pending_response_count_ = object_ids.size();
+ std::string function = builder.BuildFunction();
+ for (const std::string& object_id : object_ids) {
+ devtools_client_->GetRuntime()->CallFunctionOn(
+ runtime::CallFunctionOnParams::Builder()
+ .SetObjectId(object_id)
+ .SetArguments(builder.BuildArgumentList())
+ .SetFunctionDeclaration(function)
+ .Build(),
+ current_frame_id_,
+ base::BindOnce(&ElementFinder::OnApplyJsFilters,
+ weak_ptr_factory_.GetWeakPtr()));
}
- devtools_client_->GetRuntime()->CallFunctionOn(
- runtime::CallFunctionOnParams::Builder()
- .SetObjectId(object_id)
- .SetArguments(std::move(arguments))
- .SetFunctionDeclaration(function)
- .Build(),
- element_result_->node_frame_id,
- base::BindOnce(&ElementFinder::OnQuerySelectorAll,
- weak_ptr_factory_.GetWeakPtr(), index));
}
-void ElementFinder::OnQuerySelectorAll(
- size_t index,
+void ElementFinder::OnApplyJsFilters(
const DevtoolsClient::ReplyStatus& reply_status,
std::unique_ptr<runtime::CallFunctionOnResult> result) {
if (!result) {
@@ -221,63 +580,55 @@ void ElementFinder::OnQuerySelectorAll(
// 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 selector "
- << index << " of " << selector_;
+ VLOG(1) << __func__ << ": Context doesn't exist yet to query frame "
+ << frame_stack_.size() << " of " << selector_;
SendResult(ClientStatus(ELEMENT_RESOLUTION_FAILED));
return;
}
ClientStatus status =
CheckJavaScriptResult(reply_status, result.get(), __FILE__, __LINE__);
if (!status.ok()) {
- VLOG(1) << __func__ << ": Failed to query selector " << index << " of "
- << selector_;
+ VLOG(1) << __func__ << ": Failed to query selector for frame "
+ << frame_stack_.size() << " of " << selector_ << ": " << status;
SendResult(status);
return;
}
- int int_result;
- if (SafeGetIntValue(result->GetResult(), &int_result)) {
- DCHECK(int_result == TOO_MANY_ELEMENTS);
- SendResult(ClientStatus(TOO_MANY_ELEMENTS));
- return;
- }
- std::string object_id;
- if (!SafeGetObjectId(result->GetResult(), &object_id)) {
- SendResult(ClientStatus(ELEMENT_RESOLUTION_FAILED));
- return;
- }
- if (selector_.selectors.size() == index + 1) {
- // The pseudo type is associated to the final element matched by
- // |selector_|, which means that we currently don't handle matching an
- // element inside a pseudo element.
- if (selector_.pseudo_type == PseudoType::UNDEFINED) {
- // Return object id of the element.
- element_result_->object_id = object_id;
- SendResult(OkClientStatus());
- return;
+ // The result can be empty (nothing found), a an array (multiple matches
+ // found) or a single node.
+ std::string object_id;
+ if (SafeGetObjectId(result->GetResult(), &object_id)) {
+ if (result->GetResult()->HasSubtype() &&
+ result->GetResult()->GetSubtype() ==
+ runtime::RemoteObjectSubtype::ARRAY) {
+ current_match_arrays_.emplace_back(object_id);
+ } else {
+ current_matches_.emplace_back(object_id);
}
+ }
+ DecrementResponseCountAndContinue();
+}
- // We are looking for a pseudo element associated with this element.
- dom::PseudoType pseudo_type;
- if (!ConvertPseudoType(selector_.pseudo_type, &pseudo_type)) {
- // Return empty result.
- SendResult(ClientStatus(INVALID_ACTION));
- return;
- }
+void ElementFinder::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);
+ SendResult(ClientStatus(INVALID_ACTION));
+ return;
+ }
+ DCHECK(!object_ids.empty()); // Guaranteed by ExecuteNextTask()
+ pending_response_count_ = object_ids.size();
+ for (const std::string& object_id : object_ids) {
devtools_client_->GetDOM()->DescribeNode(
dom::DescribeNodeParams::Builder().SetObjectId(object_id).Build(),
- element_result_->node_frame_id,
+ current_frame_id_,
base::BindOnce(&ElementFinder::OnDescribeNodeForPseudoElement,
weak_ptr_factory_.GetWeakPtr(), pseudo_type));
- return;
}
-
- devtools_client_->GetDOM()->DescribeNode(
- dom::DescribeNodeParams::Builder().SetObjectId(object_id).Build(),
- element_result_->node_frame_id,
- base::BindOnce(&ElementFinder::OnDescribeNode,
- weak_ptr_factory_.GetWeakPtr(), object_id, index));
}
void ElementFinder::OnDescribeNodeForPseudoElement(
@@ -299,30 +650,35 @@ void ElementFinder::OnDescribeNodeForPseudoElement(
dom::ResolveNodeParams::Builder()
.SetBackendNodeId(pseudo_element->GetBackendNodeId())
.Build(),
- element_result_->node_frame_id,
+ current_frame_id_,
base::BindOnce(&ElementFinder::OnResolveNodeForPseudoElement,
weak_ptr_factory_.GetWeakPtr()));
return;
}
}
}
-
- // Failed to find the pseudo element: run the callback with empty result.
- SendResult(ClientStatus(ELEMENT_RESOLUTION_FAILED));
+ DecrementResponseCountAndContinue();
}
void ElementFinder::OnResolveNodeForPseudoElement(
const DevtoolsClient::ReplyStatus& reply_status,
std::unique_ptr<dom::ResolveNodeResult> result) {
if (result && result->GetObject() && result->GetObject()->HasObjectId()) {
- element_result_->object_id = result->GetObject()->GetObjectId();
+ current_matches_.emplace_back(result->GetObject()->GetObjectId());
}
- SendResult(OkClientStatus());
+ DecrementResponseCountAndContinue();
+}
+
+void ElementFinder::EnterFrame(const std::string& object_id) {
+ devtools_client_->GetDOM()->DescribeNode(
+ dom::DescribeNodeParams::Builder().SetObjectId(object_id).Build(),
+ current_frame_id_,
+ base::BindOnce(&ElementFinder::OnDescribeNodeForFrame,
+ weak_ptr_factory_.GetWeakPtr(), object_id));
}
-void ElementFinder::OnDescribeNode(
+void ElementFinder::OnDescribeNodeForFrame(
const std::string& object_id,
- size_t index,
const DevtoolsClient::ReplyStatus& reply_status,
std::unique_ptr<dom::DescribeNodeResult> result) {
if (!result || !result->GetNode()) {
@@ -337,44 +693,26 @@ void ElementFinder::OnDescribeNode(
if (node->GetNodeName() == "IFRAME") {
DCHECK(node->HasFrameId()); // Ensure all frames have an id.
- element_result_->container_frame_selector_index = index;
- element_result_->container_frame_host =
- FindCorrespondingRenderFrameHost(node->GetFrameId());
-
- Result result_frame;
- result_frame.container_frame_selector_index =
- element_result_->container_frame_selector_index;
- result_frame.container_frame_host = element_result_->container_frame_host;
- result_frame.object_id = object_id;
- element_result_->frame_stack.emplace_back(result_frame);
+ frame_stack_.push_back(BuildResult(object_id));
- if (!element_result_->container_frame_host) {
+ auto* frame = FindCorrespondingRenderFrameHost(node->GetFrameId());
+ if (!frame) {
VLOG(1) << __func__ << " Failed to find corresponding owner frame.";
SendResult(ClientStatus(FRAME_HOST_NOT_FOUND));
return;
}
+ current_frame_ = frame;
+ current_frame_root_.clear();
if (node->HasContentDocument()) {
- // If the frame has a ContentDocument it's considered a local frame. We
- // don't need to assign the frame id, since devtools can just send the
- // commands to the main session.
-
+ // 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 {
- // If the frame has no ContentDocument, it's considered an
- // OutOfProcessIFrame.
- // See https://www.chromium.org/developers/design-documents/oop-iframes
- // for full documentation.
- // With the iFrame running in a different process it is necessary to pass
- // the correct session id from devtools. We need to set the frame id,
- // such that devtools can resolve the corresponding session id.
- element_result_->node_frame_id = node->GetFrameId();
-
+ current_frame_id_ = node->GetFrameId();
// Kick off another find element chain to walk down the OOP iFrame.
- devtools_client_->GetRuntime()->Evaluate(
- std::string(kGetDocumentElement), element_result_->node_frame_id,
- base::BindOnce(&ElementFinder::OnGetDocumentElement,
- weak_ptr_factory_.GetWeakPtr(), index + 1));
+ GetDocumentElement();
return;
}
}
@@ -390,17 +728,20 @@ void ElementFinder::OnDescribeNode(
dom::ResolveNodeParams::Builder()
.SetBackendNodeId(backend_ids[0])
.Build(),
- element_result_->node_frame_id,
+ current_frame_id_,
base::BindOnce(&ElementFinder::OnResolveNode,
- weak_ptr_factory_.GetWeakPtr(), index));
+ weak_ptr_factory_.GetWeakPtr()));
return;
}
- RecursiveFindElement(object_id, index + 1);
+ // 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);
+ DecrementResponseCountAndContinue();
}
void ElementFinder::OnResolveNode(
- size_t index,
const DevtoolsClient::ReplyStatus& reply_status,
std::unique_ptr<dom::ResolveNodeResult> result) {
if (!result || !result->GetObject() || !result->GetObject()->HasObjectId()) {
@@ -409,7 +750,13 @@ void ElementFinder::OnResolveNode(
return;
}
- RecursiveFindElement(result->GetObject()->GetObjectId(), ++index);
+ std::string object_id = result->GetObject()->GetObjectId();
+ if (current_frame_root_.empty()) {
+ current_frame_root_ = object_id;
+ }
+ // Use the node as root for the rest of the evaluation.
+ current_matches_.emplace_back(object_id);
+ DecrementResponseCountAndContinue();
}
content::RenderFrameHost* ElementFinder::FindCorrespondingRenderFrameHost(
@@ -423,4 +770,236 @@ content::RenderFrameHost* ElementFinder::FindCorrespondingRenderFrameHost(
return nullptr;
}
+void ElementFinder::ApplyProximityFilter(int filter_index,
+ const std::string& array_object_id) {
+ Selector target_selector;
+ target_selector.proto.mutable_filters()->MergeFrom(
+ selector_.proto.filters(filter_index).closest().target());
+ proximity_target_filter_ =
+ std::make_unique<ElementFinder>(web_contents_, devtools_client_,
+ target_selector, ResultType::kMatchArray);
+ proximity_target_filter_->StartInternal(
+ base::BindOnce(&ElementFinder::OnProximityFilterTarget,
+ weak_ptr_factory_.GetWeakPtr(), filter_index,
+ array_object_id),
+ current_frame_, current_frame_id_, current_frame_root_);
+}
+
+void ElementFinder::OnProximityFilterTarget(int filter_index,
+ const std::string& array_object_id,
+ const ClientStatus& status,
+ std::unique_ptr<Result> result) {
+ if (!status.ok()) {
+ VLOG(1) << __func__
+ << " Could not find proximity filter target for resolving "
+ << selector_.proto.filters(filter_index);
+ SendResult(status);
+ return;
+ }
+ if (result->container_frame_host != current_frame_) {
+ VLOG(1) << __func__ << " Cannot compare elements on different frames.";
+ SendResult(ClientStatus(INVALID_SELECTOR));
+ return;
+ }
+
+ const auto& filter = selector_.proto.filters(filter_index).closest();
+
+ std::string function = R"(function(targets, maxPairs) {
+ const candidates = this;
+ const pairs = candidates.length * targets.length;
+ if (pairs > maxPairs) {
+ return pairs;
+ }
+ const candidateBoxes = candidates.map((e) => e.getBoundingClientRect());
+ let closest = null;
+ let shortestDistance = Number.POSITIVE_INFINITY;
+ for (target of targets) {
+ const targetBox = target.getBoundingClientRect();
+ for (let i = 0; i < candidates.length; i++) {
+ const box = candidateBoxes[i];
+)";
+
+ if (filter.in_alignment()) {
+ // Rejects candidates that are not on the same row or or the same column as
+ // the target.
+ function.append("if ((box.bottom <= targetBox.top || ");
+ function.append(" box.top >= targetBox.bottom) && ");
+ function.append(" (box.right <= targetBox.left || ");
+ function.append(" box.left >= targetBox.right)) continue;");
+ }
+ switch (filter.relative_position()) {
+ case SelectorProto::ProximityFilter::UNSPECIFIED_POSITION:
+ // No constraints.
+ break;
+
+ case SelectorProto::ProximityFilter::ABOVE:
+ // Candidate must be above target
+ function.append("if (box.bottom > targetBox.top) continue;");
+ break;
+
+ case SelectorProto::ProximityFilter::BELOW:
+ // Candidate must be below target
+ function.append("if (box.top < targetBox.bottom) continue;");
+ break;
+
+ case SelectorProto::ProximityFilter::LEFT:
+ // Candidate must be left of target
+ function.append("if (box.right > targetBox.left) continue;");
+ break;
+
+ case SelectorProto::ProximityFilter::RIGHT:
+ // Candidate must be right of target
+ function.append("if (box.left < targetBox.right) continue;");
+ break;
+ }
+
+ // The algorithm below computes distance to the closest border. If the
+ // distance is 0, then we have got our closest element and can stop there.
+ function.append(R"(
+ let w = 0;
+ if (targetBox.right < box.left) {
+ w = box.left - targetBox.right;
+ } else if (box.right < targetBox.left) {
+ w = targetBox.left - box.right;
+ }
+ let h = 0;
+ if (targetBox.bottom < box.top) {
+ h = box.top - targetBox.bottom;
+ } else if (box.bottom < targetBox.top) {
+ h = targetBox.top - box.bottom;
+ }
+ const dist = Math.sqrt(h * h + w * w);
+ if (dist == 0) return candidates[i];
+ if (dist < shortestDistance) {
+ closest = candidates[i];
+ shortestDistance = dist;
+ }
+ }
+ }
+ return closest;
+})");
+
+ std::vector<std::unique_ptr<runtime::CallArgument>> arguments;
+ AddRuntimeCallArgumentObjectId(result->object_id, &arguments);
+ AddRuntimeCallArgument(filter.max_pairs(), &arguments);
+
+ devtools_client_->GetRuntime()->CallFunctionOn(
+ runtime::CallFunctionOnParams::Builder()
+ .SetObjectId(array_object_id)
+ .SetArguments(std::move(arguments))
+ .SetFunctionDeclaration(function)
+ .Build(),
+ current_frame_id_,
+ base::BindOnce(&ElementFinder::OnProximityFilterJs,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void ElementFinder::OnProximityFilterJs(
+ 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 execute proximity filter " << status;
+ SendResult(status);
+ return;
+ }
+
+ std::string object_id;
+ if (SafeGetObjectId(result->GetResult(), &object_id)) {
+ // Function found a match.
+ current_matches_.push_back(object_id);
+ ExecuteNextTask();
+ return;
+ }
+
+ int pair_count = 0;
+ if (SafeGetIntValue(result->GetResult(), &pair_count)) {
+ // Function got too many pairs to check.
+ VLOG(1) << __func__ << ": Too many pairs to consider for proximity checks: "
+ << pair_count;
+ SendResult(ClientStatus(TOO_MANY_CANDIDATES));
+ return;
+ }
+
+ // Function found nothing, which is possible if the relative position
+ // constraints forced the algorithm to discard all candidates.
+ ExecuteNextTask();
+}
+
+void ElementFinder::ResolveMatchArrays(
+ const std::vector<std::string>& array_object_ids,
+ int max_count) {
+ if (array_object_ids.empty()) {
+ // Nothing to do
+ ExecuteNextTask();
+ return;
+ }
+ pending_response_count_ = array_object_ids.size();
+ for (const std::string& array_object_id : array_object_ids) {
+ ResolveMatchArrayRecursive(array_object_id, 0, max_count);
+ }
+}
+
+void ElementFinder::ResolveMatchArrayRecursive(
+ const std::string& array_object_id,
+ int index,
+ int max_count) {
+ 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(&ElementFinder::OnResolveMatchArray,
+ weak_ptr_factory_.GetWeakPtr(), array_object_id, index,
+ max_count));
+}
+
+void ElementFinder::OnResolveMatchArray(
+ const std::string& array_object_id,
+ int index,
+ int max_count,
+ 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_;
+ SendResult(status);
+ return;
+ }
+ std::string object_id;
+ if (!SafeGetObjectId(result->GetResult(), &object_id)) {
+ // We've reached the end of the array
+ DecrementResponseCountAndContinue();
+ return;
+ }
+
+ current_matches_.emplace_back(object_id);
+ int next_index = index + 1;
+ if (max_count != -1 && next_index >= max_count) {
+ DecrementResponseCountAndContinue();
+ return;
+ }
+
+ // Fetch the next element.
+ ResolveMatchArrayRecursive(array_object_id, next_index, max_count);
+}
+
+void ElementFinder::DecrementResponseCountAndContinue() {
+ if (pending_response_count_ > 1) {
+ pending_response_count_--;
+ return;
+ }
+
+ pending_response_count_ = 0;
+ ExecuteNextTask();
+ return;
+}
+
} // 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 d868e274d12..a783f77945d 100644
--- a/chromium/components/autofill_assistant/browser/web/element_finder.h
+++ b/chromium/components/autofill_assistant/browser/web/element_finder.h
@@ -11,6 +11,7 @@
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
+#include "base/strings/strcat.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"
@@ -29,24 +30,37 @@ class DevtoolsClient;
// Worker class to find element(s) matching a selector.
class ElementFinder : public WebControllerWorker {
public:
+ enum ResultType {
+ // Result.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,
+
+ // Result.object_id contains the object ID of one of the nodes that matched.
+ // If there are no matches, status is ELEMENT_RESOLUTION_FAILED.
+ kAnyMatch,
+
+ // Result.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,
+ };
+
struct Result {
Result();
~Result();
- Result(const Result& to_copy);
+ Result(const Result&);
// The render frame host contains the element.
- content::RenderFrameHost* container_frame_host;
-
- // The selector index in the given selectors corresponding to the container
- // frame. Zero indicates the element is in main frame or the first element
- // is the container frame selector. Compare main frame with the above
- // |container_frame_host| to distinguish them.
- size_t container_frame_selector_index;
+ content::RenderFrameHost* container_frame_host = nullptr;
// The object id of the element.
std::string object_id;
- // The id of the frame the element's node is in.
+ // 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 node_frame_id;
std::vector<Result> frame_stack;
@@ -54,10 +68,10 @@ class ElementFinder : public WebControllerWorker {
// |web_contents| and |devtools_client| must be valid for the lifetime of the
// instance.
- ElementFinder(content::WebContents* web_contents_,
+ ElementFinder(content::WebContents* web_contents,
DevtoolsClient* devtools_client,
const Selector& selector,
- bool strict);
+ ResultType result_type);
~ElementFinder() override;
using Callback =
@@ -67,15 +81,150 @@ class ElementFinder : public WebControllerWorker {
void Start(Callback callback_);
private:
+ // Helper for building JavaScript functions.
+ //
+ // TODO(b/155264465): extract this into a top-level class in its own file, so
+ // it can be tested.
+ class JsFilterBuilder {
+ public:
+ JsFilterBuilder();
+ ~JsFilterBuilder();
+
+ // Builds the argument list for the function.
+ std::vector<std::unique_ptr<runtime::CallArgument>> BuildArgumentList()
+ const;
+
+ // Return the JavaScript function.
+ std::string BuildFunction() const;
+
+ // Adds a filter, if possible.
+ bool AddFilter(const SelectorProto::Filter& filter);
+
+ private:
+ std::vector<std::string> arguments_;
+ std::vector<std::string> lines_;
+
+ // A number that's increased by each call to DeclareVariable() to make sure
+ // we generate unique variables.
+ int variable_counter_ = 0;
+
+ // Adds a regexp filter.
+ void AddRegexpFilter(const SelectorProto::TextFilter& filter,
+ const std::string& property);
+
+ // Declares and initializes a variable containing a RegExp object that
+ // correspond to |filter| and returns the variable name.
+ std::string AddRegexpInstance(const SelectorProto::TextFilter& filter);
+
+ // Returns the name of a new unique variable.
+ std::string DeclareVariable();
+
+ // Adds an argument to the argument list and returns its JavaScript
+ // representation.
+ //
+ // This allows passing strings to the JavaScript code without having to
+ // hardcode and escape them - this helps avoid XSS issues.
+ std::string AddArgument(const std::string& value);
+
+ // Adds a line of JavaScript code to the function, between the header and
+ // footer. At that point, the variable "elements" contains the current set
+ // of matches, as an array of nodes. It should be updated to contain the new
+ // set of matches.
+ void AddLine(const std::string& line) { lines_.emplace_back(line); }
+
+ void AddLine(const std::vector<std::string>& line) {
+ lines_.emplace_back(base::StrCat(line));
+ }
+ };
+
+ // Finds the element, starting at |frame| and calls |callback|.
+ //
+ // |document_object_id| might be empty, in which case we first look for the
+ // frame's document.
+ void StartInternal(Callback callback,
+ content::RenderFrameHost* frame,
+ const std::string& frame_id,
+ const std::string& document_object_id);
+
+ // Sends a result with the given status and no data.
void SendResult(const ClientStatus& status);
- void OnGetDocumentElement(size_t index,
- const DevtoolsClient::ReplyStatus& reply_status,
- std::unique_ptr<runtime::EvaluateResult> result);
- void RecursiveFindElement(const std::string& object_id, size_t index);
- void OnQuerySelectorAll(
- size_t index,
+
+ // Builds a result from the current state of the finder and returns it.
+ void SendSuccessResult(const std::string& object_id);
+
+ // Report |object_id| as result in |result| and initialize the frame-related
+ // fields of |result| from the current state. Leaves the frame stack empty.
+ Result BuildResult(const std::string& object_id);
+
+ // 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
+ // DecrementResponseCountAndContinue().
+ void ExecuteNextTask();
+
+ // 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 one match, take one and put it in
+ // |object_id_out|, then return true.
+ //
+ // 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 ConsumeAnyMatchOrFail(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() return false. If there are
+ // matches, but they're not in a single array, move the element into the array
+ // in the background and return false. ExecuteNextTask() is called again once
+ // the background tasks have executed.
+ 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.
+ 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(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,
const DevtoolsClient::ReplyStatus& reply_status,
@@ -83,24 +232,107 @@ class ElementFinder : public WebControllerWorker {
void OnResolveNodeForPseudoElement(
const DevtoolsClient::ReplyStatus& reply_status,
std::unique_ptr<dom::ResolveNodeResult> result);
- void OnDescribeNode(const std::string& object_id,
- size_t index,
- const DevtoolsClient::ReplyStatus& reply_status,
- std::unique_ptr<dom::DescribeNodeResult> result);
- void OnResolveNode(size_t index,
- 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);
content::RenderFrameHost* FindCorrespondingRenderFrameHost(
std::string frame_id);
+ // Handle TaskType::PROXIMITY
+ void ApplyProximityFilter(int filter_index,
+ const std::string& array_object_id);
+ void OnProximityFilterTarget(int filter_index,
+ const std::string& array_object_id,
+ const ClientStatus& status,
+ std::unique_ptr<Result> result);
+ void OnProximityFilterJs(
+ const DevtoolsClient::ReplyStatus& reply_status,
+ std::unique_ptr<runtime::CallFunctionOnResult> result);
+
+ // Get elements from |array_object_ids|, and put the result into
+ // |element_matches_|.
+ //
+ // This calls ExecuteNextTask() once all the elements of all the arrays are in
+ // |element_matches_|. If |max_count| is -1, fetch until the end of the array,
+ // otherwise fetch |max_count| elements at most in each array.
+ void ResolveMatchArrays(const std::vector<std::string>& array_object_ids,
+ int max_count);
+
+ // ResolveMatchArrayRecursive calls itself recursively, incrementing |index|,
+ // as long as there are elements. The chain of calls end with
+ // DecrementResponseCountAndContinue() as there can be more than one such
+ // chains executing at a time.
+ void ResolveMatchArrayRecursive(const std::string& array_object_ids,
+ int index,
+ int max_count);
+
+ void OnResolveMatchArray(
+ const std::string& array_object_id,
+ int index,
+ int max_count,
+ const DevtoolsClient::ReplyStatus& reply_status,
+ std::unique_ptr<runtime::CallFunctionOnResult> result);
+
+ // Tracks pending_response_count_ and call ExecuteNextTask() once the count
+ // has reached 0.
+ void DecrementResponseCountAndContinue();
+
content::WebContents* const web_contents_;
DevtoolsClient* const devtools_client_;
const Selector selector_;
-
- const bool strict_;
+ const ResultType result_type_;
Callback callback_;
- std::unique_ptr<Result> element_result_;
+
+ // The index of the next filter to process, in selector_.proto.filters.
+ int next_filter_index_ = 0;
+
+ // Pointer to the current frame
+ 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 ID of the root of |current_frame_|.
+ std::string current_frame_root_;
+
+ // Object IDs of the current set matching elements. Cleared once it's used to
+ // query or filter.
+ //
+ // More matches can be found in |current_match_arrays_|. Use one of the
+ // Consume*Match() function to current matches.
+ std::vector<std::string> current_matches_;
+
+ // Object ID of arrays of at least 2 matching elements.
+ //
+ // More matches can be found in |current_matches_|. Use one of the
+ // Consume*Match() function to current matches.
+ std::vector<std::string> current_match_arrays_;
+
+ // True if current_matches are pseudo-elements.
+ bool matching_pseudo_elements_ = false;
+
+ // Number of responses still pending.
+ //
+ // Before starting several background operations in parallel, set this counter
+ // to the number of operations and make sure that
+ // DecrementResponseCountAndContinue() is called once the result of the
+ // operation has been processed and the state of ElementFinder updated.
+ // DecrementResponseCountAndContinue() will then make sure to call
+ // ExecuteNextTask() again once this counter has reached 0 to continue the
+ // work.
+ size_t pending_response_count_ = 0;
+
+ std::vector<Result> frame_stack_;
+
+ // Finder for the target of the current proximity filter.
+ std::unique_ptr<ElementFinder> proximity_target_filter_;
base::WeakPtrFactory<ElementFinder> weak_ptr_factory_{this};
};
diff --git a/chromium/components/autofill_assistant/browser/web/element_position_getter.cc b/chromium/components/autofill_assistant/browser/web/element_position_getter.cc
index b154eb3292f..b1321964135 100644
--- a/chromium/components/autofill_assistant/browser/web/element_position_getter.cc
+++ b/chromium/components/autofill_assistant/browser/web/element_position_getter.cc
@@ -4,7 +4,6 @@
#include "components/autofill_assistant/browser/web/element_position_getter.h"
-#include "base/task/post_task.h"
#include "components/autofill_assistant/browser/devtools/devtools_client.h"
#include "components/autofill_assistant/browser/service.pb.h"
#include "components/autofill_assistant/browser/web/web_controller_util.h"
@@ -134,8 +133,8 @@ void ElementPositionGetter::OnGetBoxModelForStableCheck(
}
--remaining_rounds_;
- base::PostDelayedTask(
- FROM_HERE, {content::BrowserThread::UI},
+ content::GetUIThreadTaskRunner({})->PostDelayedTask(
+ FROM_HERE,
base::BindOnce(&ElementPositionGetter::GetAndWaitBoxModelStable,
weak_ptr_factory_.GetWeakPtr()),
check_interval_);
@@ -153,8 +152,8 @@ void ElementPositionGetter::OnScrollIntoView(
}
--remaining_rounds_;
- base::PostDelayedTask(
- FROM_HERE, {content::BrowserThread::UI},
+ content::GetUIThreadTaskRunner({})->PostDelayedTask(
+ FROM_HERE,
base::BindOnce(&ElementPositionGetter::GetAndWaitBoxModelStable,
weak_ptr_factory_.GetWeakPtr()),
check_interval_);
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 544e949a802..e9154125be4 100644
--- a/chromium/components/autofill_assistant/browser/web/element_rect_getter.cc
+++ b/chromium/components/autofill_assistant/browser/web/element_rect_getter.cc
@@ -5,6 +5,7 @@
#include "components/autofill_assistant/browser/web/element_rect_getter.h"
#include "base/callback.h"
+#include "base/logging.h"
#include "base/values.h"
#include "components/autofill_assistant/browser/devtools/devtools/domains/types_runtime.h"
#include "components/autofill_assistant/browser/devtools/devtools_client.h"
diff --git a/chromium/components/autofill_assistant/browser/web/web_controller.cc b/chromium/components/autofill_assistant/browser/web/web_controller.cc
index 28ca0272cce..a6b1d74a4ab 100644
--- a/chromium/components/autofill_assistant/browser/web/web_controller.cc
+++ b/chromium/components/autofill_assistant/browser/web/web_controller.cc
@@ -17,7 +17,6 @@
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
-#include "base/task/post_task.h"
#include "build/build_config.h"
#include "components/autofill/content/browser/content_autofill_driver.h"
#include "components/autofill/core/browser/autofill_manager.h"
@@ -645,7 +644,9 @@ void WebController::FindElement(const Selector& selector,
bool strict_mode,
ElementFinder::Callback callback) {
auto finder = std::make_unique<ElementFinder>(
- web_contents_, devtools_client_.get(), selector, strict_mode);
+ web_contents_, devtools_client_.get(), selector,
+ strict_mode ? ElementFinder::ResultType::kExactlyOneMatch
+ : ElementFinder::ResultType::kAnyMatch);
auto* ptr = finder.get();
pending_workers_.emplace_back(std::move(finder));
ptr->Start(base::BindOnce(&WebController::OnFindElementResult,
@@ -774,12 +775,16 @@ void WebController::OnFindElementForFillingForm(
FillAutofillErrorStatus(UnexpectedErrorStatus(__FILE__, __LINE__)));
return;
}
- DCHECK(!selector.empty());
- // TODO(crbug.com/806868): Figure out whether there are cases where we need
- // more than one selector, and come up with a solution that can figure out the
- // right number of selectors to include.
+
+ base::Optional<std::string> css_selector =
+ selector.ExtractSingleCssSelectorForAutofill();
+ if (!css_selector) {
+ std::move(callback).Run(ClientStatus(INVALID_SELECTOR));
+ return;
+ }
+
driver->GetAutofillAgent()->GetElementFormAndFieldData(
- std::vector<std::string>(1, selector.selectors.back()),
+ {*css_selector},
base::BindOnce(&WebController::OnGetFormAndFieldDataForFillingForm,
weak_ptr_factory_.GetWeakPtr(),
std::move(data_to_autofill), std::move(callback),
@@ -858,9 +863,16 @@ void WebController::OnFindElementToRetrieveFormAndFieldData(
autofill::FormData(), autofill::FormFieldData());
return;
}
- DCHECK(!selector.empty());
+ base::Optional<std::string> css_selector =
+ selector.ExtractSingleCssSelectorForAutofill();
+ if (!css_selector) {
+ std::move(callback).Run(ClientStatus(INVALID_SELECTOR),
+ autofill::FormData(), autofill::FormFieldData());
+ return;
+ }
+
driver->GetAutofillAgent()->GetElementFormAndFieldData(
- std::vector<std::string>(1, selector.selectors.back()),
+ {*css_selector},
base::BindOnce(&WebController::OnGetFormAndFieldDataForRetrieving,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
@@ -1189,8 +1201,8 @@ void WebController::DispatchKeyboardTextDownEvent(
}
if (delay && delay_in_millisecond > 0) {
- base::PostDelayedTask(
- FROM_HERE, {content::BrowserThread::UI},
+ content::GetUIThreadTaskRunner({})->PostDelayedTask(
+ FROM_HERE,
base::BindOnce(
&WebController::DispatchKeyboardTextDownEvent,
weak_ptr_factory_.GetWeakPtr(), node_frame_id, codepoints, index,
@@ -1642,8 +1654,8 @@ void WebController::OnWaitForDocumentToBecomeInteractive(
return;
}
- base::PostDelayedTask(
- FROM_HERE, {content::BrowserThread::UI},
+ content::GetUIThreadTaskRunner({})->PostDelayedTask(
+ FROM_HERE,
base::BindOnce(&WebController::WaitForDocumentToBecomeInteractive,
weak_ptr_factory_.GetWeakPtr(), --remaining_rounds,
object_id, node_frame_id, std::move(callback)),
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 c03df182c13..d132e4b4d21 100644
--- a/chromium/components/autofill_assistant/browser/web/web_controller_browsertest.cc
+++ b/chromium/components/autofill_assistant/browser/web/web_controller_browsertest.cc
@@ -129,7 +129,8 @@ class WebControllerBrowserTest : public content::ContentBrowserTest,
size_t* pending_number_of_checks_output,
bool expected_result,
const ClientStatus& result) {
- EXPECT_EQ(expected_result, result.ok()) << "selector: " << selector;
+ EXPECT_EQ(expected_result, result.ok())
+ << "selector: " << selector << " status: " << result;
*pending_number_of_checks_output -= 1;
if (*pending_number_of_checks_output == 0) {
std::move(done_callback).Run();
@@ -301,14 +302,13 @@ class WebControllerBrowserTest : public content::ContentBrowserTest,
}
void FindElementAndCheck(const Selector& selector,
- size_t expected_index,
bool is_main_frame) {
SCOPED_TRACE(::testing::Message() << selector << " strict");
ClientStatus status;
ElementFinder::Result result;
FindElement(selector, &status, &result);
EXPECT_EQ(ACTION_APPLIED, status.proto_status());
- CheckFindElementResult(result, expected_index, is_main_frame);
+ CheckFindElementResult(result, is_main_frame);
}
void FindElementExpectEmptyResult(const Selector& selector) {
@@ -321,16 +321,16 @@ class WebControllerBrowserTest : public content::ContentBrowserTest,
}
void CheckFindElementResult(const ElementFinder::Result& result,
- size_t expected_index,
bool is_main_frame) {
if (is_main_frame) {
EXPECT_EQ(shell()->web_contents()->GetMainFrame(),
result.container_frame_host);
+ EXPECT_EQ(result.frame_stack.size(), 0u);
} else {
EXPECT_NE(shell()->web_contents()->GetMainFrame(),
result.container_frame_host);
+ EXPECT_GE(result.frame_stack.size(), 1u);
}
- EXPECT_EQ(result.container_frame_selector_index, expected_index);
EXPECT_FALSE(result.object_id.empty());
}
@@ -486,8 +486,7 @@ class WebControllerBrowserTest : public content::ContentBrowserTest,
// the desired y position.
void TestScrollIntoView(int initial_window_scroll_y,
int initial_container_scroll_y) {
- Selector selector;
- selector.selectors.emplace_back("#scroll_item_5");
+ Selector selector({"#scroll_item_5"});
SetupScrollContainerHeights();
ScrollWindowTo(initial_window_scroll_y);
@@ -528,11 +527,11 @@ class WebControllerBrowserTest : public content::ContentBrowserTest,
protected:
std::unique_ptr<WebController> web_controller_;
+ ClientSettings settings_;
private:
std::unique_ptr<net::EmbeddedTestServer> http_server_;
std::unique_ptr<net::EmbeddedTestServer> http_server_iframe_;
- ClientSettings settings_;
DISALLOW_COPY_AND_ASSIGN(WebControllerBrowserTest);
};
@@ -546,29 +545,42 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ElementExistenceCheck) {
// A nonexistent element.
RunLaxElementCheck(Selector({"#doesnotexist"}), false);
+}
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, PseudoElementChecks) {
// A pseudo-element
- RunLaxElementCheck(Selector({"#terms-and-conditions"}, BEFORE), true);
+ RunLaxElementCheck(Selector({"#terms-and-conditions"}).SetPseudoType(BEFORE),
+ true);
// An invisible pseudo-element
//
// TODO(b/129461999): This is wrong; it should exist. Fix it.
- RunLaxElementCheck(Selector({"#button"}, BEFORE), false);
+ RunLaxElementCheck(Selector({"#button"}).SetPseudoType(BEFORE), false);
// A non-existent pseudo-element
- RunLaxElementCheck(Selector({"#button"}, AFTER), false);
+ RunLaxElementCheck(Selector({"#button"}).SetPseudoType(AFTER), false);
+}
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ElementInFrameChecks) {
// An iFrame.
RunLaxElementCheck(Selector({"#iframe"}), true);
// An element in a same-origin iFrame.
RunLaxElementCheck(Selector({"#iframe", "#button"}), true);
+ // An element in a same-origin iFrame.
+ RunLaxElementCheck(Selector({"#iframe", "#doesnotexist"}), false);
+}
+
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ElementInExternalFrameChecks) {
// An OOPIF.
RunLaxElementCheck(Selector({"#iframeExternal"}), true);
// An element in an OOPIF.
RunLaxElementCheck(Selector({"#iframeExternal", "#button"}), true);
+
+ // An element in an OOPIF.
+ RunLaxElementCheck(Selector({"#iframeExternal", "#doesnotexist"}), false);
}
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, VisibilityRequirementCheck) {
@@ -583,13 +595,16 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, VisibilityRequirementCheck) {
// A pseudo-element
RunLaxElementCheck(
- Selector({"#terms-and-conditions"}, BEFORE).MustBeVisible(), true);
+ Selector({"#terms-and-conditions"}).MustBeVisible().SetPseudoType(BEFORE),
+ true);
// An invisible pseudo-element
- RunLaxElementCheck(Selector({"#button"}, BEFORE).MustBeVisible(), false);
+ RunLaxElementCheck(
+ Selector({"#button"}).MustBeVisible().SetPseudoType(BEFORE), false);
// A non-existent pseudo-element
- RunLaxElementCheck(Selector({"#button"}, AFTER).MustBeVisible(), false);
+ RunLaxElementCheck(Selector({"#button"}).MustBeVisible().SetPseudoType(AFTER),
+ false);
// An iFrame.
RunLaxElementCheck(Selector({"#iframe"}).MustBeVisible(), true);
@@ -625,61 +640,221 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, MultipleVisibleElementCheck) {
false);
}
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SearchMultipleIframes) {
+ // There are two "iframe" elements in the document so the selector would need
+ // to search in both iframes, which isn't supported.
+ SelectorProto proto;
+ proto.add_filters()->set_css_selector("iframe");
+ proto.add_filters()->mutable_enter_frame();
+ proto.add_filters()->set_css_selector("#element_in_iframe_two");
+
+ ClientStatus status;
+ FindElement(Selector(proto), &status, nullptr);
+ EXPECT_EQ(TOO_MANY_ELEMENTS, status.proto_status());
+}
+
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, InnerTextCondition) {
- Selector selector({"#with_inner_text span"});
- selector.must_be_visible = true;
+ const Selector base_selector({"#with_inner_text span"});
+ Selector selector = base_selector;
+ selector.MustBeVisible();
RunLaxElementCheck(selector, true);
RunStrictElementCheck(selector.MustBeVisible(), false);
// No matches
- selector.inner_text_pattern = "no match";
- selector.must_be_visible = false;
+ selector = base_selector;
+ selector.MatchingInnerText("no match");
RunLaxElementCheck(selector, false);
- selector.must_be_visible = true;
+ selector.MustBeVisible();
RunLaxElementCheck(selector, false);
// Matches exactly one visible element.
- selector.inner_text_pattern = "hello, world";
- selector.must_be_visible = false;
+ selector = base_selector;
+ selector.MatchingInnerText("hello, world");
RunLaxElementCheck(selector, true);
RunStrictElementCheck(selector, true);
- selector.must_be_visible = true;
+ selector.MustBeVisible();
RunLaxElementCheck(selector, true);
RunStrictElementCheck(selector, true);
+ // Matches case (in)sensitive.
+ selector = base_selector;
+ selector.MatchingInnerText("HELLO, WORLD", /* case_sensitive=*/false);
+ RunLaxElementCheck(selector, true);
+ RunStrictElementCheck(selector, true);
+ selector = base_selector;
+ selector.MatchingInnerText("HELLO, WORLD", /* case_sensitive=*/true);
+ RunLaxElementCheck(selector, false);
+ RunStrictElementCheck(selector, false);
+
// Matches two visible elements
- selector.inner_text_pattern = "^hello";
- selector.must_be_visible = false;
+ selector = base_selector;
+ selector.MatchingInnerText("^hello");
RunLaxElementCheck(selector, true);
RunStrictElementCheck(selector, false);
- selector.must_be_visible = true;
+ selector.MustBeVisible();
RunLaxElementCheck(selector, true);
RunStrictElementCheck(selector, false);
// Matches one visible, one invisible element
- selector.inner_text_pattern = "world$";
- selector.must_be_visible = false;
+ selector = base_selector;
+ selector.MatchingInnerText("world$");
+ RunLaxElementCheck(selector, true);
+ selector.MustBeVisible();
+ RunLaxElementCheck(selector, true);
+ RunStrictElementCheck(selector, true);
+}
+
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, PseudoTypeAndInnerText) {
+ // Inner text conditions then pseudo type vs pseudo type then inner text
+ // condition.
+ Selector selector({"#with_inner_text span"});
+ selector.MatchingInnerText("world");
+ selector.SetPseudoType(PseudoType::BEFORE);
+ RunLaxElementCheck(selector, true);
+
+ // "before" is the content of the :before, checking the text of pseudo-types
+ // doesn't work.
+ selector = Selector({"#with_inner_text span"});
+ selector.SetPseudoType(PseudoType::BEFORE);
+ selector.MatchingInnerText("before");
+ RunLaxElementCheck(selector, false);
+}
+
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, MultipleBefore) {
+ Selector selector({"span"});
+ selector.SetPseudoType(PseudoType::BEFORE);
+
+ // There's more than one "span" with a before, so only a lax check can
+ // succeed.
RunLaxElementCheck(selector, true);
RunStrictElementCheck(selector, false);
- selector.must_be_visible = true;
+}
+
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, PseudoTypeThenBoundingBox) {
+ Selector selector({"span"});
+ selector.SetPseudoType(PseudoType::BEFORE);
+ selector.proto.add_filters()->mutable_bounding_box();
+
RunLaxElementCheck(selector, true);
+}
+
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, PseudoTypeThenPickOne) {
+ Selector selector({"span"});
+ selector.SetPseudoType(PseudoType::BEFORE);
+ selector.proto.add_filters()->mutable_pick_one();
+
RunStrictElementCheck(selector, true);
+}
+
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, PseudoTypeThenCss) {
+ Selector selector({"span"});
+ selector.SetPseudoType(PseudoType::BEFORE);
+ selector.proto.add_filters()->set_css_selector("div");
+
+ // This makes no sense, but shouldn't return an unexpected error.
+ ClientStatus status;
+ ElementFinder::Result result;
+ FindElement(selector, &status, &result);
+ EXPECT_EQ(ELEMENT_RESOLUTION_FAILED, status.proto_status());
+}
+
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, PseudoTypeThenInnerText) {
+ Selector selector({"span"});
+ selector.SetPseudoType(PseudoType::BEFORE);
+ selector.proto.add_filters()->mutable_inner_text()->set_re2("before");
+
+ // This isn't supported yet.
+ RunLaxElementCheck(selector, false);
+}
- // Inner text conditions are applied before looking for the pseudo-type.
- selector.pseudo_type = PseudoType::BEFORE;
- selector.inner_text_pattern = "world";
- selector.must_be_visible = false;
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, PseudoTypeContent) {
+ Selector selector({"#with_inner_text span"});
+ auto* content =
+ selector.proto.add_filters()->mutable_pseudo_element_content();
+ content->set_pseudo_type(PseudoType::BEFORE);
+ content->mutable_content()->set_re2("before");
RunLaxElementCheck(selector, true);
- selector.inner_text_pattern = "before"; // matches :before content
+
+ content->mutable_content()->set_re2("nomatch");
RunLaxElementCheck(selector, false);
}
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, InnerTextThenCss) {
+ // There are two divs containing "Section with text", but only one has a
+ // button, which removes #button.
+ SelectorProto proto;
+ proto.add_filters()->set_css_selector("div");
+ proto.add_filters()->mutable_inner_text()->set_re2("Section with text");
+ proto.add_filters()->set_css_selector("button");
+
+ ClickOrTapElement(Selector(proto), ClickType::CLICK);
+ WaitForElementRemove(Selector({"#button"}));
+}
+
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, FindFormInputByLabel) {
+ // #option1_label refers to the labelled control by id.
+ Selector option1;
+ option1.proto.add_filters()->set_css_selector("#option1_label");
+ option1.proto.add_filters()->mutable_labelled();
+
+ const std::string option1_checked = R"(
+ document.querySelector("#option1").checked;
+ )";
+ EXPECT_FALSE(content::EvalJs(shell(), option1_checked).ExtractBool());
+ ClickOrTapElement(option1, ClickType::CLICK);
+ EXPECT_TRUE(content::EvalJs(shell(), option1_checked).ExtractBool());
+
+ // #option2 contains the labelled control.
+ Selector option2;
+ option2.proto.add_filters()->set_css_selector("#option2_label");
+ option2.proto.add_filters()->mutable_labelled();
+
+ const std::string option2_checked = R"(
+ document.querySelector("#option2").checked;
+ )";
+ EXPECT_FALSE(content::EvalJs(shell(), option2_checked).ExtractBool());
+ ClickOrTapElement(option2, ClickType::CLICK);
+ EXPECT_TRUE(content::EvalJs(shell(), option2_checked).ExtractBool());
+
+ // #button is not a label.
+ Selector not_a_label;
+ not_a_label.proto.add_filters()->set_css_selector("#button");
+ not_a_label.proto.add_filters()->mutable_labelled();
+
+ // #bad_label1 and #bad_label2 are labels that don't reference a valid
+ // element. They must not cause JavaScript errors.
+ Selector bad_label1;
+ bad_label1.proto.add_filters()->set_css_selector("#bad_label1");
+ bad_label1.proto.add_filters()->mutable_labelled();
+
+ ClientStatus status;
+ FindElement(bad_label1, &status, nullptr);
+ EXPECT_EQ(ELEMENT_RESOLUTION_FAILED, status.proto_status());
+
+ Selector bad_label2;
+ bad_label2.proto.add_filters()->set_css_selector("#bad_label2");
+ bad_label2.proto.add_filters()->mutable_labelled();
+
+ FindElement(bad_label2, &status, nullptr);
+ EXPECT_EQ(ELEMENT_RESOLUTION_FAILED, status.proto_status());
+}
+
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ValueCondition) {
// One match
RunLaxElementCheck(Selector({"#input1"}).MatchingValue("helloworld1"), true);
RunStrictElementCheck(Selector({"#input1"}).MatchingValue("helloworld1"),
true);
+ // Case (in)sensitive match
+ RunLaxElementCheck(Selector({"#input1"}).MatchingValue("HELLOWORLD1", false),
+ true);
+ RunLaxElementCheck(Selector({"#input1"}).MatchingValue("HELLOWORLD1", true),
+ false);
+ RunStrictElementCheck(
+ Selector({"#input1"}).MatchingValue("HELLOWORLD1", false), true);
+ RunStrictElementCheck(
+ Selector({"#input1"}).MatchingValue("HELLOWORLD1", true), false);
+
// No matches
RunLaxElementCheck(Selector({"#input2"}).MatchingValue("doesnotmatch"),
false);
@@ -704,104 +879,72 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
std::vector<Selector> selectors;
std::vector<bool> results;
- Selector a_selector;
- a_selector.must_be_visible = true;
- a_selector.selectors.emplace_back("#button");
- selectors.emplace_back(a_selector);
+ Selector visible_button({"#button"});
+ visible_button.MustBeVisible();
+ selectors.emplace_back(visible_button);
results.emplace_back(true);
- a_selector.selectors.emplace_back("#whatever");
- selectors.emplace_back(a_selector);
+ Selector visible_with_iframe({"#button", "#watever"});
+ visible_with_iframe.MustBeVisible();
+ selectors.emplace_back(visible_with_iframe);
results.emplace_back(false);
// IFrame.
- a_selector.selectors.clear();
- a_selector.selectors.emplace_back("#iframe");
- a_selector.selectors.emplace_back("#button");
- selectors.emplace_back(a_selector);
+ selectors.emplace_back(Selector({"#iframe", "#button"}));
results.emplace_back(true);
- a_selector.selectors.emplace_back("#whatever");
- selectors.emplace_back(a_selector);
+ selectors.emplace_back(Selector({"#iframe", "#button", "#whatever"}));
results.emplace_back(false);
- a_selector.selectors.clear();
- a_selector.selectors.emplace_back("#iframe");
- a_selector.selectors.emplace_back("[name=name]");
- selectors.emplace_back(a_selector);
+ selectors.emplace_back(Selector({"#iframe", "[name=name]"}));
results.emplace_back(true);
// OOPIF.
- a_selector.selectors.clear();
- a_selector.selectors.emplace_back("#iframeExternal");
- a_selector.selectors.emplace_back("#button");
- selectors.emplace_back(a_selector);
+ selectors.emplace_back(Selector({"#iframeExternal", "#button"}));
results.emplace_back(true);
// Shadow DOM.
- a_selector.selectors.clear();
- a_selector.selectors.emplace_back("#iframe");
- a_selector.selectors.emplace_back("#shadowsection");
- a_selector.selectors.emplace_back("#shadowbutton");
- selectors.emplace_back(a_selector);
+ selectors.emplace_back(
+ Selector({"#iframe", "#shadowsection", "#shadowbutton"}));
results.emplace_back(true);
- a_selector.selectors.emplace_back("#whatever");
- selectors.emplace_back(a_selector);
+ selectors.emplace_back(
+ Selector({"#iframe", "#shadowsection", "#shadowbutton", "#whatever"}));
results.emplace_back(false);
// IFrame inside IFrame.
- a_selector.selectors.clear();
- a_selector.selectors.emplace_back("#iframe");
- a_selector.selectors.emplace_back("#iframe");
- a_selector.selectors.emplace_back("#button");
- selectors.emplace_back(a_selector);
+ selectors.emplace_back(Selector({"#iframe", "#iframe", "#button"}));
results.emplace_back(true);
- a_selector.selectors.emplace_back("#whatever");
- selectors.emplace_back(a_selector);
+ selectors.emplace_back(
+ Selector({"#iframe", "#iframe", "#button", "#whatever"}));
results.emplace_back(false);
// Hidden element.
- a_selector.selectors.clear();
- a_selector.selectors.emplace_back("#hidden");
- selectors.emplace_back(a_selector);
+ selectors.emplace_back(Selector({"#hidden"}).MustBeVisible());
results.emplace_back(false);
RunElementChecks(/* strict= */ false, selectors, results);
}
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ClickElement) {
- Selector selector;
- selector.selectors.emplace_back("#button");
+ Selector selector({"#button"});
ClickOrTapElement(selector, ClickType::CLICK);
WaitForElementRemove(selector);
}
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ClickElementInIFrame) {
- Selector selector;
- selector.selectors.emplace_back("#iframe");
- selector.selectors.emplace_back("#shadowsection");
- selector.selectors.emplace_back("#shadowbutton");
- ClickOrTapElement(selector, ClickType::CLICK);
+ ClickOrTapElement(Selector({"#iframe", "#shadowsection", "#shadowbutton"}),
+ ClickType::CLICK);
- selector.selectors.clear();
- selector.selectors.emplace_back("#iframe");
- selector.selectors.emplace_back("#button");
- WaitForElementRemove(selector);
+ WaitForElementRemove(Selector({"#iframe", "#button"}));
}
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ClickElementInOOPIF) {
- Selector selector;
- selector.selectors.emplace_back("#iframeExternal");
- selector.selectors.emplace_back("#button");
- ClickOrTapElement(selector, ClickType::CLICK);
+ ClickOrTapElement(Selector({"#iframeExternal", "#button"}), ClickType::CLICK);
- selector.selectors.clear();
- selector.selectors.emplace_back("#iframeExternal");
- selector.selectors.emplace_back("#div");
- WaitForElementRemove(selector);
+ WaitForElementRemove(Selector({"#iframeExternal", "#div"}));
}
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
@@ -820,8 +963,7 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
scrollItem3WasClicked = true;
});)"));
- Selector selector;
- selector.selectors.emplace_back("#scroll_item_3");
+ Selector selector({"#scroll_item_3"});
ClickOrTapElement(selector, ClickType::CLICK);
EXPECT_TRUE(content::EvalJs(shell(), "scrollItem3WasClicked").ExtractBool());
@@ -831,31 +973,29 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
}
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, TapElement) {
- Selector selector;
- selector.selectors.emplace_back("#touch_area_two");
- ClickOrTapElement(selector, ClickType::TAP);
- WaitForElementRemove(selector);
+ Selector area_two({"#touch_area_two"});
+ ClickOrTapElement(area_two, ClickType::TAP);
+ WaitForElementRemove(area_two);
- selector.selectors.clear();
- selector.selectors.emplace_back("#touch_area_one");
- ClickOrTapElement(selector, ClickType::TAP);
- WaitForElementRemove(selector);
+ Selector area_one({"#touch_area_one"});
+ ClickOrTapElement(area_one, ClickType::TAP);
+ WaitForElementRemove(area_one);
}
-IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, TapElementMovingOutOfView) {
- Selector selector;
- selector.selectors.emplace_back("#touch_area_three");
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
+ DISABLED_TapElementMovingOutOfView) {
+ Selector selector({"#touch_area_three"});
ClickOrTapElement(selector, ClickType::TAP);
WaitForElementRemove(selector);
}
-IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, TapElementAfterPageIsIdle) {
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
+ DISABLED_TapElementAfterPageIsIdle) {
// Set a very long timeout to make sure either the page is idle or the test
// timeout.
WaitTillPageIsIdle(base::TimeDelta::FromHours(1));
- Selector selector;
- selector.selectors.emplace_back("#touch_area_one");
+ Selector selector({"#touch_area_one"});
ClickOrTapElement(selector, ClickType::TAP);
WaitForElementRemove(selector);
@@ -863,16 +1003,14 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, TapElementAfterPageIsIdle) {
// TODO(crbug.com/920948) Disabled for strong flakiness.
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, DISABLED_TapElementInIFrame) {
- Selector selector;
- selector.selectors.emplace_back("#iframe");
- selector.selectors.emplace_back("#touch_area");
+ Selector selector({"#iframe", "#touch_area"});
ClickOrTapElement(selector, ClickType::TAP);
WaitForElementRemove(selector);
}
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
- TapRandomMovingElementRepeatedly) {
+ DISABLED_TapRandomMovingElementRepeatedly) {
Selector button_selector({"#random_moving_button"});
int num_clicks = 100;
for (int i = 0; i < num_clicks; ++i) {
@@ -903,8 +1041,7 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, TapMovingElementRepeatedly) {
}
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, TapStaticElementRepeatedly) {
- Selector button_selector;
- button_selector.selectors.emplace_back("#static_button");
+ Selector button_selector({"#static_button"});
int num_clicks = 100;
for (int i = 0; i < num_clicks; ++i) {
@@ -914,8 +1051,7 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, TapStaticElementRepeatedly) {
std::vector<Selector> click_counter_selectors;
std::vector<std::string> expected_values;
expected_values.emplace_back(base::NumberToString(num_clicks));
- Selector click_counter_selector;
- click_counter_selector.selectors.emplace_back("#static_click_counter");
+ Selector click_counter_selector({"#static_click_counter"});
click_counter_selectors.emplace_back(click_counter_selector);
GetFieldsValue(click_counter_selectors, expected_values);
}
@@ -925,54 +1061,40 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ClickPseudoElement) {
document.querySelector("#terms-and-conditions").checked;
)";
EXPECT_FALSE(content::EvalJs(shell(), javascript).ExtractBool());
- Selector selector({R"(label[for="terms-and-conditions"])"},
- PseudoType::BEFORE);
+ Selector selector({R"(label[for="terms-and-conditions"])"});
+ selector.SetPseudoType(PseudoType::BEFORE);
ClickOrTapElement(selector, ClickType::CLICK);
EXPECT_TRUE(content::EvalJs(shell(), javascript).ExtractBool());
}
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, FindElement) {
- Selector selector;
- selector.selectors.emplace_back("#button");
- FindElementAndCheck(selector, 0, true);
- selector.must_be_visible = true;
- FindElementAndCheck(selector, 0, true);
+ Selector selector({"#button"});
+ FindElementAndCheck(selector, true);
+ selector.MustBeVisible();
+ FindElementAndCheck(selector, true);
// IFrame.
- selector.selectors.clear();
- selector.selectors.emplace_back("#iframe");
- selector.selectors.emplace_back("#button");
- selector.must_be_visible = false;
- FindElementAndCheck(selector, 0, false);
- selector.must_be_visible = true;
- FindElementAndCheck(selector, 0, false);
-
- selector.selectors.clear();
- selector.selectors.emplace_back("#iframe");
- selector.selectors.emplace_back("[name=name]");
- selector.must_be_visible = false;
- FindElementAndCheck(selector, 0, false);
- selector.must_be_visible = true;
- FindElementAndCheck(selector, 0, false);
+ selector = Selector({"#iframe", "#button"});
+ FindElementAndCheck(selector, false);
+ selector.MustBeVisible();
+ FindElementAndCheck(selector, false);
+
+ selector = Selector({"#iframe", "[name=name]"});
+ FindElementAndCheck(selector, false);
+ selector.MustBeVisible();
+ FindElementAndCheck(selector, false);
// IFrame inside IFrame.
- selector.selectors.clear();
- selector.selectors.emplace_back("#iframe");
- selector.selectors.emplace_back("#iframe");
- selector.selectors.emplace_back("#button");
- selector.must_be_visible = false;
- FindElementAndCheck(selector, 1, false);
- selector.must_be_visible = true;
- FindElementAndCheck(selector, 1, false);
+ selector = Selector({"#iframe", "#iframe", "#button"});
+ FindElementAndCheck(selector, false);
+ selector.MustBeVisible();
+ FindElementAndCheck(selector, false);
// OutOfProcessIFrame.
- selector.selectors.clear();
- selector.selectors.emplace_back("#iframeExternal");
- selector.selectors.emplace_back("#button");
- selector.must_be_visible = false;
- FindElementAndCheck(selector, 0, false);
- selector.must_be_visible = true;
- FindElementAndCheck(selector, 0, false);
+ selector = Selector({"#iframeExternal", "#button"});
+ FindElementAndCheck(selector, false);
+ selector.MustBeVisible();
+ FindElementAndCheck(selector, false);
}
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, FindElementNotFound) {
@@ -986,25 +1108,18 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, FindElementNotFound) {
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, FindElementErrorStatus) {
ClientStatus status;
- FindElement(
- Selector(ElementReferenceProto::default_instance()).MustBeVisible(),
- &status, nullptr);
+ FindElement(Selector(SelectorProto::default_instance()), &status, nullptr);
EXPECT_EQ(INVALID_SELECTOR, status.proto_status());
- FindElement(Selector({"#doesnotexist"}).MustBeVisible(), &status, nullptr);
+ FindElement(Selector({"#doesnotexist"}), &status, nullptr);
EXPECT_EQ(ELEMENT_RESOLUTION_FAILED, status.proto_status());
FindElement(Selector({"div"}), &status, nullptr);
EXPECT_EQ(TOO_MANY_ELEMENTS, status.proto_status());
-
- FindElement(Selector({"div"}).MustBeVisible(), &status, nullptr);
- EXPECT_EQ(TOO_MANY_ELEMENTS, status.proto_status());
}
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, FocusElement) {
- Selector selector;
- selector.selectors.emplace_back("#iframe");
- selector.selectors.emplace_back("#focus");
+ Selector selector({"#iframe", "#focus"});
const std::string checkVisibleScript = R"(
let iframe = document.querySelector("#iframe");
@@ -1033,8 +1148,7 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
FocusElement_WithPaddingInPixels) {
- Selector selector;
- selector.selectors.emplace_back("#scroll-me");
+ Selector selector({"#scroll-me"});
const std::string checkScrollDifferentThanTargetScript = R"(
window.scrollTo(0, 0);
@@ -1062,8 +1176,7 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
FocusElement_WithPaddingInRatio) {
- Selector selector;
- selector.selectors.emplace_back("#scroll-me");
+ Selector selector({"#scroll-me"});
const std::string checkScrollDifferentThanTargetScript = R"(
window.scrollTo(0, 0);
@@ -1094,8 +1207,7 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
}
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SelectOption) {
- Selector selector;
- selector.selectors.emplace_back("#select");
+ Selector selector({"#select"});
const std::string javascript = R"(
let select = document.querySelector("#select");
@@ -1122,20 +1234,16 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SelectOption) {
SelectOption(selector, "Aü万𠜎", VALUE_MATCH).proto_status());
EXPECT_EQ("Character Test Entry", content::EvalJs(shell(), javascript));
- selector.selectors.clear();
- selector.selectors.emplace_back("#incorrect_selector");
EXPECT_EQ(ELEMENT_RESOLUTION_FAILED,
- SelectOption(selector, "not important", LABEL_STARTS_WITH)
+ SelectOption(Selector({"#incorrect_selector"}), "not important",
+ LABEL_STARTS_WITH)
.proto_status());
}
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SelectOptionInIFrame) {
- Selector select_selector;
// IFrame.
- select_selector.selectors.clear();
- select_selector.selectors.emplace_back("#iframe");
- select_selector.selectors.emplace_back("select[name=state]");
+ Selector select_selector({"#iframe", "select[name=state]"});
EXPECT_EQ(
ACTION_APPLIED,
SelectOption(select_selector, "NY", LABEL_STARTS_WITH).proto_status());
@@ -1149,16 +1257,12 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SelectOptionInIFrame) {
// OOPIF.
// Checking elements through EvalJs in OOPIF is blocked by cross-site.
- select_selector.selectors.clear();
- select_selector.selectors.emplace_back("#iframeExternal");
- select_selector.selectors.emplace_back("select[name=pet]");
+ select_selector = Selector({"#iframeExternal", "select[name=pet]"});
EXPECT_EQ(
ACTION_APPLIED,
SelectOption(select_selector, "Cat", LABEL_STARTS_WITH).proto_status());
- Selector result_selector;
- result_selector.selectors.emplace_back("#iframeExternal");
- result_selector.selectors.emplace_back("#myPet");
+ Selector result_selector({"#iframeExternal", "#myPet"});
GetFieldsValue({result_selector}, {"Cat"});
}
@@ -1166,25 +1270,20 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, GetOuterHtml) {
std::string html;
// Div.
- Selector div_selector;
- div_selector.selectors.emplace_back("#testOuterHtml");
+ Selector div_selector({"#testOuterHtml"});
ASSERT_EQ(ACTION_APPLIED, GetOuterHtml(div_selector, &html).proto_status());
EXPECT_EQ(
R"(<div id="testOuterHtml"><span>Span</span><p>Paragraph</p></div>)",
html);
// IFrame.
- Selector iframe_selector;
- iframe_selector.selectors.emplace_back("#iframe");
- iframe_selector.selectors.emplace_back("#input");
+ Selector iframe_selector({"#iframe", "#input"});
ASSERT_EQ(ACTION_APPLIED,
GetOuterHtml(iframe_selector, &html).proto_status());
EXPECT_EQ(R"(<input id="input" type="text">)", html);
// OOPIF.
- Selector oopif_selector;
- oopif_selector.selectors.emplace_back("#iframeExternal");
- oopif_selector.selectors.emplace_back("#divToRemove");
+ Selector oopif_selector({"#iframeExternal", "#divToRemove"});
ASSERT_EQ(ACTION_APPLIED, GetOuterHtml(oopif_selector, &html).proto_status());
EXPECT_EQ(R"(<div id="divToRemove">Text</div>)", html);
}
@@ -1213,15 +1312,13 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, GetAndSetFieldValue) {
std::vector<Selector> selectors;
std::vector<std::string> expected_values;
- Selector a_selector;
- a_selector.selectors.emplace_back("body"); // Body has 'undefined' value
+ Selector a_selector({"body"}); // Body has 'undefined' value
selectors.emplace_back(a_selector);
expected_values.emplace_back("");
GetFieldsValue(selectors, expected_values);
selectors.clear();
- a_selector.selectors.clear();
- a_selector.selectors.emplace_back("#input1");
+ a_selector = Selector({"#input1"});
selectors.emplace_back(a_selector);
expected_values.clear();
expected_values.emplace_back("helloworld1");
@@ -1234,8 +1331,7 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, GetAndSetFieldValue) {
GetFieldsValue(selectors, expected_values);
selectors.clear();
- a_selector.selectors.clear();
- a_selector.selectors.emplace_back("#uppercase_input");
+ a_selector = Selector({"#uppercase_input"});
selectors.emplace_back(a_selector);
EXPECT_EQ(ACTION_APPLIED,
SetFieldValue(a_selector, /* Zürich */ "Z\xc3\xbcrich",
@@ -1246,8 +1342,7 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, GetAndSetFieldValue) {
GetFieldsValue(selectors, expected_values);
selectors.clear();
- a_selector.selectors.clear();
- a_selector.selectors.emplace_back("#input2");
+ a_selector = Selector({"#input2"});
selectors.emplace_back(a_selector);
expected_values.clear();
expected_values.emplace_back("helloworld2");
@@ -1260,8 +1355,7 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, GetAndSetFieldValue) {
GetFieldsValue(selectors, expected_values);
selectors.clear();
- a_selector.selectors.clear();
- a_selector.selectors.emplace_back("#input3");
+ a_selector = Selector({"#input3"});
selectors.emplace_back(a_selector);
expected_values.clear();
expected_values.emplace_back("helloworld3");
@@ -1274,8 +1368,7 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, GetAndSetFieldValue) {
GetFieldsValue(selectors, expected_values);
selectors.clear();
- a_selector.selectors.clear();
- a_selector.selectors.emplace_back("#invalid_selector");
+ a_selector = Selector({"#invalid_selector"});
selectors.emplace_back(a_selector);
expected_values.clear();
expected_values.emplace_back("");
@@ -1286,20 +1379,15 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, GetAndSetFieldValue) {
}
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, GetAndSetFieldValueInIFrame) {
- Selector a_selector;
// IFrame.
- a_selector.selectors.clear();
- a_selector.selectors.emplace_back("#iframe");
- a_selector.selectors.emplace_back("#input");
+ Selector a_selector({"#iframe", "#input"});
EXPECT_EQ(ACTION_APPLIED,
SetFieldValue(a_selector, "text", SET_VALUE).proto_status());
GetFieldsValue({a_selector}, {"text"});
// OOPIF.
- a_selector.selectors.clear();
- a_selector.selectors.emplace_back("#iframeExternal");
- a_selector.selectors.emplace_back("#input");
+ a_selector = Selector({"#iframeExternal", "#input"});
EXPECT_EQ(ACTION_APPLIED,
SetFieldValue(a_selector, "text", SET_VALUE).proto_status());
GetFieldsValue({a_selector}, {"text"});
@@ -1310,8 +1398,7 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SendKeyboardInput) {
std::string expected_output = "Zürich";
std::vector<Selector> selectors;
- Selector a_selector;
- a_selector.selectors.emplace_back("#input6");
+ Selector a_selector({"#input6"});
selectors.emplace_back(a_selector);
EXPECT_EQ(ACTION_APPLIED,
SendKeyboardInput(a_selector, input).proto_status());
@@ -1324,8 +1411,7 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
std::string expected_output = "ZürichEnter";
std::vector<Selector> selectors;
- Selector a_selector;
- a_selector.selectors.emplace_back("#input_js_event_listener");
+ Selector a_selector({"#input_js_event_listener"});
selectors.emplace_back(a_selector);
EXPECT_EQ(ACTION_APPLIED,
SendKeyboardInput(a_selector, input).proto_status());
@@ -1342,8 +1428,7 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
std::string expected_output = "012 345";
std::vector<Selector> selectors;
- Selector a_selector;
- a_selector.selectors.emplace_back("#input_js_event_with_timeout");
+ Selector a_selector({"#input_js_event_with_timeout"});
selectors.emplace_back(a_selector);
EXPECT_EQ(ACTION_APPLIED,
SendKeyboardInput(a_selector, input, /*delay_in_milli*/ 100)
@@ -1352,10 +1437,9 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
}
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SetAttribute) {
- Selector selector;
std::vector<std::string> attribute;
- selector.selectors.emplace_back("#full_height_section");
+ Selector selector({"#full_height_section"});
attribute.emplace_back("style");
attribute.emplace_back("backgroundColor");
std::string value = "red";
@@ -1372,28 +1456,23 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ConcurrentGetFieldsValue) {
std::vector<Selector> selectors;
std::vector<std::string> expected_values;
- Selector a_selector;
- a_selector.selectors.emplace_back("#input1");
+ Selector a_selector({"#input1"});
selectors.emplace_back(a_selector);
expected_values.emplace_back("helloworld1");
- a_selector.selectors.clear();
- a_selector.selectors.emplace_back("#input2");
+ a_selector = Selector({"#input2"});
selectors.emplace_back(a_selector);
expected_values.emplace_back("helloworld2");
- a_selector.selectors.clear();
- a_selector.selectors.emplace_back("#input3");
+ a_selector = Selector({"#input3"});
selectors.emplace_back(a_selector);
expected_values.emplace_back("helloworld3");
- a_selector.selectors.clear();
- a_selector.selectors.emplace_back("#input4");
+ a_selector = Selector({"#input4"});
selectors.emplace_back(a_selector);
expected_values.emplace_back("helloworld4");
- a_selector.selectors.clear();
- a_selector.selectors.emplace_back("#input5");
+ a_selector = Selector({"#input5"});
selectors.emplace_back(a_selector);
expected_values.emplace_back("helloworld5");
@@ -1410,8 +1489,7 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, NavigateToUrl) {
}
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, HighlightElement) {
- Selector selector;
- selector.selectors.emplace_back("#select");
+ Selector selector({"#select"});
const std::string javascript = R"(
let select = document.querySelector("#select");
@@ -1509,4 +1587,233 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, GetElementPosition) {
EXPECT_LT(iframe_element_rect.bottom, iframe_rect.bottom);
}
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, GetElementByProximity) {
+ Selector input1_selector({"input"});
+ auto* input1_closest = input1_selector.proto.add_filters()->mutable_closest();
+ input1_closest->add_target()->set_css_selector("label");
+ input1_closest->add_target()->mutable_inner_text()->set_re2("Input1");
+
+ GetFieldsValue({input1_selector}, {"helloworld1"});
+}
+
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
+ GetElementByProximityWithTooManyCandidates) {
+ Selector selector({"input.pairs"});
+ auto* closest = selector.proto.add_filters()->mutable_closest();
+ closest->add_target()->set_css_selector("label.pairs");
+ closest->set_max_pairs(24);
+
+ ClientStatus status;
+ ElementFinder::Result result;
+ FindElement(selector, &status, &result);
+ EXPECT_EQ(TOO_MANY_CANDIDATES, status.proto_status());
+
+ closest->set_max_pairs(25);
+ FindElement(selector, &status, &result);
+ EXPECT_EQ(ACTION_APPLIED, status.proto_status());
+}
+
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ProximityRelative_Position) {
+ Selector selector({"#at_center"});
+ auto* closest = selector.proto.add_filters()->mutable_closest();
+ closest->add_target()->set_css_selector("table.proximity td");
+ auto* inner_text = closest->add_target()->mutable_inner_text();
+
+ // The cells of the table look like the following:
+ //
+ // One Two Three
+ // Four Center Five
+ // Six Seven Eight
+ //
+ // The element is "Center", the target is "One" to "Eight". The
+ // relative_position specify that the element should be below|above|... the
+ // target.
+
+ closest->set_relative_position(SelectorProto::ProximityFilter::BELOW);
+ inner_text->set_re2("One");
+ RunStrictElementCheck(selector, true);
+ inner_text->set_re2("Two");
+ RunStrictElementCheck(selector, true);
+ inner_text->set_re2("Three");
+ RunStrictElementCheck(selector, true);
+ inner_text->set_re2("Four");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Five");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Six");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Seven");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Eight");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Center");
+ RunStrictElementCheck(selector, false);
+
+ closest->set_relative_position(SelectorProto::ProximityFilter::ABOVE);
+ inner_text->set_re2("One");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Two");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Three");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Four");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Five");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Six");
+ RunStrictElementCheck(selector, true);
+ inner_text->set_re2("Seven");
+ RunStrictElementCheck(selector, true);
+ inner_text->set_re2("Eight");
+ RunStrictElementCheck(selector, true);
+ inner_text->set_re2("Center");
+ RunStrictElementCheck(selector, false);
+
+ closest->set_relative_position(SelectorProto::ProximityFilter::LEFT);
+ inner_text->set_re2("One");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Two");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Three");
+ RunStrictElementCheck(selector, true);
+ inner_text->set_re2("Four");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Five");
+ RunStrictElementCheck(selector, true);
+ inner_text->set_re2("Six");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Seven");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Eight");
+ RunStrictElementCheck(selector, true);
+ inner_text->set_re2("Center");
+ RunStrictElementCheck(selector, false);
+
+ closest->set_relative_position(SelectorProto::ProximityFilter::RIGHT);
+ inner_text->set_re2("One");
+ RunStrictElementCheck(selector, true);
+ inner_text->set_re2("Two");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Three");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Four");
+ RunStrictElementCheck(selector, true);
+ inner_text->set_re2("Five");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Six");
+ RunStrictElementCheck(selector, true);
+ inner_text->set_re2("Seven");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Eight");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Center");
+ RunStrictElementCheck(selector, false);
+}
+
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ProximityAlignment) {
+ Selector selector({"#at_center"});
+ auto* closest = selector.proto.add_filters()->mutable_closest();
+ closest->add_target()->set_css_selector("table.proximity td");
+ auto* inner_text = closest->add_target()->mutable_inner_text();
+
+ closest->set_in_alignment(true);
+ inner_text->set_re2("One");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Two");
+ RunStrictElementCheck(selector, true);
+ inner_text->set_re2("Three");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Four");
+ RunStrictElementCheck(selector, true);
+ inner_text->set_re2("Five");
+ RunStrictElementCheck(selector, true);
+ inner_text->set_re2("Six");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Seven");
+ RunStrictElementCheck(selector, true);
+ inner_text->set_re2("Eight");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Center");
+ RunStrictElementCheck(selector, true);
+}
+
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
+ ProximityAlignmentWithPosition) {
+ Selector selector({"#at_center"});
+ auto* closest = selector.proto.add_filters()->mutable_closest();
+ closest->add_target()->set_css_selector("table.proximity td");
+ auto* inner_text = closest->add_target()->mutable_inner_text();
+
+ closest->set_in_alignment(true);
+ closest->set_relative_position(SelectorProto::ProximityFilter::LEFT);
+
+ inner_text->set_re2("One");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Two");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Three");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Four");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Five");
+ RunStrictElementCheck(selector, true);
+ inner_text->set_re2("Six");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Seven");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Eight");
+ RunStrictElementCheck(selector, false);
+ inner_text->set_re2("Center");
+ RunStrictElementCheck(selector, false);
+}
+
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
+ FindPseudoElementToClickByProximity) {
+ const std::string javascript = R"(
+ document.querySelector("#terms-and-conditions").checked;
+ )";
+ EXPECT_FALSE(content::EvalJs(shell(), javascript).ExtractBool());
+
+ // This test clicks on the before pseudo-element that's closest to
+ // #terms-and-conditions - this has the same effect as clicking on
+ // #terms-and-conditions. This checks that pseudo-elements have positions and
+ // that we can go through an array of pseudo-elements and choose the closest
+ // one.
+ Selector selector({"label, span"});
+ selector.SetPseudoType(PseudoType::BEFORE);
+ auto* closest = selector.proto.add_filters()->mutable_closest();
+ closest->add_target()->set_css_selector("#terms-and-conditions");
+
+ ClickOrTapElement(selector, ClickType::CLICK);
+ EXPECT_TRUE(content::EvalJs(shell(), javascript).ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
+ GetElementByProximityDifferentFrames) {
+ Selector selector({"input"});
+ auto* closest = selector.proto.add_filters()->mutable_closest();
+ closest->add_target()->set_css_selector("#iframe");
+ closest->add_target()->mutable_pick_one();
+ closest->add_target()->mutable_enter_frame();
+ closest->add_target()->set_css_selector("div");
+
+ // Cannot compare position of elements on different frames.
+ ClientStatus status;
+ FindElement(Selector(SelectorProto::default_instance()), &status, nullptr);
+ EXPECT_EQ(INVALID_SELECTOR, status.proto_status());
+}
+
+IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
+ GetElementByProximitySameFrame) {
+ Selector selector({"#iframe", "input[name='email']"});
+
+ // The target is searched within #iframe.
+ auto* closest = selector.proto.add_filters()->mutable_closest();
+ closest->add_target()->set_css_selector("span");
+ closest->add_target()->mutable_inner_text()->set_re2("Email");
+
+ RunLaxElementCheck(selector, true);
+ GetFieldsValue({selector}, {"email@example.com"});
+}
+
} // namespace autofill_assistant
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 039a5c04273..32c1ea674e8 100644
--- a/chromium/components/autofill_assistant/browser/website_login_manager_impl.cc
+++ b/chromium/components/autofill_assistant/browser/website_login_manager_impl.cc
@@ -6,8 +6,8 @@
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
-#include "base/task/post_task.h"
#include "components/password_manager/core/browser/form_fetcher_impl.h"
+#include "components/password_manager/core/browser/form_parsing/form_parser.h"
#include "components/password_manager/core/browser/password_form_metrics_recorder.h"
#include "components/password_manager/core/browser/password_generation_frame_helper.h"
#include "components/password_manager/core/browser/password_manager_client.h"
@@ -27,8 +27,8 @@ autofill::PasswordForm CreatePasswordForm(
const WebsiteLoginManager::Login& login,
const std::string& password) {
autofill::PasswordForm form;
- form.signon_realm = login.origin.spec();
- form.origin = login.origin.GetOrigin();
+ form.url = login.origin.GetOrigin();
+ form.signon_realm = password_manager::GetSignonRealm(form.url);
form.username_value = base::UTF8ToUTF16(login.username);
form.password_value = base::UTF8ToUTF16(password);
@@ -71,8 +71,8 @@ class WebsiteLoginManagerImpl::PendingRequest
// destruction of |this|, which needs to happen *after* this call has
// returned.
if (notify_finished_callback_) {
- base::PostTask(FROM_HERE, {content::BrowserThread::UI},
- base::BindOnce(&PendingRequest::NotifyFinished,
+ content::GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE, base::BindOnce(&PendingRequest::NotifyFinished,
weak_ptr_factory_.GetWeakPtr(),
std::move(notify_finished_callback_)));
}
@@ -110,7 +110,7 @@ class WebsiteLoginManagerImpl::PendingFetchLoginsRequest
void OnFetchCompleted() override {
std::vector<Login> logins;
for (const auto* match : form_fetcher_->GetBestMatches()) {
- logins.emplace_back(match->origin.GetOrigin(),
+ logins.emplace_back(match->url.GetOrigin(),
base::UTF16ToUTF8(match->username_value));
}
std::move(callback_).Run(logins);
@@ -176,8 +176,9 @@ class WebsiteLoginManagerImpl::UpdatePasswordRequest
CreatePasswordSaveManagerImpl(client)),
metrics_recorder_(
base::MakeRefCounted<password_manager::PasswordFormMetricsRecorder>(
- client->IsMainFrameSecure(),
- client->GetUkmSourceId())),
+ client->IsCommittedMainFrameSecure(),
+ client->GetUkmSourceId(),
+ client->GetPrefs())),
votes_uploader_(client, true /* is_possible_change_password_form */) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);