#include "chrome/renderer/actor/tool_executor.h"
#include <cstdint>
#include <memory>
#include "base/check.h"
#include "base/functional/callback.h"
#include "base/memory/ptr_util.h"
#include "base/notreached.h"
#include "base/time/time.h"
#include "chrome/common/actor.mojom.h"
#include "chrome/common/actor/action_result.h"
#include "chrome/common/actor/journal_details_builder.h"
#include "chrome/common/chrome_features.h"
#include "chrome/renderer/actor/click_tool.h"
#include "chrome/renderer/actor/drag_and_release_tool.h"
#include "chrome/renderer/actor/journal.h"
#include "chrome/renderer/actor/mouse_move_tool.h"
#include "chrome/renderer/actor/no_op_tool.h"
#include "chrome/renderer/actor/script_tool.h"
#include "chrome/renderer/actor/scroll_tool.h"
#include "chrome/renderer/actor/select_tool.h"
#include "chrome/renderer/actor/tool_utils.h"
#include "chrome/renderer/actor/type_tool.h"
#include "content/public/renderer/render_frame.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_node.h"
using blink::WebLocalFrame;
using content::RenderFrame;
namespace actor {
ToolExecutor::ToolExecutor(RenderFrame* frame, Journal& journal)
: frame_(*frame), journal_(journal) {
CHECK(base::FeatureList::IsEnabled(features::kGlicActor));
}
ToolExecutor::~ToolExecutor() {
if (completion_callback_) {
std::move(completion_callback_)
.Run(MakeResult(mojom::ActionResultCode::kExecutorDestroyed,
false,
"The tool executor was destroyed before invocation "
"could complete."));
}
}
void ToolExecutor::InvokeTool(mojom::ToolInvocationPtr invocation,
ToolExecutorCallback callback) {
if (tool_) {
std::move(callback).Run(
MakeResult(mojom::ActionResultCode::kExecutorBusy,
false,
"Another tool invocation is still running."));
return;
}
CHECK(!completion_callback_);
completion_callback_ = std::move(callback);
invoke_journal_entry_ =
journal_->CreatePendingAsyncEntry(invocation->task_id, "InvokeTool", {});
WebLocalFrame* web_frame = frame_->GetWebFrame();
CHECK(!web_frame || web_frame->LocalRoot() == web_frame);
if (!web_frame || !web_frame->FrameWidget()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&ToolExecutor::OnCompletion,
weak_ptr_factory_.GetWeakPtr(),
MakeResult(mojom::ActionResultCode::kFrameWentAway)));
return;
}
switch (invocation->action->which()) {
case actor::mojom::ToolAction::Tag::kClick: {
tool_ = std::make_unique<ClickTool>(
frame_.get(), invocation->task_id, journal_.get(),
std::move(invocation->action->get_click()),
std::move(invocation->target),
std::move(invocation->observed_target));
break;
}
case actor::mojom::ToolAction::Tag::kMouseMove: {
tool_ = std::make_unique<MouseMoveTool>(
frame_.get(), invocation->task_id, journal_.get(),
std::move(invocation->action->get_mouse_move()),
std::move(invocation->target),
std::move(invocation->observed_target));
break;
}
case actor::mojom::ToolAction::Tag::kType: {
tool_ = std::make_unique<TypeTool>(
frame_.get(), invocation->task_id, journal_.get(),
std::move(invocation->action->get_type()),
std::move(invocation->target),
std::move(invocation->observed_target));
break;
}
case actor::mojom::ToolAction::Tag::kScroll: {
tool_ = std::make_unique<ScrollTool>(
frame_.get(), invocation->task_id, journal_.get(),
std::move(invocation->action->get_scroll()),
std::move(invocation->target),
std::move(invocation->observed_target));
break;
}
case actor::mojom::ToolAction::Tag::kSelect: {
tool_ = std::make_unique<SelectTool>(
frame_.get(), invocation->task_id, journal_.get(),
std::move(invocation->action->get_select()),
std::move(invocation->target),
std::move(invocation->observed_target));
break;
}
case actor::mojom::ToolAction::Tag::kDragAndRelease: {
tool_ = std::make_unique<DragAndReleaseTool>(
frame_.get(), invocation->task_id, journal_.get(),
std::move(invocation->action->get_drag_and_release()),
std::move(invocation->target),
std::move(invocation->observed_target));
break;
}
case actor::mojom::ToolAction::Tag::kScriptTool: {
tool_ = std::make_unique<ScriptTool>(
frame_.get(), invocation->task_id, journal_.get(),
std::move(invocation->target), std::move(invocation->observed_target),
std::move(invocation->action->get_script_tool()));
break;
}
case actor::mojom::ToolAction::Tag::kScrollTo: {
tool_ = std::make_unique<NoOpTool>(
frame_.get(), invocation->task_id, journal_.get(),
std::move(invocation->target),
std::move(invocation->observed_target));
break;
}
default:
NOTREACHED();
}
if (tool_->EnsureTargetInView()) {
performed_scroll_into_view_ = true;
}
execute_journal_entry_ = journal_->CreatePendingAsyncEntry(
invocation->task_id, "ExecuteTool",
JournalDetailsBuilder().Add("tool", tool_->DebugString()).Build());
tool_->Execute(base::BindOnce(&ToolExecutor::ToolFinished,
weak_ptr_factory_.GetWeakPtr()));
}
void ToolExecutor::CancelTool(const actor::TaskId& task_id) {
journal_->Log(
task_id, "ToolExecutor::CancelTool",
JournalDetailsBuilder().Add("tool_already_finished", !tool_).Build());
weak_ptr_factory_.InvalidateWeakPtrs();
if (!tool_) {
CHECK(!completion_callback_);
return;
}
CHECK_EQ(tool_->task_id(), task_id);
tool_->Cancel();
ToolFinished(MakeResult(mojom::ActionResultCode::kInvokeCanceled));
}
void ToolExecutor::ToolFinished(mojom::ActionResultPtr result) {
execute_journal_entry_.reset();
result->execution_end_time = base::TimeTicks::Now();
result->requires_page_stabilization |= performed_scroll_into_view_;
OnCompletion(std::move(result));
}
void ToolExecutor::OnCompletion(mojom::ActionResultPtr result) {
CHECK(completion_callback_);
CHECK(tool_);
tool_.reset();
invoke_journal_entry_.reset();
std::move(completion_callback_).Run(std::move(result));
}
}