diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-01-23 17:21:03 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-01-23 16:25:15 +0000 |
commit | c551f43206405019121bd2b2c93714319a0a3300 (patch) | |
tree | 1f48c30631c421fd4bbb3c36da20183c8a2ed7d7 /chromium/components/autofill_assistant | |
parent | 7961cea6d1041e3e454dae6a1da660b453efd238 (diff) | |
download | qtwebengine-chromium-c551f43206405019121bd2b2c93714319a0a3300.tar.gz |
BASELINE: Update Chromium to 79.0.3945.139
Change-Id: I336b7182fab9bca80b709682489c07db112eaca5
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/components/autofill_assistant')
96 files changed, 4061 insertions, 1533 deletions
diff --git a/chromium/components/autofill_assistant/browser/BUILD.gn b/chromium/components/autofill_assistant/browser/BUILD.gn index 9ddc5a1b583..68bf9050ba1 100644 --- a/chromium/components/autofill_assistant/browser/BUILD.gn +++ b/chromium/components/autofill_assistant/browser/BUILD.gn @@ -34,8 +34,6 @@ jumbo_static_library("browser") { "actions/action.cc", "actions/action.h", "actions/action_delegate.h", - "actions/autofill_action.cc", - "actions/autofill_action.h", "actions/click_action.cc", "actions/click_action.h", "actions/collect_user_data_action.cc", @@ -54,6 +52,8 @@ jumbo_static_library("browser") { "actions/popup_message_action.h", "actions/prompt_action.cc", "actions/prompt_action.h", + "actions/required_fields_fallback_handler.cc", + "actions/required_fields_fallback_handler.h", "actions/reset_action.cc", "actions/reset_action.h", "actions/select_option_action.cc", @@ -78,6 +78,10 @@ jumbo_static_library("browser") { "actions/unsupported_action.h", "actions/upload_dom_action.cc", "actions/upload_dom_action.h", + "actions/use_address_action.cc", + "actions/use_address_action.h", + "actions/use_credit_card_action.cc", + "actions/use_credit_card_action.h", "actions/wait_for_document_action.cc", "actions/wait_for_document_action.h", "actions/wait_for_dom_action.cc", @@ -185,7 +189,6 @@ jumbo_static_library("browser") { source_set("unit_tests") { testonly = true sources = [ - "actions/autofill_action_unittest.cc", "actions/collect_user_data_action_unittest.cc", "actions/configure_bottom_sheet_action_unittest.cc", "actions/mock_action_delegate.cc", @@ -194,6 +197,8 @@ source_set("unit_tests") { "actions/prompt_action_unittest.cc", "actions/set_form_field_value_action_unittest.cc", "actions/show_details_action_unittest.cc", + "actions/use_address_action_unittest.cc", + "actions/use_credit_card_action_unittest.cc", "actions/wait_for_document_action_unittest.cc", "actions/wait_for_dom_action_unittest.cc", "batch_element_checker_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 74360d76adb..e418be400cc 100644 --- a/chromium/components/autofill_assistant/browser/actions/action_delegate.h +++ b/chromium/components/autofill_assistant/browser/actions/action_delegate.h @@ -74,8 +74,9 @@ class ActionDelegate { // // TODO(crbug.com/806868): Consider embedding that wait right into // WebController and eliminate double-lookup. - virtual void ShortWaitForElement(const Selector& selector, - base::OnceCallback<void(bool)> callback) = 0; + virtual void ShortWaitForElement( + const Selector& selector, + base::OnceCallback<void(const ClientStatus&)> callback) = 0; // Wait for up to |max_wait_time| for element conditions to match on the page, // then call |callback| with a successful status if at least an element @@ -85,10 +86,10 @@ class ActionDelegate { virtual void WaitForDom( base::TimeDelta max_wait_time, bool allow_interrupt, - base::RepeatingCallback<void(BatchElementChecker*, - base::OnceCallback<void(bool)>)> - check_elements, - base::OnceCallback<void(ProcessedActionStatusProto)> callback) = 0; + base::RepeatingCallback< + void(BatchElementChecker*, + base::OnceCallback<void(const ClientStatus&)>)> check_elements, + base::OnceCallback<void(const ClientStatus&)> callback) = 0; // Click or tap the element given by |selector| on the web page. virtual void ClickOrTapElement( @@ -167,7 +168,8 @@ class ActionDelegate { // empty string in case of error or empty value. virtual void GetFieldValue( const Selector& selector, - base::OnceCallback<void(bool, const std::string&)> callback) = 0; + base::OnceCallback<void(const ClientStatus&, const std::string&)> + callback) = 0; // Set the |value| of field |selector| and return the result through // |callback|. If |simulate_key_presses| is true, the value will be set by diff --git a/chromium/components/autofill_assistant/browser/actions/autofill_action.cc b/chromium/components/autofill_assistant/browser/actions/autofill_action.cc deleted file mode 100644 index 4f67d3fda67..00000000000 --- a/chromium/components/autofill_assistant/browser/actions/autofill_action.cc +++ /dev/null @@ -1,372 +0,0 @@ -// Copyright 2018 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/autofill_action.h" - -#include <utility> -#include <vector> - -#include "base/bind.h" -#include "base/callback.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" -#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/batch_element_checker.h" -#include "components/autofill_assistant/browser/client_memory.h" -#include "components/autofill_assistant/browser/client_status.h" - -namespace autofill_assistant { - -AutofillAction::AutofillAction(ActionDelegate* delegate, - const ActionProto& proto) - : Action(delegate, proto) { - if (proto.has_use_address()) { - is_autofill_card_ = false; - prompt_ = proto.use_address().prompt(); - name_ = proto.use_address().name(); - selector_ = Selector(proto.use_address().form_field_element()); - for (const auto& required_field_proto : - proto_.use_address().required_fields()) { - required_fields_.emplace_back(); - RequiredField& required_field = required_fields_.back(); - required_field.address_field = required_field_proto.address_field(); - required_field.selector = Selector(required_field_proto.element()); - required_field.simulate_key_presses = - required_field_proto.simulate_key_presses(); - required_field.delay_in_millisecond = - required_field_proto.delay_in_millisecond(); - required_field.forced = required_field_proto.forced(); - } - } else { - DCHECK(proto.has_use_card()); - is_autofill_card_ = true; - prompt_ = proto.use_card().prompt(); - name_ = ""; - selector_ = Selector(proto.use_card().form_field_element()); - for (const auto& required_field_proto : - proto_.use_card().required_fields()) { - required_fields_.emplace_back(); - RequiredField& required_field = required_fields_.back(); - required_field.card_field = required_field_proto.card_field(); - required_field.selector = Selector(required_field_proto.element()); - required_field.simulate_key_presses = - required_field_proto.simulate_key_presses(); - required_field.delay_in_millisecond = - required_field_proto.delay_in_millisecond(); - required_field.forced = required_field_proto.forced(); - } - } - selector_.MustBeVisible(); - DCHECK(!selector_.empty()); -} - -AutofillAction::~AutofillAction() = default; - -void AutofillAction::InternalProcessAction( - ProcessActionCallback action_callback) { - process_action_callback_ = std::move(action_callback); - - // Ensure data already selected in a previous action. - bool has_valid_data = - (is_autofill_card_ && delegate_->GetClientMemory()->selected_card()) || - (!is_autofill_card_ && - delegate_->GetClientMemory()->selected_address(name_)); - if (!has_valid_data) { - 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( - delegate_->GetClientMemory()->GetAllAddressKeyNames()); - EndAction(PRECONDITION_FAILED); - return; - } - - FillFormWithData(); -} - -void AutofillAction::EndAction(ProcessedActionStatusProto status) { - EndAction(ClientStatus(status)); -} - -void AutofillAction::EndAction(const ClientStatus& status) { - UpdateProcessedAction(status); - std::move(process_action_callback_).Run(std::move(processed_action_proto_)); -} - -void AutofillAction::FillFormWithData() { - delegate_->ShortWaitForElement( - selector_, base::BindOnce(&AutofillAction::OnWaitForElement, - weak_ptr_factory_.GetWeakPtr())); -} - -void AutofillAction::OnWaitForElement(bool element_found) { - if (!element_found) { - EndAction(ELEMENT_RESOLUTION_FAILED); - return; - } - - DCHECK(!selector_.empty()); - if (is_autofill_card_) { - delegate_->GetFullCard(base::BindOnce(&AutofillAction::OnGetFullCard, - weak_ptr_factory_.GetWeakPtr())); - return; - } - - DVLOG(3) << "Retrieving address from client memory under '" << name_ << "'."; - const autofill::AutofillProfile* profile = - delegate_->GetClientMemory()->selected_address(name_); - DCHECK(profile); - auto fallback_data = std::make_unique<FallbackData>(); - fallback_data->profile = profile; - delegate_->FillAddressForm( - profile, selector_, - base::BindOnce(&AutofillAction::OnFormFilled, - weak_ptr_factory_.GetWeakPtr(), std::move(fallback_data))); -} - -void AutofillAction::OnGetFullCard(std::unique_ptr<autofill::CreditCard> card, - const base::string16& cvc) { - if (!card) { - EndAction(GET_FULL_CARD_FAILED); - return; - } - - auto fallback_data = std::make_unique<FallbackData>(); - fallback_data->cvc = base::UTF16ToUTF8(cvc); - fallback_data->expiration_month = card->expiration_month(); - fallback_data->expiration_year = card->expiration_year(); - - delegate_->FillCardForm( - std::move(card), cvc, selector_, - base::BindOnce(&AutofillAction::OnFormFilled, - weak_ptr_factory_.GetWeakPtr(), std::move(fallback_data))); -} - -void AutofillAction::OnFormFilled(std::unique_ptr<FallbackData> fallback_data, - const ClientStatus& status) { - // In case Autofill failed, we fail the action. - if (!status.ok()) { - EndAction(status); - return; - } - CheckRequiredFields(std::move(fallback_data)); -} - -void AutofillAction::CheckRequiredFields( - std::unique_ptr<FallbackData> fallback_data) { - // If there are no required fields, finish the action successfully. - if (required_fields_.empty()) { - EndAction(ACTION_APPLIED); - return; - } - - DCHECK(!batch_element_checker_); - batch_element_checker_ = std::make_unique<BatchElementChecker>(); - for (size_t i = 0; i < required_fields_.size(); i++) { - if (required_fields_[i].forced) - continue; - - batch_element_checker_->AddFieldValueCheck( - required_fields_[i].selector, - base::BindOnce(&AutofillAction::OnGetRequiredFieldValue, - weak_ptr_factory_.GetWeakPtr(), i)); - } - batch_element_checker_->AddAllDoneCallback( - base::BindOnce(&AutofillAction::OnCheckRequiredFieldsDone, - weak_ptr_factory_.GetWeakPtr(), std::move(fallback_data))); - delegate_->RunElementChecks(batch_element_checker_.get()); -} - -void AutofillAction::OnGetRequiredFieldValue(size_t required_fields_index, - bool exists, - const std::string& value) { - required_fields_[required_fields_index].status = - value.empty() ? EMPTY : NOT_EMPTY; -} - -void AutofillAction::OnCheckRequiredFieldsDone( - std::unique_ptr<FallbackData> fallback_data) { - 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)) { - should_fallback = true; - break; - } - } - - if (!should_fallback) { - EndAction(ACTION_APPLIED); - return; - } - - if (!fallback_data) { - // Validation failed and we don't want to try the fallback. - EndAction(MANUAL_FALLBACK); - return; - } - - // If there are any fallbacks for the empty fields, set them, otherwise fail - // immediately. - bool has_fallbacks = false; - for (const RequiredField& required_field : required_fields_) { - if (!required_field.ShouldFallback(/* has_fallback_data= */ true)) - continue; - - if (!GetFallbackValue(required_field, *fallback_data).empty()) { - has_fallbacks = true; - } - } - if (!has_fallbacks) { - EndAction(MANUAL_FALLBACK); - return; - } - - // Set the fallback values and check again. - SetFallbackFieldValuesSequentially(0, std::move(fallback_data)); -} - -void AutofillAction::SetFallbackFieldValuesSequentially( - size_t required_fields_index, - std::unique_ptr<FallbackData> fallback_data) { - // Skip non-empty fields. - while (required_fields_index < required_fields_.size() && - !required_fields_[required_fields_index].ShouldFallback( - fallback_data != nullptr)) { - required_fields_index++; - } - - // If there are no more fields to set, check the required fields again, - // but this time we don't want to try the fallback in case of failure. - if (required_fields_index >= required_fields_.size()) { - DCHECK_EQ(required_fields_index, required_fields_.size()); - - CheckRequiredFields(/* fallback_data= */ nullptr); - return; - } - - // Set the next field to its fallback value. - const RequiredField& required_field = required_fields_[required_fields_index]; - std::string fallback_value = GetFallbackValue(required_field, *fallback_data); - if (fallback_value.empty()) { - DVLOG(3) << "No fallback for " << required_field.selector; - // If there is no fallback value, we skip this failed field. - SetFallbackFieldValuesSequentially(++required_fields_index, - std::move(fallback_data)); - return; - } - DVLOG(3) << "Setting fallback value for " << required_field.selector; - - delegate_->SetFieldValue( - required_field.selector, fallback_value, - required_field.simulate_key_presses, required_field.delay_in_millisecond, - base::BindOnce(&AutofillAction::OnSetFallbackFieldValue, - weak_ptr_factory_.GetWeakPtr(), required_fields_index, - std::move(fallback_data))); -} - -void AutofillAction::OnSetFallbackFieldValue( - size_t required_fields_index, - std::unique_ptr<FallbackData> fallback_data, - const ClientStatus& status) { - if (!status.ok()) { - // Fallback failed: we stop the script without checking the fields. - EndAction(MANUAL_FALLBACK); - return; - } - SetFallbackFieldValuesSequentially(++required_fields_index, - std::move(fallback_data)); -} - -std::string AutofillAction::GetFallbackValue( - const RequiredField& required_field, - const FallbackData& fallback_data) { - if (required_field.address_field != - UseAddressProto::RequiredField::UNDEFINED && - fallback_data.profile) { - return base::UTF16ToUTF8(GetAddressFieldValue( - fallback_data.profile, required_field.address_field)); - } - if (required_field.card_field != - UseCreditCardProto::RequiredField::UNDEFINED) { - return GetCreditCardFieldValue(required_field.card_field, fallback_data); - } - NOTREACHED() << "Unsupported field type for " << required_field.selector; - return ""; -} - -std::string AutofillAction::GetCreditCardFieldValue( - UseCreditCardProto::RequiredField::CardField field, - const FallbackData& fallback_data) { - switch (field) { - case UseCreditCardProto::RequiredField::CREDIT_CARD_VERIFICATION_CODE: - return fallback_data.cvc; - - case UseCreditCardProto::RequiredField::CREDIT_CARD_EXP_MONTH: - if (fallback_data.expiration_month > 0) - return base::StringPrintf("%02d", fallback_data.expiration_month); - break; - - case UseCreditCardProto::RequiredField::CREDIT_CARD_EXP_2_DIGIT_YEAR: - if (fallback_data.expiration_year > 0) - return base::StringPrintf("%02d", fallback_data.expiration_year % 100); - break; - - case UseCreditCardProto::RequiredField::CREDIT_CARD_EXP_4_DIGIT_YEAR: - if (fallback_data.expiration_year > 0) - return base::NumberToString(fallback_data.expiration_year); - break; - - case UseCreditCardProto::RequiredField::UNDEFINED: - NOTREACHED(); - return ""; - } - return ""; -} - -base::string16 AutofillAction::GetAddressFieldValue( - const autofill::AutofillProfile* profile, - const UseAddressProto::RequiredField::AddressField& address_field) { - // TODO(crbug.com/806868): Get the actual application locale. - std::string app_locale = "en-US"; - switch (address_field) { - case UseAddressProto::RequiredField::FIRST_NAME: - return profile->GetInfo(autofill::NAME_FIRST, app_locale); - case UseAddressProto::RequiredField::LAST_NAME: - return profile->GetInfo(autofill::NAME_LAST, app_locale); - case UseAddressProto::RequiredField::FULL_NAME: - return profile->GetInfo(autofill::NAME_FULL, app_locale); - case UseAddressProto::RequiredField::PHONE_NUMBER: - return profile->GetInfo(autofill::PHONE_HOME_WHOLE_NUMBER, app_locale); - case UseAddressProto::RequiredField::EMAIL: - return profile->GetInfo(autofill::EMAIL_ADDRESS, app_locale); - case UseAddressProto::RequiredField::ORGANIZATION: - return profile->GetInfo(autofill::COMPANY_NAME, app_locale); - case UseAddressProto::RequiredField::COUNTRY_CODE: - return profile->GetInfo(autofill::ADDRESS_HOME_COUNTRY, app_locale); - case UseAddressProto::RequiredField::REGION: - return profile->GetInfo(autofill::ADDRESS_HOME_STATE, app_locale); - case UseAddressProto::RequiredField::STREET_ADDRESS: - return profile->GetInfo(autofill::ADDRESS_HOME_STREET_ADDRESS, - app_locale); - case UseAddressProto::RequiredField::LOCALITY: - return profile->GetInfo(autofill::ADDRESS_HOME_CITY, app_locale); - case UseAddressProto::RequiredField::DEPENDANT_LOCALITY: - return profile->GetInfo(autofill::ADDRESS_HOME_DEPENDENT_LOCALITY, - app_locale); - case UseAddressProto::RequiredField::POSTAL_CODE: - return profile->GetInfo(autofill::ADDRESS_HOME_ZIP, app_locale); - case UseAddressProto::RequiredField::UNDEFINED: - NOTREACHED(); - return base::string16(); - } -} -} // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/actions/autofill_action.h b/chromium/components/autofill_assistant/browser/actions/autofill_action.h deleted file mode 100644 index 327a55a1c69..00000000000 --- a/chromium/components/autofill_assistant/browser/actions/autofill_action.h +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2018 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_AUTOFILL_ACTION_H_ -#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_AUTOFILL_ACTION_H_ - -#include <memory> -#include <string> -#include <vector> - -#include "base/callback.h" -#include "base/macros.h" -#include "base/memory/weak_ptr.h" -#include "components/autofill_assistant/browser/actions/action.h" -#include "components/autofill_assistant/browser/batch_element_checker.h" - -namespace autofill { -class AutofillProfile; -class CreditCard; -} // namespace autofill - -namespace autofill_assistant { -class ClientStatus; - -// An action to autofill a form using a local address or credit card. -class AutofillAction : public Action { - public: - explicit AutofillAction(ActionDelegate* delegate, const ActionProto& proto); - ~AutofillAction() override; - - private: - enum FieldValueStatus { UNKNOWN, EMPTY, NOT_EMPTY }; - struct RequiredField { - Selector selector; - bool simulate_key_presses = false; - int delay_in_millisecond = 0; - bool forced = false; - FieldValueStatus status = UNKNOWN; - - // When filling in credit card, card_field must be set. When filling in - // address, address_field must be set. - UseCreditCardProto::RequiredField::CardField card_field = - UseCreditCardProto::RequiredField::UNDEFINED; - UseAddressProto::RequiredField::AddressField address_field = - UseAddressProto::RequiredField::UNDEFINED; - - // Returns true if fallback is required for this field. - bool ShouldFallback(bool has_fallback_data) const { - return status == EMPTY || (forced && has_fallback_data); - } - }; - - // 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 { - FallbackData() = default; - ~FallbackData() = default; - - // Profile for UseAddress fallback. - const autofill::AutofillProfile* profile = nullptr; - - // Card information for UseCreditCard fallback. - std::string cvc; - int expiration_year = 0; - int expiration_month = 0; - - private: - DISALLOW_COPY_AND_ASSIGN(FallbackData); - }; - - // Overrides Action: - void InternalProcessAction(ProcessActionCallback callback) override; - - void EndAction(ProcessedActionStatusProto status); - void EndAction(const ClientStatus& status); - - // Fill the form using data in client memory. Return whether filling succeeded - // or not through OnAddressFormFilled or OnCardFormFilled. - void FillFormWithData(); - void OnWaitForElement(bool element_found); - - // Called after getting full credit card with its cvc. - void OnGetFullCard(std::unique_ptr<autofill::CreditCard> card, - const base::string16& cvc); - - // Called when the form, credit card or address, has been filled. - void OnFormFilled(std::unique_ptr<FallbackData> fallback_data, - const ClientStatus& status); - - // Check whether all required fields have a non-empty value. If it is the - // case, finish the action successfully. If it's not and |fallback_data| - // is null, fail the action. If |fallback_data| is non-null, use it to attempt - // to fill the failed fields without Autofill. - void CheckRequiredFields(std::unique_ptr<FallbackData> fallback_data); - - // Triggers the check for a specific field. - void CheckRequiredFieldsSequentially( - bool allow_fallback, - size_t required_fields_index, - std::unique_ptr<FallbackData> fallback_data); - - // Updates |required_fields_value_status_|. - void OnGetRequiredFieldValue(size_t required_fields_index, - bool exists, - const std::string& value); - - // Called when all required fields have been checked. - void OnCheckRequiredFieldsDone(std::unique_ptr<FallbackData> fallback_data); - - // Gets the fallback value. - std::string GetFallbackValue(const RequiredField& required_field, - const FallbackData& fallback_data); - - // Gets the value of |field| from |fallback_data|, if available. Returns an - // empty string otherwise. - std::string GetCreditCardFieldValue( - UseCreditCardProto::RequiredField::CardField field, - const FallbackData& fallback_data); - - // Get the value of |address_field| associated to profile |profile|. Return - // empty string if there is no data available. - base::string16 GetAddressFieldValue( - const autofill::AutofillProfile* profile, - const UseAddressProto::RequiredField::AddressField& address_field); - - // Sets fallback field values for empty fields from - // |required_fields_value_status_|. - void SetFallbackFieldValuesSequentially( - size_t required_fields_index, - std::unique_ptr<FallbackData> fallback_data); - - // 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); - - // Usage of the autofilled address. Ignored if autofilling a card. - std::string name_; - std::string prompt_; - Selector selector_; - - // True if autofilling a card, otherwise we are autofilling an address. - bool is_autofill_card_; - std::vector<RequiredField> required_fields_; - - std::unique_ptr<BatchElementChecker> batch_element_checker_; - - ProcessActionCallback process_action_callback_; - base::WeakPtrFactory<AutofillAction> weak_ptr_factory_{this}; - - DISALLOW_COPY_AND_ASSIGN(AutofillAction); -}; - -} // namespace autofill_assistant -#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_AUTOFILL_ACTION_H_ diff --git a/chromium/components/autofill_assistant/browser/actions/click_action.cc b/chromium/components/autofill_assistant/browser/actions/click_action.cc index a26f48cc922..b96e0ed6f93 100644 --- a/chromium/components/autofill_assistant/browser/actions/click_action.cc +++ b/chromium/components/autofill_assistant/browser/actions/click_action.cc @@ -9,6 +9,7 @@ #include "base/bind.h" #include "base/callback.h" #include "components/autofill_assistant/browser/actions/action_delegate.h" +#include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/service.pb.h" namespace autofill_assistant { @@ -49,9 +50,9 @@ void ClickAction::InternalProcessAction(ProcessActionCallback callback) { void ClickAction::OnWaitForElement(ProcessActionCallback callback, const Selector& selector, - bool element_found) { - if (!element_found) { - UpdateProcessedAction(ELEMENT_RESOLUTION_FAILED); + const ClientStatus& element_status) { + if (!element_status.ok()) { + UpdateProcessedAction(element_status.proto_status()); std::move(callback).Run(std::move(processed_action_proto_)); return; } diff --git a/chromium/components/autofill_assistant/browser/actions/click_action.h b/chromium/components/autofill_assistant/browser/actions/click_action.h index 5c2b6afc2fe..ecd21c258a7 100644 --- a/chromium/components/autofill_assistant/browser/actions/click_action.h +++ b/chromium/components/autofill_assistant/browser/actions/click_action.h @@ -29,7 +29,7 @@ class ClickAction : public Action { void OnWaitForElement(ProcessActionCallback callback, const Selector& selector, - bool element_found); + const ClientStatus& element_status); void OnClick(ProcessActionCallback callback, const ClientStatus& status); ClickType click_type_; 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 5bf0d0be09f..d29f1a1600f 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 @@ -5,6 +5,7 @@ #include "components/autofill_assistant/browser/actions/collect_user_data_action.h" #include <algorithm> +#include <set> #include <utility> #include "base/android/locale_utils.h" @@ -32,6 +33,7 @@ namespace { using autofill_assistant::CollectUserDataOptions; +using autofill_assistant::DateTimeProto; using autofill_assistant::TermsAndConditionsState; bool IsCompleteContact( const autofill::AutofillProfile* profile, @@ -81,14 +83,6 @@ bool IsCompleteAddress(const autofill::AutofillProfile* profile, return true; } -bool IsCompleteBillingAddress( - const autofill::AutofillProfile* profile, - const CollectUserDataOptions& collect_user_data_options) { - return !collect_user_data_options.request_payment_method || - IsCompleteAddress( - profile, collect_user_data_options.require_billing_postal_code); -} - bool IsCompleteShippingAddress( const autofill::AutofillProfile* profile, const CollectUserDataOptions& collect_user_data_options) { @@ -97,14 +91,20 @@ bool IsCompleteShippingAddress( } bool IsCompleteCreditCard( - autofill::PersonalDataManager* personal_data_manager, 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) { + if (!credit_card || !billing_profile) { + return false; + } + + if (!IsCompleteAddress( + billing_profile, + collect_user_data_options.require_billing_postal_code)) { return false; } @@ -120,12 +120,6 @@ bool IsCompleteCreditCard( return false; } - auto* address_profile = personal_data_manager->GetProfileByGUID( - credit_card->billing_address_id()); - if (!IsCompleteBillingAddress(address_profile, collect_user_data_options)) { - return false; - } - std::string basic_card_network = autofill::data_util::GetPaymentRequestData(credit_card->network()) .basic_card_issuer_network; @@ -153,6 +147,71 @@ bool IsValidTermsChoice( terms_state != TermsAndConditionsState::NOT_SELECTED; } +// Comparison function for |DateTimeProto|. +// Returns 0 if equal, < 0 if |first| < |second|, > 0 if |second| > |first|. +int CompareDateTimes(const DateTimeProto& first, const DateTimeProto& second) { + auto first_tuple = std::make_tuple( + first.date().year(), first.date().month(), first.date().day(), + first.time().hour(), first.time().minute(), first.time().second()); + auto second_tuple = std::make_tuple( + second.date().year(), second.date().month(), second.date().day(), + second.time().hour(), second.time().minute(), second.time().second()); + if (first_tuple < second_tuple) { + return -1; + } else if (second_tuple < first_tuple) { + return 1; + } + return 0; +} + +bool IsValidDateTimeRange( + const DateTimeProto& start, + const DateTimeProto& end, + const CollectUserDataOptions& collect_user_data_options) { + return !collect_user_data_options.request_date_time_range || + CompareDateTimes(start, end) < 0; +} + +bool IsValidUserFormSection( + const autofill_assistant::UserFormSectionProto& proto) { + if (proto.title().empty()) { + DVLOG(2) << "UserFormSectionProto: Empty title not allowed."; + return false; + } + switch (proto.section_case()) { + case autofill_assistant::UserFormSectionProto::kStaticTextSection: + if (proto.static_text_section().text().empty()) { + DVLOG(2) << "StaticTextSectionProto: Empty text not allowed."; + return false; + } + break; + case autofill_assistant::UserFormSectionProto::kTextInputSection: { + if (proto.text_input_section().input_fields().empty()) { + DVLOG(2) << "TextInputProto: At least one input must be specified."; + return false; + } + std::set<std::string> memory_keys; + for (const auto& input_field : + proto.text_input_section().input_fields()) { + if (input_field.client_memory_key().empty()) { + DVLOG(2) << "TextInputProto: Memory key must be specified."; + return false; + } + if (!memory_keys.insert(input_field.client_memory_key()).second) { + DVLOG(2) << "TextInputProto: Duplicate memory keys (" + << input_field.client_memory_key() << ")."; + return false; + } + } + break; + } + case autofill_assistant::UserFormSectionProto::SECTION_NOT_SET: + DVLOG(2) << "UserFormSectionProto: section oneof not set."; + return false; + } + return true; +} + } // namespace namespace autofill_assistant { @@ -250,15 +309,19 @@ void CollectUserDataAction::OnGetLogins( std::unique_ptr<CollectUserDataOptions> collect_user_data_options, std::vector<WebsiteLoginFetcher::Login> logins) { for (const auto& login : logins) { - LoginChoice choice = { - base::NumberToString(collect_user_data_options->login_choices.size()), - login.username, login_option.preselection_priority()}; - collect_user_data_options->login_choices.emplace_back(std::move(choice)); + auto identifier = + base::NumberToString(collect_user_data_options->login_choices.size()); + collect_user_data_options->login_choices.emplace_back( + identifier, login.username, login_option.sublabel(), + login_option.sublabel_accessibility_hint(), + login_option.preselection_priority(), + login_option.has_info_popup() + ? base::make_optional(login_option.info_popup()) + : base::nullopt); login_details_map_.emplace( - choice.identifier, - std::make_unique<LoginDetails>( - login_option.choose_automatically_if_no_other_options(), - login_option.payload(), login)); + identifier, std::make_unique<LoginDetails>( + login_option.choose_automatically_if_no_other_options(), + login_option.payload(), login)); } ShowToUser(std::move(collect_user_data_options)); } @@ -279,6 +342,28 @@ void CollectUserDataAction::ShowToUser( user_data->terms_and_conditions = REQUIRES_REVIEW; break; } + for (const auto& additional_section : + collect_user_data.additional_prepended_sections()) { + if (additional_section.section_case() == + UserFormSectionProto::kTextInputSection) { + for (const auto& text_input : + additional_section.text_input_section().input_fields()) { + user_data->additional_values_to_store.emplace( + text_input.client_memory_key(), text_input.value()); + } + } + } + for (const auto& additional_section : + collect_user_data.additional_appended_sections()) { + if (additional_section.section_case() == + UserFormSectionProto::kTextInputSection) { + for (const auto& text_input : + additional_section.text_input_section().input_fields()) { + user_data->additional_values_to_store.emplace( + text_input.client_memory_key(), text_input.value()); + } + } + } if (collect_user_data_options->request_login_choice && collect_user_data_options->login_choices.empty()) { @@ -403,6 +488,18 @@ void CollectUserDataAction::OnGetUserData( ->set_login_payload(login_details->second->payload); } + if (collect_user_data.has_date_time_range()) { + *processed_action_proto_->mutable_collect_user_data_result() + ->mutable_date_time_start() = user_data->date_time_range_start; + *processed_action_proto_->mutable_collect_user_data_result() + ->mutable_date_time_end() = user_data->date_time_range_end; + } + + for (const auto& value : user_data->additional_values_to_store) { + delegate_->GetClientMemory()->set_additional_value(value.first, + value.second); + } + processed_action_proto_->mutable_collect_user_data_result() ->set_is_terms_and_conditions_accepted( user_data->terms_and_conditions == @@ -477,9 +574,14 @@ CollectUserDataAction::CreateOptionsFromProto() { base::NumberToString( collect_user_data_options->login_choices.size()), login_option.custom().label(), + login_option.sublabel(), + login_option.sublabel_accessibility_hint(), login_option.has_preselection_priority() ? login_option.preselection_priority() - : -1}; + : -1, + login_option.has_info_popup() + ? base::make_optional(login_option.info_popup()) + : base::nullopt}; collect_user_data_options->login_choices.emplace_back( std::move(choice)); login_details_map_.emplace( @@ -500,6 +602,42 @@ CollectUserDataAction::CreateOptionsFromProto() { } } + if (collect_user_data.has_date_time_range()) { + if (!collect_user_data.date_time_range().has_start_label() || + !collect_user_data.date_time_range().has_end_label() || + !collect_user_data.date_time_range().has_start() || + !collect_user_data.date_time_range().has_end() || + !collect_user_data.date_time_range().has_min() || + !collect_user_data.date_time_range().has_max()) { + DVLOG(1) << "Invalid action: missing one or more of the required fields " + "'start', 'end', 'min', 'max', 'start_label', end_label'."; + return nullptr; + } + collect_user_data_options->request_date_time_range = true; + collect_user_data_options->date_time_range = + collect_user_data.date_time_range(); + } + + for (const auto& section : + collect_user_data.additional_prepended_sections()) { + if (!IsValidUserFormSection(section)) { + DVLOG(1) + << "Invalid UserFormSectionProto in additional_prepended_sections"; + return nullptr; + } + collect_user_data_options->additional_prepended_sections.emplace_back( + section); + } + for (const auto& section : collect_user_data.additional_appended_sections()) { + if (!IsValidUserFormSection(section)) { + DVLOG(1) + << "Invalid UserFormSectionProto in additional_appended_sections"; + return nullptr; + } + collect_user_data_options->additional_appended_sections.emplace_back( + section); + } + // 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(); @@ -521,14 +659,26 @@ CollectUserDataAction::CreateOptionsFromProto() { if (collect_user_data.request_terms_and_conditions()) { collect_user_data_options->show_terms_as_checkbox = collect_user_data.show_terms_as_checkbox(); + + if (collect_user_data.accept_terms_and_conditions_text().empty()) { + return nullptr; + } collect_user_data_options->accept_terms_and_conditions_text = collect_user_data.accept_terms_and_conditions_text(); - if (collect_user_data_options->accept_terms_and_conditions_text.empty()) { - collect_user_data_options->accept_terms_and_conditions_text = - l10n_util::GetStringUTF8( - IDS_AUTOFILL_ASSISTANT_3RD_PARTY_TERMS_ACCEPT); + + if (!collect_user_data.show_terms_as_checkbox() && + collect_user_data.terms_require_review_text().empty()) { + return nullptr; } + collect_user_data_options->terms_require_review_text = + collect_user_data.terms_require_review_text(); + } + + if (collect_user_data.thirdparty_privacy_notice_text().empty()) { + return nullptr; } + collect_user_data_options->thirdparty_privacy_notice_text = + collect_user_data.thirdparty_privacy_notice_text(); collect_user_data_options->default_email = delegate_->GetAccountEmailAddress(); @@ -574,8 +724,14 @@ bool CollectUserDataAction::CheckInitialAutofillDataComplete( credit_cards.begin(), credit_cards.end(), [&collect_user_data_options, personal_data_manager](const auto* credit_card) { - return IsCompleteCreditCard(personal_data_manager, credit_card, - collect_user_data_options); + // TODO(b/142630213): Figure out how to retrieve billing profile if + // user has turned off addresses in Chrome settings. + return IsCompleteCreditCard( + credit_card, + credit_card != nullptr + ? personal_data_manager->GetProfileByGUID(credit_card->guid()) + : nullptr, + collect_user_data_options); }); if (completeCardIter == credit_cards.end()) { return false; @@ -589,16 +745,16 @@ bool CollectUserDataAction::CheckInitialAutofillDataComplete( // static bool CollectUserDataAction::IsUserDataComplete( - autofill::PersonalDataManager* personal_data_manager, const UserData& user_data, const CollectUserDataOptions& options) { return IsCompleteContact(user_data.contact_profile.get(), options) && - IsCompleteBillingAddress(user_data.billing_address.get(), options) && IsCompleteShippingAddress(user_data.shipping_address.get(), options) && - IsCompleteCreditCard(personal_data_manager, user_data.card.get(), - options) && + IsCompleteCreditCard(user_data.card.get(), + user_data.billing_address.get(), options) && IsValidLoginChoice(user_data.login_choice_identifier, options) && - IsValidTermsChoice(user_data.terms_and_conditions, options); + IsValidTermsChoice(user_data.terms_and_conditions, options) && + IsValidDateTimeRange(user_data.date_time_range_start, + user_data.date_time_range_end, options); } void CollectUserDataAction::OnPersonalDataChanged() { diff --git a/chromium/components/autofill_assistant/browser/actions/collect_user_data_action.h b/chromium/components/autofill_assistant/browser/actions/collect_user_data_action.h index b6c9abc520f..86bbf7100d8 100644 --- a/chromium/components/autofill_assistant/browser/actions/collect_user_data_action.h +++ b/chromium/components/autofill_assistant/browser/actions/collect_user_data_action.h @@ -33,7 +33,6 @@ class CollectUserDataAction : public Action, void OnPersonalDataChanged() override; static bool IsUserDataComplete( - autofill::PersonalDataManager* personal_data_manager, const UserData& user_data, const CollectUserDataOptions& collect_user_data_options); 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 166759c831c..60985d3705c 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 @@ -30,6 +30,28 @@ const char kMemoryLocation[] = "billing"; namespace autofill_assistant { namespace { +void SetDateTimeProto(DateTimeProto* proto, + int year, + int month, + int day, + int hour, + int minute, + int second) { + proto->mutable_date()->set_year(year); + proto->mutable_date()->set_month(month); + proto->mutable_date()->set_day(day); + proto->mutable_time()->set_hour(hour); + proto->mutable_time()->set_minute(minute); + proto->mutable_time()->set_second(second); +} + +MATCHER_P(EqualsProto, message, "") { + std::string expected_serialized, actual_serialized; + message.SerializeToString(&expected_serialized); + arg.SerializeToString(&actual_serialized); + return expected_serialized == actual_serialized; +} + using ::base::test::RunOnceCallback; using ::testing::_; using ::testing::Eq; @@ -37,6 +59,17 @@ using ::testing::Invoke; using ::testing::Property; using ::testing::Return; +void SetRequiredTermsFields(CollectUserDataProto* data, + bool request_terms_and_conditions = false) { + data->set_thirdparty_privacy_notice_text("privacy"); + + if (request_terms_and_conditions) { + data->set_accept_terms_and_conditions_text("terms and conditions"); + data->set_terms_require_review_text("terms review"); + } + data->set_request_terms_and_conditions(request_terms_and_conditions); +} + class CollectUserDataActionTest : public content::RenderViewHostTestHarness { public: void SetUp() override { @@ -77,20 +110,158 @@ class CollectUserDataActionTest : public content::RenderViewHostTestHarness { ClientMemory client_memory_; }; +TEST_F(CollectUserDataActionTest, FailsForMissingPrivacyText) { + ActionProto action_proto; + action_proto.mutable_collect_user_data(); + + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, INVALID_ACTION)))); + CollectUserDataAction action(&mock_action_delegate_, action_proto); + action.ProcessAction(callback_.Get()); +} + +TEST_F(CollectUserDataActionTest, SucceedsForPrivacyTextPresent) { + ActionProto action_proto; + auto* collect_user_data_proto = action_proto.mutable_collect_user_data(); + collect_user_data_proto->set_thirdparty_privacy_notice_text("privacy"); + collect_user_data_proto->set_request_terms_and_conditions(false); + + ON_CALL(mock_action_delegate_, CollectUserData(_, _)) + .WillByDefault(Invoke( + [](std::unique_ptr<CollectUserDataOptions> collect_user_data_options, + std::unique_ptr<UserData> user_data) { + user_data->succeed = true; + user_data->terms_and_conditions = ACCEPTED; + std::move(collect_user_data_options->confirm_callback) + .Run(std::move(user_data)); + })); + + EXPECT_CALL( + callback_, + Run(Pointee(AllOf( + Property(&ProcessedActionProto::status, ACTION_APPLIED), + Property( + &ProcessedActionProto::collect_user_data_result, + Property( + &CollectUserDataResultProto::is_terms_and_conditions_accepted, + true)))))); + CollectUserDataAction action(&mock_action_delegate_, action_proto); + action.ProcessAction(callback_.Get()); +} + +TEST_F(CollectUserDataActionTest, FailsForMissingTermsAcceptTextIfRequired) { + ActionProto action_proto; + auto* collect_user_data_proto = action_proto.mutable_collect_user_data(); + collect_user_data_proto->set_thirdparty_privacy_notice_text("privacy"); + collect_user_data_proto->set_request_terms_and_conditions(true); + collect_user_data_proto->set_terms_require_review_text("terms review"); + + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, INVALID_ACTION)))); + CollectUserDataAction action(&mock_action_delegate_, action_proto); + action.ProcessAction(callback_.Get()); +} + +TEST_F(CollectUserDataActionTest, FailsForMissingTermsReviewTextIfRequired) { + ActionProto action_proto; + auto* collect_user_data_proto = action_proto.mutable_collect_user_data(); + collect_user_data_proto->set_thirdparty_privacy_notice_text("privacy"); + collect_user_data_proto->set_request_terms_and_conditions(true); + collect_user_data_proto->set_accept_terms_and_conditions_text( + "terms and conditions"); + collect_user_data_proto->set_show_terms_as_checkbox(false); + + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, INVALID_ACTION)))); + CollectUserDataAction action(&mock_action_delegate_, action_proto); + action.ProcessAction(callback_.Get()); +} + +TEST_F(CollectUserDataActionTest, SucceedsForCheckboxIfReviewTextMissing) { + ActionProto action_proto; + auto* collect_user_data_proto = action_proto.mutable_collect_user_data(); + collect_user_data_proto->set_thirdparty_privacy_notice_text("privacy"); + collect_user_data_proto->set_request_terms_and_conditions(true); + collect_user_data_proto->set_accept_terms_and_conditions_text( + "terms and conditions"); + collect_user_data_proto->set_show_terms_as_checkbox(true); + + ON_CALL(mock_action_delegate_, CollectUserData(_, _)) + .WillByDefault(Invoke( + [](std::unique_ptr<CollectUserDataOptions> collect_user_data_options, + std::unique_ptr<UserData> user_data) { + user_data->succeed = true; + user_data->terms_and_conditions = ACCEPTED; + std::move(collect_user_data_options->confirm_callback) + .Run(std::move(user_data)); + })); + + EXPECT_CALL( + callback_, + Run(Pointee(AllOf( + Property(&ProcessedActionProto::status, ACTION_APPLIED), + Property( + &ProcessedActionProto::collect_user_data_result, + Property( + &CollectUserDataResultProto::is_terms_and_conditions_accepted, + true)))))); + CollectUserDataAction action(&mock_action_delegate_, action_proto); + action.ProcessAction(callback_.Get()); +} + +TEST_F(CollectUserDataActionTest, SucceedsForAllTermsTextPresent) { + ActionProto action_proto; + auto* collect_user_data_proto = action_proto.mutable_collect_user_data(); + collect_user_data_proto->set_thirdparty_privacy_notice_text("privacy"); + collect_user_data_proto->set_request_terms_and_conditions(true); + collect_user_data_proto->set_accept_terms_and_conditions_text( + "terms and conditions"); + collect_user_data_proto->set_show_terms_as_checkbox(false); + collect_user_data_proto->set_terms_require_review_text("terms review"); + + ON_CALL(mock_action_delegate_, CollectUserData(_, _)) + .WillByDefault(Invoke( + [](std::unique_ptr<CollectUserDataOptions> collect_user_data_options, + std::unique_ptr<UserData> user_data) { + user_data->succeed = true; + user_data->terms_and_conditions = ACCEPTED; + std::move(collect_user_data_options->confirm_callback) + .Run(std::move(user_data)); + })); + + EXPECT_CALL( + callback_, + Run(Pointee(AllOf( + Property(&ProcessedActionProto::status, ACTION_APPLIED), + Property( + &ProcessedActionProto::collect_user_data_result, + Property( + &CollectUserDataResultProto::is_terms_and_conditions_accepted, + true)))))); + CollectUserDataAction action(&mock_action_delegate_, action_proto); + action.ProcessAction(callback_.Get()); +} + TEST_F(CollectUserDataActionTest, PromptIsShown) { const char kPrompt[] = "Some message."; ActionProto action_proto; + SetRequiredTermsFields(action_proto.mutable_collect_user_data()); action_proto.mutable_collect_user_data()->set_prompt(kPrompt); - CollectUserDataAction action(&mock_action_delegate_, action_proto); EXPECT_CALL(mock_action_delegate_, SetStatusMessage(kPrompt)); EXPECT_CALL(callback_, Run(_)); + + CollectUserDataAction action(&mock_action_delegate_, action_proto); action.ProcessAction(callback_.Get()); } TEST_F(CollectUserDataActionTest, SelectLogin) { ActionProto action_proto; + SetRequiredTermsFields(action_proto.mutable_collect_user_data()); auto* login_details = action_proto.mutable_collect_user_data()->mutable_login_details(); auto* login_option = login_details->add_login_options(); @@ -126,9 +297,10 @@ TEST_F(CollectUserDataActionTest, SelectLogin) { TEST_F(CollectUserDataActionTest, LoginChoiceAutomaticIfNoOtherOptions) { ActionProto action_proto; - auto* collect_user_data = action_proto.mutable_collect_user_data(); - collect_user_data->set_request_terms_and_conditions(false); - auto* login_details = collect_user_data->mutable_login_details(); + auto* collect_user_data_proto = action_proto.mutable_collect_user_data(); + SetRequiredTermsFields(collect_user_data_proto); + collect_user_data_proto->set_request_terms_and_conditions(false); + auto* login_details = collect_user_data_proto->mutable_login_details(); auto* login_option = login_details->add_login_options(); login_option->mutable_custom()->set_label("Guest Checkout"); login_option->set_payload("guest"); @@ -154,8 +326,9 @@ TEST_F(CollectUserDataActionTest, LoginChoiceAutomaticIfNoOtherOptions) { TEST_F(CollectUserDataActionTest, SelectLoginFailsIfNoOptionAvailable) { ActionProto action_proto; - auto* collect_user_data = action_proto.mutable_collect_user_data(); - auto* login_details = collect_user_data->mutable_login_details(); + auto* collect_user_data_proto = action_proto.mutable_collect_user_data(); + SetRequiredTermsFields(collect_user_data_proto); + auto* login_details = collect_user_data_proto->mutable_login_details(); auto* login_option = login_details->add_login_options(); login_option->mutable_password_manager(); login_option->set_payload("password_manager"); @@ -173,6 +346,7 @@ TEST_F(CollectUserDataActionTest, SelectLoginFailsIfNoOptionAvailable) { TEST_F(CollectUserDataActionTest, SelectContactDetails) { ActionProto action_proto; auto* collect_user_data_proto = action_proto.mutable_collect_user_data(); + SetRequiredTermsFields(collect_user_data_proto); collect_user_data_proto->set_request_terms_and_conditions(false); auto* contact_details_proto = collect_user_data_proto->mutable_contact_details(); @@ -228,6 +402,7 @@ TEST_F(CollectUserDataActionTest, SelectContactDetails) { TEST_F(CollectUserDataActionTest, SelectPaymentMethod) { ActionProto action_proto; + SetRequiredTermsFields(action_proto.mutable_collect_user_data()); action_proto.mutable_collect_user_data()->set_request_payment_method(true); action_proto.mutable_collect_user_data()->set_request_terms_and_conditions( false); @@ -237,8 +412,6 @@ TEST_F(CollectUserDataActionTest, SelectPaymentMethod) { "Morrison", "marion@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", "91601", "US", "16505678910"); - ON_CALL(mock_personal_data_manager_, GetProfileByGUID(billing_profile.guid())) - .WillByDefault(Return(&billing_profile)); autofill::CreditCard credit_card(base::GenerateGUID(), kFakeUrl); autofill::test::SetCreditCardInfo(&credit_card, "Marion Mitchell", @@ -251,6 +424,8 @@ TEST_F(CollectUserDataActionTest, SelectPaymentMethod) { std::unique_ptr<UserData> user_data) { user_data->card = std::make_unique<autofill::CreditCard>(credit_card); + user_data->billing_address = + std::make_unique<autofill::AutofillProfile>(billing_profile); user_data->succeed = true; std::move(collect_user_data_options->confirm_callback) .Run(std::move(user_data)); @@ -286,6 +461,7 @@ TEST_F(CollectUserDataActionTest, MandatoryPostalCodeWithoutErrorMessageFails) { TEST_F(CollectUserDataActionTest, ContactDetailsCanHandleUtf8) { ActionProto action_proto; auto* collect_user_data_proto = action_proto.mutable_collect_user_data(); + SetRequiredTermsFields(collect_user_data_proto); collect_user_data_proto->set_request_terms_and_conditions(false); auto* contact_details_proto = collect_user_data_proto->mutable_contact_details(); @@ -336,39 +512,32 @@ TEST_F(CollectUserDataActionTest, ContactDetailsCanHandleUtf8) { TEST_F(CollectUserDataActionTest, UserDataComplete_Contact) { UserData user_data; CollectUserDataOptions options; - EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete(user_data, options)); user_data.contact_profile = std::make_unique<autofill::AutofillProfile>( base::GenerateGUID(), kFakeUrl); options.request_payer_email = true; - EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete(user_data, options)); user_data.contact_profile->SetRawInfo( autofill::ServerFieldType::EMAIL_ADDRESS, base::UTF8ToUTF16("joedoe@example.com")); - EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete(user_data, options)); options.request_payer_name = true; - EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete(user_data, options)); user_data.contact_profile->SetRawInfo(autofill::ServerFieldType::NAME_FULL, base::UTF8ToUTF16("Joe Doe")); - EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete(user_data, options)); options.request_payer_phone = true; - EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete(user_data, options)); user_data.contact_profile->SetRawInfo( autofill::ServerFieldType::PHONE_HOME_WHOLE_NUMBER, base::UTF8ToUTF16("+1 23 456 789 01")); - EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete(user_data, options)); } TEST_F(CollectUserDataActionTest, UserDataComplete_Payment) { @@ -376,8 +545,7 @@ TEST_F(CollectUserDataActionTest, UserDataComplete_Payment) { CollectUserDataOptions options; options.request_payment_method = true; - EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete(user_data, options)); // Valid credit card, but no billing address. user_data.card = @@ -385,15 +553,7 @@ TEST_F(CollectUserDataActionTest, UserDataComplete_Payment) { autofill::test::SetCreditCardInfo(user_data.card.get(), "Marion Mitchell", "4111 1111 1111 1111", "01", "2020", /* billing_address_id = */ ""); - EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); - - // Valid credit card, but invalid billing address. - user_data.card->set_billing_address_id("invalid"); - ON_CALL(mock_personal_data_manager_, GetProfileByGUID("invalid")) - .WillByDefault(Return(nullptr)); - EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete(user_data, options)); // Incomplete billing address. user_data.billing_address = std::make_unique<autofill::AutofillProfile>( @@ -402,70 +562,56 @@ TEST_F(CollectUserDataActionTest, UserDataComplete_Payment) { "Mitchell", "Morrison", "marion@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", /* zipcode = */ "", "US", "16505678910"); - ON_CALL(mock_personal_data_manager_, - GetProfileByGUID(user_data.billing_address->guid())) - .WillByDefault(Return(user_data.billing_address.get())); user_data.card->set_billing_address_id(user_data.billing_address->guid()); - EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete(user_data, options)); user_data.billing_address->SetRawInfo(autofill::ADDRESS_HOME_ZIP, base::UTF8ToUTF16("91601")); - EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete(user_data, options)); // Zip code is optional in Argentinian address. user_data.billing_address->SetRawInfo(autofill::ADDRESS_HOME_ZIP, base::UTF8ToUTF16("")); user_data.billing_address->SetRawInfo(autofill::ADDRESS_HOME_COUNTRY, base::UTF8ToUTF16("AR")); - EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete(user_data, options)); options.require_billing_postal_code = true; - EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete(user_data, options)); user_data.billing_address->SetRawInfo(autofill::ADDRESS_HOME_ZIP, base::UTF8ToUTF16("B1675")); - EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete(user_data, options)); } TEST_F(CollectUserDataActionTest, UserDataComplete_Terms) { UserData user_data; CollectUserDataOptions options; options.accept_terms_and_conditions_text.assign("Accept T&C"); - EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete(user_data, options)); user_data.terms_and_conditions = REQUIRES_REVIEW; - EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete(user_data, options)); user_data.terms_and_conditions = ACCEPTED; - EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete(user_data, options)); } TEST_F(CollectUserDataActionTest, UserDataComplete_Login) { UserData user_data; CollectUserDataOptions options; options.request_login_choice = true; - EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete(user_data, options)); user_data.login_choice_identifier.assign("1"); - EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete(user_data, options)); } TEST_F(CollectUserDataActionTest, UserDataComplete_ShippingAddress) { UserData user_data; CollectUserDataOptions options; options.request_shipping = true; - EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete(user_data, options)); // Incomplete address. user_data.shipping_address = std::make_unique<autofill::AutofillProfile>( @@ -474,13 +620,240 @@ TEST_F(CollectUserDataActionTest, UserDataComplete_ShippingAddress) { "Mitchell", "Morrison", "marion@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", /* zipcode = */ "", "US", "16505678910"); - EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete(user_data, options)); user_data.shipping_address->SetRawInfo(autofill::ADDRESS_HOME_ZIP, base::UTF8ToUTF16("91601")); - EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete( - &mock_personal_data_manager_, user_data, options)); + EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete(user_data, options)); +} + +TEST_F(CollectUserDataActionTest, UserDataComplete_DateTimeRange) { + UserData user_data; + CollectUserDataOptions options; + options.request_date_time_range = true; + EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete(user_data, options)); + + SetDateTimeProto(&user_data.date_time_range_start, 2019, 12, 31, 10, 30, 0); + SetDateTimeProto(&user_data.date_time_range_end, 2019, 1, 28, 16, 0, 0); + + // Start date not before end date. + EXPECT_FALSE(CollectUserDataAction::IsUserDataComplete(user_data, options)); + + user_data.date_time_range_end.mutable_date()->set_year(2020); + EXPECT_TRUE(CollectUserDataAction::IsUserDataComplete(user_data, options)); +} + +TEST_F(CollectUserDataActionTest, SelectDateTimeRange) { + ActionProto action_proto; + auto* collect_user_data_proto = action_proto.mutable_collect_user_data(); + SetRequiredTermsFields(collect_user_data_proto); + collect_user_data_proto->set_request_terms_and_conditions(false); + auto* date_time_proto = collect_user_data_proto->mutable_date_time_range(); + SetDateTimeProto(date_time_proto->mutable_start(), 2019, 10, 21, 8, 0, 0); + SetDateTimeProto(date_time_proto->mutable_end(), 2019, 11, 5, 16, 0, 0); + SetDateTimeProto(date_time_proto->mutable_min(), 2019, 11, 5, 16, 0, 0); + SetDateTimeProto(date_time_proto->mutable_max(), 2020, 11, 5, 16, 0, 0); + date_time_proto->set_start_label("Pick up"); + date_time_proto->set_end_label("Return"); + + DateTimeProto actual_pickup_time; + DateTimeProto actual_return_time; + SetDateTimeProto(&actual_pickup_time, 2019, 10, 21, 7, 0, 0); + SetDateTimeProto(&actual_return_time, 2019, 10, 25, 19, 0, 0); + + ON_CALL(mock_action_delegate_, CollectUserData(_, _)) + .WillByDefault(Invoke( + [&](std::unique_ptr<CollectUserDataOptions> collect_user_data_options, + std::unique_ptr<UserData> user_data) { + user_data->succeed = true; + user_data->date_time_range_start = actual_pickup_time; + user_data->date_time_range_end = actual_return_time; + std::move(collect_user_data_options->confirm_callback) + .Run(std::move(user_data)); + })); + + EXPECT_CALL( + callback_, + Run(Pointee( + AllOf(Property(&ProcessedActionProto::status, ACTION_APPLIED), + Property(&ProcessedActionProto::collect_user_data_result, + Property(&CollectUserDataResultProto::date_time_start, + EqualsProto(actual_pickup_time))), + Property(&ProcessedActionProto::collect_user_data_result, + Property(&CollectUserDataResultProto::date_time_end, + EqualsProto(actual_return_time))))))); + CollectUserDataAction action(&mock_action_delegate_, action_proto); + action.ProcessAction(callback_.Get()); +} + +TEST_F(CollectUserDataActionTest, StaticSectionValid) { + ActionProto action_proto; + auto* collect_user_data_proto = action_proto.mutable_collect_user_data(); + SetRequiredTermsFields(collect_user_data_proto); + collect_user_data_proto->set_request_terms_and_conditions(false); + ON_CALL(mock_action_delegate_, CollectUserData(_, _)) + .WillByDefault(Invoke( + [](std::unique_ptr<CollectUserDataOptions> collect_user_data_options, + std::unique_ptr<UserData> user_data) { + user_data->succeed = true; + std::move(collect_user_data_options->confirm_callback) + .Run(std::move(user_data)); + })); + + auto* static_section = + collect_user_data_proto->add_additional_prepended_sections(); + { + CollectUserDataAction action(&mock_action_delegate_, action_proto); + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, INVALID_ACTION)))); + action.ProcessAction(callback_.Get()); + } + + static_section->set_title("Static section"); + { + CollectUserDataAction action(&mock_action_delegate_, action_proto); + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, INVALID_ACTION)))); + action.ProcessAction(callback_.Get()); + } + + static_section->mutable_static_text_section()->set_text("Lorem ipsum."); + { + CollectUserDataAction action(&mock_action_delegate_, action_proto); + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED)))); + action.ProcessAction(callback_.Get()); + } +} + +TEST_F(CollectUserDataActionTest, TextInputSectionValid) { + ActionProto action_proto; + auto* collect_user_data_proto = action_proto.mutable_collect_user_data(); + SetRequiredTermsFields(collect_user_data_proto); + collect_user_data_proto->set_request_terms_and_conditions(false); + ON_CALL(mock_action_delegate_, CollectUserData(_, _)) + .WillByDefault(Invoke( + [](std::unique_ptr<CollectUserDataOptions> collect_user_data_options, + std::unique_ptr<UserData> user_data) { + user_data->succeed = true; + std::move(collect_user_data_options->confirm_callback) + .Run(std::move(user_data)); + })); + + auto* text_input_section = + collect_user_data_proto->add_additional_prepended_sections(); + { + CollectUserDataAction action(&mock_action_delegate_, action_proto); + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, INVALID_ACTION)))); + action.ProcessAction(callback_.Get()); + } + + text_input_section->set_title("Text input section"); + { + CollectUserDataAction action(&mock_action_delegate_, action_proto); + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, INVALID_ACTION)))); + action.ProcessAction(callback_.Get()); + } + + auto* input_field = + text_input_section->mutable_text_input_section()->add_input_fields(); + input_field->set_value("12345"); + { + CollectUserDataAction action(&mock_action_delegate_, action_proto); + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, INVALID_ACTION)))); + action.ProcessAction(callback_.Get()); + } + + input_field->set_input_type(TextInputProto::INPUT_ALPHANUMERIC); + { + CollectUserDataAction action(&mock_action_delegate_, action_proto); + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, INVALID_ACTION)))); + action.ProcessAction(callback_.Get()); + } + + input_field->set_client_memory_key("code"); + { + CollectUserDataAction action(&mock_action_delegate_, action_proto); + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED)))); + action.ProcessAction(callback_.Get()); + } + + // Duplicate input field fails due to duplicate memory key. + *text_input_section->mutable_text_input_section()->add_input_fields() = + *input_field; + { + CollectUserDataAction action(&mock_action_delegate_, action_proto); + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, INVALID_ACTION)))); + action.ProcessAction(callback_.Get()); + } + + text_input_section->mutable_text_input_section() + ->mutable_input_fields(1) + ->set_client_memory_key("something else"); + { + CollectUserDataAction action(&mock_action_delegate_, action_proto); + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED)))); + action.ProcessAction(callback_.Get()); + } +} + +TEST_F(CollectUserDataActionTest, TextInputSectionWritesToClientMemory) { + ActionProto action_proto; + auto* collect_user_data_proto = action_proto.mutable_collect_user_data(); + SetRequiredTermsFields(collect_user_data_proto); + collect_user_data_proto->set_request_terms_and_conditions(false); + ON_CALL(mock_action_delegate_, CollectUserData(_, _)) + .WillByDefault(Invoke( + [](std::unique_ptr<CollectUserDataOptions> collect_user_data_options, + std::unique_ptr<UserData> user_data) { + user_data->succeed = true; + user_data->additional_values_to_store["key2"] = "modified"; + std::move(collect_user_data_options->confirm_callback) + .Run(std::move(user_data)); + })); + + auto* text_input_section = + collect_user_data_proto->add_additional_prepended_sections(); + text_input_section->set_title("Text input section"); + + auto* input_field_1 = + text_input_section->mutable_text_input_section()->add_input_fields(); + input_field_1->set_value("initial"); + input_field_1->set_input_type(TextInputProto::INPUT_ALPHANUMERIC); + input_field_1->set_client_memory_key("key1"); + + auto* input_field_2 = + text_input_section->mutable_text_input_section()->add_input_fields(); + input_field_2->set_value("initial"); + input_field_2->set_input_type(TextInputProto::INPUT_ALPHANUMERIC); + input_field_2->set_client_memory_key("key2"); + + EXPECT_FALSE(client_memory_.has_additional_value("key1")); + EXPECT_FALSE(client_memory_.has_additional_value("key2")); + CollectUserDataAction action(&mock_action_delegate_, action_proto); + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED)))); + action.ProcessAction(callback_.Get()); + EXPECT_EQ(*client_memory_.additional_value("key1"), "initial"); + EXPECT_EQ(*client_memory_.additional_value("key2"), "modified"); } } // namespace diff --git a/chromium/components/autofill_assistant/browser/actions/focus_element_action.cc b/chromium/components/autofill_assistant/browser/actions/focus_element_action.cc index 44efa8d0398..8d3b3d2e59a 100644 --- a/chromium/components/autofill_assistant/browser/actions/focus_element_action.cc +++ b/chromium/components/autofill_assistant/browser/actions/focus_element_action.cc @@ -10,6 +10,7 @@ #include "base/bind.h" #include "base/callback.h" #include "components/autofill_assistant/browser/actions/action_delegate.h" +#include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/service.pb.h" namespace autofill_assistant { @@ -61,9 +62,9 @@ void FocusElementAction::InternalProcessAction(ProcessActionCallback callback) { void FocusElementAction::OnWaitForElement(ProcessActionCallback callback, const Selector& selector, const TopPadding& top_padding, - bool element_found) { - if (!element_found) { - UpdateProcessedAction(ELEMENT_RESOLUTION_FAILED); + const ClientStatus& element_status) { + if (!element_status.ok()) { + UpdateProcessedAction(element_status.proto_status()); std::move(callback).Run(std::move(processed_action_proto_)); return; } diff --git a/chromium/components/autofill_assistant/browser/actions/focus_element_action.h b/chromium/components/autofill_assistant/browser/actions/focus_element_action.h index 145f692ae50..cc7b665509c 100644 --- a/chromium/components/autofill_assistant/browser/actions/focus_element_action.h +++ b/chromium/components/autofill_assistant/browser/actions/focus_element_action.h @@ -27,7 +27,7 @@ class FocusElementAction : public Action { void OnWaitForElement(ProcessActionCallback callback, const Selector& selector, const TopPadding& top_padding, - bool element_found); + const ClientStatus& element_status); void OnFocusElement(ProcessActionCallback callback, const ClientStatus& status); diff --git a/chromium/components/autofill_assistant/browser/actions/highlight_element_action.cc b/chromium/components/autofill_assistant/browser/actions/highlight_element_action.cc index 5c549006ef3..765bcd74f10 100644 --- a/chromium/components/autofill_assistant/browser/actions/highlight_element_action.cc +++ b/chromium/components/autofill_assistant/browser/actions/highlight_element_action.cc @@ -9,6 +9,7 @@ #include "base/bind.h" #include "base/callback.h" #include "components/autofill_assistant/browser/actions/action_delegate.h" +#include "components/autofill_assistant/browser/client_status.h" namespace autofill_assistant { @@ -36,11 +37,12 @@ void HighlightElementAction::InternalProcessAction( std::move(callback), selector)); } -void HighlightElementAction::OnWaitForElement(ProcessActionCallback callback, - const Selector& selector, - bool element_found) { - if (!element_found) { - UpdateProcessedAction(ELEMENT_RESOLUTION_FAILED); +void HighlightElementAction::OnWaitForElement( + ProcessActionCallback callback, + const Selector& selector, + const ClientStatus& element_status) { + if (!element_status.ok()) { + UpdateProcessedAction(element_status.proto_status()); std::move(callback).Run(std::move(processed_action_proto_)); return; } diff --git a/chromium/components/autofill_assistant/browser/actions/highlight_element_action.h b/chromium/components/autofill_assistant/browser/actions/highlight_element_action.h index 9b0830079c5..6720f31acd9 100644 --- a/chromium/components/autofill_assistant/browser/actions/highlight_element_action.h +++ b/chromium/components/autofill_assistant/browser/actions/highlight_element_action.h @@ -29,7 +29,7 @@ class HighlightElementAction : public Action { void OnWaitForElement(ProcessActionCallback callback, const Selector& selector, - bool element_found); + const ClientStatus& element_status); void OnHighlightElement(ProcessActionCallback callback, const ClientStatus& status); 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 c708be319e1..d2271c8cf39 100644 --- a/chromium/components/autofill_assistant/browser/actions/mock_action_delegate.h +++ b/chromium/components/autofill_assistant/browser/actions/mock_action_delegate.h @@ -28,31 +28,33 @@ class MockActionDelegate : public ActionDelegate { MOCK_METHOD1(RunElementChecks, void(BatchElementChecker*)); - void ShortWaitForElement(const Selector& selector, - base::OnceCallback<void(bool)> callback) override { + void ShortWaitForElement( + const Selector& selector, + base::OnceCallback<void(const ClientStatus&)> callback) override { OnShortWaitForElement(selector, callback); } MOCK_METHOD2(OnShortWaitForElement, - void(const Selector& selector, base::OnceCallback<void(bool)>&)); + void(const Selector& selector, + base::OnceCallback<void(const ClientStatus&)>&)); void WaitForDom( base::TimeDelta max_wait_time, bool allow_interrupt, - base::RepeatingCallback<void(BatchElementChecker*, - base::OnceCallback<void(bool)>)> - check_elements, - base::OnceCallback<void(ProcessedActionStatusProto)> callback) override { + base::RepeatingCallback< + void(BatchElementChecker*, + base::OnceCallback<void(const ClientStatus&)>)> check_elements, + base::OnceCallback<void(const ClientStatus&)> callback) override { OnWaitForDom(max_wait_time, allow_interrupt, check_elements, callback); } - MOCK_METHOD4( - OnWaitForDom, - void(base::TimeDelta, - bool, - base::RepeatingCallback<void(BatchElementChecker*, - base::OnceCallback<void(bool)>)>&, - base::OnceCallback<void(ProcessedActionStatusProto)>&)); + MOCK_METHOD4(OnWaitForDom, + void(base::TimeDelta, + bool, + base::RepeatingCallback< + void(BatchElementChecker*, + base::OnceCallback<void(const ClientStatus&)>)>&, + base::OnceCallback<void(const ClientStatus&)>&)); MOCK_METHOD1(SetStatusMessage, void(const std::string& message)); MOCK_METHOD0(GetStatusMessage, std::string()); @@ -131,16 +133,16 @@ class MockActionDelegate : public ActionDelegate { OnGetFullCard(transformed_callback); } - void GetFieldValue( - const Selector& selector, - base::OnceCallback<void(bool, const std::string&)> callback) { + void GetFieldValue(const Selector& selector, + base::OnceCallback<void(const ClientStatus&, + const std::string&)> callback) { OnGetFieldValue(selector, callback); } - MOCK_METHOD2( - OnGetFieldValue, - void(const Selector& selector, - base::OnceCallback<void(bool, const std::string&)>& callback)); + MOCK_METHOD2(OnGetFieldValue, + void(const Selector& selector, + base::OnceCallback<void(const ClientStatus&, + const std::string&)>& callback)); void SetFieldValue(const Selector& selector, const std::string& value, diff --git a/chromium/components/autofill_assistant/browser/actions/prompt_action.cc b/chromium/components/autofill_assistant/browser/actions/prompt_action.cc index 4ac8e2c68ea..9de12bce86d 100644 --- a/chromium/components/autofill_assistant/browser/actions/prompt_action.cc +++ b/chromium/components/autofill_assistant/browser/actions/prompt_action.cc @@ -152,8 +152,10 @@ void PromptAction::CheckAutoSelect() { delegate_->RunElementChecks(auto_select_checker_.get()); } -void PromptAction::OnAutoSelectElementExists(int choice_index, bool exists) { - if (exists) +void PromptAction::OnAutoSelectElementExists( + int choice_index, + const ClientStatus& element_status) { + if (element_status.ok()) auto_select_choice_index_ = choice_index; // Calling OnSuggestionChosen() is delayed until try_done, as it indirectly diff --git a/chromium/components/autofill_assistant/browser/actions/prompt_action.h b/chromium/components/autofill_assistant/browser/actions/prompt_action.h index f166292890e..b93e0e72576 100644 --- a/chromium/components/autofill_assistant/browser/actions/prompt_action.h +++ b/chromium/components/autofill_assistant/browser/actions/prompt_action.h @@ -40,7 +40,8 @@ class PromptAction : public Action { void UpdateUserActions(); bool HasAutoSelect(); void CheckAutoSelect(); - void OnAutoSelectElementExists(int choice_index, bool exists); + void OnAutoSelectElementExists(int choice_index, + const ClientStatus& element_status); void OnAutoSelectDone(); void OnSuggestionChosen(int choice_index); 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 009a4513f1f..cb81c388ff8 100644 --- a/chromium/components/autofill_assistant/browser/actions/prompt_action_unittest.cc +++ b/chromium/components/autofill_assistant/browser/actions/prompt_action_unittest.cc @@ -37,9 +37,9 @@ class PromptActionTest : public testing::Test { void SetUp() override { ON_CALL(mock_web_controller_, OnElementCheck(_, _)) - .WillByDefault(RunOnceCallback<1>(false)); + .WillByDefault(RunOnceCallback<1>(ClientStatus())); ON_CALL(mock_web_controller_, OnGetFieldValue(_, _)) - .WillByDefault(RunOnceCallback<1>(false, "")); + .WillByDefault(RunOnceCallback<1>(ClientStatus(), "")); ON_CALL(mock_action_delegate_, RunElementChecks) .WillByDefault(Invoke([this](BatchElementChecker* checker) { @@ -146,13 +146,13 @@ TEST_F(PromptActionTest, ShowOnlyIfElementExists) { EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"element"})), _)) - .WillRepeatedly(RunOnceCallback<1>(true)); + .WillRepeatedly(RunOnceCallback<1>(OkClientStatus())); task_env_.FastForwardBy(base::TimeDelta::FromSeconds(1)); ASSERT_THAT(user_actions_, Pointee(SizeIs(1))); EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"element"})), _)) - .WillRepeatedly(RunOnceCallback<1>(false)); + .WillRepeatedly(RunOnceCallback<1>(ClientStatus())); task_env_.FastForwardBy(base::TimeDelta::FromSeconds(1)); ASSERT_THAT(user_actions_, Pointee(IsEmpty())); } @@ -170,14 +170,14 @@ TEST_F(PromptActionTest, DisabledUnlessElementExists) { EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"element"})), _)) - .WillRepeatedly(RunOnceCallback<1>(true)); + .WillRepeatedly(RunOnceCallback<1>(OkClientStatus())); task_env_.FastForwardBy(base::TimeDelta::FromSeconds(1)); ASSERT_THAT(user_actions_, Pointee(SizeIs(1))); EXPECT_TRUE((*user_actions_)[0].enabled()); EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"element"})), _)) - .WillRepeatedly(RunOnceCallback<1>(false)); + .WillRepeatedly(RunOnceCallback<1>(ClientStatus())); task_env_.FastForwardBy(base::TimeDelta::FromSeconds(1)); ASSERT_THAT(user_actions_, Pointee(SizeIs(1))); EXPECT_FALSE((*user_actions_)[0].enabled()); @@ -196,7 +196,7 @@ TEST_F(PromptActionTest, AutoSelect) { EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"element"})), _)) - .WillRepeatedly(RunOnceCallback<1>(true)); + .WillRepeatedly(RunOnceCallback<1>(OkClientStatus())); EXPECT_CALL(mock_action_delegate_, CancelPrompt()); EXPECT_CALL( @@ -226,7 +226,7 @@ TEST_F(PromptActionTest, AutoSelectWithButton) { EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"element"})), _)) - .WillRepeatedly(RunOnceCallback<1>(true)); + .WillRepeatedly(RunOnceCallback<1>(OkClientStatus())); EXPECT_CALL( callback_, Run(Pointee(AllOf(Property(&ProcessedActionProto::status, ACTION_APPLIED), diff --git a/chromium/components/autofill_assistant/browser/actions/required_fields_fallback_handler.cc b/chromium/components/autofill_assistant/browser/actions/required_fields_fallback_handler.cc new file mode 100644 index 00000000000..dff26ef11af --- /dev/null +++ b/chromium/components/autofill_assistant/browser/actions/required_fields_fallback_handler.cc @@ -0,0 +1,186 @@ +// Copyright 2018 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/required_fields_fallback_handler.h" + +#include <utility> +#include <vector> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/optional.h" +#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" + +namespace autofill_assistant { + +RequiredFieldsFallbackHandler::RequiredFieldsFallbackHandler( + const std::vector<RequiredField>& required_fields, + base::RepeatingCallback<std::string(const RequiredField&, + const FallbackData&)> + field_value_getter, + base::OnceCallback<void(const ClientStatus&, + const base::Optional<ClientStatus>&)> + status_update_callback, + ActionDelegate* action_delegate) { + required_fields_.assign(required_fields.begin(), required_fields.end()); + field_value_getter_ = std::move(field_value_getter); + status_update_callback_ = std::move(status_update_callback); + action_delegate_ = action_delegate; +} + +RequiredFieldsFallbackHandler::~RequiredFieldsFallbackHandler() {} + +RequiredFieldsFallbackHandler::FallbackData::FallbackData() {} + +void RequiredFieldsFallbackHandler::CheckAndFallbackRequiredFields( + const ClientStatus& initial_autofill_status, + std::unique_ptr<FallbackData> fallback_data) { + initial_autofill_status_ = initial_autofill_status; + + if (required_fields_.empty()) { + if (!initial_autofill_status.ok()) { + DVLOG(1) << __func__ << " Autofill failed and no fallback provided " + << initial_autofill_status.proto_status(); + } + + std::move(status_update_callback_) + .Run(initial_autofill_status, base::nullopt); + return; + } + + CheckAllRequiredFields(std::move(fallback_data)); +} + +void RequiredFieldsFallbackHandler::CheckAllRequiredFields( + std::unique_ptr<FallbackData> fallback_data) { + DCHECK(!batch_element_checker_); + batch_element_checker_ = std::make_unique<BatchElementChecker>(); + for (size_t i = 0; i < required_fields_.size(); i++) { + if (required_fields_[i].forced) { + continue; + } + + batch_element_checker_->AddFieldValueCheck( + required_fields_[i].selector, + base::BindOnce(&RequiredFieldsFallbackHandler::OnGetRequiredFieldValue, + weak_ptr_factory_.GetWeakPtr(), i)); + } + + batch_element_checker_->AddAllDoneCallback( + base::BindOnce(&RequiredFieldsFallbackHandler::OnCheckRequiredFieldsDone, + weak_ptr_factory_.GetWeakPtr(), std::move(fallback_data))); + action_delegate_->RunElementChecks(batch_element_checker_.get()); +} + +void RequiredFieldsFallbackHandler::OnGetRequiredFieldValue( + size_t required_fields_index, + const ClientStatus& element_status, + const std::string& value) { + required_fields_[required_fields_index].status = + value.empty() ? EMPTY : NOT_EMPTY; +} + +void RequiredFieldsFallbackHandler::OnCheckRequiredFieldsDone( + std::unique_ptr<FallbackData> fallback_data) { + 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)) { + should_fallback = true; + break; + } + } + + if (!should_fallback) { + std::move(status_update_callback_) + .Run(ClientStatus(ACTION_APPLIED), initial_autofill_status_); + return; + } + + if (!fallback_data) { + // Validation failed and we don't want to try the fallback. + std::move(status_update_callback_) + .Run(ClientStatus(MANUAL_FALLBACK), initial_autofill_status_); + return; + } + + // If there are any fallbacks for the empty fields, set them, otherwise fail + // immediately. + bool has_fallbacks = false; + for (const RequiredField& required_field : required_fields_) { + if (!required_field.ShouldFallback(/* has_fallback_data= */ true)) + continue; + + if (!field_value_getter_.Run(required_field, *fallback_data).empty()) { + has_fallbacks = true; + } + } + if (!has_fallbacks) { + std::move(status_update_callback_) + .Run(ClientStatus(MANUAL_FALLBACK), initial_autofill_status_); + return; + } + + // Set the fallback values and check again. + SetFallbackFieldValuesSequentially(0, std::move(fallback_data)); +} + +void RequiredFieldsFallbackHandler::SetFallbackFieldValuesSequentially( + size_t required_fields_index, + std::unique_ptr<FallbackData> fallback_data) { + // Skip non-empty fields. + while (required_fields_index < required_fields_.size() && + !required_fields_[required_fields_index].ShouldFallback( + fallback_data != nullptr)) { + required_fields_index++; + } + + // If there are no more fields to set, check the required fields again, + // but this time we don't want to try the fallback in case of failure. + if (required_fields_index >= required_fields_.size()) { + DCHECK_EQ(required_fields_index, required_fields_.size()); + + return CheckAllRequiredFields(/* fallback_data= */ nullptr); + } + + // Set the next field to its fallback value. + const RequiredField& required_field = required_fields_[required_fields_index]; + std::string fallback_value = + field_value_getter_.Run(required_field, *fallback_data); + if (fallback_value.empty()) { + DVLOG(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)); + } + DVLOG(3) << "Setting fallback value for " << required_field.selector; + + action_delegate_->SetFieldValue( + required_field.selector, fallback_value, + required_field.simulate_key_presses, required_field.delay_in_millisecond, + base::BindOnce(&RequiredFieldsFallbackHandler::OnSetFallbackFieldValue, + weak_ptr_factory_.GetWeakPtr(), required_fields_index, + std::move(fallback_data))); +} + +void RequiredFieldsFallbackHandler::OnSetFallbackFieldValue( + size_t required_fields_index, + std::unique_ptr<FallbackData> fallback_data, + const ClientStatus& setFieldStatus) { + if (!setFieldStatus.ok()) { + // Fallback failed: we stop the script without checking the fields. + std::move(status_update_callback_) + .Run(ClientStatus(MANUAL_FALLBACK), initial_autofill_status_); + return; + } + + SetFallbackFieldValuesSequentially(++required_fields_index, + std::move(fallback_data)); +} +} // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/actions/required_fields_fallback_handler.h b/chromium/components/autofill_assistant/browser/actions/required_fields_fallback_handler.h new file mode 100644 index 00000000000..5759c34cfa2 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/actions/required_fields_fallback_handler.h @@ -0,0 +1,142 @@ +// Copyright 2019 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_REQUIRED_FIELDS_FALLBACK_HANDLER_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_REQUIRED_FIELDS_FALLBACK_HANDLER_H_ + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "base/callback.h" +#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/batch_element_checker.h" + +namespace autofill { +class AutofillProfile; +} // namespace autofill + +namespace autofill_assistant { +class ClientStatus; + +// A handler for required fields and fallback values, used for example in +// UseAddressAction. +class RequiredFieldsFallbackHandler { + public: + enum FieldValueStatus { UNKNOWN, EMPTY, NOT_EMPTY }; + struct RequiredField { + Selector selector; + bool simulate_key_presses = false; + int delay_in_millisecond = 0; + bool forced = false; + FieldValueStatus status = UNKNOWN; + + // When filling in credit card, card_field must be set. When filling in + // address, address_field must be set. + UseAddressProto::RequiredField::AddressField address_field = + UseAddressProto::RequiredField::UNDEFINED; + UseCreditCardProto::RequiredField::CardField card_field = + UseCreditCardProto::RequiredField::UNDEFINED; + + // Returns true if fallback is required for this field. + bool ShouldFallback(bool has_fallback_data) const { + return status == EMPTY || (forced && has_fallback_data); + } + }; + + // 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. + // TODO(marianfe): Refactor this to use a map instead. + struct FallbackData { + FallbackData(); + ~FallbackData() = default; + + // autofill profile. + const autofill::AutofillProfile* profile; + + // Card information for UseCreditCard fallback. + std::string cvc; + std::string card_holder_name; + std::string card_number; + int expiration_year = 0; + int expiration_month = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(FallbackData); + }; + + explicit RequiredFieldsFallbackHandler( + const std::vector<RequiredField>& required_fields, + base::RepeatingCallback<std::string(const RequiredField&, + const FallbackData&)> + field_value_getter, + base::OnceCallback<void(const ClientStatus&, + const base::Optional<ClientStatus>&)> + status_update_callback, + ActionDelegate* delegate); + ~RequiredFieldsFallbackHandler(); + + // Check if there are required fields. If so, verify them and fallback if + // they are empty. If not, update the status to the result of the autofill + // action. + void CheckAndFallbackRequiredFields( + const ClientStatus& initial_autofill_status, + std::unique_ptr<FallbackData> fallback_data); + + 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); + + // Triggers the check for a specific field. + void CheckRequiredFieldsSequentially( + bool allow_fallback, + size_t required_fields_index, + std::unique_ptr<FallbackData> fallback_data); + + // Updates the status of the required field. + void OnGetRequiredFieldValue(size_t required_fields_index, + const ClientStatus& element_status, + const std::string& value); + + // Called when all required fields have been checked. + void OnCheckRequiredFieldsDone(std::unique_ptr<FallbackData> fallback_data); + + // Sets fallback field values for empty fields. + void SetFallbackFieldValuesSequentially( + size_t required_fields_index, + std::unique_ptr<FallbackData> fallback_data); + + // 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 initial_autofill_status_; + + std::vector<RequiredField> required_fields_; + base::RepeatingCallback<std::string(const RequiredField&, + const FallbackData&)> + field_value_getter_; + base::OnceCallback<void(const ClientStatus&, + const base::Optional<ClientStatus>&)> + status_update_callback_; + ActionDelegate* action_delegate_; + std::unique_ptr<BatchElementChecker> batch_element_checker_; + base::WeakPtrFactory<RequiredFieldsFallbackHandler> weak_ptr_factory_{this}; + + DISALLOW_COPY_AND_ASSIGN(RequiredFieldsFallbackHandler); +}; + +} // namespace autofill_assistant +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_REQUIRED_FIELDS_FALLBACK_HANDLER_H_ diff --git a/chromium/components/autofill_assistant/browser/actions/select_option_action.cc b/chromium/components/autofill_assistant/browser/actions/select_option_action.cc index 3c9b96ffd36..f488fa63150 100644 --- a/chromium/components/autofill_assistant/browser/actions/select_option_action.cc +++ b/chromium/components/autofill_assistant/browser/actions/select_option_action.cc @@ -9,6 +9,7 @@ #include "base/bind.h" #include "base/callback.h" #include "components/autofill_assistant/browser/actions/action_delegate.h" +#include "components/autofill_assistant/browser/client_status.h" namespace autofill_assistant { @@ -45,9 +46,9 @@ void SelectOptionAction::InternalProcessAction(ProcessActionCallback callback) { void SelectOptionAction::OnWaitForElement(ProcessActionCallback callback, const Selector& selector, - bool element_found) { - if (!element_found) { - UpdateProcessedAction(ELEMENT_RESOLUTION_FAILED); + const ClientStatus& element_status) { + if (!element_status.ok()) { + UpdateProcessedAction(element_status.proto_status()); std::move(callback).Run(std::move(processed_action_proto_)); return; } diff --git a/chromium/components/autofill_assistant/browser/actions/select_option_action.h b/chromium/components/autofill_assistant/browser/actions/select_option_action.h index 7da3615866f..ca632c4fbec 100644 --- a/chromium/components/autofill_assistant/browser/actions/select_option_action.h +++ b/chromium/components/autofill_assistant/browser/actions/select_option_action.h @@ -27,7 +27,7 @@ class SelectOptionAction : public Action { void OnWaitForElement(ProcessActionCallback callback, const Selector& selector, - bool element_found); + const ClientStatus& element_status); void OnSelectOption(ProcessActionCallback callback, const ClientStatus& status); 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 7e72315358c..42733cb9fe0 100644 --- a/chromium/components/autofill_assistant/browser/actions/set_attribute_action.cc +++ b/chromium/components/autofill_assistant/browser/actions/set_attribute_action.cc @@ -9,6 +9,7 @@ #include "base/bind.h" #include "base/callback.h" #include "components/autofill_assistant/browser/actions/action_delegate.h" +#include "components/autofill_assistant/browser/client_status.h" namespace autofill_assistant { @@ -37,9 +38,9 @@ void SetAttributeAction::InternalProcessAction(ProcessActionCallback callback) { void SetAttributeAction::OnWaitForElement(ProcessActionCallback callback, const Selector& selector, - bool element_found) { - if (!element_found) { - UpdateProcessedAction(ELEMENT_RESOLUTION_FAILED); + const ClientStatus& element_status) { + if (!element_status.ok()) { + UpdateProcessedAction(element_status.proto_status()); std::move(callback).Run(std::move(processed_action_proto_)); return; } diff --git a/chromium/components/autofill_assistant/browser/actions/set_attribute_action.h b/chromium/components/autofill_assistant/browser/actions/set_attribute_action.h index 57b3f9d1920..7bb43d21c2e 100644 --- a/chromium/components/autofill_assistant/browser/actions/set_attribute_action.h +++ b/chromium/components/autofill_assistant/browser/actions/set_attribute_action.h @@ -27,7 +27,7 @@ class SetAttributeAction : public Action { void OnWaitForElement(ProcessActionCallback callback, const Selector& selector, - bool element_found); + const ClientStatus& element_status); void OnSetAttribute(ProcessActionCallback callback, const ClientStatus& status); 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 80552b759b9..c3615cf4745 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 @@ -100,6 +100,24 @@ void SetFormFieldValueAction::InternalProcessAction( // Currently no check required. field_inputs_.emplace_back(/* value = */ keypress.text()); break; + case SetFormFieldValueProto_KeyPress::kClientMemoryKey: + if (keypress.client_memory_key().empty()) { + DVLOG(1) << "SetFormFieldValueAction: empty |client_memory_key|"; + EndAction(ClientStatus(INVALID_ACTION)); + return; + } + if (!delegate_->GetClientMemory()->has_additional_value( + keypress.client_memory_key())) { + DVLOG(1) << "SetFormFieldValueAction: requested key '" + << keypress.client_memory_key() + << "' not available in client memory"; + EndAction(ClientStatus(PRECONDITION_FAILED)); + return; + } + field_inputs_.emplace_back( + /* value = */ *delegate_->GetClientMemory()->additional_value( + keypress.client_memory_key())); + break; default: DVLOG(1) << "Unrecognized field for SetFormFieldValueProto_KeyPress"; EndAction(ClientStatus(INVALID_ACTION)); @@ -112,9 +130,10 @@ void SetFormFieldValueAction::InternalProcessAction( weak_ptr_factory_.GetWeakPtr())); } -void SetFormFieldValueAction::OnWaitForElement(bool element_found) { - if (!element_found) { - EndAction(ClientStatus(ELEMENT_RESOLUTION_FAILED)); +void SetFormFieldValueAction::OnWaitForElement( + const ClientStatus& element_status) { + if (!element_status.ok()) { + EndAction(ClientStatus(element_status.proto_status())); return; } // Start with first value, then call OnSetFieldValue() recursively until done. @@ -181,10 +200,10 @@ void SetFormFieldValueAction::OnSetFieldValueAndCheckFallback( void SetFormFieldValueAction::OnGetFieldValue( int field_index, const std::string& requested_value, - bool get_value_status, + const ClientStatus& element_status, const std::string& actual_value) { // Move to next value if |GetFieldValue| failed. - if (!get_value_status) { + if (!element_status.ok()) { OnSetFieldValue(field_index + 1, OkClientStatus()); return; } @@ -238,6 +257,8 @@ void SetFormFieldValueAction::OnGetPassword(int field_index, } void SetFormFieldValueAction::EndAction(const ClientStatus& status) { + // Clear immediately, to prevent sensitive information from staying in memory. + field_inputs_.clear(); UpdateProcessedAction(status); std::move(process_action_callback_).Run(std::move(processed_action_proto_)); } diff --git a/chromium/components/autofill_assistant/browser/actions/set_form_field_value_action.h b/chromium/components/autofill_assistant/browser/actions/set_form_field_value_action.h index b00c045999e..adfbf7b9fee 100644 --- a/chromium/components/autofill_assistant/browser/actions/set_form_field_value_action.h +++ b/chromium/components/autofill_assistant/browser/actions/set_form_field_value_action.h @@ -9,6 +9,7 @@ #include <vector> #include "base/callback.h" +#include "base/gtest_prod_util.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "components/autofill_assistant/browser/actions/action.h" @@ -24,6 +25,9 @@ class SetFormFieldValueAction : public Action { ~SetFormFieldValueAction() override; private: + FRIEND_TEST_ALL_PREFIXES(SetFormFieldValueActionTest, + PasswordIsClearedFromMemory); + // A field input as extracted from the proto, but already checked for // validity. struct FieldInput { @@ -46,11 +50,11 @@ class SetFormFieldValueAction : public Action { // Overrides Action: void InternalProcessAction(ProcessActionCallback callback) override; - void OnWaitForElement(bool element_found); + void OnWaitForElement(const ClientStatus& element_status); void OnGetFieldValue(int field_index, const std::string& requested_value, - bool status, + const ClientStatus& element_status, const std::string& actual_value); void OnSetFieldValue(int next, const ClientStatus& status); 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 4ca89445421..621f7254678 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 @@ -24,7 +24,6 @@ const char kFakePassword[] = "example_password"; } // namespace namespace autofill_assistant { -namespace { using ::base::test::RunOnceCallback; using ::testing::_; @@ -45,7 +44,7 @@ class SetFormFieldValueActionTest : public testing::Test { ON_CALL(mock_action_delegate_, GetWebsiteLoginFetcher) .WillByDefault(Return(&mock_website_login_fetcher_)); ON_CALL(mock_action_delegate_, OnShortWaitForElement(_, _)) - .WillByDefault(RunOnceCallback<1>(true)); + .WillByDefault(RunOnceCallback<1>(OkClientStatus())); ON_CALL(mock_action_delegate_, OnSetFieldValue(_, _, _, _, _)) .WillByDefault(RunOnceCallback<4>(OkClientStatus())); @@ -119,7 +118,7 @@ TEST_F(SetFormFieldValueActionTest, Username) { value->set_use_username(true); SetFormFieldValueAction action(&mock_action_delegate_, proto_); ON_CALL(mock_action_delegate_, OnGetFieldValue(_, _)) - .WillByDefault(RunOnceCallback<1>(true, kFakeUsername)); + .WillByDefault(RunOnceCallback<1>(OkClientStatus(), kFakeUsername)); EXPECT_CALL(mock_action_delegate_, OnSetFieldValue(fake_selector_, kFakeUsername, _, _, _)) .WillOnce(RunOnceCallback<4>(OkClientStatus())); @@ -135,7 +134,7 @@ TEST_F(SetFormFieldValueActionTest, Password) { value->set_use_password(true); SetFormFieldValueAction action(&mock_action_delegate_, proto_); ON_CALL(mock_action_delegate_, OnGetFieldValue(_, _)) - .WillByDefault(RunOnceCallback<1>(true, kFakePassword)); + .WillByDefault(RunOnceCallback<1>(OkClientStatus(), kFakePassword)); EXPECT_CALL(mock_action_delegate_, OnSetFieldValue(fake_selector_, kFakePassword, _, _, _)) .WillOnce(RunOnceCallback<4>(OkClientStatus())); @@ -181,7 +180,7 @@ TEST_F(SetFormFieldValueActionTest, Text) { value->set_text("SomeText𠜎"); SetFormFieldValueAction action(&mock_action_delegate_, proto_); ON_CALL(mock_action_delegate_, OnGetFieldValue(_, _)) - .WillByDefault(RunOnceCallback<1>(true, "SomeText𠜎")); + .WillByDefault(RunOnceCallback<1>(OkClientStatus(), "SomeText𠜎")); EXPECT_CALL(mock_action_delegate_, OnSetFieldValue(fake_selector_, "SomeText𠜎", _, _, _)) .WillOnce(RunOnceCallback<4>(OkClientStatus())); @@ -192,6 +191,33 @@ TEST_F(SetFormFieldValueActionTest, Text) { action.ProcessAction(callback_.Get()); } +TEST_F(SetFormFieldValueActionTest, ClientMemoryKey) { + auto* value = set_form_field_proto_->add_value(); + value->set_client_memory_key("key"); + client_memory_.set_additional_value("key", "SomeText𠜎"); + SetFormFieldValueAction action(&mock_action_delegate_, proto_); + ON_CALL(mock_action_delegate_, OnGetFieldValue(_, _)) + .WillByDefault(RunOnceCallback<1>(OkClientStatus(), "SomeText𠜎")); + EXPECT_CALL(mock_action_delegate_, + OnSetFieldValue(fake_selector_, "SomeText𠜎", _, _, _)) + .WillOnce(RunOnceCallback<4>(OkClientStatus())); + + EXPECT_CALL( + callback_, + Run(Pointee(Property(&ProcessedActionProto::status, ACTION_APPLIED)))); + action.ProcessAction(callback_.Get()); +} + +TEST_F(SetFormFieldValueActionTest, ClientMemoryKeyFailsIfNotInClientMemory) { + auto* value = set_form_field_proto_->add_value(); + value->set_client_memory_key("key"); + SetFormFieldValueAction action(&mock_action_delegate_, proto_); + + EXPECT_CALL(callback_, Run(Pointee(Property(&ProcessedActionProto::status, + PRECONDITION_FAILED)))); + action.ProcessAction(callback_.Get()); +} + // Test that automatic fallback to simulate keystrokes works. TEST_F(SetFormFieldValueActionTest, Fallback) { auto* value = set_form_field_proto_->add_value(); @@ -199,7 +225,7 @@ TEST_F(SetFormFieldValueActionTest, Fallback) { SetFormFieldValueAction action(&mock_action_delegate_, proto_); ON_CALL(mock_action_delegate_, OnGetFieldValue(_, _)) - .WillByDefault(RunOnceCallback<1>(true, "")); + .WillByDefault(RunOnceCallback<1>(OkClientStatus(), "")); { InSequence seq; @@ -222,5 +248,14 @@ TEST_F(SetFormFieldValueActionTest, Fallback) { action.ProcessAction(callback_.Get()); } -} // namespace -} // namespace autofill_assistant
\ No newline at end of file +TEST_F(SetFormFieldValueActionTest, PasswordIsClearedFromMemory) { + auto* value = set_form_field_proto_->add_value(); + value->set_use_password(true); + SetFormFieldValueAction action(&mock_action_delegate_, proto_); + ON_CALL(mock_action_delegate_, OnGetFieldValue(_, _)) + .WillByDefault(RunOnceCallback<1>(OkClientStatus(), kFakePassword)); + action.ProcessAction(callback_.Get()); + EXPECT_TRUE(action.field_inputs_.empty()); +} + +} // 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 5a0ec991479..45e05cfadd9 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 @@ -4,12 +4,12 @@ #include "components/autofill_assistant/browser/actions/show_progress_bar_action.h" -#include <algorithm> #include <memory> #include <utility> #include "base/bind.h" #include "base/callback.h" +#include "base/numerics/ranges.h" #include "components/autofill_assistant/browser/actions/action_delegate.h" namespace autofill_assistant { @@ -28,7 +28,7 @@ void ShowProgressBarAction::InternalProcessAction( delegate_->SetStatusMessage(proto_.show_progress_bar().message()); } int progress = - std::min(100, std::max(0, proto_.show_progress_bar().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()); diff --git a/chromium/components/autofill_assistant/browser/actions/upload_dom_action.cc b/chromium/components/autofill_assistant/browser/actions/upload_dom_action.cc index a339b62eb84..9d3c8a20d66 100644 --- a/chromium/components/autofill_assistant/browser/actions/upload_dom_action.cc +++ b/chromium/components/autofill_assistant/browser/actions/upload_dom_action.cc @@ -36,9 +36,9 @@ void UploadDomAction::InternalProcessAction(ProcessActionCallback callback) { void UploadDomAction::OnWaitForElement(ProcessActionCallback callback, const Selector& selector, - bool element_found) { - if (!element_found) { - UpdateProcessedAction(ELEMENT_RESOLUTION_FAILED); + const ClientStatus& element_status) { + if (!element_status.ok()) { + UpdateProcessedAction(element_status.proto_status()); std::move(callback).Run(std::move(processed_action_proto_)); return; } diff --git a/chromium/components/autofill_assistant/browser/actions/upload_dom_action.h b/chromium/components/autofill_assistant/browser/actions/upload_dom_action.h index 1d757f74f62..1d0cc50eed5 100644 --- a/chromium/components/autofill_assistant/browser/actions/upload_dom_action.h +++ b/chromium/components/autofill_assistant/browser/actions/upload_dom_action.h @@ -24,7 +24,7 @@ class UploadDomAction : public Action { void OnWaitForElement(ProcessActionCallback callback, const Selector& selector, - bool element_found); + const ClientStatus& element_status); void OnGetOuterHtml(ProcessActionCallback callback, const ClientStatus& status, const std::string& outer_html); diff --git a/chromium/components/autofill_assistant/browser/actions/use_address_action.cc b/chromium/components/autofill_assistant/browser/actions/use_address_action.cc new file mode 100644 index 00000000000..61632587c87 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/actions/use_address_action.cc @@ -0,0 +1,169 @@ +// Copyright 2018 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/use_address_action.h" + +#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" +#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/required_fields_fallback_handler.h" +#include "components/autofill_assistant/browser/client_memory.h" +#include "components/autofill_assistant/browser/client_status.h" + +namespace autofill_assistant { +using RequiredField = RequiredFieldsFallbackHandler::RequiredField; +using FallbackData = RequiredFieldsFallbackHandler::FallbackData; + +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()) { + required_fields.emplace_back(); + RequiredField& required_field = required_fields.back(); + required_field.address_field = required_field_proto.address_field(); + required_field.selector = Selector(required_field_proto.element()); + required_field.simulate_key_presses = + required_field_proto.simulate_key_presses(); + required_field.delay_in_millisecond = + required_field_proto.delay_in_millisecond(); + required_field.forced = required_field_proto.forced(); + } + + required_fields_fallback_handler_ = + std::make_unique<RequiredFieldsFallbackHandler>( + required_fields, + base::BindRepeating(&UseAddressAction::GetFallbackValue, + base::Unretained(this)), + base::BindOnce(&UseAddressAction::EndAction, base::Unretained(this)), + delegate); + selector_ = Selector(proto.use_address().form_field_element()); + selector_.MustBeVisible(); + DCHECK(!selector_.empty()); +} + +UseAddressAction::~UseAddressAction() = default; + +void UseAddressAction::InternalProcessAction( + ProcessActionCallback action_callback) { + process_action_callback_ = std::move(action_callback); + + // Ensure data already selected in a previous action. + auto* client_memory = delegate_->GetClientMemory(); + if (!client_memory->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( + client_memory->GetAllAddressKeyNames()); + error_info->set_address_pointee_was_null( + !client_memory->has_selected_address(name_) || + !client_memory->selected_address(name_)); + EndAction(ClientStatus(PRECONDITION_FAILED)); + return; + } + + FillFormWithData(); +} + +void UseAddressAction::EndAction( + const ClientStatus& final_status, + const base::Optional<ClientStatus>& optional_details_status) { + UpdateProcessedAction(final_status); + if (optional_details_status.has_value() && !optional_details_status->ok()) { + processed_action_proto_->mutable_status_details()->MergeFrom( + optional_details_status->details()); + } + std::move(process_action_callback_).Run(std::move(processed_action_proto_)); +} + +void UseAddressAction::FillFormWithData() { + delegate_->ShortWaitForElement( + selector_, base::BindOnce(&UseAddressAction::OnWaitForElement, + weak_ptr_factory_.GetWeakPtr())); +} + +void UseAddressAction::OnWaitForElement(const ClientStatus& element_status) { + if (!element_status.ok()) { + EndAction(ClientStatus(element_status.proto_status())); + return; + } + + DCHECK(!selector_.empty()); + DVLOG(3) << "Retrieving address from client memory under '" << name_ << "'."; + const autofill::AutofillProfile* profile = + delegate_->GetClientMemory()->selected_address(name_); + DCHECK(profile); + auto fallback_data = std::make_unique<FallbackData>(); + fallback_data->profile = profile; + delegate_->FillAddressForm( + profile, selector_, + base::BindOnce(&UseAddressAction::OnFormFilled, + weak_ptr_factory_.GetWeakPtr(), std::move(fallback_data))); +} + +void UseAddressAction::OnFormFilled(std::unique_ptr<FallbackData> fallback_data, + const ClientStatus& status) { + required_fields_fallback_handler_->CheckAndFallbackRequiredFields( + status, std::move(fallback_data)); +} + +std::string UseAddressAction::GetFallbackValue( + const RequiredField& required_field, + const FallbackData& fallback_data) { + return base::UTF16ToUTF8( + GetFieldValue(fallback_data.profile, required_field.address_field)); +} + +base::string16 UseAddressAction::GetFieldValue( + const autofill::AutofillProfile* profile, + const UseAddressProto::RequiredField::AddressField& address_field) { + // TODO(crbug.com/806868): Get the actual application locale. + std::string app_locale = "en-US"; + switch (address_field) { + case UseAddressProto::RequiredField::FIRST_NAME: + return profile->GetInfo(autofill::NAME_FIRST, app_locale); + case UseAddressProto::RequiredField::LAST_NAME: + return profile->GetInfo(autofill::NAME_LAST, app_locale); + case UseAddressProto::RequiredField::FULL_NAME: + return profile->GetInfo(autofill::NAME_FULL, app_locale); + case UseAddressProto::RequiredField::PHONE_NUMBER: + return profile->GetInfo(autofill::PHONE_HOME_WHOLE_NUMBER, app_locale); + case UseAddressProto::RequiredField::EMAIL: + return profile->GetInfo(autofill::EMAIL_ADDRESS, app_locale); + case UseAddressProto::RequiredField::ORGANIZATION: + return profile->GetInfo(autofill::COMPANY_NAME, app_locale); + case UseAddressProto::RequiredField::COUNTRY_CODE: + return profile->GetInfo(autofill::ADDRESS_HOME_COUNTRY, app_locale); + case UseAddressProto::RequiredField::REGION: + return profile->GetInfo(autofill::ADDRESS_HOME_STATE, app_locale); + case UseAddressProto::RequiredField::STREET_ADDRESS: + return profile->GetInfo(autofill::ADDRESS_HOME_STREET_ADDRESS, + app_locale); + case UseAddressProto::RequiredField::LOCALITY: + return profile->GetInfo(autofill::ADDRESS_HOME_CITY, app_locale); + case UseAddressProto::RequiredField::DEPENDANT_LOCALITY: + return profile->GetInfo(autofill::ADDRESS_HOME_DEPENDENT_LOCALITY, + app_locale); + case UseAddressProto::RequiredField::POSTAL_CODE: + return profile->GetInfo(autofill::ADDRESS_HOME_ZIP, app_locale); + case UseAddressProto::RequiredField::UNDEFINED: + NOTREACHED(); + return base::string16(); + } +} +} // 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 new file mode 100644 index 00000000000..3977f5d52fa --- /dev/null +++ b/chromium/components/autofill_assistant/browser/actions/use_address_action.h @@ -0,0 +1,76 @@ +// Copyright 2018 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_USE_ADDRESS_ACTION_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_USE_ADDRESS_ACTION_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "components/autofill_assistant/browser/actions/action.h" +#include "components/autofill_assistant/browser/actions/required_fields_fallback_handler.h" + +namespace autofill { +class AutofillProfile; +} // namespace autofill + +namespace autofill_assistant { +class ClientStatus; + +// An action to autofill a form using a local address. +class UseAddressAction : public Action { + public: + explicit UseAddressAction(ActionDelegate* delegate, const ActionProto& proto); + ~UseAddressAction() override; + + private: + // Overrides Action: + void InternalProcessAction(ProcessActionCallback callback) override; + + void EndAction(const ClientStatus& final_status, + 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. + void FillFormWithData(); + void OnWaitForElement(const ClientStatus& element_status); + + // Called when the address has been filled. + void OnFormFilled(std::unique_ptr<RequiredFieldsFallbackHandler::FallbackData> + fallback_data, + const ClientStatus& status); + + // Gets the fallback value. + std::string GetFallbackValue( + const RequiredFieldsFallbackHandler::RequiredField& required_field, + const RequiredFieldsFallbackHandler::FallbackData& fallback_data); + + // Get the value of |address_field| associated to profile |profile|. Return + // empty string if there is no data available. + base::string16 GetFieldValue( + const autofill::AutofillProfile* profile, + const UseAddressProto::RequiredField::AddressField& address_field); + + // Usage of the autofilled address. + std::string name_; + std::string prompt_; + Selector selector_; + + std::unique_ptr<RequiredFieldsFallbackHandler> + required_fields_fallback_handler_; + + ProcessActionCallback process_action_callback_; + base::WeakPtrFactory<UseAddressAction> weak_ptr_factory_{this}; + + DISALLOW_COPY_AND_ASSIGN(UseAddressAction); +}; + +} // namespace autofill_assistant +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_AUTOFILL_ACTION_H_ 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 new file mode 100644 index 00000000000..00de8237c26 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/actions/use_address_action_unittest.cc @@ -0,0 +1,345 @@ +// Copyright 2018 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/use_address_action.h" + +#include <utility> + +#include "base/guid.h" +#include "base/strings/utf_string_conversions.h" +#include "base/test/gmock_callback_support.h" +#include "base/test/mock_callback.h" +#include "build/build_config.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/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 { + +using ::base::test::RunOnceCallback; +using ::testing::_; +using ::testing::Eq; +using ::testing::Expectation; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::NotNull; +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, "", "", "", "", "", "", + "", ""); + autofill::test::SetProfileInfo(&autofill_profile_, kFirstName, "", + kLastName, kEmail, "", "", "", "", "", "", + "", ""); + client_memory_.set_selected_address(kAddressName, + std::move(autofill_profile)); + + ON_CALL(mock_personal_data_manager_, GetProfileByGUID) + .WillByDefault(Return(&autofill_profile_)); + ON_CALL(mock_action_delegate_, GetClientMemory) + .WillByDefault(Return(&client_memory_)); + ON_CALL(mock_action_delegate_, GetPersonalDataManager) + .WillByDefault(Return(&mock_personal_data_manager_)); + ON_CALL(mock_action_delegate_, RunElementChecks) + .WillByDefault(Invoke([this](BatchElementChecker* checker) { + checker->Run(&mock_web_controller_); + })); + ON_CALL(mock_action_delegate_, OnShortWaitForElement(_, _)) + .WillByDefault(RunOnceCallback<1>(OkClientStatus())); + } + + 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 CreateUseAddressAction() { + ActionProto action; + UseAddressProto* use_address = action.mutable_use_address(); + use_address->set_name(kAddressName); + use_address->mutable_form_field_element()->add_selectors(kFakeSelector); + return action; + } + + UseAddressProto::RequiredField* AddRequiredField( + ActionProto* action, + UseAddressProto::RequiredField::AddressField type, + std::string selector) { + auto* required_field = action->mutable_use_address()->add_required_fields(); + required_field->set_address_field(type); + required_field->mutable_element()->add_selectors(selector); + return required_field; + } + + ProcessedActionStatusProto ProcessAction(const ActionProto& action_proto) { + UseAddressAction action(&mock_action_delegate_, action_proto); + ProcessedActionProto capture; + EXPECT_CALL(callback_, Run(_)).WillOnce(SaveArgPointee<0>(&capture)); + action.ProcessAction(callback_.Get()); + return capture.status(); + } + + base::MockCallback<Action::ProcessActionCallback> callback_; + MockPersonalDataManager mock_personal_data_manager_; + MockActionDelegate mock_action_delegate_; + MockWebController mock_web_controller_; + ClientMemory client_memory_; + + autofill::AutofillProfile autofill_profile_; +}; + +#if !defined(OS_ANDROID) +#define MAYBE_FillManually FillManually +#else +#define MAYBE_FillManually DISABLED_FillManually +#endif +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; + + ActionProto action_proto = CreateUseAddressAction(); + action_proto.mutable_use_address()->set_prompt(kSelectionPrompt); + + client_memory_.set_selected_address(kAddressName, nullptr); + + EXPECT_EQ(ProcessedActionStatusProto::PRECONDITION_FAILED, + ProcessAction(action_proto)); +} + +TEST_F(UseAddressActionTest, PreconditionFailedPopulatesUnexpectedErrorInfo) { + InSequence seq; + + ActionProto action_proto = CreateUseAddressAction(); + action_proto.mutable_use_address()->set_prompt(kSelectionPrompt); + client_memory_.set_selected_address(kAddressName, nullptr); + client_memory_.set_selected_address("one_more", nullptr); + + UseAddressAction action(&mock_action_delegate_, action_proto); + + ProcessedActionProto processed_action; + EXPECT_CALL(callback_, Run(_)).WillOnce(SaveArgPointee<0>(&processed_action)); + action.ProcessAction(callback_.Get()); + + EXPECT_EQ(ProcessedActionStatusProto::PRECONDITION_FAILED, + processed_action.status()); + const auto& error_info = + processed_action.status_details().autofill_error_info(); + EXPECT_EQ(base::JoinString({kAddressName, "one_more"}, ","), + error_info.client_memory_address_key_names()); + EXPECT_EQ(kAddressName, error_info.address_key_requested()); + EXPECT_TRUE(error_info.address_pointee_was_null()); +} + +TEST_F(UseAddressActionTest, ShortWaitForElementVisible) { + EXPECT_CALL( + mock_action_delegate_, + OnShortWaitForElement(Selector({kFakeSelector}).MustBeVisible(), _)) + .WillOnce(RunOnceCallback<1>(OkClientStatus())); + + ActionProto action_proto = CreateUseAddressAction(); + // Autofill succeeds. + EXPECT_CALL(mock_action_delegate_, OnFillAddressForm(NotNull(), _, _)) + .WillOnce(RunOnceCallback<2>(OkClientStatus())); + + // Validation succeeds. + ON_CALL(mock_web_controller_, OnGetFieldValue(_, _)) + .WillByDefault(RunOnceCallback<1>(OkClientStatus(), "not empty")); + + EXPECT_EQ(ProcessedActionStatusProto::ACTION_APPLIED, + ProcessAction(action_proto)); +} + +TEST_F(UseAddressActionTest, ValidationSucceeds) { + InSequence seq; + + ActionProto action_proto = CreateUseAddressAction(); + AddRequiredField(&action_proto, UseAddressProto::RequiredField::FIRST_NAME, + "#first_name"); + AddRequiredField(&action_proto, UseAddressProto::RequiredField::LAST_NAME, + "#last_name"); + AddRequiredField(&action_proto, UseAddressProto::RequiredField::EMAIL, + "#email"); + + // Autofill succeeds. + EXPECT_CALL(mock_action_delegate_, + OnFillAddressForm( + NotNull(), Eq(Selector({kFakeSelector}).MustBeVisible()), _)) + .WillOnce(RunOnceCallback<2>(OkClientStatus())); + + // Validation succeeds. + ON_CALL(mock_web_controller_, OnGetFieldValue(_, _)) + .WillByDefault(RunOnceCallback<1>(OkClientStatus(), "not empty")); + + EXPECT_EQ(ProcessedActionStatusProto::ACTION_APPLIED, + ProcessAction(action_proto)); +} + +TEST_F(UseAddressActionTest, FallbackFails) { + InSequence seq; + + ActionProto action_proto = CreateUseAddressAction(); + AddRequiredField(&action_proto, UseAddressProto::RequiredField::FIRST_NAME, + "#first_name"); + AddRequiredField(&action_proto, UseAddressProto::RequiredField::LAST_NAME, + "#last_name"); + AddRequiredField(&action_proto, UseAddressProto::RequiredField::EMAIL, + "#email"); + + // Autofill succeeds. + EXPECT_CALL(mock_action_delegate_, + OnFillAddressForm( + NotNull(), Eq(Selector({kFakeSelector}).MustBeVisible()), _)) + .WillOnce(RunOnceCallback<2>(OkClientStatus())); + + // Validation fails when getting FIRST_NAME. + EXPECT_CALL(mock_web_controller_, + OnGetFieldValue(Eq(Selector({"#email"})), _)) + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "not empty")); + EXPECT_CALL(mock_web_controller_, + OnGetFieldValue(Eq(Selector({"#first_name"})), _)) + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "")); + EXPECT_CALL(mock_web_controller_, + OnGetFieldValue(Eq(Selector({"#last_name"})), _)) + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "not empty")); + + // Fallback fails. + EXPECT_CALL(mock_action_delegate_, + OnSetFieldValue(Eq(Selector({"#first_name"})), kFirstName, _)) + .WillOnce(RunOnceCallback<2>(ClientStatus(OTHER_ACTION_STATUS))); + + EXPECT_EQ(ProcessedActionStatusProto::MANUAL_FALLBACK, + ProcessAction(action_proto)); +} + +TEST_F(UseAddressActionTest, FallbackSucceeds) { + InSequence seq; + + ActionProto action_proto = CreateUseAddressAction(); + AddRequiredField(&action_proto, UseAddressProto::RequiredField::FIRST_NAME, + "#first_name"); + AddRequiredField(&action_proto, UseAddressProto::RequiredField::LAST_NAME, + "#last_name"); + AddRequiredField(&action_proto, UseAddressProto::RequiredField::EMAIL, + "#email"); + + // Autofill succeeds. + EXPECT_CALL(mock_action_delegate_, + OnFillAddressForm( + NotNull(), Eq(Selector({kFakeSelector}).MustBeVisible()), _)) + .WillOnce(RunOnceCallback<2>(OkClientStatus())); + + { + InSequence seq; + + // Validation fails when getting FIRST_NAME. + EXPECT_CALL(mock_web_controller_, + OnGetFieldValue(Eq(Selector({"#email"})), _)) + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "not empty")); + EXPECT_CALL(mock_web_controller_, + OnGetFieldValue(Eq(Selector({"#first_name"})), _)) + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "")); + EXPECT_CALL(mock_web_controller_, + OnGetFieldValue(Eq(Selector({"#last_name"})), _)) + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "not empty")); + + // Fallback succeeds. + EXPECT_CALL(mock_action_delegate_, + OnSetFieldValue(Eq(Selector({"#first_name"})), kFirstName, _)) + .WillOnce(RunOnceCallback<2>(OkClientStatus())); + + // Second validation succeeds. + EXPECT_CALL(mock_web_controller_, OnGetFieldValue(_, _)) + .WillRepeatedly(RunOnceCallback<1>(OkClientStatus(), "not empty")); + } + EXPECT_EQ(ProcessedActionStatusProto::ACTION_APPLIED, + ProcessAction(action_proto)); +} + +TEST_F(UseAddressActionTest, AutofillFailureWithoutRequiredFieldsIsFatal) { + ActionProto action_proto = CreateUseAddressAction(); + + EXPECT_CALL(mock_action_delegate_, + OnFillAddressForm( + NotNull(), Eq(Selector({kFakeSelector}).MustBeVisible()), _)) + .WillOnce(RunOnceCallback<2>(ClientStatus(OTHER_ACTION_STATUS))); + + 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::OTHER_ACTION_STATUS); + EXPECT_EQ(processed_action.has_status_details(), false); +} + +TEST_F(UseAddressActionTest, + AutofillFailureWithRequiredFieldsLaunchesFallback) { + ActionProto action_proto = CreateUseAddressAction(); + AddRequiredField(&action_proto, UseAddressProto::RequiredField::FIRST_NAME, + "#first_name"); + + EXPECT_CALL(mock_action_delegate_, + OnFillAddressForm( + NotNull(), Eq(Selector({kFakeSelector}).MustBeVisible()), _)) + .WillOnce(RunOnceCallback<2>( + FillAutofillErrorStatus(ClientStatus(OTHER_ACTION_STATUS)))); + + // First validation fails. + EXPECT_CALL(mock_web_controller_, + OnGetFieldValue(Selector({"#first_name"}), _)) + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "")); + // Fill first name. + Expectation set_first_name = + EXPECT_CALL(mock_action_delegate_, + OnSetFieldValue(Selector({"#first_name"}), kFirstName, _)) + .WillOnce(RunOnceCallback<2>(OkClientStatus())); + // Second validation succeeds. + EXPECT_CALL(mock_web_controller_, + OnGetFieldValue(Selector({"#first_name"}), _)) + .After(set_first_name) + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "not empty")); + + 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::ACTION_APPLIED); + EXPECT_EQ(processed_action.status_details() + .autofill_error_info() + .autofill_error_status(), + OTHER_ACTION_STATUS); +} + +} // namespace +} // namespace autofill_assistant 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 new file mode 100644 index 00000000000..e204701dd5f --- /dev/null +++ b/chromium/components/autofill_assistant/browser/actions/use_credit_card_action.cc @@ -0,0 +1,179 @@ +// Copyright 2018 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/use_credit_card_action.h" + +#include <utility> +#include <vector> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/optional.h" +#include "base/strings/strcat.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" +#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/required_fields_fallback_handler.h" +#include "components/autofill_assistant/browser/client_memory.h" +#include "components/autofill_assistant/browser/client_status.h" + +namespace autofill_assistant { +using RequiredField = RequiredFieldsFallbackHandler::RequiredField; +using FallbackData = RequiredFieldsFallbackHandler::FallbackData; + +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()) { + required_fields.emplace_back(); + RequiredField& required_field = required_fields.back(); + required_field.card_field = required_field_proto.card_field(); + required_field.selector = Selector(required_field_proto.element()); + required_field.simulate_key_presses = + required_field_proto.simulate_key_presses(); + required_field.delay_in_millisecond = + required_field_proto.delay_in_millisecond(); + required_field.forced = required_field_proto.forced(); + } + + required_fields_fallback_handler_ = + std::make_unique<RequiredFieldsFallbackHandler>( + required_fields, + base::BindRepeating(&UseCreditCardAction::GetFallbackValue, + base::Unretained(this)), + base::BindOnce(&UseCreditCardAction::EndAction, + base::Unretained(this)), + delegate); + selector_ = Selector(proto.use_card().form_field_element()); + selector_.MustBeVisible(); + DCHECK(!selector_.empty()); +} + +UseCreditCardAction::~UseCreditCardAction() = default; + +void UseCreditCardAction::InternalProcessAction( + ProcessActionCallback action_callback) { + process_action_callback_ = std::move(action_callback); + + // Ensure data already selected in a previous action. + auto* client_memory = delegate_->GetClientMemory(); + if (!client_memory->has_selected_card()) { + EndAction(ClientStatus(PRECONDITION_FAILED)); + return; + } + + FillFormWithData(); +} + +void UseCreditCardAction::EndAction( + const ClientStatus& final_status, + const base::Optional<ClientStatus>& optional_details_status) { + UpdateProcessedAction(final_status); + if (optional_details_status.has_value() && !optional_details_status->ok()) { + processed_action_proto_->mutable_status_details()->MergeFrom( + optional_details_status->details()); + } + std::move(process_action_callback_).Run(std::move(processed_action_proto_)); +} + +void UseCreditCardAction::FillFormWithData() { + delegate_->ShortWaitForElement( + selector_, base::BindOnce(&UseCreditCardAction::OnWaitForElement, + weak_ptr_factory_.GetWeakPtr())); +} + +void UseCreditCardAction::OnWaitForElement(const ClientStatus& element_status) { + if (!element_status.ok()) { + EndAction(ClientStatus(element_status.proto_status())); + return; + } + + delegate_->GetFullCard(base::BindOnce(&UseCreditCardAction::OnGetFullCard, + weak_ptr_factory_.GetWeakPtr())); + return; +} + +void UseCreditCardAction::OnGetFullCard( + std::unique_ptr<autofill::CreditCard> card, + const base::string16& cvc) { + if (!card) { + EndAction(ClientStatus(GET_FULL_CARD_FAILED)); + return; + } + + auto fallback_data = std::make_unique<FallbackData>(); + fallback_data->cvc = base::UTF16ToUTF8(cvc); + fallback_data->expiration_month = card->expiration_month(); + fallback_data->expiration_year = card->expiration_year(); + fallback_data->card_holder_name = + base::UTF16ToUTF8(card->GetRawInfo(autofill::CREDIT_CARD_NAME_FULL)); + fallback_data->card_number = + base::UTF16ToUTF8(card->GetRawInfo(autofill::CREDIT_CARD_NUMBER)); + + delegate_->FillCardForm( + std::move(card), cvc, selector_, + base::BindOnce(&UseCreditCardAction::OnFormFilled, + weak_ptr_factory_.GetWeakPtr(), std::move(fallback_data))); +} + +void UseCreditCardAction::OnFormFilled( + std::unique_ptr<FallbackData> fallback_data, + const ClientStatus& status) { + required_fields_fallback_handler_->CheckAndFallbackRequiredFields( + status, std::move(fallback_data)); +} + +std::string UseCreditCardAction::GetFallbackValue( + const RequiredField& required_field, + const FallbackData& fallback_data) { + auto field = required_field.card_field; + switch (field) { + case UseCreditCardProto::RequiredField::CREDIT_CARD_VERIFICATION_CODE: + return fallback_data.cvc; + + case UseCreditCardProto::RequiredField::CREDIT_CARD_EXP_MONTH: + if (fallback_data.expiration_month > 0) + return base::StringPrintf("%02d", fallback_data.expiration_month); + break; + + case UseCreditCardProto::RequiredField::CREDIT_CARD_EXP_2_DIGIT_YEAR: + if (fallback_data.expiration_year > 0) + return base::StringPrintf("%02d", fallback_data.expiration_year % 100); + break; + + case UseCreditCardProto::RequiredField::CREDIT_CARD_EXP_4_DIGIT_YEAR: + if (fallback_data.expiration_year > 0) + return base::NumberToString(fallback_data.expiration_year); + break; + + case UseCreditCardProto::RequiredField::CREDIT_CARD_CARD_HOLDER_NAME: + return fallback_data.card_holder_name; + break; + + case UseCreditCardProto::RequiredField::CREDIT_CARD_NUMBER: + return fallback_data.card_number; + break; + + case UseCreditCardProto::RequiredField::CREDIT_CARD_EXP_MM_YY: + if (fallback_data.expiration_month > 0 && + fallback_data.expiration_year > 0) + return base::StrCat( + {base::StringPrintf("%02d", fallback_data.expiration_month), "/", + base::StringPrintf("%02d", fallback_data.expiration_year % 100)}); + break; + + case UseCreditCardProto::RequiredField::UNDEFINED: + NOTREACHED(); + return ""; + } + return ""; +} +} // 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 new file mode 100644 index 00000000000..376ca61dd08 --- /dev/null +++ b/chromium/components/autofill_assistant/browser/actions/use_credit_card_action.h @@ -0,0 +1,73 @@ +// Copyright 2018 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_USE_CREDIT_CARD_ACTION_H_ +#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_USE_CREDIT_CARD_ACTION_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "components/autofill_assistant/browser/actions/action.h" +#include "components/autofill_assistant/browser/actions/required_fields_fallback_handler.h" + +namespace autofill { +class CreditCard; +} // namespace autofill + +namespace autofill_assistant { +class ClientStatus; + +// An action to autofill a form using a credit card. +class UseCreditCardAction : public Action { + public: + explicit UseCreditCardAction(ActionDelegate* delegate, + const ActionProto& proto); + ~UseCreditCardAction() override; + + private: + // Overrides Action: + void InternalProcessAction(ProcessActionCallback callback) override; + + void EndAction(const ClientStatus& final_status, + 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. + void FillFormWithData(); + void OnWaitForElement(const ClientStatus& element_status); + + // Called after getting full credit card with its cvc. + void OnGetFullCard(std::unique_ptr<autofill::CreditCard> card, + const base::string16& cvc); + + // Called when the form credit card has been filled. + void OnFormFilled(std::unique_ptr<RequiredFieldsFallbackHandler::FallbackData> + fallback_data, + const ClientStatus& status); + + // Gets the fallback value. + std::string GetFallbackValue( + const RequiredFieldsFallbackHandler::RequiredField& required_field, + const RequiredFieldsFallbackHandler::FallbackData& fallback_data); + + std::string prompt_; + Selector selector_; + + std::unique_ptr<RequiredFieldsFallbackHandler> + required_fields_fallback_handler_; + + ProcessActionCallback process_action_callback_; + base::WeakPtrFactory<UseCreditCardAction> weak_ptr_factory_{this}; + + DISALLOW_COPY_AND_ASSIGN(UseCreditCardAction); +}; + +} // namespace autofill_assistant +#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_ACTIONS_AUTOFILL_ACTION_H_ diff --git a/chromium/components/autofill_assistant/browser/actions/autofill_action_unittest.cc b/chromium/components/autofill_assistant/browser/actions/use_credit_card_action_unittest.cc index e2ad48920ba..e6a24867a7d 100644 --- a/chromium/components/autofill_assistant/browser/actions/autofill_action_unittest.cc +++ b/chromium/components/autofill_assistant/browser/actions/use_credit_card_action_unittest.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "components/autofill_assistant/browser/actions/autofill_action.h" +#include "components/autofill_assistant/browser/actions/use_credit_card_action.h" #include <utility> @@ -15,6 +15,7 @@ #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/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 { @@ -30,7 +31,7 @@ using ::testing::NotNull; using ::testing::Return; using ::testing::SaveArgPointee; -class AutofillActionTest : public testing::Test { +class UseCreditCardActionTest : public testing::Test { public: void SetUp() override { // Build two identical autofill profiles. One for the memory, one for the @@ -57,7 +58,7 @@ class AutofillActionTest : public testing::Test { checker->Run(&mock_web_controller_); })); ON_CALL(mock_action_delegate_, OnShortWaitForElement(_, _)) - .WillByDefault(RunOnceCallback<1>(true)); + .WillByDefault(RunOnceCallback<1>(OkClientStatus())); } protected: @@ -68,14 +69,6 @@ class AutofillActionTest : public testing::Test { const char* const kLastName = "LastName"; const char* const kEmail = "foobar@gmail.com"; - 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); - return action; - } - ActionProto CreateUseCreditCardAction() { ActionProto action; action.mutable_use_card()->mutable_form_field_element()->add_selectors( @@ -83,16 +76,6 @@ class AutofillActionTest : public testing::Test { return action; } - UseAddressProto::RequiredField* AddRequiredField( - ActionProto* action, - UseAddressProto::RequiredField::AddressField type, - std::string selector) { - auto* required_field = action->mutable_use_address()->add_required_fields(); - required_field->set_address_field(type); - required_field->mutable_element()->add_selectors(selector); - return required_field; - } - UseCreditCardProto::RequiredField* AddRequiredField( ActionProto* action, UseCreditCardProto::RequiredField::CardField type, @@ -111,7 +94,7 @@ class AutofillActionTest : public testing::Test { } ProcessedActionStatusProto ProcessAction(const ActionProto& action_proto) { - AutofillAction action(&mock_action_delegate_, action_proto); + UseCreditCardAction action(&mock_action_delegate_, action_proto); ProcessedActionProto capture; EXPECT_CALL(callback_, Run(_)).WillOnce(SaveArgPointee<0>(&capture)); action.ProcessAction(callback_.Get()); @@ -127,188 +110,13 @@ class AutofillActionTest : public testing::Test { autofill::AutofillProfile autofill_profile_; }; -#if !defined(OS_ANDROID) -#define MAYBE_FillManually FillManually -#else -#define MAYBE_FillManually DISABLED_FillManually -#endif -TEST_F(AutofillActionTest, 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(AutofillActionTest, NoSelectedAddress) { - InSequence seq; - - ActionProto action_proto = CreateUseAddressAction(); - action_proto.mutable_use_address()->set_prompt(kSelectionPrompt); - - client_memory_.set_selected_address(kAddressName, nullptr); - - EXPECT_EQ(ProcessedActionStatusProto::PRECONDITION_FAILED, - ProcessAction(action_proto)); -} - -TEST_F(AutofillActionTest, PreconditionFailedPopulatesUnexpectedErrorInfo) { - InSequence seq; - - ActionProto action_proto = CreateUseAddressAction(); - action_proto.mutable_use_address()->set_prompt(kSelectionPrompt); - client_memory_.set_selected_address(kAddressName, nullptr); - client_memory_.set_selected_address("one_more", nullptr); - - AutofillAction action(&mock_action_delegate_, action_proto); - - ProcessedActionProto processed_action; - EXPECT_CALL(callback_, Run(_)).WillOnce(SaveArgPointee<0>(&processed_action)); - action.ProcessAction(callback_.Get()); - - EXPECT_EQ(ProcessedActionStatusProto::PRECONDITION_FAILED, - processed_action.status()); - const auto& error_info = - processed_action.status_details().autofill_error_info(); - EXPECT_EQ(base::JoinString({kAddressName, "one_more"}, ","), - error_info.client_memory_address_key_names()); - EXPECT_EQ(kAddressName, error_info.address_key_requested()); -} - -TEST_F(AutofillActionTest, ShortWaitForElementVisible) { - EXPECT_CALL( - mock_action_delegate_, - OnShortWaitForElement(Selector({kFakeSelector}).MustBeVisible(), _)) - .WillOnce(RunOnceCallback<1>(true)); - - ActionProto action_proto = CreateUseAddressAction(); - // Autofill succeeds. - EXPECT_CALL(mock_action_delegate_, OnFillAddressForm(NotNull(), _, _)) - .WillOnce(RunOnceCallback<2>(OkClientStatus())); - - // Validation succeeds. - ON_CALL(mock_web_controller_, OnGetFieldValue(_, _)) - .WillByDefault(RunOnceCallback<1>(true, "not empty")); - - EXPECT_EQ(ProcessedActionStatusProto::ACTION_APPLIED, - ProcessAction(action_proto)); -} - -TEST_F(AutofillActionTest, ValidationSucceeds) { - InSequence seq; - - ActionProto action_proto = CreateUseAddressAction(); - AddRequiredField(&action_proto, UseAddressProto::RequiredField::FIRST_NAME, - "#first_name"); - AddRequiredField(&action_proto, UseAddressProto::RequiredField::LAST_NAME, - "#last_name"); - AddRequiredField(&action_proto, UseAddressProto::RequiredField::EMAIL, - "#email"); - - // Autofill succeeds. - EXPECT_CALL(mock_action_delegate_, - OnFillAddressForm( - NotNull(), Eq(Selector({kFakeSelector}).MustBeVisible()), _)) - .WillOnce(RunOnceCallback<2>(OkClientStatus())); - - // Validation succeeds. - ON_CALL(mock_web_controller_, OnGetFieldValue(_, _)) - .WillByDefault(RunOnceCallback<1>(true, "not empty")); - - EXPECT_EQ(ProcessedActionStatusProto::ACTION_APPLIED, - ProcessAction(action_proto)); -} - -TEST_F(AutofillActionTest, FallbackFails) { - InSequence seq; - - ActionProto action_proto = CreateUseAddressAction(); - AddRequiredField(&action_proto, UseAddressProto::RequiredField::FIRST_NAME, - "#first_name"); - AddRequiredField(&action_proto, UseAddressProto::RequiredField::LAST_NAME, - "#last_name"); - AddRequiredField(&action_proto, UseAddressProto::RequiredField::EMAIL, - "#email"); - - // Autofill succeeds. - EXPECT_CALL(mock_action_delegate_, - OnFillAddressForm( - NotNull(), Eq(Selector({kFakeSelector}).MustBeVisible()), _)) - .WillOnce(RunOnceCallback<2>(OkClientStatus())); - - // Validation fails when getting FIRST_NAME. - EXPECT_CALL(mock_web_controller_, - OnGetFieldValue(Eq(Selector({"#email"})), _)) - .WillOnce(RunOnceCallback<1>(true, "not empty")); - EXPECT_CALL(mock_web_controller_, - OnGetFieldValue(Eq(Selector({"#first_name"})), _)) - .WillOnce(RunOnceCallback<1>(true, "")); - EXPECT_CALL(mock_web_controller_, - OnGetFieldValue(Eq(Selector({"#last_name"})), _)) - .WillOnce(RunOnceCallback<1>(true, "not empty")); - - // Fallback fails. - EXPECT_CALL(mock_action_delegate_, - OnSetFieldValue(Eq(Selector({"#first_name"})), kFirstName, _)) - .WillOnce(RunOnceCallback<2>(ClientStatus(OTHER_ACTION_STATUS))); - - EXPECT_EQ(ProcessedActionStatusProto::MANUAL_FALLBACK, - ProcessAction(action_proto)); -} - -TEST_F(AutofillActionTest, FallbackSucceeds) { - InSequence seq; - - ActionProto action_proto = CreateUseAddressAction(); - AddRequiredField(&action_proto, UseAddressProto::RequiredField::FIRST_NAME, - "#first_name"); - AddRequiredField(&action_proto, UseAddressProto::RequiredField::LAST_NAME, - "#last_name"); - AddRequiredField(&action_proto, UseAddressProto::RequiredField::EMAIL, - "#email"); - - // Autofill succeeds. - EXPECT_CALL(mock_action_delegate_, - OnFillAddressForm( - NotNull(), Eq(Selector({kFakeSelector}).MustBeVisible()), _)) - .WillOnce(RunOnceCallback<2>(OkClientStatus())); - - { - InSequence seq; - - // Validation fails when getting FIRST_NAME. - EXPECT_CALL(mock_web_controller_, - OnGetFieldValue(Eq(Selector({"#email"})), _)) - .WillOnce(RunOnceCallback<1>(true, "not empty")); - EXPECT_CALL(mock_web_controller_, - OnGetFieldValue(Eq(Selector({"#first_name"})), _)) - .WillOnce(RunOnceCallback<1>(true, "")); - EXPECT_CALL(mock_web_controller_, - OnGetFieldValue(Eq(Selector({"#last_name"})), _)) - .WillOnce(RunOnceCallback<1>(true, "not empty")); - - // Fallback succeeds. - EXPECT_CALL(mock_action_delegate_, - OnSetFieldValue(Eq(Selector({"#first_name"})), kFirstName, _)) - .WillOnce(RunOnceCallback<2>(OkClientStatus())); - - // Second validation succeeds. - EXPECT_CALL(mock_web_controller_, OnGetFieldValue(_, _)) - .WillRepeatedly(RunOnceCallback<1>(true, "not empty")); - } - EXPECT_EQ(ProcessedActionStatusProto::ACTION_APPLIED, - ProcessAction(action_proto)); -} - -TEST_F(AutofillActionTest, FillCreditCardNoCardSelected) { +TEST_F(UseCreditCardActionTest, FillCreditCardNoCardSelected) { ActionProto action = CreateUseCreditCardAction(); EXPECT_EQ(ProcessedActionStatusProto::PRECONDITION_FAILED, ProcessAction(action)); } -TEST_F(AutofillActionTest, FillCreditCard) { +TEST_F(UseCreditCardActionTest, FillCreditCard) { ActionProto action = CreateUseCreditCardAction(); autofill::CreditCard credit_card; @@ -324,10 +132,10 @@ TEST_F(AutofillActionTest, FillCreditCard) { EXPECT_EQ(ProcessedActionStatusProto::ACTION_APPLIED, ProcessAction(action)); } -TEST_F(AutofillActionTest, FillCreditCardRequiredFieldsFilled) { +TEST_F(UseCreditCardActionTest, FillCreditCardRequiredFieldsFilled) { // Validation succeeds. ON_CALL(mock_web_controller_, OnGetFieldValue(_, _)) - .WillByDefault(RunOnceCallback<1>(true, "not empty")); + .WillByDefault(RunOnceCallback<1>(OkClientStatus(), "not empty")); ActionProto action = CreateUseCreditCardAction(); AddRequiredField( @@ -350,7 +158,7 @@ TEST_F(AutofillActionTest, FillCreditCardRequiredFieldsFilled) { EXPECT_EQ(ProcessedActionStatusProto::ACTION_APPLIED, ProcessAction(action)); } -TEST_F(AutofillActionTest, FillCreditCardWithFallback) { +TEST_F(UseCreditCardActionTest, FillCreditCardWithFallback) { ActionProto action = CreateUseCreditCardAction(); AddRequiredField( &action, UseCreditCardProto::RequiredField::CREDIT_CARD_VERIFICATION_CODE, @@ -364,16 +172,34 @@ TEST_F(AutofillActionTest, FillCreditCardWithFallback) { AddRequiredField( &action, UseCreditCardProto::RequiredField::CREDIT_CARD_EXP_4_DIGIT_YEAR, "#expyear4"); + AddRequiredField( + &action, UseCreditCardProto::RequiredField::CREDIT_CARD_CARD_HOLDER_NAME, + "#card_name"); + AddRequiredField(&action, + UseCreditCardProto::RequiredField::CREDIT_CARD_NUMBER, + "#card_number"); + AddRequiredField(&action, + UseCreditCardProto::RequiredField::CREDIT_CARD_EXP_MM_YY, + "#exp_month_year2"); // First validation fails. EXPECT_CALL(mock_web_controller_, OnGetFieldValue(Selector({"#cvc"}), _)) - .WillOnce(RunOnceCallback<1>(true, "")); + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "")); EXPECT_CALL(mock_web_controller_, OnGetFieldValue(Selector({"#expmonth"}), _)) - .WillOnce(RunOnceCallback<1>(true, "")); + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "")); EXPECT_CALL(mock_web_controller_, OnGetFieldValue(Selector({"#expyear2"}), _)) - .WillOnce(RunOnceCallback<1>(true, "")); + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "")); EXPECT_CALL(mock_web_controller_, OnGetFieldValue(Selector({"#expyear4"}), _)) - .WillOnce(RunOnceCallback<1>(true, "")); + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "")); + EXPECT_CALL(mock_web_controller_, + OnGetFieldValue(Selector({"#card_name"}), _)) + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "")); + EXPECT_CALL(mock_web_controller_, + OnGetFieldValue(Selector({"#card_number"}), _)) + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "")); + EXPECT_CALL(mock_web_controller_, + OnGetFieldValue(Selector({"#exp_month_year2"}), _)) + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "")); // Expect fields to be filled Expectation set_cvc = @@ -392,24 +218,53 @@ TEST_F(AutofillActionTest, FillCreditCardWithFallback) { EXPECT_CALL(mock_action_delegate_, OnSetFieldValue(Selector({"#expyear4"}), "2024", _)) .WillOnce(RunOnceCallback<2>(OkClientStatus())); + Expectation set_cardholder_name = + EXPECT_CALL(mock_action_delegate_, + OnSetFieldValue(Selector({"#card_name"}), "Jon Doe", _)) + .WillOnce(RunOnceCallback<2>(OkClientStatus())); + Expectation set_card_number = + EXPECT_CALL( + mock_action_delegate_, + OnSetFieldValue(Selector({"#card_number"}), "4111111111111111", _)) + .WillOnce(RunOnceCallback<2>(OkClientStatus())); + Expectation set_exp_month_year2 = + EXPECT_CALL(mock_action_delegate_, + OnSetFieldValue(Selector({"#exp_month_year2"}), "09/24", _)) + .WillOnce(RunOnceCallback<2>(OkClientStatus())); // After fallback, second validation succeeds. EXPECT_CALL(mock_web_controller_, OnGetFieldValue(Selector({"#cvc"}), _)) .After(set_cvc) - .WillOnce(RunOnceCallback<1>(true, "not empty")); + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "not empty")); EXPECT_CALL(mock_web_controller_, OnGetFieldValue(Selector({"#expmonth"}), _)) .After(set_expmonth) - .WillOnce(RunOnceCallback<1>(true, "not empty")); + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "not empty")); EXPECT_CALL(mock_web_controller_, OnGetFieldValue(Selector({"#expyear2"}), _)) .After(set_expyear2) - .WillOnce(RunOnceCallback<1>(true, "not empty")); + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "not empty")); EXPECT_CALL(mock_web_controller_, OnGetFieldValue(Selector({"#expyear4"}), _)) .After(set_expyear4) - .WillOnce(RunOnceCallback<1>(true, "not empty")); + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "not empty")); + EXPECT_CALL(mock_web_controller_, + OnGetFieldValue(Selector({"#card_name"}), _)) + .After(set_expyear4) + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "not empty")); + EXPECT_CALL(mock_web_controller_, + OnGetFieldValue(Selector({"#card_number"}), _)) + .After(set_expyear4) + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "not empty")); + EXPECT_CALL(mock_web_controller_, + OnGetFieldValue(Selector({"#exp_month_year2"}), _)) + .After(set_expyear4) + .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")); client_memory_.set_selected_card( std::make_unique<autofill::CreditCard>(credit_card)); EXPECT_CALL(mock_action_delegate_, OnGetFullCard(_)) @@ -422,7 +277,7 @@ TEST_F(AutofillActionTest, FillCreditCardWithFallback) { EXPECT_EQ(ProcessedActionStatusProto::ACTION_APPLIED, ProcessAction(action)); } -TEST_F(AutofillActionTest, ForcedFallback) { +TEST_F(UseCreditCardActionTest, ForcedFallback) { ActionProto action = CreateUseCreditCardAction(); auto* cvc_required = AddRequiredField( &action, UseCreditCardProto::RequiredField::CREDIT_CARD_VERIFICATION_CODE, @@ -433,7 +288,7 @@ TEST_F(AutofillActionTest, ForcedFallback) { // No field is ever empty ON_CALL(mock_web_controller_, OnGetFieldValue(_, _)) - .WillByDefault(RunOnceCallback<1>(true, "not empty")); + .WillByDefault(RunOnceCallback<1>(OkClientStatus(), "not empty")); // But we still want the CVC filled, with simulated keypresses. Expectation set_cvc = @@ -454,5 +309,74 @@ TEST_F(AutofillActionTest, ForcedFallback) { EXPECT_EQ(ProcessedActionStatusProto::ACTION_APPLIED, ProcessAction(action)); } +TEST_F(UseCreditCardActionTest, AutofillFailureWithoutRequiredFieldsIsFatal) { + ActionProto action_proto = CreateUseCreditCardAction(); + + autofill::CreditCard credit_card; + client_memory_.set_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"), + Selector({kFakeSelector}).MustBeVisible(), _)) + .WillOnce(RunOnceCallback<3>(ClientStatus(OTHER_ACTION_STATUS))); + + 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::OTHER_ACTION_STATUS); + EXPECT_EQ(processed_action.has_status_details(), false); +} + +TEST_F(UseCreditCardActionTest, + AutofillFailureWithRequiredFieldsLaunchesFallback) { + ActionProto action_proto = CreateUseCreditCardAction(); + AddRequiredField( + &action_proto, + UseCreditCardProto::RequiredField::CREDIT_CARD_VERIFICATION_CODE, "#cvc"); + + autofill::CreditCard credit_card; + client_memory_.set_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"), + Selector({kFakeSelector}).MustBeVisible(), _)) + .WillOnce(RunOnceCallback<3>( + FillAutofillErrorStatus(ClientStatus(OTHER_ACTION_STATUS)))); + + // First validation fails. + EXPECT_CALL(mock_web_controller_, OnGetFieldValue(Selector({"#cvc"}), _)) + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "")); + // Fill CVC. + Expectation set_cvc = + EXPECT_CALL(mock_action_delegate_, + OnSetFieldValue(Selector({"#cvc"}), "123", _)) + .WillOnce(RunOnceCallback<2>(OkClientStatus())); + // Second validation succeeds. + EXPECT_CALL(mock_web_controller_, OnGetFieldValue(Selector({"#cvc"}), _)) + .After(set_cvc) + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "not empty")); + + 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::ACTION_APPLIED); + EXPECT_EQ(processed_action.status_details() + .autofill_error_info() + .autofill_error_status(), + OTHER_ACTION_STATUS); +} + } // namespace } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/actions/wait_for_document_action.cc b/chromium/components/autofill_assistant/browser/actions/wait_for_document_action.cc index 384aa921954..976ad325ade 100644 --- a/chromium/components/autofill_assistant/browser/actions/wait_for_document_action.cc +++ b/chromium/components/autofill_assistant/browser/actions/wait_for_document_action.cc @@ -24,7 +24,7 @@ void WaitForDocumentAction::InternalProcessAction( Selector selector(proto_.wait_for_document().frame()); if (selector.empty()) { // No element to wait for. - OnShortWaitForElement(/* element_found= */ true); + OnShortWaitForElement(ClientStatus(ACTION_APPLIED)); return; } delegate_->ShortWaitForElement( @@ -32,10 +32,10 @@ void WaitForDocumentAction::InternalProcessAction( weak_ptr_factory_.GetWeakPtr())); } -void WaitForDocumentAction::OnShortWaitForElement(bool element_found) { - if (!element_found) { - SendResult(ClientStatus(ELEMENT_RESOLUTION_FAILED), - DOCUMENT_UNKNOWN_READY_STATE); +void WaitForDocumentAction::OnShortWaitForElement( + const ClientStatus& element_status) { + if (!element_status.ok()) { + SendResult(element_status, DOCUMENT_UNKNOWN_READY_STATE); return; } delegate_->GetDocumentReadyState( diff --git a/chromium/components/autofill_assistant/browser/actions/wait_for_document_action.h b/chromium/components/autofill_assistant/browser/actions/wait_for_document_action.h index 1667cbe6459..a8179934221 100644 --- a/chromium/components/autofill_assistant/browser/actions/wait_for_document_action.h +++ b/chromium/components/autofill_assistant/browser/actions/wait_for_document_action.h @@ -22,7 +22,7 @@ class WaitForDocumentAction : public Action { // Overrides Action: void InternalProcessAction(ProcessActionCallback callback) override; - void OnShortWaitForElement(bool element_found); + void OnShortWaitForElement(const ClientStatus& status); void OnGetStartState(const ClientStatus& status, DocumentReadyState start_state); 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 db02dac65cf..7cf07a0faad 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 @@ -185,7 +185,7 @@ TEST_F(WaitForDocumentActionTest, WaitForDocumentInteractiveTimesOut) { TEST_F(WaitForDocumentActionTest, CheckDocumentInFrame) { EXPECT_CALL(mock_action_delegate_, OnShortWaitForElement(Selector({"#frame"}), _)) - .WillRepeatedly(RunOnceCallback<1>(true)); + .WillRepeatedly(RunOnceCallback<1>(OkClientStatus())); EXPECT_CALL(mock_action_delegate_, OnGetDocumentReadyState(Selector({"#frame"}), _)) @@ -199,7 +199,8 @@ TEST_F(WaitForDocumentActionTest, CheckDocumentInFrame) { TEST_F(WaitForDocumentActionTest, CheckFrameElementNotFound) { EXPECT_CALL(mock_action_delegate_, OnShortWaitForElement(_, _)) - .WillRepeatedly(RunOnceCallback<1>(false)); + .WillRepeatedly( + RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED))); proto_.set_timeout_ms(0); proto_.mutable_frame()->add_selectors("#frame"); diff --git a/chromium/components/autofill_assistant/browser/actions/wait_for_dom_action.cc b/chromium/components/autofill_assistant/browser/actions/wait_for_dom_action.cc index 7fdc7791df5..339e003555d 100644 --- a/chromium/components/autofill_assistant/browser/actions/wait_for_dom_action.cc +++ b/chromium/components/autofill_assistant/browser/actions/wait_for_dom_action.cc @@ -35,14 +35,14 @@ void WaitForDomAction::InternalProcessAction(ProcessActionCallback callback) { AddConditionsFromProto(); if (conditions_.empty()) { DVLOG(2) << "WaitForDomAction: no selectors specified"; - OnCheckDone(std::move(callback), INVALID_ACTION); + OnCheckDone(std::move(callback), ClientStatus(INVALID_ACTION)); return; } for (size_t i = 0; i < conditions_.size(); i++) { if (conditions_[i].selector.empty()) { DVLOG(2) << "WaitForDomAction: selector for condition " << i << " is empty"; - OnCheckDone(std::move(callback), INVALID_SELECTOR); + OnCheckDone(std::move(callback), ClientStatus(INVALID_SELECTOR)); return; } } @@ -112,8 +112,9 @@ void WaitForDomAction::AddCondition(SelectorPredicate predicate, condition.server_payload = server_payload; } -void WaitForDomAction::CheckElements(BatchElementChecker* checker, - base::OnceCallback<void(bool)> callback) { +void WaitForDomAction::CheckElements( + BatchElementChecker* checker, + base::OnceCallback<void(const ClientStatus&)> callback) { for (size_t i = 0; i < conditions_.size(); i++) { checker->AddElementCheck( conditions_[i].selector, @@ -125,32 +126,53 @@ void WaitForDomAction::CheckElements(BatchElementChecker* checker, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } -void WaitForDomAction::OnSingleElementCheckDone(size_t condition_index, - bool found) { +void WaitForDomAction::OnSingleElementCheckDone( + size_t condition_index, + const ClientStatus& element_status) { DCHECK(condition_index < conditions_.size()); Condition& condition = conditions_[condition_index]; - condition.match = - condition.predicate == SelectorPredicate::kMatch ? found : !found; + condition.status_proto = element_status.proto_status(); + condition.match = condition.predicate == SelectorPredicate::kMatch + ? element_status.ok() + : !element_status.ok(); // We can't stop here since the batch checker does not support stopping from a // single element callback. } void WaitForDomAction::OnAllElementChecksDone( - base::OnceCallback<void(bool)> callback) { + base::OnceCallback<void(const ClientStatus&)> callback) { size_t match_count = 0; + ProcessedActionStatusProto last_error = UNKNOWN_ACTION_STATUS; for (auto& condition : conditions_) { if (condition.match) { match_count++; + } else { + switch (condition.predicate) { + case SelectorPredicate::kMatch: + // For an expected match, return the status. + last_error = condition.status_proto; + break; + case SelectorPredicate::kNoMatch: + // For an expected non-match, return an ELEMENT_RESOLUTION_FAILED for + // lack of better status. We're expecting the element to not be there, + // but it is. + last_error = ELEMENT_RESOLUTION_FAILED; + break; + + // Intentionally no default case to make compilation fail if a new + // value was added to the enum that is not treated not here. + } } } bool success = require_all_ ? match_count == conditions_.size() : match_count > 0; - std::move(callback).Run(success); + std::move(callback).Run(success ? OkClientStatus() + : ClientStatus(last_error)); } void WaitForDomAction::OnCheckDone(ProcessActionCallback callback, - ProcessedActionStatusProto status) { - UpdateProcessedAction(status); + const ClientStatus& status) { + UpdateProcessedAction(status.proto_status()); for (auto& condition : conditions_) { if (condition.match && !condition.server_payload.empty()) { processed_action_proto_->mutable_wait_for_dom_result() diff --git a/chromium/components/autofill_assistant/browser/actions/wait_for_dom_action.h b/chromium/components/autofill_assistant/browser/actions/wait_for_dom_action.h index d56fa4c6191..45fe5e0c7ab 100644 --- a/chromium/components/autofill_assistant/browser/actions/wait_for_dom_action.h +++ b/chromium/components/autofill_assistant/browser/actions/wait_for_dom_action.h @@ -12,6 +12,7 @@ #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "components/autofill_assistant/browser/actions/action.h" +#include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/service.pb.h" namespace autofill_assistant { @@ -42,6 +43,9 @@ class WaitForDomAction : public Action { // True if the condition matched. bool match = false; + // Status proto result associated with this condition. + ProcessedActionStatusProto status_proto; + // A payload to report to the server when this condition match. Empty // payloads are not reported. std::string server_payload; @@ -62,14 +66,16 @@ class WaitForDomAction : public Action { const std::string& server_payload); // Check all elements using the given BatchElementChecker and reports the - // result to |callback|. + // result to |callback|. In case of failure, the last failed status is + // returned. void CheckElements(BatchElementChecker* checker, - base::OnceCallback<void(bool)> callback); - void OnSingleElementCheckDone(size_t condition_index, bool result); - void OnAllElementChecksDone(base::OnceCallback<void(bool)> callback); + base::OnceCallback<void(const ClientStatus&)> callback); + void OnSingleElementCheckDone(size_t condition_index, + const ClientStatus& element_status); + void OnAllElementChecksDone( + base::OnceCallback<void(const ClientStatus&)> callback); - void OnCheckDone(ProcessActionCallback callback, - ProcessedActionStatusProto status); + void OnCheckDone(ProcessActionCallback callback, const ClientStatus& status); bool require_all_ = false; std::vector<Condition> conditions_; 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 15426de05de..9a3632f0c58 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 @@ -30,7 +30,8 @@ class WaitForDomActionTest : public testing::Test { void SetUp() override { ON_CALL(mock_web_controller_, OnElementCheck(_, _)) - .WillByDefault(RunOnceCallback<1>(false)); + .WillByDefault( + RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED))); EXPECT_CALL(mock_action_delegate_, OnWaitForDom(_, _, _, _)) .WillRepeatedly(Invoke(this, &WaitForDomActionTest::FakeWaitForDom)); @@ -41,10 +42,10 @@ class WaitForDomActionTest : public testing::Test { void FakeWaitForDom( base::TimeDelta max_wait_time, bool allow_interrupt, - base::RepeatingCallback<void(BatchElementChecker*, - base::OnceCallback<void(bool)>)>& - check_elements, - base::OnceCallback<void(ProcessedActionStatusProto)>& callback) { + base::RepeatingCallback< + void(BatchElementChecker*, + base::OnceCallback<void(const ClientStatus&)>)>& check_elements, + base::OnceCallback<void(const ClientStatus&)>& callback) { checker_ = std::make_unique<BatchElementChecker>(); has_check_elements_result_ = false; check_elements.Run( @@ -58,7 +59,7 @@ class WaitForDomActionTest : public testing::Test { } // Called from the check_elements callback passed to FakeWaitForDom. - void OnCheckElementsDone(bool result) { + void OnCheckElementsDone(const ClientStatus& result) { ASSERT_FALSE(has_check_elements_result_); // Duplicate calls has_check_elements_result_ = true; check_elements_result_ = result; @@ -67,11 +68,10 @@ class WaitForDomActionTest : public testing::Test { // Called by |checker_| once it's done. This ends the call to // FakeWaitForDom(). void OnWaitForDomDone( - base::OnceCallback<void(ProcessedActionStatusProto)> callback) { + base::OnceCallback<void(const ClientStatus&)> callback) { ASSERT_TRUE( has_check_elements_result_); // OnCheckElementsDone() not called - std::move(callback).Run(check_elements_result_ ? ACTION_APPLIED - : ELEMENT_RESOLUTION_FAILED); + std::move(callback).Run(check_elements_result_); } // Runs the action defined in |proto_| and reports the result to |callback_|. @@ -88,7 +88,7 @@ class WaitForDomActionTest : public testing::Test { WaitForDomProto proto_; std::unique_ptr<BatchElementChecker> checker_; bool has_check_elements_result_ = false; - bool check_elements_result_ = false; + ClientStatus check_elements_result_; }; TEST_F(WaitForDomActionTest, NoSelectors) { @@ -100,7 +100,7 @@ TEST_F(WaitForDomActionTest, NoSelectors) { TEST_F(WaitForDomActionTest, MatchOneElementFound) { EXPECT_CALL(mock_web_controller_, OnElementCheck(Selector({"#element"}), _)) - .WillRepeatedly(RunOnceCallback<1>(true)); + .WillRepeatedly(RunOnceCallback<1>(OkClientStatus())); proto_.mutable_wait_until()->add_selectors("#element"); EXPECT_CALL( @@ -118,7 +118,7 @@ TEST_F(WaitForDomActionTest, MatchOneElementNotFound) { TEST_F(WaitForDomActionTest, NoMatchOneElementFound) { EXPECT_CALL(mock_web_controller_, OnElementCheck(Selector({"#element"}), _)) - .WillRepeatedly(RunOnceCallback<1>(true)); + .WillRepeatedly(RunOnceCallback<1>(OkClientStatus())); proto_.mutable_wait_while()->add_selectors("#element"); EXPECT_CALL(callback_, Run(Pointee(Property(&ProcessedActionProto::status, @@ -136,9 +136,9 @@ TEST_F(WaitForDomActionTest, NoMatchOneElementNotFound) { TEST_F(WaitForDomActionTest, AllConditionsMetWantAll) { EXPECT_CALL(mock_web_controller_, OnElementCheck(Selector({"#element1"}), _)) - .WillRepeatedly(RunOnceCallback<1>(true)); + .WillRepeatedly(RunOnceCallback<1>(OkClientStatus())); EXPECT_CALL(mock_web_controller_, OnElementCheck(Selector({"#element2"}), _)) - .WillRepeatedly(RunOnceCallback<1>(false)); + .WillRepeatedly(RunOnceCallback<1>(ClientStatus())); proto_.mutable_wait_for_all() ->add_conditions() ->mutable_must_match() @@ -155,9 +155,9 @@ TEST_F(WaitForDomActionTest, AllConditionsMetWantAll) { TEST_F(WaitForDomActionTest, AllConditionsMetWantSome) { EXPECT_CALL(mock_web_controller_, OnElementCheck(Selector({"#element1"}), _)) - .WillRepeatedly(RunOnceCallback<1>(true)); + .WillRepeatedly(RunOnceCallback<1>(OkClientStatus())); EXPECT_CALL(mock_web_controller_, OnElementCheck(Selector({"#element2"}), _)) - .WillRepeatedly(RunOnceCallback<1>(false)); + .WillRepeatedly(RunOnceCallback<1>(ClientStatus())); proto_.mutable_wait_for_any() ->add_conditions() ->mutable_must_match() @@ -234,13 +234,13 @@ TEST_F(WaitForDomActionTest, OnConditionMetWantSome) { TEST_F(WaitForDomActionTest, ReportMatchesToServer) { EXPECT_CALL(mock_web_controller_, OnElementCheck(Selector({"#element1"}), _)) - .WillRepeatedly(RunOnceCallback<1>(true)); + .WillRepeatedly(RunOnceCallback<1>(OkClientStatus())); EXPECT_CALL(mock_web_controller_, OnElementCheck(Selector({"#element2"}), _)) - .WillRepeatedly(RunOnceCallback<1>(false)); + .WillRepeatedly(RunOnceCallback<1>(ClientStatus())); EXPECT_CALL(mock_web_controller_, OnElementCheck(Selector({"#element3"}), _)) - .WillRepeatedly(RunOnceCallback<1>(false)); + .WillRepeatedly(RunOnceCallback<1>(ClientStatus())); EXPECT_CALL(mock_web_controller_, OnElementCheck(Selector({"#element4"}), _)) - .WillRepeatedly(RunOnceCallback<1>(true)); + .WillRepeatedly(RunOnceCallback<1>(OkClientStatus())); auto* condition1 = proto_.mutable_wait_for_any()->add_conditions(); condition1->mutable_must_match()->add_selectors("#element1"); diff --git a/chromium/components/autofill_assistant/browser/batch_element_checker.cc b/chromium/components/autofill_assistant/browser/batch_element_checker.cc index 4cdad7c760c..b8aa97589f4 100644 --- a/chromium/components/autofill_assistant/browser/batch_element_checker.cc +++ b/chromium/components/autofill_assistant/browser/batch_element_checker.cc @@ -84,9 +84,9 @@ void BatchElementChecker::Run(WebController* web_controller) { void BatchElementChecker::OnElementChecked( std::vector<ElementCheckCallback>* callbacks, - bool exists) { + const ClientStatus& element_status) { for (auto& callback : *callbacks) { - std::move(callback).Run(exists); + std::move(callback).Run(element_status); } callbacks->clear(); CheckDone(); @@ -94,10 +94,10 @@ void BatchElementChecker::OnElementChecked( void BatchElementChecker::OnGetFieldValue( std::vector<GetFieldValueCallback>* callbacks, - bool exists, + const ClientStatus& element_status, const std::string& value) { for (auto& callback : *callbacks) { - std::move(callback).Run(exists, value); + std::move(callback).Run(element_status, value); } callbacks->clear(); CheckDone(); diff --git a/chromium/components/autofill_assistant/browser/batch_element_checker.h b/chromium/components/autofill_assistant/browser/batch_element_checker.h index 9cf893626ee..23670ec6354 100644 --- a/chromium/components/autofill_assistant/browser/batch_element_checker.h +++ b/chromium/components/autofill_assistant/browser/batch_element_checker.h @@ -15,6 +15,7 @@ #include "base/callback.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" +#include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/selector.h" namespace autofill_assistant { @@ -30,7 +31,7 @@ class BatchElementChecker { // Callback for AddElementCheck. Argument is true if the check passed. // // An ElementCheckCallback must not delete its calling BatchElementChecker. - using ElementCheckCallback = base::OnceCallback<void(bool)>; + using ElementCheckCallback = base::OnceCallback<void(const ClientStatus&)>; // Callback for AddFieldValueCheck. Argument is true is the element exists. // The string contains the field value, or an empty string if accessing the @@ -38,7 +39,7 @@ class BatchElementChecker { // // An ElementCheckCallback must not delete its calling BatchElementChecker. using GetFieldValueCallback = - base::OnceCallback<void(bool, const std::string&)>; + base::OnceCallback<void(const ClientStatus&, const std::string&)>; // Checks an element. // @@ -68,9 +69,9 @@ class BatchElementChecker { private: void OnElementChecked(std::vector<ElementCheckCallback>* callbacks, - bool exists); + const ClientStatus& element_status); void OnGetFieldValue(std::vector<GetFieldValueCallback>* callbacks, - bool exists, + const ClientStatus& element_status, const std::string& value); void CheckDone(); diff --git a/chromium/components/autofill_assistant/browser/batch_element_checker_unittest.cc b/chromium/components/autofill_assistant/browser/batch_element_checker_unittest.cc index 37695c241c5..dd72e03f02a 100644 --- a/chromium/components/autofill_assistant/browser/batch_element_checker_unittest.cc +++ b/chromium/components/autofill_assistant/browser/batch_element_checker_unittest.cc @@ -35,13 +35,14 @@ class BatchElementCheckerTest : public testing::Test { void SetUp() override { ON_CALL(mock_web_controller_, OnElementCheck(_, _)) - .WillByDefault(RunOnceCallback<1>(false)); + .WillByDefault(RunOnceCallback<1>(ClientStatus())); ON_CALL(mock_web_controller_, OnGetFieldValue(_, _)) - .WillByDefault(RunOnceCallback<1>(false, "")); + .WillByDefault(RunOnceCallback<1>(ClientStatus(), "")); } - void OnElementExistenceCheck(const std::string& name, bool result) { - element_exists_results_[name] = result; + void OnElementExistenceCheck(const std::string& name, + const ClientStatus& result) { + element_exists_results_[name] = result.ok(); } BatchElementChecker::ElementCheckCallback ElementExistenceCallback( @@ -50,8 +51,9 @@ class BatchElementCheckerTest : public testing::Test { base::Unretained(this), name); } - void OnVisibilityRequirementCheck(const std::string& name, bool result) { - element_visible_results_[name] = result; + void OnVisibilityRequirementCheck(const std::string& name, + const ClientStatus& result) { + element_visible_results_[name] = result.ok(); } BatchElementChecker::ElementCheckCallback VisibilityRequirementCallback( @@ -62,7 +64,7 @@ class BatchElementCheckerTest : public testing::Test { } void OnFieldValueCheck(const std::string& name, - bool exists, + const ClientStatus& result, const std::string& value) { get_field_value_results_[name] = value; } @@ -104,7 +106,7 @@ TEST_F(BatchElementCheckerTest, Empty) { TEST_F(BatchElementCheckerTest, OneElementFound) { EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"exists"})), _)) - .WillOnce(RunOnceCallback<1>(true)); + .WillOnce(RunOnceCallback<1>(OkClientStatus())); checks_.AddElementCheck(Selector({"exists"}), ElementExistenceCallback("exists")); Run("was_run"); @@ -116,7 +118,7 @@ TEST_F(BatchElementCheckerTest, OneElementFound) { TEST_F(BatchElementCheckerTest, OneElementNotFound) { EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"does_not_exist"})), _)) - .WillOnce(RunOnceCallback<1>(false)); + .WillOnce(RunOnceCallback<1>(ClientStatus())); checks_.AddElementCheck(Selector({"does_not_exist"}), ElementExistenceCallback("does_not_exist")); Run("was_run"); @@ -127,7 +129,7 @@ TEST_F(BatchElementCheckerTest, OneElementNotFound) { TEST_F(BatchElementCheckerTest, OneFieldValueFound) { EXPECT_CALL(mock_web_controller_, OnGetFieldValue(Eq(Selector({"field"})), _)) - .WillOnce(RunOnceCallback<1>(true, "some value")); + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "some value")); checks_.AddFieldValueCheck(Selector({"field"}), FieldValueCallback("field")); Run("was_run"); @@ -137,7 +139,7 @@ TEST_F(BatchElementCheckerTest, OneFieldValueFound) { TEST_F(BatchElementCheckerTest, OneFieldValueNotFound) { EXPECT_CALL(mock_web_controller_, OnGetFieldValue(Eq(Selector({"field"})), _)) - .WillOnce(RunOnceCallback<1>(false, "")); + .WillOnce(RunOnceCallback<1>(ClientStatus(), "")); checks_.AddFieldValueCheck(Selector({"field"}), FieldValueCallback("field")); Run("was_run"); @@ -147,7 +149,7 @@ TEST_F(BatchElementCheckerTest, OneFieldValueNotFound) { TEST_F(BatchElementCheckerTest, OneFieldValueEmpty) { EXPECT_CALL(mock_web_controller_, OnGetFieldValue(Eq(Selector({"field"})), _)) - .WillOnce(RunOnceCallback<1>(true, "")); + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "")); checks_.AddFieldValueCheck(Selector({"field"}), FieldValueCallback("field")); Run("was_run"); @@ -157,15 +159,15 @@ TEST_F(BatchElementCheckerTest, OneFieldValueEmpty) { TEST_F(BatchElementCheckerTest, MultipleElements) { EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"1"})), _)) - .WillOnce(RunOnceCallback<1>(true)); + .WillOnce(RunOnceCallback<1>(OkClientStatus())); EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"2"})), _)) - .WillOnce(RunOnceCallback<1>(true)); + .WillOnce(RunOnceCallback<1>(OkClientStatus())); EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"3"})), _)) - .WillOnce(RunOnceCallback<1>(false)); + .WillOnce(RunOnceCallback<1>(ClientStatus())); EXPECT_CALL(mock_web_controller_, OnGetFieldValue(Eq(Selector({"4"})), _)) - .WillOnce(RunOnceCallback<1>(true, "value")); + .WillOnce(RunOnceCallback<1>(OkClientStatus(), "value")); EXPECT_CALL(mock_web_controller_, OnGetFieldValue(Eq(Selector({"5"})), _)) - .WillOnce(RunOnceCallback<1>(false, "")); + .WillOnce(RunOnceCallback<1>(ClientStatus(), "")); checks_.AddElementCheck(Selector({"1"}), ElementExistenceCallback("1")); checks_.AddElementCheck(Selector({"2"}), ElementExistenceCallback("2")); @@ -184,9 +186,9 @@ TEST_F(BatchElementCheckerTest, MultipleElements) { TEST_F(BatchElementCheckerTest, DeduplicateElementExists) { EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"1"})), _)) - .WillOnce(RunOnceCallback<1>(true)); + .WillOnce(RunOnceCallback<1>(OkClientStatus())); EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"2"})), _)) - .WillOnce(RunOnceCallback<1>(true)); + .WillOnce(RunOnceCallback<1>(OkClientStatus())); checks_.AddElementCheck(Selector({"1"}), ElementExistenceCallback("first 1")); checks_.AddElementCheck(Selector({"1"}), @@ -204,10 +206,10 @@ TEST_F(BatchElementCheckerTest, DeduplicateElementExists) { TEST_F(BatchElementCheckerTest, DeduplicateElementVisible) { EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"1"}).MustBeVisible()), _)) - .WillOnce(RunOnceCallback<1>(true)); + .WillOnce(RunOnceCallback<1>(OkClientStatus())); EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"2"}).MustBeVisible()), _)) - .WillOnce(RunOnceCallback<1>(true)); + .WillOnce(RunOnceCallback<1>(OkClientStatus())); checks_.AddElementCheck(Selector({"1"}).MustBeVisible(), VisibilityRequirementCallback("first 1")); diff --git a/chromium/components/autofill_assistant/browser/client_memory.cc b/chromium/components/autofill_assistant/browser/client_memory.cc index 2f7dfcdebb1..579015045c3 100644 --- a/chromium/components/autofill_assistant/browser/client_memory.cc +++ b/chromium/components/autofill_assistant/browser/client_memory.cc @@ -20,7 +20,7 @@ const autofill::CreditCard* ClientMemory::selected_card() const { } bool ClientMemory::has_selected_card() const { - return selected_card_.has_value(); + return selected_card() != nullptr; } const autofill::AutofillProfile* ClientMemory::selected_address( @@ -44,7 +44,7 @@ void ClientMemory::set_selected_address( } bool ClientMemory::has_selected_address(const std::string& name) const { - return selected_addresses_.find(name) != selected_addresses_.end(); + return selected_address(name) != nullptr; } void ClientMemory::set_selected_login(const WebsiteLoginFetcher::Login& login) { @@ -52,7 +52,7 @@ void ClientMemory::set_selected_login(const WebsiteLoginFetcher::Login& login) { } bool ClientMemory::has_selected_login() const { - return selected_login_.has_value(); + return selected_login() != nullptr; } const WebsiteLoginFetcher::Login* ClientMemory::selected_login() const { @@ -62,6 +62,23 @@ const WebsiteLoginFetcher::Login* ClientMemory::selected_login() const { return nullptr; } +const std::string* ClientMemory::additional_value(const std::string& key) { + auto it = additional_values_.find(key); + if (it == additional_values_.end()) { + return nullptr; + } + return &it->second; +} + +bool ClientMemory::has_additional_value(const std::string& key) { + return additional_values_.find(key) != additional_values_.end(); +} + +void ClientMemory::set_additional_value(const std::string& key, + const std::string& value) { + additional_values_[key] = value; +} + std::string ClientMemory::GetAllAddressKeyNames() const { std::vector<std::string> entries; for (const auto& entry : selected_addresses_) { diff --git a/chromium/components/autofill_assistant/browser/client_memory.h b/chromium/components/autofill_assistant/browser/client_memory.h index ee24fb35d0a..b3e4af6acab 100644 --- a/chromium/components/autofill_assistant/browser/client_memory.h +++ b/chromium/components/autofill_assistant/browser/client_memory.h @@ -55,6 +55,15 @@ class ClientMemory { // The selected login or nullptr if no login was selected. const WebsiteLoginFetcher::Login* selected_login() const; + // The additional value for |key|, or nullptr if it does not exist. + const std::string* additional_value(const std::string& key); + + // Returns true if an additional value is stored for |key|. + bool has_additional_value(const std::string& key); + + // Sets the additional value for |key|. + void set_additional_value(const std::string& key, const std::string& value); + std::string GetAllAddressKeyNames() const; private: @@ -64,6 +73,8 @@ class ClientMemory { // The address key requested by the autofill action. std::map<std::string, std::unique_ptr<autofill::AutofillProfile>> selected_addresses_; + // Maps keys to additional values. + std::map<std::string, std::string> additional_values_; }; } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/client_settings.cc b/chromium/components/autofill_assistant/browser/client_settings.cc index f375b67fdbd..ecccc89bcb8 100644 --- a/chromium/components/autofill_assistant/browser/client_settings.cc +++ b/chromium/components/autofill_assistant/browser/client_settings.cc @@ -4,11 +4,30 @@ #include "components/autofill_assistant/browser/client_settings.h" -#include "components/autofill_assistant/browser/service.pb.h" +namespace { + +bool IsValidOverlayImageProto( + const autofill_assistant::OverlayImageProto& proto) { + if (!proto.image_url().empty() && !proto.has_image_size()) { + DVLOG(1) << __func__ << ": Missing image_size in overlay_image, ignoring"; + return false; + } + + if (!proto.text().empty() && + (!proto.has_text_color() || !proto.has_text_size())) { + DVLOG(1) << __func__ + << ": Missing text_color or text_size in overlay_image, ignoring"; + return false; + } + return true; +} + +} // namespace namespace autofill_assistant { ClientSettings::ClientSettings() = default; +ClientSettings::~ClientSettings() = default; void ClientSettings::UpdateFromProto(const ClientSettingsProto& proto) { if (proto.has_periodic_script_check_interval_ms()) { @@ -44,13 +63,6 @@ void ClientSettings::UpdateFromProto(const ClientSettingsProto& proto) { if (proto.has_document_ready_check_count()) { document_ready_check_count = proto.document_ready_check_count(); } - if (proto.has_enable_graceful_shutdown()) { - enable_graceful_shutdown = proto.enable_graceful_shutdown(); - } - if (proto.has_graceful_shutdown_delay_ms()) { - graceful_shutdown_delay = - base::TimeDelta::FromMilliseconds(proto.graceful_shutdown_delay_ms()); - } if (proto.has_cancel_delay_ms()) { cancel_delay = base::TimeDelta::FromMilliseconds(proto.cancel_delay_ms()); } @@ -65,6 +77,12 @@ void ClientSettings::UpdateFromProto(const ClientSettingsProto& proto) { tap_shutdown_delay = base::TimeDelta::FromMilliseconds(proto.tap_shutdown_delay_ms()); } + if (proto.has_overlay_image() && + IsValidOverlayImageProto(proto.overlay_image())) { + overlay_image = proto.overlay_image(); + } else { + overlay_image.reset(); + } } } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/client_settings.h b/chromium/components/autofill_assistant/browser/client_settings.h index f007b58db5c..1968670c2e5 100644 --- a/chromium/components/autofill_assistant/browser/client_settings.h +++ b/chromium/components/autofill_assistant/browser/client_settings.h @@ -6,10 +6,11 @@ #define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_CLIENT_SETTINGS_H_ #include "base/macros.h" +#include "base/optional.h" #include "base/time/time.h" +#include "components/autofill_assistant/browser/service.pb.h" namespace autofill_assistant { -class ClientSettingsProto; // Global settings for the Autofill Assistant client. // @@ -20,6 +21,7 @@ class ClientSettingsProto; // pointer to the single ClientSettings instance instead of making a copy. struct ClientSettings { ClientSettings(); + ~ClientSettings(); // Time between two periodic script precondition checks. base::TimeDelta periodic_script_check_interval = @@ -61,14 +63,6 @@ struct ClientSettings { // ready. int document_ready_check_count = 50; - // Whether graceful shutdown should be enabled. If false, the UI stays - // up until it's dismissed. - bool enable_graceful_shutdown = true; - - // How long to wait before shutting down during graceful shutdown. If 0 - // shutdown happens immediately. - base::TimeDelta graceful_shutdown_delay = base::TimeDelta::FromSeconds(5); - // How much time to give users to tap undo when they tap a cancel button. base::TimeDelta cancel_delay = base::TimeDelta::FromSeconds(5); @@ -84,6 +78,9 @@ struct ClientSettings { // taps where base::TimeDelta tap_shutdown_delay = base::TimeDelta::FromSeconds(5); + // Optional image drawn on top of overlays. + base::Optional<OverlayImageProto> overlay_image; + void UpdateFromProto(const ClientSettingsProto& proto); private: diff --git a/chromium/components/autofill_assistant/browser/client_status.cc b/chromium/components/autofill_assistant/browser/client_status.cc index 6ee79da9bef..710d2d34a51 100644 --- a/chromium/components/autofill_assistant/browser/client_status.cc +++ b/chromium/components/autofill_assistant/browser/client_status.cc @@ -77,54 +77,45 @@ std::ostream& operator<<(std::ostream& out, case ProcessedActionStatusProto::USER_ABORTED_ACTION: out << "USER_ABORTED_ACTION"; break; - case ProcessedActionStatusProto::GET_FULL_CARD_FAILED: out << "GET_FULL_CARD_FAILED"; break; - case ProcessedActionStatusProto::PRECONDITION_FAILED: out << "PRECONDITION_FAILED"; break; - case ProcessedActionStatusProto::INVALID_ACTION: out << "INVALID_ACTION"; break; - case ProcessedActionStatusProto::UNSUPPORTED: out << "UNSUPPORTED"; break; - case ProcessedActionStatusProto::TIMED_OUT: out << "TIMED_OUT"; break; - case ProcessedActionStatusProto::ELEMENT_UNSTABLE: out << "ELEMENT_UNSTABLE"; break; - case ProcessedActionStatusProto::INVALID_SELECTOR: out << "INVALID_SELECTOR"; break; - case ProcessedActionStatusProto::OPTION_VALUE_NOT_FOUND: out << "OPTION_VALUE_NOT_FOUND"; break; - case ProcessedActionStatusProto::UNEXPECTED_JS_ERROR: out << "UNEXPECTED_JS_ERROR"; break; - case ProcessedActionStatusProto::TOO_MANY_ELEMENTS: out << "TOO_MANY_ELEMENTS"; break; - case ProcessedActionStatusProto::NAVIGATION_ERROR: out << "NAVIGATION_ERROR"; break; - case ProcessedActionStatusProto::AUTOFILL_INFO_NOT_AVAILABLE: out << "AUTOFILL_INFO_NOT_AVAILABLE"; break; + case ProcessedActionStatusProto::FRAME_HOST_NOT_FOUND: + out << "FRAME_HOST_NOT_FOUND"; + 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 d1343ff116a..602d2c9dab0 100644 --- a/chromium/components/autofill_assistant/browser/controller.cc +++ b/chromium/components/autofill_assistant/browser/controller.cc @@ -85,6 +85,22 @@ bool StateEndsFlow(AutofillAssistantState state) { return false; } +// Convenience method to set all fields of a |DateTimeProto|. +void SetDateTimeProto(DateTimeProto* proto, + int year, + int month, + int day, + int hour, + int minute, + int second) { + proto->mutable_date()->set_year(year); + proto->mutable_date()->set_month(month); + proto->mutable_date()->set_day(day); + proto->mutable_time()->set_hour(hour); + proto->mutable_time()->set_minute(minute); + proto->mutable_time()->set_second(second); +} + } // namespace Controller::Controller(content::WebContents* web_contents, @@ -968,6 +984,57 @@ void Controller::OnTermsAndConditionsLinkClicked(int link) { std::move(callback).Run(link); } +void Controller::SetDateTimeRangeStart(int year, + int month, + int day, + int hour, + int minute, + int second) { + if (!user_data_) + return; + + SetDateTimeProto(&user_data_->date_time_range_start, year, month, day, hour, + minute, second); + for (ControllerObserver& observer : observers_) { + observer.OnUserDataChanged(user_data_.get()); + } + UpdateCollectUserDataActions(); +} + +void Controller::SetDateTimeRangeEnd(int year, + int month, + int day, + int hour, + int minute, + int second) { + if (!user_data_) + return; + + SetDateTimeProto(&user_data_->date_time_range_end, year, month, day, hour, + minute, second); + for (ControllerObserver& observer : observers_) { + observer.OnUserDataChanged(user_data_.get()); + } + UpdateCollectUserDataActions(); +} + +void Controller::SetAdditionalValue(const std::string& client_memory_key, + const std::string& value) { + if (!user_data_) + return; + auto it = user_data_->additional_values_to_store.find(client_memory_key); + if (it == user_data_->additional_values_to_store.end()) { + NOTREACHED() << client_memory_key << " not found"; + return; + } + it->second.assign(value); + for (ControllerObserver& observer : observers_) { + observer.OnUserDataChanged(user_data_.get()); + } + // It is currently not necessary to call |UpdateCollectUserDataActions| + // because all additional values are optional. +} + void Controller::SetShippingAddress( std::unique_ptr<autofill::AutofillProfile> address) { if (!user_data_) @@ -992,23 +1059,13 @@ void Controller::SetContactInfo( UpdateCollectUserDataActions(); } -void Controller::SetCreditCard(std::unique_ptr<autofill::CreditCard> card) { +void Controller::SetCreditCard( + std::unique_ptr<autofill::CreditCard> card, + std::unique_ptr<autofill::AutofillProfile> billing_profile) { if (!user_data_) return; - autofill::AutofillProfile* billing_profile = - !card || card->billing_address_id().empty() - ? nullptr - : GetPersonalDataManager()->GetProfileByGUID( - card->billing_address_id()); - if (billing_profile) { - auto billing_address = - std::make_unique<autofill::AutofillProfile>(*billing_profile); - user_data_->billing_address = std::move(billing_address); - } else { - user_data_->billing_address.reset(); - } - + user_data_->billing_address = std::move(billing_profile); user_data_->card = std::move(card); for (ControllerObserver& observer : observers_) { observer.OnUserDataChanged(user_data_.get()); @@ -1050,7 +1107,7 @@ void Controller::UpdateCollectUserDataActions() { } bool confirm_button_enabled = CollectUserDataAction::IsUserDataComplete( - GetPersonalDataManager(), *user_data_, *collect_user_data_options_); + *user_data_, *collect_user_data_options_); UserAction confirm(collect_user_data_options_->confirm_action); confirm.SetEnabled(confirm_button_enabled); diff --git a/chromium/components/autofill_assistant/browser/controller.h b/chromium/components/autofill_assistant/browser/controller.h index a7777502fb3..9554a6ae3a8 100644 --- a/chromium/components/autofill_assistant/browser/controller.h +++ b/chromium/components/autofill_assistant/browser/controller.h @@ -152,11 +152,27 @@ class Controller : public ScriptExecutorDelegate, std::unique_ptr<autofill::AutofillProfile> address) override; void SetContactInfo( std::unique_ptr<autofill::AutofillProfile> profile) override; - void SetCreditCard(std::unique_ptr<autofill::CreditCard> card) override; + void SetCreditCard( + std::unique_ptr<autofill::CreditCard> card, + std::unique_ptr<autofill::AutofillProfile> billing_profile) override; void SetTermsAndConditions( TermsAndConditionsState terms_and_conditions) override; void SetLoginOption(std::string identifier) override; void OnTermsAndConditionsLinkClicked(int link) override; + void SetDateTimeRangeStart(int year, + int month, + int day, + int hour, + int minute, + int second) override; + void SetDateTimeRangeEnd(int year, + int month, + int day, + int hour, + int minute, + int second) override; + void SetAdditionalValue(const std::string& client_memory_key, + const std::string& value) override; void GetTouchableArea(std::vector<RectF>* area) const override; void GetRestrictedArea(std::vector<RectF>* area) const override; void GetVisualViewport(RectF* visual_viewport) const override; diff --git a/chromium/components/autofill_assistant/browser/controller_observer.cc b/chromium/components/autofill_assistant/browser/controller_observer.cc index 15fd36975ea..f951da6e0ea 100644 --- a/chromium/components/autofill_assistant/browser/controller_observer.cc +++ b/chromium/components/autofill_assistant/browser/controller_observer.cc @@ -11,31 +11,4 @@ namespace autofill_assistant { ControllerObserver::ControllerObserver() = default; ControllerObserver::~ControllerObserver() = default; - -void ControllerObserver::OnStateChanged(AutofillAssistantState new_state) {} -void ControllerObserver::OnStatusMessageChanged(const std::string& message) {} -void ControllerObserver::OnBubbleMessageChanged(const std::string& message) {} -void ControllerObserver::CloseCustomTab() {} -void ControllerObserver::OnUserActionsChanged( - const std::vector<UserAction>& user_actions) {} -void ControllerObserver::OnCollectUserDataOptionsChanged( - const CollectUserDataOptions* options) {} -void ControllerObserver::OnUserDataChanged(const UserData* state) {} -void ControllerObserver::OnDetailsChanged(const Details* details) {} -void ControllerObserver::OnInfoBoxChanged(const InfoBox* info_box) {} -void ControllerObserver::OnProgressChanged(int progress) {} -void ControllerObserver::OnProgressVisibilityChanged(bool visible) {} -void ControllerObserver::OnTouchableAreaChanged( - const RectF& visual_viewport, - const std::vector<RectF>& touchable_areas, - const std::vector<RectF>& restricted_areas) {} -void ControllerObserver::OnViewportModeChanged(ViewportMode mode) {} -void ControllerObserver::OnPeekModeChanged( - ConfigureBottomSheetProto::PeekMode peek_mode) {} -void ControllerObserver::OnOverlayColorsChanged( - const UiDelegate::OverlayColors& colors) {} -void ControllerObserver::OnFormChanged(const FormProto* form) {} -void ControllerObserver::OnClientSettingsChanged( - const ClientSettings& settings) {} - } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/controller_observer.h b/chromium/components/autofill_assistant/browser/controller_observer.h index 2c57627757a..82b93a10aca 100644 --- a/chromium/components/autofill_assistant/browser/controller_observer.h +++ b/chromium/components/autofill_assistant/browser/controller_observer.h @@ -31,44 +31,44 @@ class ControllerObserver : public base::CheckedObserver { ~ControllerObserver() override; // Called when the controller has entered a new state. - virtual void OnStateChanged(AutofillAssistantState new_state); + virtual void OnStateChanged(AutofillAssistantState new_state) = 0; // Report that the status message has changed. - virtual void OnStatusMessageChanged(const std::string& message); + virtual void OnStatusMessageChanged(const std::string& message) = 0; // Report that the bubble / tooltip message has changed. - virtual void OnBubbleMessageChanged(const std::string& message); + virtual void OnBubbleMessageChanged(const std::string& message) = 0; // If the current chrome activity is a custom tab activity, close it. // Otherwise, do nothing. - virtual void CloseCustomTab(); + virtual void CloseCustomTab() = 0; // Report that the set of user actions has changed. virtual void OnUserActionsChanged( - const std::vector<UserAction>& user_actions); + const std::vector<UserAction>& user_actions) = 0; // Report that the options configuring a CollectUserDataAction have changed. virtual void OnCollectUserDataOptionsChanged( - const CollectUserDataOptions* options); + const CollectUserDataOptions* options) = 0; // Updates the currently selected user data (e.g., contact information). - virtual void OnUserDataChanged(const UserData* state); + virtual void OnUserDataChanged(const UserData* state) = 0; // Called when details have changed. Details will be null if they have been // cleared. - virtual void OnDetailsChanged(const Details* details); + virtual void OnDetailsChanged(const Details* details) = 0; // Called when info box has changed. |info_box| will be null if it has been // cleared. - virtual void OnInfoBoxChanged(const InfoBox* info_box); + virtual void OnInfoBoxChanged(const InfoBox* info_box) = 0; // Called when the current progress has changed. Progress, is expressed as a // percentage. - virtual void OnProgressChanged(int progress); + virtual void OnProgressChanged(int progress) = 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); + virtual void OnProgressVisibilityChanged(bool visible) = 0; // Updates the area of the visible viewport that is accessible when the // overlay state is OverlayState::PARTIAL. @@ -88,22 +88,24 @@ class ControllerObserver : public base::CheckedObserver { virtual void OnTouchableAreaChanged( const RectF& visual_viewport, const std::vector<RectF>& touchable_areas, - const std::vector<RectF>& restricted_areas); + const std::vector<RectF>& restricted_areas) = 0; // Called when the viewport mode has changed. - virtual void OnViewportModeChanged(ViewportMode mode); + virtual void OnViewportModeChanged(ViewportMode mode) = 0; // Called when the peek mode has changed. - virtual void OnPeekModeChanged(ConfigureBottomSheetProto::PeekMode peek_mode); + virtual void OnPeekModeChanged( + ConfigureBottomSheetProto::PeekMode peek_mode) = 0; // Called when the overlay colors have changed. - virtual void OnOverlayColorsChanged(const UiDelegate::OverlayColors& colors); + virtual void OnOverlayColorsChanged( + const UiDelegate::OverlayColors& colors) = 0; // Called when the form has changed. - virtual void OnFormChanged(const FormProto* form); + virtual void OnFormChanged(const FormProto* form) = 0; // Called when client settings have changed. - virtual void OnClientSettingsChanged(const ClientSettings& settings); + virtual void OnClientSettingsChanged(const ClientSettings& settings) = 0; }; } // namespace autofill_assistant #endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_CONTROLLER_OBSERVER_H_ diff --git a/chromium/components/autofill_assistant/browser/controller_unittest.cc b/chromium/components/autofill_assistant/browser/controller_unittest.cc index 66e4249393a..8c4613af003 100644 --- a/chromium/components/autofill_assistant/browser/controller_unittest.cc +++ b/chromium/components/autofill_assistant/browser/controller_unittest.cc @@ -8,8 +8,8 @@ #include <utility> #include "base/guid.h" -#include "base/strings/utf_string_conversions.h" #include "base/test/gmock_callback_support.h" +#include "base/test/gtest_util.h" #include "base/test/mock_callback.h" #include "base/test/scoped_feature_list.h" #include "components/autofill/core/browser/autofill_test_utils.h" @@ -82,11 +82,11 @@ struct MockCollectUserDataOptions : public CollectUserDataOptions { MockCollectUserDataOptions() { base::MockOnceCallback<void(std::unique_ptr<UserData>)> mock_confirm_callback; - confirm_callback = std::move(mock_confirm_callback.Get()); + confirm_callback = mock_confirm_callback.Get(); base::MockOnceCallback<void(int)> mock_actions_callback; - additional_actions_callback = std::move(mock_actions_callback.Get()); + additional_actions_callback = mock_actions_callback.Get(); base::MockOnceCallback<void(int)> mock_terms_callback; - terms_link_callback = std::move(mock_terms_callback.Get()); + terms_link_callback = mock_terms_callback.Get(); } }; @@ -127,7 +127,7 @@ class ControllerTest : public content::RenderViewHostTestHarness { .WillByDefault(RunOnceCallback<4>(true, "")); ON_CALL(*mock_web_controller_, OnElementCheck(_, _)) - .WillByDefault(RunOnceCallback<1>(false)); + .WillByDefault(RunOnceCallback<1>(ClientStatus())); ON_CALL(mock_observer_, OnStateChanged(_)) .WillByDefault(Invoke([this](AutofillAssistantState state) { @@ -522,6 +522,28 @@ TEST_F(ControllerTest, Stop) { EXPECT_TRUE(controller_->PerformUserAction(0)); } +TEST_F(ControllerTest, CloseCustomTab) { + SupportsScriptResponseProto script_response; + AddRunnableScript(&script_response, "stop"); + SetNextScriptResponse(script_response); + + ActionsResponseProto actions_response; + actions_response.add_actions()->mutable_stop()->set_close_cct(true); + std::string actions_response_str; + actions_response.SerializeToString(&actions_response_str); + EXPECT_CALL(*mock_service_, OnGetActions(StrEq("stop"), _, _, _, _, _)) + .WillOnce(RunOnceCallback<5>(true, actions_response_str)); + + Start(); + ASSERT_THAT(controller_->GetUserActions(), SizeIs(1)); + EXPECT_CALL(mock_observer_, CloseCustomTab()).Times(1); + + testing::InSequence seq; + EXPECT_CALL(fake_client_, + Shutdown(Metrics::DropOutReason::CUSTOM_TAB_CLOSED)); + EXPECT_TRUE(controller_->PerformUserAction(0)); +} + TEST_F(ControllerTest, Reset) { // 1. Fetch scripts for URL, which in contains a single "reset" script. SupportsScriptResponseProto script_response; @@ -740,7 +762,7 @@ TEST_F(ControllerTest, KeepCheckingForElement) { } EXPECT_CALL(*mock_web_controller_, OnElementCheck(_, _)) - .WillRepeatedly(RunOnceCallback<1>(true)); + .WillRepeatedly(RunOnceCallback<1>(OkClientStatus())); task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(1)); EXPECT_EQ(AutofillAssistantState::AUTOSTART_FALLBACK_PROMPT, @@ -1484,7 +1506,8 @@ TEST_F(ControllerTest, UserDataFormCreditCard) { Property(&UserAction::enabled, Eq(false))))) .Times(1); controller_->SetCreditCard( - std::make_unique<autofill::CreditCard>(*credit_card)); + std::make_unique<autofill::CreditCard>(*credit_card), + /* billing_profile =*/nullptr); // Credit card with valid billing address is ok. auto billing_address = std::make_unique<autofill::AutofillProfile>( @@ -1494,15 +1517,16 @@ TEST_F(ControllerTest, UserDataFormCreditCard) { "123 Zoo St.", "unit 5", "Hollywood", "CA", "91601", "US", "16505678910"); credit_card->set_billing_address_id(billing_address->guid()); - ON_CALL(*fake_client_.GetPersonalDataManager(), - GetProfileByGUID(billing_address->guid())) - .WillByDefault(Return(billing_address.get())); EXPECT_CALL(mock_observer_, OnUserActionsChanged(UnorderedElementsAre( Property(&UserAction::enabled, Eq(true))))) .Times(1); controller_->SetCreditCard( - std::make_unique<autofill::CreditCard>(*credit_card)); + std::make_unique<autofill::CreditCard>(*credit_card), + std::make_unique<autofill::AutofillProfile>(*billing_address)); EXPECT_THAT(controller_->GetUserData()->card->Compare(*credit_card), Eq(0)); + EXPECT_THAT( + controller_->GetUserData()->billing_address->Compare(*billing_address), + Eq(0)); } TEST_F(ControllerTest, SetTermsAndConditions) { @@ -1575,4 +1599,58 @@ TEST_F(ControllerTest, SetShippingAddress) { Eq(0)); } +TEST_F(ControllerTest, SetAdditionalValues) { + auto options = std::make_unique<MockCollectUserDataOptions>(); + auto user_data = std::make_unique<UserData>(); + + user_data->additional_values_to_store["key1"] = "123456789"; + user_data->additional_values_to_store["key2"] = ""; + user_data->additional_values_to_store["key3"] = ""; + + testing::InSequence seq; + EXPECT_CALL(mock_observer_, OnUserActionsChanged(UnorderedElementsAre( + Property(&UserAction::enabled, Eq(true))))) + .Times(1); + controller_->SetCollectUserDataOptions(std::move(options), + std::move(user_data)); + + EXPECT_CALL(mock_observer_, OnUserDataChanged(Not(nullptr))).Times(2); + controller_->SetAdditionalValue("key2", "value2"); + controller_->SetAdditionalValue("key3", "value3"); + EXPECT_EQ(controller_->GetUserData()->additional_values_to_store.at("key1"), + "123456789"); + EXPECT_EQ(controller_->GetUserData()->additional_values_to_store.at("key2"), + "value2"); + EXPECT_EQ(controller_->GetUserData()->additional_values_to_store.at("key3"), + "value3"); + EXPECT_DCHECK_DEATH(controller_->SetAdditionalValue("key4", "someValue")); +} + +TEST_F(ControllerTest, SetOverlayColors) { + EXPECT_CALL( + mock_observer_, + OnOverlayColorsChanged(AllOf( + Field(&Controller::OverlayColors::background, StrEq("#FF000000")), + Field(&Controller::OverlayColors::highlight_border, + StrEq("#FFFFFFFF"))))); + + std::map<std::string, std::string> parameters; + parameters["OVERLAY_COLORS"] = "#FF000000:#FFFFFFFF"; + auto context = TriggerContext::Create(parameters, "exps"); + + GURL url("http://a.example.com/path"); + controller_->Start(url, std::move(context)); +} + +TEST_F(ControllerTest, ChangeClientSettings) { + SupportsScriptResponseProto response; + response.mutable_client_settings()->set_periodic_script_check_interval_ms(1); + SetupScripts(response); + EXPECT_CALL(mock_observer_, + OnClientSettingsChanged( + Field(&ClientSettings::periodic_script_check_interval, + base::TimeDelta::FromMilliseconds(1)))); + Start(); +} + } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/details.cc b/chromium/components/autofill_assistant/browser/details.cc index 294d566fced..0b5b66d4862 100644 --- a/chromium/components/autofill_assistant/browser/details.cc +++ b/chromium/components/autofill_assistant/browser/details.cc @@ -7,18 +7,80 @@ #include <unordered_set> #include <base/strings/stringprintf.h> +#include "base/i18n/time_formatting.h" #include "base/strings/strcat.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" #include "components/autofill/core/browser/geo/country_names.h" #include "components/autofill_assistant/browser/trigger_context.h" #include "components/strings/grit/components_strings.h" +#include "third_party/re2/src/re2/re2.h" #include "ui/base/l10n/l10n_util.h" namespace autofill_assistant { +namespace { +// TODO(b/141850276): Remove hardcoded formatting strings. +constexpr char kDateFormat[] = "EEE, MMM d"; +constexpr char kTimeFormat[] = "h:mm a"; +constexpr char kDateTimeSeparator[] = " \xE2\x80\xA2 "; constexpr char kSpaceBetweenCardNumAndDate[] = " "; +// Parse RFC 3339 date-time. Store the value in the datetime proto field. +bool ParseDateTimeStringToProto(const std::string& datetime, + DateTimeProto* result) { + // RFC 3339 format without timezone: yyyy'-'MM'-'dd'T'HH':'mm':'ss + std::string pattern = + R"rgx((\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}))rgx"; + + int year, month, day, hour, minute, second; + if (re2::RE2::FullMatch(datetime, pattern, &year, &month, &day, &hour, + &minute, &second)) { + auto* date = result->mutable_date(); + date->set_year(year); + date->set_month(month); + date->set_day(day); + auto* time = result->mutable_time(); + time->set_hour(hour); + time->set_minute(minute); + time->set_second(second); + return true; + } else { + return false; + } +} + +// Format a datetime proto with current locale. +std::string FormatDateTimeProto(const DateTimeProto& date_time) { + if (!date_time.has_date() || !date_time.has_time()) { + return std::string(); + } + auto date_proto = date_time.date(); + auto time_proto = date_time.time(); + + base::Time::Exploded exploded_time = { + date_proto.year(), date_proto.month(), + /* day_of_week = */ -1, date_proto.day(), time_proto.hour(), + time_proto.minute(), time_proto.second(), 0}; + base::Time time; + + if (base::Time::FromLocalExploded(exploded_time, &time)) { + auto date_string = base::TimeFormatWithPattern(time, kDateFormat); + auto time_string = base::TimeFormatWithPattern(time, kTimeFormat); + + return base::StrCat({base::UTF16ToUTF8(time_string), kDateTimeSeparator, + base::UTF16ToUTF8(date_string)}); + } + + return std::string(); +} + +} // namespace + +Details::Details() = default; +Details::~Details() = default; + // static bool Details::UpdateFromProto(const ShowDetailsProto& proto, Details* details) { if (!proto.has_details()) { @@ -125,52 +187,45 @@ bool Details::UpdateFromSelectedCreditCard(const ShowDetailsProto& proto, base::Value Details::GetDebugContext() const { base::Value dict(base::Value::Type::DICTIONARY); - if (!details_proto().title().empty()) - dict.SetKey("title", base::Value(details_proto().title())); - - if (!details_proto().image_url().empty()) - dict.SetKey("image_url", base::Value(details_proto().image_url())); - - if (!details_proto().total_price().empty()) - dict.SetKey("total_price", base::Value(details_proto().total_price())); - - if (!details_proto().total_price_label().empty()) - dict.SetKey("total_price_label", - base::Value(details_proto().total_price_label())); - - if (!details_proto().description_line_1().empty()) - dict.SetKey("description_line_1", - base::Value(details_proto().description_line_1())); - - if (!details_proto().description_line_2().empty()) - dict.SetKey("description_line_2", - base::Value(details_proto().description_line_2())); - - if (!details_proto().description_line_3().empty()) - dict.SetKey("description_line_3", - base::Value(details_proto().description_line_3())); - - if (details_proto().has_datetime()) { - dict.SetKey("datetime", - base::Value(base::StringPrintf( - "%d-%02d-%02dT%02d:%02d:%02d", - static_cast<int>(details_proto().datetime().date().year()), - details_proto().datetime().date().month(), - details_proto().datetime().date().day(), - details_proto().datetime().time().hour(), - details_proto().datetime().time().minute(), - details_proto().datetime().time().second()))); + if (!proto_.title().empty()) + dict.SetKey("title", base::Value(proto_.title())); + + if (!proto_.image_url().empty()) + dict.SetKey("image_url", base::Value(proto_.image_url())); + + if (!proto_.total_price().empty()) + dict.SetKey("total_price", base::Value(proto_.total_price())); + + if (!proto_.total_price_label().empty()) + dict.SetKey("total_price_label", base::Value(proto_.total_price_label())); + + if (!proto_.description_line_1().empty()) + dict.SetKey("description_line_1", base::Value(proto_.description_line_1())); + + if (!proto_.description_line_2().empty()) + dict.SetKey("description_line_2", base::Value(proto_.description_line_2())); + + if (!proto_.description_line_3().empty()) + dict.SetKey("description_line_3", base::Value(proto_.description_line_3())); + + if (proto_.has_datetime()) { + dict.SetKey( + "datetime", + base::Value(base::StringPrintf( + "%d-%02d-%02dT%02d:%02d:%02d", + static_cast<int>(proto_.datetime().date().year()), + proto_.datetime().date().month(), proto_.datetime().date().day(), + proto_.datetime().time().hour(), proto_.datetime().time().minute(), + proto_.datetime().time().second()))); } - if (!datetime_.empty()) - dict.SetKey("datetime_str", base::Value(datetime_)); dict.SetKey("user_approval_required", - base::Value(changes().user_approval_required())); - dict.SetKey("highlight_title", base::Value(changes().highlight_title())); - dict.SetKey("highlight_line1", base::Value(changes().highlight_line1())); - dict.SetKey("highlight_line2", base::Value(changes().highlight_line2())); - dict.SetKey("highlight_line3", base::Value(changes().highlight_line3())); - dict.SetKey("highlight_line3", base::Value(changes().highlight_line3())); + base::Value(change_flags_.user_approval_required())); + dict.SetKey("highlight_title", base::Value(change_flags_.highlight_title())); + dict.SetKey("highlight_line1", base::Value(change_flags_.highlight_line1())); + dict.SetKey("highlight_line2", base::Value(change_flags_.highlight_line2())); + dict.SetKey("highlight_line3", base::Value(change_flags_.highlight_line3())); + dict.SetKey("highlight_line3", base::Value(change_flags_.highlight_line3())); return dict; } @@ -186,6 +241,7 @@ bool Details::UpdateFromParameters(const TriggerContext& context) { proto_.set_animate_placeholders(true); proto_.set_show_image_placeholder(true); if (MaybeUpdateFromDetailsParameters(context)) { + Update(); return true; } @@ -208,10 +264,13 @@ bool Details::UpdateFromParameters(const TriggerContext& context) { base::Optional<std::string> screening_datetime = context.GetParameter("MOVIES_SCREENING_DATETIME"); - if (screening_datetime) { - datetime_ = screening_datetime.value(); + if (screening_datetime && + ParseDateTimeStringToProto(screening_datetime.value(), + proto_.mutable_datetime())) { is_updated = true; } + + Update(); return is_updated; } @@ -278,8 +337,121 @@ bool Details::MaybeUpdateFromDetailsParameters(const TriggerContext& context) { return details_updated; } +void Details::SetDetailsProto(const DetailsProto& proto) { + proto_ = proto; + Update(); +} + +const std::string Details::title() const { + return proto_.title(); +} + +int Details::titleMaxLines() const { + return title_max_lines_; +} + +const std::string Details::imageUrl() const { + return proto_.image_url(); +} + +bool Details::imageAllowClickthrough() const { + return proto_.image_clickthrough_data().allow_clickthrough(); +} + +const std::string Details::imageDescription() const { + return proto_.image_clickthrough_data().description(); +} + +const std::string Details::imagePositiveText() const { + return proto_.image_clickthrough_data().positive_text(); +} + +const std::string Details::imageNegativeText() const { + return proto_.image_clickthrough_data().negative_text(); +} + +const std::string Details::imageClickthroughUrl() const { + return proto_.image_clickthrough_data().clickthrough_url(); +} + +bool Details::showImagePlaceholder() const { + return proto_.show_image_placeholder(); +} + +const std::string Details::totalPriceLabel() const { + return proto_.total_price_label(); +} + +const std::string Details::totalPrice() const { + return proto_.total_price(); +} + +const std::string Details::descriptionLine1() const { + return description_line_1_content_; +} + +const std::string Details::descriptionLine2() const { + return proto_.description_line_2(); +} + +const std::string Details::descriptionLine3() const { + return description_line_3_content_; +} + +const std::string Details::priceAttribution() const { + return price_attribution_content_; +} + +bool Details::userApprovalRequired() const { + return change_flags_.user_approval_required(); +} + +bool Details::highlightTitle() const { + return change_flags_.highlight_title(); +} + +bool Details::highlightLine1() const { + return change_flags_.highlight_line1(); +} + +bool Details::highlightLine2() const { + return change_flags_.highlight_line2(); +} + +bool Details::highlightLine3() const { + return change_flags_.highlight_line3(); +} + +bool Details::animatePlaceholders() const { + return proto_.animate_placeholders(); +} + void Details::ClearChanges() { change_flags_.Clear(); } +void Details::Update() { + auto formatted_datetime = FormatDateTimeProto(proto_.datetime()); + description_line_1_content_.assign(proto_.description_line_1().empty() + ? formatted_datetime + : proto_.description_line_1()); + + description_line_3_content_.assign(proto_.total_price().empty() + ? proto_.description_line_3() + : std::string()); + price_attribution_content_.assign(proto_.total_price().empty() + ? std::string() + : proto_.description_line_3()); + + bool isDescriptionLine1Empty = descriptionLine1().empty(); + bool isDescriptionLine2Empty = descriptionLine2().empty(); + if (isDescriptionLine1Empty && isDescriptionLine2Empty) { + title_max_lines_ = 3; + } else if (isDescriptionLine1Empty || isDescriptionLine2Empty) { + title_max_lines_ = 2; + } else { + title_max_lines_ = 1; + } +} + } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/details.h b/chromium/components/autofill_assistant/browser/details.h index ccbdf74875c..fe1e4c6116e 100644 --- a/chromium/components/autofill_assistant/browser/details.h +++ b/chromium/components/autofill_assistant/browser/details.h @@ -17,9 +17,8 @@ class TriggerContext; class Details { public: - const DetailsProto& details_proto() const { return proto_; } - const DetailsChangesProto& changes() const { return change_flags_; } - const std::string datetime() const { return datetime_; } + Details(); + ~Details(); // Returns a dictionary describing the current execution context, which // is intended to be serialized as JSON string. The execution context is @@ -54,27 +53,58 @@ class Details { ClientMemory* client_memory, Details* details); - void SetDetailsProto(const DetailsProto& proto) { proto_ = proto; } - void SetDetailsChangesProto(const DetailsChangesProto& change_flags) { - change_flags_ = change_flags; - } + const std::string title() const; + int titleMaxLines() const; + const std::string imageUrl() const; + bool imageAllowClickthrough() const; + const std::string imageDescription() const; + const std::string imagePositiveText() const; + const std::string imageNegativeText() const; + const std::string imageClickthroughUrl() const; + bool showImagePlaceholder() const; + const std::string totalPriceLabel() const; + const std::string totalPrice() const; + const std::string descriptionLine1() const; + const std::string descriptionLine2() const; + const std::string descriptionLine3() const; + const std::string priceAttribution() const; + bool userApprovalRequired() const; + bool highlightTitle() const; + bool highlightLine1() const; + bool highlightLine2() const; + bool highlightLine3() const; + bool animatePlaceholders() const; + // Clears all change flags. void ClearChanges(); private: + void SetDetailsProto(const DetailsProto& proto); + void SetDetailsChangesProto(const DetailsChangesProto& change_flags) { + change_flags_ = change_flags; + } + // Tries updating the details using generic detail parameters. Returns true // if at least one generic detail parameter was found and used. bool MaybeUpdateFromDetailsParameters(const TriggerContext& context); + // Updates fields by taking the current |proto_| values into account. + void Update(); + DetailsProto proto_; DetailsChangesProto change_flags_; - // RFC 3339 date-time. Ignore if proto.description_line_1 is set. - // - // TODO(crbug.com/806868): parse RFC 3339 date-time on the C++ side and fill - // proto.description_line_1 with the result instead of carrying a string - // representation of the datetime. - std::string datetime_; + // Maximum of lines for the title. + int title_max_lines_ = 1; + + // Content to be shown in description line 1 in the UI. + std::string description_line_1_content_; + + // Content to be shown in description line 3 in the UI. + std::string description_line_3_content_; + + // Content to be shown in the price attribution view in the UI. + std::string price_attribution_content_; }; } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/details_unittest.cc b/chromium/components/autofill_assistant/browser/details_unittest.cc index 8abf364c17d..b23cabe4fc0 100644 --- a/chromium/components/autofill_assistant/browser/details_unittest.cc +++ b/chromium/components/autofill_assistant/browser/details_unittest.cc @@ -4,6 +4,7 @@ #include "components/autofill_assistant/browser/details.h" +#include "base/test/icu_test_util.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/geo/country_names.h" @@ -19,11 +20,21 @@ namespace { using ::testing::Eq; -MATCHER_P(EqualsProto, message, "") { - std::string expected_serialized, actual_serialized; - message.SerializeToString(&expected_serialized); - arg.SerializeToString(&actual_serialized); - return expected_serialized == actual_serialized; +void SetDateTimeProto(DateTimeProto* proto, + int year, + int month, + int day, + int hour, + int minute, + int second) { + auto* date = proto->mutable_date(); + date->set_year(year); + date->set_month(month); + date->set_day(day); + auto* time = proto->mutable_time(); + time->set_hour(hour); + time->set_minute(minute); + time->set_second(second); } class DetailsTest : public testing::Test { @@ -50,27 +61,6 @@ class DetailsTest : public testing::Test { ClientMemory client_memory_; }; -TEST_F(DetailsTest, DetailsProtoStoredInMemberVariable) { - Details details; - DetailsProto proto; - proto.set_title("title"); - - details.SetDetailsProto(proto); - EXPECT_THAT(proto, EqualsProto(details.details_proto())); -} - -TEST_F(DetailsTest, DetailsChangesProto) { - Details details; - DetailsChangesProto proto; - proto.set_user_approval_required(true); - - details.SetDetailsChangesProto(proto); - EXPECT_THAT(proto, EqualsProto(details.changes())); - - details.ClearChanges(); - EXPECT_THAT(DetailsChangesProto(), EqualsProto(details.changes())); -} - TEST_F(DetailsTest, UpdateFromParametersEmpty) { Details details; // Nothing has to be updated. @@ -87,13 +77,26 @@ TEST_F(DetailsTest, UpdateFromParametersShowInitialNoUpdate) { EXPECT_FALSE(details.UpdateFromParameters(*context)); } +TEST_F(DetailsTest, UpdateFromParametersSetsPlaceholderFlags) { + std::map<std::string, std::string> parameters; + parameters["DETAILS_SHOW_INITIAL"] = "true"; + + auto context = TriggerContext::Create(parameters, "exps"); + + Details details; + details.UpdateFromParameters(*context); + + EXPECT_TRUE(details.animatePlaceholders()); + EXPECT_TRUE(details.showImagePlaceholder()); +} + TEST_F(DetailsTest, UpdateFromParametersUpdateFromDetails) { std::map<std::string, std::string> parameters; parameters["DETAILS_SHOW_INITIAL"] = "true"; parameters["DETAILS_TITLE"] = "title"; parameters["DETAILS_DESCRIPTION_LINE_1"] = "line1"; parameters["DETAILS_DESCRIPTION_LINE_2"] = "line2"; - parameters["DETAILS_DESCRIPTION_LINE_3"] = "line3"; + parameters["DETAILS_DESCRIPTION_LINE_3"] = "Est. total"; parameters["DETAILS_IMAGE_URL"] = "image"; parameters["DETAILS_IMAGE_CLICKTHROUGH_URL"] = "clickthrough"; parameters["DETAILS_TOTAL_PRICE_LABEL"] = "total"; @@ -104,47 +107,39 @@ TEST_F(DetailsTest, UpdateFromParametersUpdateFromDetails) { Details details; EXPECT_TRUE(details.UpdateFromParameters(*context)); - DetailsProto expected; - expected.set_animate_placeholders(true); - expected.set_show_image_placeholder(true); - - expected.set_title("title"); - expected.set_description_line_1("line1"); - expected.set_description_line_2("line2"); - expected.set_description_line_3("line3"); - expected.set_image_url("image"); - auto* data = expected.mutable_image_clickthrough_data(); - data->set_allow_clickthrough(true); - data->set_clickthrough_url("clickthrough"); - expected.set_total_price_label("total"); - expected.set_total_price("12"); - - EXPECT_THAT(details.details_proto(), EqualsProto(expected)); + EXPECT_TRUE(details.animatePlaceholders()); + EXPECT_THAT(details.title(), Eq("title")); + EXPECT_THAT(details.descriptionLine1(), Eq("line1")); + EXPECT_THAT(details.descriptionLine2(), Eq("line2")); + EXPECT_THAT(details.priceAttribution(), Eq("Est. total")); + EXPECT_THAT(details.imageUrl(), + Eq("image")); // Overwrites show_image_placeholder + EXPECT_TRUE(details.imageAllowClickthrough()); + EXPECT_THAT(details.imageClickthroughUrl(), Eq("clickthrough")); + EXPECT_THAT(details.totalPriceLabel(), Eq("total")); + EXPECT_THAT(details.totalPrice(), Eq("12")); } TEST_F(DetailsTest, UpdateFromParametersBackwardsCompatibility) { + base::test::ScopedRestoreICUDefaultLocale restore_locale; + base::i18n::SetICUDefaultLocale("en_US"); + std::map<std::string, std::string> parameters; parameters["MOVIES_MOVIE_NAME"] = "movie_name"; parameters["MOVIES_THEATER_NAME"] = "movie_theater"; - parameters["MOVIES_SCREENING_DATETIME"] = "datetime"; + parameters["MOVIES_SCREENING_DATETIME"] = "2019-09-26T16:40:02"; auto context = TriggerContext::Create(parameters, "exps"); Details details; EXPECT_TRUE(details.UpdateFromParameters(*context)); - DetailsProto expected; - expected.set_animate_placeholders(true); - expected.set_show_image_placeholder(true); - - expected.set_title("movie_name"); - expected.set_description_line_2("movie_theater"); - - EXPECT_THAT(details.datetime(), "datetime"); - EXPECT_THAT(details.details_proto().title(), "movie_name"); - EXPECT_THAT(details.details_proto().description_line_2(), "movie_theater"); - - EXPECT_THAT(details.details_proto(), EqualsProto(expected)); + EXPECT_TRUE(details.animatePlaceholders()); + EXPECT_TRUE(details.showImagePlaceholder()); + EXPECT_THAT(details.title(), Eq("movie_name")); + EXPECT_THAT(details.descriptionLine2(), Eq("movie_theater")); + EXPECT_THAT(details.descriptionLine1(), + Eq("4:40 PM \xE2\x80\xA2 Thu, Sep 26")); } TEST_F(DetailsTest, UpdateFromProtoNoDetails) { @@ -152,16 +147,16 @@ TEST_F(DetailsTest, UpdateFromProtoNoDetails) { EXPECT_FALSE(Details::UpdateFromProto(ShowDetailsProto(), &details)); } -TEST_F(DetailsTest, UpdateFromProto) { +TEST_F(DetailsTest, UpdateFromProtoBackwardsCompatibility) { ShowDetailsProto proto; proto.mutable_details()->set_title("title"); - proto.mutable_change_flags()->set_user_approval_required(true); + proto.mutable_details()->set_description("description"); Details details; EXPECT_TRUE(Details::UpdateFromProto(proto, &details)); - EXPECT_EQ(details.details_proto().title(), "title"); - EXPECT_TRUE(details.changes().user_approval_required()); + EXPECT_THAT(details.title(), Eq("title")); + EXPECT_THAT(details.descriptionLine2(), Eq("description")); } TEST_F(DetailsTest, UpdateFromContactDetailsNoAddressInMemory) { @@ -178,11 +173,10 @@ TEST_F(DetailsTest, UpdateFromContactDetails) { EXPECT_TRUE( Details::UpdateFromContactDetails(proto, &client_memory_, &details)); - const auto& result = details.details_proto(); - EXPECT_THAT(result.title(), + EXPECT_THAT(details.title(), Eq(l10n_util::GetStringUTF8(IDS_PAYMENTS_CONTACT_DETAILS_LABEL))); - EXPECT_THAT(result.description_line_1(), Eq("Charles Hardin Holley")); - EXPECT_THAT(result.description_line_2(), Eq("\xE2\x98\xBA@gmail.com")); + EXPECT_THAT(details.descriptionLine1(), Eq("Charles Hardin Holley")); + EXPECT_THAT(details.descriptionLine2(), Eq("\xE2\x98\xBA@gmail.com")); } TEST_F(DetailsTest, UpdateFromShippingAddressNoAddressInMemory) { @@ -199,12 +193,11 @@ TEST_F(DetailsTest, UpdateFromShippingAddress) { EXPECT_TRUE( Details::UpdateFromShippingAddress(proto, &client_memory_, &details)); - const auto& result = details.details_proto(); EXPECT_THAT( - result.title(), + details.title(), Eq(l10n_util::GetStringUTF8(IDS_PAYMENTS_SHIPPING_ADDRESS_LABEL))); - EXPECT_THAT(result.description_line_1(), Eq("Charles Hardin Holley")); - EXPECT_THAT(result.description_line_2(), + EXPECT_THAT(details.descriptionLine1(), Eq("Charles Hardin Holley")); + EXPECT_THAT(details.descriptionLine2(), Eq("123 Apple St.\nunit 6 79401 Lubbock US")); } @@ -232,13 +225,197 @@ TEST_F(DetailsTest, UpdateFromCreditCard) { EXPECT_TRUE( Details::UpdateFromSelectedCreditCard(proto, &client_memory_, &details)); - const auto& result = details.details_proto(); EXPECT_THAT( - result.title(), + details.title(), Eq(l10n_util::GetStringUTF8(IDS_PAYMENTS_METHOD_OF_PAYMENT_LABEL))); // The credit card string contains 4 non-ascii dots, we just check that it // does contain something. - EXPECT_FALSE(result.description_line_1().empty()); + EXPECT_FALSE(details.descriptionLine1().empty()); +} + +TEST_F(DetailsTest, GetTitleMaxLines) { + Details details; + + ShowDetailsProto proto_no_description; + proto_no_description.mutable_details()->set_title("title"); + EXPECT_TRUE(Details::UpdateFromProto(proto_no_description, &details)); + EXPECT_THAT(details.titleMaxLines(), Eq(3)); + + ShowDetailsProto proto_description1; + proto_description1.mutable_details()->set_title("title"); + proto_description1.mutable_details()->set_description_line_1("line 1"); + EXPECT_TRUE(Details::UpdateFromProto(proto_description1, &details)); + EXPECT_THAT(details.titleMaxLines(), Eq(2)); + + ShowDetailsProto proto_description2; + proto_description2.mutable_details()->set_title("title"); + proto_description2.mutable_details()->set_description_line_2("line 2"); + EXPECT_TRUE(Details::UpdateFromProto(proto_description2, &details)); + EXPECT_THAT(details.titleMaxLines(), Eq(2)); + + ShowDetailsProto proto_description1_date; + proto_description1_date.mutable_details()->set_title("title"); + SetDateTimeProto( + proto_description1_date.mutable_details()->mutable_datetime(), 2019, 9, + 26, 16, 40, 2); + EXPECT_TRUE(Details::UpdateFromProto(proto_description1_date, &details)); + EXPECT_THAT(details.titleMaxLines(), Eq(2)); + + ShowDetailsProto proto_both_descriptions; + proto_both_descriptions.mutable_details()->set_title("title"); + proto_both_descriptions.mutable_details()->set_description_line_1("line 1"); + proto_both_descriptions.mutable_details()->set_description_line_2("line 2"); + EXPECT_TRUE(Details::UpdateFromProto(proto_both_descriptions, &details)); + EXPECT_THAT(details.titleMaxLines(), Eq(1)); +} + +TEST_F(DetailsTest, GetDescriptionLine1) { + base::test::ScopedRestoreICUDefaultLocale restore_locale; + + Details details; + + ShowDetailsProto proto_description; + proto_description.mutable_details()->set_description_line_1("line 1"); + EXPECT_TRUE(Details::UpdateFromProto(proto_description, &details)); + EXPECT_THAT(details.descriptionLine1(), Eq("line 1")); + + base::i18n::SetICUDefaultLocale("en_US"); + ShowDetailsProto proto_date; + SetDateTimeProto(proto_date.mutable_details()->mutable_datetime(), 2019, 9, + 25, 15, 16, 0); + EXPECT_TRUE(Details::UpdateFromProto(proto_date, &details)); + EXPECT_THAT(details.descriptionLine1(), + Eq("3:16 PM \xE2\x80\xA2 Wed, Sep 25")); + + base::i18n::SetICUDefaultLocale("de_DE"); + ShowDetailsProto proto_date_de; + SetDateTimeProto(proto_date.mutable_details()->mutable_datetime(), 2019, 9, + 25, 15, 16, 0); + EXPECT_TRUE(Details::UpdateFromProto(proto_date, &details)); + EXPECT_THAT(details.descriptionLine1(), + Eq("3:16 PM \xE2\x80\xA2 Mi., 25. Sept.")); + + ShowDetailsProto proto_empty; + proto_empty.mutable_details()->set_title("title"); + EXPECT_TRUE(Details::UpdateFromProto(proto_empty, &details)); + EXPECT_THAT(details.descriptionLine1(), Eq("")); +} + +TEST_F(DetailsTest, GetDescriptionLine2) { + Details details; + + ShowDetailsProto proto_description; + proto_description.mutable_details()->set_title("title"); + proto_description.mutable_details()->set_description_line_2("line 2"); + EXPECT_TRUE(Details::UpdateFromProto(proto_description, &details)); + EXPECT_THAT(details.descriptionLine2(), Eq("line 2")); +} + +TEST_F(DetailsTest, GetDescriptionLine3) { + Details details; + + ShowDetailsProto proto_no_price; + proto_no_price.mutable_details()->set_title("title"); + proto_no_price.mutable_details()->set_description_line_3("line 3"); + EXPECT_TRUE(Details::UpdateFromProto(proto_no_price, &details)); + EXPECT_THAT(details.descriptionLine3(), Eq("line 3")); + + ShowDetailsProto proto_with_price; + proto_with_price.mutable_details()->set_title("title"); + proto_with_price.mutable_details()->set_description_line_3("Est. total"); + proto_with_price.mutable_details()->set_total_price("$2.50"); + EXPECT_TRUE(Details::UpdateFromProto(proto_with_price, &details)); + EXPECT_THAT(details.descriptionLine3(), Eq("")); +} + +TEST_F(DetailsTest, GetPriceAttribution) { + Details details; + + ShowDetailsProto proto_no_price; + proto_no_price.mutable_details()->set_title("title"); + proto_no_price.mutable_details()->set_description_line_3("line 3"); + EXPECT_TRUE(Details::UpdateFromProto(proto_no_price, &details)); + EXPECT_THAT(details.priceAttribution(), Eq("")); + + ShowDetailsProto proto_with_price; + proto_with_price.mutable_details()->set_title("title"); + proto_with_price.mutable_details()->set_description_line_3("Est. total"); + proto_with_price.mutable_details()->set_total_price("$2.50"); + EXPECT_TRUE(Details::UpdateFromProto(proto_with_price, &details)); + EXPECT_THAT(details.priceAttribution(), Eq("Est. total")); +} + +TEST_F(DetailsTest, GetTitle) { + Details details; + ShowDetailsProto proto; + proto.mutable_details()->set_title("title"); + EXPECT_TRUE(Details::UpdateFromProto(proto, &details)); + EXPECT_THAT(details.title(), Eq("title")); +} + +TEST_F(DetailsTest, GetImageUrl) { + Details details; + ShowDetailsProto proto; + proto.mutable_details()->set_image_url("url"); + EXPECT_TRUE(Details::UpdateFromProto(proto, &details)); + EXPECT_THAT(details.imageUrl(), Eq("url")); +} + +TEST_F(DetailsTest, GetClickthroughData) { + Details details; + ShowDetailsProto proto; + auto* clitkthrough_data = + proto.mutable_details()->mutable_image_clickthrough_data(); + clitkthrough_data->set_allow_clickthrough(true); + clitkthrough_data->set_description("description"); + clitkthrough_data->set_positive_text("positive"); + clitkthrough_data->set_negative_text("negative"); + proto.mutable_details() + ->mutable_image_clickthrough_data() + ->set_clickthrough_url("url"); + EXPECT_TRUE(Details::UpdateFromProto(proto, &details)); + EXPECT_TRUE(details.imageAllowClickthrough()); + EXPECT_THAT(details.imageDescription(), Eq("description")); + EXPECT_THAT(details.imagePositiveText(), Eq("positive")); + EXPECT_THAT(details.imageNegativeText(), Eq("negative")); + EXPECT_THAT(details.imageClickthroughUrl(), Eq("url")); +} + +TEST_F(DetailsTest, GetPlaceholderFlags) { + Details details; + ShowDetailsProto proto; + proto.mutable_details()->set_show_image_placeholder(true); + proto.mutable_details()->set_animate_placeholders(true); + EXPECT_TRUE(Details::UpdateFromProto(proto, &details)); + EXPECT_TRUE(details.showImagePlaceholder()); + EXPECT_TRUE(details.animatePlaceholders()); +} + +TEST_F(DetailsTest, GetTotalPrice) { + Details details; + ShowDetailsProto proto; + proto.mutable_details()->set_total_price_label("Total"); + proto.mutable_details()->set_total_price("$2.50"); + EXPECT_TRUE(Details::UpdateFromProto(proto, &details)); + EXPECT_THAT(details.totalPriceLabel(), Eq("Total")); + EXPECT_THAT(details.totalPrice(), Eq("$2.50")); +} + +TEST_F(DetailsTest, GetHighlightFlags) { + Details details; + ShowDetailsProto proto; + proto.mutable_details()->set_title("title"); + proto.mutable_change_flags()->set_user_approval_required(true); + proto.mutable_change_flags()->set_highlight_title(true); + proto.mutable_change_flags()->set_highlight_line1(true); + proto.mutable_change_flags()->set_highlight_line2(true); + proto.mutable_change_flags()->set_highlight_line3(true); + EXPECT_TRUE(Details::UpdateFromProto(proto, &details)); + EXPECT_TRUE(details.userApprovalRequired()); + EXPECT_TRUE(details.highlightTitle()); + EXPECT_TRUE(details.highlightLine1()); + EXPECT_TRUE(details.highlightLine2()); + EXPECT_TRUE(details.highlightLine3()); } } // namespace diff --git a/chromium/components/autofill_assistant/browser/devtools/devtools_api/domain_cc.template b/chromium/components/autofill_assistant/browser/devtools/devtools_api/domain_cc.template index ffa1cde75b6..309c0878a77 100644 --- a/chromium/components/autofill_assistant/browser/devtools/devtools_api/domain_cc.template +++ b/chromium/components/autofill_assistant/browser/devtools/devtools_api/domain_cc.template @@ -48,8 +48,9 @@ void Domain::RegisterEventHandlersIfNeeded() { {% set method_name = command.name | sanitize_literal | to_title_case %} void {{class_name}}::{{method_name}}( std::unique_ptr<{{method_name}}Params> params, + const std::string& optional_node_frame_id, base::OnceCallback<void(const MessageDispatcher::ReplyStatus&, std::unique_ptr<{{method_name}}Result>)> callback) { - dispatcher_->SendMessage("{{domain.domain}}.{{command.name}}", params->Serialize(), base::BindOnce(&Domain::Handle{{method_name}}Response, std::move(callback))); + dispatcher_->SendMessage("{{domain.domain}}.{{command.name}}", params->Serialize(), optional_node_frame_id, base::BindOnce(&Domain::Handle{{method_name}}Response, std::move(callback))); } {# Generate convenience methods that take the required parameters directly. #} {% if not "parameters" in command %}{% continue %}{% endif %} @@ -65,6 +66,7 @@ void {{class_name}}::{{method_name}}({##} {{resolve_type(parameter).pass_type}} {{parameter.name | camelcase_to_hacker_style -}} {% endfor %} {% if command.get("parameters", []) and not command.parameters[0].get("optional", False) %}, {% endif %}{# -#} + const std::string& optional_node_frame_id, {% if command.get("returns", []) -%} base::OnceCallback<void(const MessageDispatcher::ReplyStatus&, std::unique_ptr<{{method_name}}Result>)> callback{##} {% else -%} @@ -81,16 +83,16 @@ void {{class_name}}::{{method_name}}({##} .Build(); {# Send the message. #} {% if command.get("returns", []) -%} - dispatcher_->SendMessage("{{domain.domain}}.{{command.name}}", params->Serialize(), base::BindOnce(&Domain::Handle{{method_name}}Response, std::move(callback))); + dispatcher_->SendMessage("{{domain.domain}}.{{command.name}}", params->Serialize(), optional_node_frame_id, base::BindOnce(&Domain::Handle{{method_name}}Response, std::move(callback))); {% else %} - dispatcher_->SendMessage("{{domain.domain}}.{{command.name}}", params->Serialize(), std::move(callback)); + dispatcher_->SendMessage("{{domain.domain}}.{{command.name}}", params->Serialize(), optional_node_frame_id, std::move(callback)); {% endif %} } {# If the command has no return value, generate a convenience method that #} {# accepts a base::OnceClosure together with the parameters object. #} {% if not command.get("returns", []) %} -void {{class_name}}::{{method_name}}(std::unique_ptr<{{method_name}}Params> params, base::OnceClosure callback) { - dispatcher_->SendMessage("{{domain.domain}}.{{command.name}}", params->Serialize(), std::move(callback)); +void {{class_name}}::{{method_name}}(std::unique_ptr<{{method_name}}Params> params, const std::string& optional_node_frame_id, base::OnceClosure callback) { + dispatcher_->SendMessage("{{domain.domain}}.{{command.name}}", params->Serialize(), optional_node_frame_id, std::move(callback)); } {% endif %} {% endfor %} diff --git a/chromium/components/autofill_assistant/browser/devtools/devtools_api/domain_h.template b/chromium/components/autofill_assistant/browser/devtools/devtools_api/domain_h.template index 785d4cd7152..0d0afca14f2 100644 --- a/chromium/components/autofill_assistant/browser/devtools/devtools_api/domain_h.template +++ b/chromium/components/autofill_assistant/browser/devtools/devtools_api/domain_h.template @@ -23,7 +23,7 @@ {% if command.description %} // {{ command.description.replace('\n', '\n // ') }} {% endif %} - void {{method_name}}(std::unique_ptr<{{method_name}}Params> params, base::OnceCallback<void(const MessageDispatcher::ReplyStatus&, std::unique_ptr<{{method_name}}Result>)> callback = base::OnceCallback<void(const MessageDispatcher::ReplyStatus&, std::unique_ptr<{{method_name}}Result>)>()); + void {{method_name}}(std::unique_ptr<{{method_name}}Params> params, const std::string& optional_node_frame_id, base::OnceCallback<void(const MessageDispatcher::ReplyStatus&, std::unique_ptr<{{method_name}}Result>)> callback = base::OnceCallback<void(const MessageDispatcher::ReplyStatus&, std::unique_ptr<{{method_name}}Result>)>()); {# Generate convenience methods that take the required parameters directly. #} {# Don't generate these for experimental commands. #} {% if "parameters" in command and not command.experimental %} @@ -36,6 +36,7 @@ {{resolve_type(parameter).pass_type}} {{parameter.name | camelcase_to_hacker_style -}} {% endfor %} {% if command.get("parameters", []) and not command.parameters[0].get("optional", False) %}, {% endif %}{# -#} + const std::string& optional_node_frame_id, {% if command.get("returns", []) -%} base::OnceCallback<void(const MessageDispatcher::ReplyStatus&, std::unique_ptr<{{method_name}}Result>)> callback = base::OnceCallback<void(const MessageDispatcher::ReplyStatus&, std::unique_ptr<{{method_name}}Result>)>(){##} {% else -%} @@ -44,7 +45,7 @@ {# If the command has no return value, generate a convenience method that #} {# accepts a base::Closure together with the parameters object. #} {% if not command.get("returns", []) %} - void {{method_name}}(std::unique_ptr<{{method_name}}Params> params, base::OnceClosure callback); + void {{method_name}}(std::unique_ptr<{{method_name}}Params> params, const std::string& optional_node_frame_id, base::OnceClosure callback); {% endif %} {% endif %} {% endmacro %} diff --git a/chromium/components/autofill_assistant/browser/devtools/devtools_client.cc b/chromium/components/autofill_assistant/browser/devtools/devtools_client.cc index 39911cdb7a0..964d07bb5a4 100644 --- a/chromium/components/autofill_assistant/browser/devtools/devtools_client.cc +++ b/chromium/components/autofill_assistant/browser/devtools/devtools_client.cc @@ -26,14 +26,18 @@ DevtoolsClient::DevtoolsClient( dom_domain_(this), runtime_domain_(this), network_domain_(this), + target_domain_(this), renderer_crashed_(false), - next_message_id_(0) { + next_message_id_(0), + frame_tracker_(this) { browser_main_thread_ = base::CreateSingleThreadTaskRunner({content::BrowserThread::UI}); agent_host_->AttachClient(this); + frame_tracker_.Start(); } DevtoolsClient::~DevtoolsClient() { + frame_tracker_.Stop(); agent_host_->DetachClient(this); } @@ -53,27 +57,43 @@ network::Domain* DevtoolsClient::GetNetwork() { return &network_domain_; } +target::ExperimentalDomain* DevtoolsClient::GetTarget() { + return &target_domain_; +} + void DevtoolsClient::SendMessage( const char* method, std::unique_ptr<base::Value> params, + const std::string& optional_node_frame_id, base::OnceCallback<void(const ReplyStatus&, const base::Value&)> callback) { - SendMessageWithParams(method, std::move(params), std::move(callback)); + SendMessageWithParams(method, std::move(params), optional_node_frame_id, + std::move(callback)); } void DevtoolsClient::SendMessage(const char* method, std::unique_ptr<base::Value> params, + const std::string& optional_node_frame_id, base::OnceClosure callback) { - SendMessageWithParams(method, std::move(params), std::move(callback)); + SendMessageWithParams(method, std::move(params), optional_node_frame_id, + std::move(callback)); } template <typename CallbackType> -void DevtoolsClient::SendMessageWithParams(const char* method, - std::unique_ptr<base::Value> params, - CallbackType callback) { +void DevtoolsClient::SendMessageWithParams( + const char* method, + std::unique_ptr<base::Value> params, + const std::string& optional_node_frame_id, + CallbackType callback) { base::DictionaryValue message; message.SetString("method", method); message.Set("params", std::move(params)); + std::string optional_session_id = + GetSessionIdForFrame(optional_node_frame_id); + if (!optional_session_id.empty()) { + message.SetString("sessionId", optional_session_id); + } + if (renderer_crashed_) return; int id = next_message_id_; @@ -95,6 +115,11 @@ void DevtoolsClient::RegisterEventHandler( event_handlers_[method] = std::move(callback); } +void DevtoolsClient::UnregisterEventHandler(const char* method) { + DCHECK(event_handlers_.find(method) != event_handlers_.end()); + event_handlers_.erase(method); +} + void DevtoolsClient::DispatchProtocolMessage( content::DevToolsAgentHost* agent_host, const std::string& json_message) { @@ -254,6 +279,11 @@ void DevtoolsClient::AgentHostClosed(content::DevToolsAgentHost* agent_host) { renderer_crashed_ = true; } +std::string DevtoolsClient::GetSessionIdForFrame( + const std::string& frame_id) const { + return frame_tracker_.GetSessionIdForFrame(frame_id); +} + DevtoolsClient::Callback::Callback() = default; DevtoolsClient::Callback::Callback(Callback&& other) = default; @@ -270,4 +300,114 @@ DevtoolsClient::Callback::~Callback() = default; DevtoolsClient::Callback& DevtoolsClient::Callback::operator=( Callback&& other) = default; +DevtoolsClient::FrameTracker::FrameTracker(DevtoolsClient* client) + : client_(client) {} + +DevtoolsClient::FrameTracker::~FrameTracker() = default; + +void DevtoolsClient::FrameTracker::Start() { + client_->RegisterEventHandler( + "Target.attachedToTarget", + base::BindRepeating(&DevtoolsClient::FrameTracker::OnAttachedToTarget, + weak_ptr_factory_.GetWeakPtr())); + client_->RegisterEventHandler( + "Target.detachedFromTarget", + base::BindRepeating(&DevtoolsClient::FrameTracker::OnDetachedFromTarget, + weak_ptr_factory_.GetWeakPtr())); + + started_ = true; + + // Start auto attaching so that we can keep track of what session got started + // for what target. We use flatten = true to cover the entire frame tree. + client_->GetTarget()->SetAutoAttach( + target::SetAutoAttachParams::Builder() + .SetAutoAttach(true) + .SetWaitForDebuggerOnStart(false) + .SetFlatten(true) + .Build(), + /* node_frame_id= */ "", + base::BindOnce(&DevtoolsClient::FrameTracker::OnSetAutoAttach, + weak_ptr_factory_.GetWeakPtr())); +} + +void DevtoolsClient::FrameTracker::Stop() { + if (!started_) { + return; + } + + client_->UnregisterEventHandler("Target.attachedToTarget"); + client_->UnregisterEventHandler("Target.detachedFromTarget"); + + started_ = false; +} + +void DevtoolsClient::FrameTracker::OnSetAutoAttach( + const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<target::SetAutoAttachResult> result) { + // This is not used since result doesn't contain anything useful. The real + // action is happening in the On(Attached|Detached) functions. + DCHECK(result); +} + +std::string DevtoolsClient::FrameTracker::GetSessionIdForFrame( + std::string frame_id) const { + if (frame_id.empty()) { + return std::string(); + } + + auto it = sessions_map_.find(frame_id); + if (it != sessions_map_.end()) { + return it->second; + } + DVLOG(3) << "No session id for frame_id: " << frame_id; + return std::string(); +} + +std::string DevtoolsClient::FrameTracker::FindTargetId( + const base::Value& value) { + const base::Value* target_info = value.FindKey("targetInfo"); + if (!target_info) { + DVLOG(3) << "No target_info found in " << value; + return std::string(); + } + const std::string* target_id = target_info->FindStringKey("targetId"); + if (!target_id) { + DVLOG(3) << "No target_id found in " << *target_info; + return std::string(); + } + + return *target_id; +} + +std::string DevtoolsClient::FrameTracker::FindSessionId( + const base::Value& value) { + const std::string* session_id = value.FindStringKey("sessionId"); + if (!session_id) { + DVLOG(3) << "No session_id found in " << value; + return std::string(); + } + + return *session_id; +} + +void DevtoolsClient::FrameTracker::OnAttachedToTarget( + const base::Value& value) { + std::string session_id = FindSessionId(value); + std::string target_id = FindTargetId(value); + + if (!session_id.empty() && !target_id.empty()) { + sessions_map_[target_id] = session_id; + } +} + +void DevtoolsClient::FrameTracker::OnDetachedFromTarget( + const base::Value& value) { + std::string target_id = FindTargetId(value); + + auto it = sessions_map_.find(target_id); + if (it != sessions_map_.end()) { + sessions_map_.erase(it); + } +} + } // namespace autofill_assistant. diff --git a/chromium/components/autofill_assistant/browser/devtools/devtools_client.h b/chromium/components/autofill_assistant/browser/devtools/devtools_client.h index 4b86b9ecc65..2ec99084bc3 100644 --- a/chromium/components/autofill_assistant/browser/devtools/devtools_client.h +++ b/chromium/components/autofill_assistant/browser/devtools/devtools_client.h @@ -23,6 +23,7 @@ #include "components/autofill_assistant/browser/devtools/devtools/domains/input.h" #include "components/autofill_assistant/browser/devtools/devtools/domains/network.h" #include "components/autofill_assistant/browser/devtools/devtools/domains/runtime.h" +#include "components/autofill_assistant/browser/devtools/devtools/domains/target.h" #include "components/autofill_assistant/browser/devtools/message_dispatcher.h" #include "content/public/browser/devtools_agent_host.h" #include "content/public/browser/devtools_agent_host_client.h" @@ -39,19 +40,23 @@ class DevtoolsClient : public MessageDispatcher, dom::Domain* GetDOM(); runtime::Domain* GetRuntime(); network::Domain* GetNetwork(); + target::ExperimentalDomain* GetTarget(); // MessageDispatcher implementation: void SendMessage( const char* method, std::unique_ptr<base::Value> params, + const std::string& optional_node_frame_id, base::OnceCallback<void(const ReplyStatus&, const base::Value&)> callback) override; void SendMessage(const char* method, std::unique_ptr<base::Value> params, + const std::string& optional_node_frame_id, base::OnceClosure callback) override; void RegisterEventHandler( const char* method, base::RepeatingCallback<void(const base::Value&)> callback) override; + void UnregisterEventHandler(const char* method) override; // content::DevToolsAgentHostClient overrides: void DispatchProtocolMessage(content::DevToolsAgentHost* agent_host, @@ -76,9 +81,52 @@ class DevtoolsClient : public MessageDispatcher, callback_with_result; }; + // Manages a map to retrieve a session id from a given frame id. Registers + // itself to client events. + class FrameTracker { + public: + // Register the event handlers and start tracking new targets. |client| + // must outlive this frame tracker. + FrameTracker(DevtoolsClient* client); + ~FrameTracker(); + + void Start(); + void Stop(); + + // Returns empty string if there is no session for the given |frame_id|. + std::string GetSessionIdForFrame(std::string frame_id) const; + + private: + void OnSetAutoAttach(const DevtoolsClient::ReplyStatus& reply_status, + std::unique_ptr<target::SetAutoAttachResult> result); + void OnAttachedToTarget(const base::Value& value); + void OnDetachedFromTarget(const base::Value& value); + + // Save find of targetInfo.targetId in the given value. Returns + // empty string if nothing is found. + std::string FindTargetId(const base::Value& value); + + // Find sessionId in the given value. Returns empty string if nothing is + // found. + std::string FindSessionId(const base::Value& value); + + DevtoolsClient* client_; + bool started_ = false; + + // Holds the mappings from frame id to session id. + std::unordered_map<std::string, std::string> sessions_map_; + + base::WeakPtrFactory<FrameTracker> weak_ptr_factory_{this}; + DISALLOW_COPY_AND_ASSIGN(FrameTracker); + }; + + // If the frame is known to devtools, return the session id for it. + std::string GetSessionIdForFrame(const std::string& frame_id) const; + template <typename CallbackType> void SendMessageWithParams(const char* method, std::unique_ptr<base::Value> params, + const std::string& optional_node_frame_id, CallbackType callback); bool DispatchMessageReply(std::unique_ptr<base::Value> owning_message, const base::DictionaryValue& message_dict); @@ -105,10 +153,12 @@ class DevtoolsClient : public MessageDispatcher, dom::ExperimentalDomain dom_domain_; runtime::ExperimentalDomain runtime_domain_; network::ExperimentalDomain network_domain_; + target::ExperimentalDomain target_domain_; std::unordered_map<int, Callback> pending_messages_; EventHandlerMap event_handlers_; bool renderer_crashed_; int next_message_id_; + FrameTracker frame_tracker_; base::WeakPtrFactory<DevtoolsClient> weak_ptr_factory_{this}; DISALLOW_COPY_AND_ASSIGN(DevtoolsClient); diff --git a/chromium/components/autofill_assistant/browser/devtools/message_dispatcher.h b/chromium/components/autofill_assistant/browser/devtools/message_dispatcher.h index a5171389127..71126c85202 100644 --- a/chromium/components/autofill_assistant/browser/devtools/message_dispatcher.h +++ b/chromium/components/autofill_assistant/browser/devtools/message_dispatcher.h @@ -38,15 +38,18 @@ class MessageDispatcher { virtual void SendMessage( const char* method, std::unique_ptr<base::Value> params, + const std::string& optional_node_frame_id, base::OnceCallback<void(const ReplyStatus&, const base::Value&)> callback) = 0; virtual void SendMessage(const char* method, std::unique_ptr<base::Value> params, + const std::string& optional_node_frame_id, base::OnceClosure callback) = 0; virtual void RegisterEventHandler( const char* method, base::RepeatingCallback<void(const base::Value&)> callback) = 0; + virtual void UnregisterEventHandler(const char* method) = 0; protected: virtual ~MessageDispatcher() {} diff --git a/chromium/components/autofill_assistant/browser/element_precondition.cc b/chromium/components/autofill_assistant/browser/element_precondition.cc index 45efbfd773c..59f0986d3a6 100644 --- a/chromium/components/autofill_assistant/browser/element_precondition.cc +++ b/chromium/components/autofill_assistant/browser/element_precondition.cc @@ -42,7 +42,7 @@ void ElementPrecondition::Check(BatchElementChecker* batch_checks, callback_ = std::move(callback); for (const auto& selector : elements_exist_) { - base::OnceCallback<void(bool)> callback = + base::OnceCallback<void(const ClientStatus&)> callback = base::BindOnce(&ElementPrecondition::OnCheckElementExists, weak_ptr_factory_.GetWeakPtr()); batch_checks->AddElementCheck(selector, std::move(callback)); @@ -57,14 +57,15 @@ void ElementPrecondition::Check(BatchElementChecker* batch_checks, } } -void ElementPrecondition::OnCheckElementExists(bool exists) { - ReportCheckResult(exists); +void ElementPrecondition::OnCheckElementExists( + const ClientStatus& element_status) { + ReportCheckResult(element_status.ok()); } void ElementPrecondition::OnGetFieldValue(int index, - bool exists, + const ClientStatus& element_status, const std::string& value) { - if (!exists) { + if (!element_status.ok()) { ReportCheckResult(false); return; } diff --git a/chromium/components/autofill_assistant/browser/element_precondition.h b/chromium/components/autofill_assistant/browser/element_precondition.h index 9e7497f2791..edfce0d6719 100644 --- a/chromium/components/autofill_assistant/browser/element_precondition.h +++ b/chromium/components/autofill_assistant/browser/element_precondition.h @@ -11,6 +11,7 @@ #include "base/callback.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" +#include "components/autofill_assistant/browser/client_status.h" #include "components/autofill_assistant/browser/service.pb.h" namespace autofill_assistant { @@ -37,8 +38,10 @@ class ElementPrecondition { bool empty() { return elements_exist_.empty() && form_value_match_.empty(); } private: - void OnCheckElementExists(bool exists); - void OnGetFieldValue(int index, bool exists, const std::string& value); + void OnCheckElementExists(const ClientStatus& element_status); + void OnGetFieldValue(int index, + const ClientStatus& element_status, + const std::string& value); void ReportCheckResult(bool success); std::vector<Selector> elements_exist_; diff --git a/chromium/components/autofill_assistant/browser/element_precondition_unittest.cc b/chromium/components/autofill_assistant/browser/element_precondition_unittest.cc index eb00f23c9c3..542528b7700 100644 --- a/chromium/components/autofill_assistant/browser/element_precondition_unittest.cc +++ b/chromium/components/autofill_assistant/browser/element_precondition_unittest.cc @@ -28,20 +28,20 @@ class ElementPreconditionTest : public testing::Test { public: void SetUp() override { ON_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"exists"})), _)) - .WillByDefault(RunOnceCallback<1>(true)); + .WillByDefault(RunOnceCallback<1>(OkClientStatus())); ON_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"empty"})), _)) - .WillByDefault(RunOnceCallback<1>(true)); + .WillByDefault(RunOnceCallback<1>(OkClientStatus())); ON_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"does_not_exist"})), _)) - .WillByDefault(RunOnceCallback<1>(false)); + .WillByDefault(RunOnceCallback<1>(ClientStatus())); ON_CALL(mock_web_controller_, OnGetFieldValue(Eq(Selector({"exists"})), _)) - .WillByDefault(RunOnceCallback<1>(true, "foo")); + .WillByDefault(RunOnceCallback<1>(OkClientStatus(), "foo")); ON_CALL(mock_web_controller_, OnGetFieldValue(Eq(Selector({"does_not_exist"})), _)) - .WillByDefault(RunOnceCallback<1>(false, "")); + .WillByDefault(RunOnceCallback<1>(ClientStatus(), "")); ON_CALL(mock_web_controller_, OnGetFieldValue(Eq(Selector({"empty"})), _)) - .WillByDefault(RunOnceCallback<1>(true, "")); + .WillByDefault(RunOnceCallback<1>(OkClientStatus(), "")); } protected: diff --git a/chromium/components/autofill_assistant/browser/mock_controller_observer.h b/chromium/components/autofill_assistant/browser/mock_controller_observer.h index 151bdc1b461..995f6629cbc 100644 --- a/chromium/components/autofill_assistant/browser/mock_controller_observer.h +++ b/chromium/components/autofill_assistant/browser/mock_controller_observer.h @@ -13,6 +13,7 @@ #include "components/autofill_assistant/browser/controller_observer.h" #include "components/autofill_assistant/browser/metrics.h" #include "components/autofill_assistant/browser/script.h" +#include "components/autofill_assistant/browser/ui_delegate.h" #include "testing/gmock/include/gmock/gmock.h" namespace autofill_assistant { @@ -24,6 +25,7 @@ class MockControllerObserver : public ControllerObserver { MOCK_METHOD1(OnStatusMessageChanged, void(const std::string& message)); MOCK_METHOD1(OnBubbleMessageChanged, void(const std::string& message)); + MOCK_METHOD0(CloseCustomTab, void()); MOCK_METHOD1(OnStateChanged, void(AutofillAssistantState)); MOCK_METHOD1(OnUserActionsChanged, void(const std::vector<UserAction>& user_actions)); @@ -43,9 +45,10 @@ class MockControllerObserver : public ControllerObserver { MOCK_METHOD1(OnViewportModeChanged, void(ViewportMode mode)); MOCK_METHOD1(OnPeekModeChanged, void(ConfigureBottomSheetProto::PeekMode peek_mode)); + MOCK_METHOD1(OnOverlayColorsChanged, + void(const UiDelegate::OverlayColors& colors)); MOCK_METHOD1(OnFormChanged, void(const FormProto* form)); - - // TODO(b/141163294): add missing methods and unit tests. + MOCK_METHOD1(OnClientSettingsChanged, void(const ClientSettings& settings)); }; } // namespace autofill_assistant diff --git a/chromium/components/autofill_assistant/browser/protocol_utils.cc b/chromium/components/autofill_assistant/browser/protocol_utils.cc index da35b0958c5..fee2055417d 100644 --- a/chromium/components/autofill_assistant/browser/protocol_utils.cc +++ b/chromium/components/autofill_assistant/browser/protocol_utils.cc @@ -8,7 +8,6 @@ #include "base/feature_list.h" #include "base/logging.h" -#include "components/autofill_assistant/browser/actions/autofill_action.h" #include "components/autofill_assistant/browser/actions/click_action.h" #include "components/autofill_assistant/browser/actions/collect_user_data_action.h" #include "components/autofill_assistant/browser/actions/configure_bottom_sheet_action.h" @@ -30,6 +29,8 @@ #include "components/autofill_assistant/browser/actions/tell_action.h" #include "components/autofill_assistant/browser/actions/unsupported_action.h" #include "components/autofill_assistant/browser/actions/upload_dom_action.h" +#include "components/autofill_assistant/browser/actions/use_address_action.h" +#include "components/autofill_assistant/browser/actions/use_credit_card_action.h" #include "components/autofill_assistant/browser/actions/wait_for_document_action.h" #include "components/autofill_assistant/browser/actions/wait_for_dom_action.h" #include "components/autofill_assistant/browser/actions/wait_for_navigation_action.h" @@ -51,6 +52,9 @@ void FillClientContext(const ClientContextProto& client_context, if (trigger_context.is_cct()) { proto->set_is_cct(true); } + if (trigger_context.is_onboarding_shown()) { + proto->set_is_onboarding_shown(true); + } if (trigger_context.is_direct_action()) { proto->set_is_direct_action(true); } @@ -209,8 +213,10 @@ bool ProtocolUtils::ParseActions(ActionDelegate* delegate, break; } case ActionProto::ActionInfoCase::kUseAddress: + client_action = std::make_unique<UseAddressAction>(delegate, action); + break; case ActionProto::ActionInfoCase::kUseCard: { - client_action = std::make_unique<AutofillAction>(delegate, action); + client_action = std::make_unique<UseCreditCardAction>(delegate, action); break; } case ActionProto::ActionInfoCase::kWaitForDom: { diff --git a/chromium/components/autofill_assistant/browser/protocol_utils_unittest.cc b/chromium/components/autofill_assistant/browser/protocol_utils_unittest.cc index 741be9339d2..9441f611e16 100644 --- a/chromium/components/autofill_assistant/browser/protocol_utils_unittest.cc +++ b/chromium/components/autofill_assistant/browser/protocol_utils_unittest.cc @@ -98,6 +98,7 @@ TEST(ProtocolUtilsTest, CreateInitialScriptActionsRequest) { AssertClientContext(request.client_context()); EXPECT_THAT(request.client_context().experiment_ids(), Eq("1,2,3")); EXPECT_TRUE(request.client_context().is_cct()); + EXPECT_FALSE(request.client_context().is_onboarding_shown()); EXPECT_FALSE(request.client_context().is_direct_action()); const InitialScriptActionsRequestProto& initial = request.initial_request(); @@ -112,6 +113,41 @@ TEST(ProtocolUtilsTest, CreateInitialScriptActionsRequest) { EXPECT_EQ("script_payload", request.script_payload()); } +TEST(ProtocolUtilsTest, TestCreateInitialScriptActionsRequestFlags) { + std::map<std::string, std::string> parameters; + + ScriptActionRequestProto request; + + // With flags. + TriggerContextImpl trigger_context_flags(parameters, std::string()); + trigger_context_flags.SetCCT(true); + trigger_context_flags.SetOnboardingShown(true); + trigger_context_flags.SetDirectAction(true); + + EXPECT_TRUE( + request.ParseFromString(ProtocolUtils::CreateInitialScriptActionsRequest( + "script_path", GURL("http://example.com/"), trigger_context_flags, + "global_payload", "script_payload", CreateClientContextProto()))); + + AssertClientContext(request.client_context()); + EXPECT_TRUE(request.client_context().is_cct()); + EXPECT_TRUE(request.client_context().is_onboarding_shown()); + EXPECT_TRUE(request.client_context().is_direct_action()); + + // Without flags. + TriggerContextImpl trigger_context_no_flags(parameters, std::string()); + + EXPECT_TRUE( + request.ParseFromString(ProtocolUtils::CreateInitialScriptActionsRequest( + "script_path", GURL("http://example.com/"), trigger_context_no_flags, + "global_payload", "script_payload", CreateClientContextProto()))); + + AssertClientContext(request.client_context()); + EXPECT_FALSE(request.client_context().is_cct()); + EXPECT_FALSE(request.client_context().is_onboarding_shown()); + EXPECT_FALSE(request.client_context().is_direct_action()); +} + TEST(ProtocolUtilsTest, CreateNextScriptActionsRequest) { std::map<std::string, std::string> parameters; parameters["a"] = "b"; @@ -131,6 +167,44 @@ TEST(ProtocolUtilsTest, CreateNextScriptActionsRequest) { EXPECT_EQ(1, request.next_request().processed_actions().size()); } +TEST(ProtocolUtilsTest, TestCreateNextScriptActionsRequestFlags) { + std::map<std::string, std::string> parameters; + + std::vector<ProcessedActionProto> processed_actions; + processed_actions.emplace_back(ProcessedActionProto()); + + ScriptActionRequestProto request; + + // With flags. + TriggerContextImpl trigger_context_flags(parameters, std::string()); + trigger_context_flags.SetCCT(true); + trigger_context_flags.SetOnboardingShown(true); + trigger_context_flags.SetDirectAction(true); + + EXPECT_TRUE( + request.ParseFromString(ProtocolUtils::CreateNextScriptActionsRequest( + trigger_context_flags, "global_payload", "script_payload", + processed_actions, CreateClientContextProto()))); + + AssertClientContext(request.client_context()); + EXPECT_TRUE(request.client_context().is_cct()); + EXPECT_TRUE(request.client_context().is_onboarding_shown()); + EXPECT_TRUE(request.client_context().is_direct_action()); + + // Without flags. + TriggerContextImpl trigger_context_no_flags(parameters, std::string()); + + EXPECT_TRUE( + request.ParseFromString(ProtocolUtils::CreateNextScriptActionsRequest( + trigger_context_no_flags, "global_payload", "script_payload", + processed_actions, CreateClientContextProto()))); + + AssertClientContext(request.client_context()); + EXPECT_FALSE(request.client_context().is_cct()); + EXPECT_FALSE(request.client_context().is_onboarding_shown()); + EXPECT_FALSE(request.client_context().is_direct_action()); +} + TEST(ProtocolUtilsTest, CreateGetScriptsRequest) { std::map<std::string, std::string> parameters; parameters["a"] = "b"; @@ -146,6 +220,7 @@ TEST(ProtocolUtilsTest, CreateGetScriptsRequest) { AssertClientContext(request.client_context()); EXPECT_THAT(request.client_context().experiment_ids(), Eq("1,2,3")); EXPECT_FALSE(request.client_context().is_cct()); + EXPECT_FALSE(request.client_context().is_onboarding_shown()); EXPECT_TRUE(request.client_context().is_direct_action()); EXPECT_EQ("http://example.com/", request.url()); @@ -156,6 +231,38 @@ TEST(ProtocolUtilsTest, CreateGetScriptsRequest) { EXPECT_EQ("d", request.script_parameters(1).value()); } +TEST(ProtocolUtilsTest, TestCreateGetScriptsRequestFlags) { + std::map<std::string, std::string> parameters; + SupportsScriptRequestProto request; + + // With flags. + TriggerContextImpl trigger_context_flags(parameters, std::string()); + trigger_context_flags.SetCCT(true); + trigger_context_flags.SetOnboardingShown(true); + trigger_context_flags.SetDirectAction(true); + + EXPECT_TRUE(request.ParseFromString(ProtocolUtils::CreateGetScriptsRequest( + GURL("http://example.com/"), trigger_context_flags, + CreateClientContextProto()))); + + AssertClientContext(request.client_context()); + EXPECT_TRUE(request.client_context().is_cct()); + EXPECT_TRUE(request.client_context().is_onboarding_shown()); + EXPECT_TRUE(request.client_context().is_direct_action()); + + // Without flags. + TriggerContextImpl trigger_context_no_flags(parameters, std::string()); + + EXPECT_TRUE(request.ParseFromString(ProtocolUtils::CreateGetScriptsRequest( + GURL("http://example.com/"), trigger_context_no_flags, + CreateClientContextProto()))); + + AssertClientContext(request.client_context()); + EXPECT_FALSE(request.client_context().is_cct()); + EXPECT_FALSE(request.client_context().is_onboarding_shown()); + EXPECT_FALSE(request.client_context().is_direct_action()); +} + TEST(ProtocolUtilsTest, AddScriptIgnoreInvalid) { SupportedScriptProto script_proto; std::vector<std::unique_ptr<Script>> scripts; diff --git a/chromium/components/autofill_assistant/browser/retry_timer.cc b/chromium/components/autofill_assistant/browser/retry_timer.cc index 4ded92bbd45..5ee87740abf 100644 --- a/chromium/components/autofill_assistant/browser/retry_timer.cc +++ b/chromium/components/autofill_assistant/browser/retry_timer.cc @@ -6,6 +6,7 @@ #include "base/bind.h" #include "base/bind_helpers.h" +#include "components/autofill_assistant/browser/client_status.h" namespace autofill_assistant { @@ -14,8 +15,9 @@ RetryTimer::~RetryTimer() = default; void RetryTimer::Start( base::TimeDelta max_wait_time, - base::RepeatingCallback<void(base::OnceCallback<void(bool)>)> task, - base::OnceCallback<void(bool)> on_done) { + base::RepeatingCallback<void(base::OnceCallback<void(const ClientStatus&)>)> + task, + base::OnceCallback<void(const ClientStatus&)> on_done) { Reset(); task_ = std::move(task); on_done_ = std::move(on_done); @@ -47,15 +49,15 @@ void RetryTimer::RunTask() { weak_ptr_factory_.GetWeakPtr(), task_id_)); } -void RetryTimer::OnTaskDone(int64_t task_id, bool success) { +void RetryTimer::OnTaskDone(int64_t task_id, const ClientStatus& status) { if (task_id != task_id_) // Ignore callbacks from cancelled tasks return; remaining_attempts_--; - if (success || remaining_attempts_ <= 0) { + if (status.ok() || remaining_attempts_ <= 0) { CHECK_GE(remaining_attempts_, 0); task_.Reset(); // release any resources held by the callback - std::move(on_done_).Run(success); + std::move(on_done_).Run(status); // Don't do anything after calling on_done_, as it could have deleted this. return; } diff --git a/chromium/components/autofill_assistant/browser/retry_timer.h b/chromium/components/autofill_assistant/browser/retry_timer.h index af5876d6a47..40b964040b5 100644 --- a/chromium/components/autofill_assistant/browser/retry_timer.h +++ b/chromium/components/autofill_assistant/browser/retry_timer.h @@ -12,6 +12,7 @@ #include "base/memory/weak_ptr.h" #include "base/time/time.h" #include "base/timer/timer.h" +#include "components/autofill_assistant/browser/client_status.h" namespace autofill_assistant { @@ -39,8 +40,9 @@ class RetryTimer { // If |max_wait_time| is 0 or lower than the retry period, the task is // executed exactly once. void Start(base::TimeDelta max_wait_time, - base::RepeatingCallback<void(base::OnceCallback<void(bool)>)> task, - base::OnceCallback<void(bool)> on_done); + base::RepeatingCallback< + void(base::OnceCallback<void(const ClientStatus&)>)> task, + base::OnceCallback<void(const ClientStatus&)> on_done); // Cancels any pending tasks or timer. Any |on_done| callbacks passed to Start // is released without being called. @@ -54,13 +56,14 @@ class RetryTimer { private: void Reset(); void RunTask(); - void OnTaskDone(int64_t task_id_, bool success); + void OnTaskDone(int64_t task_id_, const ClientStatus& status); const base::TimeDelta period_; int64_t remaining_attempts_ = 1; int64_t task_id_ = 0; - base::RepeatingCallback<void(base::OnceCallback<void(bool)>)> task_; - base::OnceCallback<void(bool)> on_done_; + base::RepeatingCallback<void(base::OnceCallback<void(const ClientStatus&)>)> + task_; + base::OnceCallback<void(const ClientStatus&)> on_done_; std::unique_ptr<base::OneShotTimer> timer_; base::WeakPtrFactory<RetryTimer> weak_ptr_factory_{this}; diff --git a/chromium/components/autofill_assistant/browser/retry_timer_unittest.cc b/chromium/components/autofill_assistant/browser/retry_timer_unittest.cc index 1bad81484dc..727f3765c67 100644 --- a/chromium/components/autofill_assistant/browser/retry_timer_unittest.cc +++ b/chromium/components/autofill_assistant/browser/retry_timer_unittest.cc @@ -20,6 +20,10 @@ namespace autofill_assistant { namespace { +MATCHER_P(EqualsStatus, status, "") { + return arg.proto_status() == status.proto_status(); +} + class RetryTimerTest : public testing::Test { protected: RetryTimerTest() @@ -29,37 +33,38 @@ class RetryTimerTest : public testing::Test { task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1)); } - base::RepeatingCallback<void(base::OnceCallback<void(bool)>)> + base::RepeatingCallback<void(base::OnceCallback<void(const ClientStatus&)>)> AlwaysFailsCallback() { return base::BindRepeating(&RetryTimerTest::AlwaysFails, base::Unretained(this)); } - void AlwaysFails(base::OnceCallback<void(bool)> callback) { + void AlwaysFails(base::OnceCallback<void(const ClientStatus&)> callback) { try_count_++; - std::move(callback).Run(false); + std::move(callback).Run(ClientStatus()); } - base::RepeatingCallback<void(base::OnceCallback<void(bool)>)> + base::RepeatingCallback<void(base::OnceCallback<void(const ClientStatus&)>)> SucceedsOnceCallback(int succeds_at) { return base::BindRepeating(&RetryTimerTest::SucceedsOnce, base::Unretained(this), succeds_at); } - void SucceedsOnce(int succeeds_at, base::OnceCallback<void(bool)> callback) { + void SucceedsOnce(int succeeds_at, + base::OnceCallback<void(const ClientStatus&)> callback) { EXPECT_GE(succeeds_at, try_count_); bool success = succeeds_at == try_count_; try_count_++; - std::move(callback).Run(success); + std::move(callback).Run(success ? OkClientStatus() : ClientStatus()); } - base::RepeatingCallback<void(base::OnceCallback<void(bool)>)> + base::RepeatingCallback<void(base::OnceCallback<void(const ClientStatus&)>)> CaptureCallback() { return base::BindRepeating(&RetryTimerTest::Capture, base::Unretained(this)); } - void Capture(base::OnceCallback<void(bool)> callback) { + void Capture(base::OnceCallback<void(const ClientStatus&)> callback) { try_count_++; captured_callback_ = std::move(callback); } @@ -69,13 +74,14 @@ class RetryTimerTest : public testing::Test { base::test::TaskEnvironment task_environment_; int try_count_ = 0; - base::OnceCallback<void(bool)> captured_callback_; - base::MockCallback<base::OnceCallback<void(bool)>> done_callback_; + base::OnceCallback<void(const ClientStatus&)> captured_callback_; + base::MockCallback<base::OnceCallback<void(const ClientStatus&)>> + done_callback_; }; TEST_F(RetryTimerTest, TryOnceAndSucceed) { RetryTimer retry_timer(base::TimeDelta::FromSeconds(1)); - EXPECT_CALL(done_callback_, Run(true)); + EXPECT_CALL(done_callback_, Run(EqualsStatus(OkClientStatus()))); retry_timer.Start(base::TimeDelta::FromSeconds(10), SucceedsOnceCallback(0), done_callback_.Get()); EXPECT_EQ(1, try_count_); @@ -83,7 +89,7 @@ TEST_F(RetryTimerTest, TryOnceAndSucceed) { TEST_F(RetryTimerTest, TryOnceAndFail) { RetryTimer retry_timer(base::TimeDelta::FromSeconds(1)); - EXPECT_CALL(done_callback_, Run(false)); + EXPECT_CALL(done_callback_, Run(EqualsStatus(ClientStatus()))); retry_timer.Start(base::TimeDelta::FromSeconds(0), AlwaysFailsCallback(), done_callback_.Get()); EXPECT_EQ(1, try_count_); @@ -96,7 +102,7 @@ TEST_F(RetryTimerTest, TryMultipleTimesAndSucceed) { EXPECT_EQ(1, try_count_); FastForwardOneSecond(); EXPECT_EQ(2, try_count_); - EXPECT_CALL(done_callback_, Run(true)); + EXPECT_CALL(done_callback_, Run(EqualsStatus(OkClientStatus()))); FastForwardOneSecond(); EXPECT_EQ(3, try_count_); } @@ -108,7 +114,7 @@ TEST_F(RetryTimerTest, TryMultipleTimesAndFail) { EXPECT_EQ(1, try_count_); FastForwardOneSecond(); EXPECT_EQ(2, try_count_); - EXPECT_CALL(done_callback_, Run(false)); + EXPECT_CALL(done_callback_, Run(EqualsStatus(ClientStatus()))); FastForwardOneSecond(); EXPECT_EQ(3, try_count_); } @@ -130,7 +136,7 @@ TEST_F(RetryTimerTest, CancelWithPendingCallbacks) { done_callback_.Get()); ASSERT_TRUE(captured_callback_); retry_timer.Cancel(); - std::move(captured_callback_).Run(true); // Should do nothing + std::move(captured_callback_).Run(OkClientStatus()); // Should do nothing } TEST_F(RetryTimerTest, GiveUpWhenLeavingScope) { @@ -152,7 +158,7 @@ TEST_F(RetryTimerTest, GiveUpWhenLeavingScopeWithPendingCallback) { done_callback_.Get()); ASSERT_TRUE(captured_callback_); } - std::move(captured_callback_).Run(true); // Should do nothing + std::move(captured_callback_).Run(OkClientStatus()); // Should do nothing } TEST_F(RetryTimerTest, RestartOverridesFirstCall) { @@ -161,11 +167,12 @@ TEST_F(RetryTimerTest, RestartOverridesFirstCall) { RetryTimer retry_timer(base::TimeDelta::FromSeconds(1)); retry_timer.Start(base::TimeDelta::FromSeconds(1), AlwaysFailsCallback(), done_callback_.Get()); - base::MockCallback<base::OnceCallback<void(bool)>> done_callback2; + base::MockCallback<base::OnceCallback<void(const ClientStatus&)>> + done_callback2; retry_timer.Start(base::TimeDelta::FromSeconds(1), AlwaysFailsCallback(), done_callback2.Get()); EXPECT_EQ(2, try_count_); - EXPECT_CALL(done_callback2, Run(false)); + EXPECT_CALL(done_callback2, Run(EqualsStatus(ClientStatus()))); FastForwardOneSecond(); EXPECT_EQ(3, try_count_); } @@ -178,13 +185,14 @@ TEST_F(RetryTimerTest, RestartOverridesFirstCallWithPendingTask) { done_callback_.Get()); ASSERT_TRUE(captured_callback_); - base::MockCallback<base::OnceCallback<void(bool)>> done_callback2; + base::MockCallback<base::OnceCallback<void(const ClientStatus&)>> + done_callback2; retry_timer.Start(base::TimeDelta::FromSeconds(1), AlwaysFailsCallback(), done_callback2.Get()); - std::move(captured_callback_).Run(true); // Should do nothing + std::move(captured_callback_).Run(OkClientStatus()); // Should do nothing - EXPECT_CALL(done_callback2, Run(false)); + EXPECT_CALL(done_callback2, Run(EqualsStatus(ClientStatus()))); FastForwardOneSecond(); EXPECT_EQ(3, try_count_); } @@ -197,7 +205,7 @@ TEST_F(RetryTimerTest, Running) { done_callback_.Get()); EXPECT_TRUE(retry_timer.running()); - EXPECT_CALL(done_callback_, Run(true)); + EXPECT_CALL(done_callback_, Run(EqualsStatus(OkClientStatus()))); FastForwardOneSecond(); EXPECT_FALSE(retry_timer.running()); } diff --git a/chromium/components/autofill_assistant/browser/script_executor.cc b/chromium/components/autofill_assistant/browser/script_executor.cc index acef62b3243..8b7ae9c8065 100644 --- a/chromium/components/autofill_assistant/browser/script_executor.cc +++ b/chromium/components/autofill_assistant/browser/script_executor.cc @@ -154,7 +154,7 @@ void ScriptExecutor::RunElementChecks(BatchElementChecker* checker) { void ScriptExecutor::ShortWaitForElement( const Selector& selector, - base::OnceCallback<void(bool)> callback) { + base::OnceCallback<void(const ClientStatus&)> callback) { current_action_data_.wait_for_dom = std::make_unique<WaitForDomOperation>( this, delegate_, delegate_->GetSettings().short_wait_for_element_deadline, /* allow_interrupt= */ false, @@ -169,9 +169,9 @@ void ScriptExecutor::WaitForDom( base::TimeDelta max_wait_time, bool allow_interrupt, base::RepeatingCallback<void(BatchElementChecker*, - base::OnceCallback<void(bool)>)> + base::OnceCallback<void(const ClientStatus&)>)> check_elements, - base::OnceCallback<void(ProcessedActionStatusProto)> callback) { + base::OnceCallback<void(const ClientStatus&)> callback) { current_action_data_.wait_for_dom = std::make_unique<WaitForDomOperation>( this, delegate_, max_wait_time, allow_interrupt, check_elements, base::BindOnce(&ScriptExecutor::OnWaitForElementVisibleWithInterrupts, @@ -373,7 +373,8 @@ void ScriptExecutor::SetProgressVisible(bool visible) { void ScriptExecutor::GetFieldValue( const Selector& selector, - base::OnceCallback<void(bool, const std::string&)> callback) { + base::OnceCallback<void(const ClientStatus&, const std::string&)> + callback) { delegate_->GetWebController()->GetFieldValue(selector, std::move(callback)); } @@ -710,38 +711,37 @@ void ScriptExecutor::OnProcessedAction( void ScriptExecutor::CheckElementMatches( const Selector& selector, BatchElementChecker* checker, - base::OnceCallback<void(bool)> callback) { + base::OnceCallback<void(const ClientStatus&)> callback) { checker->AddElementCheck(selector, std::move(callback)); } void ScriptExecutor::OnShortWaitForElement( - base::OnceCallback<void(bool)> callback, - bool element_found, + base::OnceCallback<void(const ClientStatus&)> callback, + const ClientStatus& element_status, const Result* interrupt_result) { // Interrupts cannot run, so should never be reported. DCHECK(!interrupt_result); - std::move(callback).Run(element_found); + std::move(callback).Run(element_status); } void ScriptExecutor::OnWaitForElementVisibleWithInterrupts( - base::OnceCallback<void(ProcessedActionStatusProto)> callback, - bool element_found, + base::OnceCallback<void(const ClientStatus&)> callback, + const ClientStatus& element_status, const Result* interrupt_result) { if (interrupt_result) { if (!interrupt_result->success) { - std::move(callback).Run(INTERRUPT_FAILED); + std::move(callback).Run(ClientStatus(INTERRUPT_FAILED)); return; } if (interrupt_result->at_end != CONTINUE) { at_end_ = interrupt_result->at_end; should_stop_script_ = true; - std::move(callback).Run(MANUAL_FALLBACK); + std::move(callback).Run(ClientStatus(MANUAL_FALLBACK)); return; } } - std::move(callback).Run(element_found ? ACTION_APPLIED - : ELEMENT_RESOLUTION_FAILED); + std::move(callback).Run(element_status); } ScriptExecutor::WaitForDomOperation::WaitForDomOperation( @@ -750,7 +750,7 @@ ScriptExecutor::WaitForDomOperation::WaitForDomOperation( base::TimeDelta max_wait_time, bool allow_interrupt, base::RepeatingCallback<void(BatchElementChecker*, - base::OnceCallback<void(bool)>)> + base::OnceCallback<void(const ClientStatus&)>)> check_elements, WaitForDomOperation::Callback callback) : main_script_(main_script), @@ -822,9 +822,9 @@ void ScriptExecutor::WaitForDomOperation::OnScriptListChanged( } void ScriptExecutor::WaitForDomOperation::RunChecks( - base::OnceCallback<void(bool)> report_attempt_result) { + base::OnceCallback<void(const ClientStatus&)> report_attempt_result) { // Reset state possibly left over from previous runs. - element_check_result_ = false; + element_check_result_ = ClientStatus(); runnable_interrupts_.clear(); batch_element_checker_ = std::make_unique<BatchElementChecker>(); check_elements_.Run(batch_element_checker_.get(), @@ -860,15 +860,16 @@ void ScriptExecutor::WaitForDomOperation::OnPreconditionCheckDone( runnable_interrupts_.insert(interrupt); } -void ScriptExecutor::WaitForDomOperation::OnElementCheckDone(bool result) { - element_check_result_ = result; +void ScriptExecutor::WaitForDomOperation::OnElementCheckDone( + const ClientStatus& element_status) { + element_check_result_ = element_status; // Wait for all checks to run before reporting that the element was found to // the caller, so interrupts have a chance to run. } void ScriptExecutor::WaitForDomOperation::OnAllChecksDone( - base::OnceCallback<void(bool)> report_attempt_result) { + base::OnceCallback<void(const ClientStatus&)> report_attempt_result) { if (!runnable_interrupts_.empty()) { // We must go through runnable_interrupts_ to make sure priority order is // respected in case more than one interrupt is ready to run. @@ -903,7 +904,7 @@ void ScriptExecutor::WaitForDomOperation::OnInterruptDone( const ScriptExecutor::Result& result) { interrupt_executor_.reset(); if (!result.success || result.at_end != ScriptExecutor::CONTINUE) { - RunCallbackWithResult(false, &result); + RunCallbackWithResult(ClientStatus(INTERRUPT_FAILED), &result); return; } RestoreStatusMessage(); @@ -914,12 +915,13 @@ void ScriptExecutor::WaitForDomOperation::OnInterruptDone( Start(); } -void ScriptExecutor::WaitForDomOperation::RunCallback(bool found) { - RunCallbackWithResult(found, nullptr); +void ScriptExecutor::WaitForDomOperation::RunCallback( + const ClientStatus& element_status) { + RunCallbackWithResult(element_status, nullptr); } void ScriptExecutor::WaitForDomOperation::RunCallbackWithResult( - bool check_result, + const ClientStatus& element_status, const ScriptExecutor::Result* result) { // stop element checking if one is still in progress batch_element_checker_.reset(); @@ -928,7 +930,7 @@ void ScriptExecutor::WaitForDomOperation::RunCallbackWithResult( return; RestorePreInterruptScroll(); - std::move(callback_).Run(check_result, result); + std::move(callback_).Run(element_status, result); } void ScriptExecutor::WaitForDomOperation::SavePreInterruptState() { diff --git a/chromium/components/autofill_assistant/browser/script_executor.h b/chromium/components/autofill_assistant/browser/script_executor.h index aa79feb0621..4313941bb1f 100644 --- a/chromium/components/autofill_assistant/browser/script_executor.h +++ b/chromium/components/autofill_assistant/browser/script_executor.h @@ -101,15 +101,16 @@ class ScriptExecutor : public ActionDelegate, // Override ActionDelegate: void RunElementChecks(BatchElementChecker* checker) override; - void ShortWaitForElement(const Selector& selector, - base::OnceCallback<void(bool)> callback) override; + void ShortWaitForElement( + const Selector& selector, + base::OnceCallback<void(const ClientStatus&)> callback) override; void WaitForDom( base::TimeDelta max_wait_time, bool allow_interrupt, - base::RepeatingCallback<void(BatchElementChecker*, - base::OnceCallback<void(bool)>)> - check_elements, - base::OnceCallback<void(ProcessedActionStatusProto)> callback) override; + base::RepeatingCallback< + void(BatchElementChecker*, + base::OnceCallback<void(const ClientStatus&)>)> check_elements, + base::OnceCallback<void(const ClientStatus&)> callback) override; void SetStatusMessage(const std::string& message) override; std::string GetStatusMessage() override; void SetBubbleMessage(const std::string& message) override; @@ -148,7 +149,8 @@ class ScriptExecutor : public ActionDelegate, const ElementAreaProto& touchable_element_area) override; void GetFieldValue( const Selector& selector, - base::OnceCallback<void(bool, const std::string&)> callback) override; + base::OnceCallback<void(const ClientStatus&, const std::string&)> + callback) override; void SetFieldValue( const Selector& selector, const std::string& value, @@ -219,8 +221,8 @@ class ScriptExecutor : public ActionDelegate, // // If the given result is non-null, it should be forwarded as the result of // the main script. - using Callback = - base::OnceCallback<void(bool, const ScriptExecutor::Result*)>; + using Callback = base::OnceCallback<void(const ClientStatus&, + const ScriptExecutor::Result*)>; // |main_script_| must not be null and outlive this instance. WaitForDomOperation( @@ -228,9 +230,9 @@ class ScriptExecutor : public ActionDelegate, ScriptExecutorDelegate* delegate, base::TimeDelta max_wait_time, bool allow_interrupt, - base::RepeatingCallback<void(BatchElementChecker*, - base::OnceCallback<void(bool)>)> - check_elements, + base::RepeatingCallback< + void(BatchElementChecker*, + base::OnceCallback<void(const ClientStatus&)>)> check_elements, WaitForDomOperation::Callback callback); ~WaitForDomOperation() override; @@ -251,15 +253,17 @@ class ScriptExecutor : public ActionDelegate, void OnScriptListChanged( std::vector<std::unique_ptr<Script>> scripts) override; - void RunChecks(base::OnceCallback<void(bool)> report_attempt_result); + void RunChecks( + base::OnceCallback<void(const ClientStatus&)> report_attempt_result); void OnPreconditionCheckDone(const Script* interrupt, bool precondition_match); - void OnElementCheckDone(bool found); - void OnAllChecksDone(base::OnceCallback<void(bool)> report_attempt_result); + void OnElementCheckDone(const ClientStatus&); + void OnAllChecksDone( + base::OnceCallback<void(const ClientStatus&)> report_attempt_result); void RunInterrupt(const Script* interrupt); void OnInterruptDone(const ScriptExecutor::Result& result); - void RunCallback(bool found); - void RunCallbackWithResult(bool found, + void RunCallback(const ClientStatus& element_status); + void RunCallbackWithResult(const ClientStatus& element_status, const ScriptExecutor::Result* result); // Saves the current state and sets save_pre_interrupt_state_. @@ -277,13 +281,13 @@ class ScriptExecutor : public ActionDelegate, const base::TimeDelta max_wait_time_; const bool allow_interrupt_; base::RepeatingCallback<void(BatchElementChecker*, - base::OnceCallback<void(bool)>)> + base::OnceCallback<void(const ClientStatus&)>)> check_elements_; WaitForDomOperation::Callback callback_; std::unique_ptr<BatchElementChecker> batch_element_checker_; std::set<const Script*> runnable_interrupts_; - bool element_check_result_ = false; + ClientStatus element_check_result_; // An empty vector of interrupts that can be passed to interrupt_executor_ // and outlives it. Interrupts must not run interrupts. @@ -321,15 +325,17 @@ class ScriptExecutor : public ActionDelegate, void GetNextActions(); void OnProcessedAction(base::TimeTicks start_time, std::unique_ptr<ProcessedActionProto> action); - void CheckElementMatches(const Selector& selector, - BatchElementChecker* checker, - base::OnceCallback<void(bool)> callback); - void OnShortWaitForElement(base::OnceCallback<void(bool)> callback, - bool element_found, - const Result* interrupt_result); + void CheckElementMatches( + const Selector& selector, + BatchElementChecker* checker, + base::OnceCallback<void(const ClientStatus&)> callback); + void OnShortWaitForElement( + base::OnceCallback<void(const ClientStatus&)> callback, + const ClientStatus& element_status, + const Result* interrupt_result); void OnWaitForElementVisibleWithInterrupts( - base::OnceCallback<void(ProcessedActionStatusProto)> callback, - bool element_found, + base::OnceCallback<void(const ClientStatus&)> callback, + const ClientStatus& element_status, const Result* interrupt_result); void OnGetUserData( base::OnceCallback<void(std::unique_ptr<UserData>)> callback, diff --git a/chromium/components/autofill_assistant/browser/script_executor_unittest.cc b/chromium/components/autofill_assistant/browser/script_executor_unittest.cc index f6d73e7576b..8fdd457c6da 100644 --- a/chromium/components/autofill_assistant/browser/script_executor_unittest.cc +++ b/chromium/components/autofill_assistant/browser/script_executor_unittest.cc @@ -69,7 +69,7 @@ class ScriptExecutorTest : public testing::Test, .WillByDefault(RunOnceCallback<1>(ClientStatus(OTHER_ACTION_STATUS))); ON_CALL(mock_web_controller_, OnElementCheck(_, _)) - .WillByDefault(RunOnceCallback<1>(true)); + .WillByDefault(RunOnceCallback<1>(OkClientStatus())); ON_CALL(mock_web_controller_, OnFocusElement(_, _, _)) .WillByDefault(RunOnceCallback<2>(OkClientStatus())); } @@ -524,12 +524,12 @@ TEST_F(ScriptExecutorTest, WaitForDomWaitUntil) { // element is found, and the action succeeds. EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"element"})), _)) - .WillOnce(RunOnceCallback<1>(false)); + .WillOnce(RunOnceCallback<1>(ClientStatus())); executor_->Run(executor_callback_.Get()); EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"element"})), _)) - .WillRepeatedly(RunOnceCallback<1>(true)); + .WillRepeatedly(RunOnceCallback<1>(OkClientStatus())); EXPECT_CALL(executor_callback_, Run(_)); task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1)); @@ -553,12 +553,12 @@ TEST_F(ScriptExecutorTest, WaitForDomWaitWhile) { // disappears, and the action succeeds. EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"element"})), _)) - .WillOnce(RunOnceCallback<1>(true)); + .WillOnce(RunOnceCallback<1>(OkClientStatus())); executor_->Run(executor_callback_.Get()); EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"element"})), _)) - .WillRepeatedly(RunOnceCallback<1>(false)); + .WillRepeatedly(RunOnceCallback<1>(ClientStatus())); EXPECT_CALL(executor_callback_, Run(_)); task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1)); @@ -724,10 +724,10 @@ TEST_F(ScriptExecutorTest, DoNotRunInterruptIfPreconditionsDontMatch) { EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"element"})), _)) - .WillRepeatedly(RunOnceCallback<1>(true)); + .WillRepeatedly(RunOnceCallback<1>(OkClientStatus())); EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"interrupt_trigger"})), _)) - .WillRepeatedly(RunOnceCallback<1>(false)); + .WillRepeatedly(RunOnceCallback<1>(ClientStatus())); EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _)) .WillRepeatedly(RunOnceCallback<4>(true, "")); @@ -1002,7 +1002,7 @@ TEST_F(ScriptExecutorTest, PauseWaitForDomWhileNavigating) { // First check does not find the element, wait for dom waits 1s. EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"element"})), _)) - .WillOnce(RunOnceCallback<1>(false)); + .WillOnce(RunOnceCallback<1>(ClientStatus())); executor_->Run(executor_callback_.Get()); // Navigation starts while WaitForDom is waiting. The action doesn't fail, @@ -1016,7 +1016,7 @@ TEST_F(ScriptExecutorTest, PauseWaitForDomWhileNavigating) { // The end of navigation un-pauses WaitForDom. EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"element"})), _)) - .WillRepeatedly(RunOnceCallback<1>(true)); + .WillRepeatedly(RunOnceCallback<1>(OkClientStatus())); EXPECT_CALL(executor_callback_, Run(_)); delegate_.UpdateNavigationState(/* navigating= */ false, /* error= */ false); @@ -1156,14 +1156,14 @@ TEST_F(ScriptExecutorTest, ReportNavigationEnd) { // fails. EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"element"})), _)) - .WillOnce(RunOnceCallback<1>(false)); + .WillOnce(RunOnceCallback<1>(ClientStatus())); 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. EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"element"})), _)) - .WillOnce(RunOnceCallback<1>(true)); + .WillOnce(RunOnceCallback<1>(OkClientStatus())); task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1)); ASSERT_THAT(processed_actions_capture, SizeIs(1)); @@ -1187,7 +1187,7 @@ TEST_F(ScriptExecutorTest, ReportUnexpectedNavigationStart) { // As the element doesn't exist, WaitForDom returns and waits for 1s. EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"element"})), _)) - .WillOnce(RunOnceCallback<1>(false)); + .WillOnce(RunOnceCallback<1>(ClientStatus())); EXPECT_CALL(executor_callback_, Run(_)); executor_->Run(executor_callback_.Get()); @@ -1196,7 +1196,7 @@ TEST_F(ScriptExecutorTest, ReportUnexpectedNavigationStart) { // Navigation end forces a re-check, which succeeds EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"element"})), _)) - .WillRepeatedly(RunOnceCallback<1>(true)); + .WillRepeatedly(RunOnceCallback<1>(OkClientStatus())); delegate_.UpdateNavigationState(/* navigating= */ false, /* error= */ false); ASSERT_THAT(processed_actions_capture, SizeIs(1)); @@ -1221,7 +1221,7 @@ TEST_F(ScriptExecutorTest, ReportExpectedNavigationStart) { // As the element doesn't exist, WaitForDom returns and waits for 1s. EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"element"})), _)) - .WillOnce(RunOnceCallback<1>(false)); + .WillOnce(RunOnceCallback<1>(ClientStatus())); EXPECT_CALL(executor_callback_, Run(_)); executor_->Run(executor_callback_.Get()); @@ -1230,7 +1230,7 @@ TEST_F(ScriptExecutorTest, ReportExpectedNavigationStart) { // Navigation end forces a re-check, which succeeds EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"element"})), _)) - .WillRepeatedly(RunOnceCallback<1>(true)); + .WillRepeatedly(RunOnceCallback<1>(OkClientStatus())); delegate_.UpdateNavigationState(/* navigating= */ false, /* error= */ false); ASSERT_THAT(processed_actions_capture, SizeIs(2)); diff --git a/chromium/components/autofill_assistant/browser/script_precondition_unittest.cc b/chromium/components/autofill_assistant/browser/script_precondition_unittest.cc index d4057a1bff8..68e1a77af81 100644 --- a/chromium/components/autofill_assistant/browser/script_precondition_unittest.cc +++ b/chromium/components/autofill_assistant/browser/script_precondition_unittest.cc @@ -58,10 +58,10 @@ class ScriptPreconditionTest : public testing::Test { public: void SetUp() override { ON_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"exists"})), _)) - .WillByDefault(RunOnceCallback<1>(true)); + .WillByDefault(RunOnceCallback<1>(OkClientStatus())); ON_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"does_not_exist"})), _)) - .WillByDefault(RunOnceCallback<1>(false)); + .WillByDefault(RunOnceCallback<1>(ClientStatus())); SetUrl("http://www.example.com/path"); @@ -177,7 +177,7 @@ TEST_F(ScriptPreconditionTest, BadPathPattern) { TEST_F(ScriptPreconditionTest, IgnoreEmptyElementsExist) { EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"exists"})), _)) - .WillOnce(RunOnceCallback<1>(true)); + .WillOnce(RunOnceCallback<1>(OkClientStatus())); ScriptPreconditionProto proto; proto.add_elements_exist()->add_selectors("exists"); diff --git a/chromium/components/autofill_assistant/browser/script_tracker_unittest.cc b/chromium/components/autofill_assistant/browser/script_tracker_unittest.cc index 131774ab00f..29d84a93fb2 100644 --- a/chromium/components/autofill_assistant/browser/script_tracker_unittest.cc +++ b/chromium/components/autofill_assistant/browser/script_tracker_unittest.cc @@ -36,10 +36,10 @@ class ScriptTrackerTest : public testing::Test, public ScriptTracker::Listener { delegate_.SetCurrentURL(GURL("http://www.example.com/")); ON_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"exists"})), _)) - .WillByDefault(RunOnceCallback<1>(true)); + .WillByDefault(RunOnceCallback<1>(OkClientStatus())); ON_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"does_not_exist"})), _)) - .WillByDefault(RunOnceCallback<1>(false)); + .WillByDefault(RunOnceCallback<1>(ClientStatus())); // Scripts run, but have no actions. ON_CALL(mock_service_, OnGetActions(_, _, _, _, _, _)) @@ -153,7 +153,6 @@ TEST_F(ScriptTrackerTest, SomeRunnableScripts) { } TEST_F(ScriptTrackerTest, DoNotCheckInterruptWithNoName) { - // The interrupt's preconditions would all be met, but it won't be reported // since it doesn't have a name. auto* no_name = AddScript("", "path1", "exists"); @@ -264,7 +263,7 @@ TEST_F(ScriptTrackerTest, CheckScriptsAgainAfterScriptEnd) { TEST_F(ScriptTrackerTest, CheckScriptsAfterDOMChange) { EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"maybe_exists"})), _)) - .WillOnce(RunOnceCallback<1>(false)); + .WillOnce(RunOnceCallback<1>(ClientStatus())); AddScript("script name", "script path", "maybe_exists"); SetAndCheckScripts(); @@ -272,10 +271,10 @@ TEST_F(ScriptTrackerTest, CheckScriptsAfterDOMChange) { // No scripts are runnable. EXPECT_THAT(runnable_scripts(), IsEmpty()); - // DOM has changed; OnElementExists now returns true. + // DOM has changed; OnElementExists now returns truthy. EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"maybe_exists"})), _)) - .WillOnce(RunOnceCallback<1>(true)); + .WillOnce(RunOnceCallback<1>(OkClientStatus())); tracker_.CheckScripts(); // The script can now run diff --git a/chromium/components/autofill_assistant/browser/service.proto b/chromium/components/autofill_assistant/browser/service.proto index e4be415d685..4e0ea9275fa 100644 --- a/chromium/components/autofill_assistant/browser/service.proto +++ b/chromium/components/autofill_assistant/browser/service.proto @@ -42,6 +42,9 @@ message ClientContextProto { // Assistant. optional bool is_cct = 8; + // True if the onboarding screen was shown to the user. + optional bool is_onboarding_shown = 10; + // True if the script was triggered by a direct action. optional bool is_direct_action = 9; } @@ -73,7 +76,39 @@ message SupportsScriptResponseProto { optional ClientSettingsProto client_settings = 3; } +// Represents a dimension, e.g., width or height. +message ClientDimensionProto { + oneof size { + int32 dpi = 1; + // Factor to multiply with the client's total width. + float width_factor = 2; + // Factor to multiply with the client's total height. + float height_factor = 3; + } +} + +// Overlay image to be drawn on top of full overlays. +message OverlayImageProto { + // The image to display. If set, |image_size| is mandatory. + optional string image_url = 1; + // The size of the image to display. + optional ClientDimensionProto image_size = 2; + // The margin between the top of the page (anchor) and the image. + optional ClientDimensionProto image_top_margin = 3; + // The margin between the bottom of the image and the baseline of the text. + optional ClientDimensionProto image_bottom_margin = 4; + // The text to display beneath the image. If set, |text_color| and |text_size| + // are mandatory. + optional string text = 5; + // Supported formats: #RRGGBB or #AARRGGBB. + optional string text_color = 6; + // The size of the text to display. + optional ClientDimensionProto text_size = 7; +} + message ClientSettingsProto { + reserved 10, 11; + // Time between two periodic script precondition checks. optional int32 periodic_script_check_interval_ms = 1; @@ -108,14 +143,6 @@ message ClientSettingsProto { // ready. optional int32 document_ready_check_count = 9; - // Whether graceful shutdown should be enabled. If false, the UI stays - // up until it's dismissed. - optional bool enable_graceful_shutdown = 10; - - // How long to wait before shutting down during graceful shutdown. If 0 - // shutdown happens immediately. - optional int32 graceful_shutdown_delay_ms = 11; - // How much time to give users to tap undo when they tap a cancel button. optional int32 cancel_delay_ms = 12; @@ -130,6 +157,9 @@ message ClientSettingsProto { // How much time to give users to tap undo when after |tap_count| unexpected // taps where optional int32 tap_shutdown_delay_ms = 15; + + // Optional image drawn on top of overlays. + optional OverlayImageProto overlay_image = 16; } message ScriptTimeoutError { @@ -440,6 +470,8 @@ message ActionProto { // script finishes with this action. It has no effect if there is any other // action sent to the client after this one. Default is false. optional bool clean_contextual_ui = 33; + + reserved 47; } // Result of |CollectUserDataProto| to be sent to the server. @@ -458,6 +490,10 @@ message CollectUserDataResultProto { optional int32 terms_link = 5; // The payload of the chosen login option. optional bytes login_payload = 6; + // The start of the date/time range, if requested. + optional DateTimeProto date_time_start = 7; + // The end of the date/time range, if requested. + optional DateTimeProto date_time_end = 8; } message ProcessedActionProto { @@ -513,8 +549,6 @@ message ProcessedActionStatusDetailsProto { optional ProcessedActionStatusProto original_status = 2; // More information included for autofill related errors. - // - // For now this is only filled for PRECONDITION_FAILED errors. optional AutofillErrorInfoProto autofill_error_info = 3; } @@ -583,8 +617,15 @@ message AutofillErrorInfoProto { // Name of the address key requested in the list of keys in // |client_memory_address_key_names|. optional string address_key_requested = 2; + + // Whether the client memory at |address_key_requested| pointed to null. + optional bool address_pointee_was_null = 3; + + // Error status of the Chrome autofill attempt. + optional ProcessedActionStatusProto autofill_error_status = 4; } +// Next: 22 enum ProcessedActionStatusProto { UNKNOWN_ACTION_STATUS = 0; @@ -684,6 +725,9 @@ enum ProcessedActionStatusProto { // The requested autofill info (e.g., Chrome password manager login) was not // available. It might have been recently deleted. AUTOFILL_INFO_NOT_AVAILABLE = 21; + + // An unexpected error occurred during element resolution. + FRAME_HOST_NOT_FOUND = 22; } // The pseudo type values come from @@ -901,6 +945,9 @@ message UseCreditCardProto { CREDIT_CARD_EXP_MONTH = 2; CREDIT_CARD_EXP_2_DIGIT_YEAR = 3; CREDIT_CARD_EXP_4_DIGIT_YEAR = 4; + CREDIT_CARD_CARD_HOLDER_NAME = 5; + CREDIT_CARD_NUMBER = 6; + CREDIT_CARD_EXP_MM_YY = 7; } optional CardField card_field = 1; @@ -1159,6 +1206,14 @@ message ContactDetailsProto { optional bool request_payer_phone = 4; } +// A generic read-only popup message. +message InfoPopupProto { + // The title of the popup window. + optional string title = 1; + // The text of the popup window. + optional string text = 2; +} + message LoginDetailsProto { // A custom login option which will be handled by the backend, e.g., // 'Guest checkout' or 'Log in with Google'. @@ -1171,6 +1226,13 @@ message LoginDetailsProto { message LoginOptionPasswordManagerProto {} message LoginOptionProto { + // If set, an info icon will be shown that displays a popup when tapped. + optional InfoPopupProto info_popup = 6; + + // The optional sublabel to display beneath the label. + optional string sublabel = 7; + optional string sublabel_accessibility_hint = 8; + // If the option was chosen, this payload will be returned to the server. optional bytes payload = 1; @@ -1192,8 +1254,63 @@ message LoginDetailsProto { repeated LoginOptionProto login_options = 2; } +message DateTimeRangeProto { + // The start value of the date/time range. + optional DateTimeProto start = 1; + // The end value of the date/time range. + optional DateTimeProto end = 2; + // The minimum allowed value of the date/time range. + optional DateTimeProto min = 3; + // The maximum allowed value of the date/time range. + optional DateTimeProto max = 4; + // The label of the start date/time value (e.g., 'Pick-up'). + optional string start_label = 5; + // The label of the end date/time value (e.g., 'Return'). + optional string end_label = 6; +} + +// A section showing a simple text message. +message StaticTextSectionProto { + // The text to display. Can contain markup tags like <b>. + optional string text = 1; +} + +// A single text input. +message TextInputProto { + optional string hint = 1; + enum InputType { + UNDEFINED = 0; + // Regular text, no special formatting rules. + INPUT_TEXT = 1; + // An alphanumeric input, e.g. postal code or discount code. + INPUT_ALPHANUMERIC = 2; + } + optional InputType input_type = 2; + + // The client memory key to store the result in. + optional string client_memory_key = 3; + + // The initial value of the text input. + optional string value = 4; +} + +// A section containing one or multiple text inputs. +message TextInputSectionProto { + repeated TextInputProto input_fields = 1; +} + +message UserFormSectionProto { + optional string title = 1; + + oneof section { + StaticTextSectionProto static_text_section = 2; + TextInputSectionProto text_input_section = 3; + } +} + // Asks to provide the data used by UseAddressAction and // UseCreditCardAction. +// Next: 22 message CollectUserDataProto { enum TermsAndConditionsState { // No choice has been made yet. @@ -1236,6 +1353,12 @@ message CollectUserDataProto { // that will finish this action and return the clicked link in the action // result. optional string accept_terms_and_conditions_text = 13; + // Message that indicates that the user wants to review the terms and + // conditions of a 3rd party's domain, e.g., 'example.com'. + optional string terms_require_review_text = 20; + // Privacy notice telling users that autofill assistant will send personal + // data to a third party’s website. + optional string thirdparty_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 @@ -1250,6 +1373,12 @@ message CollectUserDataProto { optional string billing_postal_code_missing_text = 15; // The login details that should be gathered. optional LoginDetailsProto login_details = 16; + // The date/time range that should be gathered. + optional DateTimeRangeProto date_time_range = 17; + // An optional list of additional sections, which is above all other sections. + repeated UserFormSectionProto additional_prepended_sections = 18; + // An optional list of additional sections, which is below all other sections. + repeated UserFormSectionProto additional_appended_sections = 19; } // Resets Autofill Assistant: clears any state and server payload. @@ -1399,6 +1528,8 @@ message SetFormFieldValueProto { // Use the password from the Chrome password manager login previously // selected in a CollectUserDataAction. bool use_password = 5; + // Use the value stored at the specified memory location. + string client_memory_key = 6; } } diff --git a/chromium/components/autofill_assistant/browser/state.h b/chromium/components/autofill_assistant/browser/state.h index d7479a72727..3cad37daf2b 100644 --- a/chromium/components/autofill_assistant/browser/state.h +++ b/chromium/components/autofill_assistant/browser/state.h @@ -81,14 +81,13 @@ enum class AutofillAssistantState { // Autofill assistant is stopped, but the controller is still available. // // This is a final state for the UI, which, when entering this state, detaches - // itself from the controller, waits for a few seconds to let the user read - // the message and then disappears. + // itself from the controller and lets the user read the message. // // In that scenario, the status message at the time of transition to STOPPED // is supposed to contain the final message. // - // Next states: TRACKING. - STOPPED + // Next states: TRACKING + STOPPED, }; inline std::ostream& operator<<(std::ostream& out, diff --git a/chromium/components/autofill_assistant/browser/trigger_context.cc b/chromium/components/autofill_assistant/browser/trigger_context.cc index c872b7e5315..a2e13813571 100644 --- a/chromium/components/autofill_assistant/browser/trigger_context.cc +++ b/chromium/components/autofill_assistant/browser/trigger_context.cc @@ -62,6 +62,10 @@ bool TriggerContextImpl::is_cct() const { return cct_; } +bool TriggerContextImpl::is_onboarding_shown() const { + return onboarding_shown_; +} + bool TriggerContextImpl::is_direct_action() const { return direct_action_; } @@ -112,6 +116,14 @@ bool MergedTriggerContext::is_cct() const { return false; } +bool MergedTriggerContext::is_onboarding_shown() const { + for (const TriggerContext* context : contexts_) { + if (context->is_onboarding_shown()) + return true; + } + return false; +} + bool MergedTriggerContext::is_direct_action() const { for (const TriggerContext* context : contexts_) { if (context->is_direct_action()) diff --git a/chromium/components/autofill_assistant/browser/trigger_context.h b/chromium/components/autofill_assistant/browser/trigger_context.h index 9d887b7bd03..11e78d111c1 100644 --- a/chromium/components/autofill_assistant/browser/trigger_context.h +++ b/chromium/components/autofill_assistant/browser/trigger_context.h @@ -53,6 +53,10 @@ class TriggerContext { // Java. virtual bool is_cct() const = 0; + // Returns true if the onboarding was shown at the beginning when this + // autofill assistant flow got triggered. + virtual bool is_onboarding_shown() const = 0; + // Returns true if the current action was triggered by a direct action. virtual bool is_direct_action() const = 0; }; @@ -76,10 +80,12 @@ class TriggerContextImpl : public TriggerContext { 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; } std::string experiment_ids() const override; bool is_cct() const override; + bool is_onboarding_shown() const override; bool is_direct_action() const override; private: @@ -93,6 +99,8 @@ class TriggerContextImpl : public TriggerContext { bool cct_ = false; bool direct_action_ = false; + + bool onboarding_shown_ = false; }; // Merges several TriggerContexts together. @@ -110,6 +118,7 @@ class MergedTriggerContext : public TriggerContext { const std::string& name) const override; std::string experiment_ids() const override; bool is_cct() const override; + bool is_onboarding_shown() const override; bool is_direct_action() const override; private: diff --git a/chromium/components/autofill_assistant/browser/trigger_context_unittest.cc b/chromium/components/autofill_assistant/browser/trigger_context_unittest.cc index d98b51157db..890841d2d54 100644 --- a/chromium/components/autofill_assistant/browser/trigger_context_unittest.cc +++ b/chromium/components/autofill_assistant/browser/trigger_context_unittest.cc @@ -102,6 +102,28 @@ TEST(TriggerContextText, MergeCCT) { EXPECT_TRUE(one_with_cct->is_cct()); } +TEST(TriggerContextTest, OnboardingShown) { + TriggerContextImpl context; + + EXPECT_FALSE(context.is_onboarding_shown()); + context.SetOnboardingShown(true); + EXPECT_TRUE(context.is_onboarding_shown()); +} + +TEST(TriggerContextTest, MergeOnboardingShown) { + auto empty = TriggerContext::CreateEmpty(); + + auto all_empty = TriggerContext::Merge({empty.get(), empty.get()}); + EXPECT_FALSE(all_empty->is_onboarding_shown()); + + TriggerContextImpl onboarding_context; + onboarding_context.SetOnboardingShown(true); + auto one_with_onboarding = + TriggerContext::Merge({empty.get(), &onboarding_context, empty.get()}); + + EXPECT_TRUE(one_with_onboarding->is_onboarding_shown()); +} + TEST(TriggerContextText, DirectAction) { TriggerContextImpl context; diff --git a/chromium/components/autofill_assistant/browser/ui_delegate.h b/chromium/components/autofill_assistant/browser/ui_delegate.h index b6d5f7c2c31..63ff2f17465 100644 --- a/chromium/components/autofill_assistant/browser/ui_delegate.h +++ b/chromium/components/autofill_assistant/browser/ui_delegate.h @@ -108,8 +108,11 @@ class UiDelegate { virtual void SetContactInfo( std::unique_ptr<autofill::AutofillProfile> profile) = 0; - // Sets credit card, in response to the current collect user data options. - virtual void SetCreditCard(std::unique_ptr<autofill::CreditCard> card) = 0; + // Sets credit card and billing profile, in response to the current collect + // user data options. + virtual void SetCreditCard( + std::unique_ptr<autofill::CreditCard> card, + std::unique_ptr<autofill::AutofillProfile> billing_profile) = 0; // Sets the state of the third party terms & conditions, pertaining to the // current collect user data options. @@ -123,6 +126,26 @@ class UiDelegate { // Called when the user clicks a link on the terms & conditions message. virtual void OnTermsAndConditionsLinkClicked(int link) = 0; + // Sets the start of the date/time range. + virtual void SetDateTimeRangeStart(int year, + int month, + int day, + int hour, + int minute, + int second) = 0; + + // Sets the end of the date/time range. + virtual void SetDateTimeRangeEnd(int year, + int month, + int day, + int hour, + int minute, + int second) = 0; + + // Sets an additional value. + virtual void SetAdditionalValue(const std::string& client_memory_key, + const std::string& value) = 0; + // Adds the rectangles that correspond to the current touchable area to // the given vector. // diff --git a/chromium/components/autofill_assistant/browser/user_data.cc b/chromium/components/autofill_assistant/browser/user_data.cc index c518bdfaf26..2ba170bfa34 100644 --- a/chromium/components/autofill_assistant/browser/user_data.cc +++ b/chromium/components/autofill_assistant/browser/user_data.cc @@ -10,10 +10,19 @@ namespace autofill_assistant { -LoginChoice::LoginChoice(const std::string& id, - const std::string& text, - int priority) - : identifier(id), label(text), preselect_priority(priority) {} +LoginChoice::LoginChoice(const std::string& _identifier, + const std::string& _label, + const std::string& _sublabel, + const std::string& _sublabel_accessibility_hint, + int _preselect_priority, + const base::Optional<InfoPopupProto>& _info_popup) + : identifier(_identifier), + label(_label), + sublabel(_sublabel), + sublabel_accessibility_hint(_sublabel_accessibility_hint), + preselect_priority(_preselect_priority), + info_popup(_info_popup) {} +LoginChoice::LoginChoice(const LoginChoice& another) = default; LoginChoice::~LoginChoice() = default; UserData::UserData() = default; diff --git a/chromium/components/autofill_assistant/browser/user_data.h b/chromium/components/autofill_assistant/browser/user_data.h index 4e394741e4e..4b1815d0aab 100644 --- a/chromium/components/autofill_assistant/browser/user_data.h +++ b/chromium/components/autofill_assistant/browser/user_data.h @@ -5,11 +5,13 @@ #ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_USER_DATA_H_ #define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_USER_DATA_H_ +#include <map> #include <memory> #include <string> #include <vector> #include "base/callback.h" +#include "base/optional.h" #include "components/autofill_assistant/browser/service.pb.h" #include "components/autofill_assistant/browser/user_action.h" @@ -29,18 +31,35 @@ enum TermsAndConditionsState { REQUIRES_REVIEW = 2, }; +// GENERATED_JAVA_ENUM_PACKAGE: ( +// org.chromium.chrome.browser.autofill_assistant.user_data.additional_sections) +// GENERATED_JAVA_CLASS_NAME_OVERRIDE: AssistantTextInputType +enum TextInputType { INPUT_TEXT = 0, INPUT_ALPHANUMERIC = 1 }; + // Represents a concrete login choice in the UI, e.g., 'Guest checkout' or // a particular Chrome PWM login account. struct LoginChoice { - LoginChoice(const std::string& id, const std::string& text, int priority); + LoginChoice(const std::string& id, + const std::string& label, + const std::string& sublabel, + const std::string& sublabel_accessibility_hint, + int priority, + const base::Optional<InfoPopupProto>& info_popup); + LoginChoice(const LoginChoice& another); ~LoginChoice(); // Uniquely identifies this login choice. std::string identifier; // The label to display to the user. std::string label; + // The sublabel to display to the user. + std::string sublabel; + // The a11y hint for |sublabel|. + std::string sublabel_accessibility_hint; // The priority to pre-select this choice (-1 == not set/automatic). int preselect_priority = -1; + // The popup to show to provide more information about this login choice. + base::Optional<InfoPopupProto> info_popup; }; // Struct for holding the user data. @@ -55,6 +74,11 @@ struct UserData { std::unique_ptr<autofill::AutofillProfile> billing_address; std::string login_choice_identifier; TermsAndConditionsState terms_and_conditions = NOT_SELECTED; + DateTimeProto date_time_range_start; + DateTimeProto date_time_range_end; + + // A set of additional key/value pairs to be stored in client_memory. + std::map<std::string, std::string> additional_values_to_store; }; // Struct for holding the payment request options. @@ -68,12 +92,15 @@ struct CollectUserDataOptions { bool request_shipping = false; bool request_payment_method = false; bool request_login_choice = false; + bool request_date_time_range = false; bool require_billing_postal_code = false; std::string billing_postal_code_missing_text; // If empty, terms and conditions should not be shown. std::string accept_terms_and_conditions_text; + std::string terms_require_review_text; + std::string thirdparty_privacy_notice_text; bool show_terms_as_checkbox = false; std::vector<std::string> supported_basic_card_networks; @@ -83,6 +110,9 @@ struct CollectUserDataOptions { UserActionProto confirm_action; std::vector<UserActionProto> additional_actions; TermsAndConditionsState initial_terms_and_conditions = NOT_SELECTED; + DateTimeRangeProto date_time_range; + std::vector<UserFormSectionProto> additional_prepended_sections; + std::vector<UserFormSectionProto> additional_appended_sections; base::OnceCallback<void(std::unique_ptr<UserData>)> confirm_callback; base::OnceCallback<void(int)> additional_actions_callback; diff --git a/chromium/components/autofill_assistant/browser/web/element_finder.cc b/chromium/components/autofill_assistant/browser/web/element_finder.cc index 86fadcce821..158253d4429 100644 --- a/chromium/components/autofill_assistant/browser/web/element_finder.cc +++ b/chromium/components/autofill_assistant/browser/web/element_finder.cc @@ -135,10 +135,11 @@ void ElementFinder::Start(Callback callback) { SendResult(ClientStatus(INVALID_SELECTOR)); return; } + devtools_client_->GetRuntime()->Evaluate( - std::string(kGetDocumentElement), + std::string(kGetDocumentElement), /* node_frame_id= */ std::string(), base::BindOnce(&ElementFinder::OnGetDocumentElement, - weak_ptr_factory_.GetWeakPtr())); + weak_ptr_factory_.GetWeakPtr(), 0)); } void ElementFinder::SendResult(const ClientStatus& status) { @@ -148,6 +149,7 @@ void ElementFinder::SendResult(const ClientStatus& status) { } void ElementFinder::OnGetDocumentElement( + size_t index, const DevtoolsClient::ReplyStatus& reply_status, std::unique_ptr<runtime::EvaluateResult> result) { ClientStatus status = @@ -163,10 +165,14 @@ void ElementFinder::OnGetDocumentElement( SendResult(ClientStatus(ELEMENT_RESOLUTION_FAILED)); return; } - element_result_->container_frame_host = web_contents_->GetMainFrame(); - element_result_->container_frame_selector_index = 0; - element_result_->object_id = ""; - RecursiveFindElement(object_id, 0); + + element_result_->container_frame_selector_index = index; + if (element_result_->container_frame_host == nullptr) { + // Don't overwrite results from previous OOPIF passes. + element_result_->container_frame_host = web_contents_->GetMainFrame(); + } + element_result_->object_id = std::string(); + RecursiveFindElement(object_id, index); } void ElementFinder::RecursiveFindElement(const std::string& object_id, @@ -214,6 +220,7 @@ void ElementFinder::RecursiveFindElement(const std::string& object_id, .SetArguments(std::move(argument)) .SetFunctionDeclaration(function) .Build(), + element_result_->node_frame_id, base::BindOnce(&ElementFinder::OnQuerySelectorAll, weak_ptr_factory_.GetWeakPtr(), index)); } @@ -273,6 +280,7 @@ void ElementFinder::OnQuerySelectorAll( devtools_client_->GetDOM()->DescribeNode( dom::DescribeNodeParams::Builder().SetObjectId(object_id).Build(), + element_result_->node_frame_id, base::BindOnce(&ElementFinder::OnDescribeNodeForPseudoElement, weak_ptr_factory_.GetWeakPtr(), pseudo_type)); return; @@ -280,6 +288,7 @@ void ElementFinder::OnQuerySelectorAll( 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)); } @@ -303,6 +312,7 @@ void ElementFinder::OnDescribeNodeForPseudoElement( dom::ResolveNodeParams::Builder() .SetBackendNodeId(pseudo_element->GetBackendNodeId()) .Build(), + element_result_->node_frame_id, base::BindOnce(&ElementFinder::OnResolveNodeForPseudoElement, weak_ptr_factory_.GetWeakPtr())); return; @@ -336,39 +346,67 @@ void ElementFinder::OnDescribeNode( auto* node = result->GetNode(); std::vector<int> backend_ids; + if (node->HasContentDocument()) { + // If the frame has a ContentDocument, it's considered a local frame. + // We need to resolve the RenderFrameHost for autofill. + backend_ids.emplace_back(node->GetContentDocument()->GetBackendNodeId()); element_result_->container_frame_selector_index = index; - // Find out the corresponding render frame host through document url and - // name. - // TODO(crbug.com/806868): Use more attributes to find out the render frame - // host if name and document url are not enough to uniquely identify it. - std::string frame_name; - if (node->HasAttributes()) { - const std::vector<std::string>* attributes = node->GetAttributes(); - for (size_t i = 0; i < attributes->size();) { - if ((*attributes)[i] == "name") { - frame_name = (*attributes)[i + 1]; - break; + if (node->HasFrameId()) { + element_result_->container_frame_host = + FindCorrespondingRenderFrameHost(node->GetFrameId()); + } else { + // TODO(b/143318024): Remove the fallback. + std::string frame_name; + if (node->HasAttributes()) { + const std::vector<std::string>* attributes = node->GetAttributes(); + for (size_t i = 0; i < attributes->size();) { + if ((*attributes)[i] == "name") { + frame_name = (*attributes)[i + 1]; + break; + } + // Jump two positions since attribute name and value are always + // paired. + i = i + 2; } - // Jump two positions since attribute name and value are always paired. - i = i + 2; } + element_result_->container_frame_host = FindCorrespondingRenderFrameHost( + frame_name, node->GetContentDocument()->GetDocumentURL()); } - element_result_->container_frame_host = FindCorrespondingRenderFrameHost( - frame_name, node->GetContentDocument()->GetDocumentURL()); + if (!element_result_->container_frame_host) { DVLOG(1) << __func__ << " Failed to find corresponding owner frame."; - SendResult( - UnexpectedDevtoolsErrorStatus(reply_status, __FILE__, __LINE__)); + SendResult(ClientStatus(FRAME_HOST_NOT_FOUND)); return; } } else if (node->HasFrameId()) { - // TODO(crbug.com/806868): Support out-of-process iframe. - DVLOG(3) << "Warning (unsupported): the element is inside an OOPIF."; - SendResult(ClientStatus(UNSUPPORTED)); + // If the frame has no ContentDocument, it's considered an + // OutOfProcessIFrame. + // See https://www.chromium.org/developers/design-documents/oop-iframes for + // full documentation. + // We need to assign the frame id, such that devtools can resolve the + // session calls should be executed on. We also need to resolve the + // RenderFrameHost for autofill. + + element_result_->node_frame_id = node->GetFrameId(); + element_result_->container_frame_selector_index = index; + element_result_->container_frame_host = + FindCorrespondingRenderFrameHost(node->GetFrameId()); + + if (!element_result_->container_frame_host) { + DVLOG(1) << __func__ << " Failed to find corresponding owner frame."; + SendResult(ClientStatus(FRAME_HOST_NOT_FOUND)); + return; + } + + // 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)); return; } @@ -383,6 +421,7 @@ void ElementFinder::OnDescribeNode( dom::ResolveNodeParams::Builder() .SetBackendNodeId(backend_ids[0]) .Build(), + element_result_->node_frame_id, base::BindOnce(&ElementFinder::OnResolveNode, weak_ptr_factory_.GetWeakPtr(), index)); return; @@ -405,6 +444,17 @@ void ElementFinder::OnResolveNode( } content::RenderFrameHost* ElementFinder::FindCorrespondingRenderFrameHost( + std::string frame_id) { + for (auto* frame : web_contents_->GetAllFrames()) { + if (frame->GetDevToolsFrameToken().ToString() == frame_id) { + return frame; + } + } + + return nullptr; +} + +content::RenderFrameHost* ElementFinder::FindCorrespondingRenderFrameHost( std::string name, std::string document_url) { content::RenderFrameHost* ret_frame = nullptr; diff --git a/chromium/components/autofill_assistant/browser/web/element_finder.h b/chromium/components/autofill_assistant/browser/web/element_finder.h index 3acc08bd797..880bc86cf3d 100644 --- a/chromium/components/autofill_assistant/browser/web/element_finder.h +++ b/chromium/components/autofill_assistant/browser/web/element_finder.h @@ -44,6 +44,9 @@ class ElementFinder : public WebControllerWorker { // The object id of the element. std::string object_id; + + // The id of the frame the element's node is in. + std::string node_frame_id; }; // |web_contents| and |devtools_client| must be valid for the lifetime of the @@ -62,7 +65,8 @@ class ElementFinder : public WebControllerWorker { private: void SendResult(const ClientStatus& status); - void OnGetDocumentElement(const DevtoolsClient::ReplyStatus& reply_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( @@ -83,6 +87,9 @@ class ElementFinder : public WebControllerWorker { void OnResolveNode(size_t index, const DevtoolsClient::ReplyStatus& reply_status, std::unique_ptr<dom::ResolveNodeResult> result); + + content::RenderFrameHost* FindCorrespondingRenderFrameHost( + std::string frame_id); content::RenderFrameHost* FindCorrespondingRenderFrameHost( std::string name, std::string document_url); @@ -90,6 +97,7 @@ class ElementFinder : public WebControllerWorker { content::WebContents* const web_contents_; DevtoolsClient* const devtools_client_; const Selector selector_; + const bool strict_; Callback callback_; std::unique_ptr<Result> element_result_; 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 09e0aab18e3..6cc0ff3daa4 100644 --- a/chromium/components/autofill_assistant/browser/web/element_position_getter.cc +++ b/chromium/components/autofill_assistant/browser/web/element_position_getter.cc @@ -24,11 +24,14 @@ const char* const kScrollIntoViewIfNeededScript = namespace autofill_assistant { -ElementPositionGetter::ElementPositionGetter(DevtoolsClient* devtools_client, - const ClientSettings& settings) +ElementPositionGetter::ElementPositionGetter( + DevtoolsClient* devtools_client, + const ClientSettings& settings, + const std::string& optional_node_frame_id) : check_interval_(settings.box_model_check_interval), max_rounds_(settings.box_model_check_count), devtools_client_(devtools_client), + node_frame_id_(optional_node_frame_id), weak_ptr_factory_(this) {} ElementPositionGetter::~ElementPositionGetter() = default; @@ -64,6 +67,7 @@ void ElementPositionGetter::OnVisualStateUpdatedCallback(bool success) { void ElementPositionGetter::GetAndWaitBoxModelStable() { devtools_client_->GetDOM()->GetBoxModel( dom::GetBoxModelParams::Builder().SetObjectId(object_id_).Build(), + node_frame_id_, base::BindOnce(&ElementPositionGetter::OnGetBoxModelForStableCheck, weak_ptr_factory_.GetWeakPtr())); } @@ -124,13 +128,14 @@ void ElementPositionGetter::OnGetBoxModelForStableCheck( .SetFunctionDeclaration(std::string(kScrollIntoViewIfNeededScript)) .SetReturnByValue(true) .Build(), + node_frame_id_, base::BindOnce(&ElementPositionGetter::OnScrollIntoView, weak_ptr_factory_.GetWeakPtr())); return; } --remaining_rounds_; - base::PostDelayedTaskWithTraits( + base::PostDelayedTask( FROM_HERE, {content::BrowserThread::UI}, base::BindOnce(&ElementPositionGetter::GetAndWaitBoxModelStable, weak_ptr_factory_.GetWeakPtr()), @@ -149,7 +154,7 @@ void ElementPositionGetter::OnScrollIntoView( } --remaining_rounds_; - base::PostDelayedTaskWithTraits( + base::PostDelayedTask( FROM_HERE, {content::BrowserThread::UI}, base::BindOnce(&ElementPositionGetter::GetAndWaitBoxModelStable, weak_ptr_factory_.GetWeakPtr()), diff --git a/chromium/components/autofill_assistant/browser/web/element_position_getter.h b/chromium/components/autofill_assistant/browser/web/element_position_getter.h index 6639c419ef8..8ed19691250 100644 --- a/chromium/components/autofill_assistant/browser/web/element_position_getter.h +++ b/chromium/components/autofill_assistant/browser/web/element_position_getter.h @@ -30,7 +30,8 @@ class ElementPositionGetter : public WebControllerWorker { public: // |devtools_client| must be valid for the lifetime of the instance. ElementPositionGetter(DevtoolsClient* devtools_client, - const ClientSettings& settings); + const ClientSettings& settings, + const std::string& optional_node_frame_id); ~ElementPositionGetter() override; // Callback that receives the position that corresponds to the center @@ -75,6 +76,8 @@ class ElementPositionGetter : public WebControllerWorker { int point_x_ = 0; int point_y_ = 0; + std::string node_frame_id_; + base::WeakPtrFactory<ElementPositionGetter> weak_ptr_factory_; }; diff --git a/chromium/components/autofill_assistant/browser/web/mock_web_controller.h b/chromium/components/autofill_assistant/browser/web/mock_web_controller.h index 03e0fb70e9b..bf89e46bbf6 100644 --- a/chromium/components/autofill_assistant/browser/web/mock_web_controller.h +++ b/chromium/components/autofill_assistant/browser/web/mock_web_controller.h @@ -47,24 +47,26 @@ class MockWebController : public WebController { const TopPadding& top_padding, base::OnceCallback<void(const ClientStatus&)>& callback)); - void ElementCheck(const Selector& selector, - bool strict, - base::OnceCallback<void(bool)> callback) override { + void ElementCheck( + const Selector& selector, + bool strict, + base::OnceCallback<void(const ClientStatus&)> callback) override { OnElementCheck(selector, callback); } MOCK_METHOD2(OnElementCheck, void(const Selector& selector, - base::OnceCallback<void(bool)>& callback)); + base::OnceCallback<void(const ClientStatus&)>& callback)); void GetFieldValue( const Selector& selector, - base::OnceCallback<void(bool, const std::string&)> callback) override { + base::OnceCallback<void(const ClientStatus&, const std::string&)> + callback) override { OnGetFieldValue(selector, callback); } - MOCK_METHOD2( - OnGetFieldValue, - void(const Selector& selector, - base::OnceCallback<void(bool, const std::string&)>& callback)); + MOCK_METHOD2(OnGetFieldValue, + void(const Selector& selector, + base::OnceCallback<void(const ClientStatus&, + const std::string&)>& callback)); void GetVisualViewport( base::OnceCallback<void(bool, const RectF&)> callback) override { diff --git a/chromium/components/autofill_assistant/browser/web/web_controller.cc b/chromium/components/autofill_assistant/browser/web/web_controller.cc index 85c2ae72466..063b22ce2ad 100644 --- a/chromium/components/autofill_assistant/browser/web/web_controller.cc +++ b/chromium/components/autofill_assistant/browser/web/web_controller.cc @@ -299,6 +299,7 @@ void WebController::OnFindElementForClickOrTap( std::string element_object_id = result->object_id; WaitForDocumentToBecomeInteractive( settings_->document_ready_check_count, element_object_id, + result->node_frame_id, base::BindOnce( &WebController::OnWaitDocumentToBecomeInteractiveForClickOrTap, weak_ptr_factory_.GetWeakPtr(), std::move(callback), click_type, @@ -333,6 +334,7 @@ void WebController::ClickOrTapElement( .SetFunctionDeclaration(std::string(kScrollIntoViewCenterScript)) .SetReturnByValue(true) .Build(), + target_element->node_frame_id, base::BindOnce(&WebController::OnScrollIntoView, weak_ptr_factory_.GetWeakPtr(), std::move(target_element), std::move(callback), click_type)); @@ -364,20 +366,22 @@ void WebController::OnScrollIntoView( .SetArguments(std::move(argument)) .SetFunctionDeclaration(kClickElement) .Build(), + target_element->node_frame_id, base::BindOnce(&WebController::OnClickJS, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); return; } std::unique_ptr<ElementPositionGetter> getter = - std::make_unique<ElementPositionGetter>(devtools_client_.get(), - *settings_); + std::make_unique<ElementPositionGetter>( + devtools_client_.get(), *settings_, target_element->node_frame_id); auto* ptr = getter.get(); pending_workers_.emplace_back(std::move(getter)); - ptr->Start(target_element->container_frame_host, target_element->object_id, - base::BindOnce(&WebController::TapOrClickOnCoordinates, - weak_ptr_factory_.GetWeakPtr(), ptr, - std::move(callback), click_type)); + ptr->Start( + target_element->container_frame_host, target_element->object_id, + base::BindOnce(&WebController::TapOrClickOnCoordinates, + weak_ptr_factory_.GetWeakPtr(), ptr, std::move(callback), + target_element->node_frame_id, click_type)); } void WebController::OnClickJS( @@ -395,6 +399,7 @@ void WebController::OnClickJS( void WebController::TapOrClickOnCoordinates( ElementPositionGetter* getter_to_release, base::OnceCallback<void(const ClientStatus&)> callback, + const std::string& node_frame_id, ClickAction::ClickType click_type, bool has_coordinates, int x, @@ -419,9 +424,10 @@ void WebController::TapOrClickOnCoordinates( .SetButton(input::DispatchMouseEventButton::LEFT) .SetType(input::DispatchMouseEventType::MOUSE_PRESSED) .Build(), + node_frame_id, base::BindOnce(&WebController::OnDispatchPressMouseEvent, - weak_ptr_factory_.GetWeakPtr(), std::move(callback), x, - y)); + weak_ptr_factory_.GetWeakPtr(), std::move(callback), + node_frame_id, x, y)); return; } @@ -434,12 +440,15 @@ void WebController::TapOrClickOnCoordinates( .SetType(input::DispatchTouchEventType::TOUCH_START) .SetTouchPoints(std::move(touch_points)) .Build(), + node_frame_id, base::BindOnce(&WebController::OnDispatchTouchEventStart, - weak_ptr_factory_.GetWeakPtr(), std::move(callback))); + weak_ptr_factory_.GetWeakPtr(), std::move(callback), + node_frame_id)); } void WebController::OnDispatchPressMouseEvent( base::OnceCallback<void(const ClientStatus&)> callback, + const std::string& node_frame_id, int x, int y, const DevtoolsClient::ReplyStatus& reply_status, @@ -460,6 +469,7 @@ void WebController::OnDispatchPressMouseEvent( .SetButton(input::DispatchMouseEventButton::LEFT) .SetType(input::DispatchMouseEventType::MOUSE_RELEASED) .Build(), + node_frame_id, base::BindOnce(&WebController::OnDispatchReleaseMouseEvent, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } @@ -479,6 +489,7 @@ void WebController::OnDispatchReleaseMouseEvent( void WebController::OnDispatchTouchEventStart( base::OnceCallback<void(const ClientStatus&)> callback, + const std::string& node_frame_id, const DevtoolsClient::ReplyStatus& reply_status, std::unique_ptr<input::DispatchTouchEventResult> result) { if (!result) { @@ -495,6 +506,7 @@ void WebController::OnDispatchTouchEventStart( .SetType(input::DispatchTouchEventType::TOUCH_END) .SetTouchPoints(std::move(touch_points)) .Build(), + node_frame_id, base::BindOnce(&WebController::OnDispatchTouchEventEnd, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } @@ -512,9 +524,10 @@ void WebController::OnDispatchTouchEventEnd( std::move(callback).Run(OkClientStatus()); } -void WebController::ElementCheck(const Selector& selector, - bool strict, - base::OnceCallback<void(bool)> callback) { +void WebController::ElementCheck( + const Selector& selector, + bool strict, + base::OnceCallback<void(const ClientStatus&)> callback) { DCHECK(!selector.empty()); FindElement( selector, strict, @@ -523,13 +536,13 @@ void WebController::ElementCheck(const Selector& selector, } void WebController::OnFindElementForCheck( - base::OnceCallback<void(bool)> callback, + base::OnceCallback<void(const ClientStatus&)> callback, const ClientStatus& status, std::unique_ptr<ElementFinder::Result> result) { DVLOG_IF(1, !status.ok() && status.proto_status() != ELEMENT_RESOLUTION_FAILED) << __func__ << ": " << status; - std::move(callback).Run(status.ok()); + std::move(callback).Run(status); } void WebController::WaitForWindowHeightChange( @@ -539,6 +552,7 @@ void WebController::WaitForWindowHeightChange( .SetExpression(kWaitForWindowHeightChange) .SetAwaitPromise(true) .Build(), + /* node_frame_id= */ std::string(), base::BindOnce(&WebController::OnWaitForWindowHeightChange, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } @@ -576,6 +590,7 @@ void WebController::WaitForDocumentReadyState( .SetReturnByValue(true) .SetAwaitPromise(true) .Build(), + /* node_frame_id= */ std::string(), base::BindOnce(&OnWaitForDocumentReadyState<runtime::EvaluateResult>, std::move(callback))); return; @@ -614,6 +629,7 @@ void WebController::OnFindElementForWaitForDocumentReadyState( .SetReturnByValue(true) .SetAwaitPromise(true) .Build(), + element->node_frame_id, base::BindOnce( &OnWaitForDocumentReadyState<runtime::CallFunctionOnResult>, std::move(callback))); @@ -655,6 +671,7 @@ void WebController::OnFindElementForFocusElement( std::string element_object_id = element_result->object_id; WaitForDocumentToBecomeInteractive( settings_->document_ready_check_count, element_object_id, + element_result->node_frame_id, base::BindOnce( &WebController::OnWaitDocumentToBecomeInteractiveForFocusElement, weak_ptr_factory_.GetWeakPtr(), top_padding, std::move(callback), @@ -690,6 +707,7 @@ void WebController::OnWaitDocumentToBecomeInteractiveForFocusElement( .SetFunctionDeclaration(std::string(kScrollIntoViewWithPaddingScript)) .SetReturnByValue(true) .Build(), + target_element->node_frame_id, base::BindOnce(&WebController::OnFocusElement, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } @@ -720,6 +738,23 @@ void WebController::FillAddressForm( std::move(callback))); } +void WebController::FillCardForm( + std::unique_ptr<autofill::CreditCard> card, + const base::string16& cvc, + const Selector& selector, + base::OnceCallback<void(const ClientStatus&)> callback) { + DVLOG(3) << __func__ << " " << selector; + auto data_to_autofill = std::make_unique<FillFormInputData>(); + data_to_autofill->card = std::move(card); + data_to_autofill->cvc = cvc; + FindElement(selector, + /* strict_mode= */ true, + base::BindOnce(&WebController::OnFindElementForFillingForm, + weak_ptr_factory_.GetWeakPtr(), + std::move(data_to_autofill), selector, + std::move(callback))); +} + void WebController::OnFindElementForFillingForm( std::unique_ptr<FillFormInputData> data_to_autofill, const Selector& selector, @@ -728,12 +763,18 @@ void WebController::OnFindElementForFillingForm( std::unique_ptr<ElementFinder::Result> element_result) { if (!status.ok()) { DVLOG(1) << __func__ << " Failed to find the element for filling the form."; - std::move(callback).Run(status); + std::move(callback).Run(FillAutofillErrorStatus(status)); return; } ContentAutofillDriver* driver = ContentAutofillDriver::GetForRenderFrameHost( element_result->container_frame_host); + if (driver == nullptr) { + DVLOG(1) << __func__ << " Failed to get the autofill driver."; + std::move(callback).Run( + 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 @@ -754,15 +795,17 @@ void WebController::OnGetFormAndFieldDataForFillingForm( const autofill::FormFieldData& form_field) { if (form_data.fields.empty()) { DVLOG(1) << __func__ << " Failed to get form data to fill form."; - std::move(callback).Run(UnexpectedErrorStatus(__FILE__, __LINE__)); + std::move(callback).Run( + FillAutofillErrorStatus(UnexpectedErrorStatus(__FILE__, __LINE__))); return; } ContentAutofillDriver* driver = ContentAutofillDriver::GetForRenderFrameHost(container_frame_host); - if (!driver) { + if (driver == nullptr) { DVLOG(1) << __func__ << " Failed to get the autofill driver."; - std::move(callback).Run(UnexpectedErrorStatus(__FILE__, __LINE__)); + std::move(callback).Run( + FillAutofillErrorStatus(UnexpectedErrorStatus(__FILE__, __LINE__))); return; } @@ -778,23 +821,6 @@ void WebController::OnGetFormAndFieldDataForFillingForm( std::move(callback).Run(OkClientStatus()); } -void WebController::FillCardForm( - std::unique_ptr<autofill::CreditCard> card, - const base::string16& cvc, - const Selector& selector, - base::OnceCallback<void(const ClientStatus&)> callback) { - DVLOG(3) << __func__ << " " << selector; - auto data_to_autofill = std::make_unique<FillFormInputData>(); - data_to_autofill->card = std::move(card); - data_to_autofill->cvc = cvc; - FindElement(selector, - /* strict_mode= */ true, - base::BindOnce(&WebController::OnFindElementForFillingForm, - weak_ptr_factory_.GetWeakPtr(), - std::move(data_to_autofill), selector, - std::move(callback))); -} - void WebController::SelectOption( const Selector& selector, const std::string& selected_option, @@ -830,6 +856,7 @@ void WebController::OnFindElementForSelectOption( .SetFunctionDeclaration(std::string(kSelectOptionScript)) .SetReturnByValue(true) .Build(), + element_result->node_frame_id, base::BindOnce(&WebController::OnSelectOption, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } @@ -891,6 +918,7 @@ void WebController::OnFindElementForHighlightElement( .SetFunctionDeclaration(std::string(kHighlightElementScript)) .SetReturnByValue(true) .Build(), + element_result->node_frame_id, base::BindOnce(&WebController::OnHighlightElement, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } @@ -920,7 +948,8 @@ void WebController::FocusElement( void WebController::GetFieldValue( const Selector& selector, - base::OnceCallback<void(bool, const std::string&)> callback) { + base::OnceCallback<void(const ClientStatus&, const std::string&)> + callback) { FindElement( selector, /* strict_mode= */ true, @@ -929,12 +958,12 @@ void WebController::GetFieldValue( } void WebController::OnFindElementForGetFieldValue( - base::OnceCallback<void(bool, const std::string&)> callback, + base::OnceCallback<void(const ClientStatus&, const std::string&)> callback, const ClientStatus& status, std::unique_ptr<ElementFinder::Result> element_result) { const std::string object_id = element_result->object_id; if (!status.ok()) { - std::move(callback).Run(/* exists= */ false, ""); + std::move(callback).Run(status, ""); return; } @@ -944,12 +973,14 @@ void WebController::OnFindElementForGetFieldValue( .SetFunctionDeclaration(std::string(kGetValueAttributeScript)) .SetReturnByValue(true) .Build(), + element_result->node_frame_id, base::BindOnce(&WebController::OnGetValueAttribute, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } void WebController::OnGetValueAttribute( - base::OnceCallback<void(bool, const std::string&)> callback, + base::OnceCallback<void(const ClientStatus& element_status, + const std::string&)> callback, const DevtoolsClient::ReplyStatus& reply_status, std::unique_ptr<runtime::CallFunctionOnResult> result) { std::string value; @@ -959,7 +990,7 @@ void WebController::OnGetValueAttribute( DVLOG_IF(1, !status.ok()) << __func__ << "Failed to get attribute value: " << status; SafeGetStringValue(result->GetResult(), &value); - std::move(callback).Run(/* exists= */ true, value); + std::move(callback).Run(status, value); } void WebController::SetFieldValue( @@ -1011,6 +1042,7 @@ void WebController::OnClearFieldForSendKeyboardInput( } void WebController::OnClickElementForSendKeyboardInput( + const std::string& node_frame_id, const std::vector<UChar32>& codepoints, int delay_in_millisecond, base::OnceCallback<void(const ClientStatus&)> callback, @@ -1019,11 +1051,13 @@ void WebController::OnClickElementForSendKeyboardInput( std::move(callback).Run(click_status); return; } - DispatchKeyboardTextDownEvent(codepoints, 0, /*delay=*/false, - delay_in_millisecond, std::move(callback)); + DispatchKeyboardTextDownEvent(node_frame_id, codepoints, 0, + /* delay= */ false, delay_in_millisecond, + std::move(callback)); } void WebController::DispatchKeyboardTextDownEvent( + const std::string& node_frame_id, const std::vector<UChar32>& codepoints, size_t index, bool delay, @@ -1037,10 +1071,10 @@ void WebController::DispatchKeyboardTextDownEvent( if (delay && delay_in_millisecond > 0) { base::PostDelayedTask( FROM_HERE, {content::BrowserThread::UI}, - base::BindOnce(&WebController::DispatchKeyboardTextDownEvent, - weak_ptr_factory_.GetWeakPtr(), codepoints, index, - /*delay=*/false, delay_in_millisecond, - std::move(callback)), + base::BindOnce( + &WebController::DispatchKeyboardTextDownEvent, + weak_ptr_factory_.GetWeakPtr(), node_frame_id, codepoints, index, + /* delay= */ false, delay_in_millisecond, std::move(callback)), base::TimeDelta::FromMilliseconds(delay_in_millisecond)); return; } @@ -1049,12 +1083,14 @@ void WebController::DispatchKeyboardTextDownEvent( CreateKeyEventParamsForCharacter( autofill_assistant::input::DispatchKeyEventType::KEY_DOWN, codepoints[index]), + node_frame_id, base::BindOnce(&WebController::DispatchKeyboardTextUpEvent, - weak_ptr_factory_.GetWeakPtr(), codepoints, index, - delay_in_millisecond, std::move(callback))); + weak_ptr_factory_.GetWeakPtr(), node_frame_id, codepoints, + index, delay_in_millisecond, std::move(callback))); } void WebController::DispatchKeyboardTextUpEvent( + const std::string& node_frame_id, const std::vector<UChar32>& codepoints, size_t index, int delay_in_millisecond, @@ -1064,10 +1100,11 @@ void WebController::DispatchKeyboardTextUpEvent( CreateKeyEventParamsForCharacter( autofill_assistant::input::DispatchKeyEventType::KEY_UP, codepoints[index]), - base::BindOnce(&WebController::DispatchKeyboardTextDownEvent, - weak_ptr_factory_.GetWeakPtr(), codepoints, index + 1, - /*delay=*/true, delay_in_millisecond, - std::move(callback))); + node_frame_id, + base::BindOnce( + &WebController::DispatchKeyboardTextDownEvent, + weak_ptr_factory_.GetWeakPtr(), node_frame_id, codepoints, index + 1, + /* delay= */ true, delay_in_millisecond, std::move(callback))); } auto WebController::CreateKeyEventParamsForCharacter( @@ -1115,6 +1152,7 @@ void WebController::OnFindElementForSetFieldValue( .SetArguments(std::move(argument)) .SetFunctionDeclaration(std::string(kSetValueAttributeScript)) .Build(), + element_result->node_frame_id, base::BindOnce(&WebController::OnSetValueAttribute, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } @@ -1175,6 +1213,7 @@ void WebController::OnFindElementForSetAttribute( .SetArguments(std::move(arguments)) .SetFunctionDeclaration(std::string(kSetAttributeScript)) .Build(), + element_result->node_frame_id, base::BindOnce(&WebController::OnSetAttribute, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } @@ -1202,8 +1241,7 @@ void WebController::SendKeyboardInput( DCHECK(!selector.empty()); FindElement( - selector, - /* strict_mode= */ true, + selector, /* strict_mode= */ true, base::BindOnce(&WebController::OnFindElementForSendKeyboardInput, weak_ptr_factory_.GetWeakPtr(), selector, codepoints, delay_in_millisecond, std::move(callback))); @@ -1223,7 +1261,8 @@ void WebController::OnFindElementForSendKeyboardInput( ClickOrTapElement( selector, ClickAction::CLICK, base::BindOnce(&WebController::OnClickElementForSendKeyboardInput, - weak_ptr_factory_.GetWeakPtr(), codepoints, + weak_ptr_factory_.GetWeakPtr(), + element_result->node_frame_id, codepoints, delay_in_millisecond, std::move(callback))); } @@ -1246,6 +1285,7 @@ void WebController::GetVisualViewport( .SetExpression(std::string(kGetVisualViewport)) .SetReturnByValue(true) .Build(), + /* node_frame_id= */ std::string(), base::BindOnce(&WebController::OnGetVisualViewport, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } @@ -1311,6 +1351,7 @@ void WebController::OnFindElementForPosition( .SetFunctionDeclaration(std::string(kGetBoundingClientRectAsList)) .SetReturnByValue(true) .Build(), + result->node_frame_id, base::BindOnce(&WebController::OnGetElementPositionResult, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } @@ -1358,6 +1399,7 @@ void WebController::OnFindElementForGetOuterHtml( .SetFunctionDeclaration(std::string(kGetOuterHtmlScript)) .SetReturnByValue(true) .Build(), + element_result->node_frame_id, base::BindOnce(&WebController::OnGetOuterHtml, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } @@ -1380,7 +1422,8 @@ void WebController::OnGetOuterHtml( void WebController::WaitForDocumentToBecomeInteractive( int remaining_rounds, - std::string object_id, + const std::string& object_id, + const std::string& node_frame_id, base::OnceCallback<void(bool)> callback) { devtools_client_->GetRuntime()->CallFunctionOn( runtime::CallFunctionOnParams::Builder() @@ -1388,14 +1431,16 @@ void WebController::WaitForDocumentToBecomeInteractive( .SetFunctionDeclaration(std::string(kIsDocumentReadyForInteract)) .SetReturnByValue(true) .Build(), + node_frame_id, base::BindOnce(&WebController::OnWaitForDocumentToBecomeInteractive, weak_ptr_factory_.GetWeakPtr(), remaining_rounds, - object_id, std::move(callback))); + object_id, node_frame_id, std::move(callback))); } void WebController::OnWaitForDocumentToBecomeInteractive( int remaining_rounds, - std::string object_id, + const std::string& object_id, + const std::string& node_frame_id, base::OnceCallback<void(bool)> callback, const DevtoolsClient::ReplyStatus& reply_status, std::unique_ptr<runtime::CallFunctionOnResult> result) { @@ -1420,7 +1465,7 @@ void WebController::OnWaitForDocumentToBecomeInteractive( FROM_HERE, {content::BrowserThread::UI}, base::BindOnce(&WebController::WaitForDocumentToBecomeInteractive, weak_ptr_factory_.GetWeakPtr(), --remaining_rounds, - object_id, std::move(callback)), + object_id, node_frame_id, std::move(callback)), settings_->document_ready_check_interval); } diff --git a/chromium/components/autofill_assistant/browser/web/web_controller.h b/chromium/components/autofill_assistant/browser/web/web_controller.h index 31c32e264ca..713190568c5 100644 --- a/chromium/components/autofill_assistant/browser/web/web_controller.h +++ b/chromium/components/autofill_assistant/browser/web/web_controller.h @@ -122,7 +122,8 @@ class WebController { // Normally done through BatchElementChecker. virtual void GetFieldValue( const Selector& selector, - base::OnceCallback<void(bool, const std::string&)> callback); + base::OnceCallback<void(const ClientStatus&, const std::string&)> + callback); // Set the |value| of field |selector| and return the result through // |callback|. If |simulate_key_presses| is true, the value will be set by @@ -180,9 +181,10 @@ class WebController { // pass. Otherwise, there must be at least one. // // To check multiple elements, use a BatchElementChecker. - virtual void ElementCheck(const Selector& selector, - bool strict, - base::OnceCallback<void(bool)> callback); + virtual void ElementCheck( + const Selector& selector, + bool strict, + base::OnceCallback<void(const ClientStatus&)> callback); // Calls the callback once the main document window has been resized. virtual void WaitForWindowHeightChange( @@ -247,12 +249,14 @@ class WebController { void TapOrClickOnCoordinates( ElementPositionGetter* getter_to_release, base::OnceCallback<void(const ClientStatus&)> callback, + const std::string& node_frame_id, ClickAction::ClickType click_type, bool has_coordinates, int x, int y); void OnDispatchPressMouseEvent( base::OnceCallback<void(const ClientStatus&)> callback, + const std::string& node_frame_id, int x, int y, const DevtoolsClient::ReplyStatus& reply_status, @@ -263,15 +267,17 @@ class WebController { std::unique_ptr<input::DispatchMouseEventResult> result); void OnDispatchTouchEventStart( base::OnceCallback<void(const ClientStatus&)> callback, + const std::string& node_frame_id, const DevtoolsClient::ReplyStatus& reply_status, std::unique_ptr<input::DispatchTouchEventResult> result); void OnDispatchTouchEventEnd( base::OnceCallback<void(const ClientStatus&)> callback, const DevtoolsClient::ReplyStatus& reply_status, std::unique_ptr<input::DispatchTouchEventResult> result); - void OnFindElementForCheck(base::OnceCallback<void(bool)> callback, - const ClientStatus& status, - std::unique_ptr<ElementFinder::Result> result); + void OnFindElementForCheck( + base::OnceCallback<void(const ClientStatus&)> callback, + const ClientStatus& status, + std::unique_ptr<ElementFinder::Result> result); void OnWaitForWindowHeightChange( base::OnceCallback<void(const ClientStatus&)> callback, const DevtoolsClient::ReplyStatus& reply_status, @@ -329,11 +335,13 @@ class WebController { const DevtoolsClient::ReplyStatus& reply_status, std::unique_ptr<runtime::CallFunctionOnResult> result); void OnFindElementForGetFieldValue( - base::OnceCallback<void(bool, const std::string&)> callback, + base::OnceCallback<void(const ClientStatus&, const std::string&)> + callback, const ClientStatus& status, std::unique_ptr<ElementFinder::Result> element_result); void OnGetValueAttribute( - base::OnceCallback<void(bool, const std::string&)> callback, + base::OnceCallback<void(const ClientStatus&, const std::string&)> + callback, const DevtoolsClient::ReplyStatus& reply_status, std::unique_ptr<runtime::CallFunctionOnResult> result); void InternalSetFieldValue( @@ -347,17 +355,20 @@ class WebController { base::OnceCallback<void(const ClientStatus&)> callback, const ClientStatus& status); void OnClickElementForSendKeyboardInput( + const std::string& node_frame_id, const std::vector<UChar32>& codepoints, int delay_in_milli, base::OnceCallback<void(const ClientStatus&)> callback, const ClientStatus& click_status); void DispatchKeyboardTextDownEvent( + const std::string& node_frame_id, const std::vector<UChar32>& codepoints, size_t index, bool delay, int delay_in_milli, base::OnceCallback<void(const ClientStatus&)> callback); void DispatchKeyboardTextUpEvent( + const std::string& node_frame_id, const std::vector<UChar32>& codepoints, size_t index, int delay_in_milli, @@ -420,11 +431,13 @@ class WebController { // Waits for the document.readyState to be 'interactive' or 'complete'. void WaitForDocumentToBecomeInteractive( int remaining_rounds, - std::string object_id, + const std::string& object_id, + const std::string& node_frame_id, base::OnceCallback<void(bool)> callback); void OnWaitForDocumentToBecomeInteractive( int remaining_rounds, - std::string object_id, + const std::string& object_id, + const std::string& node_frame_id, base::OnceCallback<void(bool)> callback, const DevtoolsClient::ReplyStatus& reply_status, std::unique_ptr<runtime::CallFunctionOnResult> result); 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 9481f480c9c..15bfb057975 100644 --- a/chromium/components/autofill_assistant/browser/web/web_controller_browsertest.cc +++ b/chromium/components/autofill_assistant/browser/web/web_controller_browsertest.cc @@ -24,6 +24,8 @@ namespace autofill_assistant { using ::testing::AnyOf; using ::testing::IsEmpty; +// Flag to enable site per process to enforce OOPIFs. +const char* kSitePerProcess = "site-per-process"; const char* kTargetWebsitePath = "/autofill_assistant_target_website.html"; class WebControllerBrowserTest : public content::ContentBrowserTest, @@ -32,13 +34,26 @@ class WebControllerBrowserTest : public content::ContentBrowserTest, WebControllerBrowserTest() {} ~WebControllerBrowserTest() override {} + void SetUpCommandLine(base::CommandLine* command_line) override { + command_line->AppendSwitch(kSitePerProcess); + } + void SetUpOnMainThread() override { ContentBrowserTest::SetUpOnMainThread(); + + // Start a mock server for hosting an OOPIF. + http_server_iframe_ = std::make_unique<net::EmbeddedTestServer>( + net::EmbeddedTestServer::TYPE_HTTP); + http_server_iframe_->ServeFilesFromSourceDirectory( + "components/test/data/autofill_assistant/html_iframe"); + ASSERT_TRUE(http_server_iframe_->Start(8081)); + + // Start the main server hosting the test page. http_server_ = std::make_unique<net::EmbeddedTestServer>( net::EmbeddedTestServer::TYPE_HTTP); http_server_->ServeFilesFromSourceDirectory( - "components/test/data/autofill_assistant"); - ASSERT_TRUE(http_server_->Start()); + "components/test/data/autofill_assistant/html"); + ASSERT_TRUE(http_server_->Start(8080)); ASSERT_TRUE( NavigateToURL(shell(), http_server_->GetURL(kTargetWebsitePath))); web_controller_ = WebController::CreateForWebContents( @@ -46,25 +61,20 @@ class WebControllerBrowserTest : public content::ContentBrowserTest, Observe(shell()->web_contents()); } - void DidCommitAndDrawCompositorFrame() override { - paint_occurred_during_last_loop_ = true; - } - - void SetUpCommandLine(base::CommandLine* command_line) override { - ContentBrowserTest::SetUpCommandLine(command_line); - command_line->AppendSwitch("allow-pre-commit-input"); - } - void WaitTillPageIsIdle(base::TimeDelta continuous_paint_timeout) { base::TimeTicks finished_load_time = base::TimeTicks::Now(); - bool page_is_loading = false; - do { - paint_occurred_during_last_loop_ = false; - base::RunLoop heart_beat; - base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( - FROM_HERE, heart_beat.QuitClosure(), base::TimeDelta::FromSeconds(3)); - heart_beat.Run(); - page_is_loading = + while (true) { + content::RenderFrameSubmissionObserver frame_submission_observer( + web_contents()); + // Runs a loop for 3 seconds to see if the renderer is idle. + { + base::RunLoop heart_beat; + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, heart_beat.QuitClosure(), + base::TimeDelta::FromSeconds(3)); + heart_beat.Run(); + } + bool page_is_loading = web_contents()->IsWaitingForResponse() || web_contents()->IsLoading(); if (page_is_loading) { finished_load_time = base::TimeTicks::Now(); @@ -76,8 +86,12 @@ class WebControllerBrowserTest : public content::ContentBrowserTest, // blinking caret or a persistent animation is making Chrome paint at // regular intervals. Exit. break; + } else if (frame_submission_observer.render_frame_count() == 0) { + // If the renderer has stopped submitting frames for 3 seconds then + // we're done. + break; } - } while (page_is_loading || paint_occurred_during_last_loop_); + } } void RunStrictElementCheck(const Selector& selector, bool result) { @@ -114,8 +128,8 @@ class WebControllerBrowserTest : public content::ContentBrowserTest, const Selector& selector, size_t* pending_number_of_checks_output, bool expected_result, - bool result) { - EXPECT_EQ(expected_result, result) << "selector: " << selector; + const ClientStatus& result) { + EXPECT_EQ(expected_result, result.ok()) << "selector: " << selector; *pending_number_of_checks_output -= 1; if (*pending_number_of_checks_output == 0) { done_callback.Run(); @@ -150,9 +164,9 @@ class WebControllerBrowserTest : public content::ContentBrowserTest, void OnWaitForElementRemove(const base::Closure& done_callback, const Selector& selector, - bool result) { + const ClientStatus& result) { done_callback.Run(); - if (result) { + if (result.ok()) { WaitForElementRemove(selector); } } @@ -315,7 +329,7 @@ class WebControllerBrowserTest : public content::ContentBrowserTest, void OnGetFieldValue(const base::Closure& done_callback, size_t* pending_number_of_checks_output, const std::string& expected_value, - bool exists, + const ClientStatus& status, const std::string& value) { // Don't use ASSERT_EQ here: if the check fails, this would result in // an endless loop without meaningful test results. @@ -461,7 +475,7 @@ class WebControllerBrowserTest : public content::ContentBrowserTest, private: std::unique_ptr<net::EmbeddedTestServer> http_server_; - bool paint_occurred_during_last_loop_ = false; + std::unique_ptr<net::EmbeddedTestServer> http_server_iframe_; ClientSettings settings_; DISALLOW_COPY_AND_ASSIGN(WebControllerBrowserTest); @@ -487,6 +501,18 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ElementExistenceCheck) { // A non-existent pseudo-element RunLaxElementCheck(Selector({"#button"}, AFTER), false); + + // An iFrame. + RunLaxElementCheck(Selector({"#iframe"}), true); + + // An element in a same-origin iFrame. + RunLaxElementCheck(Selector({"#iframe", "#button"}), true); + + // An OOPIF. + RunLaxElementCheck(Selector({"#iframeExternal"}), true); + + // An element in an OOPIF. + RunLaxElementCheck(Selector({"#iframeExternal", "#button"}), true); } IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, VisibilityRequirementCheck) { @@ -508,6 +534,19 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, VisibilityRequirementCheck) { // A non-existent pseudo-element RunLaxElementCheck(Selector({"#button"}, AFTER).MustBeVisible(), false); + + // An iFrame. + RunLaxElementCheck(Selector({"#iframe"}).MustBeVisible(), true); + + // An element in a same-origin iFrame. + RunLaxElementCheck(Selector({"#iframe", "#button"}).MustBeVisible(), true); + + // An OOPIF. + RunLaxElementCheck(Selector({"#iframeExternal"}).MustBeVisible(), true); + + // An element in an OOPIF. + RunLaxElementCheck(Selector({"#iframeExternal", "#button"}).MustBeVisible(), + true); } IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, MultipleVisibleElementCheck) { @@ -636,6 +675,13 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, selectors.emplace_back(a_selector); 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); + results.emplace_back(true); + // Shadow DOM. a_selector.selectors.clear(); a_selector.selectors.emplace_back("#iframe"); @@ -690,6 +736,18 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ClickElementInIFrame) { WaitForElementRemove(selector); } +IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ClickElementInOOPIF) { + Selector selector; + selector.selectors.emplace_back("#iframeExternal"); + selector.selectors.emplace_back("#button"); + ClickOrTapElement(selector, ClickAction::CLICK); + + selector.selectors.clear(); + selector.selectors.emplace_back("#iframeExternal"); + selector.selectors.emplace_back("#div"); + WaitForElementRemove(selector); +} + IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ClickElementInScrollContainer) { // Make sure #scroll_item_3 is not visible, no matter the screen height. It @@ -1059,6 +1117,28 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, GetAndSetFieldValue) { .proto_status()); } +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"); + EXPECT_EQ(ACTION_APPLIED, SetFieldValue(a_selector, "text", + /* simulate_key_presses= */ false) + .proto_status()); + GetFieldsValue({a_selector}, {"text"}); + + // OOPIF. + a_selector.selectors.clear(); + a_selector.selectors.emplace_back("#iframeExternal"); + a_selector.selectors.emplace_back("#input"); + EXPECT_EQ(ACTION_APPLIED, SetFieldValue(a_selector, "text", + /* simulate_key_presses= */ false) + .proto_status()); + GetFieldsValue({a_selector}, {"text"}); +} + IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SendKeyboardInput) { auto input = UTF8ToUnicode("Zürich"); std::string expected_output = "Zürich"; diff --git a/chromium/components/autofill_assistant/browser/web/web_controller_util.cc b/chromium/components/autofill_assistant/browser/web/web_controller_util.cc index aa0f66d7ab0..f84cbeacf60 100644 --- a/chromium/components/autofill_assistant/browser/web/web_controller_util.cc +++ b/chromium/components/autofill_assistant/browser/web/web_controller_util.cc @@ -47,6 +47,13 @@ ClientStatus JavaScriptErrorStatus( return status; } +ClientStatus FillAutofillErrorStatus(ClientStatus status) { + status.mutable_details() + ->mutable_autofill_error_info() + ->set_autofill_error_status(status.proto_status()); + return status; +} + bool SafeGetObjectId(const runtime::RemoteObject* result, std::string* out) { if (result && result->HasObjectId()) { *out = result->GetObjectId(); diff --git a/chromium/components/autofill_assistant/browser/web/web_controller_util.h b/chromium/components/autofill_assistant/browser/web/web_controller_util.h index b96669513f1..acda6137c8b 100644 --- a/chromium/components/autofill_assistant/browser/web/web_controller_util.h +++ b/chromium/components/autofill_assistant/browser/web/web_controller_util.h @@ -53,6 +53,9 @@ ClientStatus CheckJavaScriptResult( return OkClientStatus(); } +// Fills a ClientStatus with appropriate details for a Chrome Autofill error. +ClientStatus FillAutofillErrorStatus(ClientStatus status); + // Safely gets an object id from a RemoteObject bool SafeGetObjectId(const runtime::RemoteObject* result, std::string* out); diff --git a/chromium/components/autofill_assistant/browser/website_login_fetcher.h b/chromium/components/autofill_assistant/browser/website_login_fetcher.h index fe8c36df27c..41e11fa8fc8 100644 --- a/chromium/components/autofill_assistant/browser/website_login_fetcher.h +++ b/chromium/components/autofill_assistant/browser/website_login_fetcher.h @@ -23,7 +23,7 @@ class WebsiteLoginFetcher { Login(const Login& other); ~Login(); - // The scheme, host, port and path of the login website. + // The origin of the login website. GURL origin; std::string username; }; diff --git a/chromium/components/autofill_assistant/browser/website_login_fetcher_impl.cc b/chromium/components/autofill_assistant/browser/website_login_fetcher_impl.cc index 8352cedc3fb..4f8d624ad86 100644 --- a/chromium/components/autofill_assistant/browser/website_login_fetcher_impl.cc +++ b/chromium/components/autofill_assistant/browser/website_login_fetcher_impl.cc @@ -85,9 +85,9 @@ class WebsiteLoginFetcherImpl::PendingFetchLoginsRequest // From PendingRequest: void OnFetchCompleted() override { std::vector<Login> logins; - for (const auto& match : form_fetcher_->GetBestMatches()) { - logins.emplace_back(match.second->origin, - base::UTF16ToUTF8(match.second->username_value)); + for (const auto* match : form_fetcher_->GetBestMatches()) { + logins.emplace_back(match->origin.GetOrigin(), + base::UTF16ToUTF8(match->username_value)); } std::move(callback_).Run(logins); PendingRequest::OnFetchCompleted(); |