blob: 017921ff388c79a928a1980770bb52ef9021dc73 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/test/scoped_feature_list.h"
#include "content/browser/renderer_host/navigation_controller_impl.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_frame_host_manager_browsertest.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/content_navigation_policy.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.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/content_mock_cert_verifier.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/render_document_feature.h"
#include "content/test/test_content_browser_client.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/network/public/cpp/features.h"
#include "third_party/blink/public/common/action_after_pagehide.h"
using blink::ActionAfterPagehide;
namespace content {
namespace {
content::RenderFrameHostChangedCallback GetAsyncScriptExecutorCallback(
std::string callback_script) {
return base::BindOnce(
[](std::string callback_script, RenderFrameHost* old_host,
RenderFrameHost* new_host) {
ExecuteScriptAsync(old_host, callback_script);
},
callback_script);
}
// DO NOT USE THIS FUNCTION, use GetAsyncScriptExecutorCallback() instead.
// GetScriptExecutorCallback must not be used, because it forces waiting for a
// browser <-> renderer IPC roundtrip while being in the middle of a very
// complex operation: swapping the current RenderFrameHost
content::RenderFrameHostChangedCallback GetScriptExecutorCallback(
std::string callback_script) {
return base::BindOnce(
[](std::string callback_script, RenderFrameHost* old_host,
RenderFrameHost* new_host) {
EXPECT_TRUE(ExecuteScript(old_host, callback_script));
},
callback_script);
}
// Helper function for error page navigations that makes sure that the last
// committed origin on |node| is an opaque origin with a precursor that matches
// |url|'s origin.
// Returns true if the frame has an opaque origin with the expected precursor
// information. Otherwise returns false.
bool IsOriginOpaqueAndCompatibleWithURL(FrameTreeNode* node, const GURL& url) {
url::Origin frame_origin =
node->current_frame_host()->GetLastCommittedOrigin();
if (!frame_origin.opaque()) {
LOG(ERROR) << "Frame origin was not opaque. " << frame_origin;
return false;
}
const GURL url_origin = url.DeprecatedGetOriginAsURL();
const GURL precursor_origin =
frame_origin.GetTupleOrPrecursorTupleIfOpaque().GetURL();
if (url_origin != precursor_origin) {
LOG(ERROR) << "url_origin '" << url_origin << "' != precursor_origin '"
<< precursor_origin << "'";
return false;
}
return true;
}
bool IsMainFrameOriginOpaqueAndCompatibleWithURL(Shell* shell,
const GURL& url) {
return IsOriginOpaqueAndCompatibleWithURL(
static_cast<WebContentsImpl*>(shell->web_contents())
->GetPrimaryFrameTree()
.root(),
url);
}
bool HasErrorPageSiteInfo(SiteInstance* site_instance) {
auto* site_instance_impl = static_cast<SiteInstanceImpl*>(site_instance);
return site_instance_impl->GetSiteInfo().is_error_page();
}
} // namespace
class ProactivelySwapBrowsingInstancesCrossSiteSwapProcessTest
: public RenderFrameHostManagerTest {
public:
ProactivelySwapBrowsingInstancesCrossSiteSwapProcessTest() {
std::map<std::string, std::string> parameters;
parameters[kProactivelySwapBrowsingInstanceLevelParameterName] =
"CrossSiteSwapProcess";
feature_list_.InitAndEnableFeatureWithParameters(
features::kProactivelySwapBrowsingInstance, parameters);
}
~ProactivelySwapBrowsingInstancesCrossSiteSwapProcessTest() override =
default;
private:
base::test::ScopedFeatureList feature_list_;
};
// Test to ensure that the error page navigation does not change
// BrowsingInstances when window.open is present.
IN_PROC_BROWSER_TEST_P(
ProactivelySwapBrowsingInstancesCrossSiteSwapProcessTest,
ErrorPageNavigationWithWindowOpenDoesNotChangeBrowsingInstance) {
StartEmbeddedServer();
GURL url(embedded_test_server()->GetURL("/title1.html"));
GURL error_url(embedded_test_server()->GetURL("/empty.html"));
std::unique_ptr<URLLoaderInterceptor> url_interceptor =
SetupRequestFailForURL(error_url);
NavigationControllerImpl& nav_controller =
static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
// Start with a successful navigation to a document and verify there is
// only one entry in session history.
EXPECT_TRUE(NavigateToURL(shell(), url));
scoped_refptr<SiteInstance> success_site_instance =
shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_EQ(1, nav_controller.GetEntryCount());
// Open a new window to ensure that we can't swap BrowsingInstances
// as we have to preserve the scripting relationship.
EXPECT_TRUE(OpenPopup(shell(), GURL(url::kAboutBlankURL), ""));
// Navigate to an url resulting in an error page and ensure a new entry
// was added to session history.
EXPECT_FALSE(NavigateToURL(shell(), error_url));
EXPECT_EQ(2, nav_controller.GetEntryCount());
scoped_refptr<SiteInstance> initial_instance =
shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_TRUE(HasErrorPageSiteInfo(initial_instance.get()));
EXPECT_TRUE(IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
EXPECT_TRUE(success_site_instance->IsRelatedSiteInstance(
shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));
// Reload of the error page that still results in an error should stay in
// the related SiteInstance. Ensure this works for both browser-initiated
// reloads and renderer-initiated ones.
{
TestNavigationObserver reload_observer(shell()->web_contents());
shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false);
reload_observer.Wait();
EXPECT_FALSE(reload_observer.last_navigation_succeeded());
EXPECT_EQ(2, nav_controller.GetEntryCount());
EXPECT_TRUE(
IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
EXPECT_TRUE(success_site_instance->IsRelatedSiteInstance(
shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));
}
{
TestNavigationObserver reload_observer(shell()->web_contents());
EXPECT_TRUE(ExecuteScript(shell(), "location.reload();"));
reload_observer.Wait();
EXPECT_FALSE(reload_observer.last_navigation_succeeded());
EXPECT_EQ(2, nav_controller.GetEntryCount());
EXPECT_TRUE(
IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
EXPECT_TRUE(success_site_instance->IsRelatedSiteInstance(
shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));
}
// Allow the navigation to succeed and ensure the new SiteInstance
// stays related.
url_interceptor.reset();
{
TestNavigationObserver reload_observer(shell()->web_contents());
EXPECT_TRUE(ExecuteScript(shell(), "location.reload();"));
reload_observer.Wait();
EXPECT_TRUE(reload_observer.last_navigation_succeeded());
EXPECT_EQ(2, nav_controller.GetEntryCount());
EXPECT_TRUE(success_site_instance->IsRelatedSiteInstance(
shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));
}
}
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesCrossSiteSwapProcessTest,
ReloadShouldNotChangeBrowsingInstance) {
StartEmbeddedServer();
GURL url(embedded_test_server()->GetURL("/title1.html"));
// 1) Navigate to the page.
EXPECT_TRUE(NavigateToURL(shell(), url));
scoped_refptr<SiteInstance> site_instance =
shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
// 2) Reload page.
shell()->web_contents()->GetPrimaryMainFrame()->Reload();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
// Ensure that we do not change BrowsingInstances for reload.
// We should keep this even when we start swapping BrowsingInstances
// for same-site navigations.
EXPECT_EQ(site_instance,
shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance());
}
class ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest
: public RenderFrameHostManagerTest {
public:
ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest() {
std::map<std::string, std::string> parameters;
parameters[kProactivelySwapBrowsingInstanceLevelParameterName] =
"CrossSiteReuseProcess";
feature_list_.InitAndEnableFeatureWithParameters(
features::kProactivelySwapBrowsingInstance, parameters);
}
~ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest() override =
default;
void SetUpCommandLine(base::CommandLine* command_line) override {
RenderFrameHostManagerTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kDisableSiteIsolation);
if (AreAllSitesIsolatedForTesting()) {
LOG(WARNING)
<< "This test should be run without strict site isolation. "
<< "It's going to fail when --site-per-process is specified.";
}
}
private:
base::test::ScopedFeatureList feature_list_;
};
// ProactivelySwapBrowsingInstance makes us swap BrowsingInstances for
// renderer-initiated navigations, which we normally would've kept in the same
// BrowsingInstance as before - which means we can keep the old process because
// we would've continued using that process before anyways.
IN_PROC_BROWSER_TEST_P(
ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest,
RendererInitiatedCrossSiteNavigationReusesProcess) {
if (AreAllSitesIsolatedForTesting())
return;
ASSERT_TRUE(embedded_test_server()->Start());
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// Navigate to A.
EXPECT_TRUE(NavigateToURL(shell(), a_url));
scoped_refptr<SiteInstanceImpl> a_site_instance =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Navigate to B. The navigation is document/renderer initiated.
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url));
scoped_refptr<SiteInstanceImpl> b_site_instance =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Check that A and B are in different BrowsingInstances but have the same
// renderer process. When default SiteInstances are enabled, A and B are
// both default SiteInstances of different BrowsingInstances.
EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
EXPECT_EQ(a_site_instance->GetProcess(), b_site_instance->GetProcess());
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
a_site_instance->IsDefaultSiteInstance());
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
b_site_instance->IsDefaultSiteInstance());
}
// Different from renderer-initiated cross-site navigations, browser-initiated
// cross-site navigations do swap BrowsingInstances and processes without
// ProactivelySwapBrowsingInstance. Because of that, we shouldn't reuse the
// process for the new BrowsingInstance.
IN_PROC_BROWSER_TEST_P(
ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest,
BrowserInitiatedCrossSiteNavigationDoesNotReuseProcess) {
if (AreAllSitesIsolatedForTesting())
return;
ASSERT_TRUE(embedded_test_server()->Start());
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// Navigate to A.
EXPECT_TRUE(NavigateToURL(shell(), a_url));
scoped_refptr<SiteInstanceImpl> a_site_instance =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Navigate to B. The navigation is browser initiated.
EXPECT_TRUE(NavigateToURL(shell(), b_url));
scoped_refptr<SiteInstanceImpl> b_site_instance =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Check that A and B are in different BrowsingInstances and renderer
// processes. When default SiteInstances are enabled, A and B are
// both default SiteInstances of different BrowsingInstances.
EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
EXPECT_NE(a_site_instance->GetProcess(), b_site_instance->GetProcess());
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
a_site_instance->IsDefaultSiteInstance());
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
b_site_instance->IsDefaultSiteInstance());
}
// A test ContentBrowserClient implementation that enforce process-per-site mode
// if |should_use_process_per_site_| is true. It is used to verify that we don't
// reuse the current page's renderer process when navigating to sites that uses
// process-per-site.
class ProcessPerSiteContentBrowserClient : public TestContentBrowserClient {
public:
ProcessPerSiteContentBrowserClient() = default;
ProcessPerSiteContentBrowserClient(
const ProcessPerSiteContentBrowserClient&) = delete;
ProcessPerSiteContentBrowserClient& operator=(
const ProcessPerSiteContentBrowserClient&) = delete;
void SetShouldUseProcessPerSite(bool should_use_process_per_site) {
should_use_process_per_site_ = should_use_process_per_site;
}
bool ShouldUseProcessPerSite(BrowserContext* browser_context,
const GURL& site_url) override {
return should_use_process_per_site_;
}
private:
bool should_use_process_per_site_ = false;
};
// We should not reuse the current process on renderer-initiated navigations to
// sites that needs to use process-per-site, and should create a new process for
// the site if there isn't already a process for that site.
IN_PROC_BROWSER_TEST_P(
ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest,
RendererInitiatedCrossSiteNavigationToProcessPerSiteURLCreatesNewProcess) {
if (AreAllSitesIsolatedForTesting())
return;
ASSERT_TRUE(embedded_test_server()->Start());
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
GURL c_url(embedded_test_server()->GetURL("c.com", "/title1.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
ProcessPerSiteContentBrowserClient content_browser_client;
ContentBrowserClient* old_client =
SetBrowserClientForTesting(&content_browser_client);
// Navigate to A.
EXPECT_TRUE(NavigateToURL(shell(), a_url));
scoped_refptr<SiteInstanceImpl> a_site_instance =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
RenderProcessHost* original_process = a_site_instance->GetProcess();
// Navigate to B. The navigation is document/renderer initiated.
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url));
scoped_refptr<SiteInstanceImpl> b_site_instance =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Check that A and B are in different BrowsingInstances but have the same
// renderer process.
EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
EXPECT_EQ(b_site_instance->GetProcess(), original_process);
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
a_site_instance->IsDefaultSiteInstance());
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
b_site_instance->IsDefaultSiteInstance());
// Make sure we will use process-per-site for C.
// Note this is enforcing process-per-site for all sites, which is why we turn
// it off right after the navigation to C. We might reconsider after
// crbug.com/1062211 is fixed.
content_browser_client.SetShouldUseProcessPerSite(true);
// Navigate to C. The navigation is document/renderer initiated.
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), c_url));
scoped_refptr<SiteInstanceImpl> c_site_instance =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Check that B and C are in different BrowsingInstances and renderer
// processes.
EXPECT_FALSE(b_site_instance->IsRelatedSiteInstance(c_site_instance.get()));
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
c_site_instance->IsDefaultSiteInstance());
EXPECT_NE(c_site_instance->GetProcess(), original_process);
// C is using the process for C's site.
EXPECT_EQ(c_site_instance->GetProcess(),
RenderProcessHostImpl::GetSoleProcessHostForSite(
c_site_instance->GetIsolationContext(),
c_site_instance->GetSiteInfo()));
// Make sure we will not use process-per-site for B.
content_browser_client.SetShouldUseProcessPerSite(false);
// Navigate to B again. The navigation is document/renderer initiated.
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url));
scoped_refptr<SiteInstanceImpl> b2_site_instance =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
EXPECT_FALSE(b2_site_instance->IsRelatedSiteInstance(c_site_instance.get()));
EXPECT_FALSE(b2_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
b2_site_instance->IsDefaultSiteInstance());
EXPECT_NE(b2_site_instance->GetProcess(), original_process);
// B will reuse C's process here, even though C is process-per-site, because
// neither of them require a dedicated process.
EXPECT_EQ(b2_site_instance->GetProcess(), c_site_instance->GetProcess());
SetBrowserClientForTesting(old_client);
}
// We should not reuse the current process on renderer-initiated navigations to
// sites that needs to use process-per-site, and should use the sole process for
// that site if it already exists.
IN_PROC_BROWSER_TEST_P(
ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest,
RendererInitiatedCrossSiteNavigationToProcessPerSiteURLUsesProcessForSite) {
if (AreAllSitesIsolatedForTesting())
return;
ASSERT_TRUE(embedded_test_server()->Start());
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
ProcessPerSiteContentBrowserClient content_browser_client;
ContentBrowserClient* old_client =
SetBrowserClientForTesting(&content_browser_client);
// Navigate to A.
EXPECT_TRUE(NavigateToURL(shell(), a_url));
scoped_refptr<SiteInstanceImpl> a_site_instance =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
RenderProcessHost* original_process = a_site_instance->GetProcess();
// Create a new process and set it as the sole process host for B.
scoped_refptr<SiteInstanceImpl> placeholder_b_site_instance =
SiteInstanceImpl::CreateForTesting(web_contents->GetBrowserContext(),
b_url);
RenderProcessHost* process_for_b =
RenderProcessHostImpl::CreateRenderProcessHost(
web_contents->GetBrowserContext(), placeholder_b_site_instance.get());
RenderProcessHostImpl::RegisterSoleProcessHostForSite(
process_for_b, placeholder_b_site_instance.get());
EXPECT_EQ(process_for_b,
RenderProcessHostImpl::GetSoleProcessHostForSite(
placeholder_b_site_instance->GetIsolationContext(),
placeholder_b_site_instance->GetSiteInfo()));
// Make sure we will use process-per-site for B.
content_browser_client.SetShouldUseProcessPerSite(true);
// Navigate to B. The navigation is document/renderer initiated.
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url));
scoped_refptr<SiteInstanceImpl> b_site_instance =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Check that A and B are in different BrowsingInstances but B should use the
// sole process assigned to site B.
EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
a_site_instance->IsDefaultSiteInstance());
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
b_site_instance->IsDefaultSiteInstance());
EXPECT_NE(b_site_instance->GetProcess(), original_process);
EXPECT_EQ(b_site_instance->GetProcess(), process_for_b);
EXPECT_EQ(b_site_instance->GetProcess(),
RenderProcessHostImpl::GetSoleProcessHostForSite(
b_site_instance->GetIsolationContext(),
b_site_instance->GetSiteInfo()));
SetBrowserClientForTesting(old_client);
}
// We should not reuse the current process on renderer-initiated navigations to
// sites that require a dedicated process.
IN_PROC_BROWSER_TEST_P(
ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest,
NavigationToSiteThatRequiresDedicatedProcess) {
if (AreAllSitesIsolatedForTesting())
return;
ASSERT_TRUE(embedded_test_server()->Start());
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// The client will make sure b.com require a dedicated process.
EffectiveURLContentBrowserClient modified_client(
b_url /* url_to_modify */, b_url, true /* requires_dedicated_process */);
ContentBrowserClient* old_client =
SetBrowserClientForTesting(&modified_client);
// 1) Navigate to A.
EXPECT_TRUE(NavigateToURL(shell(), a_url));
scoped_refptr<SiteInstanceImpl> a_site_instance =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
EXPECT_FALSE(a_site_instance->RequiresDedicatedProcess());
// 2) Navigate cross-site to B. The navigation is document/renderer initiated.
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url));
scoped_refptr<SiteInstanceImpl> b_site_instance =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
EXPECT_TRUE(b_site_instance->RequiresDedicatedProcess());
// Check that A and B are in different BrowsingInstances and processes.
EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
EXPECT_NE(a_site_instance->GetProcess(), b_site_instance->GetProcess());
SetBrowserClientForTesting(old_client);
}
// We should not reuse the current process on renderer-initiated navigations to
// sites that require a dedicated process.
IN_PROC_BROWSER_TEST_P(
ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest,
NavigationFromSiteThatRequiresDedicatedProcess) {
if (AreAllSitesIsolatedForTesting())
return;
ASSERT_TRUE(embedded_test_server()->Start());
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// The client will make sure a.com require a dedicated process.
EffectiveURLContentBrowserClient modified_client(
a_url /* url_to_modify */, a_url, true /* requires_dedicated_process */);
ContentBrowserClient* old_client =
SetBrowserClientForTesting(&modified_client);
// 1) Navigate to A.
EXPECT_TRUE(NavigateToURL(shell(), a_url));
scoped_refptr<SiteInstanceImpl> a_site_instance =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
EXPECT_TRUE(a_site_instance->RequiresDedicatedProcess());
// 2) Navigate cross-site to B. The navigation is document/renderer initiated.
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url));
scoped_refptr<SiteInstanceImpl> b_site_instance =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
EXPECT_FALSE(b_site_instance->RequiresDedicatedProcess());
// Check that A and B are in different BrowsingInstances and processes.
EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
EXPECT_NE(a_site_instance->GetProcess(), b_site_instance->GetProcess());
SetBrowserClientForTesting(old_client);
}
class ProactivelySwapBrowsingInstancesSameSiteTest
: public RenderFrameHostManagerTest {
public:
ProactivelySwapBrowsingInstancesSameSiteTest() {
std::map<std::string, std::string> parameters;
parameters[kProactivelySwapBrowsingInstanceLevelParameterName] = "SameSite";
feature_list_.InitAndEnableFeatureWithParameters(
features::kProactivelySwapBrowsingInstance, parameters);
}
~ProactivelySwapBrowsingInstancesSameSiteTest() override = default;
void ExpectTotalCount(base::StringPiece name,
base::HistogramBase::Count count) {
FetchHistogramsFromChildProcesses();
histogram_tester_.ExpectTotalCount(name, count);
}
template <typename T>
void ExpectBucketCount(base::StringPiece name,
T sample,
base::HistogramBase::Count expected_count) {
FetchHistogramsFromChildProcesses();
histogram_tester_.ExpectBucketCount(name, sample, expected_count);
}
protected:
const char* kActionAfterPagehideHistogramName =
"BackForwardCache.SameSite.ActionAfterPagehide2";
private:
base::test::ScopedFeatureList feature_list_;
base::HistogramTester histogram_tester_;
};
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
RendererInitiatedSameSiteNavigationReusesProcess) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
GURL url_2(embedded_test_server()->GetURL("/title2.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// Navigate to title1.html.
EXPECT_TRUE(NavigateToURL(shell(), url_1));
scoped_refptr<SiteInstanceImpl> site_instance_1 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Navigate to title2.html. The navigation is document/renderer initiated.
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url_2));
scoped_refptr<SiteInstanceImpl> site_instance_2 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Check that title1.html and title2.html are in different BrowsingInstances
// but have the same renderer process.
EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());
}
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
BrowserInitiatedSameSiteNavigationReusesProcess) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
GURL url_2(embedded_test_server()->GetURL("/title2.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// 1) Navigate to title1.html.
EXPECT_TRUE(NavigateToURL(shell(), url_1));
scoped_refptr<SiteInstanceImpl> site_instance_1 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// 2) Navigate to title2.html. The navigation is browser initiated.
EXPECT_TRUE(NavigateToURL(shell(), url_2));
scoped_refptr<SiteInstanceImpl> site_instance_2 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Check that title1.html and title2.html are in different BrowsingInstances
// but have the same renderer process.
EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());
// 3) Do a back navigation to title1.html.
shell()->web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_1);
scoped_refptr<SiteInstanceImpl> site_instance_1_history_nav =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// We will reuse the SiteInstance and renderer process of |site_instance_1|.
EXPECT_EQ(site_instance_1_history_nav, site_instance_1);
EXPECT_EQ(site_instance_1_history_nav->GetProcess(),
site_instance_1->GetProcess());
}
// Tests that navigations that started but haven't committed yet will be
// overridden by navigations started later if both navigations created
// speculative RFHs.
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
MultipleNavigationsStarted) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL a1_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL a2_url(embedded_test_server()->GetURL("a.com", "/title2.html"));
GURL b1_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
GURL b2_url(embedded_test_server()->GetURL("b.com", "/title2.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// 1) Navigate to A1.
EXPECT_TRUE(NavigateToURL(shell(), a1_url));
auto* a1_rfh = web_contents->GetPrimaryMainFrame();
FrameTreeNode* node = a1_rfh->frame_tree_node();
scoped_refptr<SiteInstanceImpl> a1_site_instance =
static_cast<SiteInstanceImpl*>(a1_rfh->GetSiteInstance());
// 2) Start same-site navigation to A2 without committing.
TestNavigationManager navigation_a2(shell()->web_contents(), a2_url);
shell()->LoadURL(a2_url);
EXPECT_TRUE(navigation_a2.WaitForRequestStart());
// Verify that we're now navigating to |a2_url|.
EXPECT_EQ(node->navigation_request()->GetURL(), a2_url);
// We should have a speculative RFH for this navigation.
RenderFrameHostImpl* a2_speculative_rfh =
node->render_manager()->speculative_frame_host();
EXPECT_TRUE(a2_speculative_rfh);
EXPECT_NE(a1_rfh, a2_speculative_rfh);
// The speculative RFH should use a different BrowsingInstance than the
// current RFH.
scoped_refptr<SiteInstanceImpl> a2_site_instance =
static_cast<SiteInstanceImpl*>(a2_speculative_rfh->GetSiteInstance());
EXPECT_FALSE(a1_site_instance->IsRelatedSiteInstance(a2_site_instance.get()));
// 3) Start cross-site navigation to B1 without committing.
TestNavigationManager navigation_b1(shell()->web_contents(), b1_url);
shell()->LoadURL(b1_url);
EXPECT_TRUE(navigation_b1.WaitForRequestStart());
// Verify that we're now navigating to |b1_url|.
EXPECT_EQ(node->navigation_request()->GetURL(), b1_url);
// We should have a speculative RFH for this navigation.
RenderFrameHostImpl* b1_speculative_rfh =
node->render_manager()->speculative_frame_host();
EXPECT_TRUE(b1_speculative_rfh);
EXPECT_NE(a1_rfh, b1_speculative_rfh);
// The speculative RFH should use a different BrowsingInstance than the
// current RFH.
scoped_refptr<SiteInstanceImpl> b1_site_instance =
static_cast<SiteInstanceImpl*>(b1_speculative_rfh->GetSiteInstance());
EXPECT_FALSE(a1_site_instance->IsRelatedSiteInstance(b1_site_instance.get()));
// 4) Start same-site navigation to B2 without committing.
TestNavigationManager navigation_b2(shell()->web_contents(), b2_url);
shell()->LoadURL(b2_url);
EXPECT_TRUE(navigation_b2.WaitForRequestStart());
// Verify that we're now navigating to |b2_url|.
EXPECT_EQ(node->navigation_request()->GetURL(), b2_url);
// We should have a speculative RFH for this navigation.
RenderFrameHostImpl* b2_speculative_rfh =
node->render_manager()->speculative_frame_host();
EXPECT_TRUE(b2_speculative_rfh);
EXPECT_NE(a1_rfh, b2_speculative_rfh);
// The speculative RFH should use a different BrowsingInstance than the
// current RFH.
scoped_refptr<SiteInstanceImpl> b2_site_instance =
static_cast<SiteInstanceImpl*>(b2_speculative_rfh->GetSiteInstance());
EXPECT_FALSE(a1_site_instance->IsRelatedSiteInstance(b2_site_instance.get()));
}
// Tests history same-site process reuse:
// 1. Visit A1, A2, B.
// 2. Go back to A2 (should use new process).
// 3. Go back to A1 (should reuse A2's process).
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
HistoryNavigationReusesProcess) {
// This test expects a renderer process to eventually get deleted when we
// navigate away from the page using it, which won't happen if the page is
// kept alive in the back-forward cache. So, we should disable back-forward
// cache for this test.
DisableBackForwardCache(BackForwardCacheImpl::TEST_REQUIRES_NO_CACHING);
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
GURL url_2(embedded_test_server()->GetURL("/title2.html"));
GURL cross_site_url(embedded_test_server()->GetURL("b.com", "/title3.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// 1) Navigate to title1.html.
EXPECT_TRUE(NavigateToURL(shell(), url_1));
scoped_refptr<SiteInstanceImpl> site_instance_1 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// 2) Navigate same-site to title2.html.
EXPECT_TRUE(NavigateToURL(shell(), url_2));
scoped_refptr<SiteInstanceImpl> site_instance_2 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Check that title1.html and title2.html are in different BrowsingInstances
// but have the same renderer process.
EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());
// 3) Navigate cross-site to b.com/title3.html.
RenderFrameDeletedObserver rfh_2_deleted_observer(
web_contents->GetPrimaryMainFrame());
EXPECT_TRUE(NavigateToURL(shell(), cross_site_url));
scoped_refptr<SiteInstanceImpl> site_instance_3 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Wait until the RFH for title2.html got deleted, and check that
// title2.html and b.com/title3.html are in different BrowsingInstances and
// renderer processes (We check this by checking whether |site_instance_2|
// still has a process or not - if it's gone then that means
// |site_instance_3| uses a different process).
rfh_2_deleted_observer.WaitUntilDeleted();
EXPECT_FALSE(site_instance_2->IsRelatedSiteInstance(site_instance_3.get()));
EXPECT_FALSE(site_instance_2->HasProcess());
// 4) Do a back navigation to title2.html.
RenderFrameDeletedObserver rfh_3_deleted_observer(
web_contents->GetPrimaryMainFrame());
shell()->web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_2);
scoped_refptr<SiteInstanceImpl> site_instance_2_history_nav =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// We should use different BrowsingInstances and processes after going back to
// title2.html because it's a cross-site navigation.
rfh_3_deleted_observer.WaitUntilDeleted();
EXPECT_FALSE(site_instance_2_history_nav->IsRelatedSiteInstance(
site_instance_3.get()));
EXPECT_FALSE(site_instance_3->HasProcess());
// 5) Do a back navigation to title1.html.
shell()->web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_1);
scoped_refptr<SiteInstanceImpl> site_instance_1_history_nav =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// We should use different BrowsingInstances for title1.html and title2.html,
// but reuse the process (because in the original navigation, the BI change
// was caused by proactive BI swap).
EXPECT_FALSE(site_instance_1_history_nav->IsRelatedSiteInstance(
site_instance_2_history_nav.get()));
EXPECT_EQ(site_instance_1_history_nav, site_instance_1);
EXPECT_TRUE(site_instance_2_history_nav->HasProcess());
EXPECT_EQ(site_instance_1_history_nav->GetProcess(),
site_instance_2_history_nav->GetProcess());
}
// Tests history same-site process reuse:
// 1. Visit A1, A2, B.
// 2. Go back two entries to A1 (should use new process).
// 3. Go forward to A2 (should reuse A1's process).
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
HistoryNavigationReusesProcess_SkipSameSiteEntry) {
// This test expects a renderer process to eventually get deleted when we
// navigate away from the page using it, which won't happen if the page is
// kept alive in the back-forward cache. So, we should disable back-forward
// cache for this test.
DisableBackForwardCache(BackForwardCacheImpl::TEST_REQUIRES_NO_CACHING);
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
GURL url_2(embedded_test_server()->GetURL("/title2.html"));
GURL cross_site_url(embedded_test_server()->GetURL("b.com", "/title3.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// 1) Navigate to title1.html.
EXPECT_TRUE(NavigateToURL(shell(), url_1));
scoped_refptr<SiteInstanceImpl> site_instance_1 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// 2) Navigate same-site to title2.html.
EXPECT_TRUE(NavigateToURL(shell(), url_2));
scoped_refptr<SiteInstanceImpl> site_instance_2 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Check that title1.html and title2.html are in different BrowsingInstances
// but have the same renderer process.
EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());
// 3) Navigate cross-site to b.com/title3.html.
RenderFrameDeletedObserver rfh_2_deleted_observer(
web_contents->GetPrimaryMainFrame());
EXPECT_TRUE(NavigateToURL(shell(), cross_site_url));
scoped_refptr<SiteInstanceImpl> site_instance_3 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Wait until the RFH for title2.html got deleted, and check that
// title2.html and b.com/title3.html are in different BrowsingInstances and
// renderer processes (We check this by checking whether |site_instance_2|
// still has a process or not - if it's gone then that means
// |site_instance_3| uses a different process).
rfh_2_deleted_observer.WaitUntilDeleted();
EXPECT_FALSE(site_instance_2->IsRelatedSiteInstance(site_instance_3.get()));
EXPECT_FALSE(site_instance_2->HasProcess());
// 4) Navigate back 2 entries to title1.html.
RenderFrameDeletedObserver rfh_3_deleted_observer(
web_contents->GetPrimaryMainFrame());
EXPECT_TRUE(ExecJs(shell(), "history.go(-2)"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_1);
scoped_refptr<SiteInstanceImpl> site_instance_1_history_nav =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// We should use different BrowsingInstances and processes after going back to
// title2.html because it's a cross-site navigation.
rfh_3_deleted_observer.WaitUntilDeleted();
EXPECT_FALSE(site_instance_1_history_nav->IsRelatedSiteInstance(
site_instance_3.get()));
EXPECT_EQ(site_instance_1_history_nav, site_instance_1);
EXPECT_FALSE(site_instance_3->HasProcess());
// 5) Navigate 1 entry forward to title2.html.
EXPECT_TRUE(ExecJs(shell(), "history.go(1)"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_2);
scoped_refptr<SiteInstanceImpl> site_instance_2_history_nav =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// We should use different BrowsingInstances for title1.html and title2.html,
// but reuse the process (because in the original navigation, the BI change
// was caused by proactive BI swap).
EXPECT_FALSE(site_instance_1_history_nav->IsRelatedSiteInstance(
site_instance_2_history_nav.get()));
EXPECT_EQ(site_instance_2_history_nav, site_instance_2);
EXPECT_TRUE(site_instance_1_history_nav->HasProcess());
EXPECT_EQ(site_instance_1_history_nav->GetProcess(),
site_instance_2_history_nav->GetProcess());
}
// Tests history same-site process reuse:
// 1. Visit A1, B, A3.
// 2. Go back two entries to A1 (should use A3's process).
// 3. Go forward to B (should use new process).
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
HistoryNavigationReusesProcess_SkipCrossSiteEntry) {
// This test expects a renderer process to eventually get deleted when we
// navigate away from the page using it, which won't happen if the page is
// kept alive in the back-forward cache. So, we should disable back-forward
// cache for this test.
DisableBackForwardCache(BackForwardCacheImpl::TEST_REQUIRES_NO_CACHING);
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
GURL cross_site_url(embedded_test_server()->GetURL("b.com", "/title2.html"));
GURL url_3(embedded_test_server()->GetURL("/title3.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// 1) Navigate to title1.html.
EXPECT_TRUE(NavigateToURL(shell(), url_1));
scoped_refptr<SiteInstanceImpl> site_instance_1 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
RenderFrameDeletedObserver rfh_1_deleted_observer(
web_contents->GetPrimaryMainFrame());
// 2) Navigate cross-site to b.com/title2.html.
EXPECT_TRUE(NavigateToURL(shell(), cross_site_url));
scoped_refptr<SiteInstanceImpl> site_instance_2 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Check that title1.html and b.com/title2.html are in different
// BrowsingInstances and renderer processes (We check this by checking
// whether |site_instance_1| still has a process or not - if it's gone then
// that means |site_instance_2| uses a different process).
rfh_1_deleted_observer.WaitUntilDeleted();
EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
EXPECT_FALSE(site_instance_1->HasProcess());
// 3) Navigate cross-site to title3.html.
RenderFrameDeletedObserver rfh_2_deleted_observer(
web_contents->GetPrimaryMainFrame());
EXPECT_TRUE(NavigateToURL(shell(), url_3));
scoped_refptr<SiteInstanceImpl> site_instance_3 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Wait until the RFH for title2.html got deleted, and check that
// b.com/title2.html and title3.html are in different BrowsingInstances and
// renderer processes (We check this by checking whether |site_instance_2|
// still has a process or not - if it's gone then that means
// |site_instance_3| uses a different process).
rfh_2_deleted_observer.WaitUntilDeleted();
EXPECT_FALSE(site_instance_2->IsRelatedSiteInstance(site_instance_3.get()));
EXPECT_FALSE(site_instance_2->HasProcess());
// 4) Navigate back 2 entries from title3.html to title1.html.
EXPECT_TRUE(ExecJs(shell(), "history.go(-2)"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_1);
scoped_refptr<SiteInstanceImpl> site_instance_1_history_nav =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// We should use different BrowsingInstances but reuse the process when going
// back from title3.html to title1.html because it's a same-site history
// navigation.
EXPECT_FALSE(site_instance_1_history_nav->IsRelatedSiteInstance(
site_instance_3.get()));
EXPECT_EQ(site_instance_1_history_nav, site_instance_1);
EXPECT_TRUE(site_instance_3->HasProcess());
EXPECT_EQ(site_instance_1_history_nav->GetProcess(),
site_instance_3->GetProcess());
}
// Tests history same-site process reuse:
// 1. Visit A1 (which window.opens A2) then B.
// 2. Visit A3, which should use a new process (can't use A2's process).
// 2. Go back two entries to A1 (should use A2's process - the same process it
// used originally).
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
HistoryNavigationReusesProcessThatIsStillAlive) {
// This test expects a renderer process to eventually get deleted when we
// navigate away from the page using it, which won't happen if the page is
// kept alive in the back-forward cache. So, we should disable back-forward
// cache for this test.
DisableBackForwardCache(BackForwardCacheImpl::TEST_REQUIRES_NO_CACHING);
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
GURL url_to_open(embedded_test_server()->GetURL("/empty.html"));
GURL cross_site_url(embedded_test_server()->GetURL("b.com", "/title2.html"));
GURL url_3(embedded_test_server()->GetURL("/title3.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// 1) Navigate to title1.html and open a popup.
EXPECT_TRUE(NavigateToURL(shell(), url_1));
OpenPopup(shell(), url_to_open, "foo");
scoped_refptr<SiteInstanceImpl> site_instance_1 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// 2) Navigate cross-site to b.com/title2.html.
EXPECT_TRUE(NavigateToURL(shell(), cross_site_url));
scoped_refptr<SiteInstanceImpl> site_instance_2 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Check that title1.html and b.com/title2.html are in different
// BrowsingInstances and renderer processes. title1.html's process will still
// be around because the window it opened earlier is still alive.
EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
EXPECT_TRUE(site_instance_1->HasProcess());
EXPECT_NE(site_instance_1->GetProcess(), site_instance_2->GetProcess());
// 3) Navigate cross-site to title3.html (same-site with title1.html).
RenderFrameDeletedObserver rfh_2_deleted_observer(
web_contents->GetPrimaryMainFrame());
EXPECT_TRUE(NavigateToURL(shell(), url_3));
scoped_refptr<SiteInstanceImpl> site_instance_3 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Wait until the RFH for b.com/title2.html got deleted, and check that
// b.com/title2.html and title3.html are in different BrowsingInstances and
// renderer processes (We check this by checking whether |site_instance_2|
// still has a process or not - if it's gone then that means
// |site_instance_3| uses a different process).
rfh_2_deleted_observer.WaitUntilDeleted();
EXPECT_FALSE(site_instance_2->IsRelatedSiteInstance(site_instance_3.get()));
EXPECT_FALSE(site_instance_2->HasProcess());
// Even though title1.html and title3.html are same-site, they should use
// different processes.
EXPECT_NE(site_instance_1->GetProcess(), site_instance_3->GetProcess());
// 4) Navigate back 2 entries from title3.html to title1.html.
RenderFrameDeletedObserver rfh_3_deleted_observer(
web_contents->GetPrimaryMainFrame());
EXPECT_TRUE(ExecJs(shell(), "history.go(-2)"));
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_1);
scoped_refptr<SiteInstanceImpl> site_instance_1_history_nav =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// We should use different BrowsingInstances and not reuse the process when
// going back from title3.html to title1.html because the original process
// for title1.html is still around (also title3.html shouldn't be able to
// script the window opened by title1.html).
rfh_3_deleted_observer.WaitUntilDeleted();
EXPECT_FALSE(site_instance_1_history_nav->IsRelatedSiteInstance(
site_instance_3.get()));
EXPECT_EQ(site_instance_1_history_nav, site_instance_1);
EXPECT_FALSE(site_instance_3->HasProcess());
}
// If the navigation is same-document or ends up using the same NavigationEntry
// (e.g., enter in omnibox converted to a reload), we should not do a proactive
// BrowsingInstance swap.
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
SameEntryAndSameDocumentNavigationDoesNotSwap) {
ASSERT_TRUE(embedded_test_server()->Start());
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// 1) Navigate to title1.html#foo.
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/title1.html#foo")));
scoped_refptr<SiteInstanceImpl> site_instance_1 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// 2) Navigate from title1.html#foo to title1.html.
// This is a same-document, different-entry navigation.
EXPECT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
scoped_refptr<SiteInstanceImpl> site_instance_2 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Check that #1 and #2 are in the same SiteInstance.
EXPECT_EQ(site_instance_1, site_instance_2);
// 3) Navigate from title1.html to title1.html.
// This is a different-document, same-entry navigation.
EXPECT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
scoped_refptr<SiteInstanceImpl> site_instance_3 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// We should keep the same SiteInstance again.
EXPECT_EQ(site_instance_2, site_instance_3);
// 4) Navigate from title1.html to title1.html#foo.
// This is a same document navigation.
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/title1.html#foo")));
scoped_refptr<SiteInstanceImpl> site_instance_4 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// We should keep the same SiteInstance again.
EXPECT_EQ(site_instance_3, site_instance_4);
// 5) Navigate from title1.html#foo to title1.html#foo.
// This is a different-document, same-entry navigation.
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/title1.html#foo")));
scoped_refptr<SiteInstanceImpl> site_instance_5 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// We should keep the same SiteInstance again.
EXPECT_EQ(site_instance_4, site_instance_5);
// 6) Navigate from title1.html#foo to title1.html#bar.
// This is a same document navigation.
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/title1.html#bar")));
scoped_refptr<SiteInstanceImpl> site_instance_6 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// We should keep the same SiteInstance again.
EXPECT_EQ(site_instance_5, site_instance_6);
// 7) Do a history navigation from title1.html#bar to title1.html#foo.
// This is a same-document, different-entry history navigation.
shell()->web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
scoped_refptr<SiteInstanceImpl> site_instance_7 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// We should keep the same SiteInstance again.
EXPECT_EQ(site_instance_6, site_instance_7);
}
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
ReloadDoesNotSwap) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL("/title1.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
FrameTreeNode* root = web_contents->GetPrimaryFrameTree().root();
// 1) Navigate to title1.html.
EXPECT_TRUE(NavigateToURL(shell(), url));
scoped_refptr<SiteInstanceImpl> site_instance_1 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// 2) Request a reload to happen when the controller becomes active (e.g.
// after the renderer gets killed in background on Android).
NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
ASSERT_FALSE(controller.NeedsReload());
controller.SetNeedsReload();
ASSERT_TRUE(controller.NeedsReload());
// Set the controller as active, triggering the requested reload.
controller.SetActive(true);
EXPECT_TRUE(WaitForLoadStop(web_contents));
ASSERT_FALSE(controller.NeedsReload());
scoped_refptr<SiteInstanceImpl> site_instance_2 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Check that we're still in the same SiteInstance.
EXPECT_EQ(site_instance_1, site_instance_2);
// 3) Trigger reload using Reload().
{
TestNavigationObserver reload_observer(shell()->web_contents());
shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false);
reload_observer.Wait();
EXPECT_TRUE(reload_observer.last_navigation_succeeded());
}
scoped_refptr<SiteInstanceImpl> site_instance_3 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Check that we're still in the same SiteInstance.
EXPECT_EQ(site_instance_2, site_instance_3);
// 4) Trigger reload using location.reload().
{
TestNavigationObserver reload_observer(shell()->web_contents());
EXPECT_TRUE(ExecuteScript(shell(), "location.reload();"));
reload_observer.Wait();
EXPECT_TRUE(reload_observer.last_navigation_succeeded());
}
scoped_refptr<SiteInstanceImpl> site_instance_4 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Check that we're still in the same SiteInstance.
EXPECT_EQ(site_instance_3, site_instance_4);
// 5) Do a replaceState to another URL.
{
TestNavigationObserver observer(web_contents);
std::string script = "history.replaceState({}, '', '/title2.html')";
EXPECT_TRUE(ExecJs(root, script));
observer.Wait();
}
scoped_refptr<SiteInstanceImpl> site_instance_5 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Check that we're still in the same SiteInstance.
EXPECT_EQ(site_instance_4, site_instance_5);
// 6) Reload after a replaceState by simulating the user hitting Enter in the
// omnibox without changing the URL.
{
TestNavigationObserver observer(web_contents);
web_contents->GetController().LoadURL(web_contents->GetLastCommittedURL(),
Referrer(), ui::PAGE_TRANSITION_LINK,
std::string());
observer.Wait();
}
scoped_refptr<SiteInstanceImpl> site_instance_6 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Check that we're still in the same SiteInstance.
EXPECT_EQ(site_instance_5, site_instance_6);
}
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
SwapOnNavigationToPageThatRedirects) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
GURL url_2(embedded_test_server()->GetURL("/title2.html"));
// This is a same-site URL, and will redirect to another same-site URL.
GURL same_site_redirector_url(
embedded_test_server()->GetURL("/server-redirect?" + url_2.spec()));
GURL url_3(embedded_test_server()->GetURL("/title3.html"));
// This is a cross-site URL, but will redirect to a same-site URL.
GURL cross_site_redirector_url(embedded_test_server()->GetURL(
"b.com", "/server-redirect?" + url_3.spec()));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// 1) Navigate to title1.html.
EXPECT_TRUE(NavigateToURL(shell(), url_1));
scoped_refptr<SiteInstanceImpl> site_instance_1 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// 2) Go to a same-site URL that will redirect us same-site to /title2.html.
EXPECT_TRUE(NavigateToURL(shell(), same_site_redirector_url,
url_2 /* expected_commit_url */));
scoped_refptr<SiteInstanceImpl> site_instance_2 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Check that we are using a different BrowsingInstance but still using the
// same renderer process.
EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());
// 3) Go to a cross-site URL that will redirect us same-site to /title3.html.
// Note that we're using a renderer-initiated navigation here. If we do a
// browser-initiated navigation, it will hit the case at crbug.com/1094147
// where we can't reuse |url_2|'s process even though |url_3| is same-site.
// TODO(crbug.com/1094147): Test with browser-initiated navigation too once
// the issue is fixed.
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), cross_site_redirector_url,
url_3 /* expected_commit_url */));
scoped_refptr<SiteInstanceImpl> site_instance_3 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Check that we are using a different BrowsingInstance but still using the
// same renderer process.
EXPECT_FALSE(site_instance_2->IsRelatedSiteInstance(site_instance_3.get()));
EXPECT_EQ(site_instance_2->GetProcess(), site_instance_3->GetProcess());
}
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
DoNotSwapWhenReplacingHistoryEntry) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
GURL url_2(embedded_test_server()->GetURL("/title2.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// 1) Navigate to title1.html.
EXPECT_TRUE(NavigateToURL(shell(), url_1));
scoped_refptr<SiteInstanceImpl> site_instance_1 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// 2) Do a location.replace() to title2.html.
{
TestNavigationObserver navigation_observer(shell()->web_contents(), 1);
EXPECT_TRUE(
ExecJs(shell(), JsReplace("window.location.replace($1)", url_2)));
navigation_observer.Wait();
EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_2);
}
scoped_refptr<SiteInstanceImpl> site_instance_2 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
EXPECT_EQ(site_instance_1, site_instance_2);
}
// When we do a same-document navigation from A to A#foo then a navigation that
// does replacement (e.g., cross-process reload, or location.replace, or other
// client redirects) such that B takes the place of A#foo, we can go back to A
// with the back navigation. In this case, we might want to do a proactive BI
// swap so that page A can be bfcached.
// However, this test is currently disabled because we won't swap on any
// navigation that will replace the current history entry.
// TODO(rakina): Support this case.
IN_PROC_BROWSER_TEST_P(
ProactivelySwapBrowsingInstancesSameSiteTest,
DISABLED_ShouldSwapWhenReplacingEntryWithSameDocumentPreviousEntry) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
GURL url_1_anchor(embedded_test_server()->GetURL("/title1.html#foo"));
GURL url_2(embedded_test_server()->GetURL("/title2.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// 1) Navigate to title1.html.
EXPECT_TRUE(NavigateToURL(shell(), url_1));
scoped_refptr<SiteInstanceImpl> site_instance_1 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// 2) Navigate same-document to title1.html#foo.
EXPECT_TRUE(NavigateToURL(shell(), url_1_anchor));
scoped_refptr<SiteInstanceImpl> site_instance_2 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
EXPECT_EQ(site_instance_1, site_instance_2);
// 3) Do a location.replace() to title2.html.
{
TestNavigationObserver navigation_observer(web_contents, 1);
EXPECT_TRUE(
ExecJs(shell(), JsReplace("window.location.replace($1)", url_2)));
navigation_observer.Wait();
EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
EXPECT_EQ(web_contents->GetLastCommittedURL(), url_2);
}
scoped_refptr<SiteInstanceImpl> site_instance_3 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// We should swap BrowsingInstance here so that the page at url_1 (which is
// now the previous history entry) can be bfcached.
EXPECT_NE(site_instance_2, site_instance_3);
// Assert that a back navigation will go to |url_1|.
{
TestNavigationObserver navigation_observer(web_contents);
web_contents->GetController().GoBack();
navigation_observer.Wait();
EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
EXPECT_EQ(web_contents->GetLastCommittedURL(), url_1);
}
}
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
DoNotSwapWhenRelatedContentsPresent) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
GURL url_2(embedded_test_server()->GetURL("/title2.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// 1) Navigate and open a new window.
EXPECT_TRUE(NavigateToURL(shell(), url_1));
OpenPopup(shell(), url_1, "foo");
scoped_refptr<SiteInstanceImpl> site_instance_1 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// 2) Navigate to title2.html.
EXPECT_TRUE(NavigateToURL(shell(), url_2));
scoped_refptr<SiteInstanceImpl> site_instance_2 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Check that title1.html and title2.html are using the same SiteInstance.
EXPECT_EQ(site_instance_1, site_instance_2);
}
// We should reuse the current process on same-site navigations even if the
// site requires a dedicated process (because we are still in the same site).
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
NavigationToSiteThatRequiresDedicatedProcess) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
GURL url_2(embedded_test_server()->GetURL("/title2.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// Make sure the site require a dedicated process.
EffectiveURLContentBrowserClient modified_client(
url_1 /* url_to_modify */, url_1, /* requires_dedicated_process */ true);
ContentBrowserClient* old_client =
SetBrowserClientForTesting(&modified_client);
// 1) Navigate to A.
EXPECT_TRUE(NavigateToURL(shell(), url_1));
scoped_refptr<SiteInstanceImpl> site_instance_1 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
EXPECT_TRUE(site_instance_1->RequiresDedicatedProcess());
// 2) Navigate cross-site to B. The navigation is document/renderer initiated.
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url_2));
scoped_refptr<SiteInstanceImpl> site_instance_2 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
EXPECT_TRUE(site_instance_2->RequiresDedicatedProcess());
// Check that A and B are in different BrowsingInstances but reuse the same
// process.
EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());
SetBrowserClientForTesting(old_client);
}
// Tests that pagehide handlers of the old RFH are run during the commit
// of the new RFH when swapping RFH for same-site navigations due to proactive
// BrowsingInstance swap.
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
PagehideRunsDuringCommit) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
GURL url_2(embedded_test_server()->GetURL("/local_storage.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// 1) Navigate to title1.html.
EXPECT_TRUE(NavigateToURL(shell(), url_1));
RenderFrameHostImpl* main_frame_1 = web_contents->GetPrimaryMainFrame();
// Create a pagehide handler that sets item "pagehide_storage" in
// localStorage.
EXPECT_TRUE(ExecJs(
main_frame_1,
base::StringPrintf(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 != %s) {
localStorage.setItem('pagehide_storage', 'wrong_persisted');
} else {
localStorage.setItem('pagehide_storage',
'dispatched_once');
}
dispatched_pagehide = true;
})",
IsBackForwardCacheEnabled() ? "true" : "false")));
// 2) Navigate to local_storage.html.
RenderFrameDeletedObserver main_frame_1_deleted_observer(main_frame_1);
EXPECT_TRUE(NavigateToURL(shell(), url_2));
// Check that title1.html and local_storage.html are in different RFHs.
RenderFrameHostImpl* main_frame_2 = web_contents->GetPrimaryMainFrame();
EXPECT_NE(main_frame_1, main_frame_2);
// Check that the value set by |main_frame_1|'s pagehide handler can be
// accessed by |main_frame_2| at load time (the first time the new page runs
// scripts), setting the |pagehide_storage_at_load| variable correctly.
EXPECT_EQ("dispatched_once",
EvalJs(main_frame_2, "pagehide_storage_at_load"));
// Check that the value for 'pagehide_storage' stays the same after
// |main_frame_2| finished loading (or |main_frame_1| deleted if bfcache is
// not enabled).
if (!IsBackForwardCacheEnabled())
main_frame_1_deleted_observer.WaitUntilDeleted();
EXPECT_EQ("dispatched_once",
EvalJs(main_frame_2, "localStorage.getItem('pagehide_storage')"));
}
// Tests that visibilitychange handlers of the old RFH are run during the commit
// of the new RFH when swapping RFH for same-site navigations due to proactive
// BrowsingInstance swap.
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
VisibilitychangeRunsDuringCommit) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
GURL url_2(embedded_test_server()->GetURL("/local_storage.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// 1) Navigate to title1.html.
EXPECT_TRUE(NavigateToURL(shell(), url_1));
RenderFrameHostImpl* main_frame_1 = web_contents->GetPrimaryMainFrame();
// Create a visibilitychange handler that sets item "visibilitychange_storage"
// localStorage.
EXPECT_TRUE(ExecJs(main_frame_1, R"(
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;
})"));
// 2) Navigate to local_storage.html.
RenderFrameDeletedObserver main_frame_1_deleted_observer(main_frame_1);
EXPECT_TRUE(NavigateToURL(shell(), url_2));
// Check that title1.html and local_storage.html are in different RFHs.
RenderFrameHostImpl* main_frame_2 = web_contents->GetPrimaryMainFrame();
EXPECT_NE(main_frame_1, main_frame_2);
// Check that the value set by |main_frame_1|'s pagehide handler can be
// accessed by |main_frame_2| at load time (the first time the new page runs
// scripts), setting the |visibilitychange_storage_at_load| variable
// correctly.
EXPECT_EQ("dispatched_once",
EvalJs(main_frame_2, "visibilitychange_storage_at_load"));
// Check that the value for 'visibilitychange_storage' stays the same after
// |main_frame_2| finished loading (or |main_frame_1| got deleted, if bfcache
// is not enabled).
if (!IsBackForwardCacheEnabled())
main_frame_1_deleted_observer.WaitUntilDeleted();
EXPECT_EQ(
"dispatched_once",
EvalJs(main_frame_2, "localStorage.getItem('visibilitychange_storage')"));
}
// Tests that unload handlers of the old RFH are run during commit of the new
// RFH when swapping RFH for same-site navigations due to proactive
// BrowsingInstance swap.
// TODO(crbug.com/1110744): support this.
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
DISABLED_UnloadRunsDuringCommit) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
GURL url_2(embedded_test_server()->GetURL("/local_storage.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// 1) Navigate to title1.html.
EXPECT_TRUE(NavigateToURL(shell(), url_1));
RenderFrameHostImpl* main_frame_1 = web_contents->GetPrimaryMainFrame();
// Create an unload handler that sets item "unload_storage" in localStorage.
EXPECT_TRUE(ExecJs(main_frame_1, R"(
localStorage.setItem('unload_storage', 'not_dispatched');
var dispatched_unload = false;
window.onunload = function(e) {
if (dispatched_unload) {
// We shouldn't dispatch unload more than once.
localStorage.setItem('unload_storage',
'dispatched_more_than_once');
} else {
localStorage.setItem('unload_storage', 'dispatched_once');
}
dispatched_unload = true;
};)"));
// 2) Navigate to local_storage.html.
RenderFrameDeletedObserver main_frame_1_deleted_observer(main_frame_1);
EXPECT_TRUE(NavigateToURL(shell(), url_2));
// Check that title1.html and local_storage.html are in different RFHs.
RenderFrameHostImpl* main_frame_2 = web_contents->GetPrimaryMainFrame();
EXPECT_NE(main_frame_1, main_frame_2);
// Check that the value set by |main_frame_1|'s unload handler can be
// accessed by |main_frame_2| at load time (the first time the new page runs
// scripts), setting the |unload_storage_at_load| variable correctly.
EXPECT_EQ("dispatched_once", EvalJs(main_frame_2, "unload_storage_at_load"));
// Check that the value for 'unload_storage' stays the same after
// |main_frame_2| finished loading (or |main_frame_1| got deleted, if bfcache
// is not enabled).
if (!IsBackForwardCacheEnabled())
main_frame_1_deleted_observer.WaitUntilDeleted();
EXPECT_EQ("dispatched_once",
EvalJs(main_frame_2, "localStorage.getItem('unload_storage')"));
}
// Tests that pagehide and visibilitychange handlers of a subframe in the old
// page are run during the commit of a new main RFH when swapping RFH for
// same-site navigations due to proactive BrowsingInstance swap.
IN_PROC_BROWSER_TEST_P(
ProactivelySwapBrowsingInstancesSameSiteTest,
PagehideAndVisibilitychangeInSubframesAreRunDuringCommit) {
if (IsBackForwardCacheEnabled()) {
// bfcached subframes with unload/pagehide/visibilitychange handlers will
// crash on a failed DCHECK due to crbug.com/1109742.
// TODO(crbug.com/1109742): don't skip this test when bfcache is enabled.
return;
}
ASSERT_TRUE(embedded_test_server()->Start());
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a.com(a.com)"));
GURL child_url = embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a.com()");
GURL url_2(embedded_test_server()->GetURL("a.com", "/local_storage.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// 1) Navigate to |main_url|.
EXPECT_TRUE(NavigateToURL(shell(), main_url));
RenderFrameHostImpl* main_frame_1 = web_contents->GetPrimaryMainFrame();
FrameTreeNode* root = web_contents->GetPrimaryFrameTree().root();
ASSERT_EQ(1U, root->child_count());
// Check if the subframe is navigated to the correct URL.
FrameTreeNode* child = root->child_at(0);
EXPECT_EQ(child_url, child->current_url());
// Create a pagehide handler that sets item "pagehide_storage" and a
// visibilitychange handler that sets item "visibilitychange_storage" in
// localStorage in the subframe.
EXPECT_TRUE(ExecJs(
child,
base::StringPrintf(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 != %s) {
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;
})",
IsBackForwardCacheEnabled() ? "true" : "false")));
// 2) Navigate to local_storage.html.
RenderFrameDeletedObserver main_frame_1_deleted_observer(main_frame_1);
EXPECT_TRUE(NavigateToURL(shell(), url_2));
// Check that |main_url| and local_storage.html are in different RFHs.
RenderFrameHostImpl* main_frame_2 = web_contents->GetPrimaryMainFrame();
EXPECT_NE(main_frame_1, main_frame_2);
// Check that the value set by |child|'s pagehide and visibilitychange
// handlers can be accessed by |main_frame_2| at load time (the first time the
// new page runs scripts), setting the |pagehide_storage_at_load| and
// |visibilitychange_storage_at_load| variable correctly.
EXPECT_EQ("dispatched_once",
EvalJs(main_frame_2, "pagehide_storage_at_load"));
EXPECT_EQ("dispatched_once",
EvalJs(main_frame_2, "visibilitychange_storage_at_load"));
// Check that the value for 'pagehide_storage' and 'visibilitychange_storage'
// stays the same after |main_frame_2| finished loading (or |main_frame_1| got
// deleted, if bfcache is not enabled).
if (!IsBackForwardCacheEnabled())
main_frame_1_deleted_observer.WaitUntilDeleted();
EXPECT_EQ("dispatched_once",
EvalJs(main_frame_2, "localStorage.getItem('pagehide_storage')"));
EXPECT_EQ(
"dispatched_once",
EvalJs(main_frame_2, "localStorage.getItem('visibilitychange_storage')"));
}
// Tests that pagehide handlers of the old RFH are run during the commit
// of the new RFH when swapping RFH for same-site navigations due to proactive
// BrowsingInstance swap even if the page is already hidden (and
// visibilitychange won't run).
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
PagehideRunsDuringCommitOfHiddenPage) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
GURL url_2(embedded_test_server()->GetURL("/local_storage.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// 1) Navigate to |url_1| and hide the tab.
EXPECT_TRUE(NavigateToURL(shell(), url_1));
RenderFrameHostImpl* main_frame_1 = web_contents->GetPrimaryMainFrame();
// We need to set it to Visibility::VISIBLE first in case this is the first
// time the visibility is updated.
web_contents->UpdateWebContentsVisibility(Visibility::VISIBLE);
web_contents->UpdateWebContentsVisibility(Visibility::HIDDEN);
EXPECT_EQ(Visibility::HIDDEN, web_contents->GetVisibility());
// Create a pagehide handler that sets item "pagehide_storage" and a
// visibilitychange handler that sets item "visibilitychange_storage" in
// localStorage.
EXPECT_TRUE(ExecJs(
main_frame_1,
base::StringPrintf(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 != %s) {
localStorage.setItem('pagehide_storage', 'wrong_persisted');
} else {
localStorage.setItem('pagehide_storage', 'dispatched_once');
}
dispatched_pagehide = true;
}
localStorage.setItem('visibilitychange_storage', 'not_dispatched');
document.onvisibilitychange = function(e) {
localStorage.setItem('visibilitychange_storage',
'should_not_be_dispatched');
})",
IsBackForwardCacheEnabled() ? "true" : "false")));
// |visibilitychange_storage| should be set to its initial correct value.
EXPECT_EQ(
"not_dispatched",
EvalJs(main_frame_1, "localStorage.getItem('visibilitychange_storage')"));
// 2) Navigate to local_storage.html.
RenderFrameDeletedObserver main_frame_1_deleted_observer(main_frame_1);
EXPECT_TRUE(NavigateToURL(shell(), url_2));
// Check that |url_1| and local_storage.html are in different RFHs.
RenderFrameHostImpl* main_frame_2 = web_contents->GetPrimaryMainFrame();
EXPECT_NE(main_frame_1, main_frame_2);
// Check that the value set by |main_frame_1|'s pagehide handler can be
// accessed by |main_frame_2| at load time (the first time the new page runs
// scripts), setting the |pagehide_storage_at_load| and variable correctly.
EXPECT_EQ("dispatched_once",
EvalJs(main_frame_2, "pagehide_storage_at_load"));
// |visibilitychange_storage_at_load| should not be modified.
EXPECT_EQ("not_dispatched",
EvalJs(main_frame_2, "visibilitychange_storage_at_load"));
// Check that the value for 'pagehide_storage' and 'visibilitychange_storage'
// stays the same after |main_frame_2| finished loading (or |main_frame_1| got
// deleted, if bfcache is not enabled).
if (!IsBackForwardCacheEnabled())
main_frame_1_deleted_observer.WaitUntilDeleted();
EXPECT_EQ("dispatched_once",
EvalJs(main_frame_2, "localStorage.getItem('pagehide_storage')"));
EXPECT_EQ(
"not_dispatched",
EvalJs(main_frame_2, "localStorage.getItem('visibilitychange_storage')"));
}
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
NavigationAfterPagehideHistogram) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// 1) Navigate to a.com/title1.html.
EXPECT_TRUE(NavigateToURL(shell(), url_a1));
ExpectTotalCount(kActionAfterPagehideHistogramName, 0);
// 2) Set up a navigation that will start after we commit the next navigation.
RenderFrameHostChangedCallbackRunner navigate_after_commit(
web_contents,
GetAsyncScriptExecutorCallback("window.location.reload();"));
// 3) Navigate same-site to a.com/title2.html.
EXPECT_TRUE(NavigateToURL(shell(), url_a2));
// We should record the fact that a navigation started after pagehide was
// dispatched.
content::FetchHistogramsFromChildProcesses();
ExpectBucketCount(kActionAfterPagehideHistogramName,
ActionAfterPagehide::kNavigation, 1);
}
// TODO(crbug.com/1274974): Make this work with NavigationThreadingOptimizations
// enabled.
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
DISABLED_PostMessageAfterPagehideHistogram) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html"));
GURL url_b1(embedded_test_server()->GetURL("b.com", "/title1.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// 1) Navigate to a.com/title1.html.
EXPECT_TRUE(NavigateToURL(shell(), url_a1));
ExpectTotalCount(kActionAfterPagehideHistogramName, 0);
{
// 2) Set up a script that will call postMessage on the current window
// after we commit the next navigation.
// TODO(https://crbug.com/1110497): GetAsyncScriptExecutorCallback() must be
// removed in favor of GetSyncExecutorCallback()
RenderFrameHostChangedCallbackRunner post_message_after_same_site_commit(
web_contents,
GetScriptExecutorCallback("window.postMessage('hello', '*')"));
// 3) Navigate same-site to a.com/title2.html.
EXPECT_TRUE(NavigateToURL(shell(), url_a2));
// We should record the fact that a postMessage call was done after
// pagehide was dispatched and since we're calling it on our own window, we
// are also receiving the message.
ExpectBucketCount(kActionAfterPagehideHistogramName,
ActionAfterPagehide::kSentPostMessage, 1);
ExpectBucketCount(kActionAfterPagehideHistogramName,
ActionAfterPagehide::kReceivedPostMessage, 1);
}
{
// 4) Set up a script that will call postMessage on the current window
// after we commit the next navigation.
// TODO(https://crbug.com/1110497): GetAsyncScriptExecutorCallback() must be
// removed in favor of GetSyncExecutorCallback()
RenderFrameHostChangedCallbackRunner post_message_after_cross_site_commit(
web_contents,
GetScriptExecutorCallback("window.postMessage('hello', '*')"));
// 5) Navigate cross-site to |url_b1|.
EXPECT_TRUE(NavigateToURL(shell(), url_b1));
// Since the navigation is cross-site, the postMessage will happen before
// pagehide gets dispatched (at unload time), so the histogram stays the
// same.
ExpectBucketCount(kActionAfterPagehideHistogramName,
ActionAfterPagehide::kSentPostMessage, 1);
ExpectBucketCount(kActionAfterPagehideHistogramName,
ActionAfterPagehide::kReceivedPostMessage, 1);
}
}
// TODO(crbug.com/1274974): Make this work with NavigationThreadingOptimizations
// enabled.
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
DISABLED_PostMessageAfterPagehideHistogramSubframe) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a1(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(a)"));
GURL url_a2(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
GURL url_a3(embedded_test_server()->GetURL("a.com", "/title3.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// 1) Navigate to |url_a1|, which has one same-site iframe.
EXPECT_TRUE(NavigateToURL(shell(), url_a1));
ExpectTotalCount(kActionAfterPagehideHistogramName, 0);
{
// 2) Set up a script that will call postMessage on a same-site iframe
// after we commit the next navigation.
// TODO(https://crbug.com/1110497): GetAsyncScriptExecutorCallback() must be
// removed in favor of GetSyncExecutorCallback()
RenderFrameHostChangedCallbackRunner post_message_after_same_site_commit(
web_contents, GetScriptExecutorCallback(
"window.frames[0].postMessage('hello', '*')"));
// 3) Navigate same-site to |url_a2|, which has one cross-site iframe.
EXPECT_TRUE(NavigateToURL(shell(), url_a2));
// We should record the fact that a postMessage call was done after
// pagehide was dispatched and since we're calling it on a same-site
// iframe's window, we will track it.
ExpectBucketCount(kActionAfterPagehideHistogramName,
ActionAfterPagehide::kSentPostMessage, 1);
ExpectBucketCount(kActionAfterPagehideHistogramName,
ActionAfterPagehide::kReceivedPostMessage, 1);
}
{
FrameTreeNode* root = web_contents->GetPrimaryFrameTree().root();
RenderFrameHostImpl* child_rfh = root->child_at(0)->current_frame_host();
bool subframe_was_in_same_site_instance =
root->current_frame_host()->GetSiteInstance() ==
child_rfh->GetSiteInstance();
// 4) Set up a script that will call postMessage on a cross-site iframe
// after we commit the next navigation.
// TODO(https://crbug.com/1110497): GetAsyncScriptExecutorCallback() must be
// removed in favor of GetSyncExecutorCallback()
RenderFrameHostChangedCallbackRunner post_message_after_same_site_commit(
web_contents, GetScriptExecutorCallback(
"window.frames[0].postMessage('hello', '*')"));
// 5) Navigate same-site to |url_a3|.
EXPECT_TRUE(NavigateToURL(shell(), url_a3));
// We should record the fact that a postMessage call was done after
// pagehide was dispatched. On the receiving part, if the cross-site
// subframe is in the same SiteInstance as the main frame (it's not
// isolated) or the iframe is saved in the back-forward cache, the
// postMessage will arrive after we dispatch pagehide. The former case
// happens because we will dispatch pagehide at commit time for all
// subframes in the same SiteInstance as the main frame, and the latter case
// happens because we will send the freeze message to the subframe before
// the postMessage arrives in the subframe. Otherwise (if the cross-site
// subframe is isolated and it's not saved in the back-forward cache), the
// postMessage will never actually arrive in the subframe (because we would
// have already unloaded).
ExpectBucketCount(kActionAfterPagehideHistogramName,
ActionAfterPagehide::kSentPostMessage, 2);
if (!subframe_was_in_same_site_instance && !IsBackForwardCacheEnabled()) {
ExpectBucketCount(kActionAfterPagehideHistogramName,
ActionAfterPagehide::kReceivedPostMessage, 1);
} else {
ExpectBucketCount(kActionAfterPagehideHistogramName,
ActionAfterPagehide::kReceivedPostMessage, 2);
}
}
}
// Flaky on all major platforms: https://crbug.com/1156218
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
DISABLED_StorageModificationAfterPagehideHistogram) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html"));
GURL url_a3(embedded_test_server()->GetURL("a.com", "/title3.html"));
GURL url_b1(embedded_test_server()->GetURL("b.com", "/title1.html"));
GURL url_b2(embedded_test_server()->GetURL("b.com", "/title2.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// 1) Navigate to a.com/title1.html.
EXPECT_TRUE(NavigateToURL(shell(), url_a1));
ExpectTotalCount(kActionAfterPagehideHistogramName, 0);
{
// 2) Set up a script that will modify localStorage after we commit the next
// navigation.
RenderFrameHostChangedCallbackRunner
set_local_storage_after_same_site_commit(
web_contents, GetAsyncScriptExecutorCallback(
"localStorage.setItem('foo', 'bar'); "));
// 3) Navigate same-site to a.com/title2.html.
EXPECT_TRUE(NavigateToURL(shell(), url_a2));
// We should record the fact that we modified localStorage after pagehide
// was dispatched.
ExpectBucketCount(kActionAfterPagehideHistogramName,
ActionAfterPagehide::kLocalStorageModification, 1);
ExpectBucketCount(kActionAfterPagehideHistogramName,
ActionAfterPagehide::kSessionStorageModification, 0);
}
{
// 4) Set up a script that will modify sessionStorage after we commit the
// next navigation.
RenderFrameHostChangedCallbackRunner
set_session_storage_after_same_site_commit(
web_contents, GetAsyncScriptExecutorCallback(
"sessionStorage.setItem('foo', 'bar'); "));
// 5) Navigate same-site to a.com/title3.html.
EXPECT_TRUE(NavigateToURL(shell(), url_a3));
// We should record the fact that we modified sessionStorage after pagehide
// was dispatched.
ExpectBucketCount(kActionAfterPagehideHistogramName,
ActionAfterPagehide::kLocalStorageModification, 1);
ExpectBucketCount(kActionAfterPagehideHistogramName,
ActionAfterPagehide::kSessionStorageModification, 1);
}
{
// 6) Set up a script that will modify localStorage and sessionStorage after
// we commit the next navigation.
RenderFrameHostChangedCallbackRunner set_storage_after_cross_site_commit(
web_contents, GetAsyncScriptExecutorCallback(R"(
localStorage.setItem('foo', 'bar');
sessionStorage.setItem('foo', 'bar');
)"));
// 7) Navigate cross-site to b.com/title1.html.
EXPECT_TRUE(NavigateToURL(shell(), url_b1));
// Since the navigation is cross-site, the localStorage and sessionStorage
// modification (which is done at commit time) will happen before pagehide
// gets dispatched (at unload time), so the histogram stays the same.
ExpectBucketCount(kActionAfterPagehideHistogramName,
ActionAfterPagehide::kLocalStorageModification, 1);
ExpectBucketCount(kActionAfterPagehideHistogramName,
ActionAfterPagehide::kSessionStorageModification, 1);
}
{
// 8) Set up a script that will access localStorage and sessionStorage after
// we commit the next navigation.
RenderFrameHostChangedCallbackRunner get_storage_after_same_site_commit(
web_contents, GetAsyncScriptExecutorCallback(R"(
localStorage.getItem('foo');
sessionStorage.getItem('foo');
)"));
// 9) Navigate same-site to b.com/title2.html.
EXPECT_TRUE(NavigateToURL(shell(), url_b2));
// Even though the script runs after pagehide was dispatched, we did not
// modify anything in localStorage/sessionStorage (getItem only reads
// values), so the histogram stays the same.
ExpectBucketCount(kActionAfterPagehideHistogramName,
ActionAfterPagehide::kLocalStorageModification, 1);
ExpectBucketCount(kActionAfterPagehideHistogramName,
ActionAfterPagehide::kSessionStorageModification, 1);
}
}
class ProactivelySwapBrowsingInstancesSameSiteCoopTest
: public ProactivelySwapBrowsingInstancesSameSiteTest {
public:
ProactivelySwapBrowsingInstancesSameSiteCoopTest()
: https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
std::vector<base::Feature> features;
feature_list_.InitAndEnableFeature(
network::features::kCrossOriginOpenerPolicy);
}
~ProactivelySwapBrowsingInstancesSameSiteCoopTest() override = default;
net::EmbeddedTestServer* https_server() { return &https_server_; }
private:
void SetUpOnMainThread() override {
ProactivelySwapBrowsingInstancesSameSiteTest::SetUpOnMainThread();
mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
https_server()->ServeFilesFromSourceDirectory(GetTestDataFilePath());
SetupCrossSiteRedirector(https_server());
net::test_server::RegisterDefaultHandlers(&https_server_);
ASSERT_TRUE(https_server()->Start());
}
void SetUpCommandLine(base::CommandLine* command_line) override {
ProactivelySwapBrowsingInstancesSameSiteTest::SetUpCommandLine(
command_line);
mock_cert_verifier_.SetUpCommandLine(command_line);
}
void SetUpInProcessBrowserTestFixture() override {
ProactivelySwapBrowsingInstancesSameSiteTest::
SetUpInProcessBrowserTestFixture();
mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
}
void TearDownInProcessBrowserTestFixture() override {
ProactivelySwapBrowsingInstancesSameSiteTest::
TearDownInProcessBrowserTestFixture();
mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
}
base::test::ScopedFeatureList feature_list_;
net::EmbeddedTestServer https_server_;
content::ContentMockCertVerifier mock_cert_verifier_;
};
// Tests history same-site process reuse:
// 1. Visit A1 (non-COOP), A2 (non-COOP, should reuse A1's process), A3 (uses
// COOP + COEP, should use new process).
// 2. Go back to A2 (should use new process).
// 3. Go back to A1 (should reuse A2's process).
IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteCoopTest,
HistoryNavigationReusesProcess_COOP) {
// This test expects a renderer process to eventually get deleted when we
// navigate away from the page using it, which won't happen if the page is
// kept alive in the back-forward cache. So, we should disable back-forward
// cache for this test.
DisableBackForwardCache(BackForwardCacheImpl::TEST_REQUIRES_NO_CACHING);
GURL url_1(https_server()->GetURL("a.com", "/title1.html"));
GURL url_2(https_server()->GetURL("a.com", "/title2.html"));
GURL coop_url(
https_server()->GetURL("a.com",
"/set-header?"
"Cross-Origin-Opener-Policy: same-origin&"
"Cross-Origin-Embedder-Policy: require-corp"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// 1) Navigate to title1.html.
EXPECT_TRUE(NavigateToURL(shell(), url_1));
EXPECT_EQ(
web_contents->GetPrimaryMainFrame()->cross_origin_opener_policy().value,
network::mojom::CrossOriginOpenerPolicyValue::kUnsafeNone);
scoped_refptr<SiteInstanceImpl> site_instance_1 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// 2) Navigate same-site to title2.html.
EXPECT_TRUE(NavigateToURL(shell(), url_2));
EXPECT_EQ(
web_contents->GetPrimaryMainFrame()->cross_origin_opener_policy().value,
network::mojom::CrossOriginOpenerPolicyValue::kUnsafeNone);
scoped_refptr<SiteInstanceImpl> site_instance_2 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// Check that title1.html and title2.html are in different BrowsingInstances
// but have the same renderer process.
EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());
// 3) Navigate same-site to a crossOriginIsolated page (uses COOP+COEP).
RenderFrameDeletedObserver rfh_2_deleted_observer(
web_contents->GetPrimaryMainFrame());
EXPECT_TRUE(NavigateToURL(shell(), coop_url));
EXPECT_EQ(
web_contents->GetPrimaryMainFrame()->cross_origin_opener_policy().value,
network::mojom::CrossOriginOpenerPolicyValue::kSameOriginPlusCoep);
// Wait until the RFH for title2.html got deleted, and check that
// title2.html and title3.html are in different BrowsingInstances and
// renderer processes (We check this by checking whether |site_instance_2|
// still has a process or not - if it's gone then that means
// |site_instance_3| uses a different process).
rfh_2_deleted_observer.WaitUntilDeleted();
scoped_refptr<SiteInstanceImpl> site_instance_3 =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
EXPECT_FALSE(site_instance_2->IsRelatedSiteInstance(site_instance_3.get()));
EXPECT_FALSE(site_instance_2->HasProcess());
EXPECT_NE(site_instance_2->GetProcess(), site_instance_3->GetProcess());
// 4) Do a back navigation to title2.html.
RenderFrameDeletedObserver rfh_3_deleted_observer(
web_contents->GetPrimaryMainFrame());
shell()->web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_2);
scoped_refptr<SiteInstanceImpl> site_instance_2_history_nav =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// We should use different BrowsingInstances and processes after going back to
// title2.html because it's transitioning from a crossOriginIsolated page
// (COOP+COEP) to a non-crossOriginIsolated page, even though the two are
// same-site.
rfh_3_deleted_observer.WaitUntilDeleted();
EXPECT_FALSE(site_instance_2_history_nav->IsRelatedSiteInstance(
site_instance_3.get()));
EXPECT_FALSE(site_instance_3->HasProcess());
// 5) Do a back navigation to title1.html.
shell()->web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_1);
scoped_refptr<SiteInstanceImpl> site_instance_1_history_nav =
static_cast<SiteInstanceImpl*>(
web_contents->GetPrimaryMainFrame()->GetSiteInstance());
// We should use different BrowsingInstances for title1.html and title2.html,
// but reuse the process (because in the original navigation, the BI change
// was caused by proactive BI swap).
EXPECT_FALSE(site_instance_1_history_nav->IsRelatedSiteInstance(
site_instance_2_history_nav.get()));
EXPECT_EQ(site_instance_1_history_nav, site_instance_1);
EXPECT_TRUE(site_instance_2_history_nav->HasProcess());
EXPECT_EQ(site_instance_1_history_nav->GetProcess(),
site_instance_2_history_nav->GetProcess());
}
// Tests that enable clearing window.name on on cross-site
// cross-BrowsingInstance navigations when
// ProactivelySwapBrowsingInstancesSameSite is enabled.
class ProactivelySwapBrowsingInstancesSameSiteClearWindowNameTest
: public ProactivelySwapBrowsingInstancesSameSiteTest {
public:
ProactivelySwapBrowsingInstancesSameSiteClearWindowNameTest() {
feature_list_.InitAndEnableFeature(
features::kClearCrossSiteCrossBrowsingContextGroupWindowName);
}
~ProactivelySwapBrowsingInstancesSameSiteClearWindowNameTest() override =
default;
private:
base::test::ScopedFeatureList feature_list_;
};
// Verify that same-site main frame navigation that swaps BrowsingInstances
// does not clear window.name.
IN_PROC_BROWSER_TEST_P(
ProactivelySwapBrowsingInstancesSameSiteClearWindowNameTest,
NotClearWindowNameSameSite) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html"));
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
// Navigate to a.com/title1.html.
EXPECT_TRUE(NavigateToURL(shell(), url_a1));
// Set window.name.
EXPECT_TRUE(content::ExecuteScript(web_contents, "window.name='foo'"));
auto* frame_a1 = web_contents->GetPrimaryMainFrame();
EXPECT_EQ("foo", frame_a1->GetFrameName());
scoped_refptr<SiteInstance> site_instance_a1 = frame_a1->GetSiteInstance();
// Navigate to a.com/title2.html. Even though we proactively swap
// BrowsingInstances for same-site navigation as well, we should only clear
// window.name for cross-BrowsingInstance navigation that's not same-site.
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#resetBCName.
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url_a2));
auto* frame_a2 = web_contents->GetPrimaryMainFrame();
// Check that title1.html and title2.html are in different BrowsingInstances.
scoped_refptr<SiteInstance> site_instance_a2 = frame_a2->GetSiteInstance();
EXPECT_FALSE(site_instance_a1->IsRelatedSiteInstance(site_instance_a2.get()));
// Window.name should not be cleared.
EXPECT_EQ("foo", frame_a2->GetFrameName());
}
INSTANTIATE_TEST_SUITE_P(
All,
ProactivelySwapBrowsingInstancesCrossSiteSwapProcessTest,
testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(
All,
ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest,
testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(All,
ProactivelySwapBrowsingInstancesSameSiteTest,
testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(All,
ProactivelySwapBrowsingInstancesSameSiteCoopTest,
testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(
All,
ProactivelySwapBrowsingInstancesSameSiteClearWindowNameTest,
testing::ValuesIn(RenderDocumentFeatureLevelValues()));
} // namespace content