blob: 1e4dffa22b8721cbfd7b935007df2eac24bca401 [file] [log] [blame]
// 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.
#include "components/autofill_assistant/browser/actions/show_form_action.h"
#include <memory>
#include <utility>
#include "base/callback.h"
#include "components/autofill_assistant/browser/actions/action_delegate.h"
#include "components/strings/grit/components_strings.h"
#include "ui/base/l10n/l10n_util.h"
namespace autofill_assistant {
ShowFormAction::ShowFormAction(const ActionProto& proto)
: Action(proto), weak_ptr_factory_(this) {
DCHECK(proto_.has_show_form() && proto_.show_form().has_form());
}
ShowFormAction::~ShowFormAction() {}
void ShowFormAction::InternalProcessAction(ActionDelegate* delegate,
ProcessActionCallback callback) {
callback_ = std::move(callback);
// Show the form. This will call OnFormValuesChanged with the initial result,
// which will in turn show the "Continue" chip.
if (!delegate->SetForm(
std::make_unique<FormProto>(proto_.show_form().form()),
base::BindRepeating(&ShowFormAction::OnFormValuesChanged,
weak_ptr_factory_.GetWeakPtr(), delegate))) {
// The form contains unsupported or invalid inputs.
UpdateProcessedAction(UNSUPPORTED);
std::move(callback_).Run(std::move(processed_action_proto_));
return;
}
}
void ShowFormAction::OnFormValuesChanged(ActionDelegate* delegate,
const FormProto::Result* form_result) {
// Copy the current values to the action result.
*processed_action_proto_->mutable_form_result() = *form_result;
// Show "Continue" chip.
// TODO(crbug.com/806868): Make this chip configurable.
auto chips = std::make_unique<std::vector<Chip>>();
bool form_is_valid = IsFormValid(proto_.show_form().form(), *form_result);
if (proto_.show_form().has_chip()) {
chips->emplace_back(proto_.show_form().chip());
SetDefaultChipType(chips.get());
} else {
chips->emplace_back();
chips->back().text =
l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_PAYMENT_INFO_CONFIRM);
chips->back().type = HIGHLIGHTED_ACTION;
}
chips->back().disabled = !form_is_valid;
if (form_is_valid) {
chips->back().callback =
base::BindOnce(&ShowFormAction::OnButtonClicked,
weak_ptr_factory_.GetWeakPtr(), delegate);
}
delegate->Prompt(std::move(chips));
}
bool ShowFormAction::IsFormValid(const FormProto& form,
const FormProto::Result& result) {
// TODO(crbug.com/806868): Only check validity of inputs whose value changed
// instead of all inputs.
DCHECK_EQ(form.inputs_size(), result.input_results_size());
for (int i = 0; i < form.inputs_size(); i++) {
const FormInputProto& input = form.inputs(i);
const FormInputProto::Result& input_result = result.input_results(i);
switch (input.input_type_case()) {
case FormInputProto::InputTypeCase::kCounter:
DCHECK(input_result.has_counter());
if (!IsCounterInputValid(input.counter(), input_result.counter())) {
return false;
}
break;
case FormInputProto::InputTypeCase::kSelection:
DCHECK(input_result.has_selection());
if (!IsSelectionInputValid(input.selection(),
input_result.selection())) {
return false;
}
break;
case FormInputProto::InputTypeCase::INPUT_TYPE_NOT_SET:
NOTREACHED();
break;
// Intentionally no default case to make compilation fail if a new value
// was added to the enum but not to this list.
}
}
return true;
}
bool ShowFormAction::IsCounterInputValid(
const CounterInputProto& input,
const CounterInputProto::Result& result) {
DCHECK_EQ(input.counters_size(), result.values_size());
if (!input.has_validation_rule())
return true;
return IsCounterValidationRuleSatisfied(input.validation_rule(), input,
result);
}
bool ShowFormAction::IsCounterValidationRuleSatisfied(
const CounterInputProto::ValidationRule& rule,
const CounterInputProto& input,
const CounterInputProto::Result& result) {
switch (rule.rule_type_case()) {
case CounterInputProto::ValidationRule::RuleTypeCase::kBoolean: {
// Satisfied if the number of satisfied sub rules is within
// [min_satisfied_rules; max_satisfied_rules].
auto boolean_rule = rule.boolean();
int n = 0;
for (const CounterInputProto::ValidationRule& sub_rule :
boolean_rule.sub_rules()) {
if (IsCounterValidationRuleSatisfied(sub_rule, input, result)) {
n++;
}
}
return n >= boolean_rule.min_satisfied_rules() &&
n <= boolean_rule.max_satisfied_rules();
}
case CounterInputProto::ValidationRule::RuleTypeCase::kCounter: {
// Satisfied if the value of |counters[counter_index]| is within
// [min_value; max_value].
auto counter_rule = rule.counter();
int index = counter_rule.counter_index();
DCHECK_GE(index, 0);
DCHECK_LT(index, result.values_size());
int value = result.values(index);
return value >= counter_rule.min_value() &&
value <= counter_rule.max_value();
}
case CounterInputProto::ValidationRule::RuleTypeCase::kCountersSum: {
// Satisfied if the sum of all counters values is within [min_value;
// max_value].
auto counters_sum_rule = rule.counters_sum();
long sum = 0;
for (int value : result.values()) {
sum += value;
}
return sum >= counters_sum_rule.min_value() &&
sum <= counters_sum_rule.max_value();
}
case CounterInputProto::ValidationRule::RuleTypeCase::RULE_TYPE_NOT_SET:
// Unknown validation rule: suppose it is satisfied.
return true;
}
}
bool ShowFormAction::IsSelectionInputValid(
const SelectionInputProto& input,
const SelectionInputProto::Result& result) {
DCHECK_EQ(input.choices_size(), result.selected_size());
// A selection input is valid if the number of selected choices is
// greater or equal than |min_selected_choices|.
int min_selected = input.min_selected_choices();
if (min_selected == 0)
return true;
int n = 0;
for (bool selected : result.selected()) {
if (selected && ++n >= min_selected) {
return true;
}
}
return n >= min_selected;
}
void ShowFormAction::OnButtonClicked(ActionDelegate* delegate) {
DCHECK(callback_);
delegate->SetForm(nullptr, base::DoNothing());
UpdateProcessedAction(ACTION_APPLIED);
std::move(callback_).Run(std::move(processed_action_proto_));
}
} // namespace autofill_assistant