blob: d51b5cfa90182083061ec1793aa0fbd1b5d509bd [file] [log] [blame]
// Copyright 2022 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/js_flow_action.h"
#include "base/base64.h"
#include "base/json/json_writer.h"
#include "base/metrics/field_trial.h"
#include "components/autofill_assistant/browser/actions/action_delegate.h"
#include "components/autofill_assistant/browser/js_flow_executor_impl.h"
#include "components/autofill_assistant/browser/js_flow_util.h"
#include "components/autofill_assistant/browser/protocol_utils.h"
namespace autofill_assistant {
namespace {
// When starting a JS flow action, a synthetic field trial is recorded. This is
// used to allow tracking stability metrics as we start using this new action.
// Note there is no control group - this is purely for stability tracking.
const char kJsFlowActionSyntheticFieldTrialName[] =
"AutofillAssistantJsFlowAction";
const char kJsFlowActionEnabledGroup[] = "Enabled";
} // namespace
JsFlowAction::JsFlowAction(ActionDelegate* delegate, const ActionProto& proto)
: Action(delegate, proto),
js_flow_executor_(std::make_unique<JsFlowExecutorImpl>(
delegate->GetWebContents()->GetBrowserContext(),
this)) {
DCHECK(proto_.has_js_flow());
}
JsFlowAction::JsFlowAction(ActionDelegate* delegate,
const ActionProto& proto,
std::unique_ptr<JsFlowExecutor> js_flow_executor)
: Action(delegate, proto), js_flow_executor_(std::move(js_flow_executor)) {
DCHECK(proto_.has_js_flow());
}
JsFlowAction::~JsFlowAction() = default;
Action::ActionData& JsFlowAction::GetActionData() {
if (!current_native_action_) {
return Action::GetActionData();
}
return current_native_action_->GetActionData();
}
void JsFlowAction::RunNativeAction(
int action_id,
const std::string& action,
base::OnceCallback<void(const ClientStatus& result_status,
std::unique_ptr<base::Value> result_value)>
finished_callback) {
DCHECK(!current_native_action_) << "Must not call RunNativeAction while "
"already executing a native action";
std::string error_message;
absl::optional<ActionProto> action_proto =
ProtocolUtils::ParseFromString(action_id, action, &error_message);
if (!action_proto) {
VLOG(1) << error_message;
std::move(finished_callback).Run(ClientStatus(INVALID_ACTION), nullptr);
return;
}
if (action_proto->action_info_case() ==
ActionProto::ActionInfoCase::kJsFlow) {
LOG(ERROR) << "Nested JS flow actions are not allowed!";
std::move(finished_callback).Run(ClientStatus(INVALID_ACTION), nullptr);
return;
}
current_native_action_ =
ProtocolUtils::CreateAction(delegate_, *action_proto);
VLOG(2) << "Running native action: " << action_proto->action_info_case();
current_native_action_->ProcessAction(base::BindOnce(
&JsFlowAction::OnNativeActionFinished, weak_ptr_factory_.GetWeakPtr(),
std::move(finished_callback)));
}
void JsFlowAction::OnNativeActionFinished(
base::OnceCallback<void(const ClientStatus& result_status,
std::unique_ptr<base::Value> result_value)>
finished_callback,
std::unique_ptr<ProcessedActionProto> processed_action) {
VLOG(2) << "Native action finished with status "
<< processed_action->status();
current_native_action_.reset();
std::move(finished_callback)
.Run(ClientStatus(processed_action->status(),
processed_action->status_details()),
js_flow_util::NativeActionResultToResultValue(*processed_action));
}
void JsFlowAction::InternalProcessAction(ProcessActionCallback callback) {
base::FieldTrialList::CreateFieldTrial(kJsFlowActionSyntheticFieldTrialName,
kJsFlowActionEnabledGroup);
js_flow_executor_->Start(
proto_.js_flow().js_flow(),
base::BindOnce(&JsFlowAction::OnFlowFinished,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void JsFlowAction::OnFlowFinished(ProcessActionCallback callback,
const ClientStatus& status,
std::unique_ptr<base::Value> return_value) {
// Since we can not know in advance how many native actions need to be run
// we will create a dangling promise. By destroying the flow executor we make
// sure that these will not be executed.
js_flow_executor_.reset(nullptr);
UpdateProcessedAction(status);
// If the flow returned a value, we extract the status and possibly a flow
// return value from that, and the overall action result will be whatever the
// flow returned. Flows that don't return a value are assumed to have
// succeeded. See js_flow_util::ExtractJsFlowActionReturnValue for details.
if (return_value) {
std::unique_ptr<base::Value> out_return_value;
UpdateProcessedAction(js_flow_util::ExtractJsFlowActionReturnValue(
*return_value, out_return_value));
if (out_return_value) {
base::JSONWriter::Write(*out_return_value,
processed_action_proto_->mutable_js_flow_result()
->mutable_result_json());
}
}
// Since JS flows have the potential to be quite big, we remove them from the
// action response. The backend has access to the full script anyway.
processed_action_proto_->mutable_action()->mutable_js_flow()->clear_js_flow();
std::move(callback).Run(std::move(processed_action_proto_));
}
} // namespace autofill_assistant