blob: ed805228d89edaae3e60296b3a51e6c8bcb5987e [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/command_line.h"
#include "base/test/scoped_feature_list.h"
#include "content/browser/frame_host/back_forward_cache.h"
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h"
using testing::Each;
using testing::Not;
namespace content {
namespace {
// Test about the BackForwardCache.
class BackForwardCacheBrowserTest : public ContentBrowserTest {
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
feature_list_.InitAndEnableFeature(features::kBackForwardCache);
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
}
WebContentsImpl* web_contents() const {
return static_cast<WebContentsImpl*>(shell()->web_contents());
}
RenderFrameHostImpl* current_frame_host() {
return web_contents()->GetFrameTree()->root()->current_frame_host();
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Match RenderFrameHostImpl* that are in the BackForwardCache.
MATCHER(InBackForwardCache, "") {
return arg->is_in_back_forward_cache();
}
// Match RenderFrameDeleteObserver* which observed deletion of the RenderFrame.
MATCHER(Deleted, "") {
return arg->deleted();
}
// Helper function to pass an initializer list to the EXPECT_THAT macro. This is
// indeed the identity function.
std::initializer_list<RenderFrameHostImpl*> Elements(
std::initializer_list<RenderFrameHostImpl*> t) {
return t;
}
} // namespace
// Navigate from A to B and go back.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, Basic) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
const GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
// 1) Navigate to A.
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_rfh_a(rfh_a);
// 2) Navigate to B.
EXPECT_TRUE(NavigateToURL(shell(), url_b));
RenderFrameHostImpl* rfh_b = current_frame_host();
RenderFrameDeletedObserver delete_rfh_b(rfh_b);
EXPECT_FALSE(delete_rfh_a.deleted());
EXPECT_TRUE(rfh_a->is_in_back_forward_cache());
EXPECT_EQ(rfh_a->GetVisibilityState(), PageVisibilityState::kHidden);
EXPECT_FALSE(rfh_b->is_in_back_forward_cache());
EXPECT_EQ(rfh_b->GetVisibilityState(), PageVisibilityState::kVisible);
// 3) Go back to A.
web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_FALSE(delete_rfh_a.deleted());
EXPECT_FALSE(delete_rfh_b.deleted());
EXPECT_EQ(rfh_a, current_frame_host());
EXPECT_FALSE(rfh_a->is_in_back_forward_cache());
EXPECT_EQ(rfh_a->GetVisibilityState(), PageVisibilityState::kVisible);
EXPECT_TRUE(rfh_b->is_in_back_forward_cache());
EXPECT_EQ(rfh_b->GetVisibilityState(), PageVisibilityState::kHidden);
}
// Navigate from A to B and go back.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, BasicDocumentInitiated) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
const GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
// 1) Navigate to A.
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_rfh_a(rfh_a);
// 2) Navigate to B.
EXPECT_TRUE(ExecJs(shell(), JsReplace("location = $1;", url_b.spec())));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
RenderFrameHostImpl* rfh_b = current_frame_host();
RenderFrameDeletedObserver delete_rfh_b(rfh_b);
EXPECT_FALSE(delete_rfh_a.deleted());
EXPECT_TRUE(rfh_a->is_in_back_forward_cache());
EXPECT_FALSE(rfh_b->is_in_back_forward_cache());
// The two pages are using different BrowsingInstances.
EXPECT_FALSE(rfh_a->GetSiteInstance()->IsRelatedSiteInstance(
rfh_b->GetSiteInstance()));
// 3) Go back to A.
EXPECT_TRUE(ExecJs(shell(), "history.back();"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_FALSE(delete_rfh_a.deleted());
EXPECT_FALSE(delete_rfh_b.deleted());
EXPECT_EQ(rfh_a, current_frame_host());
EXPECT_FALSE(rfh_a->is_in_back_forward_cache());
EXPECT_TRUE(rfh_b->is_in_back_forward_cache());
}
// Navigate from back and forward repeatedly.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
NavigateBackForwardRepeatedly) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
const GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
// 1) Navigate to A.
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_rfh_a(rfh_a);
// 2) Navigate to B.
EXPECT_TRUE(NavigateToURL(shell(), url_b));
RenderFrameHostImpl* rfh_b = current_frame_host();
RenderFrameDeletedObserver delete_rfh_b(rfh_b);
EXPECT_TRUE(rfh_a->is_in_back_forward_cache());
EXPECT_FALSE(rfh_b->is_in_back_forward_cache());
// 3) Go back to A.
web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(rfh_a, current_frame_host());
EXPECT_FALSE(rfh_a->is_in_back_forward_cache());
EXPECT_TRUE(rfh_b->is_in_back_forward_cache());
// 4) Go forward to B.
web_contents()->GetController().GoForward();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(rfh_b, current_frame_host());
EXPECT_TRUE(rfh_a->is_in_back_forward_cache());
EXPECT_FALSE(rfh_b->is_in_back_forward_cache());
// 5) Go back to A.
web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(rfh_a, current_frame_host());
EXPECT_FALSE(rfh_a->is_in_back_forward_cache());
EXPECT_TRUE(rfh_b->is_in_back_forward_cache());
// 6) Go forward to B.
web_contents()->GetController().GoForward();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(rfh_b, current_frame_host());
EXPECT_TRUE(rfh_a->is_in_back_forward_cache());
EXPECT_FALSE(rfh_b->is_in_back_forward_cache());
EXPECT_FALSE(delete_rfh_a.deleted());
EXPECT_FALSE(delete_rfh_b.deleted());
}
// The current page can't enter the BackForwardCache if another page can script
// it. This can happen when one document opens a popup using window.open() for
// instance. It prevents the BackForwardCache from being used.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, WindowOpen) {
// This test assumes cross-site navigation staying in the same
// BrowsingInstance to use a different SiteInstance. Otherwise, it will
// timeout at step 2).
if (!SiteIsolationPolicy::UseDedicatedProcessesForAllSites())
return;
ASSERT_TRUE(embedded_test_server()->Start());
const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
const GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
// 1) Navigate to A and open a popup.
EXPECT_TRUE(NavigateToURL(shell(), url_a));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_rfh_a(rfh_a);
EXPECT_EQ(1u, rfh_a->GetSiteInstance()->GetRelatedActiveContentsCount());
Shell* popup = OpenPopup(rfh_a, url_a, "");
EXPECT_EQ(2u, rfh_a->GetSiteInstance()->GetRelatedActiveContentsCount());
// 2) Navigate to B. The previous document can't enter the BackForwardCache,
// because of the popup.
EXPECT_TRUE(ExecJs(rfh_a, JsReplace("location = $1;", url_b.spec())));
delete_rfh_a.WaitUntilDeleted();
RenderFrameHostImpl* rfh_b = current_frame_host();
EXPECT_EQ(2u, rfh_b->GetSiteInstance()->GetRelatedActiveContentsCount());
// 3) Go back to A. The previous document can't enter the BackForwardCache,
// because of the popup.
RenderFrameDeletedObserver delete_rfh_b(rfh_b);
EXPECT_TRUE(ExecJs(rfh_b, "history.back();"));
delete_rfh_b.WaitUntilDeleted();
// 4) Make the popup drop the window.opener connection. It happens when the
// user does an omnibox-initiated navigation, which happens in a new
// BrowsingInstance.
RenderFrameHostImpl* rfh_a_new = current_frame_host();
EXPECT_EQ(2u, rfh_a_new->GetSiteInstance()->GetRelatedActiveContentsCount());
NavigateToURL(popup, url_b);
EXPECT_EQ(1u, rfh_a_new->GetSiteInstance()->GetRelatedActiveContentsCount());
// 5) Navigate to B again. In theory, the current document should be able to
// enter the BackForwardCache. It can't, because the RenderFrameHost still
// "remembers" it had access to the popup. See
// RenderFrameHostImpl::scheduler_tracked_features().
RenderFrameDeletedObserver delete_rfh_a_new(rfh_a_new);
EXPECT_TRUE(ExecJs(rfh_a_new, JsReplace("location = $1;", url_b.spec())));
EXPECT_TRUE(WaitForLoadStop(web_contents()));
delete_rfh_a_new.WaitUntilDeleted();
// 6) Go back to A. The current document can finally enter the
// BackForwardCache, because it is alone in its BrowsingInstance and has never
// been related to any other document.
RenderFrameHostImpl* rfh_b_new = current_frame_host();
RenderFrameDeletedObserver delete_rfh_b_new(rfh_b_new);
EXPECT_TRUE(ExecJs(rfh_b_new, "history.back();"));
EXPECT_TRUE(WaitForLoadStop(web_contents()));
EXPECT_FALSE(delete_rfh_b_new.deleted());
EXPECT_TRUE(rfh_b_new->is_in_back_forward_cache());
}
// Navigate from A(B) to C and go back.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, BasicIframe) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL url_a(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
const GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html"));
// 1) Navigate to A(B).
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_rfh_a(rfh_a);
RenderFrameDeletedObserver delete_rfh_b(rfh_b);
// 2) Navigate to C.
EXPECT_TRUE(NavigateToURL(shell(), url_c));
RenderFrameHostImpl* rfh_c = current_frame_host();
RenderFrameDeletedObserver delete_rfh_c(rfh_c);
EXPECT_FALSE(delete_rfh_a.deleted());
EXPECT_FALSE(delete_rfh_b.deleted());
EXPECT_TRUE(rfh_a->is_in_back_forward_cache());
EXPECT_TRUE(rfh_b->is_in_back_forward_cache());
EXPECT_FALSE(rfh_c->is_in_back_forward_cache());
// 3) Go back to A(B).
web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_FALSE(delete_rfh_a.deleted());
EXPECT_FALSE(delete_rfh_b.deleted());
EXPECT_FALSE(delete_rfh_c.deleted());
EXPECT_EQ(rfh_a, current_frame_host());
EXPECT_FALSE(rfh_a->is_in_back_forward_cache());
EXPECT_FALSE(rfh_b->is_in_back_forward_cache());
EXPECT_TRUE(rfh_c->is_in_back_forward_cache());
}
// Ensure flushing the BackForwardCache works properly.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, BackForwardCacheFlush) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
const GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
// 1) Navigate to A.
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_rfh_a(rfh_a);
// 2) Navigate to B.
EXPECT_TRUE(NavigateToURL(shell(), url_b));
RenderFrameHostImpl* rfh_b = current_frame_host();
RenderFrameDeletedObserver delete_rfh_b(rfh_b);
EXPECT_FALSE(delete_rfh_a.deleted());
// 3) Flush A.
web_contents()->GetController().back_forward_cache().Flush();
EXPECT_TRUE(delete_rfh_a.deleted());
EXPECT_FALSE(delete_rfh_b.deleted());
// 4) Go back to a new A.
web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_TRUE(delete_rfh_a.deleted());
EXPECT_FALSE(delete_rfh_b.deleted());
// 5) Flush B.
web_contents()->GetController().back_forward_cache().Flush();
EXPECT_TRUE(delete_rfh_b.deleted());
}
// Check the visible URL in the omnibox is properly updated when restoring a
// document from the BackForwardCache.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, VisibleURL) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
const GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
// 1) Go to A.
EXPECT_TRUE(NavigateToURL(shell(), url_a));
// 2) Go to B.
EXPECT_TRUE(NavigateToURL(shell(), url_b));
// 3) Go back to A.
web_contents()->GetController().GoBack();
EXPECT_EQ(url_a, web_contents()->GetVisibleURL());
// 4) Go forward to B.
web_contents()->GetController().GoForward();
EXPECT_EQ(url_b, web_contents()->GetVisibleURL());
}
// Test documents are evicted from the BackForwardCache at some point.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CacheEviction) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
const GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url_a)); // BackForwardCache size is 0.
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_rfh_a(rfh_a);
EXPECT_TRUE(NavigateToURL(shell(), url_b)); // BackForwardCache size is 1.
RenderFrameHostImpl* rfh_b = current_frame_host();
RenderFrameDeletedObserver delete_rfh_b(rfh_b);
// The number of document the BackForwardCache can hold per tab.
static constexpr size_t kBackForwardCacheLimit = 3;
for (size_t i = 2; i < kBackForwardCacheLimit; ++i) {
NavigateToURL(shell(), i % 2 ? url_b : url_a);
// After |i+1| navigations, |i| documents went into the BackForwardCache.
// When |i| is greater than the BackForwardCache size limit, they are
// evicted:
EXPECT_EQ(i >= kBackForwardCacheLimit + 1, delete_rfh_a.deleted());
EXPECT_EQ(i >= kBackForwardCacheLimit + 2, delete_rfh_b.deleted());
}
}
// Similar to BackForwardCacheBrowserTest.SubframeSurviveCache*
// Test case: a1(b2) -> c3 -> a1(b2)
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, SubframeSurviveCache1) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL url_a(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
const GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html"));
std::vector<RenderFrameDeletedObserver*> rfh_observer;
// 1) Navigate to a1(b2).
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';"));
// 2) Navigate to c3.
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()));
// 3) Go back to a1(b2).
web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
ASSERT_THAT(rfh_observer, Each(Not(Deleted())));
EXPECT_THAT(Elements({a1, b2}), Each(Not(InBackForwardCache())));
EXPECT_THAT(c3, InBackForwardCache());
// Even after a new IPC round trip with the renderer, b2 must still be alive.
EXPECT_EQ("I am alive", EvalJs(b2, "window.alive"));
EXPECT_FALSE(b2_observer.deleted());
}
// Similar to BackForwardCacheBrowserTest.SubframeSurviveCache*
// Test case: a1(b2) -> b3 -> a1(b2).
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, SubframeSurviveCache2) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL url_a(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
const GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
std::vector<RenderFrameDeletedObserver*> rfh_observer;
// 1) Navigate to a1(b2).
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';"));
// 2) Navigate to b3.
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()));
// 3) Go back to a1(b2).
web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->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());
// Even after a new IPC round trip with the renderer, b2 must still be alive.
EXPECT_EQ("I am alive", EvalJs(b2, "window.alive"));
EXPECT_FALSE(b2_observer.deleted());
}
// Similar to BackForwardCacheBrowserTest.tSubframeSurviveCache*
// Test case: a1(b2) -> b3(a4) -> a1(b2) -> b3(a4)
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, SubframeSurviveCache3) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL url_a(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
const GURL url_b(embedded_test_server()->GetURL(
"b.com", "/cross_site_iframe_factory.html?b(a)"));
std::vector<RenderFrameDeletedObserver*> rfh_observer;
// 1) Navigate to a1(b2).
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';"));
// 2) Navigate to b3(a4)
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';"));
// 3) Go back to a1(b2).
web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->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()));
// Even after a new IPC round trip with the renderer, b2 must still be alive.
EXPECT_EQ("I am alive", EvalJs(b2, "window.alive"));
EXPECT_FALSE(b2_observer.deleted());
// 4) Go forward to b3(a4).
web_contents()->GetController().GoForward();
EXPECT_TRUE(WaitForLoadStop(shell()->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())));
// Even after a new IPC round trip with the renderer, a4 must still be alive.
EXPECT_EQ("I am alive", EvalJs(a4, "window.alive"));
EXPECT_FALSE(a4_observer.deleted());
}
// Similar to BackForwardCacheBrowserTest.SubframeSurviveCache*
// Test case: a1(b2) -> b3 -> a4 -> b5 -> a1(b2).
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, SubframeSurviveCache4) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL url_ab(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
const GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
std::vector<RenderFrameDeletedObserver*> rfh_observer;
// 1) Navigate to a1(b2).
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';"));
// 2) Navigate to b3.
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()));
// 3) Navigate to a4.
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())));
// 4) Navigate to b5
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()));
// 3) Go back to a1(b2).
web_contents()->GetController().GoToOffset(-3);
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
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())));
// Even after a new IPC round trip with the renderer, b2 must still be alive.
EXPECT_EQ("I am alive", EvalJs(b2, "window.alive"));
EXPECT_FALSE(b2_observer.deleted());
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DisallowedFeatureOnPage) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
const GURL url_b(embedded_test_server()->GetURL(
"b.com", "/back_forward_cache/page_with_dedicated_worker.html"));
// Navigate to page A which is cacheable.
NavigateToURL(shell(), url_a);
EXPECT_EQ(url_a, web_contents()->GetVisibleURL());
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_rfh_a(rfh_a);
// Navigate to page B which has an unsupported feature for bfcache.
NavigateToURL(shell(), url_b);
EXPECT_EQ(url_b, web_contents()->GetVisibleURL());
RenderFrameHostImpl* rfh_b = current_frame_host();
RenderFrameDeletedObserver delete_rfh_b(rfh_b);
// Page A should now be cached.
EXPECT_FALSE(delete_rfh_a.deleted());
EXPECT_TRUE(rfh_a->is_in_back_forward_cache());
// Navigate back to page A.
web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(url_a, web_contents()->GetVisibleURL());
// Page B with the unsupported feature should have been deleted (not cached).
delete_rfh_b.WaitUntilDeleted();
}
// Check that unload event handlers are not dispatched when the page goes
// into BackForwardCache.
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
ConfirmUnloadEventNotFired) {
ASSERT_TRUE(embedded_test_server()->Start());
const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
const GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
// 1) Navigate to A.
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_rfh_a(rfh_a);
// 2) Set unload handler and check the title.
EXPECT_TRUE(ExecJs(rfh_a,
"document.title = 'loaded!';"
"window.addEventListener('unload', () => {"
" document.title = 'unloaded!';"
"});"));
{
base::string16 title_when_loaded = base::UTF8ToUTF16("loaded!");
TitleWatcher title_watcher(web_contents(), title_when_loaded);
EXPECT_EQ(title_watcher.WaitAndGetTitle(), title_when_loaded);
}
// 3) Navigate to B.
EXPECT_TRUE(NavigateToURL(shell(), url_b));
RenderFrameHostImpl* rfh_b = current_frame_host();
RenderFrameDeletedObserver delete_rfh_b(rfh_b);
EXPECT_FALSE(delete_rfh_a.deleted());
EXPECT_TRUE(rfh_a->is_in_back_forward_cache());
EXPECT_FALSE(rfh_b->is_in_back_forward_cache());
// 4) Go back to A and check the title again.
web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(web_contents()));
EXPECT_FALSE(delete_rfh_a.deleted());
EXPECT_FALSE(delete_rfh_b.deleted());
EXPECT_EQ(rfh_a, current_frame_host());
EXPECT_FALSE(rfh_a->is_in_back_forward_cache());
EXPECT_TRUE(rfh_b->is_in_back_forward_cache());
{
base::string16 title_when_loaded = base::UTF8ToUTF16("loaded!");
TitleWatcher title_watcher(web_contents(), title_when_loaded);
EXPECT_EQ(title_watcher.WaitAndGetTitle(), title_when_loaded);
}
}
} // namespace content