blob: 6c2b1a7779f162208689e3e0ae774c0e8a7375a4 [file] [log] [blame]
// 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,
ActionDelegate* action_delegate) {
required_fields_.assign(required_fields.begin(), required_fields.end());
action_delegate_ = action_delegate;
}
RequiredFieldsFallbackHandler::~RequiredFieldsFallbackHandler() {}
RequiredFieldsFallbackHandler::FallbackData::FallbackData() {}
RequiredFieldsFallbackHandler::FallbackData::~FallbackData() {}
base::Optional<std::string>
RequiredFieldsFallbackHandler::FallbackData::GetValue(int key) {
auto it = field_values.find(key);
if (it != field_values.end()) {
return it->second;
}
return base::nullopt;
}
void RequiredFieldsFallbackHandler::CheckAndFallbackRequiredFields(
const ClientStatus& initial_autofill_status,
std::unique_ptr<FallbackData> fallback_data,
base::OnceCallback<void(const ClientStatus&,
const base::Optional<ClientStatus>&)>
status_update_callback) {
initial_autofill_status_ = initial_autofill_status;
status_update_callback_ = std::move(status_update_callback);
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 (fallback_data->GetValue(required_field.fallback_key).has_value()) {
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];
auto fallback_value = fallback_data->GetValue(required_field.fallback_key);
if (!fallback_value.has_value()) {
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.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& set_field_status) {
if (!set_field_status.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