blob: 950a06e1a46da5766255f90f72f77436b4cecf3c [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 "base/test/test_future.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/common/actor.mojom.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
using base::test::TestFuture;
using content::ChildFrameAt;
using content::ExecJs;
using content::RenderFrameHost;
using content::TestNavigationManager;
using content::WaitForCopyableViewInWebContents;
using content::WaitForDOMContentLoaded;
namespace actor {
namespace {
class ActorHistoryToolBrowserTest : public ActorToolsGeneralPageStabilityTest {
public:
ActorHistoryToolBrowserTest() = default;
~ActorHistoryToolBrowserTest() override = default;
void SetUpOnMainThread() override {
ActorToolsTest::SetUpOnMainThread();
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(embedded_https_test_server().Start());
}
};
INSTANTIATE_TEST_SUITE_P(
,
ActorHistoryToolBrowserTest,
testing::ValuesIn(kActorGeneralPageStabilityModeValues),
ActorToolsGeneralPageStabilityTest::DescribeParam);
// TODO(crbug.com/415385900): Add a test for navigation API canceling a
// same-document navigation.
// Basic test of the HistoryTool going back.
IN_PROC_BROWSER_TEST_P(ActorHistoryToolBrowserTest, HistoryTool_Back) {
const GURL url_first =
embedded_test_server()->GetURL("/actor/blank.html?start");
const GURL url_second =
embedded_test_server()->GetURL("/actor/blank.html?target");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_first));
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_second));
ActResultFuture result_success;
std::unique_ptr<ToolRequest> action = MakeHistoryBackRequest(*active_tab());
actor_task().Act(ToRequestList(action), result_success.GetCallback());
ExpectOkResult(result_success);
EXPECT_EQ(web_contents()->GetURL(), url_first);
}
// Basic test of the HistoryTool going forward
IN_PROC_BROWSER_TEST_P(ActorHistoryToolBrowserTest, HistoryTool_Forward) {
const GURL url_first =
embedded_test_server()->GetURL("/actor/blank.html?start");
const GURL url_second =
embedded_test_server()->GetURL("/actor/blank.html?target");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_first));
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_second));
GoBack();
ASSERT_EQ(web_contents()->GetURL(), url_first);
ActResultFuture result_success;
std::unique_ptr<ToolRequest> action =
MakeHistoryForwardRequest(*active_tab());
actor_task().Act(ToRequestList(action), result_success.GetCallback());
ExpectOkResult(result_success);
EXPECT_EQ(web_contents()->GetURL(), url_second);
}
// Basic test will, under normal circumstances use BFCache. Ensure coverage
// without BFCache as well.
IN_PROC_BROWSER_TEST_P(ActorHistoryToolBrowserTest, HistoryTool_BackNoBFCache) {
content::DisableBackForwardCacheForTesting(
web_contents(), content::BackForwardCache::DisableForTestingReason::
TEST_REQUIRES_NO_CACHING);
const GURL url_first =
embedded_test_server()->GetURL("/actor/blank.html?start");
const GURL url_second =
embedded_test_server()->GetURL("/actor/blank.html?target");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_first));
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_second));
ActResultFuture result_success;
std::unique_ptr<ToolRequest> action = MakeHistoryBackRequest(*active_tab());
actor_task().Act(ToRequestList(action), result_success.GetCallback());
ExpectOkResult(result_success);
EXPECT_EQ(web_contents()->GetURL(), url_first);
}
// Test that tool fails validation if there's no further session history in the
// direction of travel.
IN_PROC_BROWSER_TEST_P(ActorHistoryToolBrowserTest,
HistoryTool_FailNoSessionHistory) {
const GURL url_first =
embedded_test_server()->GetURL("/actor/blank.html?first");
const GURL url_second =
embedded_test_server()->GetURL("/actor/blank.html?second");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_first));
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_second));
// Attempting a forward history navigation should fail since we're at the
// latest entry.
{
ActResultFuture result;
std::unique_ptr<ToolRequest> action =
MakeHistoryForwardRequest(*active_tab());
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectErrorResult(result,
mojom::ActionResultCode::kHistoryNoForwardEntries);
EXPECT_EQ(web_contents()->GetURL(), url_second);
}
// Prune all earlier entries so we can't go back.
web_contents()->GetController().PruneAllButLastCommitted();
ASSERT_FALSE(web_contents()->GetController().CanGoBack());
// Attempting a back history navigation should fail since we're at the first
// entry.
{
ActResultFuture result;
std::unique_ptr<ToolRequest> action = MakeHistoryBackRequest(*active_tab());
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectErrorResult(result, mojom::ActionResultCode::kHistoryNoBackEntries);
EXPECT_EQ(web_contents()->GetURL(), url_second);
}
}
// Test history tool across same document navigations
IN_PROC_BROWSER_TEST_P(ActorHistoryToolBrowserTest,
HistoryTool_BackSameDocument) {
const GURL url_first = embedded_test_server()->GetURL("/actor/blank.html");
const GURL url_second =
embedded_test_server()->GetURL("/actor/blank.html#foo");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_first));
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_second));
{
ActResultFuture result;
std::unique_ptr<ToolRequest> action = MakeHistoryBackRequest(*active_tab());
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
EXPECT_EQ(web_contents()->GetURL(), url_first);
}
{
ActResultFuture result;
std::unique_ptr<ToolRequest> action =
MakeHistoryForwardRequest(*active_tab());
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
EXPECT_EQ(web_contents()->GetURL(), url_second);
}
}
// Test history tool across same document navigations
IN_PROC_BROWSER_TEST_P(ActorHistoryToolBrowserTest,
HistoryTool_BasicIframeBack) {
const GURL main_frame_url =
embedded_test_server()->GetURL("/actor/simple_iframe.html");
const GURL child_frame_url_1 =
embedded_test_server()->GetURL("/actor/blank.html");
const GURL child_frame_url_2 =
embedded_test_server()->GetURL("/actor/blank.html?next");
ASSERT_TRUE(content::NavigateToURL(web_contents(), main_frame_url));
EXPECT_TRUE(WaitForLoadStop(web_contents()));
// Navigate the child frame to a new document.
RenderFrameHost* child_frame =
content::ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0);
ASSERT_TRUE(child_frame);
ASSERT_EQ(child_frame->GetLastCommittedURL(), child_frame_url_1);
ASSERT_TRUE(
content::NavigateToURLFromRenderer(child_frame, child_frame_url_2));
child_frame = content::ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0);
ASSERT_EQ(child_frame->GetLastCommittedURL(), child_frame_url_2);
// Invoke the history back tool. The iframe should be navigated back.
ActResultFuture result;
std::unique_ptr<ToolRequest> action = MakeHistoryBackRequest(*active_tab());
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
child_frame = content::ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0);
EXPECT_EQ(child_frame->GetLastCommittedURL(), child_frame_url_1);
EXPECT_EQ(web_contents()->GetURL(), main_frame_url);
}
// Ensure the history tool doesn't return until the navigation completes.
IN_PROC_BROWSER_TEST_P(ActorHistoryToolBrowserTest, HistoryTool_SlowBack) {
content::DisableBackForwardCacheForTesting(
web_contents(), content::BackForwardCache::DisableForTestingReason::
TEST_REQUIRES_NO_CACHING);
const GURL url_first =
embedded_test_server()->GetURL("/actor/blank.html?start");
const GURL url_second =
embedded_test_server()->GetURL("/actor/blank.html?target");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_first));
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_second));
TestNavigationManager back_navigation(web_contents(), url_first);
ActResultFuture result_success;
std::unique_ptr<ToolRequest> action = MakeHistoryBackRequest(*active_tab());
actor_task().Act(ToRequestList(action), result_success.GetCallback());
ASSERT_TRUE(back_navigation.WaitForResponse());
EXPECT_FALSE(result_success.IsReady());
for (int i = 0; i < 3; ++i) {
TinyWait();
EXPECT_FALSE(result_success.IsReady());
}
ASSERT_TRUE(back_navigation.WaitForNavigationFinished());
ExpectOkResult(result_success);
}
// Test a case where history back causes navigation in two frames.
IN_PROC_BROWSER_TEST_P(ActorHistoryToolBrowserTest,
HistoryTool_ConcurrentNavigations) {
const GURL main_frame_url =
embedded_test_server()->GetURL("/actor/concurrent_navigations.html");
const GURL child_frame_1_start_url =
embedded_test_server()->GetURL("/actor/blank.html?A1");
const GURL child_frame_1_target_url =
embedded_test_server()->GetURL("/actor/blank.html?A2");
const GURL child_frame_2_start_url =
embedded_test_server()->GetURL("/actor/blank.html?B1");
const GURL child_frame_2_target_url =
embedded_test_server()->GetURL("/actor/blank.html?B2");
ASSERT_TRUE(content::NavigateToURL(web_contents(), main_frame_url));
EXPECT_TRUE(WaitForLoadStop(web_contents()));
// Navigate the child frame to a new document.
RenderFrameHost* child_frame_1 =
content::ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0);
RenderFrameHost* child_frame_2 =
content::ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 1);
ASSERT_TRUE(child_frame_1);
ASSERT_TRUE(child_frame_2);
ASSERT_EQ(child_frame_1->GetLastCommittedURL(), child_frame_1_start_url);
ASSERT_EQ(child_frame_2->GetLastCommittedURL(), child_frame_2_start_url);
ASSERT_TRUE(content::NavigateToURLFromRenderer(child_frame_1,
child_frame_1_target_url));
child_frame_1 =
content::ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0);
ASSERT_EQ(child_frame_1->GetLastCommittedURL(), child_frame_1_target_url);
// The first frame navigated to A2 so the session history looks like:
// [about:blank], [Main, A1, B1], [Main, A2, B1]
// Now navigate the second iframe but with replacement so we get:
// [about:blank], [Main, A1, B1], [Main, A2, B2]
TestNavigationManager replace_navigation(web_contents(),
child_frame_2_target_url);
ASSERT_TRUE(ExecJs(
child_frame_2,
content::JsReplace("location.replace($1);", child_frame_2_target_url)));
ASSERT_TRUE(replace_navigation.WaitForNavigationFinished());
child_frame_2 = ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 1);
ASSERT_EQ(child_frame_2->GetLastCommittedURL(), child_frame_2_target_url);
// Invoke the history back tool. Both should be navigated back to their
// starting URL.
ActResultFuture result;
std::unique_ptr<ToolRequest> action = MakeHistoryBackRequest(*active_tab());
actor_task().Act(ToRequestList(action), result.GetCallback());
ExpectOkResult(result);
child_frame_1 = ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0);
child_frame_2 = ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 1);
EXPECT_EQ(child_frame_1->GetLastCommittedURL(), child_frame_1_start_url);
EXPECT_EQ(child_frame_2->GetLastCommittedURL(), child_frame_2_start_url);
EXPECT_EQ(web_contents()->GetURL(), main_frame_url);
}
// Ensure the history tool works correctly when a before unload handler is
// present (but doesn't cause a prompt to show).
IN_PROC_BROWSER_TEST_P(ActorHistoryToolBrowserTest,
HistoryTool_HasBeforeUnload) {
const GURL url_first =
embedded_test_server()->GetURL("/actor/blank.html?start");
const GURL url_second =
embedded_test_server()->GetURL("/actor/blank.html?target");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_first));
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_second));
// Add a no-op beforeunload handler. This won't show the prompt but may force
// the browser to send an event to the renderer to confirm which can change
// the async path taken by the navigation.
ASSERT_TRUE(ExecJs(web_contents(),
R"JS(
addEventListener('beforeunload', () => {});
)JS"));
ActResultFuture result_success;
std::unique_ptr<ToolRequest> action = MakeHistoryBackRequest(*active_tab());
actor_task().Act(ToRequestList(action), result_success.GetCallback());
ExpectOkResult(result_success);
EXPECT_EQ(web_contents()->GetURL(), url_first);
}
// Ensure that when navigating to a new document, the history tool delays
// completion until the new page has fired the load event.
IN_PROC_BROWSER_TEST_P(ActorHistoryToolBrowserTest,
HistoryTool_DelaysUntilLoad) {
// Ensure BFCache isn't used so the back navigation loads a new document.
content::DisableBackForwardCacheForTesting(
web_contents(), content::BackForwardCache::DisableForTestingReason::
TEST_REQUIRES_NO_CACHING);
const GURL url_first =
embedded_test_server()->GetURL("/actor/simple_iframe.html?start");
const GURL url_second =
embedded_test_server()->GetURL("/actor/simple_iframe.html?target");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_first));
const GURL url_subframe =
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)
->GetLastCommittedURL();
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_second));
TestNavigationManager subframe_manager(web_contents(), url_subframe);
TestNavigationManager main_manager(web_contents(), url_first);
ActResultFuture result;
std::unique_ptr<ToolRequest> action = MakeHistoryBackRequest(*active_tab());
actor_task().Act(ToRequestList(action), result.GetCallback());
// Wait for the main frame navigation to finish and for the main document to
// reach DOMContentLoaded and for a frame to be presented.
ASSERT_TRUE(main_manager.WaitForNavigationFinished());
ASSERT_TRUE(WaitForDOMContentLoaded(main_frame()));
WaitForCopyableViewInWebContents(web_contents());
// Prevent the subframe response from being processed.
ASSERT_TRUE(subframe_manager.WaitForResponse());
EXPECT_FALSE(result.IsReady());
TinyWait();
EXPECT_FALSE(result.IsReady());
ASSERT_FALSE(web_contents()->IsDocumentOnLoadCompletedInPrimaryMainFrame());
// Unblocking the subframe response will allow the page to fire the load event
// and complete the tool request.
ASSERT_TRUE(subframe_manager.WaitForNavigationFinished());
ExpectOkResult(result);
}
// Test that the history tool correctly adds the acted on tab to the task's set
// of tabs.
IN_PROC_BROWSER_TEST_P(ActorHistoryToolBrowserTest,
HistoryTool_RecordActingOnTask) {
ASSERT_TRUE(actor_task().GetTabs().empty());
const GURL url = embedded_test_server()->GetURL("/actor/blank.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
ASSERT_TRUE(actor_task().GetTabs().empty());
ActResultFuture result_success;
std::unique_ptr<ToolRequest> action = MakeHistoryBackRequest(*active_tab());
actor_task().Act(ToRequestList(action), result_success.GetCallback());
ExpectOkResult(result_success);
EXPECT_EQ(actor_task().GetTabs().size(), 1ul);
EXPECT_TRUE(actor_task().GetTabs().contains(active_tab()->GetHandle()));
}
} // namespace
} // namespace actor