blob: ebee2f9b3b56b1afda703832e4671f4d596c3ec8 [file]
// 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 <sstream>
#include <string>
#include <tuple>
#include "base/strings/strcat.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/test/test_timeouts.h"
#include "chrome/browser/actor/actor_features.h"
#include "chrome/browser/actor/actor_test_util.h"
#include "chrome/browser/actor/tools/tool_request.h"
#include "chrome/browser/actor/tools/tools_test_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/actor.mojom.h"
#include "chrome/common/chrome_features.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/download_test_observer.h"
#include "pdf/buildflags.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/point_conversions.h"
#if BUILDFLAG(ENABLE_PDF)
#include "components/pdf/browser/pdf_document_helper.h"
#include "pdf/pdf_features.h"
#endif // BUILDFLAG(ENABLE_PDF)
using base::test::TestFuture;
using content::ChildFrameAt;
using content::EvalJs;
using content::ExecJs;
using content::GetDOMNodeId;
using content::NavigateIframeToURL;
using content::RenderFrameHost;
namespace actor {
namespace {
#if BUILDFLAG(ENABLE_PDF) && !BUILDFLAG(IS_CHROMEOS)
void CheckForConditionAndWaitMoreIfNeeded(
base::RepeatingCallback<bool()> condition,
base::OnceClosure quit_closure) {
if (condition.Run()) {
std::move(quit_closure).Run();
return;
}
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&CheckForConditionAndWaitMoreIfNeeded,
std::move(condition), std::move(quit_closure)),
TestTimeouts::tiny_timeout());
}
// Wait until |condition| returns true.
void WaitForCondition(base::RepeatingCallback<bool()> condition,
const std::string& description) {
base::RunLoop run_loop;
CheckForConditionAndWaitMoreIfNeeded(condition, run_loop.QuitClosure());
run_loop.Run();
ASSERT_TRUE(condition.Run())
<< "Timeout waiting for condition: " << description;
}
#endif
// This observer detects when WebContents receives notification of a user
// gesture having occurred, following a user input event targeted to
// a RenderWidgetHost under that WebContents.
class UserInteractionObserver : public content::WebContentsObserver {
public:
explicit UserInteractionObserver(content::WebContents* web_contents)
: WebContentsObserver(web_contents) {}
UserInteractionObserver(const UserInteractionObserver&) = delete;
UserInteractionObserver& operator=(const UserInteractionObserver&) = delete;
~UserInteractionObserver() override {}
// Retrieve the flag. There is no need to wait on a loop since
// DidGetUserInteraction() should be called synchronously with the input
// event processing in the browser process.
bool WasUserInteractionReceived() { return user_interaction_received_; }
void Reset() { user_interaction_received_ = false; }
private:
// WebContentsObserver
void DidGetUserInteraction(const blink::WebInputEvent& event) override {
user_interaction_received_ = true;
}
bool user_interaction_received_ = false;
};
class ActorClickToolBrowserTest : public ActorToolsTest {
public:
ActorClickToolBrowserTest() {
feature_list_.InitAndEnableFeatureWithParameters(
::features::kGlicActor,
{{features::kGlicActorClickDelay.name, "200ms"},
{features::kGlicActorPolicyControlExemption.name, "true"}});
}
~ActorClickToolBrowserTest() override = default;
void SetUpOnMainThread() override {
ActorToolsTest::SetUpOnMainThread();
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(embedded_https_test_server().Start());
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Basic test to ensure sending a click to an element works.
IN_PROC_BROWSER_TEST_F(ActorClickToolBrowserTest, ClickTool_SentToElement) {
const GURL url =
embedded_test_server()->GetURL("/actor/page_with_clickable_element.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
// Send a click to the document body.
{
std::optional<int> body_id = GetDOMNodeId(*main_frame(), "body");
ASSERT_TRUE(body_id);
std::unique_ptr<ToolRequest> action =
MakeClickRequest(*main_frame(), body_id.value());
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
EXPECT_THAT(
EvalJs(web_contents(), "mouse_event_log.join(',')").ExtractString(),
testing::EndsWith(
"mousemove[BODY#],mousedown[BODY#],mouseup[BODY#],click[BODY#]"));
}
ASSERT_TRUE(ExecJs(web_contents(), "mouse_event_log = []"));
// Send a second click to the button.
{
std::optional<int> button_id =
GetDOMNodeId(*main_frame(), "button#clickable");
ASSERT_TRUE(button_id);
std::unique_ptr<ToolRequest> action =
MakeClickRequest(*main_frame(), button_id.value());
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
EXPECT_THAT(
EvalJs(web_contents(), "mouse_event_log.join(',')").ExtractString(),
testing::EndsWith(
"mousemove[BUTTON#clickable],mousedown[BUTTON#clickable],"
"mouseup[BUTTON#clickable],click[BUTTON#clickable]"));
// Ensure the button's event handler was invoked.
EXPECT_EQ(true, EvalJs(web_contents(), "button_clicked"));
}
}
// Sending a click to an element that doesn't exist fails.
IN_PROC_BROWSER_TEST_F(ActorClickToolBrowserTest,
ClickTool_NonExistentElement) {
const GURL url =
embedded_test_server()->GetURL("/actor/page_with_clickable_element.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
// Use a random node id that doesn't exist.
std::unique_ptr<ToolRequest> action =
MakeClickRequest(*main_frame(), kNonExistentContentNodeId);
ActResultFuture result_fail;
actor_task().Act(ToRequestList(action), result_fail.GetCallback());
// The node id doesn't exist so the tool will return false.
ExpectErrorResult(result_fail, mojom::ActionResultCode::kInvalidDomNodeId);
// The page should not have received any click events.
EXPECT_THAT(
EvalJs(web_contents(), "mouse_event_log.join(',')").ExtractString(),
testing::Not(testing::HasSubstr("mousedown")));
}
// Sending a click to a disabled element should fail without dispatching events.
IN_PROC_BROWSER_TEST_F(ActorClickToolBrowserTest, ClickTool_DisabledElement) {
const GURL url =
embedded_test_server()->GetURL("/actor/page_with_clickable_element.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
std::optional<int> button_id = GetDOMNodeId(*main_frame(), "button#disabled");
ASSERT_TRUE(button_id);
std::unique_ptr<ToolRequest> action =
MakeClickRequest(*main_frame(), button_id.value());
ActResultFuture result_fail;
actor_task().Act(ToRequestList(action), result_fail.GetCallback());
ExpectErrorResult(result_fail, mojom::ActionResultCode::kElementDisabled);
// The page should not have received any click events.
EXPECT_THAT(
EvalJs(web_contents(), "mouse_event_log.join(',')").ExtractString(),
testing::Not(testing::HasSubstr("mousedown")));
}
// Sending a click to an element that's not in the viewport should cause it to
// first be scrolled into view then clicked.
IN_PROC_BROWSER_TEST_F(ActorClickToolBrowserTest, ClickTool_OffscreenElement) {
const GURL url =
embedded_test_server()->GetURL("/actor/page_with_clickable_element.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
// Page starts unscrolled
ASSERT_EQ(0, EvalJs(web_contents(), "window.scrollY"));
std::optional<int> button_id =
GetDOMNodeId(*main_frame(), "button#offscreen");
ASSERT_TRUE(button_id);
std::unique_ptr<ToolRequest> action =
MakeClickRequest(*main_frame(), button_id.value());
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
// Page is now scrolled.
ASSERT_GT(EvalJs(web_contents(), "window.scrollY"), 0);
// The page should not have received any events.
EXPECT_THAT(
EvalJs(web_contents(), "mouse_event_log.join(',')").ExtractString(),
testing::EndsWith(
"mousemove[BUTTON#offscreen],mousedown[BUTTON#offscreen],"
"mouseup[BUTTON#offscreen],click[BUTTON#offscreen]"));
}
// Sending a click to an element that's not in the viewport should cause it to
// first be scrolled into view then clicked.
IN_PROC_BROWSER_TEST_F(ActorClickToolBrowserTest,
ClickTool_OffscreenHiddenElement) {
const GURL url = embedded_test_server()->GetURL("/actor/oov_elements.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
for (const char* selector :
{"#detailButton", "#hiddenButton", "#autoButton"}) {
SCOPED_TRACE(selector);
// Starts unscrolled
ASSERT_TRUE(EvalJs(web_contents(), "window.scroll(0,0)").is_ok());
std::optional<int> button_id = GetDOMNodeId(*main_frame(), selector);
ASSERT_TRUE(button_id);
std::unique_ptr<ToolRequest> action =
MakeClickRequest(*main_frame(), button_id.value());
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
// Page is now scrolled.
EXPECT_GT(EvalJs(web_contents(), "window.scrollY"), 0);
}
}
// Ensure clicks can be sent to elements that are only partially onscreen.
IN_PROC_BROWSER_TEST_F(ActorClickToolBrowserTest, ClickTool_ClippedElements) {
const GURL url =
embedded_test_server()->GetURL("/actor/click_with_overflow_clip.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
std::vector<std::string> test_cases = {
"offscreenButton", "overflowHiddenButton", "overflowScrollButton"};
for (auto button : test_cases) {
SCOPED_TRACE(testing::Message() << "WHILE TESTING: " << button);
std::optional<int> button_id =
GetDOMNodeId(*main_frame(), base::StrCat({"#", button}));
ASSERT_TRUE(button_id);
std::unique_ptr<ToolRequest> action =
MakeClickRequest(*main_frame(), button_id.value());
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
EXPECT_EQ(button, EvalJs(web_contents(), "clicked_button"));
ASSERT_TRUE(ExecJs(web_contents(), "clicked_button = ''"));
}
}
// Ensure clicks can be sent to a coordinate onscreen.
IN_PROC_BROWSER_TEST_F(ActorClickToolBrowserTest, ClickTool_SentToCoordinate) {
const GURL url =
embedded_test_server()->GetURL("/actor/page_with_clickable_element.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
// Send a click to a (0,0) coordinate inside the document.
{
std::unique_ptr<ToolRequest> action =
MakeClickRequest(*active_tab(), gfx::Point(0, 0));
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
EXPECT_THAT(
EvalJs(web_contents(), "mouse_event_log.join(',')").ExtractString(),
testing::EndsWith(
"mousemove[HTML#],mousedown[HTML#],mouseup[HTML#],click[HTML#]"));
}
ASSERT_TRUE(ExecJs(web_contents(), "mouse_event_log = []"));
// Send a second click to a coordinate on the button.
{
gfx::Point click_point = gfx::ToFlooredPoint(
GetCenterCoordinatesOfElementWithId(web_contents(), "clickable"));
std::unique_ptr<ToolRequest> action =
MakeClickRequest(*active_tab(), click_point);
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
EXPECT_THAT(
EvalJs(web_contents(), "mouse_event_log.join(',')").ExtractString(),
testing::EndsWith(
"mousemove[BUTTON#clickable],mousedown[BUTTON#clickable],"
"mouseup[BUTTON#clickable],click[BUTTON#clickable]"));
// Ensure the button's event handler was invoked.
EXPECT_EQ(true, EvalJs(web_contents(), "button_clicked"));
}
}
// Sending a click to a coordinate not in the viewport should fail without
// dispatching events.
IN_PROC_BROWSER_TEST_F(ActorClickToolBrowserTest,
ClickTool_SentToCoordinateOffScreen) {
const GURL url =
embedded_test_server()->GetURL("/actor/page_with_clickable_element.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
// Send a click to a negative coordinate offscreen.
{
gfx::Point negative_offscreen = {-1, 0};
std::unique_ptr<ToolRequest> action =
MakeClickRequest(*active_tab(), negative_offscreen);
ActResultFuture result_fail;
actor_task().Act(ToRequestList(action), result_fail.GetCallback());
ExpectErrorResult(result_fail,
mojom::ActionResultCode::kCoordinatesOutOfBounds);
// The page should not have received any click events.
EXPECT_THAT(
EvalJs(web_contents(), "mouse_event_log.join(',')").ExtractString(),
testing::Not(testing::HasSubstr("mousedown")));
}
// Send a click to a positive coordinate offscreen.
{
gfx::Point positive_offscreen = gfx::ToFlooredPoint(
GetCenterCoordinatesOfElementWithId(web_contents(), "offscreen"));
std::unique_ptr<ToolRequest> action =
MakeClickRequest(*active_tab(), positive_offscreen);
ActResultFuture result_fail;
actor_task().Act(ToRequestList(action), result_fail.GetCallback());
ExpectErrorResult(result_fail,
mojom::ActionResultCode::kCoordinatesOutOfBounds);
// The page should not have received any click events.
EXPECT_THAT(
EvalJs(web_contents(), "mouse_event_log.join(',')").ExtractString(),
testing::Not(testing::HasSubstr("mousedown")));
}
}
// Ensure click is using viewport coordinate.
IN_PROC_BROWSER_TEST_F(ActorClickToolBrowserTest,
ClickTool_ViewportCoordinate) {
const GURL url =
embedded_test_server()->GetURL("/actor/page_with_clickable_element.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
// Scroll the window by 100vh so #offscreen button is in viewport.
ASSERT_TRUE(ExecJs(web_contents(), "window.scrollBy(0, window.innerHeight)"));
// Send a click to button's viewport coordinate.
{
gfx::Point click_point = gfx::ToFlooredPoint(
GetCenterCoordinatesOfElementWithId(web_contents(), "offscreen"));
std::unique_ptr<ToolRequest> action =
MakeClickRequest(*active_tab(), click_point);
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
EXPECT_THAT(
EvalJs(web_contents(), "mouse_event_log.join(',')").ExtractString(),
testing::EndsWith(
"mousemove[BUTTON#offscreen],mousedown[BUTTON#offscreen],"
"mouseup[BUTTON#offscreen],click[BUTTON#offscreen]"));
// Ensure the button's event handler was invoked.
EXPECT_EQ(true, EvalJs(web_contents(), "offscreen_button_clicked"));
}
}
// Ensure click works correctly when clicking on a cross process iframe using a
// DomNodeId
IN_PROC_BROWSER_TEST_F(ActorClickToolBrowserTest,
ClickTool_Subframe_DomNodeId) {
// This test only applies if cross-origin frames are put into separate
// processes.
if (!content::AreAllSitesIsolatedForTesting()) {
GTEST_SKIP();
}
const GURL url = embedded_https_test_server().GetURL(
"foo.com", "/actor/positioned_iframe.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
const GURL subframe_url = embedded_https_test_server().GetURL(
"bar.com", "/actor/page_with_clickable_element.html");
ASSERT_TRUE(NavigateIframeToURL(web_contents(), "iframe", subframe_url));
RenderFrameHost* subframe =
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0);
ASSERT_TRUE(subframe);
ASSERT_TRUE(subframe->IsCrossProcessSubframe());
// Send a click to the button in the subframe.
std::optional<int> button_id = GetDOMNodeId(*subframe, "button#clickable");
ASSERT_TRUE(button_id);
std::unique_ptr<ToolRequest> action =
MakeClickRequest(*subframe, button_id.value());
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
// Ensure the button's event handler was invoked.
EXPECT_EQ(true, EvalJs(subframe, "button_clicked"));
}
// Ensure that page tools (click is arbitrary here) correctly add the acted on
// tab to the task's tab set.
IN_PROC_BROWSER_TEST_F(ActorClickToolBrowserTest,
ClickTool_RecordActingOnTask) {
ASSERT_TRUE(actor_task().GetTabs().empty());
// Send a click to the document body.
std::optional<int> body_id = GetDOMNodeId(*main_frame(), "body");
ASSERT_TRUE(body_id);
std::unique_ptr<ToolRequest> action =
MakeClickRequest(*main_frame(), body_id.value());
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
EXPECT_TRUE(actor_task().GetTabs().contains(active_tab()->GetHandle()));
}
IN_PROC_BROWSER_TEST_F(ActorClickToolBrowserTest, ClickTool_Delay) {
const GURL url =
embedded_test_server()->GetURL("/actor/page_with_clickable_element.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
std::optional<int> body_id = GetDOMNodeId(*main_frame(), "body");
ASSERT_TRUE(body_id);
std::unique_ptr<ToolRequest> action =
MakeClickRequest(*main_frame(), body_id.value());
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
const double mousedown_timestamp = EvalJs(main_frame(), R"(
let index = mouse_event_log.findIndex(
(entry) => entry.startsWith('mousedown'));
mouse_event_timestamps[index]
)")
.ExtractDouble();
const double mouseup_timestamp = EvalJs(main_frame(), R"(
let index = mouse_event_log.findIndex(
(entry) => entry.startsWith('mouseup'));
mouse_event_timestamps[index]
)")
.ExtractDouble();
const base::TimeDelta delta =
base::Milliseconds(mouseup_timestamp - mousedown_timestamp);
EXPECT_GE(delta, features::kGlicActorClickDelay.Get());
}
IN_PROC_BROWSER_TEST_F(ActorClickToolBrowserTest, UserInteractionTriggered) {
const GURL start_url =
embedded_https_test_server().GetURL("example.com", "/actor/blank.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), start_url));
UserInteractionObserver observer(web_contents());
std::unique_ptr<ToolRequest> action =
MakeClickRequest(*active_tab(), gfx::Point(1, 1));
ASSERT_FALSE(observer.WasUserInteractionReceived());
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
ASSERT_TRUE(observer.WasUserInteractionReceived());
}
// Test that we can dispatch a click to a checkbox that's entirely overlaid by
// a pseudo element in its associated label.
IN_PROC_BROWSER_TEST_F(ActorClickToolBrowserTest, CheckboxOverlayedByPseudo) {
const GURL start_url = embedded_https_test_server().GetURL(
"example.com", "/actor/page_with_clickable_element.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), start_url));
std::optional<int> checkbox_id =
GetDOMNodeId(*main_frame(), "#checkboxPseudo");
ASSERT_TRUE(checkbox_id);
std::unique_ptr<ToolRequest> action =
MakeClickRequest(*main_frame(), checkbox_id.value());
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
}
class ActorClickToolScaledBrowserTest : public ActorToolsTest {
public:
ActorClickToolScaledBrowserTest() = default;
~ActorClickToolScaledBrowserTest() override = default;
void SetUpOnMainThread() override {
ActorToolsTest::SetUpOnMainThread();
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(embedded_https_test_server().Start());
}
void SetUpCommandLine(base::CommandLine* command_line) override {
ActorToolsTest::SetUpCommandLine(command_line);
command_line->RemoveSwitch(switches::kForceDeviceScaleFactor);
command_line->AppendSwitchASCII(switches::kForceDeviceScaleFactor, "2");
}
};
// Ensure clicks can be sent to elements that are only partially onscreen with
// scaling.
IN_PROC_BROWSER_TEST_F(ActorClickToolScaledBrowserTest,
ClickTool_ScaledClippedElements) {
const GURL url =
embedded_test_server()->GetURL("/actor/click_with_overflow_clip.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
std::vector<std::string> test_cases = {
"offscreenButton", "overflowHiddenButton", "overflowScrollButton"};
for (auto button : test_cases) {
SCOPED_TRACE(testing::Message() << "WHILE TESTING: " << button);
std::optional<int> button_id =
GetDOMNodeId(*main_frame(), base::StrCat({"#", button}));
ASSERT_TRUE(button_id);
std::unique_ptr<ToolRequest> action =
MakeClickRequest(*main_frame(), button_id.value());
ActResultFuture result;
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
EXPECT_EQ(button, EvalJs(web_contents(), "clicked_button"));
ASSERT_TRUE(ExecJs(web_contents(), "clicked_button = ''"));
}
}
#if BUILDFLAG(ENABLE_PDF) && !BUILDFLAG(IS_CHROMEOS)
class ActorClickToolPDFBrowserTest
: public ActorToolsTest,
public ::testing::WithParamInterface<bool> {
public:
ActorClickToolPDFBrowserTest() {
if (BypassTOUValidationForGuestView()) {
feature_list_.InitWithFeatures({kActorBypassTOUValidationForGuestView},
{chrome_pdf::features::kPdfOopif});
} else {
feature_list_.InitWithFeatures({},
{chrome_pdf::features::kPdfOopif,
kActorBypassTOUValidationForGuestView});
}
}
~ActorClickToolPDFBrowserTest() override = default;
bool BypassTOUValidationForGuestView() { return GetParam(); }
void SetUpOnMainThread() override {
ActorToolsTest::SetUpOnMainThread();
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(embedded_https_test_server().Start());
}
static std::string DescribeParams(
const testing::TestParamInfo<ParamType>& info) {
return info.param ? "BypassGuestViewTOU" : "CheckGuestViewTOU";
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Ensure clicks can rotate on a PDF.
// TODO(crbug.com/485814156): Re-enable the test.
#if BUILDFLAG(IS_LINUX)
#define MAYBE_Click DISABLED_Click
#else
#define MAYBE_Click Click
#endif
IN_PROC_BROWSER_TEST_P(ActorClickToolPDFBrowserTest, MAYBE_Click) {
const GURL url = embedded_test_server()->GetURL("/pdf/test.pdf");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
WaitForCondition(
base::BindLambdaForTesting([this]() {
auto* pdf_helper =
pdf::PDFDocumentHelper::MaybeGetForWebContents(web_contents());
if (!pdf_helper) {
return false;
}
return pdf_helper->IsDocumentLoadComplete() &&
web_contents()->IsDocumentOnLoadCompletedInPrimaryMainFrame();
}),
"PDF Loaded");
while (true) {
GetPageApc();
std::unique_ptr<ToolRequest> action =
MakeClickRequest(*active_tab(), gfx::Point(650, 25));
ActResultFuture future;
actor_task().Act(ToRequestList(action), future.GetCallback());
if (BypassTOUValidationForGuestView()) {
// This should always pass the first time.
ExpectOkResult(future);
break;
} else {
// Sometimes it might be allowed, but it will fail eventually. Keep
// looping until we fail.
const auto& action_results = future.Get();
ASSERT_EQ(action_results.size(), 1u);
const auto& result = *action_results[0].result;
if (result.code ==
mojom::ActionResultCode::kFrameLocationChangedSinceObservation) {
break;
}
}
}
}
INSTANTIATE_TEST_SUITE_P(,
ActorClickToolPDFBrowserTest,
::testing::Bool(),
&ActorClickToolPDFBrowserTest::DescribeParams);
#endif // BUILDFLAG(ENABLE_PDF) && !BUILDFLAG(IS_CHROMEOS)
} // namespace
} // namespace actor