blob: c6bda89922ed7124a1b41e424248a5fac77e6ffa [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/autofill_assistant/browser/actions/show_generic_ui_action.h"
#include <utility>
#include "base/containers/flat_map.h"
#include "base/optional.h"
#include "components/autofill_assistant/browser/actions/action_delegate.h"
#include "components/autofill_assistant/browser/client_status.h"
#include "components/autofill_assistant/browser/user_data_util.h"
#include "components/autofill_assistant/browser/user_model.h"
#include "components/autofill_assistant/browser/web/element.h"
#include "content/public/browser/web_contents.h"
namespace autofill_assistant {
namespace {
void WriteCreditCardsToUserModel(
std::unique_ptr<std::vector<std::unique_ptr<autofill::CreditCard>>>
credit_cards,
const ShowGenericUiProto::RequestAutofillCreditCards& proto,
UserModel* user_model) {
DCHECK(credit_cards);
DCHECK(user_model);
ValueProto model_value;
model_value.set_is_client_side_only(true);
for (const auto& credit_card : *credit_cards) {
DCHECK(!credit_card->guid().empty());
model_value.mutable_credit_cards()->add_values()->set_guid(
credit_card->guid());
}
user_model->SetAutofillCreditCards(std::move(credit_cards));
user_model->SetValue(proto.model_identifier(), model_value);
}
void WriteProfilesToUserModel(
std::unique_ptr<std::vector<std::unique_ptr<autofill::AutofillProfile>>>
profiles,
const ShowGenericUiProto::RequestAutofillProfiles& proto,
UserModel* user_model) {
DCHECK(profiles);
DCHECK(user_model);
ValueProto model_value;
model_value.set_is_client_side_only(true);
for (const auto& profile : *profiles) {
DCHECK(!profile->guid().empty());
model_value.mutable_profiles()->add_values()->set_guid(profile->guid());
}
user_model->SetAutofillProfiles(std::move(profiles));
user_model->SetValue(proto.model_identifier(), model_value);
}
void WriteLoginOptionsToUserModel(
const ShowGenericUiProto::RequestLoginOptions& proto,
UserModel* user_model,
std::vector<WebsiteLoginManager::Login> logins) {
DCHECK(user_model);
ValueProto model_value;
model_value.set_is_client_side_only(true);
for (const auto& login_option : proto.login_options()) {
switch (login_option.type_case()) {
case ShowGenericUiProto::RequestLoginOptions::LoginOption::
kCustomLoginOption:
*model_value.mutable_login_options()->add_values() =
login_option.custom_login_option();
break;
case ShowGenericUiProto::RequestLoginOptions::LoginOption::
kPasswordManagerLogins: {
for (const auto& login : logins) {
auto* option = model_value.mutable_login_options()->add_values();
option->set_label(login.username);
option->set_sublabel(
login_option.password_manager_logins().sublabel());
option->set_payload(login_option.password_manager_logins().payload());
}
break;
}
case ShowGenericUiProto::RequestLoginOptions::LoginOption::TYPE_NOT_SET:
NOTREACHED();
break;
}
}
user_model->SetValue(proto.model_identifier(), model_value);
}
} // namespace
void ShowGenericUiAction::OnInterruptStarted() {
delegate_->GetPersonalDataManager()->RemoveObserver(this);
delegate_->ClearGenericUi();
}
void ShowGenericUiAction::OnInterruptFinished() {
delegate_->SetGenericUi(
std::make_unique<GenericUserInterfaceProto>(
proto_.show_generic_ui().generic_user_interface()),
base::BindOnce(&ShowGenericUiAction::OnEndActionInteraction,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&ShowGenericUiAction::OnViewInflationFinished,
weak_ptr_factory_.GetWeakPtr(), false));
}
ShowGenericUiAction::ShowGenericUiAction(ActionDelegate* delegate,
const ActionProto& proto)
: Action(delegate, proto) {
DCHECK(proto_.has_show_generic_ui());
}
ShowGenericUiAction::~ShowGenericUiAction() {
delegate_->GetPersonalDataManager()->RemoveObserver(this);
}
bool ShowGenericUiAction::ShouldInterruptOnPause() const {
return true;
}
void ShowGenericUiAction::InternalProcessAction(
ProcessActionCallback callback) {
callback_ = std::move(callback);
// Check that |output_model_identifiers| is a subset of input model.
UserModel temp_model;
temp_model.MergeWithProto(
proto_.show_generic_ui().generic_user_interface().model(),
/* force_notifications = */ false);
if (!temp_model.GetValues(proto_.show_generic_ui().output_model_identifiers())
.has_value()) {
EndAction(ClientStatus(INVALID_ACTION));
return;
}
for (const auto& element_check :
proto_.show_generic_ui().periodic_element_checks().element_checks()) {
if (element_check.model_identifier().empty()) {
VLOG(1) << "Invalid action: ElementCheck with empty model_identifier";
EndAction(ClientStatus(INVALID_ACTION));
return;
}
}
for (const auto& additional_value :
proto_.show_generic_ui().request_user_data().additional_values()) {
if (!delegate_->GetUserData()->has_additional_value(
additional_value.source_identifier())) {
EndAction(ClientStatus(PRECONDITION_FAILED));
return;
}
}
for (const auto& additional_value :
proto_.show_generic_ui().request_user_data().additional_values()) {
ValueProto value = *delegate_->GetUserData()->additional_value(
additional_value.source_identifier());
value.set_is_client_side_only(true);
delegate_->GetUserModel()->SetValue(additional_value.model_identifier(),
value);
}
if (proto_.show_generic_ui().has_request_login_options()) {
auto login_options =
proto_.show_generic_ui().request_login_options().login_options();
if (std::find_if(login_options.begin(), login_options.end(),
[&](const auto& option) {
return option.type_case() ==
ShowGenericUiProto::RequestLoginOptions::
LoginOption::kPasswordManagerLogins;
}) != login_options.end()) {
delegate_->GetWebsiteLoginManager()->GetLoginsForUrl(
delegate_->GetWebContents()->GetLastCommittedURL(),
base::BindOnce(&WriteLoginOptionsToUserModel,
proto_.show_generic_ui().request_login_options(),
delegate_->GetUserModel()));
} else {
WriteLoginOptionsToUserModel(
proto_.show_generic_ui().request_login_options(),
delegate_->GetUserModel(),
/* logins = */ std::vector<WebsiteLoginManager::Login>());
}
}
base::OnceCallback<void()> end_on_navigation_callback;
if (proto_.show_generic_ui().end_on_navigation()) {
end_on_navigation_callback =
base::BindOnce(&ShowGenericUiAction::OnNavigationEnded,
weak_ptr_factory_.GetWeakPtr());
}
delegate_->Prompt(/* user_actions = */ nullptr,
/* disable_force_expand_sheet = */ false,
std::move(end_on_navigation_callback));
delegate_->SetGenericUi(
std::make_unique<GenericUserInterfaceProto>(
proto_.show_generic_ui().generic_user_interface()),
base::BindOnce(&ShowGenericUiAction::OnEndActionInteraction,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&ShowGenericUiAction::OnViewInflationFinished,
weak_ptr_factory_.GetWeakPtr(),
/* first_inflation= */ true));
}
void ShowGenericUiAction::OnViewInflationFinished(bool first_inflation,
const ClientStatus& status) {
if (!status.ok()) {
EndAction(status);
return;
}
delegate_->GetPersonalDataManager()->AddObserver(this);
OnPersonalDataChanged();
if (!first_inflation) {
return;
}
for (const auto& element_check :
proto_.show_generic_ui().periodic_element_checks().element_checks()) {
preconditions_.emplace_back(std::make_unique<ElementPrecondition>(
element_check.element_condition()));
}
if (proto_.show_generic_ui().allow_interrupt() ||
std::any_of(
preconditions_.begin(), preconditions_.end(),
[&](const auto& precondition) { return !precondition->empty(); })) {
has_pending_wait_for_dom_ = true;
delegate_->WaitForDom(
base::TimeDelta::Max(), proto_.show_generic_ui().allow_interrupt(),
this,
base::BindRepeating(&ShowGenericUiAction::RegisterChecks,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&ShowGenericUiAction::OnWaitForElementTimed,
weak_ptr_factory_.GetWeakPtr(),
base::BindOnce(&ShowGenericUiAction::OnDoneWaitForDom,
weak_ptr_factory_.GetWeakPtr())));
}
wait_time_start_ = base::TimeTicks::Now();
}
void ShowGenericUiAction::OnNavigationEnded() {
action_stopwatch_.TransferToWaitTime(base::TimeTicks::Now() -
wait_time_start_);
processed_action_proto_->mutable_show_generic_ui_result()
->set_navigation_ended(true);
OnEndActionInteraction(ClientStatus(ACTION_APPLIED));
}
void ShowGenericUiAction::RegisterChecks(
BatchElementChecker* checker,
base::OnceCallback<void(const ClientStatus&)> wait_for_dom_callback) {
if (!callback_) {
// Action is done; checks aren't necessary anymore.
std::move(wait_for_dom_callback).Run(OkClientStatus());
return;
}
for (size_t i = 0; i < preconditions_.size(); i++) {
preconditions_[i]->Check(
checker, base::BindOnce(&ShowGenericUiAction::OnPreconditionResult,
weak_ptr_factory_.GetWeakPtr(), i));
}
// Let WaitForDom know we're still waiting for elements.
checker->AddAllDoneCallback(base::BindOnce(
&ShowGenericUiAction::OnElementChecksDone, weak_ptr_factory_.GetWeakPtr(),
std::move(wait_for_dom_callback)));
}
void ShowGenericUiAction::OnPreconditionResult(
size_t precondition_index,
const ClientStatus& status,
const std::vector<std::string>& ignored_payloads,
const base::flat_map<std::string, DomObjectFrameStack>& ignored_elements) {
if (should_end_action_) {
return;
}
delegate_->GetUserModel()->SetValue(proto_.show_generic_ui()
.periodic_element_checks()
.element_checks(precondition_index)
.model_identifier(),
SimpleValue(status.ok()));
}
void ShowGenericUiAction::OnElementChecksDone(
base::OnceCallback<void(const ClientStatus&)> wait_for_dom_callback) {
// Calling wait_for_dom_callback with successful status is a way of asking the
// WaitForDom to end gracefully and call OnDoneWaitForDom with the status.
// Note that it is possible for WaitForDom to decide not to call
// OnDoneWaitForDom, if an interrupt triggers at the same time, so we cannot
// cancel the prompt and choose the suggestion just yet.
if (should_end_action_) {
std::move(wait_for_dom_callback).Run(OkClientStatus());
return;
}
// Let WaitForDom know we're still waiting for an element.
std::move(wait_for_dom_callback).Run(ClientStatus(ELEMENT_RESOLUTION_FAILED));
}
void ShowGenericUiAction::OnDoneWaitForDom(const ClientStatus& status) {
if (!callback_) {
return;
}
EndAction(status);
}
void ShowGenericUiAction::OnEndActionInteraction(const ClientStatus& status) {
// If WaitForDom was called, we end the action the next time the callback
// is called in order to end WaitForDom gracefully.
if (has_pending_wait_for_dom_) {
should_end_action_ = true;
return;
}
action_stopwatch_.TransferToWaitTime(base::TimeTicks::Now() -
wait_time_start_);
EndAction(status);
}
void ShowGenericUiAction::EndAction(const ClientStatus& status) {
if (!callback_) {
// Avoid race condition: it is possible that a breaking navigation event
// occurs immediately before or after the action would end naturally.
return;
}
delegate_->ClearGenericUi();
delegate_->CleanUpAfterPrompt();
UpdateProcessedAction(status);
if (status.ok()) {
const auto& output_model_identifiers =
proto_.show_generic_ui().output_model_identifiers();
auto values =
delegate_->GetUserModel()->GetValues(output_model_identifiers);
// This should always be the case since there is no way to erase a value
// from the model.
DCHECK(values.has_value());
auto* output_model =
processed_action_proto_->mutable_show_generic_ui_result()
->mutable_model();
for (size_t i = 0; i < values->size(); ++i) {
auto* output_value = output_model->add_values();
output_value->set_identifier(output_model_identifiers.at(i));
if (!values->at(i).is_client_side_only()) {
*output_value->mutable_value() = values->at(i);
}
}
}
std::move(callback_).Run(std::move(processed_action_proto_));
}
void ShowGenericUiAction::OnPersonalDataChanged() {
if (proto_.show_generic_ui().has_request_profiles()) {
auto profiles = std::make_unique<
std::vector<std::unique_ptr<autofill::AutofillProfile>>>();
for (const auto* profile :
delegate_->GetPersonalDataManager()->GetProfilesToSuggest()) {
profiles->emplace_back(MakeUniqueFromProfile(*profile));
}
WriteProfilesToUserModel(std::move(profiles),
proto_.show_generic_ui().request_profiles(),
delegate_->GetUserModel());
}
if (proto_.show_generic_ui().has_request_credit_cards()) {
auto credit_cards =
std::make_unique<std::vector<std::unique_ptr<autofill::CreditCard>>>();
for (const auto* credit_card :
delegate_->GetPersonalDataManager()->GetCreditCardsToSuggest(true)) {
credit_cards->emplace_back(
std::make_unique<autofill::CreditCard>(*credit_card));
}
WriteCreditCardsToUserModel(std::move(credit_cards),
proto_.show_generic_ui().request_credit_cards(),
delegate_->GetUserModel());
}
}
} // namespace autofill_assistant