blob: 8737f9416c93e036e135e6d86b585db02898e756 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/renderer_host/navigator.h"
#include <stdint.h>
#include "base/feature_list.h"
#include "base/test/test_simple_task_runner.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/navigation_controller_impl.h"
#include "content/browser/renderer_host/navigation_entry_impl.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/navigation_request_info.h"
#include "content/browser/renderer_host/render_frame_host_manager.h"
#include "content/browser/site_info.h"
#include "content/browser/site_instance_impl.h"
#include "content/common/content_navigation_policy.h"
#include "content/common/frame.mojom.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/common/content_features.h"
#include "content/public/common/url_constants.h"
#include "content/public/common/url_utils.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_navigation_throttle_inserter.h"
#include "content/public/test/test_utils.h"
#include "content/test/navigation_simulator_impl.h"
#include "content/test/task_runner_deferring_throttle.h"
#include "content/test/test_navigation_url_loader.h"
#include "content/test/test_render_frame_host.h"
#include "content/test/test_web_contents.h"
#include "net/base/load_flags.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/redirect_info.h"
#include "ui/base/page_transition_types.h"
#include "url/url_constants.h"
namespace content {
namespace {
// Helper function that determines if a test should expect a cross-site
// navigation to trigger a SiteInstance change based on the current process
// model.
bool ExpectSiteInstanceChange(SiteInstanceImpl* site_instance) {
return AreAllSitesIsolatedForTesting() ||
CanCrossSiteNavigationsProactivelySwapBrowsingInstances() ||
!site_instance->IsDefaultSiteInstance();
}
// Same as above but does not return true if back/forward cache is the only
// trigger for SiteInstance change. This function is useful if, e.g. the test
// intends to disable back/forward cache.
bool ExpectSiteInstanceChangeWithoutBackForwardCache(
SiteInstanceImpl* site_instance) {
return AreAllSitesIsolatedForTesting() ||
!site_instance->IsDefaultSiteInstance();
}
} // namespace
class NavigatorTest : public RenderViewHostImplTestHarness {
public:
using SiteInstanceDescriptor = RenderFrameHostManager::SiteInstanceDescriptor;
using SiteInstanceRelation = RenderFrameHostManager::SiteInstanceRelation;
void SetUp() override { RenderViewHostImplTestHarness::SetUp(); }
void TearDown() override { RenderViewHostImplTestHarness::TearDown(); }
TestNavigationURLLoader* GetLoaderForNavigationRequest(
NavigationRequest* request) const {
return static_cast<TestNavigationURLLoader*>(request->loader_for_testing());
}
TestRenderFrameHost* GetSpeculativeRenderFrameHost(FrameTreeNode* node) {
return static_cast<TestRenderFrameHost*>(
node->render_manager()->speculative_render_frame_host_.get());
}
scoped_refptr<SiteInstanceImpl> ConvertToSiteInstance(
RenderFrameHostManager* rfhm,
const SiteInstanceDescriptor& descriptor,
SiteInstance* candidate_instance) {
return static_cast<SiteInstanceImpl*>(
rfhm->ConvertToSiteInstance(
descriptor, static_cast<SiteInstanceImpl*>(candidate_instance))
.get());
}
SiteInfo CreateExpectedSiteInfo(const GURL& url) {
return SiteInfo::CreateForTesting(IsolationContext(browser_context()), url);
}
};
// Tests a complete browser-initiated navigation starting with a non-live
// renderer.
TEST_F(NavigatorTest, SimpleBrowserInitiatedNavigationFromNonLiveRenderer) {
const GURL kUrl("http://chromium.org/");
EXPECT_FALSE(main_test_rfh()->IsRenderFrameLive());
// Start a browser-initiated navigation.
auto navigation =
NavigationSimulator::CreateBrowserInitiated(kUrl, contents());
auto site_instance_id = main_test_rfh()->GetSiteInstance()->GetId();
FrameTreeNode* node = main_test_rfh()->frame_tree_node();
navigation->Start();
NavigationRequest* request = node->navigation_request();
ASSERT_TRUE(request);
EXPECT_EQ(kUrl, request->common_params().url);
EXPECT_TRUE(request->browser_initiated());
// As there's no live renderer the navigation should not wait for a
// beforeUnload completion callback being invoked by the renderer and
// start right away.
EXPECT_EQ(NavigationRequest::WILL_START_REQUEST, request->state());
ASSERT_TRUE(GetLoaderForNavigationRequest(request));
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
navigation->ReadyToCommit();
EXPECT_TRUE(main_test_rfh()->is_loading());
EXPECT_FALSE(node->navigation_request());
// Commit the navigation.
navigation->Commit();
EXPECT_TRUE(main_test_rfh()->IsActive());
EXPECT_EQ(main_test_rfh()->lifecycle_state(),
RenderFrameHostImpl::LifecycleStateImpl::kActive);
if (AreStrictSiteInstancesEnabled()) {
EXPECT_EQ(CreateExpectedSiteInfo(kUrl),
main_test_rfh()->GetSiteInstance()->GetSiteInfo());
} else {
EXPECT_TRUE(main_test_rfh()->GetSiteInstance()->IsDefaultSiteInstance());
}
EXPECT_EQ(kUrl, contents()->GetLastCommittedURL());
// The main RenderFrameHost should not have been changed, and the renderer
// should have been initialized.
EXPECT_EQ(site_instance_id, main_test_rfh()->GetSiteInstance()->GetId());
EXPECT_TRUE(main_test_rfh()->IsRenderFrameLive());
// After a navigation is finished no speculative RenderFrameHost should
// exist.
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
}
// Tests a complete renderer-initiated same-site navigation.
TEST_F(NavigatorTest, SimpleRendererInitiatedSameSiteNavigation) {
const GURL kUrl1("http://www.chromium.org/");
const GURL kUrl2("http://www.chromium.org/Home");
contents()->NavigateAndCommit(kUrl1);
EXPECT_TRUE(main_test_rfh()->IsRenderFrameLive());
static_cast<mojom::FrameHost*>(main_test_rfh())->DidStopLoading();
FrameTreeNode* node = main_test_rfh()->frame_tree_node();
// Start a renderer-initiated non-user-initiated navigation.
EXPECT_FALSE(node->navigation_request());
auto navigation =
NavigationSimulator::CreateRendererInitiated(kUrl2, main_test_rfh());
navigation->SetTransition(ui::PageTransitionFromInt(
ui::PAGE_TRANSITION_LINK | ui::PAGE_TRANSITION_CLIENT_REDIRECT));
navigation->SetHasUserGesture(false);
navigation->Start();
NavigationRequest* request = node->navigation_request();
ASSERT_TRUE(request);
// The navigation is immediately started as there's no need to wait for
// beforeUnload to be executed.
EXPECT_EQ(NavigationRequest::WILL_START_REQUEST, request->state());
EXPECT_FALSE(request->common_params().has_user_gesture);
EXPECT_EQ(kUrl2, request->common_params().url);
EXPECT_FALSE(request->browser_initiated());
if (CanSameSiteMainFrameNavigationsChangeRenderFrameHosts()) {
// If same-site ProactivelySwapBrowsingInstance or main-frame RenderDocument
// is enabled, the RFH should change so we should have a speculative RFH.
EXPECT_TRUE(GetSpeculativeRenderFrameHost(node));
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node)->is_loading());
} else {
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
}
EXPECT_FALSE(main_test_rfh()->is_loading());
// Have the current RenderFrameHost commit the navigation
navigation->ReadyToCommit();
if (CanSameSiteMainFrameNavigationsChangeRenderFrameHosts()) {
EXPECT_TRUE(GetSpeculativeRenderFrameHost(node)->is_loading());
} else {
EXPECT_TRUE(main_test_rfh()->is_loading());
}
EXPECT_FALSE(node->navigation_request());
// Commit the navigation.
navigation->Commit();
EXPECT_TRUE(main_test_rfh()->IsActive());
if (AreStrictSiteInstancesEnabled()) {
EXPECT_EQ(CreateExpectedSiteInfo(kUrl2),
main_test_rfh()->GetSiteInstance()->GetSiteInfo());
} else {
EXPECT_TRUE(main_test_rfh()->GetSiteInstance()->IsDefaultSiteInstance());
}
EXPECT_EQ(kUrl2, contents()->GetLastCommittedURL());
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
}
// Tests a complete renderer-initiated navigation that should be
// cross-site but does not result in a SiteInstance swap because its
// renderer-initiated.
TEST_F(NavigatorTest, SimpleRendererInitiatedCrossSiteNavigation) {
const GURL kUrl1("http://www.chromium.org/");
const GURL kUrl2("http://www.google.com");
contents()->NavigateAndCommit(kUrl1);
EXPECT_TRUE(main_test_rfh()->IsRenderFrameLive());
scoped_refptr<SiteInstanceImpl> site_instance_1 =
main_test_rfh()->GetSiteInstance();
bool expect_site_instance_change =
ExpectSiteInstanceChange(site_instance_1.get());
FrameTreeNode* node = main_test_rfh()->frame_tree_node();
// Start a renderer-initiated navigation.
EXPECT_FALSE(node->navigation_request());
auto navigation =
NavigationSimulator::CreateRendererInitiated(kUrl2, main_test_rfh());
navigation->Start();
NavigationRequest* request = node->navigation_request();
ASSERT_TRUE(request);
// The navigation is immediately started as there's no need to wait for
// beforeUnload to be executed.
EXPECT_EQ(NavigationRequest::WILL_START_REQUEST, request->state());
EXPECT_EQ(kUrl2, request->common_params().url);
EXPECT_FALSE(request->browser_initiated());
if (expect_site_instance_change) {
EXPECT_TRUE(GetSpeculativeRenderFrameHost(node));
} else {
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
}
// Have the current RenderFrameHost commit the navigation.
navigation->ReadyToCommit();
if (expect_site_instance_change) {
EXPECT_EQ(navigation->GetFinalRenderFrameHost(),
GetSpeculativeRenderFrameHost(node));
}
EXPECT_FALSE(node->navigation_request());
// Commit the navigation.
navigation->Commit();
EXPECT_TRUE(main_test_rfh()->IsActive());
EXPECT_EQ(kUrl2, contents()->GetLastCommittedURL());
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
if (expect_site_instance_change) {
EXPECT_NE(site_instance_1->GetId(),
main_test_rfh()->GetSiteInstance()->GetId());
EXPECT_EQ(site_instance_1->IsDefaultSiteInstance(),
main_test_rfh()->GetSiteInstance()->IsDefaultSiteInstance());
} else {
EXPECT_EQ(site_instance_1->GetId(),
main_test_rfh()->GetSiteInstance()->GetId());
}
}
// Tests that when a navigation to about:blank is renderer-aborted,
// after another cross-site navigation has been initiated, that the
// second navigation is undisturbed.
TEST_F(NavigatorTest, RendererAbortedAboutBlankNavigation) {
const GURL kUrl0("http://www.google.com/");
const GURL kUrl1("about:blank");
const GURL kUrl2("http://www.chromium.org/Home");
contents()->NavigateAndCommit(kUrl0);
EXPECT_TRUE(main_test_rfh()->IsRenderFrameLive());
// The test expects cross-site navigations to change RenderFrameHosts, but not
// same-site navigations. Return if that can't be satisfied.
DisableBackForwardCacheForTesting(
contents(), BackForwardCache::TEST_ASSUMES_NO_RENDER_FRAME_CHANGE);
if (!ExpectSiteInstanceChangeWithoutBackForwardCache(
main_test_rfh()->GetSiteInstance()) ||
ShouldCreateNewHostForAllFrames()) {
GTEST_SKIP();
}
// Start a renderer-initiated navigation to about:blank.
EXPECT_FALSE(main_test_rfh()->is_loading());
auto navigation1 =
NavigationSimulator::CreateRendererInitiated(kUrl1, main_test_rfh());
navigation1->SetTransition(ui::PAGE_TRANSITION_LINK);
navigation1->Start();
navigation1->ReadyToCommit();
// about:blank should load on the main rfhi, not a speculative one,
// and automatically advance to READY_TO_COMMIT since it requires
// no network resources.
FrameTreeNode* node = main_test_rfh()->frame_tree_node();
ASSERT_FALSE(node->navigation_request());
EXPECT_TRUE(main_test_rfh()->is_loading());
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
// Start a second, cross-origin navigation.
auto navigation2 =
NavigationSimulator::CreateRendererInitiated(kUrl2, main_test_rfh());
navigation2->SetTransition(ui::PAGE_TRANSITION_LINK);
navigation2->Start();
ASSERT_TRUE(node->navigation_request());
EXPECT_TRUE(GetSpeculativeRenderFrameHost(node));
// Abort the initial navigation.
navigation1->AbortFromRenderer();
// But the speculative rfhi and second navigation request
// should be unaffected.
ASSERT_TRUE(node->navigation_request());
EXPECT_TRUE(GetSpeculativeRenderFrameHost(node));
}
// Tests that when a navigation to about:blank is renderer-aborted,
// after another cross-site navigation has been initiated, that the
// second navigation is undisturbed. In this variation, the second
// navigation is initially same-site, then redirects cross-site,
// and a throttle DEFERs during WillProcessResponse(). The initial
// navigation gets aborted during this defer.
TEST_F(NavigatorTest,
RedirectedRendererAbortedAboutBlankNavigationwithDeferredCommit) {
const GURL kUrl0("http://www.google.com/");
const GURL kUrl0SameSiteVariation("http://www.google.com/home");
const GURL kUrl1("about:blank");
const GURL kUrl2("http://www.chromium.org/Home");
contents()->NavigateAndCommit(kUrl0);
EXPECT_TRUE(main_test_rfh()->IsRenderFrameLive());
// The test expects cross-site navigations to change RenderFrameHosts, but not
// same-site navigations. Return if that can't be satisfied.
DisableBackForwardCacheForTesting(
contents(), BackForwardCache::TEST_ASSUMES_NO_RENDER_FRAME_CHANGE);
if (!ExpectSiteInstanceChangeWithoutBackForwardCache(
main_test_rfh()->GetSiteInstance()) ||
ShouldCreateNewHostForAllFrames()) {
GTEST_SKIP();
}
// Start a renderer-initiated navigation to about:blank.
EXPECT_FALSE(main_test_rfh()->is_loading());
auto navigation1 =
NavigationSimulator::CreateRendererInitiated(kUrl1, main_test_rfh());
navigation1->SetTransition(ui::PAGE_TRANSITION_LINK);
navigation1->Start();
navigation1->ReadyToCommit();
// about:blank should load on the main rfhi, not a speculative one,
// and automatically advance to READY_TO_COMMIT since it requires
// no network resources.
FrameTreeNode* node = main_test_rfh()->frame_tree_node();
ASSERT_FALSE(node->navigation_request());
EXPECT_TRUE(main_test_rfh()->is_loading());
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
// Start a second, same-origin navigation.
auto navigation2 = NavigationSimulator::CreateRendererInitiated(
kUrl0SameSiteVariation, main_test_rfh());
navigation2->SetTransition(ui::PAGE_TRANSITION_LINK);
navigation2->SetAutoAdvance(false);
// Insert a TaskRunnerDeferringThrottle that will defer
// during WillProcessResponse() of navigation2.
auto task_runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
auto* raw_runner = task_runner.get();
TestNavigationThrottleInserter throttle_inserter(
web_contents(),
base::BindRepeating(&TaskRunnerDeferringThrottle::Create,
std::move(task_runner), false /* defer_start */,
false /* defer-redirect */,
true /* defer_response */));
navigation2->Start();
ASSERT_TRUE(node->navigation_request());
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
// Redirect navigation2 cross-site, which, once ReadyCommit()
// is called, will force a speculative RFHI to be created,
// and update the associated site instance type of the
// NavigationRequest for navigation2. This will prevent
// the abort of navigation1 from destroying the speculative
// RFHI that navigation2 depends on.
navigation2->Redirect(kUrl2);
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
EXPECT_TRUE(node->navigation_request()->GetAssociatedRFHType() ==
NavigationRequest::AssociatedRenderFrameHostType::CURRENT);
navigation2->ReadyToCommit();
EXPECT_EQ(1u, raw_runner->NumPendingTasks());
EXPECT_TRUE(navigation2->IsDeferred());
EXPECT_TRUE(GetSpeculativeRenderFrameHost(node));
EXPECT_EQ(node->navigation_request()->GetRenderFrameHost(),
GetSpeculativeRenderFrameHost(node));
// Abort the initial navigation.
navigation1->AbortFromRenderer();
// The speculative rfhi and second navigation request
// should be unaffected.
ASSERT_TRUE(node->navigation_request());
EXPECT_TRUE(GetSpeculativeRenderFrameHost(node));
}
// Tests that a beforeUnload denial cancels the navigation.
TEST_F(NavigatorTest, BeforeUnloadDenialCancelNavigation) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2("http://www.chromium.org/");
contents()->NavigateAndCommit(kUrl1);
// This test assumes a beforeunload handler is present.
main_test_rfh()->SuddenTerminationDisablerChanged(
true, blink::mojom::SuddenTerminationDisablerType::kBeforeUnloadHandler);
// Start a new navigation.
FrameTreeNode* node = main_test_rfh()->frame_tree_node();
auto navigation =
NavigationSimulatorImpl::CreateBrowserInitiated(kUrl2, contents());
navigation->BrowserInitiatedStartAndWaitBeforeUnload();
NavigationRequest* request = node->navigation_request();
ASSERT_TRUE(request);
EXPECT_TRUE(request->browser_initiated());
EXPECT_EQ(NavigationRequest::WAITING_FOR_RENDERER_RESPONSE, request->state());
EXPECT_TRUE(GetSpeculativeRenderFrameHost(node));
RenderFrameDeletedObserver rfh_deleted_observer(
GetSpeculativeRenderFrameHost(node));
// Simulate a beforeUnload denial.
main_test_rfh()->SimulateBeforeUnloadCompleted(false);
EXPECT_FALSE(node->navigation_request());
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
EXPECT_TRUE(rfh_deleted_observer.deleted());
}
// Test that a proper NavigationRequest is created at navigation start.
TEST_F(NavigatorTest, BeginNavigation) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2("http://www.chromium.org/");
const GURL kUrl3("http://www.gmail.com/");
contents()->NavigateAndCommit(kUrl1);
// Add a subframe.
FrameTreeNode* root_node = contents()->GetPrimaryFrameTree().root();
TestRenderFrameHost* subframe_rfh = main_test_rfh()->AppendChild("Child");
ASSERT_TRUE(subframe_rfh);
// This test assumes a beforeunload handler is present on the subframe.
subframe_rfh->SuddenTerminationDisablerChanged(
true, blink::mojom::SuddenTerminationDisablerType::kBeforeUnloadHandler);
// Start a navigation at the subframe.
FrameTreeNode* subframe_node = subframe_rfh->frame_tree_node();
auto navigation =
NavigationSimulatorImpl::CreateBrowserInitiated(kUrl2, contents());
NavigationController::LoadURLParams load_url_params(kUrl2);
load_url_params.frame_tree_node_id = subframe_node->frame_tree_node_id();
navigation->SetLoadURLParams(&load_url_params);
navigation->BrowserInitiatedStartAndWaitBeforeUnload();
NavigationRequest* subframe_request = subframe_node->navigation_request();
// We should be waiting for the BeforeUnload event to execute in the subframe.
ASSERT_TRUE(subframe_request);
EXPECT_EQ(NavigationRequest::WAITING_FOR_RENDERER_RESPONSE,
subframe_request->state());
EXPECT_TRUE(subframe_rfh->is_waiting_for_beforeunload_completion());
// Start the navigation, which will internally simulate that the beforeUnload
// completion callback has been invoked.
navigation->Start();
TestNavigationURLLoader* subframe_loader =
GetLoaderForNavigationRequest(subframe_request);
ASSERT_TRUE(subframe_loader);
EXPECT_EQ(NavigationRequest::WILL_START_REQUEST, subframe_request->state());
EXPECT_EQ(kUrl2, subframe_request->common_params().url);
EXPECT_EQ(kUrl2, subframe_loader->request_info()->common_params->url);
EXPECT_TRUE(
net::IsolationInfo::Create(
net::IsolationInfo::RequestType::kSubFrame,
url::Origin::Create(kUrl1), url::Origin::Create(kUrl2),
net::SiteForCookies::FromUrl(kUrl1), /*nonce=*/std::nullopt,
net::NetworkIsolationPartition::kGeneral,
net::IsolationInfo::FrameAncestorRelation::kSameOrigin)
.IsEqualForTesting(subframe_loader->request_info()->isolation_info));
EXPECT_FALSE(subframe_loader->request_info()->is_main_frame);
EXPECT_TRUE(subframe_request->browser_initiated());
EXPECT_FALSE(GetSpeculativeRenderFrameHost(root_node));
// Subframe navigations should never create a speculative RenderFrameHost,
// unless site-per-process or another mode where a SiteInstance cannot contain
// multiple sites is enabled. In that case, as the subframe navigation is to a
// different site and is still ongoing, it should have one.
bool expect_site_instance_change = AreStrictSiteInstancesEnabled();
if (expect_site_instance_change) {
EXPECT_TRUE(GetSpeculativeRenderFrameHost(subframe_node));
} else {
EXPECT_FALSE(GetSpeculativeRenderFrameHost(subframe_node));
}
// Now start a navigation at the root node.
auto navigation2 =
NavigationSimulatorImpl::CreateBrowserInitiated(kUrl3, contents());
navigation2->BrowserInitiatedStartAndWaitBeforeUnload();
NavigationRequest* main_request = root_node->navigation_request();
ASSERT_TRUE(main_request);
EXPECT_EQ(NavigationRequest::WAITING_FOR_RENDERER_RESPONSE,
main_request->state());
// Main frame navigation to a different site should use a speculative
// RenderFrameHost.
EXPECT_TRUE(GetSpeculativeRenderFrameHost(root_node));
// Start the navigation, which will internally simulate that the beforeUnload
// completion callback has been invoked.
navigation2->Start();
TestNavigationURLLoader* main_loader =
GetLoaderForNavigationRequest(main_request);
EXPECT_EQ(kUrl3, main_request->common_params().url);
EXPECT_EQ(kUrl3, main_loader->request_info()->common_params->url);
EXPECT_TRUE(
net::IsolationInfo::Create(net::IsolationInfo::RequestType::kMainFrame,
url::Origin::Create(kUrl3),
url::Origin::Create(kUrl3),
net::SiteForCookies::FromUrl(kUrl3))
.IsEqualForTesting(main_loader->request_info()->isolation_info));
EXPECT_TRUE(main_loader->request_info()->is_main_frame);
EXPECT_TRUE(main_request->browser_initiated());
// BeforeUnloadCompleted callback was invoked by the renderer so the
// navigation should have started.
EXPECT_EQ(NavigationRequest::WILL_START_REQUEST, main_request->state());
EXPECT_TRUE(GetSpeculativeRenderFrameHost(root_node));
// As the main frame hasn't yet committed the subframe still exists. Thus, the
// above situation regarding subframe navigations is valid here.
if (expect_site_instance_change) {
EXPECT_TRUE(GetSpeculativeRenderFrameHost(subframe_node));
} else {
EXPECT_FALSE(GetSpeculativeRenderFrameHost(subframe_node));
}
}
// Tests that committing an HTTP 204 or HTTP 205 response cancels
// the navigation.
TEST_F(NavigatorTest, NoContent) {
const GURL kUrl1("http://www.chromium.org/");
const GURL kUrl2("http://www.google.com/");
// Load a URL.
contents()->NavigateAndCommit(kUrl1);
FrameTreeNode* node = main_test_rfh()->frame_tree_node();
// Navigate to a different site.
EXPECT_FALSE(node->navigation_request());
auto navigation =
NavigationSimulator::CreateBrowserInitiated(kUrl2, contents());
navigation->Start();
NavigationRequest* main_request = node->navigation_request();
ASSERT_TRUE(main_request);
// Navigations to a different site do create a speculative RenderFrameHost.
EXPECT_TRUE(GetSpeculativeRenderFrameHost(node));
// Commit an HTTP 204 response.
auto response = network::mojom::URLResponseHead::New();
const char kNoContentHeaders[] = "HTTP/1.1 204 No Content\0\0";
response->headers = new net::HttpResponseHeaders(
std::string(kNoContentHeaders, std::size(kNoContentHeaders)));
GetLoaderForNavigationRequest(main_request)
->CallOnResponseStarted(std::move(response),
mojo::ScopedDataPipeConsumerHandle(),
std::nullopt);
// There should be no pending nor speculative RenderFrameHost; the navigation
// was aborted.
EXPECT_FALSE(node->navigation_request());
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
// Now, repeat the test with 205 Reset Content.
// Navigate to a different site again.
auto navigation2 =
NavigationSimulator::CreateBrowserInitiated(kUrl2, contents());
navigation2->Start();
main_request = node->navigation_request();
ASSERT_TRUE(main_request);
EXPECT_TRUE(GetSpeculativeRenderFrameHost(node));
// Commit an HTTP 205 response.
response = network::mojom::URLResponseHead::New();
const char kResetContentHeaders[] = "HTTP/1.1 205 Reset Content\0\0";
response->headers = new net::HttpResponseHeaders(
std::string(kResetContentHeaders, std::size(kResetContentHeaders)));
GetLoaderForNavigationRequest(main_request)
->CallOnResponseStarted(std::move(response),
mojo::ScopedDataPipeConsumerHandle(),
std::nullopt);
// There should be no pending nor speculative RenderFrameHost; the navigation
// was aborted.
EXPECT_FALSE(node->navigation_request());
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
}
// Test that a new RenderFrameHost is created when doing a cross site
// navigation.
TEST_F(NavigatorTest, CrossSiteNavigation) {
const GURL kUrl1("http://www.chromium.org/");
const GURL kUrl2("http://www.google.com/");
contents()->NavigateAndCommit(kUrl1);
RenderFrameHostImpl* initial_rfh = main_test_rfh();
FrameTreeNode* node = main_test_rfh()->frame_tree_node();
// Navigate to a different site.
EXPECT_EQ(main_test_rfh()->navigation_requests().size(), 0u);
auto navigation =
NavigationSimulator::CreateBrowserInitiated(kUrl2, contents());
navigation->Start();
NavigationRequest* main_request = node->navigation_request();
ASSERT_TRUE(main_request);
TestRenderFrameHost* speculative_rfh = GetSpeculativeRenderFrameHost(node);
ASSERT_TRUE(speculative_rfh);
EXPECT_EQ(speculative_rfh, GetSpeculativeRenderFrameHost(node));
navigation->ReadyToCommit();
EXPECT_EQ(speculative_rfh, GetSpeculativeRenderFrameHost(node));
EXPECT_EQ(speculative_rfh->navigation_requests().size(), 1u);
EXPECT_EQ(main_test_rfh()->navigation_requests().size(), 0u);
navigation->Commit();
RenderFrameHostImpl* final_rfh = main_test_rfh();
EXPECT_EQ(speculative_rfh, final_rfh);
EXPECT_NE(initial_rfh, final_rfh);
EXPECT_TRUE(final_rfh->IsRenderFrameLive());
EXPECT_TRUE(final_rfh->render_view_host()->IsRenderViewLive());
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
}
// Test that redirects are followed and the speculative RenderFrameHost logic
// behaves as expected.
TEST_F(NavigatorTest, RedirectCrossSite) {
const GURL kUrl1("http://www.chromium.org/");
const GURL kUrl2("http://www.google.com/");
contents()->NavigateAndCommit(kUrl1);
RenderFrameHostImpl* rfh = main_test_rfh();
FrameTreeNode* node = main_test_rfh()->frame_tree_node();
// Navigate to a URL on the same site.
EXPECT_EQ(main_test_rfh()->navigation_requests().size(), 0u);
auto navigation =
NavigationSimulator::CreateBrowserInitiated(kUrl1, contents());
navigation->Start();
NavigationRequest* main_request = node->navigation_request();
ASSERT_TRUE(main_request);
if (ShouldCreateNewHostForAllFrames()) {
EXPECT_TRUE(GetSpeculativeRenderFrameHost(node));
} else {
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
}
// It then redirects to another site.
navigation->Redirect(kUrl2);
// The redirect should have been followed.
EXPECT_EQ(1, GetLoaderForNavigationRequest(main_request)->redirect_count());
if (ShouldCreateNewHostForAllFrames()) {
EXPECT_TRUE(GetSpeculativeRenderFrameHost(node));
} else {
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
}
navigation->ReadyToCommit();
TestRenderFrameHost* final_speculative_rfh =
GetSpeculativeRenderFrameHost(node);
EXPECT_TRUE(final_speculative_rfh);
EXPECT_EQ(final_speculative_rfh->navigation_requests().size(), 1u);
navigation->Commit();
RenderFrameHostImpl* final_rfh = main_test_rfh();
ASSERT_TRUE(final_rfh);
EXPECT_NE(rfh, final_rfh);
EXPECT_EQ(final_speculative_rfh, final_rfh);
EXPECT_TRUE(final_rfh->IsRenderFrameLive());
EXPECT_TRUE(final_rfh->render_view_host()->IsRenderViewLive());
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
}
// Test that a navigation is canceled if another browser-initiated request has
// been issued in the meantime. Also confirms that the speculative
// RenderFrameHost is correctly updated in the process.
TEST_F(NavigatorTest, BrowserInitiatedNavigationCancel) {
const GURL kUrl0("http://www.wikipedia.org/");
const GURL kUrl1("http://www.chromium.org/");
const auto kUrl1SiteInfo = CreateExpectedSiteInfo(kUrl1);
const GURL kUrl2("http://www.google.com/");
const auto kUrl2SiteInfo = CreateExpectedSiteInfo(kUrl2);
// Initialization.
contents()->NavigateAndCommit(kUrl0);
FrameTreeNode* node = main_test_rfh()->frame_tree_node();
// Request navigation to the 1st URL.
EXPECT_FALSE(node->navigation_request());
auto navigation1 =
NavigationSimulator::CreateBrowserInitiated(kUrl1, contents());
navigation1->Start();
NavigationRequest* request1 = node->navigation_request();
ASSERT_TRUE(request1);
EXPECT_EQ(kUrl1, request1->common_params().url);
EXPECT_TRUE(request1->browser_initiated());
base::WeakPtr<TestNavigationURLLoader> loader1 =
GetLoaderForNavigationRequest(request1)->AsWeakPtr();
EXPECT_TRUE(loader1);
// Confirm a speculative RenderFrameHost was created.
TestRenderFrameHost* speculative_rfh = GetSpeculativeRenderFrameHost(node);
ASSERT_TRUE(speculative_rfh);
auto site_instance_id_1 = speculative_rfh->GetSiteInstance()->GetId();
if (AreStrictSiteInstancesEnabled()) {
EXPECT_EQ(kUrl1SiteInfo, speculative_rfh->GetSiteInstance()->GetSiteInfo());
} else {
EXPECT_TRUE(speculative_rfh->GetSiteInstance()->IsDefaultSiteInstance());
}
// Request navigation to the 2nd URL; the NavigationRequest must have been
// replaced by a new one with a different URL.
auto navigation2 =
NavigationSimulator::CreateBrowserInitiated(kUrl2, contents());
navigation2->Start();
NavigationRequest* request2 = node->navigation_request();
ASSERT_TRUE(request2);
EXPECT_EQ(kUrl2, request2->common_params().url);
EXPECT_TRUE(request2->browser_initiated());
// Confirm that the first loader got destroyed.
EXPECT_FALSE(loader1);
// Confirm that a new speculative RenderFrameHost was created.
speculative_rfh = GetSpeculativeRenderFrameHost(node);
ASSERT_TRUE(speculative_rfh);
auto site_instance_id_2 = speculative_rfh->GetSiteInstance()->GetId();
if (AreStrictSiteInstancesEnabled()) {
EXPECT_NE(site_instance_id_1, site_instance_id_2);
} else {
EXPECT_TRUE(speculative_rfh->GetSiteInstance()->IsDefaultSiteInstance());
EXPECT_EQ(site_instance_id_1, site_instance_id_2);
}
navigation2->ReadyToCommit();
EXPECT_EQ(speculative_rfh->navigation_requests().size(), 1u);
EXPECT_EQ(main_test_rfh()->navigation_requests().size(), 0u);
// Have the RenderFrameHost commit the navigation.
navigation2->Commit();
// Confirm that the commit corresponds to the new request.
ASSERT_TRUE(main_test_rfh());
if (AreStrictSiteInstancesEnabled()) {
EXPECT_EQ(kUrl2SiteInfo, main_test_rfh()->GetSiteInstance()->GetSiteInfo());
} else {
EXPECT_TRUE(main_test_rfh()->GetSiteInstance()->IsDefaultSiteInstance());
}
EXPECT_EQ(kUrl2, contents()->GetLastCommittedURL());
// Confirm that the committed RenderFrameHost is the latest speculative one.
EXPECT_EQ(site_instance_id_2, main_test_rfh()->GetSiteInstance()->GetId());
}
// Test that a browser-initiated navigation is canceled if a renderer-initiated
// user-initiated request has been issued in the meantime.
TEST_F(NavigatorTest, RendererUserInitiatedNavigationCancel) {
const GURL kUrl0("http://www.wikipedia.org/");
const GURL kUrl1("http://www.chromium.org/");
const GURL kUrl2("http://www.google.com/");
// Initialization.
contents()->NavigateAndCommit(kUrl0);
FrameTreeNode* node = main_test_rfh()->frame_tree_node();
bool expect_site_instance_change =
ExpectSiteInstanceChange(main_test_rfh()->GetSiteInstance());
// Start a browser-initiated navigation to the 1st URL and invoke its
// beforeUnload completion callback.
EXPECT_FALSE(node->navigation_request());
auto navigation2 =
NavigationSimulator::CreateBrowserInitiated(kUrl1, contents());
navigation2->Start();
NavigationRequest* request1 = node->navigation_request();
ASSERT_TRUE(request1);
EXPECT_EQ(kUrl1, request1->common_params().url);
EXPECT_TRUE(request1->browser_initiated());
base::WeakPtr<TestNavigationURLLoader> loader1 =
GetLoaderForNavigationRequest(request1)->AsWeakPtr();
EXPECT_TRUE(loader1);
// Confirm that a speculative RenderFrameHost was created.
ASSERT_TRUE(GetSpeculativeRenderFrameHost(node));
// Now receive a renderer-initiated user-initiated request. It should replace
// the current NavigationRequest.
auto navigation =
NavigationSimulator::CreateRendererInitiated(kUrl2, main_test_rfh());
navigation->SetTransition(ui::PAGE_TRANSITION_LINK);
navigation->SetHasUserGesture(true);
navigation->Start();
NavigationRequest* request2 = node->navigation_request();
ASSERT_TRUE(request2);
EXPECT_EQ(kUrl2, request2->common_params().url);
EXPECT_FALSE(request2->browser_initiated());
EXPECT_TRUE(request2->common_params().has_user_gesture);
// Confirm that the first loader got destroyed.
EXPECT_FALSE(loader1);
// Confirm that the speculative RenderFrameHost was destroyed in the non
// SitePerProcess case.
if (expect_site_instance_change) {
EXPECT_TRUE(GetSpeculativeRenderFrameHost(node));
} else {
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
}
// Commit the navigation.
navigation->Commit();
// Confirm that the commit corresponds to the new request.
ASSERT_TRUE(main_test_rfh());
EXPECT_EQ(kUrl2, contents()->GetLastCommittedURL());
}
// Tests that a renderer-initiated user-initiated navigation is
// canceled if a renderer-initiated non-user-initiated request is issued in the
// meantime.
TEST_F(NavigatorTest,
RendererNonUserInitiatedNavigationCancelsRendererUserInitiated) {
const GURL kUrl0("http://www.wikipedia.org/");
const GURL kUrl1("http://www.chromium.org/");
const GURL kUrl2("http://www.google.com/");
// Initialization.
contents()->NavigateAndCommit(kUrl0);
FrameTreeNode* node = main_test_rfh()->frame_tree_node();
bool expect_site_instance_change =
ExpectSiteInstanceChange(main_test_rfh()->GetSiteInstance());
// Start a renderer-initiated user-initiated navigation to the 1st URL.
EXPECT_FALSE(node->navigation_request());
auto user_initiated_navigation =
NavigationSimulator::CreateRendererInitiated(kUrl1, main_test_rfh());
user_initiated_navigation->SetTransition(ui::PAGE_TRANSITION_LINK);
user_initiated_navigation->SetHasUserGesture(true);
user_initiated_navigation->Start();
NavigationRequest* request1 = node->navigation_request();
ASSERT_TRUE(request1);
EXPECT_EQ(kUrl1, request1->common_params().url);
EXPECT_FALSE(request1->browser_initiated());
EXPECT_TRUE(request1->common_params().has_user_gesture);
if (expect_site_instance_change) {
EXPECT_TRUE(GetSpeculativeRenderFrameHost(node));
} else {
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
}
// Now receive a renderer-initiated non-user-initiated request. The previous
// navigation should be replaced.
auto non_user_initiated_navigation =
NavigationSimulator::CreateRendererInitiated(kUrl2, main_test_rfh());
non_user_initiated_navigation->SetTransition(ui::PAGE_TRANSITION_LINK);
non_user_initiated_navigation->SetHasUserGesture(false);
non_user_initiated_navigation->Start();
NavigationRequest* request2 = node->navigation_request();
ASSERT_TRUE(request2);
EXPECT_NE(request1, request2);
EXPECT_EQ(kUrl2, request2->common_params().url);
EXPECT_FALSE(request2->browser_initiated());
EXPECT_FALSE(request2->common_params().has_user_gesture);
if (expect_site_instance_change) {
EXPECT_TRUE(GetSpeculativeRenderFrameHost(node));
} else {
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
}
// Commit the navigation.
non_user_initiated_navigation->Commit();
EXPECT_EQ(kUrl2, contents()->GetLastCommittedURL());
}
// PlzNavigate: Test that a browser-initiated navigation is NOT canceled if a
// renderer-initiated non-user-initiated request is issued in the meantime.
TEST_F(NavigatorTest,
RendererNonUserInitiatedNavigationDoesntCancelBrowserInitiated) {
const GURL kUrl0("http://www.wikipedia.org/");
const GURL kUrl1("http://www.chromium.org/");
const GURL kUrl2("http://www.google.com/");
// Initialization.
contents()->NavigateAndCommit(kUrl0);
FrameTreeNode* node = main_test_rfh()->frame_tree_node();
// Start a browser-initiated navigation to the 1st URL.
EXPECT_FALSE(node->navigation_request());
auto navigation =
NavigationSimulator::CreateBrowserInitiated(kUrl1, contents());
navigation->Start();
NavigationRequest* request1 = node->navigation_request();
ASSERT_TRUE(request1);
EXPECT_EQ(kUrl1, request1->common_params().url);
EXPECT_TRUE(request1->browser_initiated());
TestRenderFrameHost* speculative_rfh = GetSpeculativeRenderFrameHost(node);
ASSERT_TRUE(speculative_rfh);
// Now receive a renderer-initiated non-user-initiated request. Nothing should
// change.
main_test_rfh()->SendRendererInitiatedNavigationRequest(
kUrl2, false /* has_user_gesture */);
NavigationRequest* request2 = node->navigation_request();
ASSERT_TRUE(request2);
EXPECT_EQ(request1, request2);
EXPECT_EQ(kUrl1, request2->common_params().url);
EXPECT_TRUE(request2->browser_initiated());
EXPECT_TRUE(speculative_rfh);
navigation->ReadyToCommit();
EXPECT_EQ(speculative_rfh->navigation_requests().size(), 1u);
EXPECT_EQ(main_test_rfh()->navigation_requests().size(), 0u);
navigation->Commit();
EXPECT_EQ(kUrl1, contents()->GetLastCommittedURL());
}
// PlzNavigate: Test that a renderer-initiated non-user-initiated navigation is
// canceled if a another similar request is issued in the meantime.
TEST_F(NavigatorTest,
RendererNonUserInitiatedNavigationCancelSimilarNavigation) {
const GURL kUrl0("http://www.wikipedia.org/");
const GURL kUrl1("http://www.chromium.org/");
const GURL kUrl2("http://www.google.com/");
// Initialization.
contents()->NavigateAndCommit(kUrl0);
FrameTreeNode* node = main_test_rfh()->frame_tree_node();
auto site_instance_id_0 = main_test_rfh()->GetSiteInstance()->GetId();
bool expect_site_instance_change =
ExpectSiteInstanceChange(main_test_rfh()->GetSiteInstance());
// Start a renderer-initiated non-user-initiated navigation to the 1st URL.
EXPECT_FALSE(node->navigation_request());
auto navigation1 =
NavigationSimulator::CreateRendererInitiated(kUrl1, main_test_rfh());
navigation1->SetTransition(ui::PageTransitionFromInt(
ui::PAGE_TRANSITION_LINK | ui::PAGE_TRANSITION_CLIENT_REDIRECT));
navigation1->SetHasUserGesture(false);
navigation1->Start();
NavigationRequest* request1 = node->navigation_request();
ASSERT_TRUE(request1);
EXPECT_EQ(kUrl1, request1->common_params().url);
EXPECT_FALSE(request1->browser_initiated());
EXPECT_FALSE(request1->common_params().has_user_gesture);
if (expect_site_instance_change) {
EXPECT_TRUE(GetSpeculativeRenderFrameHost(node));
} else {
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
}
base::WeakPtr<TestNavigationURLLoader> loader1 =
GetLoaderForNavigationRequest(request1)->AsWeakPtr();
EXPECT_TRUE(loader1);
// Now receive a 2nd similar request that should replace the current one.
auto navigation2 =
NavigationSimulator::CreateRendererInitiated(kUrl2, main_test_rfh());
navigation2->SetTransition(ui::PageTransitionFromInt(
ui::PAGE_TRANSITION_LINK | ui::PAGE_TRANSITION_CLIENT_REDIRECT));
navigation2->SetHasUserGesture(false);
navigation2->Start();
NavigationRequest* request2 = node->navigation_request();
EXPECT_EQ(kUrl2, request2->common_params().url);
EXPECT_FALSE(request2->browser_initiated());
EXPECT_FALSE(request2->common_params().has_user_gesture);
if (expect_site_instance_change) {
EXPECT_TRUE(GetSpeculativeRenderFrameHost(node));
} else {
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
}
// Confirm that the first loader got destroyed.
EXPECT_FALSE(loader1);
// Commit the navigation.
navigation2->Commit();
EXPECT_EQ(kUrl2, contents()->GetLastCommittedURL());
if (expect_site_instance_change) {
EXPECT_NE(site_instance_id_0, main_test_rfh()->GetSiteInstance()->GetId());
} else {
EXPECT_EQ(site_instance_id_0, main_test_rfh()->GetSiteInstance()->GetId());
}
}
// PlzNavigate: Test that a reload navigation is properly signaled to the
// RenderFrame when the navigation can commit. A speculative RenderFrameHost
// should not be created at any step, unless RenderDocument is enabled.
TEST_F(NavigatorTest, Reload) {
const GURL kUrl("http://www.google.com/");
contents()->NavigateAndCommit(kUrl);
FrameTreeNode* node = main_test_rfh()->frame_tree_node();
controller().Reload(ReloadType::NORMAL, false);
auto reload1 =
NavigationSimulator::CreateFromPending(contents()->GetController());
// A NavigationRequest should have been generated.
NavigationRequest* main_request = node->navigation_request();
ASSERT_TRUE(main_request != nullptr);
EXPECT_EQ(blink::mojom::NavigationType::RELOAD,
main_request->common_params().navigation_type);
reload1->ReadyToCommit();
if (ShouldCreateNewHostForAllFrames()) {
EXPECT_TRUE(GetSpeculativeRenderFrameHost(node));
} else {
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
}
reload1->Commit();
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
// Now do a shift+reload.
controller().Reload(ReloadType::BYPASSING_CACHE, false);
auto reload2 =
NavigationSimulator::CreateFromPending(contents()->GetController());
// A NavigationRequest should have been generated.
main_request = node->navigation_request();
ASSERT_TRUE(main_request != nullptr);
EXPECT_EQ(blink::mojom::NavigationType::RELOAD_BYPASSING_CACHE,
main_request->common_params().navigation_type);
reload2->ReadyToCommit();
if (ShouldCreateNewHostForAllFrames()) {
EXPECT_TRUE(GetSpeculativeRenderFrameHost(node));
} else {
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
}
}
// PlzNavigate: Confirm that a speculative RenderFrameHost is used when
// navigating from one site to another.
TEST_F(NavigatorTest, SpeculativeRendererWorksBaseCase) {
// Navigate to an initial site.
const GURL kUrlInit("http://wikipedia.org/");
contents()->NavigateAndCommit(kUrlInit);
FrameTreeNode* node = main_test_rfh()->frame_tree_node();
// Begin navigating to another site.
const GURL kUrl("http://google.com/");
EXPECT_FALSE(node->navigation_request());
auto navigation =
NavigationSimulator::CreateBrowserInitiated(kUrl, contents());
navigation->Start();
TestRenderFrameHost* speculative_rfh = GetSpeculativeRenderFrameHost(node);
auto site_instance_id = speculative_rfh->GetSiteInstance()->GetId();
ASSERT_TRUE(speculative_rfh);
EXPECT_NE(speculative_rfh, main_test_rfh());
if (AreStrictSiteInstancesEnabled()) {
EXPECT_EQ(CreateExpectedSiteInfo(kUrl),
speculative_rfh->GetSiteInstance()->GetSiteInfo());
} else {
EXPECT_TRUE(speculative_rfh->GetSiteInstance()->IsDefaultSiteInstance());
}
navigation->ReadyToCommit();
EXPECT_EQ(speculative_rfh->navigation_requests().size(), 1u);
// Ask the navigation to commit.
navigation->Commit();
EXPECT_EQ(site_instance_id, main_test_rfh()->GetSiteInstance()->GetId());
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
}
// PlzNavigate: Confirm that a speculative RenderFrameHost is thrown away when
// the final URL's site differs from the initial one due to redirects.
TEST_F(NavigatorTest, SpeculativeRendererDiscardedAfterRedirectToAnotherSite) {
// Navigate to an initial site.
const GURL kUrlInit("http://wikipedia.org/");
contents()->NavigateAndCommit(kUrlInit);
FrameTreeNode* node = main_test_rfh()->frame_tree_node();
auto init_site_instance_id = main_test_rfh()->GetSiteInstance()->GetId();
// Begin navigating to another site.
const GURL kUrl("http://google.com/");
EXPECT_FALSE(node->navigation_request());
auto navigation =
NavigationSimulator::CreateBrowserInitiated(kUrl, contents());
navigation->Start();
TestRenderFrameHost* speculative_rfh = GetSpeculativeRenderFrameHost(node);
ASSERT_TRUE(speculative_rfh);
auto site_instance_id = speculative_rfh->GetSiteInstance()->GetId();
RenderFrameDeletedObserver rfh_deleted_observer(speculative_rfh);
EXPECT_NE(init_site_instance_id, site_instance_id);
EXPECT_EQ(init_site_instance_id, main_test_rfh()->GetSiteInstance()->GetId());
EXPECT_NE(speculative_rfh, main_test_rfh());
if (AreStrictSiteInstancesEnabled()) {
EXPECT_EQ(CreateExpectedSiteInfo(kUrl),
speculative_rfh->GetSiteInstance()->GetSiteInfo());
} else {
EXPECT_TRUE(speculative_rfh->GetSiteInstance()->IsDefaultSiteInstance());
}
// It then redirects to yet another site.
NavigationRequest* main_request = node->navigation_request();
ASSERT_TRUE(main_request);
const GURL kUrlRedirect("https://www.google.com/");
navigation->Redirect(kUrlRedirect);
EXPECT_EQ(init_site_instance_id, main_test_rfh()->GetSiteInstance()->GetId());
// For now, ensure that the speculative RenderFrameHost does not change after
// the redirect.
// TODO(carlosk): once the speculative RenderFrameHost updates with redirects
// this next check will be changed to verify that it actually happens.
EXPECT_EQ(speculative_rfh, GetSpeculativeRenderFrameHost(node));
EXPECT_EQ(site_instance_id, speculative_rfh->GetSiteInstance()->GetId());
EXPECT_FALSE(rfh_deleted_observer.deleted());
// Send the commit to the renderer.
navigation->ReadyToCommit();
// Once commit happens the speculative RenderFrameHost is updated to match the
// known final SiteInstance.
speculative_rfh = GetSpeculativeRenderFrameHost(node);
ASSERT_TRUE(speculative_rfh);
EXPECT_EQ(speculative_rfh->navigation_requests().size(), 1u);
EXPECT_EQ(init_site_instance_id, main_test_rfh()->GetSiteInstance()->GetId());
auto redirect_site_instance_id = speculative_rfh->GetSiteInstance()->GetId();
// Expect the initial and redirect SiteInstances to be different because
// they should be associated with different BrowsingInstances.
EXPECT_NE(init_site_instance_id, redirect_site_instance_id);
if (AreStrictSiteInstancesEnabled()) {
EXPECT_EQ(CreateExpectedSiteInfo(kUrlRedirect),
speculative_rfh->GetSiteInstance()->GetSiteInfo());
EXPECT_NE(site_instance_id, redirect_site_instance_id);
// Verify the old speculative RenderFrameHost was deleted because
// the SiteInstance changed.
EXPECT_TRUE(rfh_deleted_observer.deleted());
} else {
EXPECT_TRUE(speculative_rfh->GetSiteInstance()->IsDefaultSiteInstance());
EXPECT_EQ(site_instance_id, redirect_site_instance_id);
// Verify the old speculative RenderFrameHost was not deleted because
// the SiteInstance stayed the same.
EXPECT_FALSE(rfh_deleted_observer.deleted());
}
// Invoke DidCommitProvisionalLoad.
navigation->Commit();
// Check that the speculative RenderFrameHost was swapped in.
EXPECT_EQ(redirect_site_instance_id,
main_test_rfh()->GetSiteInstance()->GetId());
EXPECT_FALSE(GetSpeculativeRenderFrameHost(node));
}
// PlzNavigate: Verify that data urls are properly handled.
TEST_F(NavigatorTest, DataUrls) {
const GURL kUrl1("http://wikipedia.org/");
const GURL kUrl2("data:text/html,test");
// Isolate kUrl1 so it can't be mapped into a default SiteInstance along with
// kUrl2. This ensures that the speculative RenderFrameHost will always be
// used because the URLs map to different SiteInstances.
ChildProcessSecurityPolicy::GetInstance()->AddFutureIsolatedOrigins(
{url::Origin::Create(kUrl1)},
ChildProcessSecurityPolicy::IsolatedOriginSource::TEST,
browser_context());
// Navigate to an initial site.
contents()->NavigateAndCommit(kUrl1);
FrameTreeNode* node = main_test_rfh()->frame_tree_node();
EXPECT_FALSE(main_test_rfh()->GetSiteInstance()->IsDefaultSiteInstance());
// Navigate to a data url. The request should have been sent to the IO
// thread and not committed immediately.
auto navigation =
NavigationSimulator::CreateBrowserInitiated(kUrl2, contents());
navigation->Start();
TestRenderFrameHost* speculative_rfh = GetSpeculativeRenderFrameHost(node);
ASSERT_TRUE(speculative_rfh);
EXPECT_FALSE(speculative_rfh->is_loading());
EXPECT_TRUE(node->navigation_request());
navigation->ReadyToCommit();
EXPECT_TRUE(speculative_rfh->is_loading());
EXPECT_FALSE(node->navigation_request());
EXPECT_NE(main_test_rfh(), speculative_rfh);
navigation->Commit();
EXPECT_EQ(main_test_rfh(), speculative_rfh);
// Go back to the initial site.
contents()->NavigateAndCommit(kUrl1);
// Do a renderer-initiated navigation to a data url. The request should be
// sent to the IO thread.
auto navigation_to_data_url =
NavigationSimulator::CreateRendererInitiated(kUrl2, main_test_rfh());
navigation_to_data_url->Start();
EXPECT_FALSE(main_test_rfh()->is_loading());
EXPECT_TRUE(node->navigation_request());
}
// Tests several cases for converting SiteInstanceDescriptors into
// SiteInstances:
// 1) Pointer to the current SiteInstance.
// 2) Pointer to an unrelated SiteInstance.
// 3) Same-site URL, related.
// 4) Cross-site URL, related.
// 5) Same-site URL, unrelated (with and without candidate SiteInstances).
// 6) Cross-site URL, unrelated (with candidate SiteInstance).
TEST_F(NavigatorTest, SiteInstanceDescriptionConversion) {
// Navigate to set a current SiteInstance on the RenderFrameHost.
GURL kUrl1("http://a.com");
// Isolate one of the sites so the both can't be mapped to the default
// site instance.
ChildProcessSecurityPolicy::GetInstance()->AddFutureIsolatedOrigins(
{url::Origin::Create(kUrl1)},
ChildProcessSecurityPolicy::IsolatedOriginSource::TEST,
browser_context());
contents()->NavigateAndCommit(kUrl1);
SiteInstanceImpl* current_instance = main_test_rfh()->GetSiteInstance();
ASSERT_TRUE(current_instance);
// 1) Convert a descriptor pointing to the current instance.
RenderFrameHostManager* rfhm =
main_test_rfh()->frame_tree_node()->render_manager();
{
SiteInstanceDescriptor descriptor(current_instance);
scoped_refptr<SiteInstance> converted_instance =
ConvertToSiteInstance(rfhm, descriptor, nullptr);
EXPECT_EQ(current_instance, converted_instance);
}
// 2) Convert a descriptor pointing an instance unrelated to the current one,
// with a different site.
GURL kUrl2("http://b.com");
scoped_refptr<SiteInstanceImpl> unrelated_instance(
SiteInstanceImpl::CreateForURL(browser_context(), kUrl2));
EXPECT_FALSE(
current_instance->IsRelatedSiteInstance(unrelated_instance.get()));
{
SiteInstanceDescriptor descriptor(unrelated_instance.get());
scoped_refptr<SiteInstance> converted_instance =
ConvertToSiteInstance(rfhm, descriptor, nullptr);
EXPECT_EQ(unrelated_instance.get(), converted_instance);
}
// 3) Convert a descriptor of a related instance with the same site as the
// current one.
GURL kUrlSameSiteAs1("http://www.a.com/foo");
{
SiteInstanceDescriptor descriptor(
UrlInfo::CreateForTesting(kUrlSameSiteAs1),
SiteInstanceRelation::RELATED);
scoped_refptr<SiteInstance> converted_instance =
ConvertToSiteInstance(rfhm, descriptor, nullptr);
EXPECT_EQ(current_instance, converted_instance);
}
// 4) Convert a descriptor of a related instance with a site different from
// the current one.
GURL kUrlSameSiteAs2("http://www.b.com/foo");
scoped_refptr<SiteInstanceImpl> related_instance;
{
SiteInstanceDescriptor descriptor(
UrlInfo::CreateForTesting(kUrlSameSiteAs2),
SiteInstanceRelation::RELATED);
related_instance = ConvertToSiteInstance(rfhm, descriptor, nullptr);
// If kUrlSameSiteAs2 requires a dedicated process on this platform, this
// should return a new instance, related to the current and set to the new
// site URL.
// Otherwise, this should return the default site instance
EXPECT_TRUE(
current_instance->IsRelatedSiteInstance(related_instance.get()));
EXPECT_NE(current_instance, related_instance.get());
EXPECT_NE(unrelated_instance.get(), related_instance.get());
if (AreStrictSiteInstancesEnabled()) {
EXPECT_EQ(SiteInfo::CreateForTesting(
current_instance->GetIsolationContext(), kUrlSameSiteAs2),
related_instance->GetSiteInfo());
} else {
ASSERT_TRUE(related_instance->IsDefaultSiteInstance());
}
}
// 5) Convert a descriptor of an unrelated instance with the same site as the
// current one, several times, with and without candidate sites.
{
SiteInstanceDescriptor descriptor(
UrlInfo::CreateForTesting(kUrlSameSiteAs1),
SiteInstanceRelation::UNRELATED);
scoped_refptr<SiteInstanceImpl> converted_instance_1 =
ConvertToSiteInstance(rfhm, descriptor, nullptr);
// Should return a new instance, unrelated to the current one, set to the
// provided site URL.
EXPECT_FALSE(
current_instance->IsRelatedSiteInstance(converted_instance_1.get()));
EXPECT_NE(current_instance, converted_instance_1.get());
EXPECT_NE(unrelated_instance.get(), converted_instance_1.get());
EXPECT_EQ(CreateExpectedSiteInfo(kUrlSameSiteAs1),
converted_instance_1->GetSiteInfo());
// Does the same but this time using unrelated_instance as a candidate,
// which has a different site.
scoped_refptr<SiteInstanceImpl> converted_instance_2 =
ConvertToSiteInstance(rfhm, descriptor, unrelated_instance.get());
// Should return yet another new instance, unrelated to the current one, set
// to the same site URL.
EXPECT_FALSE(
current_instance->IsRelatedSiteInstance(converted_instance_2.get()));
EXPECT_NE(current_instance, converted_instance_2.get());
EXPECT_NE(unrelated_instance.get(), converted_instance_2.get());
EXPECT_NE(converted_instance_1.get(), converted_instance_2.get());
EXPECT_EQ(CreateExpectedSiteInfo(kUrlSameSiteAs1),
converted_instance_1->GetSiteInfo());
// Converts once more but with |converted_instance_1| as a candidate.
scoped_refptr<SiteInstance> converted_instance_3 =
ConvertToSiteInstance(rfhm, descriptor, converted_instance_1.get());
// Should return |converted_instance_1| because its site matches and it is
// unrelated to the current SiteInstance.
EXPECT_EQ(converted_instance_1.get(), converted_instance_3);
}
// 6) Convert a descriptor of an unrelated instance with the same site of
// related_instance and using it as a candidate.
{
SiteInstanceDescriptor descriptor(
UrlInfo::CreateForTesting(kUrlSameSiteAs2),
SiteInstanceRelation::UNRELATED);
scoped_refptr<SiteInstanceImpl> converted_instance_1 =
ConvertToSiteInstance(rfhm, descriptor, related_instance.get());
// Should return a new instance, unrelated to the current, set to the
// provided site URL.
EXPECT_FALSE(
current_instance->IsRelatedSiteInstance(converted_instance_1.get()));
EXPECT_NE(related_instance.get(), converted_instance_1.get());
EXPECT_NE(unrelated_instance.get(), converted_instance_1.get());
if (AreStrictSiteInstancesEnabled()) {
EXPECT_EQ(CreateExpectedSiteInfo(kUrlSameSiteAs2),
converted_instance_1->GetSiteInfo());
} else {
EXPECT_TRUE(converted_instance_1->IsDefaultSiteInstance());
}
scoped_refptr<SiteInstance> converted_instance_2 =
ConvertToSiteInstance(rfhm, descriptor, unrelated_instance.get());
// Should return |unrelated_instance| because its site matches and it is
// unrelated to the current SiteInstance.
EXPECT_EQ(unrelated_instance.get(), converted_instance_2);
}
}
// A renderer process might try and claim that a cross site navigation was
// within the same document by setting was_within_same_document = true in
// DidCommitProvisionalLoadParams. Such case should be detected on the browser
// side and the renderer process should be killed.
TEST_F(NavigatorTest, CrossSiteClaimWithinPage) {
const GURL kUrl1("http://www.chromium.org/");
const GURL kUrl2("http://www.google.com/");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kUrl1);
// Navigate to a different site and claim that the navigation was within same
// page.
int bad_msg_count = process()->bad_msg_count();
auto simulator =
NavigationSimulator::CreateRendererInitiated(kUrl2, main_test_rfh());
simulator->CommitSameDocument();
EXPECT_EQ(process()->bad_msg_count(), bad_msg_count + 1);
}
// Permissions Policy: Test that the permissions policy is reset when navigating
// pages within a site.
TEST_F(NavigatorTest, PermissionsPolicySameSiteNavigation) {
const GURL kUrl1("http://www.chromium.org/");
const GURL kUrl2("http://www.chromium.org/Home");
contents()->NavigateAndCommit(kUrl1);
// Check the permissions policy before navigation.
const network::PermissionsPolicy* original_permissions_policy =
main_test_rfh()->GetPermissionsPolicy();
ASSERT_TRUE(original_permissions_policy);
// Navigate to the new URL.
contents()->NavigateAndCommit(kUrl2);
// Check the permissions policy after navigation.
const network::PermissionsPolicy* final_permissions_policy =
main_test_rfh()->GetPermissionsPolicy();
ASSERT_TRUE(final_permissions_policy);
ASSERT_NE(original_permissions_policy, final_permissions_policy);
}
// Permissions Policy: Test that the permissions policy is not reset when
// navigating within a page.
TEST_F(NavigatorTest, PermissionsPolicyFragmentNavigation) {
const GURL kUrl1("http://www.chromium.org/");
const GURL kUrl2("http://www.chromium.org/#Home");
contents()->NavigateAndCommit(kUrl1);
// Check the permissions policy before navigation.
const network::PermissionsPolicy* original_permissions_policy =
main_test_rfh()->GetPermissionsPolicy();
ASSERT_TRUE(original_permissions_policy);
// Navigate to the new URL.
contents()->NavigateAndCommit(kUrl2);
// Check the permissions policy after navigation.
const network::PermissionsPolicy* final_permissions_policy =
main_test_rfh()->GetPermissionsPolicy();
ASSERT_EQ(original_permissions_policy, final_permissions_policy);
}
// Permissions Policy: Test that the permissions policy is set correctly when
// inserting a new child frame.
TEST_F(NavigatorTest, PermissionsPolicyNewChild) {
const GURL kUrl1("http://www.chromium.org/");
const GURL kUrl2("http://www.chromium.org/Home");
contents()->NavigateAndCommit(kUrl1);
// Simulate the navigation triggered by inserting a child frame into a page.
TestRenderFrameHost* subframe_rfh =
contents()->GetPrimaryMainFrame()->AppendChild("child");
NavigationSimulator::NavigateAndCommitFromDocument(kUrl2, subframe_rfh);
const network::PermissionsPolicy* subframe_permissions_policy =
subframe_rfh->GetPermissionsPolicy();
ASSERT_TRUE(subframe_permissions_policy);
ASSERT_FALSE(subframe_permissions_policy->GetOriginForTest().opaque());
}
TEST_F(NavigatorTest, TwoNavigationsRacingCommit) {
const GURL kUrl1("http://www.chromium.org/");
const GURL kUrl2("http://www.chromium.org/Home");
EXPECT_EQ(0u, contents()->GetPrimaryMainFrame()->navigation_requests_.size());
// Have the first navigation reach ReadyToCommit.
auto first_navigation =
NavigationSimulator::CreateBrowserInitiated(kUrl1, contents());
first_navigation->ReadyToCommit();
EXPECT_EQ(1u, contents()->GetPrimaryMainFrame()->navigation_requests_.size());
// A second navigation starts.
auto second_navigation =
NavigationSimulator::CreateBrowserInitiated(kUrl1, contents());
second_navigation->Start();
EXPECT_EQ(1u, contents()->GetPrimaryMainFrame()->navigation_requests_.size());
// The first navigation commits.
first_navigation->Commit();
EXPECT_EQ(0u, contents()->GetPrimaryMainFrame()->navigation_requests_.size());
// The second navigation commits.
second_navigation->Commit();
EXPECT_EQ(0u, contents()->GetPrimaryMainFrame()->navigation_requests_.size());
}
} // namespace content