| // 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 |