#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/threading/platform_thread.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/common/actor.mojom.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.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"
#include "net/base/net_errors.h"
#include "net/dns/mock_host_resolver.h"
#include "testing/gmock/include/gmock/gmock.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 {
constexpr char kDomainA[] = "a.test";
class ActorHistoryToolBrowserTest : public ActorToolsTest {
public:
ActorHistoryToolBrowserTest() {
scoped_feature_list_.InitWithFeatures(
{},
{kGlicCrossOriginNavigationGating});
}
~ActorHistoryToolBrowserTest() override = default;
void SetUpOnMainThread() override {
ActorToolsTest::SetUpOnMainThread();
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(embedded_https_test_server().Start());
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(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);
}
IN_PROC_BROWSER_TEST_F(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);
}
IN_PROC_BROWSER_TEST_F(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);
}
IN_PROC_BROWSER_TEST_F(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));
{
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);
}
web_contents()->GetController().PruneAllButLastCommitted();
ASSERT_FALSE(web_contents()->GetController().CanGoBack());
{
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);
}
}
IN_PROC_BROWSER_TEST_F(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);
}
}
IN_PROC_BROWSER_TEST_F(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()));
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);
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);
}
IN_PROC_BROWSER_TEST_F(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);
}
IN_PROC_BROWSER_TEST_F(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()));
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);
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);
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);
}
IN_PROC_BROWSER_TEST_F(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));
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);
}
IN_PROC_BROWSER_TEST_F(ActorHistoryToolBrowserTest, HistoryTool_BackFromPOST) {
content::DisableBackForwardCacheForTesting(
web_contents(), content::BackForwardCache::DisableForTestingReason::
TEST_REQUIRES_NO_CACHING);
const GURL url_a =
embedded_test_server()->GetURL(kDomainA, "/actor/history_post_form.html");
const GURL url_b = embedded_test_server()->GetURL(
kDomainA, "/actor/history_post_page_b.html");
const GURL url_c =
embedded_test_server()->GetURL(kDomainA, "/actor/blank.html?page_c");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_a));
ASSERT_EQ(web_contents()->GetURL(), url_a);
{
content::TestNavigationObserver nav_observer(web_contents(), 1);
ASSERT_TRUE(
ExecJs(web_contents(), "document.getElementById('submit').click();"));
nav_observer.Wait();
ASSERT_EQ(web_contents()->GetURL(), url_b);
ASSERT_TRUE(web_contents()
->GetController()
.GetLastCommittedEntry()
->GetHasPostData());
}
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_c));
ASSERT_EQ(web_contents()->GetURL(), url_c);
{
content::TestNavigationObserver back_nav_observer(web_contents(), 1);
ActResultFuture fut;
std::unique_ptr<ToolRequest> action = MakeHistoryBackRequest(*active_tab());
actor_task().Act(ToRequestList(action), fut.GetCallback());
back_nav_observer.Wait();
EXPECT_EQ(back_nav_observer.last_net_error_code(), net::ERR_CACHE_MISS);
const mojom::ActionResultPtr& result = std::get<0>(fut.Get());
EXPECT_EQ(result->code, mojom::ActionResultCode::kHistoryErrorPage);
EXPECT_THAT(result->message, testing::HasSubstr("ERR_CACHE_MISS"));
EXPECT_EQ(web_contents()->GetController().GetLastCommittedEntry()->GetURL(),
url_b);
EXPECT_EQ(web_contents()->GetURL(), url_b);
EXPECT_FALSE(web_contents()->GetController().GetPendingEntry());
}
ActResultFuture fut;
std::unique_ptr<ToolRequest> action = MakeHistoryBackRequest(*active_tab());
actor_task().Act(ToRequestList(action), fut.GetCallback());
ExpectOkResult(fut);
EXPECT_EQ(web_contents()->GetURL(), url_a);
}
IN_PROC_BROWSER_TEST_F(ActorHistoryToolBrowserTest,
HistoryTool_ForwardFromPOST) {
content::DisableBackForwardCacheForTesting(
web_contents(), content::BackForwardCache::DisableForTestingReason::
TEST_REQUIRES_NO_CACHING);
const GURL url_a =
embedded_test_server()->GetURL(kDomainA, "/actor/history_post_form.html");
const GURL url_b = embedded_test_server()->GetURL(
kDomainA, "/actor/history_post_page_b.html");
const GURL url_c =
embedded_test_server()->GetURL(kDomainA, "/actor/blank.html?page_c");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_a));
ASSERT_EQ(web_contents()->GetURL(), url_a);
{
content::TestNavigationObserver nav_observer(web_contents(), 1);
ASSERT_TRUE(
ExecJs(web_contents(), "document.getElementById('submit').click();"));
nav_observer.Wait();
ASSERT_EQ(web_contents()->GetURL(), url_b);
ASSERT_TRUE(web_contents()
->GetController()
.GetLastCommittedEntry()
->GetHasPostData());
}
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_c));
ASSERT_EQ(web_contents()->GetURL(), url_c);
{
content::TestNavigationObserver back_nav_observer(web_contents(), 1);
ActResultFuture fut;
std::unique_ptr<ToolRequest> action = MakeHistoryBackRequest(*active_tab());
actor_task().Act(ToRequestList(action), fut.GetCallback());
back_nav_observer.Wait();
EXPECT_EQ(back_nav_observer.last_net_error_code(), net::ERR_CACHE_MISS);
const mojom::ActionResultPtr& result = std::get<0>(fut.Get());
EXPECT_EQ(result->code, mojom::ActionResultCode::kHistoryErrorPage);
EXPECT_THAT(result->message, testing::HasSubstr("ERR_CACHE_MISS"));
EXPECT_EQ(web_contents()->GetController().GetLastCommittedEntry()->GetURL(),
url_b);
EXPECT_EQ(web_contents()->GetURL(), url_b);
EXPECT_FALSE(web_contents()->GetController().GetPendingEntry());
}
ActResultFuture fut;
std::unique_ptr<ToolRequest> action =
MakeHistoryForwardRequest(*active_tab());
actor_task().Act(ToRequestList(action), fut.GetCallback());
ExpectOkResult(fut);
EXPECT_EQ(web_contents()->GetURL(), url_c);
}
IN_PROC_BROWSER_TEST_F(ActorHistoryToolBrowserTest,
HistoryTool_NavigateFromPOST) {
content::DisableBackForwardCacheForTesting(
web_contents(), content::BackForwardCache::DisableForTestingReason::
TEST_REQUIRES_NO_CACHING);
const GURL url_a =
embedded_test_server()->GetURL(kDomainA, "/actor/history_post_form.html");
const GURL url_b = embedded_test_server()->GetURL(
kDomainA, "/actor/history_post_page_b.html");
const GURL url_c =
embedded_test_server()->GetURL(kDomainA, "/actor/blank.html?page_c");
const GURL url_d =
embedded_test_server()->GetURL(kDomainA, "/actor/blank.html?page_d");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_a));
ASSERT_EQ(web_contents()->GetURL(), url_a);
{
content::TestNavigationObserver nav_observer(web_contents(), 1);
ASSERT_TRUE(
ExecJs(web_contents(), "document.getElementById('submit').click();"));
nav_observer.Wait();
ASSERT_EQ(web_contents()->GetURL(), url_b);
ASSERT_TRUE(web_contents()
->GetController()
.GetLastCommittedEntry()
->GetHasPostData());
}
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_c));
ASSERT_EQ(web_contents()->GetURL(), url_c);
{
content::TestNavigationObserver back_nav_observer(web_contents(), 1);
ActResultFuture fut;
std::unique_ptr<ToolRequest> action = MakeHistoryBackRequest(*active_tab());
actor_task().Act(ToRequestList(action), fut.GetCallback());
back_nav_observer.Wait();
EXPECT_EQ(back_nav_observer.last_net_error_code(), net::ERR_CACHE_MISS);
const mojom::ActionResultPtr& result = std::get<0>(fut.Get());
EXPECT_EQ(result->code, mojom::ActionResultCode::kHistoryErrorPage);
EXPECT_THAT(result->message, testing::HasSubstr("ERR_CACHE_MISS"));
EXPECT_EQ(web_contents()->GetController().GetLastCommittedEntry()->GetURL(),
url_b);
EXPECT_EQ(web_contents()->GetURL(), url_b);
EXPECT_FALSE(web_contents()->GetController().GetPendingEntry());
}
ActResultFuture fut;
std::unique_ptr<ToolRequest> action =
MakeNavigateRequest(*active_tab(), url_d.spec());
actor_task().Act(ToRequestList(action), fut.GetCallback());
ExpectOkResult(fut);
EXPECT_EQ(web_contents()->GetURL(), url_d);
}
IN_PROC_BROWSER_TEST_F(ActorHistoryToolBrowserTest,
HistoryTool_DelaysUntilLoad) {
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());
ASSERT_TRUE(main_manager.WaitForNavigationFinished());
ASSERT_TRUE(WaitForDOMContentLoaded(main_frame()));
WaitForCopyableViewInWebContents(web_contents());
ASSERT_TRUE(subframe_manager.WaitForResponse());
EXPECT_FALSE(result.IsReady());
TinyWait();
EXPECT_FALSE(result.IsReady());
ASSERT_FALSE(web_contents()->IsDocumentOnLoadCompletedInPrimaryMainFrame());
ASSERT_TRUE(subframe_manager.WaitForNavigationFinished());
ExpectOkResult(result);
}
IN_PROC_BROWSER_TEST_F(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()));
}
IN_PROC_BROWSER_TEST_F(ActorHistoryToolBrowserTest,
HistoryTool_BackToBlockedUrlFailsValidation) {
const GURL url_a =
embedded_test_server()->GetURL(kDomainA, "/actor/blank.html?a");
const GURL url_blocked = embedded_test_server()->GetURL(
"blocked.example.com", "/actor/blank.html?blocked");
const GURL url_c =
embedded_test_server()->GetURL(kDomainA, "/actor/blank.html?c");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_a));
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_blocked));
ASSERT_TRUE(content::NavigateToURL(web_contents(), url_c));
ActResultFuture fut;
std::unique_ptr<ToolRequest> action = MakeHistoryBackRequest(*active_tab());
actor_task().Act(ToRequestList(action), fut.GetCallback());
ExpectErrorResult(fut, mojom::ActionResultCode::kUrlBlocked);
EXPECT_EQ(web_contents()->GetURL(), url_c);
}
}
}