blob: c4f9851c2a9323394475025947261ca5704708c4 [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 <memory>
#include <string>
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "content/browser/process_lock.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/site_instance_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/content_navigation_policy.h"
#include "content/common/features.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/browser/web_exposed_isolation_level.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/commit_message_delayer.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_content_browser_client.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/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/render_document_feature.h"
#include "net/dns/mock_host_resolver.h"
#include "url/gurl.h"
// Unassigned SiteInstances occur when ShouldAssignSiteForURL returns false,
// allowing a given SiteInstance to be reused for a future navigation. This test
// suite covers how Chrome should behave in these cases, and when SiteInstances
// should be reused or replaced. Note that there are some differences between
// about:blank and custom embedder-defined cases.
//
// TODO(crbug.com/40214665): We would like to enforce the fact that unassigned
// SiteInstances only ever exist in their own BrowsingInstance. The exact way to
// achieve that is still unclear. We might only allow leaving SiteInstances
// unassigned for empty schemes, or make the siteless behavior kick in only for
// the first navigation in a BrowsingInstance, for example.
//
// The test suite covers expectations for the following cases:
// - Navigation to about:blank, renderer/browser initiated.
// - Navigation from about:blank, renderer/browser initiated.
// - Navigation to embedder defined url, renderer/browser initiated.
// - Navigation from embedder defined url, renderer/browser initiated.
// - Initial empty document in popups.
// - Navigation in a popup, to about:blank, renderer/browser initiated.
// - Navigation in a popup, to embedder defined url, renderer/browser initiated.
// - Initial empty document in iframes.
// - Navigation in an iframe, to about:blank, renderer initiated.
// - Navigation in an iframe, to embedder defined url, renderer initiated.
// - Interactions with crossOriginIsolated pages.
// - Some bug reproducers, testing things like races and history navigations.
namespace content {
namespace {
const std::string kEmptySchemeForTesting = "empty-scheme";
} // namespace
// Note that this test suite is parametrized for RenderDocument and
// BackForwardCache, like many tests involving navigations, and SiteInstance
// picking. This is due to the fact that both features have an important impact
// on navigations and are likely to interact.
class UnassignedSiteInstanceBrowserTest
: public ContentBrowserTest,
public ::testing::WithParamInterface<std::tuple<std::string, bool>> {
public:
UnassignedSiteInstanceBrowserTest()
: https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
InitAndEnableRenderDocumentFeature(&feature_list_for_render_document_,
std::get<0>(GetParam()));
InitBackForwardCacheFeature(&feature_list_for_back_forward_cache_,
std::get<1>(GetParam()));
url::AddEmptyDocumentScheme(kEmptySchemeForTesting.c_str());
}
// Provides meaningful param names instead of /0, /1, ...
static std::string DescribeParams(
const testing::TestParamInfo<ParamType>& info) {
auto [render_document_level, enable_back_forward_cache] = info.param;
return base::StringPrintf(
"%s_%s",
GetRenderDocumentLevelNameForTestParams(render_document_level).c_str(),
enable_back_forward_cache ? "BFCacheEnabled" : "BFCacheDisabled");
}
void SetUpOnMainThread() override {
// Allow all hosts on HTTPS.
mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
// Support multiple sites on the test server.
host_resolver()->AddRule("*", "127.0.0.1");
https_server_.AddDefaultHandlers(GetTestDataFilePath());
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(https_server_.Start());
regular_url_ = https_server_.GetURL("a.test", "/title1.html");
cross_origin_isolated_url_ =
https_server_.GetURL("a.test",
"/set-header?"
"Cross-Origin-Opener-Policy: same-origin&"
"Cross-Origin-Embedder-Policy: require-corp");
unassigned_url_ = GURL("about:blank");
embedder_defined_unassigned_url_ = GURL(kEmptySchemeForTesting + "://test");
regular_http_url_ =
embedded_test_server()->GetURL("a.test", "/title1.html");
}
void SetUpCommandLine(base::CommandLine* command_line) override {
mock_cert_verifier_.SetUpCommandLine(command_line);
}
void SetUpInProcessBrowserTestFixture() override {
mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
}
void TearDownInProcessBrowserTestFixture() override {
mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
}
WebContentsImpl* web_contents() {
return static_cast<WebContentsImpl*>(shell()->web_contents());
}
net::EmbeddedTestServer* https_server() { return &https_server_; }
void DisableBackForwardCache(
BackForwardCacheImpl::DisableForTestingReason reason) const {
return static_cast<WebContentsImpl*>(shell()->web_contents())
->GetController()
.GetBackForwardCache()
.DisableForTesting(reason);
}
// Returns an url that assigns a site to the SiteInstance it lives in.
const GURL& regular_url() const { return regular_url_; }
// Returns regular_url()'s origin, including port if Origin Isolation is
// enabled.
GURL RegularUrlOriginMaybeWithPort() const {
GURL result = regular_url();
GURL::Replacements replacements;
replacements.ClearPath();
if (!SiteIsolationPolicy::AreOriginKeyedProcessesEnabledByDefault()) {
// Only include the port for origin-isolated urls.
replacements.ClearPort();
}
return result.ReplaceComponents(replacements);
}
// Returns an url that assigns a site to the SiteInstance it lives in and is
// crossOriginIsolated.
const GURL& cross_origin_isolated_url() const {
return cross_origin_isolated_url_;
}
// Returns an url that does not assign a site to the SiteInstance it lives in.
const GURL& unassigned_url() const { return unassigned_url_; }
// Returns an url from an empty document scheme that does not assign a site to
// the SiteInstance it lives in, as decided by the content embedder.
const GURL& embedder_defined_unassigned_url() const {
return embedder_defined_unassigned_url_;
}
// Returns an url that assigns a site to the SiteInstance it lives in, and
// that is served using HTTP.
const GURL& regular_http_url() const { return regular_http_url_; }
private:
net::EmbeddedTestServer https_server_;
content::ContentMockCertVerifier mock_cert_verifier_;
GURL regular_url_;
GURL cross_origin_isolated_url_;
GURL unassigned_url_;
GURL embedder_defined_unassigned_url_;
// This is the only URL that uses HTTP instead of HTTPS, because
// IsolateOriginsForTesting() has a hardcoded HTTP filter in it. It should
// only be used for that specific purpose.
GURL regular_http_url_;
base::test::ScopedFeatureList feature_list_for_render_document_;
base::test::ScopedFeatureList feature_list_for_back_forward_cache_;
url::ScopedSchemeRegistryForTests scheme_registry_;
};
IN_PROC_BROWSER_TEST_P(UnassignedSiteInstanceBrowserTest,
RendererInitiatedNavigationTo) {
// Get a base page with a site.
EXPECT_TRUE(NavigateToURL(web_contents(), regular_url()));
RenderFrameHostImpl* initial_rfh = web_contents()->GetPrimaryMainFrame();
scoped_refptr<SiteInstanceImpl> initial_si = initial_rfh->GetSiteInstance();
EXPECT_TRUE(initial_si->HasSite());
// Do a renderer-initiated navigation to an unassigned url.
EXPECT_TRUE(NavigateToURLFromRenderer(initial_rfh, unassigned_url()));
scoped_refptr<SiteInstanceImpl> unassigned_si =
web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
if (!BackForwardCache::IsBackForwardCacheFeatureEnabled()) {
// Normally we reuse the SiteInstance.
EXPECT_TRUE(unassigned_si->HasSite());
EXPECT_EQ(unassigned_si, initial_si);
} else {
// With back/forward cache, we will swap browsing instance for same-site
// navigations, not reusing the previous SiteInstance.
EXPECT_FALSE(unassigned_si->IsRelatedSiteInstance(initial_si.get()));
// Because this was a renderer-initiated navigation, and the unassigned URL
// is about:blank, the committed document inherits its origin from the
// initiator which is `regular_url()` (https://crbug.com/585649). Hence,
// the new SiteInstance should not stay unassigned, but rather it will have
// a site that reflects the initiator. See https://crbug.com/1426928.
EXPECT_TRUE(unassigned_si->HasSite());
}
}
IN_PROC_BROWSER_TEST_P(UnassignedSiteInstanceBrowserTest,
BrowserInitiatedNavigationTo) {
// Get a base page with a site.
EXPECT_TRUE(NavigateToURL(web_contents(), regular_url()));
scoped_refptr<SiteInstanceImpl> initial_si =
web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_TRUE(initial_si->HasSite());
// Do a browser initiated navigation to an unassigned url.
EXPECT_TRUE(NavigateToURL(shell(), unassigned_url()));
scoped_refptr<SiteInstanceImpl> unassigned_si =
web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
if (!BackForwardCache::IsBackForwardCacheFeatureEnabled()) {
// Normally we reuse the SiteInstance.
EXPECT_TRUE(unassigned_si->HasSite());
EXPECT_EQ(unassigned_si, initial_si);
} else {
// With back/forward cache, we will swap browsing instance for same-site
// navigations, not reusing the previous SiteInstance.
EXPECT_FALSE(unassigned_si->HasSite());
EXPECT_FALSE(unassigned_si->IsRelatedSiteInstance(initial_si.get()));
}
}
IN_PROC_BROWSER_TEST_P(UnassignedSiteInstanceBrowserTest,
RendererInitiatedNavigationFrom) {
// Get a base page without a site.
EXPECT_TRUE(NavigateToURL(web_contents(), unassigned_url()));
RenderFrameHostImpl* unassigned_url_rfh =
web_contents()->GetPrimaryMainFrame();
scoped_refptr<SiteInstanceImpl> unassigned_si =
unassigned_url_rfh->GetSiteInstance();
EXPECT_FALSE(unassigned_si->HasSite());
// Do a renderer-initiated navigation to an assigned url. We reuse the
// unassigned SiteInstance and set its site.
EXPECT_TRUE(NavigateToURLFromRenderer(unassigned_url_rfh, regular_url()));
scoped_refptr<SiteInstanceImpl> initial_si =
web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_TRUE(initial_si->HasSite());
EXPECT_EQ(unassigned_si, initial_si);
}
IN_PROC_BROWSER_TEST_P(UnassignedSiteInstanceBrowserTest,
BrowserInitiatedNavigationFrom) {
// Get a base page without a site.
EXPECT_TRUE(NavigateToURL(web_contents(), unassigned_url()));
scoped_refptr<SiteInstanceImpl> unassigned_si =
web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_FALSE(unassigned_si->HasSite());
// Do a browser-initiated navigation to an assigned url. We reuse the
// unassigned SiteInstance and set its site.
EXPECT_TRUE(NavigateToURL(shell(), regular_url()));
scoped_refptr<SiteInstanceImpl> initial_si =
web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_TRUE(initial_si->HasSite());
EXPECT_EQ(unassigned_si, initial_si);
}
IN_PROC_BROWSER_TEST_P(UnassignedSiteInstanceBrowserTest,
RendererInitiatedNavigationTo_CustomUrl) {
// Get a base page with a site.
EXPECT_TRUE(NavigateToURL(shell(), regular_url()));
RenderFrameHostImpl* initial_rfh = web_contents()->GetPrimaryMainFrame();
scoped_refptr<SiteInstanceImpl> initial_si = initial_rfh->GetSiteInstance();
EXPECT_TRUE(initial_si->HasSite());
// Do a renderer-initiated navigation to an embedder-defined unassigned url.
// We use a new related or unrelated SiteInstance depending on the
// ProactivelySwapBrowsingInstance feature. This differs from the base
// unassigned behavior, because about:blank is explicitly considered as same
// site with all other site, but custom embedder-defined unassigned urls are
// not.
EXPECT_TRUE(NavigateToURLFromRenderer(initial_rfh,
embedder_defined_unassigned_url()));
scoped_refptr<SiteInstanceImpl> unassigned_si =
web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_FALSE(unassigned_si->HasSite());
if (CanCrossSiteNavigationsProactivelySwapBrowsingInstances()) {
EXPECT_FALSE(unassigned_si->IsRelatedSiteInstance(initial_si.get()));
} else {
EXPECT_TRUE(unassigned_si->IsRelatedSiteInstance(initial_si.get()));
}
}
IN_PROC_BROWSER_TEST_P(UnassignedSiteInstanceBrowserTest,
BrowserInitiatedNavigationTo_CustomUrl) {
// Get a base page with a site.
EXPECT_TRUE(NavigateToURL(web_contents(), regular_url()));
scoped_refptr<SiteInstanceImpl> initial_si =
web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_TRUE(initial_si->HasSite());
// Do a browser initiated navigation to an embedder-defined unassigned url.
// We do not reuse the assigned SiteInstance. We use a new SiteInstance in its
// own BrowsingInstance.
// Note: This differs from the renderer initiated behavior. This comes from
// the fact that IsBrowsingInstanceSwapAllowedForPageTransition() explicitly
// excludes renderer initiated navigations from generic cross-site
// BrowsingInstance swap.
EXPECT_TRUE(NavigateToURL(web_contents(), embedder_defined_unassigned_url()));
scoped_refptr<SiteInstanceImpl> unassigned_si =
web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_FALSE(unassigned_si->HasSite());
EXPECT_FALSE(unassigned_si->IsRelatedSiteInstance(initial_si.get()));
}
IN_PROC_BROWSER_TEST_P(UnassignedSiteInstanceBrowserTest,
RendererInitiatedNavigationFrom_CustomUrl) {
// Get a base page without a site.
EXPECT_TRUE(NavigateToURL(web_contents(), embedder_defined_unassigned_url()));
RenderFrameHostImpl* unassigned_url_rfh =
web_contents()->GetPrimaryMainFrame();
scoped_refptr<SiteInstanceImpl> unassigned_si =
unassigned_url_rfh->GetSiteInstance();
EXPECT_FALSE(unassigned_si->HasSite());
// Do a renderer-initiated navigation to an assigned url. We reuse the
// unassigned SiteInstance and set its site, because the process is unused.
EXPECT_TRUE(NavigateToURLFromRenderer(unassigned_url_rfh, regular_url()));
scoped_refptr<SiteInstanceImpl> initial_si =
web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_TRUE(initial_si->HasSite());
EXPECT_EQ(unassigned_si, initial_si);
}
IN_PROC_BROWSER_TEST_P(UnassignedSiteInstanceBrowserTest,
BrowserInitiatedNavigationFrom_CustomUrl) {
// Get a base page without a site.
EXPECT_TRUE(NavigateToURL(web_contents(), embedder_defined_unassigned_url()));
scoped_refptr<SiteInstanceImpl> unassigned_si =
web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_FALSE(unassigned_si->HasSite());
// Do a browser-initiated navigation to an assigned url. We reuse the
// unassigned SiteInstance and set its site.
EXPECT_TRUE(NavigateToURL(shell(), regular_url()));
scoped_refptr<SiteInstanceImpl> initial_si =
web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_TRUE(initial_si->HasSite());
EXPECT_EQ(unassigned_si, initial_si);
}
IN_PROC_BROWSER_TEST_P(UnassignedSiteInstanceBrowserTest,
InPopup_InitialAboutBlank) {
// Get a base page with a site.
EXPECT_TRUE(NavigateToURL(shell(), regular_url()));
RenderFrameHostImpl* original_rfh = web_contents()->GetPrimaryMainFrame();
scoped_refptr<SiteInstanceImpl> original_si = original_rfh->GetSiteInstance();
EXPECT_TRUE(original_si->HasSite());
// Create a popup. It should reuse the opener SiteInstance.
ShellAddedObserver shell_observer;
EXPECT_TRUE(ExecJs(original_rfh, "window.open()"));
scoped_refptr<SiteInstanceImpl> popup_si =
static_cast<WebContentsImpl*>(shell_observer.GetShell()->web_contents())
->GetPrimaryMainFrame()
->GetSiteInstance();
EXPECT_TRUE(popup_si->HasSite());
EXPECT_EQ(popup_si, original_si);
}
IN_PROC_BROWSER_TEST_P(UnassignedSiteInstanceBrowserTest,
InPopup_RendererInitiatedNavigateTo) {
// Get a base page with a site.
EXPECT_TRUE(NavigateToURL(shell(), regular_url()));
RenderFrameHostImpl* original_rfh = web_contents()->GetPrimaryMainFrame();
scoped_refptr<SiteInstanceImpl> original_si = original_rfh->GetSiteInstance();
EXPECT_TRUE(original_si->HasSite());
// Create a same-origin popup.
ShellAddedObserver shell_observer;
EXPECT_TRUE(
ExecJs(original_rfh, JsReplace("window.open($1)", regular_url())));
WebContentsImpl* popup_web_contents =
static_cast<WebContentsImpl*>(shell_observer.GetShell()->web_contents());
EXPECT_TRUE(WaitForLoadStop(popup_web_contents));
scoped_refptr<SiteInstanceImpl> popup_si =
popup_web_contents->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_TRUE(popup_si->HasSite());
EXPECT_EQ(popup_si, original_si);
if (!AreStrictSiteInstancesEnabled()) {
EXPECT_TRUE(popup_si->IsDefaultSiteInstance());
}
// In the popup, do a renderer-initiated navigation to an unassigned url. We
// should reuse the SiteInstance.
EXPECT_TRUE(NavigateToURLFromRenderer(
popup_web_contents->GetPrimaryMainFrame(), unassigned_url()));
scoped_refptr<SiteInstanceImpl> post_navigation_si =
popup_web_contents->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_TRUE(post_navigation_si->HasSite());
EXPECT_EQ(post_navigation_si, original_si);
}
IN_PROC_BROWSER_TEST_P(UnassignedSiteInstanceBrowserTest,
InPopup_BrowserInitiatedNavigateTo) {
// Get a base page with a site.
EXPECT_TRUE(NavigateToURL(shell(), regular_url()));
RenderFrameHostImpl* original_rfh = web_contents()->GetPrimaryMainFrame();
scoped_refptr<SiteInstanceImpl> original_si = original_rfh->GetSiteInstance();
EXPECT_TRUE(original_si->HasSite());
// Create a same-origin popup.
ShellAddedObserver shell_observer;
EXPECT_TRUE(
ExecJs(original_rfh, JsReplace("window.open($1)", regular_url())));
WebContentsImpl* popup_web_contents =
static_cast<WebContentsImpl*>(shell_observer.GetShell()->web_contents());
EXPECT_TRUE(WaitForLoadStop(popup_web_contents));
scoped_refptr<SiteInstanceImpl> popup_si =
popup_web_contents->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_TRUE(popup_si->HasSite());
EXPECT_EQ(popup_si, original_si);
if (!AreStrictSiteInstancesEnabled()) {
EXPECT_TRUE(popup_si->IsDefaultSiteInstance());
}
// In the popup, do a browser-initiated navigation to an unassigned url. We
// should reuse the SiteInstance.
EXPECT_TRUE(NavigateToURL(popup_web_contents, unassigned_url()));
scoped_refptr<SiteInstanceImpl> post_navigation_si =
popup_web_contents->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_TRUE(post_navigation_si->HasSite());
EXPECT_EQ(post_navigation_si, original_si);
}
IN_PROC_BROWSER_TEST_P(UnassignedSiteInstanceBrowserTest,
InPopup_RendererInitiatedNavigateTo_CustomUrl) {
// Get a base page with a site.
EXPECT_TRUE(NavigateToURL(shell(), regular_url()));
RenderFrameHostImpl* original_rfh = web_contents()->GetPrimaryMainFrame();
scoped_refptr<SiteInstanceImpl> original_si = original_rfh->GetSiteInstance();
EXPECT_TRUE(original_si->HasSite());
// Create a same-origin popup.
ShellAddedObserver shell_observer;
EXPECT_TRUE(
ExecJs(original_rfh, JsReplace("window.open($1)", regular_url())));
WebContentsImpl* popup_web_contents =
static_cast<WebContentsImpl*>(shell_observer.GetShell()->web_contents());
EXPECT_TRUE(WaitForLoadStop(popup_web_contents));
scoped_refptr<SiteInstanceImpl> popup_si =
popup_web_contents->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_TRUE(popup_si->HasSite());
EXPECT_EQ(popup_si, original_si);
if (!AreStrictSiteInstancesEnabled()) {
EXPECT_TRUE(popup_si->IsDefaultSiteInstance());
}
// In the popup, do a renderer-initiated navigation to an embedder-defined
// unassigned url. We use another related SiteInstance. Note that contrary to
// its main window counterpart, here we never swap BrowsingInstance, because
// ProactivelySwapBrowsingInstance never applies to popups.
EXPECT_TRUE(
NavigateToURLFromRenderer(popup_web_contents->GetPrimaryMainFrame(),
embedder_defined_unassigned_url()));
scoped_refptr<SiteInstanceImpl> post_navigation_si =
popup_web_contents->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_FALSE(post_navigation_si->HasSite());
EXPECT_TRUE(post_navigation_si->IsRelatedSiteInstance(original_si.get()));
}
IN_PROC_BROWSER_TEST_P(UnassignedSiteInstanceBrowserTest,
InPopup_BrowserInitiatedNavigateTo_CustomUrl) {
// Get a base page with a site.
EXPECT_TRUE(NavigateToURL(shell(), regular_url()));
RenderFrameHostImpl* original_rfh = web_contents()->GetPrimaryMainFrame();
scoped_refptr<SiteInstanceImpl> original_si = original_rfh->GetSiteInstance();
EXPECT_TRUE(original_si->HasSite());
// Create a same-origin popup.
ShellAddedObserver shell_observer;
EXPECT_TRUE(
ExecJs(original_rfh, JsReplace("window.open($1)", regular_url())));
WebContentsImpl* popup_web_contents =
static_cast<WebContentsImpl*>(shell_observer.GetShell()->web_contents());
EXPECT_TRUE(WaitForLoadStop(popup_web_contents));
scoped_refptr<SiteInstanceImpl> popup_si =
popup_web_contents->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_TRUE(popup_si->HasSite());
EXPECT_EQ(popup_si, original_si);
if (!AreStrictSiteInstancesEnabled()) {
EXPECT_TRUE(popup_si->IsDefaultSiteInstance());
}
// In the popup, do a browser-initiated navigation to an embedder-defined
// unassigned url. We swap browsing instance because the navigation is
// considered cross-site.
EXPECT_TRUE(
NavigateToURL(popup_web_contents, embedder_defined_unassigned_url()));
scoped_refptr<SiteInstanceImpl> post_navigation_si =
popup_web_contents->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_FALSE(post_navigation_si->HasSite());
EXPECT_FALSE(post_navigation_si->IsRelatedSiteInstance(original_si.get()));
}
IN_PROC_BROWSER_TEST_P(UnassignedSiteInstanceBrowserTest,
CrossOriginIsolated_BrowserInitiatedNavigationTo) {
// Get a base crossOriginIsolated page with a site.
EXPECT_TRUE(NavigateToURL(web_contents(), cross_origin_isolated_url()));
scoped_refptr<SiteInstanceImpl> initial_si =
web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_TRUE(initial_si->HasSite());
EXPECT_TRUE(initial_si->IsCrossOriginIsolated());
// Do a browser initiated navigation to an unassigned url.
EXPECT_TRUE(NavigateToURL(shell(), unassigned_url()));
scoped_refptr<SiteInstanceImpl> unassigned_si =
web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
// We explicitly disable SiteInstance reuse for now. Restricting which custom
// sites are allowed to be described by the embedder as unassigned might
// change that in the future.
EXPECT_FALSE(unassigned_si->HasSite());
EXPECT_FALSE(unassigned_si->IsRelatedSiteInstance(initial_si.get()));
}
IN_PROC_BROWSER_TEST_P(UnassignedSiteInstanceBrowserTest,
CrossOriginIsolated_BrowserInitiatedNavigationFrom) {
// Get a base page without a site.
EXPECT_TRUE(NavigateToURL(web_contents(), unassigned_url()));
scoped_refptr<SiteInstanceImpl> unassigned_si =
web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_FALSE(unassigned_si->HasSite());
// Do a browser-initiated navigation to a crossOriginIsolated assigned url. We
// should not reuse the unassigned SiteInstance currently, because we have no
// guarantee that the unassigned page won't load content in the process.
// Restricting which custom sites are allowed to be described by the embedder
// as unassigned might change that in the future.
EXPECT_TRUE(NavigateToURL(shell(), cross_origin_isolated_url()));
scoped_refptr<SiteInstanceImpl> initial_si =
web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_TRUE(initial_si->HasSite());
EXPECT_TRUE(initial_si->IsCrossOriginIsolated());
EXPECT_FALSE(initial_si->IsRelatedSiteInstance(unassigned_si.get()));
}
IN_PROC_BROWSER_TEST_P(UnassignedSiteInstanceBrowserTest,
InIframe_InitialAboutBlank) {
// Get a base page with a site.
EXPECT_TRUE(NavigateToURL(shell(), regular_url()));
RenderFrameHostImpl* original_rfh = web_contents()->GetPrimaryMainFrame();
scoped_refptr<SiteInstanceImpl> original_si = original_rfh->GetSiteInstance();
EXPECT_TRUE(original_si->HasSite());
// Create an iframe. The initial empty document should reuse the parent
// SiteInstance.
ASSERT_TRUE(ExecJs(original_rfh, R"(
const frame = document.createElement('iframe');
document.body.appendChild(frame);
)"));
scoped_refptr<SiteInstanceImpl> iframe_si =
original_rfh->child_at(0)->current_frame_host()->GetSiteInstance();
EXPECT_TRUE(iframe_si->HasSite());
EXPECT_EQ(iframe_si, original_si);
}
IN_PROC_BROWSER_TEST_P(UnassignedSiteInstanceBrowserTest,
InIframe_RendererInitiatedNavigateTo) {
// Get a base page with a site.
EXPECT_TRUE(NavigateToURL(shell(), regular_url()));
RenderFrameHostImpl* original_rfh = web_contents()->GetPrimaryMainFrame();
scoped_refptr<SiteInstanceImpl> original_si = original_rfh->GetSiteInstance();
EXPECT_TRUE(original_si->HasSite());
// Create a same-origin iframe. It should reuse the parent SiteInstance.
ASSERT_TRUE(ExecJs(original_rfh, JsReplace(R"(
const frame = document.createElement('iframe');
frame.src = $1;
document.body.appendChild(frame);
)",
regular_url())));
EXPECT_TRUE(WaitForLoadStop(web_contents()));
RenderFrameHostImpl* iframe_rfh =
original_rfh->child_at(0)->current_frame_host();
scoped_refptr<SiteInstanceImpl> iframe_si = iframe_rfh->GetSiteInstance();
EXPECT_TRUE(iframe_si->HasSite());
EXPECT_EQ(iframe_si, original_si);
if (!AreStrictSiteInstancesEnabled()) {
EXPECT_TRUE(iframe_si->IsDefaultSiteInstance());
}
// In the iframe, navigate to an unassigned url. We reuse the SiteInstance.
EXPECT_TRUE(NavigateToURLFromRenderer(iframe_rfh, unassigned_url()));
scoped_refptr<SiteInstanceImpl> post_navigation_si =
original_rfh->child_at(0)->current_frame_host()->GetSiteInstance();
EXPECT_TRUE(post_navigation_si->HasSite());
EXPECT_EQ(post_navigation_si, original_si);
}
IN_PROC_BROWSER_TEST_P(UnassignedSiteInstanceBrowserTest,
InIframe_RendererInitiatedNavigateTo_CustomUrl) {
// Get a base page with a site.
EXPECT_TRUE(NavigateToURL(shell(), regular_url()));
RenderFrameHostImpl* original_rfh = web_contents()->GetPrimaryMainFrame();
scoped_refptr<SiteInstanceImpl> original_si = original_rfh->GetSiteInstance();
EXPECT_TRUE(original_si->HasSite());
// Create a same-origin iframe. It should reuse the parent SiteInstance.
ASSERT_TRUE(ExecJs(original_rfh, JsReplace(R"(
const frame = document.createElement('iframe');
frame.src = $1;
document.body.appendChild(frame);
)",
regular_url())));
EXPECT_TRUE(WaitForLoadStop(web_contents()));
RenderFrameHostImpl* iframe_rfh =
original_rfh->child_at(0)->current_frame_host();
scoped_refptr<SiteInstanceImpl> iframe_si = iframe_rfh->GetSiteInstance();
EXPECT_TRUE(iframe_si->HasSite());
EXPECT_EQ(iframe_si, original_si);
if (!AreStrictSiteInstancesEnabled()) {
EXPECT_TRUE(iframe_si->IsDefaultSiteInstance());
}
// In the iframe, navigate to an embedder defined unassigned url. We use a new
// related SiteInstance because the navigation is considered cross-site.
EXPECT_TRUE(
NavigateToURLFromRenderer(iframe_rfh, embedder_defined_unassigned_url()));
scoped_refptr<SiteInstanceImpl> post_navigation_si =
original_rfh->child_at(0)->current_frame_host()->GetSiteInstance();
if (AreStrictSiteInstancesEnabled()) {
EXPECT_FALSE(post_navigation_si->HasSite());
EXPECT_TRUE(post_navigation_si->IsRelatedSiteInstance(original_si.get()));
} else {
// On Android, we sometimes do not get a related SiteInstance, but the same
// default SiteInstance to reduce memory footprint.
EXPECT_TRUE(post_navigation_si->IsDefaultSiteInstance());
EXPECT_TRUE(post_navigation_si->HasSite());
EXPECT_EQ(post_navigation_si, original_si);
}
}
// Ensure that coming back to a NavigationEntry with a previously unassigned
// SiteInstance (which is now used for another site) properly switches processes
// and SiteInstances. See https://crbug.com/945399.
IN_PROC_BROWSER_TEST_P(UnassignedSiteInstanceBrowserTest,
BackToNowAssignedSiteInstance) {
// Navigate to a URL that does not assign site URLs.
EXPECT_TRUE(NavigateToURL(shell(), embedder_defined_unassigned_url()));
EXPECT_EQ(embedder_defined_unassigned_url(),
web_contents()->GetLastCommittedURL());
scoped_refptr<SiteInstanceImpl> instance1(
web_contents()->GetPrimaryMainFrame()->GetSiteInstance());
RenderProcessHost* process1 = instance1->GetProcess();
EXPECT_EQ(GURL(), instance1->GetSiteURL());
// Navigate to page that uses up the site. It should reuse the previous
// SiteInstance and set its site URL.
EXPECT_TRUE(NavigateToURL(shell(), regular_url()));
EXPECT_EQ(instance1,
web_contents()->GetPrimaryMainFrame()->GetSiteInstance());
EXPECT_TRUE(instance1->HasSite());
if (AreStrictSiteInstancesEnabled()) {
EXPECT_EQ(RegularUrlOriginMaybeWithPort(), instance1->GetSiteURL());
} else {
EXPECT_TRUE(instance1->IsDefaultSiteInstance());
}
// The previously committed entry should get a new, related instance to avoid
// a SiteInstance mismatch when returning to it. See http://crbug.com/992198
// for further context.
scoped_refptr<SiteInstanceImpl> prev_entry_instance =
web_contents()
->GetController()
.GetEntryAtIndex(0)
->root_node()
->frame_entry->site_instance();
EXPECT_NE(prev_entry_instance, instance1);
EXPECT_NE(prev_entry_instance, nullptr);
EXPECT_TRUE(prev_entry_instance->IsRelatedSiteInstance(instance1.get()));
EXPECT_EQ(GURL(), prev_entry_instance->GetSiteURL());
// Navigate to bar.com, which destroys the previous RenderProcessHost.
GURL other_regular_url(
https_server()->GetURL("another.test", "/title1.html"));
RenderProcessHostWatcher exit_observer(
process1, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
// With BackForwardCache, old process won't be deleted on navigation as it is
// still in use by the bfcached document, disable back-forward cache to ensure
// that the process gets deleted.
DisableBackForwardCache(BackForwardCacheImpl::TEST_REQUIRES_NO_CACHING);
EXPECT_TRUE(NavigateToURL(shell(), other_regular_url));
exit_observer.Wait();
if (AreStrictSiteInstancesEnabled()) {
EXPECT_NE(instance1,
web_contents()->GetPrimaryMainFrame()->GetSiteInstance());
} else {
// Verify that the new navigation also results in a default SiteInstance,
// and verify that it is not related to |instance1| because the navigation
// swapped to a new BrowsingInstance.
EXPECT_TRUE(web_contents()
->GetPrimaryMainFrame()
->GetSiteInstance()
->IsDefaultSiteInstance());
EXPECT_FALSE(instance1->IsRelatedSiteInstance(
web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));
}
// At this point, process1 is deleted, and the first entry is unfortunately
// pointing to instance1, which has been locked to url2 and has no process.
EXPECT_FALSE(instance1->HasProcess());
if (AreAllSitesIsolatedForTesting()) {
// In site-per-process, we cannot use foo.com's SiteInstance for a.com.
EXPECT_FALSE(instance1->IsSuitableForUrlInfo(
UrlInfo::CreateForTesting(embedder_defined_unassigned_url())));
} else if (AreStrictSiteInstancesEnabled()) {
// If neither foo.com nor a.com require dedicated processes, and we're using
// default SiteInstanceGroup instead of default SiteInstance, then we can
// use the same process.
EXPECT_TRUE(instance1->IsSuitableForUrlInfo(
UrlInfo::CreateForTesting(embedder_defined_unassigned_url())));
} else {
// Since |instance1| is a default SiteInstance AND this test explicitly
// ensures that ShouldAssignSiteForURL(url1) will return false, |url1|
// cannot be placed in the default SiteInstance. This also means that |url1|
// cannot be placed in the same process as the default SiteInstance.
EXPECT_FALSE(instance1->IsSuitableForUrlInfo(
UrlInfo::CreateForTesting(embedder_defined_unassigned_url())));
}
// Go back to url1's entry, which should swap to a new SiteInstance with an
// unused site URL.
TestNavigationObserver observer(web_contents());
web_contents()->GetController().GoToOffset(-2);
observer.Wait();
scoped_refptr<SiteInstanceImpl> new_instance =
web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_EQ(embedder_defined_unassigned_url(),
web_contents()->GetLastCommittedURL());
EXPECT_NE(instance1, new_instance);
EXPECT_EQ(GURL(), new_instance->GetSiteURL());
EXPECT_TRUE(new_instance->HasProcess());
// Because embedder_defined_unassigned_url does not set a site URL, it should
// not lock the new process either, so that it can be used for subsequent
// navigations.
RenderProcessHost* new_process = new_instance->GetProcess();
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
EXPECT_TRUE(policy->CanAccessOrigin(
new_process->GetDeprecatedID(),
url::Origin::Create(embedder_defined_unassigned_url()),
ChildProcessSecurityPolicyImpl::AccessType::kCanCommitNewOrigin));
EXPECT_TRUE(policy->CanAccessOrigin(
new_process->GetDeprecatedID(), url::Origin::Create(regular_url()),
ChildProcessSecurityPolicyImpl::AccessType::kCanCommitNewOrigin));
}
// Check that when a navigation to a URL that doesn't require assigning a site
// URL is in progress, another navigation can't reuse the same process in the
// meantime. Such reuse previously led to a renderer kill when the unassigned
// URL later committed; a real-world example of the unassigned URL was
// chrome-native://newtab. See https://crbug.com/970046.
IN_PROC_BROWSER_TEST_P(UnassignedSiteInstanceBrowserTest,
NavigationRacesWithCommitInunassignedSiteInstance) {
if (ShouldCreateNewHostForAllFrames()) {
// This test involves starting a navigation while another navigation is
// committing, which might lead to deletion of a pending commit RFH, which
// will crash when RenderDocument is enabled. Skip the test if so.
// TODO(crbug.com/40186427): Update this test to work under
// navigation queueing, which will prevent the deletion of the pending
// commit RFH but still fails because this test waits for the new navigation
// to get to the WillProcessResponse stage before finishing the commit of
// the pending commit RFH.
return;
}
// Prepare for a second navigation to a normal URL. Ensure it's isolated so
// that it requires a process lock on all platforms.
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
policy->AddFutureIsolatedOrigins(
{url::Origin::Create(regular_url())},
ChildProcessSecurityPolicy::IsolatedOriginSource::TEST);
// Create a new shell where the normal url origin isolation will take effect.
Shell* shell = CreateBrowser();
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell->web_contents());
FrameTreeNode* root = web_contents->GetPrimaryFrameTree().root();
RenderProcessHost* regular_process = nullptr;
TestNavigationManager regular_manager(web_contents, regular_url());
auto& current_isolation_context =
root->current_frame_host()->GetSiteInstance()->GetIsolationContext();
auto site_info = SiteInfo::CreateForTesting(current_isolation_context,
GURL("https://a.test"));
EXPECT_TRUE(site_info.RequiresDedicatedProcess(current_isolation_context));
// Set up the work to be done after the renderer is asked to commit
// |embedder_defined_unassigned_url|, but before the corresponding
// DidCommitProvisionalLoad IPC is processed. This will start a navigation to
// |regular_url| and wait for its response.
auto did_commit_callback =
base::BindLambdaForTesting([&](RenderFrameHost* rfh) {
// The navigation should stay in the initial empty SiteInstance, with
// the site still unassigned.
EXPECT_FALSE(
static_cast<SiteInstanceImpl*>(rfh->GetSiteInstance())->HasSite());
EXPECT_EQ(ShouldCreateNewHostForAllFrames(),
!!root->render_manager()->speculative_frame_host());
shell->LoadURL(regular_url());
// This will cause |regular_manager| to spin up a nested message loop
// while we're blocked in the current message loop
// (within DidCommitNavigationInterceptor). Thus, it's important to
// allow nestable tasks in |regular_manager|'s message loop, so that it
// can process the response before we unblock the
// DidCommitNavigationInterceptor's message loop and finish processing
// the commit.
regular_manager.AllowNestableTasks();
EXPECT_TRUE(regular_manager.WaitForResponse());
// The foo.com navigation should swap to a new process, since it is not
// safe to reuse |embedder_defined_unassigned_url|'s process before
// |embedder_defined_unassigned_url| commits.
EXPECT_TRUE(root->render_manager()->speculative_frame_host());
regular_process =
root->render_manager()->speculative_frame_host()->GetProcess();
regular_manager.ResumeNavigation();
// After returning here, the commit for
// |embedder_defined_unassigned_url| will be processed.
});
CommitMessageDelayer commit_delayer(
web_contents, embedder_defined_unassigned_url() /* deferred_url */,
std::move(did_commit_callback));
// Start the first navigation, which does not assign a site URL.
shell->LoadURL(embedder_defined_unassigned_url());
// The navigation should stay in the initial empty SiteInstance, so there
// shouldn't be a speculative RFH at this point, unless RenderDocument is
// enabled for all frames.
EXPECT_EQ(ShouldCreateNewHostForAllFrames(),
!!root->render_manager()->speculative_frame_host());
// Wait for the DidCommit IPC for |embedder_defined_unassigned_url|, and
// before processing it, trigger a navigation to |regular_url| and wait for
// its response.
commit_delayer.Wait();
// Check that the renderer hasn't been killed. At this point, it should've
// successfully committed the navigation to |embedder_defined_unassigned_url|,
// and it shouldn't be locked.
EXPECT_TRUE(web_contents->GetPrimaryMainFrame()->IsRenderFrameLive());
EXPECT_EQ(embedder_defined_unassigned_url(),
web_contents->GetPrimaryMainFrame()->GetLastCommittedURL());
RenderProcessHost* process1 =
web_contents->GetPrimaryMainFrame()->GetProcess();
EXPECT_FALSE(
web_contents->GetPrimaryMainFrame()->GetSiteInstance()->HasSite());
auto process1_lock = process1->GetProcessLock();
EXPECT_FALSE(process1_lock.is_invalid());
EXPECT_TRUE(process1_lock.allows_any_site());
// Now wait for second navigation to finish and ensure it also succeeds.
ASSERT_TRUE(regular_manager.WaitForNavigationFinished());
EXPECT_TRUE(regular_manager.was_successful());
EXPECT_TRUE(web_contents->GetPrimaryMainFrame()->IsRenderFrameLive());
EXPECT_EQ(regular_url(),
web_contents->GetPrimaryMainFrame()->GetLastCommittedURL());
// The regular url navigation should've used a different process, locked to
// a.test.
BrowserContext* browser_context = web_contents->GetBrowserContext();
RenderProcessHost* process2 =
web_contents->GetPrimaryMainFrame()->GetProcess();
EXPECT_NE(process1, process2);
EXPECT_EQ(
RegularUrlOriginMaybeWithPort(),
web_contents->GetPrimaryMainFrame()->GetSiteInstance()->GetSiteURL());
EXPECT_EQ(ProcessLock::FromSiteInfo(SiteInfo::CreateForTesting(
IsolationContext(browser_context), regular_url())),
policy->GetProcessLock(process2->GetDeprecatedID()));
// Ensure also that the regular url process didn't change midway through the
// navigation.
EXPECT_EQ(regular_process, process2);
}
static auto kTestParams =
testing::Combine(testing::ValuesIn(RenderDocumentFeatureLevelValues()),
testing::Bool());
INSTANTIATE_TEST_SUITE_P(All,
UnassignedSiteInstanceBrowserTest,
kTestParams,
UnassignedSiteInstanceBrowserTest::DescribeParams);
} // namespace content