blob: f06fbdb63f637d84e69a3069b438d276b3854101 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/actor/actor_test_util.h"
#include <memory>
#include <string_view>
#include "base/base64.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/types/cxx23_to_underlying.h"
#include "base/values.h"
#include "chrome/browser/actor/execution_engine.h"
#include "chrome/browser/actor/shared_types.h"
#include "chrome/browser/actor/tools/attempt_login_tool_request.h"
#include "chrome/browser/actor/tools/click_tool_request.h"
#include "chrome/browser/actor/tools/drag_and_release_tool_request.h"
#include "chrome/browser/actor/tools/history_tool_request.h"
#include "chrome/browser/actor/tools/move_mouse_tool_request.h"
#include "chrome/browser/actor/tools/navigate_tool_request.h"
#include "chrome/browser/actor/tools/page_tool_request.h"
#include "chrome/browser/actor/tools/script_tool_request.h"
#include "chrome/browser/actor/tools/scroll_to_tool_request.h"
#include "chrome/browser/actor/tools/scroll_tool_request.h"
#include "chrome/browser/actor/tools/select_tool_request.h"
#include "chrome/browser/actor/tools/tab_management_tool_request.h"
#include "chrome/browser/actor/tools/type_tool_request.h"
#include "chrome/browser/actor/tools/wait_tool_request.h"
#include "chrome/common/actor.mojom.h"
#include "chrome/common/actor/action_result.h"
#include "chrome/common/actor/actor_constants.h"
#include "chrome/common/actor/task_id.h"
#include "components/optimization_guide/content/browser/page_content_proto_provider.h"
#include "components/optimization_guide/core/filters/bloom_filter.h"
#include "components/optimization_guide/core/optimization_guide_switches.h"
#include "components/optimization_guide/proto/features/actions_data.pb.h"
#include "components/optimization_guide/proto/hints.pb.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/window_open_disposition.h"
#include "ui/gfx/geometry/point.h"
namespace actor {
using ::content::RenderFrameHost;
using ::content::WebContents;
using ::optimization_guide::DocumentIdentifierUserData;
using ::optimization_guide::proto::Actions;
using ::optimization_guide::proto::ActivateWindowAction;
using ::optimization_guide::proto::ClickAction;
using ClickType = ::optimization_guide::proto::ClickAction::ClickType;
using ClickCount = ::optimization_guide::proto::ClickAction::ClickCount;
using ::optimization_guide::proto::CloseWindowAction;
using ::optimization_guide::proto::Coordinate;
using ::optimization_guide::proto::CreateTabAction;
using ::optimization_guide::proto::CreateWindowAction;
using ::optimization_guide::proto::DragAndReleaseAction;
using ::optimization_guide::proto::HistoryBackAction;
using ::optimization_guide::proto::HistoryForwardAction;
using ::optimization_guide::proto::MoveMouseAction;
using ::optimization_guide::proto::NavigateAction;
using ::optimization_guide::proto::ScrollAction;
using ::optimization_guide::proto::ScrollToAction;
using ::optimization_guide::proto::SelectAction;
using ::optimization_guide::proto::TypeAction;
using tabs::TabHandle;
using tabs::TabInterface;
namespace {
TabHandle GetTabHandleForFrame(content::RenderFrameHost& rfh) {
auto* tab = TabInterface::GetFromContents(
content::WebContents::FromRenderFrameHost(&rfh));
CHECK(tab);
return tab->GetHandle();
}
} // namespace
Actions MakeClick(RenderFrameHost& rfh,
int content_node_id,
ClickType click_type,
ClickCount click_count) {
Actions actions;
ClickAction* click = actions.add_actions()->mutable_click();
click->mutable_target()->set_content_node_id(content_node_id);
click->mutable_target()->mutable_document_identifier()->set_serialized_token(
*DocumentIdentifierUserData::GetDocumentIdentifier(
rfh.GetGlobalFrameToken()));
click->set_click_type(click_type);
click->set_click_count(click_count);
click->set_tab_id(GetTabHandleForFrame(rfh).raw_value());
return actions;
}
Actions MakeClick(TabHandle tab_handle,
const gfx::Point& click_point,
ClickType click_type,
ClickCount click_count) {
Actions actions;
ClickAction* click = actions.add_actions()->mutable_click();
Coordinate* coordinate = click->mutable_target()->mutable_coordinate();
coordinate->set_x(click_point.x());
coordinate->set_y(click_point.y());
click->set_click_type(click_type);
click->set_click_count(click_count);
click->set_tab_id(tab_handle.raw_value());
return actions;
}
Actions MakeHistoryBack(TabHandle tab_handle) {
Actions actions;
HistoryBackAction* back = actions.add_actions()->mutable_back();
back->set_tab_id(tab_handle.raw_value());
return actions;
}
Actions MakeHistoryForward(TabHandle tab_handle) {
Actions actions;
HistoryForwardAction* forward = actions.add_actions()->mutable_forward();
forward->set_tab_id(tab_handle.raw_value());
return actions;
}
Actions MakeMouseMove(RenderFrameHost& rfh, int content_node_id) {
Actions actions;
MoveMouseAction* move = actions.add_actions()->mutable_move_mouse();
move->mutable_target()->set_content_node_id(content_node_id);
move->mutable_target()->mutable_document_identifier()->set_serialized_token(
*DocumentIdentifierUserData::GetDocumentIdentifier(
rfh.GetGlobalFrameToken()));
move->set_tab_id(GetTabHandleForFrame(rfh).raw_value());
return actions;
}
Actions MakeMouseMove(TabHandle tab_handle, const gfx::Point& move_point) {
Actions actions;
MoveMouseAction* move = actions.add_actions()->mutable_move_mouse();
Coordinate* coordinate = move->mutable_target()->mutable_coordinate();
coordinate->set_x(move_point.x());
coordinate->set_y(move_point.y());
move->set_tab_id(tab_handle.raw_value());
return actions;
}
Actions MakeNavigate(tabs::TabHandle tab_handle, std::string_view target_url) {
Actions actions;
NavigateAction* navigate = actions.add_actions()->mutable_navigate();
navigate->mutable_url()->assign(target_url);
navigate->set_tab_id(tab_handle.raw_value());
return actions;
}
Actions MakeCreateTab(SessionID window_id, bool foreground) {
Actions actions;
CreateTabAction* create_tab = actions.add_actions()->mutable_create_tab();
create_tab->set_foreground(foreground);
create_tab->set_window_id(window_id.id());
return actions;
}
Actions MakeActivateWindow(SessionID window_id) {
Actions actions;
ActivateWindowAction* activate_window =
actions.add_actions()->mutable_activate_window();
activate_window->set_window_id(window_id.id());
return actions;
}
Actions MakeCreateWindow() {
Actions actions;
actions.add_actions()->mutable_create_window();
return actions;
}
Actions MakeCloseWindow(SessionID window_id) {
Actions actions;
CloseWindowAction* close_window =
actions.add_actions()->mutable_close_window();
close_window->set_window_id(window_id.id());
return actions;
}
Actions MakeType(RenderFrameHost& rfh,
int content_node_id,
std::string_view text,
bool follow_by_enter,
optimization_guide::proto::TypeAction::TypeMode mode) {
// TODO(crbug.com/417270084): TypeAction currently only supports the
// DELETE_EXISTING mode.
CHECK_EQ(mode, optimization_guide::proto::TypeAction::TypeMode::
TypeAction_TypeMode_DELETE_EXISTING);
Actions actions;
TypeAction* type_action = actions.add_actions()->mutable_type();
type_action->mutable_target()->set_content_node_id(content_node_id);
type_action->mutable_target()
->mutable_document_identifier()
->set_serialized_token(*DocumentIdentifierUserData::GetDocumentIdentifier(
rfh.GetGlobalFrameToken()));
type_action->set_text(text);
type_action->set_mode(mode);
type_action->set_follow_by_enter(follow_by_enter);
type_action->set_tab_id(GetTabHandleForFrame(rfh).raw_value());
return actions;
}
Actions MakeType(TabHandle tab_handle,
const gfx::Point& type_point,
std::string_view text,
bool follow_by_enter,
optimization_guide::proto::TypeAction::TypeMode mode) {
Actions actions;
TypeAction* type_action = actions.add_actions()->mutable_type();
Coordinate* coordinate = type_action->mutable_target()->mutable_coordinate();
coordinate->set_x(type_point.x());
coordinate->set_y(type_point.y());
type_action->set_text(text);
// TODO(crbug.com/409570203): Tests should set a mode.
// Currently uses DELETE_EXISTING behavior in all cases.
type_action->set_mode(mode);
type_action->set_follow_by_enter(follow_by_enter);
type_action->set_tab_id(tab_handle.raw_value());
return actions;
}
Actions MakeScroll(RenderFrameHost& rfh,
std::optional<int> content_node_id,
float scroll_offset_x,
float scroll_offset_y) {
CHECK(!scroll_offset_x || !scroll_offset_y)
<< "Scroll action supports only one axis at a time.";
Actions actions;
ScrollAction* scroll = actions.add_actions()->mutable_scroll();
auto* tab = TabInterface::GetFromContents(
content::WebContents::FromRenderFrameHost(&rfh));
scroll->set_tab_id(tab->GetHandle().raw_value());
if (content_node_id.has_value()) {
scroll->mutable_target()->set_content_node_id(content_node_id.value());
scroll->mutable_target()
->mutable_document_identifier()
->set_serialized_token(
*DocumentIdentifierUserData::GetDocumentIdentifier(
rfh.GetGlobalFrameToken()));
} else {
CHECK(rfh.IsInPrimaryMainFrame())
<< "Empty target is only used to scroll the main frame";
}
if (scroll_offset_x > 0) {
scroll->set_direction(ScrollAction::RIGHT);
scroll->set_distance(scroll_offset_x);
} else if (scroll_offset_x < 0) {
scroll->set_direction(ScrollAction::LEFT);
scroll->set_distance(-scroll_offset_x);
}
if (scroll_offset_y > 0) {
scroll->set_direction(ScrollAction::DOWN);
scroll->set_distance(scroll_offset_y);
} else if (scroll_offset_y < 0) {
scroll->set_direction(ScrollAction::UP);
scroll->set_distance(-scroll_offset_y);
}
return actions;
}
Actions MakeScrollTo(RenderFrameHost& rfh, int content_node_id) {
Actions actions;
ScrollToAction* scroll_to = actions.add_actions()->mutable_scroll_to();
auto* tab = TabInterface::GetFromContents(
content::WebContents::FromRenderFrameHost(&rfh));
scroll_to->set_tab_id(tab->GetHandle().raw_value());
scroll_to->mutable_target()->set_content_node_id(content_node_id);
scroll_to->mutable_target()
->mutable_document_identifier()
->set_serialized_token(*DocumentIdentifierUserData::GetDocumentIdentifier(
rfh.GetGlobalFrameToken()));
return actions;
}
Actions MakeSelect(RenderFrameHost& rfh,
int content_node_id,
std::string_view value) {
Actions actions;
SelectAction* select_action = actions.add_actions()->mutable_select();
select_action->mutable_target()->set_content_node_id(content_node_id);
select_action->mutable_target()
->mutable_document_identifier()
->set_serialized_token(*DocumentIdentifierUserData::GetDocumentIdentifier(
rfh.GetGlobalFrameToken()));
select_action->set_value(value);
select_action->set_tab_id(GetTabHandleForFrame(rfh).raw_value());
return actions;
}
Actions MakeDragAndRelease(tabs::TabHandle tab_handle,
const gfx::Point& from_point,
const gfx::Point& to_point) {
Actions actions;
DragAndReleaseAction* drag_and_release =
actions.add_actions()->mutable_drag_and_release();
drag_and_release->mutable_from_target()->mutable_coordinate()->set_x(
from_point.x());
drag_and_release->mutable_from_target()->mutable_coordinate()->set_y(
from_point.y());
drag_and_release->mutable_to_target()->mutable_coordinate()->set_x(
to_point.x());
drag_and_release->mutable_to_target()->mutable_coordinate()->set_y(
to_point.y());
drag_and_release->set_tab_id(tab_handle.raw_value());
return actions;
}
Actions MakeDragAndRelease(content::RenderFrameHost& rfh,
int from_node_id,
int to_node_id) {
Actions actions;
DragAndReleaseAction* drag_and_release =
actions.add_actions()->mutable_drag_and_release();
drag_and_release->mutable_from_target()->set_content_node_id(from_node_id);
drag_and_release->mutable_from_target()
->mutable_document_identifier()
->set_serialized_token(*DocumentIdentifierUserData::GetDocumentIdentifier(
rfh.GetGlobalFrameToken()));
drag_and_release->mutable_to_target()->set_content_node_id(to_node_id);
drag_and_release->mutable_to_target()
->mutable_document_identifier()
->set_serialized_token(*DocumentIdentifierUserData::GetDocumentIdentifier(
rfh.GetGlobalFrameToken()));
drag_and_release->set_tab_id(GetTabHandleForFrame(rfh).raw_value());
return actions;
}
Actions MakeWait() {
Actions actions;
actions.add_actions()->mutable_wait();
return actions;
}
Actions MakeAttemptLogin() {
Actions actions;
actions.add_actions()->mutable_attempt_login();
return actions;
}
Actions MakeScriptTool(content::RenderFrameHost& rfh,
const std::string& name,
const std::string& input_arguments) {
Actions action;
auto* script_tool = action.add_actions()->mutable_script_tool();
script_tool->mutable_document_identifier()->set_serialized_token(
*DocumentIdentifierUserData::GetDocumentIdentifier(
rfh.GetGlobalFrameToken()));
script_tool->set_tool_name(name);
script_tool->set_input_arguments(input_arguments);
script_tool->set_tab_id(GetTabHandleForFrame(rfh).raw_value());
return action;
}
PageTarget MakeTarget(content::RenderFrameHost& rfh, int content_node_id) {
std::string document_identifier =
*DocumentIdentifierUserData::GetDocumentIdentifier(
rfh.GetGlobalFrameToken());
return PageTarget(DomNode{.node_id = content_node_id,
.document_identifier = document_identifier});
}
PageTarget MakeTarget(const gfx::Point& point) {
return PageTarget(point);
}
std::unique_ptr<ToolRequest> MakeClickRequest(content::RenderFrameHost& rfh,
int content_node_id) {
return std::make_unique<ClickToolRequest>(
GetTabHandleForFrame(rfh), MakeTarget(rfh, content_node_id),
MouseClickType::kLeft, MouseClickCount::kSingle);
}
std::unique_ptr<ToolRequest> MakeClickRequest(TabInterface& tab,
const gfx::Point& click_point) {
return std::make_unique<ClickToolRequest>(
tab.GetHandle(), MakeTarget(click_point), MouseClickType::kLeft,
MouseClickCount::kSingle);
}
std::unique_ptr<ToolRequest> MakeHistoryBackRequest(TabInterface& tab) {
return std::make_unique<HistoryToolRequest>(
tab.GetHandle(), HistoryToolRequest::Direction::kBack);
}
std::unique_ptr<ToolRequest> MakeHistoryForwardRequest(TabInterface& tab) {
return std::make_unique<HistoryToolRequest>(
tab.GetHandle(), HistoryToolRequest::Direction::kForward);
}
std::unique_ptr<ToolRequest> MakeMouseMoveRequest(content::RenderFrameHost& rfh,
int content_node_id) {
return std::make_unique<MoveMouseToolRequest>(
GetTabHandleForFrame(rfh), MakeTarget(rfh, content_node_id));
}
std::unique_ptr<ToolRequest> MakeMouseMoveRequest(
TabInterface& tab,
const gfx::Point& move_point) {
return std::make_unique<MoveMouseToolRequest>(tab.GetHandle(),
MakeTarget(move_point));
}
std::unique_ptr<ToolRequest> MakeNavigateRequest(TabInterface& tab,
std::string_view target_url) {
return std::make_unique<NavigateToolRequest>(tab.GetHandle(),
GURL(target_url));
}
std::unique_ptr<ToolRequest> MakeTypeRequest(content::RenderFrameHost& rfh,
int content_node_id,
std::string_view text,
bool follow_by_enter) {
// TODO(crbug.com/409570203): Tests should set a mode.
return std::make_unique<TypeToolRequest>(
GetTabHandleForFrame(rfh), MakeTarget(rfh, content_node_id), text,
follow_by_enter, TypeToolRequest::Mode::kReplace);
}
std::unique_ptr<ToolRequest> MakeTypeRequest(TabInterface& tab,
const gfx::Point& type_point,
std::string_view text,
bool follow_by_enter) {
return std::make_unique<TypeToolRequest>(
tab.GetHandle(), MakeTarget(type_point), text, follow_by_enter,
TypeToolRequest::Mode::kReplace);
}
std::unique_ptr<ToolRequest> MakeSelectRequest(content::RenderFrameHost& rfh,
int content_node_id,
std::string_view value) {
return std::make_unique<SelectToolRequest>(
GetTabHandleForFrame(rfh), MakeTarget(rfh, content_node_id), value);
}
std::unique_ptr<ToolRequest> MakeScrollRequest(
content::RenderFrameHost& rfh,
std::optional<int> content_node_id,
float scroll_offset_x,
float scroll_offset_y) {
CHECK(scroll_offset_x == 0.f || scroll_offset_y == 0.f);
int node_id =
content_node_id.has_value() ? *content_node_id : kRootElementDomNodeId;
float distance = 0.f;
ScrollToolRequest::Direction direction = ScrollToolRequest::Direction::kDown;
if (scroll_offset_x > 0) {
direction = ScrollToolRequest::Direction::kRight;
distance = scroll_offset_x;
} else if (scroll_offset_x < 0) {
direction = ScrollToolRequest::Direction::kLeft;
distance = -scroll_offset_x;
}
if (scroll_offset_y > 0) {
direction = ScrollToolRequest::Direction::kDown;
distance = scroll_offset_y;
} else if (scroll_offset_y < 0) {
direction = ScrollToolRequest::Direction::kUp;
distance = -scroll_offset_y;
}
return std::make_unique<ScrollToolRequest>(
GetTabHandleForFrame(rfh), MakeTarget(rfh, node_id), direction, distance);
}
std::unique_ptr<ToolRequest> MakeScrollToRequest(content::RenderFrameHost& rfh,
int content_node_id) {
return std::make_unique<ScrollToToolRequest>(
GetTabHandleForFrame(rfh), MakeTarget(rfh, content_node_id));
}
std::unique_ptr<ToolRequest> MakeDragAndReleaseRequest(
TabInterface& tab,
const gfx::Point& from_point,
const gfx::Point& to_point) {
return std::make_unique<DragAndReleaseToolRequest>(
tab.GetHandle(), MakeTarget(from_point), MakeTarget(to_point));
}
std::unique_ptr<ToolRequest> MakeWaitRequest() {
// TODO(bokan): Move this the default in WaitToolRequest.
constexpr base::TimeDelta kWaitTime = base::Seconds(3);
return std::make_unique<WaitToolRequest>(kWaitTime);
}
std::unique_ptr<ToolRequest> MakeCreateTabRequest(SessionID window_id,
bool foreground) {
return std::make_unique<CreateTabToolRequest>(
window_id.id(), foreground ? WindowOpenDisposition::NEW_FOREGROUND_TAB
: WindowOpenDisposition::NEW_BACKGROUND_TAB);
}
std::unique_ptr<ToolRequest> MakeAttemptLoginRequest(TabInterface& tab) {
return std::make_unique<AttemptLoginToolRequest>(tab.GetHandle());
}
std::unique_ptr<ToolRequest> MakeScriptToolRequest(
content::RenderFrameHost& rfh,
const std::string& name,
const std::string& input_arguments) {
return std::make_unique<ScriptToolRequest>(
GetTabHandleForFrame(rfh), MakeTarget(rfh, kRootElementDomNodeId), name,
input_arguments);
}
std::vector<std::unique_ptr<ToolRequest>> ToRequestList(
std::unique_ptr<ToolRequest> request) {
std::vector<std::unique_ptr<ToolRequest>> vec;
vec.push_back(std::move(request));
return vec;
}
void ExpectOkResult(const mojom::ActionResult& result) {
EXPECT_TRUE(IsOk(result))
<< "Expected OK result, got " << ToDebugString(result);
}
void ExpectOkResult(base::test::TestFuture<mojom::ActionResultPtr>& future) {
const auto& result = *(future.Get<0>());
ExpectOkResult(result);
}
void ExpectOkResult(ActResultFuture& future) {
const auto& result = *(future.Get<0>());
ExpectOkResult(result);
}
void ExpectOkResult(PerformActionsFuture& future) {
const auto& result = future.Get<0>();
EXPECT_TRUE(IsOk(result)) << "Expected OK result, got " << result;
}
void ExpectErrorResult(ActResultFuture& future,
mojom::ActionResultCode expected_code) {
const auto& result = *(future.Get<0>());
EXPECT_EQ(result.code, expected_code)
<< "Expected error " << base::to_underlying(expected_code) << ", got "
<< ToDebugString(result);
}
void SetUpBlocklist(base::CommandLine* command_line,
const std::string& blocked_host) {
constexpr uint32_t kNumHashFunctions = 7;
constexpr uint32_t kNumBits = 511;
optimization_guide::BloomFilter blocklist_bloom_filter(kNumHashFunctions,
kNumBits);
blocklist_bloom_filter.Add(blocked_host);
std::string blocklist_bloom_filter_data(
reinterpret_cast<const char*>(&blocklist_bloom_filter.bytes()[0]),
blocklist_bloom_filter.bytes().size());
optimization_guide::proto::Configuration config;
optimization_guide::proto::OptimizationFilter* blocklist_optimization_filter =
config.add_optimization_blocklists();
blocklist_optimization_filter->set_optimization_type(
optimization_guide::proto::GLIC_ACTION_PAGE_BLOCK);
blocklist_optimization_filter->mutable_bloom_filter()->set_num_hash_functions(
kNumHashFunctions);
blocklist_optimization_filter->mutable_bloom_filter()->set_num_bits(kNumBits);
blocklist_optimization_filter->mutable_bloom_filter()->set_data(
blocklist_bloom_filter_data);
std::string encoded_config;
config.SerializeToString(&encoded_config);
encoded_config = base::Base64Encode(encoded_config);
command_line->AppendSwitchASCII(
optimization_guide::switches::kHintsProtoOverride, encoded_config);
}
} // namespace actor