#include "content/browser/back_forward_cache_browsertest.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/navigation_handle_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/url_loader_interceptor.h"
#include "content/shell/browser/shell.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
using testing::_;
using testing::Each;
using testing::ElementsAre;
using testing::Not;
using testing::UnorderedElementsAreArray;
namespace content {
namespace {
void InsertSubFrameWithUrl(RenderFrameHost* rfh, std::string url) {
std::string insert_script = base::StringPrintf(
R"(
const iframeElement = document.createElement("iframe");
iframeElement.src = "%s";
document.body.appendChild(iframeElement);
)",
url.c_str());
ASSERT_TRUE(ExecJs(rfh, insert_script));
}
}
using NotRestoredReason = BackForwardCacheMetrics::NotRestoredReason;
using NotRestoredReasons =
BackForwardCacheCanStoreDocumentResult::NotRestoredReasons;
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, Basic) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
url::Origin origin_a = url::Origin::Create(url_a);
url::Origin origin_b = url::Origin::Create(url_b);
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
EXPECT_TRUE(NavigateToURL(shell(), url_b));
RenderFrameHostImpl* rfh_b = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
EXPECT_EQ(rfh_a->GetVisibilityState(), PageVisibilityState::kHidden);
EXPECT_EQ(origin_a, rfh_a->GetLastCommittedOrigin());
EXPECT_EQ(origin_b, rfh_b->GetLastCommittedOrigin());
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
EXPECT_EQ(rfh_b->GetVisibilityState(), PageVisibilityState::kVisible);
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_FALSE(delete_observer_rfh_b.deleted());
EXPECT_EQ(origin_a, rfh_a->GetLastCommittedOrigin());
EXPECT_EQ(origin_b, rfh_b->GetLastCommittedOrigin());
EXPECT_EQ(rfh_a, current_frame_host());
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
EXPECT_EQ(rfh_a->GetVisibilityState(), PageVisibilityState::kVisible);
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
EXPECT_EQ(rfh_b->GetVisibilityState(), PageVisibilityState::kHidden);
ExpectRestored(FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, BasicDocumentInitiated) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
ASSERT_TRUE(NavigateToURLFromRenderer(shell(), url_b));
RenderFrameHostImpl* rfh_b = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
EXPECT_FALSE(rfh_a->GetSiteInstance()->IsRelatedSiteInstance(
rfh_b->GetSiteInstance()));
EXPECT_TRUE(ExecJs(shell(), "history.back();"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_FALSE(delete_observer_rfh_b.deleted());
EXPECT_EQ(rfh_a, current_frame_host());
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
ExpectRestored(FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
NavigateBackForwardRepeatedly) {
DoNotFailForUnexpectedMessagesWhileCached();
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
EXPECT_TRUE(NavigateToURL(shell(), url_b));
RenderFrameHostImpl* rfh_b = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_EQ(rfh_a, current_frame_host());
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
ExpectRestored(FROM_HERE);
ASSERT_TRUE(HistoryGoForward(web_contents()));
EXPECT_EQ(rfh_b, current_frame_host());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
ExpectRestored(FROM_HERE);
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_EQ(rfh_a, current_frame_host());
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
ExpectRestored(FROM_HERE);
ASSERT_TRUE(HistoryGoForward(web_contents()));
EXPECT_EQ(rfh_b, current_frame_host());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_FALSE(delete_observer_rfh_b.deleted());
ExpectRestored(FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, WindowOpen) {
if (!SiteIsolationPolicy::UseDedicatedProcessesForAllSites())
return;
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
ASSERT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImplWrapper rfh_a(current_frame_host());
EXPECT_EQ(1u, rfh_a->GetSiteInstance()->GetRelatedActiveContentsCount());
Shell* popup = OpenPopup(rfh_a.get(), url_a, "");
EXPECT_EQ(2u, rfh_a->GetSiteInstance()->GetRelatedActiveContentsCount());
rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this);
ASSERT_TRUE(NavigateToURLFromRenderer(rfh_a.get(), url_b));
ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
RenderFrameHostImplWrapper rfh_b(current_frame_host());
EXPECT_EQ(2u, rfh_b->GetSiteInstance()->GetRelatedActiveContentsCount());
ASSERT_TRUE(ExecJs(rfh_b.get(), "history.back();"));
ASSERT_TRUE(rfh_b.WaitUntilRenderFrameDeleted());
ExpectNotRestored({NotRestoredReason::kRelatedActiveContentsExist,
NotRestoredReason::kBrowsingInstanceNotSwapped},
{},
{ShouldSwapBrowsingInstance::kNo_HasRelatedActiveContents},
{}, {}, FROM_HERE);
EXPECT_THAT(
GetTreeResult()->GetDocumentResult(),
MatchesDocumentResult(
NotRestoredReasons(NotRestoredReason::kRelatedActiveContentsExist,
NotRestoredReason::kBrowsingInstanceNotSwapped),
BlockListedFeatures()));
RenderFrameHostImplWrapper rfh_a_new(current_frame_host());
EXPECT_EQ(2u, rfh_a_new->GetSiteInstance()->GetRelatedActiveContentsCount());
ASSERT_TRUE(NavigateToURL(popup, url_b));
EXPECT_EQ(1u, rfh_a_new->GetSiteInstance()->GetRelatedActiveContentsCount());
ASSERT_TRUE(NavigateToURLFromRenderer(rfh_a_new.get(), url_b));
EXPECT_FALSE(rfh_a_new.IsRenderFrameDeleted());
EXPECT_TRUE(rfh_a_new->IsInBackForwardCache());
RenderFrameHostImplWrapper rfh_b_new(current_frame_host());
ASSERT_TRUE(ExecJs(rfh_b_new.get(), "history.back();"));
ASSERT_TRUE(WaitForLoadStop(web_contents()));
EXPECT_FALSE(rfh_b_new.IsRenderFrameDeleted());
EXPECT_TRUE(rfh_b_new->IsInBackForwardCache());
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, WindowOpenThenClose) {
net::test_server::ControllableHttpResponse response(embedded_test_server(),
"/title2.html");
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL("a.test", "/title1.html"));
GURL url_b(embedded_test_server()->GetURL("b.test", "/title2.html"));
ASSERT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImplWrapper rfh_a(current_frame_host());
EXPECT_EQ(1u, rfh_a->GetSiteInstance()->GetRelatedActiveContentsCount());
Shell* popup = OpenPopup(rfh_a.get(), url_a, "");
EXPECT_EQ(2u, rfh_a->GetSiteInstance()->GetRelatedActiveContentsCount());
TestNavigationObserver observer(web_contents());
shell()->LoadURL(url_b);
response.WaitForRequest();
RenderFrameHostImplWrapper rfh_popup(
popup->web_contents()->GetPrimaryMainFrame());
ASSERT_TRUE(ExecJs(rfh_popup.get(), "window.close();"));
ASSERT_TRUE(rfh_popup.WaitUntilRenderFrameDeleted());
EXPECT_EQ(1u, rfh_a->GetSiteInstance()->GetRelatedActiveContentsCount());
response.Send(net::HTTP_OK, "text/html", "foo");
response.Done();
observer.Wait();
EXPECT_EQ(0u, rfh_a->GetSiteInstance()->GetRelatedActiveContentsCount());
ASSERT_TRUE(rfh_a->IsInBackForwardCache());
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_FALSE(rfh_a.IsRenderFrameDeleted());
ExpectRestored(FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, BasicIframe) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
EXPECT_TRUE(NavigateToURL(shell(), url_c));
RenderFrameHostImpl* rfh_c = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_c(rfh_c);
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_FALSE(delete_observer_rfh_b.deleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
EXPECT_FALSE(rfh_c->IsInBackForwardCache());
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_FALSE(delete_observer_rfh_b.deleted());
EXPECT_FALSE(delete_observer_rfh_c.deleted());
EXPECT_EQ(rfh_a, current_frame_host());
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
EXPECT_TRUE(rfh_c->IsInBackForwardCache());
ExpectRestored(FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, SubframeSurviveCache1) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html"));
std::vector<RenderFrameDeletedObserver*> rfh_observer;
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* a1 = current_frame_host();
RenderFrameHostImpl* b2 = a1->child_at(0)->current_frame_host();
RenderFrameDeletedObserver a1_observer(a1), b2_observer(b2);
rfh_observer.insert(rfh_observer.end(), {&a1_observer, &b2_observer});
EXPECT_TRUE(ExecJs(b2, "window.alive = 'I am alive';"));
EXPECT_TRUE(NavigateToURL(shell(), url_c));
RenderFrameHostImpl* c3 = current_frame_host();
RenderFrameDeletedObserver c3_observer(c3);
rfh_observer.push_back(&c3_observer);
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
EXPECT_THAT(Elements({a1, b2}), Each(InBackForwardCache()));
EXPECT_THAT(c3, Not(InBackForwardCache()));
ASSERT_TRUE(HistoryGoBack(web_contents()));
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
EXPECT_THAT(Elements({a1, b2}), Each(Not(InBackForwardCache())));
EXPECT_THAT(c3, InBackForwardCache());
EXPECT_EQ("I am alive", EvalJs(b2, "window.alive"));
EXPECT_FALSE(b2_observer.deleted());
ExpectRestored(FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, SubframeSurviveCache2) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
std::vector<RenderFrameDeletedObserver*> rfh_observer;
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* a1 = current_frame_host();
RenderFrameHostImpl* b2 = a1->child_at(0)->current_frame_host();
RenderFrameDeletedObserver a1_observer(a1), b2_observer(b2);
rfh_observer.insert(rfh_observer.end(), {&a1_observer, &b2_observer});
EXPECT_TRUE(ExecJs(b2, "window.alive = 'I am alive';"));
EXPECT_TRUE(NavigateToURL(shell(), url_b));
RenderFrameHostImpl* b3 = current_frame_host();
RenderFrameDeletedObserver b3_observer(b3);
rfh_observer.push_back(&b3_observer);
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
EXPECT_THAT(Elements({a1, b2}), Each(InBackForwardCache()));
EXPECT_THAT(b3, Not(InBackForwardCache()));
ASSERT_TRUE(HistoryGoBack(web_contents()));
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
EXPECT_EQ(a1, current_frame_host());
EXPECT_THAT(Elements({a1, b2}), Each(Not(InBackForwardCache())));
EXPECT_THAT(b3, InBackForwardCache());
EXPECT_EQ("I am alive", EvalJs(b2, "window.alive"));
EXPECT_FALSE(b2_observer.deleted());
ExpectRestored(FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, SubframeSurviveCache3) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
GURL url_b(embedded_test_server()->GetURL(
"b.com", "/cross_site_iframe_factory.html?b(a)"));
std::vector<RenderFrameDeletedObserver*> rfh_observer;
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* a1 = current_frame_host();
RenderFrameHostImpl* b2 = a1->child_at(0)->current_frame_host();
RenderFrameDeletedObserver a1_observer(a1), b2_observer(b2);
rfh_observer.insert(rfh_observer.end(), {&a1_observer, &b2_observer});
EXPECT_TRUE(ExecJs(b2, "window.alive = 'I am alive';"));
EXPECT_TRUE(NavigateToURL(shell(), url_b));
RenderFrameHostImpl* b3 = current_frame_host();
RenderFrameHostImpl* a4 = b3->child_at(0)->current_frame_host();
RenderFrameDeletedObserver b3_observer(b3), a4_observer(a4);
rfh_observer.insert(rfh_observer.end(), {&b3_observer, &a4_observer});
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
EXPECT_THAT(Elements({a1, b2}), Each(InBackForwardCache()));
EXPECT_THAT(Elements({b3, a4}), Each(Not(InBackForwardCache())));
EXPECT_TRUE(ExecJs(a4, "window.alive = 'I am alive';"));
ASSERT_TRUE(HistoryGoBack(web_contents()));
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
EXPECT_EQ(a1, current_frame_host());
EXPECT_THAT(Elements({a1, b2}), Each(Not(InBackForwardCache())));
EXPECT_THAT(Elements({b3, a4}), Each(InBackForwardCache()));
EXPECT_EQ("I am alive", EvalJs(b2, "window.alive"));
EXPECT_FALSE(b2_observer.deleted());
ExpectRestored(FROM_HERE);
ASSERT_TRUE(HistoryGoForward(web_contents()));
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
EXPECT_EQ(b3, current_frame_host());
EXPECT_THAT(Elements({a1, b2}), Each(InBackForwardCache()));
EXPECT_THAT(Elements({b3, a4}), Each(Not(InBackForwardCache())));
EXPECT_EQ("I am alive", EvalJs(a4, "window.alive"));
EXPECT_FALSE(a4_observer.deleted());
ExpectRestored(FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(HighCacheSizeBackForwardCacheBrowserTest,
SubframeSurviveCache4) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_ab(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
std::vector<RenderFrameDeletedObserver*> rfh_observer;
EXPECT_TRUE(NavigateToURL(shell(), url_ab));
RenderFrameHostImpl* a1 = current_frame_host();
RenderFrameHostImpl* b2 = a1->child_at(0)->current_frame_host();
RenderFrameDeletedObserver a1_observer(a1), b2_observer(b2);
rfh_observer.insert(rfh_observer.end(), {&a1_observer, &b2_observer});
EXPECT_TRUE(ExecJs(b2, "window.alive = 'I am alive';"));
EXPECT_TRUE(NavigateToURL(shell(), url_b));
RenderFrameHostImpl* b3 = current_frame_host();
RenderFrameDeletedObserver b3_observer(b3);
rfh_observer.push_back(&b3_observer);
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
EXPECT_THAT(Elements({a1, b2}), Each(InBackForwardCache()));
EXPECT_THAT(b3, Not(InBackForwardCache()));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* a4 = current_frame_host();
RenderFrameDeletedObserver a4_observer(a4);
rfh_observer.push_back(&a4_observer);
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
EXPECT_TRUE(NavigateToURL(shell(), url_b));
RenderFrameHostImpl* b5 = current_frame_host();
RenderFrameDeletedObserver b5_observer(b5);
rfh_observer.push_back(&b5_observer);
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
EXPECT_THAT(Elements({a1, b2, b3, a4}), Each(InBackForwardCache()));
EXPECT_THAT(b5, Not(InBackForwardCache()));
ASSERT_TRUE(HistoryGoToOffset(web_contents(), -3));
EXPECT_EQ(a1, current_frame_host());
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
EXPECT_THAT(Elements({b3, a4, b5}), Each(InBackForwardCache()));
EXPECT_THAT(Elements({a1, b2}), Each(Not(InBackForwardCache())));
EXPECT_EQ("I am alive", EvalJs(b2, "window.alive"));
EXPECT_FALSE(b2_observer.deleted());
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheUnloadBrowserTest,
ConfirmUnloadEventNotFired) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
EXPECT_TRUE(ExecJs(rfh_a,
"document.title = 'loaded!';"
"window.addEventListener('unload', () => {"
" document.title = 'unloaded!';"
"});"));
{
std::u16string title_when_loaded = u"loaded!";
TitleWatcher title_watcher(web_contents(), title_when_loaded);
EXPECT_EQ(title_watcher.WaitAndGetTitle(), title_when_loaded);
}
EXPECT_TRUE(NavigateToURL(shell(), url_b));
RenderFrameHostImpl* rfh_b = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_FALSE(delete_observer_rfh_b.deleted());
EXPECT_EQ(rfh_a, current_frame_host());
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
{
std::u16string title_when_loaded = u"loaded!";
TitleWatcher title_watcher(web_contents(), title_when_loaded);
EXPECT_EQ(title_watcher.WaitAndGetTitle(), title_when_loaded);
}
}
class BackForwardCacheStillLoadingBrowserTest
: public BackForwardCacheBrowserTest,
public ::testing::WithParamInterface<TestFrameType> {
protected:
std::string GetMainFramePath() {
switch (GetParam()) {
case TestFrameType::kMainFrame:
return "/controlled";
case TestFrameType::kSubFrame:
return "/back_forward_cache/controllable_subframe.html";
case TestFrameType::kSubFrameOfSubframe:
return "/back_forward_cache/controllable_subframe_of_subframe.html";
}
}
int GetNavigationCount() {
switch (GetParam()) {
case TestFrameType::kMainFrame:
return 1;
case TestFrameType::kSubFrame:
return 2;
case TestFrameType::kSubFrameOfSubframe:
return 3;
}
}
};
INSTANTIATE_TEST_SUITE_P(All,
BackForwardCacheStillLoadingBrowserTest,
::testing::Values(TestFrameType::kMainFrame,
TestFrameType::kSubFrame,
TestFrameType::kSubFrameOfSubframe));
IN_PROC_BROWSER_TEST_P(BackForwardCacheStillLoadingBrowserTest,
DoesNotCacheIfFrameStillLoading) {
std::string controlled_path("/controlled");
net::test_server::ControllableHttpResponse response(embedded_test_server(),
controlled_path);
ASSERT_TRUE(embedded_test_server()->Start());
bool testing_main_frame = GetParam() == TestFrameType::kMainFrame;
GURL main_frame_url(
embedded_test_server()->GetURL("a.com", GetMainFramePath()));
TestNavigationObserver observer(web_contents(), GetNavigationCount());
observer.set_wait_event(
TestNavigationObserver::WaitEvent::kNavigationFinished);
shell()->LoadURL(main_frame_url);
response.WaitForRequest();
response.Send(
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"\r\n"
"<html><body>...");
observer.WaitForNavigationFinished();
RenderFrameHostImplWrapper rfh_a(current_frame_host());
shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
ASSERT_TRUE(WaitForLoadStop(web_contents()));
ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
ASSERT_NE(HistoryGoBack(web_contents()), testing_main_frame);
ExpectNotRestored({NotRestoredReason::kLoading}, {}, {}, {}, {}, FROM_HERE);
}
class BackForwardCacheStillNavigatingBrowserTest
: public BackForwardCacheBrowserTest,
public ::testing::WithParamInterface<TestFrameType> {
protected:
std::string GetMainFramePath() {
switch (GetParam()) {
case TestFrameType::kMainFrame:
NOTREACHED();
return "";
case TestFrameType::kSubFrame:
return "/back_forward_cache/controllable_subframe.html";
case TestFrameType::kSubFrameOfSubframe:
return "/back_forward_cache/controllable_subframe_of_subframe.html";
}
}
};
INSTANTIATE_TEST_SUITE_P(All,
BackForwardCacheStillNavigatingBrowserTest,
::testing::Values(TestFrameType::kSubFrame,
TestFrameType::kSubFrameOfSubframe));
IN_PROC_BROWSER_TEST_P(BackForwardCacheStillNavigatingBrowserTest,
DoesNotCacheNavigatingSubframe) {
net::test_server::ControllableHttpResponse response(embedded_test_server(),
"/controlled");
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL("a.com", GetMainFramePath()));
shell()->LoadURL(url);
response.WaitForRequest();
RenderFrameHostImplWrapper rfh_a(current_frame_host());
ASSERT_EQ(42, EvalJs(rfh_a.get(), "domContentLoaded"));
shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
EXPECT_TRUE(WaitForLoadStop(web_contents()));
ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted());
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored({NotRestoredReason::kSubframeIsNavigating}, {}, {}, {}, {},
FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
FrameWithInvalidURLDoesntAffectCache) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title2.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
InsertSubFrameWithUrl(rfh_a, "javascript:false");
InsertSubFrameWithUrl(rfh_a, "blob:");
InsertSubFrameWithUrl(rfh_a, "file:///");
InsertSubFrameWithUrl(rfh_a, "htt://");
for (size_t i = 0; i < rfh_a->child_count(); i++) {
RenderFrameHostImpl* rfh_subframe =
rfh_a->child_at(i)->current_frame_host();
EXPECT_FALSE(rfh_subframe->IsDOMContentLoaded());
EXPECT_FALSE(rfh_subframe->has_committed_any_navigation());
}
EXPECT_TRUE(NavigateToURL(shell(), url_b));
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectRestored(FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DoesNotCacheIfHttpError) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL error_url(embedded_test_server()->GetURL("a.com", "/page404.html"));
GURL url(embedded_test_server()->GetURL("b.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), error_url));
EXPECT_EQ(net::HTTP_NOT_FOUND, current_frame_host()->last_http_status_code());
RenderFrameDeletedObserver delete_rfh_a(current_frame_host());
EXPECT_TRUE(NavigateToURL(shell(), url));
delete_rfh_a.WaitUntilDeleted();
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectNotRestored({NotRestoredReason::kHTTPStatusNotOK}, {}, {}, {}, {},
FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
DoesNotCacheIfPageUnreachable) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL error_url(embedded_test_server()->GetURL("a.com", "/empty.html"));
GURL url(embedded_test_server()->GetURL("b.com", "/title1.html"));
std::unique_ptr<URLLoaderInterceptor> url_interceptor =
URLLoaderInterceptor::SetupRequestFailForURL(error_url,
net::ERR_DNS_TIMED_OUT);
EXPECT_TRUE(NavigateToURL(shell(), url));
EXPECT_EQ(net::HTTP_OK, current_frame_host()->last_http_status_code());
NavigationHandleObserver observer(shell()->web_contents(), error_url);
EXPECT_FALSE(NavigateToURL(shell(), error_url));
EXPECT_TRUE(observer.is_error());
EXPECT_EQ(net::ERR_DNS_TIMED_OUT, observer.net_error_code());
EXPECT_EQ(GURL(kUnreachableWebDataURL), shell()
->web_contents()
->GetPrimaryMainFrame()
->GetSiteInstance()
->GetSiteURL());
EXPECT_EQ(net::OK, current_frame_host()->last_http_status_code());
RenderFrameDeletedObserver delete_rfh_a(current_frame_host());
EXPECT_TRUE(NavigateToURL(shell(), url));
delete_rfh_a.WaitUntilDeleted();
web_contents()->GetController().GoBack();
EXPECT_FALSE(WaitForLoadStop(shell()->web_contents()));
ExpectNotRestored(
{NotRestoredReason::kHTTPStatusNotOK, NotRestoredReason::kNoResponseHead,
NotRestoredReason::kErrorDocument},
{}, {}, {}, {}, FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, Events) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL(
"a.com", "/back_forward_cache/record_events.html"));
GURL url_b(embedded_test_server()->GetURL(
"b.com", "/back_forward_cache/record_events.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImplWrapper rfh_a(current_frame_host());
MatchEventList(rfh_a.get(), ListValueOf("window.pageshow"));
content::FetchHistogramsFromChildProcesses();
EXPECT_TRUE(NavigateToURL(shell(), url_b));
RenderFrameHostImplWrapper rfh_b(current_frame_host());
EXPECT_FALSE(rfh_a.IsRenderFrameDeleted());
EXPECT_FALSE(rfh_b.IsRenderFrameDeleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
MatchEventList(rfh_b.get(), ListValueOf("window.pageshow"));
content::FetchHistogramsFromChildProcesses();
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_FALSE(rfh_a.IsRenderFrameDeleted());
EXPECT_FALSE(rfh_b.IsRenderFrameDeleted());
EXPECT_EQ(rfh_a.get(), current_frame_host());
MatchEventList(
rfh_a.get(),
ListValueOf("window.pageshow", "window.pagehide.persisted",
"document.visibilitychange", "window.visibilitychange",
"document.freeze", "document.resume",
"document.visibilitychange", "window.visibilitychange",
"window.pageshow.persisted"));
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, EventsForSubframes) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
StartRecordingEvents(rfh_a);
StartRecordingEvents(rfh_b);
EXPECT_TRUE(NavigateToURL(shell(), url_c));
RenderFrameHostImpl* rfh_c = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_c(rfh_c);
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_FALSE(delete_observer_rfh_b.deleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
EXPECT_TRUE(rfh_b->IsInBackForwardCache());
EXPECT_FALSE(rfh_c->IsInBackForwardCache());
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_FALSE(delete_observer_rfh_b.deleted());
EXPECT_FALSE(delete_observer_rfh_c.deleted());
EXPECT_EQ(rfh_a, current_frame_host());
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
EXPECT_TRUE(rfh_c->IsInBackForwardCache());
MatchEventList(
rfh_a,
ListValueOf("window.pagehide.persisted", "document.visibilitychange",
"window.visibilitychange", "document.freeze",
"document.resume", "document.visibilitychange",
"window.visibilitychange", "window.pageshow.persisted"));
MatchEventList(
rfh_b,
ListValueOf("window.pagehide.persisted", "document.visibilitychange",
"window.visibilitychange", "document.freeze",
"document.resume", "document.visibilitychange",
"window.visibilitychange", "window.pageshow.persisted"));
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
EventsAfterDocumentInitiatedNavigation) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
StartRecordingEvents(rfh_a);
ASSERT_TRUE(NavigateToURLFromRenderer(shell(), url_b));
RenderFrameHostImpl* rfh_b = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_FALSE(delete_observer_rfh_b.deleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_FALSE(delete_observer_rfh_b.deleted());
EXPECT_EQ(rfh_a, current_frame_host());
MatchEventList(
rfh_a,
ListValueOf("window.pagehide.persisted", "document.visibilitychange",
"window.visibilitychange", "document.freeze",
"document.resume", "document.visibilitychange",
"window.visibilitychange", "window.pageshow.persisted"));
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
EventsForPageIneligibleAfterPagehidePersisted) {
ASSERT_TRUE(CreateHttpsServer()->Start());
GURL url_1(https_server()->GetURL("a.com", "/title1.html"));
GURL url_2(https_server()->GetURL("a.com", "/title2.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_1));
RenderFrameHostImpl* rfh_1 = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_1(rfh_1);
EXPECT_TRUE(ExecJs(rfh_1, "window.foo = new BroadcastChannel('foo');"));
EXPECT_TRUE(ExecJs(rfh_1, R"(
window.onpagehide = (e) => {
console.log("onagepagehide", e.persisted);
localStorage.setItem('pagehide_persisted',
e.persisted ? 'true' : 'false');
}
document.onvisibilitychange = () => {
localStorage.setItem('visibilitychange',
document.visibilityState);
}
)"));
EXPECT_TRUE(NavigateToURL(shell(), url_2));
delete_observer_rfh_1.WaitUntilDeleted();
EXPECT_EQ("true",
GetLocalStorage(current_frame_host(), "pagehide_persisted"));
EXPECT_EQ("hidden",
GetLocalStorage(current_frame_host(), "visibilitychange"));
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
EventsForPageIneligibleBeforePagehide) {
ASSERT_TRUE(CreateHttpsServer()->Start());
GURL url_1(https_server()->GetURL("a.com", "/title1.html"));
GURL url_2(https_server()->GetURL("b.com", "/title2.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_1));
RenderFrameHostImpl* rfh_1 = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_1(rfh_1);
rfh_1->UseDummyStickyBackForwardCacheDisablingFeatureForTesting();
EXPECT_TRUE(ExecJs(rfh_1, R"(
window.onpagehide = (e) => {
if (!e.persisted) {
window.domAutomationController.send('pagehide.not_persisted');
}
}
document.onvisibilitychange = () => {
if (document.visibilityState == 'hidden') {
window.domAutomationController.send('visibilitychange.hidden');
}
}
)"));
DOMMessageQueue dom_message_queue(shell()->web_contents());
EXPECT_TRUE(NavigateToURL(shell(), url_2));
delete_observer_rfh_1.WaitUntilDeleted();
int num_messages_received = 0;
std::string expected_messages[] = {"\"pagehide.not_persisted\"",
"\"visibilitychange.hidden\""};
std::string message;
while (dom_message_queue.PopMessage(&message)) {
EXPECT_EQ(expected_messages[num_messages_received], message);
num_messages_received++;
}
EXPECT_EQ(num_messages_received, 2);
}
enum class StickinessType {
kSticky,
kNonSticky,
};
class BackForwardCacheBrowserTestWithVaryingFrameAndFeatureStickinessType
: public BackForwardCacheBrowserTest,
public ::testing::WithParamInterface<
testing::tuple<TestFrameType, StickinessType>> {};
INSTANTIATE_TEST_SUITE_P(
All,
BackForwardCacheBrowserTestWithVaryingFrameAndFeatureStickinessType,
::testing::Combine(::testing::Values(TestFrameType::kMainFrame,
TestFrameType::kSubFrame),
::testing::Values(StickinessType::kSticky,
StickinessType::kNonSticky)));
IN_PROC_BROWSER_TEST_P(
BackForwardCacheBrowserTestWithVaryingFrameAndFeatureStickinessType,
TestPagehidePersistedValue) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
TestFrameType parameter_frame = std::get<0>(GetParam());
StickinessType use_sticky_feature = std::get<1>(GetParam());
RenderFrameHostImplWrapper rfh_with_blocking_feature(
parameter_frame == TestFrameType::kSubFrame
? current_frame_host()->child_at(0)->current_frame_host()
: current_frame_host());
if (use_sticky_feature == StickinessType::kSticky) {
rfh_with_blocking_feature.get()
->UseDummyStickyBackForwardCacheDisablingFeatureForTesting();
} else {
EXPECT_TRUE(ExecJs(rfh_with_blocking_feature.get(),
"window.foo = new BroadcastChannel('foo');"));
}
EXPECT_TRUE(ExecJs(current_frame_host(), R"(
window.onpagehide = (e) => {
localStorage.setItem('pagehide_persisted',
e.persisted ? 'true' : 'false');
}
)"));
EXPECT_TRUE(NavigateToURL(shell(), url_b));
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_EQ(use_sticky_feature == StickinessType::kSticky ? "false" : "true",
GetLocalStorage(current_frame_host(), "pagehide_persisted"));
blink::scheduler::WebSchedulerTrackedFeature expected_feature =
(use_sticky_feature == StickinessType::kSticky)
? blink::scheduler::WebSchedulerTrackedFeature::kDummy
: blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel;
ExpectNotRestored({NotRestoredReason::kBlocklistedFeatures},
{expected_feature}, {}, {}, {}, FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(HighCacheSizeBackForwardCacheBrowserTest,
CanCacheMultiplesPagesOnSameDomain) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_b2(embedded_test_server()->GetURL("b.com", "/title1.html"));
GURL url_a3(embedded_test_server()->GetURL("a.com", "/title2.html"));
GURL url_b4(embedded_test_server()->GetURL("b.com", "/title2.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_a1));
RenderFrameHostImpl* rfh_a1 = current_frame_host();
EXPECT_TRUE(NavigateToURL(shell(), url_b2));
RenderFrameHostImpl* rfh_b2 = current_frame_host();
EXPECT_TRUE(rfh_a1->IsInBackForwardCache());
EXPECT_TRUE(NavigateToURL(shell(), url_a3));
RenderFrameHostImpl* rfh_a3 = current_frame_host();
EXPECT_TRUE(rfh_a1->IsInBackForwardCache());
EXPECT_TRUE(rfh_b2->IsInBackForwardCache());
EXPECT_NE(rfh_a1->GetSiteInstance(), rfh_a3->GetSiteInstance());
EXPECT_TRUE(NavigateToURL(shell(), url_b4));
RenderFrameHostImpl* rfh_b4 = current_frame_host();
EXPECT_TRUE(rfh_a1->IsInBackForwardCache());
EXPECT_TRUE(rfh_b2->IsInBackForwardCache());
EXPECT_TRUE(rfh_a3->IsInBackForwardCache());
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_TRUE(rfh_a1->IsInBackForwardCache());
EXPECT_TRUE(rfh_b2->IsInBackForwardCache());
EXPECT_TRUE(rfh_b4->IsInBackForwardCache());
EXPECT_EQ(rfh_a3, current_frame_host());
EXPECT_NE(rfh_b2->GetSiteInstance(), rfh_b4->GetSiteInstance());
ASSERT_TRUE(HistoryGoToIndex(web_contents(), 0));
EXPECT_TRUE(rfh_b2->IsInBackForwardCache());
EXPECT_TRUE(rfh_b4->IsInBackForwardCache());
EXPECT_TRUE(rfh_a3->IsInBackForwardCache());
EXPECT_EQ(rfh_a1, current_frame_host());
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, Encoding) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL(
"a.com", "/back_forward_cache/charset_windows-1250.html"));
GURL url_b(embedded_test_server()->GetURL(
"b.com", "/back_forward_cache/charset_utf-8.html"));
url::Origin origin_a = url::Origin::Create(url_a);
url::Origin origin_b = url::Origin::Create(url_b);
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
EXPECT_EQ(web_contents()->GetEncoding(), "windows-1250");
EXPECT_TRUE(NavigateToURL(shell(), url_b));
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
EXPECT_EQ(web_contents()->GetEncoding(), "UTF-8");
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_EQ(web_contents()->GetEncoding(), "windows-1250");
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
DoesNotCacheCrossSiteHttpPost) {
SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
GURL form_url(embedded_test_server()->GetURL(
"a.com", "/form_that_posts_cross_site.html"));
GURL redirect_target_url(embedded_test_server()->GetURL("x.com", "/echoall"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), form_url));
TestNavigationObserver form_post_observer(shell()->web_contents(), 1);
EXPECT_TRUE(ExecJs(shell(), "document.getElementById('text-form').submit()"));
form_post_observer.Wait();
EXPECT_EQ(redirect_target_url,
shell()->web_contents()->GetLastCommittedURL());
RenderFrameDeletedObserver delete_observer_rfh(current_frame_host());
EXPECT_TRUE(NavigateToURL(shell(), url_b));
delete_observer_rfh.WaitUntilDeleted();
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DISABLED_NavigationStart) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL(
"a.com", "/back_forward_cache/record_navigation_start_time_stamp.html"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
double initial_page_show_time_stamp =
EvalJs(shell(), "window.initialPageShowTimeStamp").ExtractDouble();
EXPECT_DOUBLE_EQ(
initial_page_show_time_stamp,
EvalJs(shell(), "window.latestPageShowTimeStamp").ExtractDouble());
double initial_navigation_start =
EvalJs(shell(), "window.initialNavigationStart").ExtractDouble();
EXPECT_TRUE(NavigateToURL(shell(), url_b));
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
NavigationHandleObserver observer(web_contents(), url_a);
base::TimeTicks time_before_navigation = base::TimeTicks::Now();
double js_time_before_navigation =
EvalJs(shell(), "performance.now()").ExtractDouble();
ASSERT_TRUE(HistoryGoBack(web_contents()));
base::TimeTicks time_after_navigation = base::TimeTicks::Now();
double js_time_after_navigation =
EvalJs(shell(), "performance.now()").ExtractDouble();
base::TimeTicks back_navigation_start = observer.navigation_start();
EXPECT_LT(time_before_navigation, back_navigation_start);
EXPECT_GT(time_after_navigation, back_navigation_start);
EXPECT_DOUBLE_EQ(
initial_navigation_start,
EvalJs(shell(), "window.initialNavigationStart").ExtractDouble());
EXPECT_DOUBLE_EQ(
initial_navigation_start,
EvalJs(shell(), "performance.timing.navigationStart").ExtractDouble());
EXPECT_DOUBLE_EQ(
initial_page_show_time_stamp,
EvalJs(shell(), "window.initialPageShowTimeStamp").ExtractDouble());
double latest_page_show_time_stamp =
EvalJs(shell(), "window.latestPageShowTimeStamp").ExtractDouble();
EXPECT_LT(initial_page_show_time_stamp, latest_page_show_time_stamp);
EXPECT_NEAR(
(back_navigation_start - base::TimeTicks::UnixEpoch()).InMillisecondsF(),
latest_page_show_time_stamp + initial_navigation_start, 1.0);
EXPECT_LT(js_time_before_navigation, latest_page_show_time_stamp);
EXPECT_GT(js_time_after_navigation, latest_page_show_time_stamp);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
CanUseCacheWhenNavigatingAwayToErrorPage) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL error_url(embedded_test_server()->GetURL("b.com", "/empty.html"));
auto url_interceptor = URLLoaderInterceptor::SetupRequestFailForURL(
error_url, net::ERR_DNS_TIMED_OUT);
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
EXPECT_FALSE(NavigateToURL(shell(), error_url));
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
ASSERT_TRUE(HistoryGoBack(web_contents()));
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CoepReporter) {
ASSERT_TRUE(CreateHttpsServer()->Start());
GURL url_a(https_server()->GetURL("a.com",
"/set-header?"
"Cross-Origin-Embedder-Policy-Report-Only: "
"require-corp; report-to%3d\"a\""));
GURL url_b(https_server()->GetURL("b.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
EXPECT_TRUE(rfh_a->coep_reporter());
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
EXPECT_TRUE(NavigateToURL(shell(), url_b));
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_EQ(rfh_a, current_frame_host());
EXPECT_TRUE(rfh_a->coep_reporter());
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CoopReporter) {
ASSERT_TRUE(CreateHttpsServer()->Start());
GURL url_a(https_server()->GetURL("a.com",
"/set-header?"
"Cross-Origin-Opener-Policy-Report-Only: "
"same-origin; report-to%3d\"a\""));
GURL url_b(https_server()->GetURL("b.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
EXPECT_TRUE(rfh_a->coop_access_report_manager()->coop_reporter());
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
EXPECT_TRUE(NavigateToURL(shell(), url_b));
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_EQ(rfh_a, current_frame_host());
EXPECT_TRUE(rfh_a->coop_access_report_manager()->coop_reporter());
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, Coep) {
ASSERT_TRUE(CreateHttpsServer()->Start());
GURL url_a(https_server()->GetURL(
"a.com", "/set-header?Cross-Origin-Embedder-Policy: require-corp"));
GURL url_b(https_server()->GetURL("b.com", "/title1.html"));
network::CrossOriginEmbedderPolicy coep;
coep.value = network::mojom::CrossOriginEmbedderPolicyValue::kRequireCorp;
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
EXPECT_EQ(coep, rfh_a->cross_origin_embedder_policy());
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
EXPECT_TRUE(NavigateToURL(shell(), url_b));
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_EQ(rfh_a, current_frame_host());
EXPECT_EQ(coep, rfh_a->cross_origin_embedder_policy());
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
PagehideAndVisibilitychangeRuns) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_1(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_2(embedded_test_server()->GetURL("b.com", "/title2.html"));
GURL url_3(embedded_test_server()->GetURL("a.com", "/title2.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
EXPECT_TRUE(NavigateToURL(shell(), url_1));
RenderFrameHostImpl* main_frame_1 = web_contents->GetPrimaryMainFrame();
EXPECT_TRUE(ExecJs(main_frame_1,
R"(
localStorage.setItem('pagehide_storage', 'not_dispatched');
var dispatched_pagehide = false;
window.onpagehide = function(e) {
if (dispatched_pagehide) {
// We shouldn't dispatch pagehide more than once.
localStorage.setItem('pagehide_storage', 'dispatched_more_than_once');
} else if (!e.persisted) {
localStorage.setItem('pagehide_storage', 'wrong_persisted');
} else {
localStorage.setItem('pagehide_storage', 'dispatched_once');
}
dispatched_pagehide = true;
}
localStorage.setItem('visibilitychange_storage', 'not_dispatched');
var dispatched_visibilitychange = false;
document.onvisibilitychange = function(e) {
if (dispatched_visibilitychange) {
// We shouldn't dispatch visibilitychange more than once.
localStorage.setItem('visibilitychange_storage',
'dispatched_more_than_once');
} else if (document.visibilityState != 'hidden') {
// We should dispatch the event when the visibilityState is 'hidden'.
localStorage.setItem('visibilitychange_storage', 'not_hidden');
} else {
localStorage.setItem('visibilitychange_storage', 'dispatched_once');
}
dispatched_visibilitychange = true;
}
)"));
EXPECT_TRUE(NavigateToURL(shell(), url_2));
EXPECT_TRUE(main_frame_1->IsInBackForwardCache());
EXPECT_TRUE(NavigateToURL(shell(), url_3));
RenderFrameHostImpl* main_frame_3 = web_contents->GetPrimaryMainFrame();
EXPECT_EQ("dispatched_once",
GetLocalStorage(main_frame_3, "pagehide_storage"));
EXPECT_EQ("dispatched_once",
GetLocalStorage(main_frame_3, "visibilitychange_storage"));
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
RendererHistory_BrowserInitiated) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url1(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
GURL url2(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url1));
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
FrameTreeNode* subframe = root->child_at(0);
std::string restore_time_length_saver_script =
"var resumeLength = -1;"
"var pageshowLength = -1;"
"document.onresume = () => {"
" resumeLength = history.length;"
"};"
"window.onpageshow = () => {"
" pageshowLength = history.length;"
"};";
EXPECT_TRUE(ExecJs(root, restore_time_length_saver_script));
EXPECT_TRUE(ExecJs(subframe, restore_time_length_saver_script));
EXPECT_EQ(EvalJs(root, "history.length").ExtractInt(), 1);
EXPECT_EQ(EvalJs(subframe, "history.length").ExtractInt(), 1);
EXPECT_TRUE(NavigateToURL(shell(), url2));
EXPECT_TRUE(ExecJs(root, restore_time_length_saver_script));
EXPECT_EQ(EvalJs(root, "history.length").ExtractInt(), 2);
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_EQ(web_contents()->GetLastCommittedURL(), url1);
EXPECT_EQ(EvalJs(root, "history.length").ExtractInt(), 2);
EXPECT_EQ(EvalJs(root, "resumeLength").ExtractInt(), 2);
EXPECT_EQ(EvalJs(root, "pageshowLength").ExtractInt(), 2);
EXPECT_EQ(EvalJs(subframe, "history.length").ExtractInt(), 2);
EXPECT_EQ(EvalJs(subframe, "resumeLength").ExtractInt(), 2);
EXPECT_EQ(EvalJs(subframe, "pageshowLength").ExtractInt(), 2);
ASSERT_TRUE(HistoryGoForward(web_contents()));
EXPECT_EQ(web_contents()->GetLastCommittedURL(), url2);
EXPECT_EQ(EvalJs(root, "history.length").ExtractInt(), 2);
EXPECT_EQ(EvalJs(root, "resumeLength").ExtractInt(), 2);
EXPECT_EQ(EvalJs(root, "pageshowLength").ExtractInt(), 2);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
RendererHistory_RendererInitiated) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url1(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
GURL url2(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url1));
FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
FrameTreeNode* subframe = root->child_at(0);
std::string restore_time_length_saver_script =
"var resumeLength = -1;"
"var pageshowLength = -1;"
"document.onresume = () => {"
" resumeLength = history.length;"
"};"
"window.onpageshow = () => {"
" pageshowLength = history.length;"
"};";
EXPECT_TRUE(ExecJs(root, restore_time_length_saver_script));
EXPECT_TRUE(ExecJs(subframe, restore_time_length_saver_script));
EXPECT_EQ(EvalJs(root, "history.length").ExtractInt(), 1);
EXPECT_EQ(EvalJs(subframe, "history.length").ExtractInt(), 1);
EXPECT_TRUE(NavigateToURL(shell(), url2));
EXPECT_TRUE(ExecJs(root, restore_time_length_saver_script));
EXPECT_EQ(EvalJs(root, "history.length").ExtractInt(), 2);
EXPECT_TRUE(ExecJs(root, "history.back()"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(web_contents()->GetLastCommittedURL(), url1);
EXPECT_EQ(EvalJs(root, "history.length").ExtractInt(), 2);
EXPECT_EQ(EvalJs(root, "resumeLength").ExtractInt(), 2);
EXPECT_EQ(EvalJs(root, "pageshowLength").ExtractInt(), 2);
EXPECT_EQ(EvalJs(subframe, "history.length").ExtractInt(), 2);
EXPECT_EQ(EvalJs(subframe, "resumeLength").ExtractInt(), 2);
EXPECT_EQ(EvalJs(subframe, "pageshowLength").ExtractInt(), 2);
EXPECT_TRUE(ExecJs(root, "history.forward()"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(web_contents()->GetLastCommittedURL(), url2);
EXPECT_EQ(EvalJs(root, "history.length").ExtractInt(), 2);
EXPECT_EQ(EvalJs(root, "resumeLength").ExtractInt(), 2);
EXPECT_EQ(EvalJs(root, "pageshowLength").ExtractInt(), 2);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
NavigatingToAboutBlankDoesNotPreventCaching) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL("a.com", "/empty.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
GURL blank_url(url::kAboutBlankURL);
EXPECT_TRUE(NavigateToURL(shell(), blank_url));
ASSERT_TRUE(HistoryGoBack(web_contents()));
ExpectRestored(FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, NoContent) {
ASSERT_TRUE(embedded_test_server()->Start());
NavigationControllerImpl& controller = web_contents()->GetController();
GURL url_a(embedded_test_server()->GetURL("a.com", "/empty.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
EXPECT_EQ(1, controller.GetEntryCount());
EXPECT_EQ(url_a, controller.GetLastCommittedEntry()->GetURL());
GURL url_b(embedded_test_server()->GetURL("b.com", "/empty.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_b));
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(url_b, controller.GetLastCommittedEntry()->GetURL());
GURL url_c(embedded_test_server()->GetURL("c.com", "/echo?status=204"));
EXPECT_TRUE(NavigateToURL(shell(), url_c, url_b));
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(url_b, controller.GetLastCommittedEntry()->GetURL());
ASSERT_TRUE(HistoryGoBack(web_contents()));
EXPECT_EQ(2, controller.GetEntryCount());
EXPECT_EQ(url_a, controller.GetLastCommittedEntry()->GetURL());
ExpectRestored(FROM_HERE);
}
class CacheSizeOneBackForwardCacheBrowserTest
: public BackForwardCacheBrowserTest {
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
EnableFeatureAndSetParams(features::kBackForwardCache, "cache_size",
base::NumberToString(1));
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
}
};
IN_PROC_BROWSER_TEST_F(CacheSizeOneBackForwardCacheBrowserTest,
ReplacedNavigationEntry) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL("a.test", "/title1.html"));
GURL url_b(embedded_test_server()->GetURL("b.test", "/title1.html"));
GURL url_c(embedded_test_server()->GetURL("c.test", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImplWrapper rfh_a(current_frame_host());
EXPECT_TRUE(NavigateToURL(shell(), url_b));
RenderFrameHostImplWrapper rfh_b(current_frame_host());
EXPECT_FALSE(rfh_a.IsRenderFrameDeleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
EXPECT_FALSE(rfh_b->IsInBackForwardCache());
EXPECT_TRUE(
ExecJs(shell(), JsReplace("window.location.replace($1);", url_c)));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
RenderFrameHostImplWrapper rfh_c(current_frame_host());
EXPECT_FALSE(rfh_a.IsRenderFrameDeleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
EXPECT_EQ(rfh_a.get(), current_frame_host());
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
EXPECT_EQ(rfh_a->GetVisibilityState(), PageVisibilityState::kVisible);
ASSERT_TRUE(HistoryGoForward(shell()->web_contents()));
EXPECT_EQ(rfh_c.get(), current_frame_host());
EXPECT_EQ(rfh_c->GetVisibilityState(), PageVisibilityState::kVisible);
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, ServerRedirect) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html"));
ASSERT_TRUE(NavigateToURL(web_contents(), url_a));
RenderFrameHostImplWrapper rfh_a(current_frame_host());
EXPECT_EQ(url_a, rfh_a->GetLastCommittedURL());
std::string replace_state =
"window.history.replaceState(null, '', '/server-redirect?" +
url_b.spec() + "');";
EXPECT_TRUE(ExecJs(rfh_a.get(), replace_state));
EXPECT_TRUE(NavigateToURL(shell(), url_c));
RenderFrameHostImplWrapper rfh_b(current_frame_host());
EvictByJavaScript(rfh_a.get());
ASSERT_TRUE(HistoryGoBack(web_contents()));
RenderFrameHostImplWrapper rfh_a_redirect(current_frame_host());
EXPECT_EQ(url_b, rfh_a_redirect->GetLastCommittedURL());
ExpectNotRestored({NotRestoredReason::kJavaScriptExecution}, {}, {}, {}, {},
FROM_HERE);
}
class BackForwardCacheBrowsingContextStateBrowserTest
: public BackForwardCacheBrowserTest,
public ::testing::WithParamInterface<bool> {
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
IsolateAllSitesForTesting(command_line);
if (GetParam()) {
EnableFeatureAndSetParams(
features::kNewBrowsingContextStateOnBrowsingContextGroupSwap, "", "");
}
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
}
};
INSTANTIATE_TEST_SUITE_P(All,
BackForwardCacheBrowsingContextStateBrowserTest,
::testing::Bool());
IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowsingContextStateBrowserTest,
SlowUnloadHandlerInIframe) {
DoNotFailForUnexpectedMessagesWhileCached();
ASSERT_TRUE(embedded_test_server()->Start());
GURL initial_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html"));
GURL url_d(embedded_test_server()->GetURL("d.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), initial_url));
RenderFrameHostImplWrapper rfh_a(current_frame_host());
RenderFrameHostImplWrapper rfh_b(rfh_a->child_at(0)->current_frame_host());
auto unload_ack_filter = base::BindRepeating([] { return true; });
rfh_b->SetUnloadACKCallbackForTesting(unload_ack_filter);
rfh_b->DisableUnloadTimerForTesting();
EXPECT_TRUE(NavigateToURLFromRenderer(rfh_a->child_at(0), url_c));
RenderFrameHostImplWrapper rfh_c(rfh_a->child_at(0)->current_frame_host());
EXPECT_TRUE(NavigateToURL(shell(), url_d));
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
EXPECT_TRUE(rfh_c->IsInBackForwardCache());
EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kRunningUnloadHandlers,
rfh_b->lifecycle_state());
rfh_b->SetUnloadACKCallbackForTesting(base::NullCallback());
rfh_b->OnUnloadACK();
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
EXPECT_TRUE(rfh_b.IsDestroyed());
EXPECT_FALSE(rfh_c.IsDestroyed());
EXPECT_TRUE(rfh_c->IsInBackForwardCache());
web_contents()->WasHidden();
ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));
EXPECT_EQ(rfh_a.get(), current_frame_host());
EXPECT_FALSE(rfh_a->IsInBackForwardCache());
EXPECT_FALSE(rfh_c->IsInBackForwardCache());
ExpectRestored(FROM_HERE);
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://c.com/",
DepictFrameTree(current_frame_host()->frame_tree_node()));
}
}