blob: e6194f9bff03f6419a0e60afbce5616ce85bd440 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/frame_host/render_frame_host_manager.h"
#include <stdint.h>
#include <string>
#include <tuple>
#include <unordered_set>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/frame_host/navigation_controller_impl.h"
#include "content/browser/frame_host/navigation_entry_impl.h"
#include "content/browser/frame_host/navigation_request.h"
#include "content/browser/frame_host/navigator.h"
#include "content/browser/frame_host/render_frame_proxy_host.h"
#include "content/browser/site_instance_impl.h"
#include "content/browser/webui/web_ui_controller_factory_registry.h"
#include "content/common/frame_messages.h"
#include "content/common/frame_owner_properties.h"
#include "content/common/input_messages.h"
#include "content/common/view_messages.h"
#include "content/common/widget_messages.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_iterator.h"
#include "content/public/browser/render_widget_host_observer.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_ui_controller.h"
#include "content/public/common/bindings_policy.h"
#include "content/public/common/content_features.h"
#include "content/public/common/javascript_dialog_type.h"
#include "content/public/common/previews_state.h"
#include "content/public/common/url_constants.h"
#include "content/public/common/url_utils.h"
#include "content/public/test/browser_side_navigation_test_utils.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_utils.h"
#include "content/test/mock_widget_input_handler.h"
#include "content/test/navigation_simulator_impl.h"
#include "content/test/test_content_browser_client.h"
#include "content/test/test_content_client.h"
#include "content/test/test_render_frame_host.h"
#include "content/test/test_render_view_host.h"
#include "content/test/test_render_widget_host.h"
#include "content/test/test_web_contents.h"
#include "net/base/load_flags.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/frame/frame_policy.h"
#include "third_party/blink/public/platform/web_insecure_request_policy.h"
#include "ui/base/page_transition_types.h"
#if defined(OS_ANDROID)
#include "content/public/browser/android/compositor.h"
#endif
namespace content {
namespace {
// Helper to check that the provided RenderProcessHost received exactly one
// page focus message with the provided focus and routing ID values.
void VerifyPageFocusMessage(MockRenderProcessHost* rph,
bool expected_focus,
int expected_routing_id) {
const IPC::Message* message =
rph->sink().GetUniqueMessageMatching(InputMsg_SetFocus::ID);
EXPECT_TRUE(message);
EXPECT_EQ(expected_routing_id, message->routing_id());
InputMsg_SetFocus::Param params;
EXPECT_TRUE(InputMsg_SetFocus::Read(message, &params));
EXPECT_EQ(expected_focus, std::get<0>(params));
}
// VerifyPageFocusMessage from the mojo input handler.
void VerifyPageFocusMessage(TestRenderWidgetHost* twh, bool expected_focus) {
MockWidgetInputHandler::MessageVector events =
twh->GetMockWidgetInputHandler()->GetAndResetDispatchedMessages();
EXPECT_EQ(1u, events.size());
MockWidgetInputHandler::DispatchedFocusMessage* focus_message =
events.at(0)->ToFocus();
EXPECT_TRUE(focus_message);
EXPECT_EQ(expected_focus, focus_message->focused());
}
// Helper function for strict mixed content checking tests.
void CheckInsecureRequestPolicyIPC(
TestRenderFrameHost* rfh,
blink::WebInsecureRequestPolicy expected_param,
int expected_routing_id) {
const IPC::Message* message =
rfh->GetProcess()->sink().GetUniqueMessageMatching(
FrameMsg_EnforceInsecureRequestPolicy::ID);
ASSERT_TRUE(message);
EXPECT_EQ(expected_routing_id, message->routing_id());
FrameMsg_EnforceInsecureRequestPolicy::Param params;
EXPECT_TRUE(FrameMsg_EnforceInsecureRequestPolicy::Read(message, &params));
EXPECT_EQ(expected_param, std::get<0>(params));
}
// Helper function to find a message with the specified type and routing ID in
// an IPC sink.
bool FindMessageForRoutingId(const IPC::TestSink& sink,
uint32_t type,
int routing_id) {
for (size_t i = 0; i < sink.message_count(); i++) {
const IPC::Message* msg = sink.GetMessageAt(i);
if (msg->type() == type && msg->routing_id() == routing_id)
return true;
}
return false;
}
class RenderFrameHostManagerTestWebUIControllerFactory
: public WebUIControllerFactory {
public:
RenderFrameHostManagerTestWebUIControllerFactory()
: should_create_webui_(false), type_(1) {
CHECK_NE(reinterpret_cast<WebUI::TypeID>(type_), WebUI::kNoWebUI);
}
~RenderFrameHostManagerTestWebUIControllerFactory() override {}
void set_should_create_webui(bool should_create_webui) {
should_create_webui_ = should_create_webui;
}
// This method simulates the expectation that different WebUI instance types
// would be created. The |type| value will be returned by GetWebUIType casted
// to WebUI::TypeID.
// As WebUI::TypeID is a typedef to void pointer, factory implementations
// return values that they know to be unique to their respective cases. So
// values set here should be safe if kept very low (just above zero).
void set_webui_type(uintptr_t type) {
CHECK_NE(reinterpret_cast<WebUI::TypeID>(type), WebUI::kNoWebUI);
type_ = type;
}
// WebUIFactory implementation.
std::unique_ptr<WebUIController> CreateWebUIControllerForURL(
WebUI* web_ui,
const GURL& url) override {
// If WebUI creation is enabled for the test and this is a WebUI URL,
// returns a new instance.
if (should_create_webui_ && HasWebUIScheme(url))
return std::make_unique<WebUIController>(web_ui);
return nullptr;
}
WebUI::TypeID GetWebUIType(BrowserContext* browser_context,
const GURL& url) override {
// If WebUI creation is enabled for the test and this is a WebUI URL,
// returns a mock WebUI type.
if (should_create_webui_ && HasWebUIScheme(url)) {
return reinterpret_cast<WebUI::TypeID>(type_);
}
return WebUI::kNoWebUI;
}
bool UseWebUIForURL(BrowserContext* browser_context,
const GURL& url) override {
return HasWebUIScheme(url);
}
bool UseWebUIBindingsForURL(BrowserContext* browser_context,
const GURL& url) override {
return HasWebUIScheme(url);
}
private:
bool should_create_webui_;
uintptr_t type_;
DISALLOW_COPY_AND_ASSIGN(RenderFrameHostManagerTestWebUIControllerFactory);
};
class BeforeUnloadFiredWebContentsDelegate : public WebContentsDelegate {
public:
BeforeUnloadFiredWebContentsDelegate() {}
~BeforeUnloadFiredWebContentsDelegate() override {}
void BeforeUnloadFired(WebContents* web_contents,
bool proceed,
bool* proceed_to_fire_unload) override {
*proceed_to_fire_unload = proceed;
}
private:
DISALLOW_COPY_AND_ASSIGN(BeforeUnloadFiredWebContentsDelegate);
};
class CloseWebContentsDelegate : public WebContentsDelegate {
public:
CloseWebContentsDelegate() : close_called_(false) {}
~CloseWebContentsDelegate() override {}
void CloseContents(WebContents* web_contents) override {
close_called_ = true;
}
bool is_closed() { return close_called_; }
private:
bool close_called_;
DISALLOW_COPY_AND_ASSIGN(CloseWebContentsDelegate);
};
// This observer keeps track of the last deleted RenderViewHost to avoid
// accessing it and causing use-after-free condition.
class RenderViewHostDeletedObserver : public WebContentsObserver {
public:
explicit RenderViewHostDeletedObserver(RenderViewHost* rvh)
: WebContentsObserver(WebContents::FromRenderViewHost(rvh)),
process_id_(rvh->GetProcess()->GetID()),
routing_id_(rvh->GetRoutingID()),
deleted_(false) {}
void RenderViewDeleted(RenderViewHost* render_view_host) override {
if (render_view_host->GetProcess()->GetID() == process_id_ &&
render_view_host->GetRoutingID() == routing_id_) {
deleted_ = true;
}
}
bool deleted() {
return deleted_;
}
private:
int process_id_;
int routing_id_;
bool deleted_;
DISALLOW_COPY_AND_ASSIGN(RenderViewHostDeletedObserver);
};
// This observer keeps track of the last created RenderFrameHost to allow tests
// to ensure that no RenderFrameHost objects are created when not expected.
class RenderFrameHostCreatedObserver : public WebContentsObserver {
public:
explicit RenderFrameHostCreatedObserver(WebContents* web_contents)
: WebContentsObserver(web_contents), created_(false) {}
void RenderFrameCreated(RenderFrameHost* render_frame_host) override {
created_ = true;
}
bool created() {
return created_;
}
private:
bool created_;
DISALLOW_COPY_AND_ASSIGN(RenderFrameHostCreatedObserver);
};
// This WebContents observer keep track of its RVH change.
class RenderViewHostChangedObserver : public WebContentsObserver {
public:
explicit RenderViewHostChangedObserver(WebContents* web_contents)
: WebContentsObserver(web_contents), host_changed_(false) {}
// WebContentsObserver.
void RenderViewHostChanged(RenderViewHost* old_host,
RenderViewHost* new_host) override {
host_changed_ = true;
}
bool DidHostChange() {
bool host_changed = host_changed_;
Reset();
return host_changed;
}
void Reset() { host_changed_ = false; }
private:
bool host_changed_;
DISALLOW_COPY_AND_ASSIGN(RenderViewHostChangedObserver);
};
// This observer is used to check whether IPC messages are being filtered for
// swapped out RenderFrameHost objects. It observes the plugin crash and favicon
// update events, which the FilterMessagesWhileSwappedOut test simulates being
// sent. The test is successful if the event is not observed.
// See http://crbug.com/351815
class PluginFaviconMessageObserver : public WebContentsObserver {
public:
explicit PluginFaviconMessageObserver(WebContents* web_contents)
: WebContentsObserver(web_contents),
plugin_crashed_(false),
favicon_received_(false) {}
void PluginCrashed(const base::FilePath& plugin_path,
base::ProcessId plugin_pid) override {
plugin_crashed_ = true;
}
void DidUpdateFaviconURL(const std::vector<FaviconURL>& candidates) override {
favicon_received_ = true;
}
bool plugin_crashed() {
return plugin_crashed_;
}
bool favicon_received() {
return favicon_received_;
}
private:
bool plugin_crashed_;
bool favicon_received_;
DISALLOW_COPY_AND_ASSIGN(PluginFaviconMessageObserver);
};
} // namespace
class RenderFrameHostManagerTest : public RenderViewHostImplTestHarness {
public:
void SetUp() override {
RenderViewHostImplTestHarness::SetUp();
WebUIControllerFactory::RegisterFactory(&factory_);
if (AreDefaultSiteInstancesEnabled()) {
// Isolate |isolated_cross_site_url()| so we can't get a default
// SiteInstance for it.
ChildProcessSecurityPolicyImpl::GetInstance()->AddIsolatedOrigins(
{url::Origin::Create(isolated_cross_site_url())},
ChildProcessSecurityPolicy::IsolatedOriginSource::TEST,
browser_context());
// Reset the WebContents so the isolated origin will be honored by
// all BrowsingInstances used in the test.
SetContents(CreateTestWebContents());
}
#if defined(OS_ANDROID)
Compositor::Initialize();
#endif
}
void TearDown() override {
RenderViewHostImplTestHarness::TearDown();
WebUIControllerFactory::UnregisterFactoryForTesting(&factory_);
}
void set_should_create_webui(bool should_create_webui) {
factory_.set_should_create_webui(should_create_webui);
}
void set_webui_type(int type) { factory_.set_webui_type(type); }
GURL isolated_cross_site_url() const {
return GURL("http://isolated-cross-site.com");
}
// Creates a test RenderViewHost that's swapped out.
void CreateSwappedOutRenderViewHost() {
const GURL kChromeURL(GetWebUIURL("foo"));
const GURL kDestUrl("http://www.google.com/");
// Navigate our first tab to a chrome url and then to the destination.
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kChromeURL);
TestRenderFrameHost* ntp_rfh = contents()->GetMainFrame();
// Navigate to a cross-site URL.
auto navigation =
NavigationSimulator::CreateBrowserInitiated(kDestUrl, contents());
navigation->ReadyToCommit();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
// Manually increase the number of active frames in the
// SiteInstance that ntp_rfh belongs to, to prevent it from being
// destroyed when it gets swapped out.
ntp_rfh->GetSiteInstance()->IncrementActiveFrameCount();
TestRenderFrameHost* dest_rfh = contents()->GetPendingMainFrame();
CHECK(dest_rfh);
EXPECT_NE(ntp_rfh, dest_rfh);
// Commit. This will swap out ntp_rfh.
navigation->Commit();
}
// Returns the RenderFrameHost that should be used in the navigation to
// |entry|.
RenderFrameHostImpl* NavigateToEntry(RenderFrameHostManager* manager,
NavigationEntryImpl* entry) {
// Tests currently only navigate using main frame FrameNavigationEntries.
FrameNavigationEntry* frame_entry = entry->root_node()->frame_entry.get();
FrameTreeNode* frame_tree_node =
manager->current_frame_host()->frame_tree_node();
NavigationControllerImpl* controller =
static_cast<NavigationControllerImpl*>(manager->current_frame_host()
->frame_tree_node()
->navigator()
->GetController());
mojom::NavigationType navigate_type =
entry->restore_type() == RestoreType::NONE
? mojom::NavigationType::DIFFERENT_DOCUMENT
: mojom::NavigationType::RESTORE;
scoped_refptr<network::ResourceRequestBody> request_body;
std::string post_content_type;
if (frame_entry->method() == "POST") {
request_body = frame_entry->GetPostData(&post_content_type);
// Might have a LF at end.
post_content_type =
base::TrimWhitespaceASCII(post_content_type, base::TRIM_ALL)
.as_string();
}
auto& referrer = frame_entry->referrer();
mojom::CommonNavigationParamsPtr common_params =
entry->ConstructCommonNavigationParams(
*frame_entry, request_body, frame_entry->url(),
blink::mojom::Referrer::New(referrer.url, referrer.policy),
navigate_type, PREVIEWS_UNSPECIFIED, base::TimeTicks::Now(),
base::TimeTicks::Now());
mojom::CommitNavigationParamsPtr commit_params =
entry->ConstructCommitNavigationParams(
*frame_entry, common_params->url, frame_entry->committed_origin(),
common_params->method,
entry->GetSubframeUniqueNames(frame_tree_node),
controller->GetPendingEntryIndex() ==
-1 /* intended_as_new_entry */,
controller->GetIndexOfEntry(entry),
controller->GetLastCommittedEntryIndex(),
controller->GetEntryCount());
commit_params->post_content_type = post_content_type;
std::unique_ptr<NavigationRequest> navigation_request =
NavigationRequest::CreateBrowserInitiated(
frame_tree_node, std::move(common_params), std::move(commit_params),
!entry->is_renderer_initiated(), entry->extra_headers(),
frame_entry, entry, request_body, nullptr /* navigation_ui_data */);
// Simulates request creation that triggers the 1st internal call to
// GetFrameHostForNavigation.
manager->DidCreateNavigationRequest(navigation_request.get());
// And also simulates the 2nd and final call to GetFrameHostForNavigation
// that determines the final frame that will commit the navigation.
TestRenderFrameHost* frame_host = static_cast<TestRenderFrameHost*>(
manager->GetFrameHostForNavigation(*navigation_request));
CHECK(frame_host);
return frame_host;
}
// Returns the speculative RenderFrameHost.
RenderFrameHostImpl* GetPendingFrameHost(
RenderFrameHostManager* manager) {
return manager->speculative_render_frame_host_.get();
}
// Exposes RenderFrameHostManager::CollectOpenerFrameTrees for testing.
void CollectOpenerFrameTrees(
FrameTreeNode* node,
std::vector<FrameTree*>* opener_frame_trees,
std::unordered_set<FrameTreeNode*>* nodes_with_back_links) {
node->render_manager()->CollectOpenerFrameTrees(opener_frame_trees,
nodes_with_back_links);
}
private:
RenderFrameHostManagerTestWebUIControllerFactory factory_;
};
// Tests that when you navigate from a chrome:// url to another page, and
// then do that same thing in another tab, that the two resulting pages have
// different SiteInstances, BrowsingInstances, and RenderProcessHosts. This is
// a regression test for bug 9364.
TEST_F(RenderFrameHostManagerTest, ChromeSchemeProcesses) {
set_should_create_webui(true);
const GURL kChromeUrl(GetWebUIURL("foo"));
const GURL kDestUrl("http://www.google.com/");
// Navigate our first tab to the chrome url and then to the destination,
// ensuring we grant bindings to the chrome URL.
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kChromeUrl);
EXPECT_TRUE(main_rfh()->GetEnabledBindings() &
BINDINGS_POLICY_WEB_UI);
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kDestUrl);
EXPECT_FALSE(contents()->GetPendingMainFrame());
// Make a second tab.
std::unique_ptr<TestWebContents> contents2(
TestWebContents::Create(browser_context(), nullptr));
// Load the two URLs in the second tab. Note that the first navigation creates
// a RFH that's not pending (since there is no cross-site transition), so
// we use the committed one.
auto navigation1 =
NavigationSimulator::CreateBrowserInitiated(kChromeUrl, contents2.get());
navigation1->Start();
EXPECT_FALSE(contents2->CrossProcessNavigationPending());
navigation1->Commit();
// The second one is the opposite, creating a cross-site transition.
auto navigation2 =
NavigationSimulator::CreateBrowserInitiated(kDestUrl, contents2.get());
navigation2->Start();
EXPECT_TRUE(contents2->CrossProcessNavigationPending());
TestRenderFrameHost* dest_rfh2 = contents2->GetPendingMainFrame();
ASSERT_TRUE(dest_rfh2);
navigation2->Commit();
// The two RFH's should be different in every way.
EXPECT_NE(contents()->GetMainFrame()->GetProcess(), dest_rfh2->GetProcess());
EXPECT_NE(contents()->GetMainFrame()->GetSiteInstance(),
dest_rfh2->GetSiteInstance());
EXPECT_FALSE(dest_rfh2->GetSiteInstance()->IsRelatedSiteInstance(
contents()->GetMainFrame()->GetSiteInstance()));
// Navigate both to a chrome://... URL, and verify that they have a separate
// RenderProcessHost and a separate SiteInstance.
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kChromeUrl);
EXPECT_FALSE(contents()->GetPendingMainFrame());
NavigationSimulator::NavigateAndCommitFromBrowser(contents2.get(),
kChromeUrl);
EXPECT_NE(contents()->GetMainFrame()->GetSiteInstance(),
contents2->GetMainFrame()->GetSiteInstance());
EXPECT_NE(contents()->GetMainFrame()->GetSiteInstance()->GetProcess(),
contents2->GetMainFrame()->GetSiteInstance()->GetProcess());
}
// Ensure that the browser ignores most IPC messages that arrive from a
// RenderViewHost that has been swapped out. We do not want to take
// action on requests from a non-active renderer. The main exception is
// for synchronous messages, which cannot be ignored without leaving the
// renderer in a stuck state. See http://crbug.com/93427.
TEST_F(RenderFrameHostManagerTest, FilterMessagesWhileSwappedOut) {
const GURL kChromeURL(GetWebUIURL("foo"));
const GURL kDestUrl("http://www.google.com/");
std::vector<FaviconURL> icons;
// Navigate our first tab to a chrome url and then to the destination.
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kChromeURL);
TestRenderFrameHost* ntp_rfh = contents()->GetMainFrame();
// Send an update favicon message and make sure it works.
{
PluginFaviconMessageObserver observer(contents());
EXPECT_TRUE(ntp_rfh->OnMessageReceived(
FrameHostMsg_UpdateFaviconURL(ntp_rfh->GetRoutingID(), icons)));
EXPECT_TRUE(observer.favicon_received());
}
// Create one more frame in the same SiteInstance where ntp_rfh
// exists so that it doesn't get deleted on navigation to another
// site.
ntp_rfh->GetSiteInstance()->IncrementActiveFrameCount();
// Navigate to a cross-site URL (don't swap out to keep |ntp_rfh| alive).
auto navigation =
NavigationSimulatorImpl::CreateBrowserInitiated(kDestUrl, contents());
navigation->set_drop_swap_out_ack(true);
navigation->Commit();
TestRenderFrameHost* dest_rfh = contents()->GetMainFrame();
ASSERT_TRUE(dest_rfh);
EXPECT_NE(ntp_rfh, dest_rfh);
// The new RVH should be able to update its favicon.
{
PluginFaviconMessageObserver observer(contents());
EXPECT_TRUE(dest_rfh->OnMessageReceived(
FrameHostMsg_UpdateFaviconURL(dest_rfh->GetRoutingID(), icons)));
EXPECT_TRUE(observer.favicon_received());
}
// The old renderer, being slow, now updates the favicon. It should be
// filtered out and not take effect.
{
PluginFaviconMessageObserver observer(contents());
EXPECT_TRUE(ntp_rfh->OnMessageReceived(
FrameHostMsg_UpdateFaviconURL(ntp_rfh->GetRoutingID(), icons)));
EXPECT_FALSE(observer.favicon_received());
}
}
// Test that the FrameHostMsg_UpdateFaviconURL IPC message is ignored if the
// renderer is in the STATE_PENDING_SWAP_OUT_STATE. The favicon code assumes
// that it only gets FrameHostMsg_UpdateFaviconURL messages for the most
// recently committed navigation for each WebContentsImpl.
TEST_F(RenderFrameHostManagerTest, UpdateFaviconURLWhilePendingSwapOut) {
const GURL kChromeURL(GetWebUIURL("foo"));
const GURL kDestUrl("http://www.google.com/");
std::vector<FaviconURL> icons;
// Navigate our first tab to a chrome url and then to the destination.
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kChromeURL);
TestRenderFrameHost* ntp_rfh = contents()->GetMainFrame();
// Send an update favicon message and make sure it works.
{
PluginFaviconMessageObserver observer(contents());
EXPECT_TRUE(ntp_rfh->OnMessageReceived(
FrameHostMsg_UpdateFaviconURL(ntp_rfh->GetRoutingID(), icons)));
EXPECT_TRUE(observer.favicon_received());
}
// Create one more frame in the same SiteInstance where |ntp_rfh| exists so
// that it doesn't get deleted on navigation to another site.
ntp_rfh->GetSiteInstance()->IncrementActiveFrameCount();
// Navigate to a cross-site URL and commit the new page.
auto navigation =
NavigationSimulatorImpl::CreateBrowserInitiated(kDestUrl, contents());
navigation->set_drop_swap_out_ack(true);
navigation->Commit();
TestRenderFrameHost* dest_rfh = contents()->GetMainFrame();
EXPECT_FALSE(ntp_rfh->is_active());
EXPECT_TRUE(dest_rfh->is_active());
// The new RVH should be able to update its favicons.
{
PluginFaviconMessageObserver observer(contents());
EXPECT_TRUE(dest_rfh->OnMessageReceived(
FrameHostMsg_UpdateFaviconURL(dest_rfh->GetRoutingID(), icons)));
EXPECT_TRUE(observer.favicon_received());
}
// The old renderer, being slow, now updates its favicons. The message should
// be ignored.
{
PluginFaviconMessageObserver observer(contents());
EXPECT_TRUE(ntp_rfh->OnMessageReceived(
FrameHostMsg_UpdateFaviconURL(ntp_rfh->GetRoutingID(), icons)));
EXPECT_FALSE(observer.favicon_received());
}
}
// Test if RenderViewHost::GetRenderWidgetHosts() only returns active
// widgets.
TEST_F(RenderFrameHostManagerTest, GetRenderWidgetHostsReturnsActiveViews) {
CreateSwappedOutRenderViewHost();
std::unique_ptr<RenderWidgetHostIterator> widgets(
RenderWidgetHost::GetRenderWidgetHosts());
// We know that there is the only one active widget. Another view is
// now swapped out, so the swapped out view is not included in the
// list.
RenderWidgetHost* widget = widgets->GetNextHost();
EXPECT_FALSE(widgets->GetNextHost());
RenderViewHost* rvh = RenderViewHost::From(widget);
EXPECT_TRUE(static_cast<RenderViewHostImpl*>(rvh)->is_active());
}
// Test if RenderViewHost::GetRenderWidgetHosts() returns a subset of
// RenderViewHostImpl::GetAllRenderWidgetHosts().
// RenderViewHost::GetRenderWidgetHosts() returns only active widgets, but
// RenderViewHostImpl::GetAllRenderWidgetHosts() returns everything
// including swapped out ones.
TEST_F(RenderFrameHostManagerTest,
GetRenderWidgetHostsWithinGetAllRenderWidgetHosts) {
CreateSwappedOutRenderViewHost();
std::unique_ptr<RenderWidgetHostIterator> widgets(
RenderWidgetHost::GetRenderWidgetHosts());
while (RenderWidgetHost* w = widgets->GetNextHost()) {
bool found = false;
std::unique_ptr<RenderWidgetHostIterator> all_widgets(
RenderWidgetHostImpl::GetAllRenderWidgetHosts());
while (RenderWidgetHost* widget = all_widgets->GetNextHost()) {
if (w == widget) {
found = true;
break;
}
}
EXPECT_TRUE(found);
}
}
// Test if SiteInstanceImpl::active_frame_count() is correctly updated
// as frames in a SiteInstance get swapped out and in.
TEST_F(RenderFrameHostManagerTest, ActiveFrameCountWhileSwappingInAndOut) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2("http://www.chromium.org/");
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = main_test_rfh();
SiteInstanceImpl* instance1 = rfh1->GetSiteInstance();
EXPECT_EQ(instance1->active_frame_count(), 1U);
// Create 2 new tabs and simulate them being the opener chain for the main
// tab. They should be in the same SiteInstance.
std::unique_ptr<TestWebContents> opener1(
TestWebContents::Create(browser_context(), instance1));
contents()->SetOpener(opener1.get());
std::unique_ptr<TestWebContents> opener2(
TestWebContents::Create(browser_context(), instance1));
opener1->SetOpener(opener2.get());
EXPECT_EQ(instance1->active_frame_count(), 3U);
// Navigate to a cross-site URL (different SiteInstance but same
// BrowsingInstance).
contents()->NavigateAndCommit(kUrl2);
TestRenderFrameHost* rfh2 = main_test_rfh();
SiteInstanceImpl* instance2 = rfh2->GetSiteInstance();
if (AreDefaultSiteInstancesEnabled()) {
EXPECT_TRUE(instance1->IsDefaultSiteInstance());
EXPECT_EQ(instance1->active_frame_count(), 3U);
EXPECT_EQ(instance1, instance2);
} else {
// rvh2 is on chromium.org which is different from google.com on
// which other tabs are.
EXPECT_EQ(instance2->active_frame_count(), 1U);
// There are two active views on google.com now.
EXPECT_EQ(instance1->active_frame_count(), 2U);
}
// Navigate to the original origin (google.com).
contents()->NavigateAndCommit(kUrl1);
EXPECT_EQ(instance1->active_frame_count(), 3U);
}
// This deletes a WebContents when the given RVH is deleted. This is
// only for testing whether deleting an RVH does not cause any UaF in
// other parts of the system. For now, this class is only used for the
// next test cases to detect the bug mentioned at
// http://crbug.com/259859.
class RenderViewHostDestroyer : public WebContentsObserver {
public:
RenderViewHostDestroyer(RenderViewHost* render_view_host,
std::unique_ptr<WebContents> web_contents)
: WebContentsObserver(WebContents::FromRenderViewHost(render_view_host)),
render_view_host_(render_view_host),
web_contents_(std::move(web_contents)) {}
void RenderViewDeleted(RenderViewHost* render_view_host) override {
if (render_view_host == render_view_host_)
web_contents_.reset();
}
private:
RenderViewHost* render_view_host_;
std::unique_ptr<WebContents> web_contents_;
DISALLOW_COPY_AND_ASSIGN(RenderViewHostDestroyer);
};
// Test if ShutdownRenderViewHostsInSiteInstance() does not touch any
// RenderWidget that has been freed while deleting a RenderViewHost in
// a previous iteration. This is a regression test for
// http://crbug.com/259859.
TEST_F(RenderFrameHostManagerTest,
DetectUseAfterFreeInShutdownRenderViewHostsInSiteInstance) {
const GURL kChromeURL(GetWebUIURL("newtab"));
const GURL kUrl1("http://www.google.com");
const GURL kUrl2("http://www.chromium.org");
// Navigate our first tab to a chrome url and then to the destination.
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kChromeURL);
TestRenderFrameHost* ntp_rfh = contents()->GetMainFrame();
// Create one more tab and navigate to kUrl1. web_contents is not
// wrapped as scoped_ptr since it intentionally deleted by destroyer
// below as part of this test.
std::unique_ptr<TestWebContents> web_contents =
TestWebContents::Create(browser_context(), ntp_rfh->GetSiteInstance());
web_contents->NavigateAndCommit(kUrl1);
RenderViewHostDestroyer destroyer(ntp_rfh->GetRenderViewHost(),
std::move(web_contents));
// This causes the first tab to navigate to kUrl2, which destroys
// the ntp_rfh in ShutdownRenderViewHostsInSiteInstance(). When
// ntp_rfh is destroyed, it also destroys the RVHs in web_contents
// too. This can test whether
// SiteInstanceImpl::ShutdownRenderViewHostsInSiteInstance() can
// touch any object freed in this way or not while iterating through
// all widgets.
contents()->NavigateAndCommit(kUrl2);
}
// When there is an error with the specified page, renderer exits view-source
// mode. See WebFrameImpl::DidFail(). We check by this test that
// EnableViewSourceMode message is sent on every navigation regardless
// RenderView is being newly created or reused.
TEST_F(RenderFrameHostManagerTest, AlwaysSendEnableViewSourceMode) {
const GURL kChromeUrl(GetWebUIURL("foo"));
const GURL kUrl("http://foo/");
const GURL kViewSourceUrl("view-source:http://foo/");
// We have to navigate to some page at first since without this, the first
// navigation will reuse the SiteInstance created by Init(), and the second
// one will create a new SiteInstance. Because current_instance and
// new_instance will be different, a new RenderViewHost will be created for
// the second navigation. We have to avoid this in order to exercise the
// target code path.
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kChromeUrl);
// Navigate. Note that "view source" URLs are implemented by putting the RFH
// into a view-source mode and then navigating to the inner URL, so that's why
// the bare URL is what's committed and returned by the last committed entry's
// GetURL() call.
auto navigation = NavigationSimulatorImpl::CreateBrowserInitiated(
kViewSourceUrl, contents());
navigation->Start();
NavigationRequest* request =
main_test_rfh()->frame_tree_node()->navigation_request();
CHECK(request);
ASSERT_TRUE(contents()->GetPendingMainFrame())
<< "Expected new pending RenderFrameHost to be created.";
RenderFrameHost* last_rfh = contents()->GetPendingMainFrame();
navigation->Commit();
EXPECT_EQ(1, controller().GetLastCommittedEntryIndex());
NavigationEntry* last_committed = controller().GetLastCommittedEntry();
ASSERT_NE(nullptr, last_committed);
EXPECT_EQ(kUrl, last_committed->GetURL());
EXPECT_EQ(kViewSourceUrl, last_committed->GetVirtualURL());
EXPECT_FALSE(controller().GetPendingEntry());
// Because we're using TestWebContents and TestRenderViewHost in this
// unittest, no one calls WebContentsImpl::RenderViewCreated(). So, we see no
// EnableViewSourceMode message, here.
// Clear queued messages before load.
process()->sink().ClearMessages();
// Navigate, again.
navigation = NavigationSimulatorImpl::CreateBrowserInitiated(kViewSourceUrl,
contents());
navigation->set_did_create_new_entry(false);
navigation->Start();
request = main_test_rfh()->frame_tree_node()->navigation_request();
CHECK(request);
// The same RenderViewHost should be reused.
navigation->ReadyToCommit();
EXPECT_FALSE(contents()->GetPendingMainFrame());
EXPECT_EQ(last_rfh, contents()->GetMainFrame());
navigation->Commit();
EXPECT_EQ(1, controller().GetLastCommittedEntryIndex());
EXPECT_FALSE(controller().GetPendingEntry());
// New message should be sent out to make sure to enter view-source mode.
EXPECT_TRUE(process()->sink().GetUniqueMessageMatching(
FrameMsg_EnableViewSourceMode::ID));
}
// Tests the Init function by checking the initial RenderViewHost.
TEST_F(RenderFrameHostManagerTest, Init) {
// Using TestBrowserContext.
scoped_refptr<SiteInstanceImpl> instance =
SiteInstanceImpl::Create(browser_context());
EXPECT_FALSE(instance->HasSite());
std::unique_ptr<TestWebContents> web_contents(
TestWebContents::Create(browser_context(), instance));
RenderFrameHostManager* manager = web_contents->GetRenderManagerForTesting();
RenderViewHostImpl* rvh = manager->current_host();
RenderFrameHostImpl* rfh = manager->current_frame_host();
ASSERT_TRUE(rvh);
ASSERT_TRUE(rfh);
EXPECT_EQ(rvh, rfh->render_view_host());
EXPECT_EQ(instance, rvh->GetSiteInstance());
EXPECT_EQ(web_contents.get(), rvh->GetDelegate());
EXPECT_EQ(web_contents.get(), rfh->delegate());
EXPECT_TRUE(manager->GetRenderWidgetHostView());
}
// Tests the Navigate function. We navigate three sites consecutively and check
// how the pending/committed RenderViewHost are modified.
TEST_F(RenderFrameHostManagerTest, Navigate) {
std::unique_ptr<TestWebContents> web_contents(TestWebContents::Create(
browser_context(), SiteInstance::Create(browser_context())));
RenderViewHostChangedObserver change_observer(web_contents.get());
RenderFrameHostManager* manager = web_contents->GetRenderManagerForTesting();
RenderFrameHostImpl* host = nullptr;
// 1) The first navigation. --------------------------
const GURL kUrl1("http://www.google.com/");
NavigationEntryImpl entry1(
nullptr /* instance */, kUrl1, Referrer(), base::nullopt,
base::string16() /* title */, ui::PAGE_TRANSITION_TYPED,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
host = NavigateToEntry(manager, &entry1);
// The RenderFrameHost created in Init will be reused.
EXPECT_TRUE(host == manager->current_frame_host());
EXPECT_FALSE(GetPendingFrameHost(manager));
// Commit.
manager->DidNavigateFrame(host, true /* was_caused_by_user_gesture */,
false /* is_same_document_navigation */);
// Commit to SiteInstance should be delayed until RenderFrame commit.
EXPECT_TRUE(host == manager->current_frame_host());
ASSERT_TRUE(host);
EXPECT_FALSE(host->GetSiteInstance()->HasSite());
host->GetSiteInstance()->SetSite(kUrl1);
manager->GetRenderWidgetHostView()->SetBackgroundColor(SK_ColorRED);
// 2) Navigate to next site. -------------------------
const GURL kUrl2("http://www.google.com/foo");
const url::Origin kInitiatorOrigin =
url::Origin::Create(GURL("https://initiator.example.com"));
NavigationEntryImpl entry2(
nullptr /* instance */, kUrl2,
Referrer(kUrl1, network::mojom::ReferrerPolicy::kDefault),
kInitiatorOrigin, base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
true /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
host = NavigateToEntry(manager, &entry2);
// The RenderFrameHost created in Init will be reused.
EXPECT_TRUE(host == manager->current_frame_host());
EXPECT_FALSE(GetPendingFrameHost(manager));
// Commit.
manager->DidNavigateFrame(host, true /* was_caused_by_user_gesture */,
false /* is_same_document_navigation */);
EXPECT_TRUE(host == manager->current_frame_host());
ASSERT_TRUE(host);
EXPECT_TRUE(host->GetSiteInstance()->HasSite());
ASSERT_TRUE(manager->GetRenderWidgetHostView()->GetBackgroundColor());
EXPECT_EQ(SK_ColorRED,
*manager->GetRenderWidgetHostView()->GetBackgroundColor());
// 3) Cross-site navigate to next site. --------------
const GURL kUrl3("http://webkit.org/");
NavigationEntryImpl entry3(
nullptr /* instance */, kUrl3,
Referrer(kUrl2, network::mojom::ReferrerPolicy::kDefault), base::nullopt,
base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
host = NavigateToEntry(manager, &entry3);
// A new RenderFrameHost should be created.
EXPECT_TRUE(GetPendingFrameHost(manager));
ASSERT_EQ(host, GetPendingFrameHost(manager));
change_observer.Reset();
// Commit.
manager->DidNavigateFrame(GetPendingFrameHost(manager),
true /* was_caused_by_user_gesture */,
false /* is_same_document_navigation */);
EXPECT_TRUE(host == manager->current_frame_host());
ASSERT_TRUE(host);
EXPECT_TRUE(host->GetSiteInstance()->HasSite());
// Check the pending RenderFrameHost has been committed.
EXPECT_FALSE(GetPendingFrameHost(manager));
// We should observe RVH changed event.
EXPECT_TRUE(change_observer.DidHostChange());
ASSERT_TRUE(manager->GetRenderWidgetHostView()->GetBackgroundColor());
EXPECT_EQ(SK_ColorRED,
*manager->GetRenderWidgetHostView()->GetBackgroundColor());
}
// Tests WebUI creation.
TEST_F(RenderFrameHostManagerTest, WebUI) {
set_should_create_webui(true);
scoped_refptr<SiteInstance> instance =
SiteInstance::Create(browser_context());
std::unique_ptr<TestWebContents> web_contents(
TestWebContents::Create(browser_context(), instance));
RenderFrameHostManager* manager = web_contents->GetRenderManagerForTesting();
RenderFrameHostImpl* initial_rfh = manager->current_frame_host();
EXPECT_FALSE(manager->current_host()->IsRenderViewLive());
EXPECT_FALSE(manager->current_frame_host()->web_ui());
EXPECT_TRUE(initial_rfh);
const GURL kUrl(GetWebUIURL("foo"));
NavigationEntryImpl entry(
nullptr /* instance */, kUrl, Referrer(), base::nullopt,
base::string16() /* title */, ui::PAGE_TRANSITION_TYPED,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
RenderFrameHostImpl* host = NavigateToEntry(manager, &entry);
// We commit the pending RenderFrameHost immediately because the previous
// RenderFrameHost was not live. We test a case where it is live in
// WebUIInNewTab.
EXPECT_TRUE(host);
EXPECT_NE(initial_rfh, host);
EXPECT_EQ(host, manager->current_frame_host());
EXPECT_FALSE(GetPendingFrameHost(manager));
// It's important that the SiteInstance get set on the Web UI page as soon
// as the navigation starts, rather than lazily after it commits, so we don't
// try to re-use the SiteInstance/process for non Web UI things that may
// get loaded in between.
EXPECT_TRUE(host->GetSiteInstance()->HasSite());
EXPECT_EQ(kUrl, host->GetSiteInstance()->GetSiteURL());
// There will be a navigating WebUI because GetFrameHostForNavigation was
// already called twice and the committed WebUI should be set to be reused.
EXPECT_TRUE(manager->GetNavigatingWebUI());
EXPECT_EQ(host->web_ui(), manager->GetNavigatingWebUI());
EXPECT_EQ(host->web_ui(), host->pending_web_ui());
EXPECT_TRUE(manager->current_frame_host()->web_ui());
// Commit.
manager->DidNavigateFrame(host, true /* was_caused_by_user_gesture */,
false /* is_same_document_navigation */);
EXPECT_TRUE(host->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
}
// Tests that we can open a WebUI link in a new tab from a WebUI page and still
// grant the correct bindings. http://crbug.com/189101.
TEST_F(RenderFrameHostManagerTest, WebUIInNewTab) {
set_should_create_webui(true);
scoped_refptr<SiteInstance> blank_instance =
SiteInstance::Create(browser_context());
blank_instance->GetProcess()->Init();
// Create a blank tab.
std::unique_ptr<TestWebContents> web_contents1(
TestWebContents::Create(browser_context(), blank_instance));
RenderFrameHostManager* manager1 =
web_contents1->GetRenderManagerForTesting();
// Test the case that new RVH is considered live.
manager1->current_host()->CreateRenderView(-1, MSG_ROUTING_NONE,
base::UnguessableToken::Create(),
FrameReplicationState(), false);
EXPECT_TRUE(manager1->current_host()->IsRenderViewLive());
EXPECT_TRUE(manager1->current_frame_host()->IsRenderFrameLive());
// Navigate to a WebUI page.
const GURL kUrl1(GetWebUIURL("foo"));
NavigationEntryImpl entry1(
nullptr /* instance */, kUrl1, Referrer(), base::nullopt,
base::string16() /* title */, ui::PAGE_TRANSITION_TYPED,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
RenderFrameHostImpl* host1 = NavigateToEntry(manager1, &entry1);
// We should have a pending navigation to the WebUI RenderViewHost.
// It should already have bindings.
EXPECT_EQ(host1, GetPendingFrameHost(manager1));
EXPECT_NE(host1, manager1->current_frame_host());
EXPECT_TRUE(host1->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
// Commit and ensure we still have bindings.
manager1->DidNavigateFrame(host1, true /* was_caused_by_user_gesture */,
false /* is_same_document_navigation */);
SiteInstance* webui_instance = host1->GetSiteInstance();
EXPECT_EQ(host1, manager1->current_frame_host());
EXPECT_TRUE(host1->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
// Now simulate clicking a link that opens in a new tab.
std::unique_ptr<TestWebContents> web_contents2(
TestWebContents::Create(browser_context(), webui_instance));
RenderFrameHostManager* manager2 =
web_contents2->GetRenderManagerForTesting();
// Make sure the new RVH is considered live. This is usually done in
// RenderWidgetHost::Init when opening a new tab from a link.
manager2->current_host()->CreateRenderView(-1, MSG_ROUTING_NONE,
base::UnguessableToken::Create(),
FrameReplicationState(), false);
EXPECT_TRUE(manager2->current_host()->IsRenderViewLive());
const GURL kUrl2(GetWebUIURL("foo/bar"));
const url::Origin kInitiatorOrigin =
url::Origin::Create(GURL("https://initiator.example.com"));
NavigationEntryImpl entry2(
nullptr /* instance */, kUrl2, Referrer(), kInitiatorOrigin,
base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
true /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
RenderFrameHostImpl* host2 = NavigateToEntry(manager2, &entry2);
// No cross-process transition happens because we are already in the right
// SiteInstance. We should grant bindings immediately.
EXPECT_EQ(host2, manager2->current_frame_host());
EXPECT_TRUE(manager2->GetNavigatingWebUI());
EXPECT_FALSE(host2->web_ui());
EXPECT_TRUE(host2->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
manager2->DidNavigateFrame(host2, true /* was_caused_by_user_gesture */,
false /* is_same_document_navigation */);
}
// Tests that a WebUI is correctly reused between chrome:// pages.
TEST_F(RenderFrameHostManagerTest, WebUIWasReused) {
set_should_create_webui(true);
// Navigate to a WebUI page.
const GURL kUrl1(GetWebUIURL("foo"));
contents()->NavigateAndCommit(kUrl1);
WebUIImpl* web_ui = main_test_rfh()->web_ui();
EXPECT_TRUE(web_ui);
// Navigate to another WebUI page which should be same-site and keep the
// current WebUI.
const GURL kUrl2(GetWebUIURL("foo/bar"));
contents()->NavigateAndCommit(kUrl2);
EXPECT_EQ(web_ui, main_test_rfh()->web_ui());
}
// Tests that a WebUI is correctly cleaned up when navigating from a chrome://
// page to a non-chrome:// page.
TEST_F(RenderFrameHostManagerTest, WebUIWasCleared) {
set_should_create_webui(true);
// Navigate to a WebUI page.
const GURL kUrl1(GetWebUIURL("foo"));
contents()->NavigateAndCommit(kUrl1);
EXPECT_TRUE(main_test_rfh()->web_ui());
// Navigate to a non-WebUI page.
const GURL kUrl2("http://www.google.com");
contents()->NavigateAndCommit(kUrl2);
EXPECT_FALSE(main_test_rfh()->web_ui());
}
// Ensure that we can go back and forward even if a SwapOut ACK isn't received.
// See http://crbug.com/93427.
TEST_F(RenderFrameHostManagerTest, NavigateAfterMissingSwapOutACK) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2 = isolated_cross_site_url();
// Navigate to two pages.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = main_test_rfh();
// Keep active_frame_count nonzero so that no swapped out frames in
// this SiteInstance get forcefully deleted.
rfh1->GetSiteInstance()->IncrementActiveFrameCount();
contents()->NavigateAndCommit(kUrl2);
TestRenderFrameHost* rfh2 = main_test_rfh();
rfh2->GetSiteInstance()->IncrementActiveFrameCount();
// Now go back, but suppose the SwapOut_ACK isn't received. This shouldn't
// happen, but we have seen it when going back quickly across many entries
// (http://crbug.com/93427).
auto back_navigation1 =
NavigationSimulatorImpl::CreateHistoryNavigation(-1, contents());
back_navigation1->ReadyToCommit();
EXPECT_FALSE(rfh2->is_waiting_for_beforeunload_ack());
// The back navigation commits.
back_navigation1->set_drop_swap_out_ack(true);
back_navigation1->Commit();
EXPECT_TRUE(rfh2->IsWaitingForUnloadACK());
EXPECT_FALSE(rfh2->is_active());
// We should be able to navigate forward.
NavigationSimulator::GoForward(contents());
EXPECT_TRUE(main_test_rfh()->is_active());
}
// Test that we create swapped out RFHs for the opener chain when navigating an
// opened tab cross-process. This allows us to support certain cross-process
// JavaScript calls (http://crbug.com/99202).
TEST_F(RenderFrameHostManagerTest, CreateSwappedOutOpenerRFHs) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2 = isolated_cross_site_url();
const GURL kChromeUrl(GetWebUIURL("foo"));
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();
TestRenderFrameHost* rfh1 = main_test_rfh();
scoped_refptr<SiteInstanceImpl> site_instance1 = rfh1->GetSiteInstance();
RenderFrameDeletedObserver rfh1_deleted_observer(rfh1);
TestRenderViewHost* rvh1 = test_rvh();
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
site_instance1->IsDefaultSiteInstance());
// Create 2 new tabs and simulate them being the opener chain for the main
// tab. They should be in the same SiteInstance.
std::unique_ptr<TestWebContents> opener1(
TestWebContents::Create(browser_context(), site_instance1.get()));
RenderFrameHostManager* opener1_manager =
opener1->GetRenderManagerForTesting();
contents()->SetOpener(opener1.get());
std::unique_ptr<TestWebContents> opener2(
TestWebContents::Create(browser_context(), site_instance1.get()));
RenderFrameHostManager* opener2_manager =
opener2->GetRenderManagerForTesting();
opener1->SetOpener(opener2.get());
// Navigate to a cross-site URL (different SiteInstance but same
// BrowsingInstance).
contents()->NavigateAndCommit(kUrl2);
TestRenderFrameHost* rfh2 = main_test_rfh();
TestRenderViewHost* rvh2 = test_rvh();
EXPECT_NE(site_instance1, rfh2->GetSiteInstance());
EXPECT_TRUE(site_instance1->IsRelatedSiteInstance(rfh2->GetSiteInstance()));
// Ensure rvh1 is placed on swapped out list of the current tab.
EXPECT_TRUE(rfh1_deleted_observer.deleted());
EXPECT_TRUE(manager->GetRenderFrameProxyHost(site_instance1.get()));
EXPECT_EQ(rvh1,
manager->GetSwappedOutRenderViewHost(rvh1->GetSiteInstance()));
// Ensure a proxy and swapped out RVH are created in the first opener tab.
EXPECT_TRUE(
opener1_manager->GetRenderFrameProxyHost(rfh2->GetSiteInstance()));
TestRenderViewHost* opener1_rvh = static_cast<TestRenderViewHost*>(
opener1_manager->GetSwappedOutRenderViewHost(rvh2->GetSiteInstance()));
EXPECT_FALSE(opener1_rvh->is_active());
// Ensure a proxy and swapped out RVH are created in the second opener tab.
EXPECT_TRUE(
opener2_manager->GetRenderFrameProxyHost(rfh2->GetSiteInstance()));
TestRenderViewHost* opener2_rvh = static_cast<TestRenderViewHost*>(
opener2_manager->GetSwappedOutRenderViewHost(rvh2->GetSiteInstance()));
EXPECT_FALSE(opener2_rvh->is_active());
// Navigate to a cross-BrowsingInstance URL.
contents()->NavigateAndCommit(kChromeUrl);
TestRenderFrameHost* rfh3 = main_test_rfh();
EXPECT_NE(site_instance1, rfh3->GetSiteInstance());
EXPECT_FALSE(site_instance1->IsRelatedSiteInstance(rfh3->GetSiteInstance()));
// No scripting is allowed across BrowsingInstances, so we should not create
// swapped out RVHs for the opener chain in this case.
EXPECT_FALSE(opener1_manager->GetRenderFrameProxyHost(
rfh3->GetSiteInstance()));
EXPECT_FALSE(opener1_manager->GetSwappedOutRenderViewHost(
rfh3->GetSiteInstance()));
EXPECT_FALSE(opener2_manager->GetRenderFrameProxyHost(
rfh3->GetSiteInstance()));
EXPECT_FALSE(opener2_manager->GetSwappedOutRenderViewHost(
rfh3->GetSiteInstance()));
}
// Test that a page can disown the opener of the WebContents.
TEST_F(RenderFrameHostManagerTest, DisownOpener) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2 = isolated_cross_site_url();
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = main_test_rfh();
scoped_refptr<SiteInstanceImpl> site_instance1 = rfh1->GetSiteInstance();
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
site_instance1->IsDefaultSiteInstance());
// Create a new tab and simulate having it be the opener for the main tab.
std::unique_ptr<TestWebContents> opener1(
TestWebContents::Create(browser_context(), rfh1->GetSiteInstance()));
contents()->SetOpener(opener1.get());
EXPECT_TRUE(contents()->HasOpener());
// Navigate to a cross-site URL (different SiteInstance but same
// BrowsingInstance).
contents()->NavigateAndCommit(kUrl2);
TestRenderFrameHost* rfh2 = main_test_rfh();
EXPECT_NE(site_instance1, rfh2->GetSiteInstance());
// Disown the opener from rfh2.
rfh2->DidChangeOpener(MSG_ROUTING_NONE);
// Ensure the opener is cleared.
EXPECT_FALSE(contents()->HasOpener());
}
// Test that a page can disown a same-site opener of the WebContents.
TEST_F(RenderFrameHostManagerTest, DisownSameSiteOpener) {
const GURL kUrl1("http://www.google.com/");
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = main_test_rfh();
// Create a new tab and simulate having it be the opener for the main tab.
std::unique_ptr<TestWebContents> opener1(
TestWebContents::Create(browser_context(), rfh1->GetSiteInstance()));
contents()->SetOpener(opener1.get());
EXPECT_TRUE(contents()->HasOpener());
// Disown the opener from rfh1.
rfh1->DidChangeOpener(MSG_ROUTING_NONE);
// Ensure the opener is cleared even if it is in the same process.
EXPECT_FALSE(contents()->HasOpener());
}
// Test that a page can disown the opener just as a cross-process navigation is
// in progress.
TEST_F(RenderFrameHostManagerTest, DisownOpenerDuringNavigation) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2 = isolated_cross_site_url();
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
scoped_refptr<SiteInstanceImpl> site_instance1 =
main_test_rfh()->GetSiteInstance();
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
site_instance1->IsDefaultSiteInstance());
// Create a new tab and simulate having it be the opener for the main tab.
std::unique_ptr<TestWebContents> opener1(
TestWebContents::Create(browser_context(), site_instance1.get()));
contents()->SetOpener(opener1.get());
EXPECT_TRUE(contents()->HasOpener());
// Navigate to a cross-site URL (different SiteInstance but same
// BrowsingInstance).
contents()->NavigateAndCommit(kUrl2);
TestRenderFrameHost* rfh2 = main_test_rfh();
EXPECT_NE(site_instance1, rfh2->GetSiteInstance());
// Start a back navigation.
contents()->GetController().GoBack();
contents()->GetMainFrame()->PrepareForCommit();
// Disown the opener from rfh2.
rfh2->DidChangeOpener(MSG_ROUTING_NONE);
// Ensure the opener is cleared.
EXPECT_FALSE(contents()->HasOpener());
// The back navigation commits.
NavigationEntry* entry1 = contents()->GetController().GetPendingEntry();
contents()->GetPendingMainFrame()->SendNavigateWithTransition(
entry1->GetUniqueID(), false, entry1->GetURL(),
entry1->GetTransitionType());
// Ensure the opener is still cleared.
EXPECT_FALSE(contents()->HasOpener());
}
// Test that a page can disown the opener just after a cross-process navigation
// commits.
TEST_F(RenderFrameHostManagerTest, DisownOpenerAfterNavigation) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2 = isolated_cross_site_url();
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
scoped_refptr<SiteInstanceImpl> site_instance1 =
main_test_rfh()->GetSiteInstance();
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
site_instance1->IsDefaultSiteInstance());
// Create a new tab and simulate having it be the opener for the main tab.
std::unique_ptr<TestWebContents> opener1(
TestWebContents::Create(browser_context(), site_instance1.get()));
contents()->SetOpener(opener1.get());
EXPECT_TRUE(contents()->HasOpener());
// Navigate to a cross-site URL (different SiteInstance but same
// BrowsingInstance).
contents()->NavigateAndCommit(kUrl2);
TestRenderFrameHost* rfh2 = main_test_rfh();
EXPECT_NE(site_instance1, rfh2->GetSiteInstance());
// Commit a back navigation before the DidChangeOpener message arrives.
contents()->GetController().GoBack();
contents()->GetMainFrame()->PrepareForCommit();
NavigationEntry* entry1 = contents()->GetController().GetPendingEntry();
contents()->GetPendingMainFrame()->SendNavigateWithTransition(
entry1->GetUniqueID(), false, entry1->GetURL(),
entry1->GetTransitionType());
// Disown the opener from rfh2.
rfh2->DidChangeOpener(MSG_ROUTING_NONE);
EXPECT_FALSE(contents()->HasOpener());
}
// Test that we clean up swapped out RenderViewHosts when a process hosting
// those associated RenderViews crashes. http://crbug.com/258993
TEST_F(RenderFrameHostManagerTest, CleanUpSwappedOutRVHOnProcessCrash) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2 = isolated_cross_site_url();
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = contents()->GetMainFrame();
// Create a new tab as an opener for the main tab.
std::unique_ptr<TestWebContents> opener1(
TestWebContents::Create(browser_context(), rfh1->GetSiteInstance()));
RenderFrameHostManager* opener1_manager =
opener1->GetRenderManagerForTesting();
contents()->SetOpener(opener1.get());
// Make sure the new opener RVH is considered live.
opener1_manager->current_host()->CreateRenderView(
-1, MSG_ROUTING_NONE, base::UnguessableToken::Create(),
FrameReplicationState(), false);
EXPECT_TRUE(opener1_manager->current_host()->IsRenderViewLive());
EXPECT_TRUE(opener1_manager->current_frame_host()->IsRenderFrameLive());
// Use a cross-process navigation in the opener to swap out the old RVH.
EXPECT_FALSE(
opener1_manager->GetSwappedOutRenderViewHost(rfh1->GetSiteInstance()));
opener1->NavigateAndCommit(kUrl2);
RenderViewHostImpl* swapped_out_rvh =
opener1_manager->GetSwappedOutRenderViewHost(rfh1->GetSiteInstance());
EXPECT_TRUE(swapped_out_rvh);
EXPECT_TRUE(swapped_out_rvh->is_swapped_out_);
EXPECT_FALSE(swapped_out_rvh->is_active());
// Fake a process crash.
rfh1->GetProcess()->SimulateCrash();
// Ensure that the RenderFrameProxyHost stays around and the RenderFrameProxy
// is deleted.
RenderFrameProxyHost* render_frame_proxy_host =
opener1_manager->GetRenderFrameProxyHost(rfh1->GetSiteInstance());
EXPECT_TRUE(render_frame_proxy_host);
EXPECT_FALSE(render_frame_proxy_host->is_render_frame_proxy_live());
// Expect the swapped out RVH to exist but not be live.
EXPECT_TRUE(
opener1_manager->GetSwappedOutRenderViewHost(rfh1->GetSiteInstance()));
EXPECT_FALSE(
opener1_manager->GetSwappedOutRenderViewHost(rfh1->GetSiteInstance())
->IsRenderViewLive());
// Reload the initial tab. This should recreate the opener's swapped out RVH
// in the original SiteInstance.
contents()->GetController().Reload(ReloadType::NORMAL, true);
contents()->GetMainFrame()->PrepareForCommit();
TestRenderFrameHost* rfh2 = contents()->GetMainFrame();
EXPECT_TRUE(
opener1_manager->GetSwappedOutRenderViewHost(rfh2->GetSiteInstance())
->IsRenderViewLive());
EXPECT_EQ(
opener1_manager->GetRoutingIdForSiteInstance(rfh2->GetSiteInstance()),
rfh2->GetRenderViewHost()->opener_frame_route_id());
}
// Test that RenderViewHosts created for WebUI navigations are properly
// granted WebUI bindings even if an unprivileged swapped out RenderViewHost
// is in the same process (http://crbug.com/79918).
TEST_F(RenderFrameHostManagerTest, EnableWebUIWithSwappedOutOpener) {
set_should_create_webui(true);
const GURL kSettingsUrl(GetWebUIURL("chrome/settings"));
const GURL kPluginUrl(GetWebUIURL("plugins"));
// Navigate to an initial WebUI URL.
contents()->NavigateAndCommit(kSettingsUrl);
// Ensure the RVH has WebUI bindings.
TestRenderViewHost* rvh1 = test_rvh();
EXPECT_TRUE(rvh1->GetMainFrame()->GetEnabledBindings() &
BINDINGS_POLICY_WEB_UI);
// Create a new tab and simulate it being the opener for the main
// tab. It should be in the same SiteInstance.
std::unique_ptr<TestWebContents> opener1(
TestWebContents::Create(browser_context(), rvh1->GetSiteInstance()));
RenderFrameHostManager* opener1_manager =
opener1->GetRenderManagerForTesting();
contents()->SetOpener(opener1.get());
// Navigate to a different WebUI URL (different SiteInstance, same
// BrowsingInstance).
contents()->NavigateAndCommit(kPluginUrl);
TestRenderViewHost* rvh2 = test_rvh();
EXPECT_NE(rvh1->GetSiteInstance(), rvh2->GetSiteInstance());
EXPECT_TRUE(rvh1->GetSiteInstance()->IsRelatedSiteInstance(
rvh2->GetSiteInstance()));
// Ensure a proxy and swapped out RVH are created in the first opener tab.
EXPECT_TRUE(
opener1_manager->GetRenderFrameProxyHost(rvh2->GetSiteInstance()));
TestRenderViewHost* opener1_rvh = static_cast<TestRenderViewHost*>(
opener1_manager->GetSwappedOutRenderViewHost(rvh2->GetSiteInstance()));
EXPECT_FALSE(opener1_rvh->is_active());
// Ensure the new RVH has WebUI bindings.
EXPECT_TRUE(rvh2->GetMainFrame()->GetEnabledBindings() &
BINDINGS_POLICY_WEB_UI);
}
// Test that we reuse the same guest SiteInstance if we navigate across sites.
TEST_F(RenderFrameHostManagerTest, NoSwapOnGuestNavigations) {
GURL guest_url(std::string(kGuestScheme).append("://abc123"));
scoped_refptr<SiteInstance> instance =
SiteInstance::CreateForURL(browser_context(), guest_url);
std::unique_ptr<TestWebContents> web_contents(
TestWebContents::Create(browser_context(), instance));
EXPECT_TRUE(instance->GetSiteURL().SchemeIs(kGuestScheme));
RenderFrameHostManager* manager = web_contents->GetRenderManagerForTesting();
RenderFrameHostImpl* host = nullptr;
// 1) The first navigation. --------------------------
const GURL kUrl1("http://www.google.com/");
NavigationEntryImpl entry1(
nullptr /* instance */, kUrl1, Referrer(), base::nullopt,
base::string16() /* title */, ui::PAGE_TRANSITION_TYPED,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
host = NavigateToEntry(manager, &entry1);
// The RenderFrameHost created in Init will be reused.
EXPECT_TRUE(host == manager->current_frame_host());
EXPECT_FALSE(manager->speculative_frame_host());
EXPECT_EQ(manager->current_frame_host()->GetSiteInstance(), instance);
// Commit.
manager->DidNavigateFrame(host, true /* was_caused_by_user_gesture */,
false /* is_same_document_navigation */);
// Commit to SiteInstance should be delayed until RenderFrame commit.
EXPECT_EQ(host, manager->current_frame_host());
ASSERT_TRUE(host);
EXPECT_TRUE(host->GetSiteInstance()->HasSite());
// 2) Navigate to a different domain. -------------------------
// Guests stay in the same process on navigation.
const GURL kUrl2("http://www.chromium.org");
const url::Origin kInitiatorOrigin =
url::Origin::Create(GURL("https://initiator.example.com"));
NavigationEntryImpl entry2(
nullptr /* instance */, kUrl2,
Referrer(kUrl1, network::mojom::ReferrerPolicy::kDefault),
kInitiatorOrigin, base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
true /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
host = NavigateToEntry(manager, &entry2);
// The RenderFrameHost created in Init will be reused.
EXPECT_EQ(host, manager->current_frame_host());
EXPECT_FALSE(manager->speculative_frame_host());
// Commit.
manager->DidNavigateFrame(host, true /* was_caused_by_user_gesture */,
false /* is_same_document_navigation */);
EXPECT_EQ(host, manager->current_frame_host());
ASSERT_TRUE(host);
EXPECT_EQ(host->GetSiteInstance(), instance);
}
namespace {
class WidgetDestructionObserver : public RenderWidgetHostObserver {
public:
explicit WidgetDestructionObserver(base::OnceClosure closure)
: closure_(std::move(closure)) {}
void RenderWidgetHostDestroyed(RenderWidgetHost* widget_host) override {
std::move(closure_).Run();
}
private:
base::OnceClosure closure_;
DISALLOW_COPY_AND_ASSIGN(WidgetDestructionObserver);
};
} // namespace
// Test that we cancel a pending RVH if we close the tab while it's pending.
// http://crbug.com/294697.
TEST_F(RenderFrameHostManagerTest, NavigateWithEarlyClose) {
scoped_refptr<SiteInstance> instance =
SiteInstance::Create(browser_context());
BeforeUnloadFiredWebContentsDelegate delegate;
std::unique_ptr<TestWebContents> web_contents(
TestWebContents::Create(browser_context(), instance));
RenderViewHostChangedObserver change_observer(web_contents.get());
web_contents->SetDelegate(&delegate);
RenderFrameHostManager* manager = web_contents->GetRenderManagerForTesting();
// 1) The first navigation. --------------------------
const GURL kUrl1("http://www.google.com/");
NavigationEntryImpl entry1(
nullptr /* instance */, kUrl1, Referrer(), base::nullopt,
base::string16() /* title */, ui::PAGE_TRANSITION_TYPED,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
RenderFrameHostImpl* host = NavigateToEntry(manager, &entry1);
// The RenderFrameHost created in Init will be reused.
EXPECT_EQ(host, manager->current_frame_host());
EXPECT_FALSE(GetPendingFrameHost(manager));
// We should observe RVH changed event.
EXPECT_TRUE(change_observer.DidHostChange());
// Commit.
manager->DidNavigateFrame(host, true /* was_caused_by_user_gesture */,
false /* is_same_document_navigation */);
// Commit to SiteInstance should be delayed until RenderFrame commits.
EXPECT_EQ(host, manager->current_frame_host());
EXPECT_FALSE(host->GetSiteInstance()->HasSite());
host->GetSiteInstance()->SetSite(kUrl1);
// 2) Cross-site navigate to next site. -------------------------
const GURL kUrl2("http://www.example.com");
NavigationEntryImpl entry2(
nullptr /* instance */, kUrl2, Referrer(), base::nullopt,
base::string16() /* title */, ui::PAGE_TRANSITION_TYPED,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
RenderFrameHostImpl* host2 = NavigateToEntry(manager, &entry2);
// A new RenderFrameHost should be created.
ASSERT_EQ(host2, GetPendingFrameHost(manager));
EXPECT_NE(host2, host);
EXPECT_EQ(host, manager->current_frame_host());
EXPECT_EQ(host2, GetPendingFrameHost(manager));
// 3) Close the tab. -------------------------
base::RunLoop run_loop;
WidgetDestructionObserver observer(run_loop.QuitClosure());
host2->render_view_host()->GetWidget()->AddObserver(&observer);
manager->OnBeforeUnloadACK(true, base::TimeTicks());
run_loop.Run();
EXPECT_FALSE(GetPendingFrameHost(manager));
EXPECT_EQ(host, manager->current_frame_host());
}
TEST_F(RenderFrameHostManagerTest, CloseWithPendingWhileUnresponsive) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2 = isolated_cross_site_url();
CloseWebContentsDelegate close_delegate;
contents()->SetDelegate(&close_delegate);
// Navigate to the first page.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = contents()->GetMainFrame();
// Start to close the tab, but assume it's unresponsive.
rfh1->render_view_host()->ClosePage();
EXPECT_TRUE(rfh1->render_view_host()->is_waiting_for_close_ack());
// Start a navigation to a new site.
controller().LoadURL(
kUrl2, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
rfh1->PrepareForCommit();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
// Simulate the unresponsiveness timer. The tab should close.
rfh1->render_view_host()->ClosePageTimeout();
EXPECT_TRUE(close_delegate.is_closed());
}
// Tests that the RenderFrameHost is properly deleted when the SwapOutACK is
// received. (SwapOut and the corresponding ACK always occur after commit.)
// Also tests that an early SwapOutACK is properly ignored.
TEST_F(RenderFrameHostManagerTest, DeleteFrameAfterSwapOutACK) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2("http://www.chromium.org/");
// Navigate to the first page.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = contents()->GetMainFrame();
RenderFrameDeletedObserver rfh_deleted_observer(rfh1);
EXPECT_TRUE(rfh1->is_active());
// Navigate to new site, simulating onbeforeunload approval.
auto navigation =
NavigationSimulatorImpl::CreateBrowserInitiated(kUrl2, contents());
navigation->ReadyToCommit();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
EXPECT_TRUE(rfh1->is_active());
TestRenderFrameHost* rfh2 = contents()->GetPendingMainFrame();
// Simulate the swap out ack, unexpectedly early (before commit). It should
// have no effect.
rfh1->SimulateSwapOutACK();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
EXPECT_TRUE(rfh1->is_active());
// The new page commits.
navigation->set_drop_swap_out_ack(true);
navigation->Commit();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(rfh2, contents()->GetMainFrame());
EXPECT_TRUE(contents()->GetPendingMainFrame() == nullptr);
EXPECT_TRUE(rfh2->is_active());
EXPECT_FALSE(rfh1->is_active());
// Simulate the swap out ack.
rfh1->SimulateSwapOutACK();
// rfh1 should have been deleted.
EXPECT_TRUE(rfh_deleted_observer.deleted());
rfh1 = nullptr;
}
// Tests that the RenderFrameHost is properly swapped out when the SwapOut ACK
// is received. (SwapOut and the corresponding ACK always occur after commit.)
TEST_F(RenderFrameHostManagerTest, SwapOutFrameAfterSwapOutACK) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2("http://www.chromium.org/");
// Navigate to the first page.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = contents()->GetMainFrame();
RenderFrameDeletedObserver rfh_deleted_observer(rfh1);
EXPECT_TRUE(rfh1->is_active());
// Increment the number of active frames in SiteInstanceImpl so that rfh1 is
// not deleted on swap out.
rfh1->GetSiteInstance()->IncrementActiveFrameCount();
// Navigate to new site, simulating onbeforeunload approval.
auto navigation =
NavigationSimulatorImpl::CreateBrowserInitiated(kUrl2, contents());
navigation->ReadyToCommit();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
EXPECT_TRUE(rfh1->is_active());
TestRenderFrameHost* rfh2 = contents()->GetPendingMainFrame();
// The new page commits.
navigation->set_drop_swap_out_ack(true);
navigation->Commit();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(rfh2, contents()->GetMainFrame());
EXPECT_TRUE(contents()->GetPendingMainFrame() == nullptr);
EXPECT_FALSE(rfh1->is_active());
EXPECT_TRUE(rfh2->is_active());
// Simulate the swap out ack.
rfh1->OnSwappedOut();
// rfh1 should be deleted.
EXPECT_TRUE(rfh_deleted_observer.deleted());
}
// Test that the RenderViewHost is properly swapped out if a navigation in the
// new renderer commits before sending the SwapOut message to the old renderer.
// This simulates a cross-site navigation to a synchronously committing URL
// (e.g., a data URL) and ensures it works properly.
TEST_F(RenderFrameHostManagerTest,
CommitNewNavigationBeforeSendingSwapOut) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2("http://www.chromium.org/");
// Navigate to the first page.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = contents()->GetMainFrame();
RenderFrameDeletedObserver rfh_deleted_observer(rfh1);
EXPECT_TRUE(rfh1->is_active());
// Increment the number of active frames in SiteInstanceImpl so that rfh1 is
// not deleted on swap out.
scoped_refptr<SiteInstanceImpl> site_instance = rfh1->GetSiteInstance();
site_instance->IncrementActiveFrameCount();
// Navigate to new site, simulating onbeforeunload approval.
auto navigation =
NavigationSimulatorImpl::CreateBrowserInitiated(kUrl2, contents());
navigation->ReadyToCommit();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
EXPECT_TRUE(rfh1->is_active());
TestRenderFrameHost* rfh2 = contents()->GetPendingMainFrame();
// The new page commits.
navigation->set_drop_swap_out_ack(true);
navigation->Commit();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(rfh2, contents()->GetMainFrame());
EXPECT_TRUE(contents()->GetPendingMainFrame() == nullptr);
EXPECT_FALSE(rfh1->is_active());
EXPECT_TRUE(rfh2->is_active());
// Simulate the swap out ack.
rfh1->OnSwappedOut();
// rfh1 should be deleted.
EXPECT_TRUE(rfh_deleted_observer.deleted());
EXPECT_TRUE(contents()->GetFrameTree()->root()->render_manager()
->GetRenderFrameProxyHost(site_instance.get()));
}
// Test that a RenderFrameHost is properly deleted when a cross-site navigation
// is cancelled.
TEST_F(RenderFrameHostManagerTest,
CancelPendingProperlyDeletesOrSwaps) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2 = isolated_cross_site_url();
RenderFrameHostImpl* pending_rfh = nullptr;
base::TimeTicks now = base::TimeTicks::Now();
// Navigate to the first page.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = main_test_rfh();
EXPECT_TRUE(rfh1->is_active());
// Navigate to a new site, starting a cross-site navigation.
controller().LoadURL(
kUrl2, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
{
pending_rfh = contents()->GetPendingMainFrame();
RenderFrameDeletedObserver rfh_deleted_observer(pending_rfh);
// Cancel the navigation by simulating a declined beforeunload dialog.
contents()->GetMainFrame()->OnMessageReceived(
FrameHostMsg_BeforeUnload_ACK(0, false, now, now));
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
// Since the pending RFH is the only one for the new SiteInstance, it should
// be deleted.
EXPECT_TRUE(rfh_deleted_observer.deleted());
}
// Start another cross-site navigation.
controller().LoadURL(
kUrl2, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
{
pending_rfh = contents()->GetPendingMainFrame();
RenderFrameDeletedObserver rfh_deleted_observer(pending_rfh);
// Increment the number of active frames in the new SiteInstance, which will
// cause the pending RFH to be deleted and a RenderFrameProxyHost to be
// created.
scoped_refptr<SiteInstanceImpl> site_instance =
pending_rfh->GetSiteInstance();
site_instance->IncrementActiveFrameCount();
contents()->GetMainFrame()->OnMessageReceived(
FrameHostMsg_BeforeUnload_ACK(0, false, now, now));
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_TRUE(rfh_deleted_observer.deleted());
EXPECT_TRUE(contents()->GetFrameTree()->root()->render_manager()
->GetRenderFrameProxyHost(site_instance.get()));
}
}
class RenderFrameHostManagerTestWithSiteIsolation
: public RenderFrameHostManagerTest {
public:
RenderFrameHostManagerTestWithSiteIsolation() {
IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
}
};
// Test that a pending RenderFrameHost in a non-root frame tree node is properly
// deleted when the node is detached. Motivated by http://crbug.com/441357 and
// http://crbug.com/444955.
TEST_F(RenderFrameHostManagerTestWithSiteIsolation, DetachPendingChild) {
const GURL kUrlA("http://www.google.com/");
const GURL kUrlB("http://webkit.org/");
constexpr auto kOwnerType = blink::FrameOwnerElementType::kIframe;
// Create a page with two child frames.
contents()->NavigateAndCommit(kUrlA);
contents()->GetMainFrame()->OnCreateChildFrame(
contents()->GetMainFrame()->GetProcess()->GetNextRoutingID(),
TestRenderFrameHost::CreateStubInterfaceProviderRequest(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
blink::WebTreeScopeType::kDocument, "frame_name", "uniqueName1", false,
base::UnguessableToken::Create(), blink::FramePolicy(),
FrameOwnerProperties(), kOwnerType);
contents()->GetMainFrame()->OnCreateChildFrame(
contents()->GetMainFrame()->GetProcess()->GetNextRoutingID(),
TestRenderFrameHost::CreateStubInterfaceProviderRequest(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
blink::WebTreeScopeType::kDocument, "frame_name", "uniqueName2", false,
base::UnguessableToken::Create(), blink::FramePolicy(),
FrameOwnerProperties(), kOwnerType);
RenderFrameHostManager* root_manager =
contents()->GetFrameTree()->root()->render_manager();
RenderFrameHostManager* iframe1 =
contents()->GetFrameTree()->root()->child_at(0)->render_manager();
RenderFrameHostManager* iframe2 =
contents()->GetFrameTree()->root()->child_at(1)->render_manager();
// 1) The first navigation.
NavigationEntryImpl entryA(
nullptr /* instance */, kUrlA, Referrer(), base::nullopt,
base::string16() /* title */, ui::PAGE_TRANSITION_TYPED,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
RenderFrameHostImpl* host1 = NavigateToEntry(iframe1, &entryA);
// The RenderFrameHost created in Init will be reused.
EXPECT_TRUE(host1 == iframe1->current_frame_host());
EXPECT_FALSE(GetPendingFrameHost(iframe1));
// Commit.
iframe1->DidNavigateFrame(host1, true /* was_caused_by_user_gesture */,
false /* is_same_document_navigation */);
// Commit to SiteInstance should be delayed until RenderFrame commit.
EXPECT_TRUE(host1 == iframe1->current_frame_host());
ASSERT_TRUE(host1);
EXPECT_TRUE(host1->GetSiteInstance()->HasSite());
// 2) Cross-site navigate both frames to next site.
NavigationEntryImpl entryB(
nullptr /* instance */, kUrlB,
Referrer(kUrlA, network::mojom::ReferrerPolicy::kDefault), base::nullopt,
base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
host1 = NavigateToEntry(iframe1, &entryB);
RenderFrameHostImpl* host2 = NavigateToEntry(iframe2, &entryB);
// A new, pending RenderFrameHost should be created in each FrameTreeNode.
EXPECT_TRUE(GetPendingFrameHost(iframe1));
EXPECT_TRUE(GetPendingFrameHost(iframe2));
EXPECT_EQ(host1, GetPendingFrameHost(iframe1));
EXPECT_EQ(host2, GetPendingFrameHost(iframe2));
EXPECT_TRUE(GetPendingFrameHost(iframe1)->is_active());
EXPECT_TRUE(GetPendingFrameHost(iframe2)->is_active());
EXPECT_NE(GetPendingFrameHost(iframe1), GetPendingFrameHost(iframe2));
EXPECT_EQ(GetPendingFrameHost(iframe1)->GetSiteInstance(),
GetPendingFrameHost(iframe2)->GetSiteInstance());
EXPECT_NE(iframe1->current_frame_host(), GetPendingFrameHost(iframe1));
EXPECT_NE(iframe2->current_frame_host(), GetPendingFrameHost(iframe2));
EXPECT_FALSE(contents()->CrossProcessNavigationPending())
<< "There should be no top-level pending navigation.";
RenderFrameDeletedObserver delete_watcher1(GetPendingFrameHost(iframe1));
RenderFrameDeletedObserver delete_watcher2(GetPendingFrameHost(iframe2));
EXPECT_FALSE(delete_watcher1.deleted());
EXPECT_FALSE(delete_watcher2.deleted());
// Keep the SiteInstance alive for testing.
scoped_refptr<SiteInstanceImpl> site_instance =
GetPendingFrameHost(iframe1)->GetSiteInstance();
EXPECT_TRUE(site_instance->HasSite());
EXPECT_NE(site_instance, contents()->GetSiteInstance());
EXPECT_EQ(2U, site_instance->active_frame_count());
// Proxies should exist.
EXPECT_NE(nullptr,
root_manager->GetRenderFrameProxyHost(site_instance.get()));
EXPECT_NE(nullptr,
iframe1->GetRenderFrameProxyHost(site_instance.get()));
EXPECT_NE(nullptr,
iframe2->GetRenderFrameProxyHost(site_instance.get()));
// Detach the first child FrameTreeNode. This should kill the pending host but
// not yet destroy proxies in |site_instance| since the other child remains.
iframe1->current_frame_host()->OnMessageReceived(
FrameHostMsg_Detach(iframe1->current_frame_host()->GetRoutingID()));
iframe1 = nullptr; // Was just destroyed.
EXPECT_TRUE(delete_watcher1.deleted());
EXPECT_FALSE(delete_watcher2.deleted());
EXPECT_EQ(1U, site_instance->active_frame_count());
// Proxies should still exist.
EXPECT_NE(nullptr,
root_manager->GetRenderFrameProxyHost(site_instance.get()));
EXPECT_NE(nullptr,
iframe2->GetRenderFrameProxyHost(site_instance.get()));
// Detach the second child FrameTreeNode. This should trigger cleanup of
// RenderFrameProxyHosts in |site_instance|.
iframe2->current_frame_host()->OnMessageReceived(
FrameHostMsg_Detach(iframe2->current_frame_host()->GetRoutingID()));
iframe2 = nullptr; // Was just destroyed.
EXPECT_TRUE(delete_watcher1.deleted());
EXPECT_TRUE(delete_watcher2.deleted());
EXPECT_EQ(0U, site_instance->active_frame_count());
EXPECT_EQ(nullptr,
root_manager->GetRenderFrameProxyHost(site_instance.get()))
<< "Proxies should have been cleaned up";
EXPECT_TRUE(site_instance->HasOneRef())
<< "This SiteInstance should be destroyable now.";
}
// Two tabs in the same process crash. The first tab is reloaded, and the second
// tab navigates away without reloading. The second tab's navigation shouldn't
// mess with the first tab's content. Motivated by http://crbug.com/473714.
TEST_F(RenderFrameHostManagerTestWithSiteIsolation,
TwoTabsCrashOneReloadsOneLeaves) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2("http://webkit.org/");
const GURL kUrl3("http://whatwg.org/");
// |contents1| and |contents2| navigate to the same page and then crash.
TestWebContents* contents1 = contents();
std::unique_ptr<TestWebContents> contents2(
TestWebContents::Create(browser_context(), contents1->GetSiteInstance()));
contents1->NavigateAndCommit(kUrl1);
contents2->NavigateAndCommit(kUrl1);
MockRenderProcessHost* rph = contents1->GetMainFrame()->GetProcess();
EXPECT_EQ(rph, contents2->GetMainFrame()->GetProcess());
EXPECT_TRUE(contents1->GetMainFrame()->GetView());
EXPECT_TRUE(contents2->GetMainFrame()->GetView());
rph->SimulateCrash();
EXPECT_FALSE(contents1->GetMainFrame()->IsRenderFrameLive());
EXPECT_FALSE(contents2->GetMainFrame()->IsRenderFrameLive());
EXPECT_EQ(contents1->GetSiteInstance(), contents2->GetSiteInstance());
EXPECT_FALSE(contents1->GetMainFrame()->GetView());
EXPECT_FALSE(contents2->GetMainFrame()->GetView());
// Reload |contents1|.
contents1->NavigateAndCommit(kUrl1);
EXPECT_TRUE(contents1->GetMainFrame()->IsRenderFrameLive());
EXPECT_FALSE(contents2->GetMainFrame()->IsRenderFrameLive());
EXPECT_EQ(contents1->GetSiteInstance(), contents2->GetSiteInstance());
EXPECT_TRUE(contents1->GetMainFrame()->GetView());
EXPECT_FALSE(contents2->GetMainFrame()->GetView());
// |contents1| creates an out of process iframe.
contents1->GetMainFrame()->OnCreateChildFrame(
contents1->GetMainFrame()->GetProcess()->GetNextRoutingID(),
TestRenderFrameHost::CreateStubInterfaceProviderRequest(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
blink::WebTreeScopeType::kDocument, "frame_name", "uniqueName1", false,
base::UnguessableToken::Create(), blink::FramePolicy(),
FrameOwnerProperties(), blink::FrameOwnerElementType::kIframe);
RenderFrameHostManager* iframe =
contents()->GetFrameTree()->root()->child_at(0)->render_manager();
NavigationEntryImpl entry(
nullptr /* instance */, kUrl2,
Referrer(kUrl1, network::mojom::ReferrerPolicy::kDefault), base::nullopt,
base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
RenderFrameHostImpl* cross_site = NavigateToEntry(iframe, &entry);
iframe->DidNavigateFrame(cross_site, true /* was_caused_by_user_gesture */,
false /* is_same_document_navigation */);
// A proxy to the iframe should now exist in the SiteInstance of the main
// frames.
EXPECT_NE(cross_site->GetSiteInstance(), contents1->GetSiteInstance());
EXPECT_NE(nullptr,
iframe->GetRenderFrameProxyHost(contents1->GetSiteInstance()));
EXPECT_NE(nullptr,
iframe->GetRenderFrameProxyHost(contents2->GetSiteInstance()));
// Navigate |contents2| away from the sad tab (and thus away from the
// SiteInstance of |contents1|). This should not destroy the proxies needed by
// |contents1| -- that was http://crbug.com/473714.
EXPECT_FALSE(contents2->GetMainFrame()->IsRenderFrameLive());
contents2->NavigateAndCommit(kUrl3);
EXPECT_TRUE(contents2->GetMainFrame()->IsRenderFrameLive());
EXPECT_NE(nullptr,
iframe->GetRenderFrameProxyHost(contents1->GetSiteInstance()));
EXPECT_EQ(nullptr,
iframe->GetRenderFrameProxyHost(contents2->GetSiteInstance()));
}
// Ensure that we don't grant WebUI bindings to a pending RenderViewHost when
// creating proxies for a non-WebUI subframe navigation. This was possible due
// to the InitRenderView call from CreateRenderFrameProxy.
// See https://crbug.com/536145.
TEST_F(RenderFrameHostManagerTestWithSiteIsolation,
DontGrantPendingWebUIToSubframe) {
set_should_create_webui(true);
// Make sure the initial process is live so that the pending WebUI navigation
// does not commit immediately. Give the page a subframe as well.
const GURL kUrl1("http://foo.com");
RenderFrameHostImpl* main_rfh = contents()->GetMainFrame();
NavigateAndCommit(kUrl1);
EXPECT_TRUE(main_rfh->render_view_host()->IsRenderViewLive());
EXPECT_TRUE(main_rfh->IsRenderFrameLive());
main_rfh->OnCreateChildFrame(
main_rfh->GetProcess()->GetNextRoutingID(),
TestRenderFrameHost::CreateStubInterfaceProviderRequest(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
blink::WebTreeScopeType::kDocument, std::string(), "uniqueName1", false,
base::UnguessableToken::Create(), blink::FramePolicy(),
FrameOwnerProperties(), blink::FrameOwnerElementType::kIframe);
RenderFrameHostManager* subframe_rfhm =
contents()->GetFrameTree()->root()->child_at(0)->render_manager();
// Start a pending WebUI navigation in the main frame and verify that the
// pending RVH has bindings.
const GURL kWebUIUrl(GetWebUIURL("foo"));
NavigationEntryImpl webui_entry(
nullptr /* instance */, kWebUIUrl, Referrer(), base::nullopt,
base::string16() /* title */, ui::PAGE_TRANSITION_TYPED,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
RenderFrameHostManager* main_rfhm = contents()->GetRenderManagerForTesting();
RenderFrameHostImpl* webui_rfh = NavigateToEntry(main_rfhm, &webui_entry);
EXPECT_EQ(webui_rfh, GetPendingFrameHost(main_rfhm));
EXPECT_TRUE(webui_rfh->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
// Before it commits, do a cross-process navigation in a subframe. This
// should not grant WebUI bindings to the subframe's RVH.
const GURL kSubframeUrl("http://bar.com");
NavigationEntryImpl subframe_entry(
nullptr /* instance */, kSubframeUrl, Referrer(), base::nullopt,
base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
RenderFrameHostImpl* bar_rfh =
NavigateToEntry(subframe_rfhm, &subframe_entry);
EXPECT_FALSE(bar_rfh->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
}
// Test that opener proxies are created properly with a cycle on the opener
// chain.
TEST_F(RenderFrameHostManagerTest, CreateOpenerProxiesWithCycleOnOpenerChain) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2 = isolated_cross_site_url();
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = main_test_rfh();
scoped_refptr<SiteInstanceImpl> site_instance1 = rfh1->GetSiteInstance();
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
site_instance1->IsDefaultSiteInstance());
// Create 2 new tabs and construct the opener chain as follows:
//
// tab2 <--- tab1 <---- contents()
// | ^
// +-------+
//
std::unique_ptr<TestWebContents> tab1(
TestWebContents::Create(browser_context(), site_instance1.get()));
RenderFrameHostManager* tab1_manager = tab1->GetRenderManagerForTesting();
std::unique_ptr<TestWebContents> tab2(
TestWebContents::Create(browser_context(), site_instance1.get()));
RenderFrameHostManager* tab2_manager = tab2->GetRenderManagerForTesting();
contents()->SetOpener(tab1.get());
tab1->SetOpener(tab2.get());
tab2->SetOpener(tab1.get());
// Navigate main window to a cross-site URL. This will call
// CreateOpenerProxies() to create proxies for the two opener tabs in the new
// SiteInstance.
contents()->NavigateAndCommit(kUrl2);
TestRenderFrameHost* rfh2 = main_test_rfh();
EXPECT_NE(site_instance1, rfh2->GetSiteInstance());
// Check that each tab now has a proxy in the new SiteInstance.
RenderFrameProxyHost* tab1_proxy =
tab1_manager->GetRenderFrameProxyHost(rfh2->GetSiteInstance());
EXPECT_TRUE(tab1_proxy);
RenderFrameProxyHost* tab2_proxy =
tab2_manager->GetRenderFrameProxyHost(rfh2->GetSiteInstance());
EXPECT_TRUE(tab2_proxy);
// Verify that the proxies' openers point to each other.
int tab1_opener_routing_id =
tab1_manager->GetOpenerRoutingID(rfh2->GetSiteInstance());
int tab2_opener_routing_id =
tab2_manager->GetOpenerRoutingID(rfh2->GetSiteInstance());
EXPECT_EQ(tab2_proxy->GetRoutingID(), tab1_opener_routing_id);
EXPECT_EQ(tab1_proxy->GetRoutingID(), tab2_opener_routing_id);
// Setting tab2_proxy's opener required an extra IPC message to be set, since
// the opener's routing ID wasn't available when tab2_proxy was created.
// Verify that this IPC was sent and that it passed correct routing ID.
const IPC::Message* message =
rfh2->GetProcess()->sink().GetUniqueMessageMatching(
FrameMsg_UpdateOpener::ID);
EXPECT_TRUE(message);
FrameMsg_UpdateOpener::Param params;
EXPECT_TRUE(FrameMsg_UpdateOpener::Read(message, &params));
EXPECT_EQ(tab2_opener_routing_id, std::get<0>(params));
}
// Test that opener proxies are created properly when the opener points
// to itself.
TEST_F(RenderFrameHostManagerTest, CreateOpenerProxiesWhenOpenerPointsToSelf) {
const GURL kUrl1("http://www.google.com/");
const GURL kUrl2 = isolated_cross_site_url();
// Navigate to an initial URL.
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* rfh1 = main_test_rfh();
scoped_refptr<SiteInstanceImpl> site_instance1 = rfh1->GetSiteInstance();
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
site_instance1->IsDefaultSiteInstance());
// Create an opener tab, and simulate that its opener points to itself.
std::unique_ptr<TestWebContents> opener(
TestWebContents::Create(browser_context(), site_instance1.get()));
RenderFrameHostManager* opener_manager = opener->GetRenderManagerForTesting();
contents()->SetOpener(opener.get());
opener->SetOpener(opener.get());
// Navigate main window to a cross-site URL. This will call
// CreateOpenerProxies() to create proxies for the opener tab in the new
// SiteInstance.
contents()->NavigateAndCommit(kUrl2);
TestRenderFrameHost* rfh2 = main_test_rfh();
EXPECT_NE(site_instance1, rfh2->GetSiteInstance());
// Check that the opener now has a proxy in the new SiteInstance.
RenderFrameProxyHost* opener_proxy =
opener_manager->GetRenderFrameProxyHost(rfh2->GetSiteInstance());
EXPECT_TRUE(opener_proxy);
// Verify that the proxy's opener points to itself.
int opener_routing_id =
opener_manager->GetOpenerRoutingID(rfh2->GetSiteInstance());
EXPECT_EQ(opener_proxy->GetRoutingID(), opener_routing_id);
// Setting the opener in opener_proxy required an extra IPC message, since
// the opener's routing ID wasn't available when opener_proxy was created.
// Verify that this IPC was sent and that it passed correct routing ID.
const IPC::Message* message =
rfh2->GetProcess()->sink().GetUniqueMessageMatching(
FrameMsg_UpdateOpener::ID);
EXPECT_TRUE(message);
FrameMsg_UpdateOpener::Param params;
EXPECT_TRUE(FrameMsg_UpdateOpener::Read(message, &params));
EXPECT_EQ(opener_routing_id, std::get<0>(params));
}
// Build the following frame opener graph and see that it can be properly
// traversed when creating opener proxies:
//
// +-> root4 <--+ root3 <---- root2 +--- root1
// | / | ^ / \ | / \ .
// | 42 +-----|------- 22 23 <--+ 12 13
// | +------------+ | | ^
// +-------------------------------+ +-+
//
// The test starts traversing openers from root1 and expects to discover all
// four FrameTrees. Nodes 13 (with cycle to itself) and 42 (with back link to
// root3) should be put on the list of nodes that will need their frame openers
// set separately in a second pass, since their opener routing IDs won't be
// available during the first pass of CreateOpenerProxies.
TEST_F(RenderFrameHostManagerTest, TraverseComplexOpenerChain) {
contents()->NavigateAndCommit(GURL("http://tab1.com"));
FrameTree* tree1 = contents()->GetFrameTree();
FrameTreeNode* root1 = tree1->root();
int process_id = root1->current_frame_host()->GetProcess()->GetID();
constexpr auto kOwnerType = blink::FrameOwnerElementType::kIframe;
tree1->AddFrame(
root1, process_id, 12,
TestRenderFrameHost::CreateStubInterfaceProviderRequest(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
blink::WebTreeScopeType::kDocument, std::string(), "uniqueName0", false,
base::UnguessableToken::Create(), blink::FramePolicy(),
FrameOwnerProperties(), false, kOwnerType);
tree1->AddFrame(
root1, process_id, 13,
TestRenderFrameHost::CreateStubInterfaceProviderRequest(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
blink::WebTreeScopeType::kDocument, std::string(), "uniqueName1", false,
base::UnguessableToken::Create(), blink::FramePolicy(),
FrameOwnerProperties(), false, kOwnerType);
std::unique_ptr<TestWebContents> tab2(
TestWebContents::Create(browser_context(), nullptr));
tab2->NavigateAndCommit(GURL("http://tab2.com"));
FrameTree* tree2 = tab2->GetFrameTree();
FrameTreeNode* root2 = tree2->root();
process_id = root2->current_frame_host()->GetProcess()->GetID();
tree2->AddFrame(
root2, process_id, 22,
TestRenderFrameHost::CreateStubInterfaceProviderRequest(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
blink::WebTreeScopeType::kDocument, std::string(), "uniqueName2", false,
base::UnguessableToken::Create(), blink::FramePolicy(),
FrameOwnerProperties(), false, kOwnerType);
tree2->AddFrame(
root2, process_id, 23,
TestRenderFrameHost::CreateStubInterfaceProviderRequest(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
blink::WebTreeScopeType::kDocument, std::string(), "uniqueName3", false,
base::UnguessableToken::Create(), blink::FramePolicy(),
FrameOwnerProperties(), false, kOwnerType);
std::unique_ptr<TestWebContents> tab3(
TestWebContents::Create(browser_context(), nullptr));
FrameTree* tree3 = tab3->GetFrameTree();
FrameTreeNode* root3 = tree3->root();
std::unique_ptr<TestWebContents> tab4(
TestWebContents::Create(browser_context(), nullptr));
tab4->NavigateAndCommit(GURL("http://tab4.com"));
FrameTree* tree4 = tab4->GetFrameTree();
FrameTreeNode* root4 = tree4->root();
process_id = root4->current_frame_host()->GetProcess()->GetID();
tree4->AddFrame(
root4, process_id, 42,
TestRenderFrameHost::CreateStubInterfaceProviderRequest(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
blink::WebTreeScopeType::kDocument, std::string(), "uniqueName4", false,
base::UnguessableToken::Create(), blink::FramePolicy(),
FrameOwnerProperties(), false, kOwnerType);
root1->child_at(1)->SetOpener(root1->child_at(1));
root1->SetOpener(root2->child_at(1));
root2->SetOpener(root3);
root2->child_at(0)->SetOpener(root4);
root2->child_at(1)->SetOpener(root4);
root4->child_at(0)->SetOpener(root3);
std::vector<FrameTree*> opener_frame_trees;
std::unordered_set<FrameTreeNode*> nodes_with_back_links;
CollectOpenerFrameTrees(root1, &opener_frame_trees, &nodes_with_back_links);
EXPECT_EQ(4U, opener_frame_trees.size());
EXPECT_EQ(tree1, opener_frame_trees[0]);
EXPECT_EQ(tree2, opener_frame_trees[1]);
EXPECT_EQ(tree3, opener_frame_trees[2]);
EXPECT_EQ(tree4, opener_frame_trees[3]);
EXPECT_EQ(2U, nodes_with_back_links.size());
EXPECT_TRUE(nodes_with_back_links.find(root1->child_at(1)) !=
nodes_with_back_links.end());
EXPECT_TRUE(nodes_with_back_links.find(root4->child_at(0)) !=
nodes_with_back_links.end());
}
// Check that when a window is focused/blurred, the message that sets
// page-level focus updates is sent to each process involved in rendering the
// current page.
//
// TODO(alexmos): Move this test to FrameTree unit tests once NavigateToEntry
// is moved to a common place. See https://crbug.com/547275.
TEST_F(RenderFrameHostManagerTest, PageFocusPropagatesToSubframeProcesses) {
// This test only makes sense when cross-site subframes use separate
// processes.
if (!AreAllSitesIsolatedForTesting())
return;
const GURL kUrlA("http://a.com/");
const GURL kUrlB("http://b.com/");
const GURL kUrlC("http://c.com/");
constexpr auto kOwnerType = blink::FrameOwnerElementType::kIframe;
// Set up a page at a.com with three subframes: two for b.com and one for
// c.com.
contents()->NavigateAndCommit(kUrlA);
main_test_rfh()->OnCreateChildFrame(
main_test_rfh()->GetProcess()->GetNextRoutingID(),
TestRenderFrameHost::CreateStubInterfaceProviderRequest(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
blink::WebTreeScopeType::kDocument, "frame1", "uniqueName1", false,
base::UnguessableToken::Create(), blink::FramePolicy(),
FrameOwnerProperties(), kOwnerType);
main_test_rfh()->OnCreateChildFrame(
main_test_rfh()->GetProcess()->GetNextRoutingID(),
TestRenderFrameHost::CreateStubInterfaceProviderRequest(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
blink::WebTreeScopeType::kDocument, "frame2", "uniqueName2", false,
base::UnguessableToken::Create(), blink::FramePolicy(),
FrameOwnerProperties(), kOwnerType);
main_test_rfh()->OnCreateChildFrame(
main_test_rfh()->GetProcess()->GetNextRoutingID(),
TestRenderFrameHost::CreateStubInterfaceProviderRequest(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
blink::WebTreeScopeType::kDocument, "frame3", "uniqueName3", false,
base::UnguessableToken::Create(), blink::FramePolicy(),
FrameOwnerProperties(), kOwnerType);
FrameTreeNode* root = contents()->GetFrameTree()->root();
RenderFrameHostManager* child1 = root->child_at(0)->render_manager();
RenderFrameHostManager* child2 = root->child_at(1)->render_manager();
RenderFrameHostManager* child3 = root->child_at(2)->render_manager();
// Navigate first two subframes to B.
NavigationEntryImpl entryB(
nullptr /* instance */, kUrlB,
Referrer(kUrlA, network::mojom::ReferrerPolicy::kDefault), base::nullopt,
base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
TestRenderFrameHost* host1 =
static_cast<TestRenderFrameHost*>(NavigateToEntry(child1, &entryB));
TestRenderFrameHost* host2 =
static_cast<TestRenderFrameHost*>(NavigateToEntry(child2, &entryB));
child1->DidNavigateFrame(host1, true /* was_caused_by_user_gesture */,
false /* is_same_document_navigation */);
child2->DidNavigateFrame(host2, true /* was_caused_by_user_gesture */,
false /* is_same_document_navigation */);
// Navigate the third subframe to C.
NavigationEntryImpl entryC(
nullptr /* instance */, kUrlC,
Referrer(kUrlA, network::mojom::ReferrerPolicy::kDefault), base::nullopt,
base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
TestRenderFrameHost* host3 =
static_cast<TestRenderFrameHost*>(NavigateToEntry(child3, &entryC));
child3->DidNavigateFrame(host3, true /* was_caused_by_user_gesture */,
false /* is_same_document_navigation */);
// Make sure the first two subframes and the third subframe are placed in
// distinct processes.
EXPECT_NE(host1->GetProcess(), main_test_rfh()->GetProcess());
EXPECT_EQ(host1->GetProcess(), host2->GetProcess());
EXPECT_NE(host3->GetProcess(), main_test_rfh()->GetProcess());
EXPECT_NE(host3->GetProcess(), host1->GetProcess());
// The main frame should have proxies for B and C.
RenderFrameProxyHost* proxyB =
root->render_manager()->GetRenderFrameProxyHost(host1->GetSiteInstance());
EXPECT_TRUE(proxyB);
RenderFrameProxyHost* proxyC =
root->render_manager()->GetRenderFrameProxyHost(host3->GetSiteInstance());
EXPECT_TRUE(proxyC);
base::RunLoop().RunUntilIdle();
// Focus the main page, and verify that the focus message was sent to all
// processes. The message to A should be sent through the main frame's
// RenderViewHost, and the message to B and C should be send through proxies
// that the main frame has for B and C.
main_test_rfh()->GetProcess()->sink().ClearMessages();
host1->GetProcess()->sink().ClearMessages();
host3->GetProcess()->sink().ClearMessages();
main_test_rfh()->GetRenderWidgetHost()->Focus();
base::RunLoop().RunUntilIdle();
VerifyPageFocusMessage(main_test_rfh()->GetRenderWidgetHost(), true);
VerifyPageFocusMessage(host1->GetProcess(), true, proxyB->GetRoutingID());
VerifyPageFocusMessage(host3->GetProcess(), true, proxyC->GetRoutingID());
// Similarly, simulate focus loss on main page, and verify that the focus
// message was sent to all processes.
main_test_rfh()->GetProcess()->sink().ClearMessages();
host1->GetProcess()->sink().ClearMessages();
host3->GetProcess()->sink().ClearMessages();
main_test_rfh()->GetRenderWidgetHost()->Blur();
base::RunLoop().RunUntilIdle();
VerifyPageFocusMessage(main_test_rfh()->GetRenderWidgetHost(), false);
VerifyPageFocusMessage(host1->GetProcess(), false, proxyB->GetRoutingID());
VerifyPageFocusMessage(host3->GetProcess(), false, proxyC->GetRoutingID());
}
// Check that page-level focus state is preserved across subframe navigations.
//
// TODO(alexmos): Move this test to FrameTree unit tests once NavigateToEntry
// is moved to a common place. See https://crbug.com/547275.
TEST_F(RenderFrameHostManagerTest,
PageFocusIsPreservedAcrossSubframeNavigations) {
// This test only makes sense when cross-site subframes use separate
// processes.
if (!AreAllSitesIsolatedForTesting())
return;
const GURL kUrlA("http://a.com/");
const GURL kUrlB("http://b.com/");
const GURL kUrlC("http://c.com/");
constexpr auto kOwnerType = blink::FrameOwnerElementType::kIframe;
// Set up a page at a.com with a b.com subframe.
contents()->NavigateAndCommit(kUrlA);
main_test_rfh()->OnCreateChildFrame(
main_test_rfh()->GetProcess()->GetNextRoutingID(),
TestRenderFrameHost::CreateStubInterfaceProviderRequest(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubDocumentInterfaceBrokerReceiver(),
TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
blink::WebTreeScopeType::kDocument, "frame1", "uniqueName1", false,
base::UnguessableToken::Create(), blink::FramePolicy(),
FrameOwnerProperties(), kOwnerType);
FrameTreeNode* root = contents()->GetFrameTree()->root();
RenderFrameHostManager* child = root->child_at(0)->render_manager();
// Navigate subframe to B.
NavigationEntryImpl entryB(
nullptr /* instance */, kUrlB,
Referrer(kUrlA, network::mojom::ReferrerPolicy::kDefault), base::nullopt,
base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
TestRenderFrameHost* hostB =
static_cast<TestRenderFrameHost*>(NavigateToEntry(child, &entryB));
child->DidNavigateFrame(hostB, true /* was_caused_by_user_gesture */,
false /* is_same_document_navigation */);
// Ensure that the main page is focused.
main_test_rfh()->GetView()->Focus();
EXPECT_TRUE(main_test_rfh()->GetView()->HasFocus());
// Navigate the subframe to C.
NavigationEntryImpl entryC(
nullptr /* instance */, kUrlC,
Referrer(kUrlA, network::mojom::ReferrerPolicy::kDefault), base::nullopt,
base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
TestRenderFrameHost* hostC =
static_cast<TestRenderFrameHost*>(NavigateToEntry(child, &entryC));
child->DidNavigateFrame(hostC, true /* was_caused_by_user_gesture */,
false /* is_same_document_navigation */);
// The main frame should now have a proxy for C.
RenderFrameProxyHost* proxy =
root->render_manager()->GetRenderFrameProxyHost(hostC->GetSiteInstance());
EXPECT_TRUE(proxy);
// Since the B->C navigation happened while the current page was focused,
// page focus should propagate to the new subframe process. Check that
// process C received the proper focus message.
VerifyPageFocusMessage(hostC->GetProcess(), true, proxy->GetRoutingID());
}
// Checks that a restore navigation to a WebUI works.
TEST_F(RenderFrameHostManagerTest, RestoreNavigationToWebUI) {
set_should_create_webui(true);
const GURL kInitUrl(GetWebUIURL("foo"));
scoped_refptr<SiteInstanceImpl> initial_instance =
SiteInstanceImpl::Create(browser_context());
initial_instance->SetSite(kInitUrl);
std::unique_ptr<TestWebContents> web_contents(
TestWebContents::Create(browser_context(), initial_instance));
RenderFrameHostManager* manager = web_contents->GetRenderManagerForTesting();
NavigationControllerImpl& controller = web_contents->GetController();
// Setup a restored entry.
std::vector<std::unique_ptr<NavigationEntry>> entries;
std::unique_ptr<NavigationEntry> new_entry =
NavigationController::CreateNavigationEntry(
kInitUrl, Referrer(), base::nullopt, ui::PAGE_TRANSITION_TYPED, false,
std::string(), browser_context(),
nullptr /* blob_url_loader_factory */);
entries.push_back(std::move(new_entry));
controller.Restore(0, RestoreType::LAST_SESSION_EXITED_CLEANLY, &entries);
ASSERT_EQ(0u, entries.size());
ASSERT_EQ(1, controller.GetEntryCount());
RenderFrameHostImpl* initial_host = manager->current_frame_host();
ASSERT_TRUE(initial_host);
EXPECT_FALSE(initial_host->IsRenderFrameLive());
EXPECT_FALSE(initial_host->web_ui());
// Navigation request to an entry from a previous browsing session.
NavigationEntryImpl entry(
nullptr /* instance */, kInitUrl, Referrer(), base::nullopt,
base::string16() /* title */, ui::PAGE_TRANSITION_RELOAD,
false /* is_renderer_init */, nullptr /* blob_url_loader_factory */);
entry.set_restore_type(RestoreType::LAST_SESSION_EXITED_CLEANLY);
NavigateToEntry(manager, &entry);
// As the initial renderer was not live, the new RenderFrameHost should be
// made immediately active at request time.
EXPECT_FALSE(GetPendingFrameHost(manager));
TestRenderFrameHost* current_host =
static_cast<TestRenderFrameHost*>(manager->current_frame_host());
ASSERT_TRUE(current_host);
EXPECT_EQ(current_host, initial_host);
EXPECT_TRUE(current_host->IsRenderFrameLive());
WebUIImpl* web_ui = manager->GetNavigatingWebUI();
EXPECT_TRUE(web_ui);
EXPECT_EQ(web_ui, current_host->pending_web_ui());
EXPECT_FALSE(current_host->web_ui());
// The RenderFrameHost committed.
manager->DidNavigateFrame(current_host, true /* was_caused_by_user_gesture */,
false /* is_same_document_navigation */);
EXPECT_EQ(current_host, manager->current_frame_host());
EXPECT_EQ(web_ui, current_host->web_ui());
EXPECT_FALSE(current_host->pending_web_ui());
}
// Simulates two simultaneous navigations involving one WebUI where the current
// RenderFrameHost commits.
TEST_F(RenderFrameHostManagerTest, SimultaneousNavigationWithOneWebUI1) {
set_should_create_webui(true);
NavigationSimulator::NavigateAndCommitFromBrowser(contents(),
GetWebUIURL("foo/"));
RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();
RenderFrameHostImpl* host1 = manager->current_frame_host();
EXPECT_TRUE(host1->IsRenderFrameLive());
WebUIImpl* web_ui = host1->web_ui();
EXPECT_TRUE(web_ui);
// Starts a reload of the WebUI page.
contents()->GetController().Reload(ReloadType::NORMAL, true);
auto reload = NavigationSimulator::CreateFromPending(contents());
reload->ReadyToCommit();
// It should be a same-site navigation reusing the same WebUI.
EXPECT_EQ(web_ui, manager->GetNavigatingWebUI());
EXPECT_EQ(web_ui, host1->web_ui());
EXPECT_EQ(web_ui, host1->pending_web_ui());
EXPECT_FALSE(GetPendingFrameHost(manager));
// Navigation request to a non-WebUI page.
const GURL kUrl("http://google.com");
auto navigation =
NavigationSimulator::CreateBrowserInitiated(kUrl, contents());
navigation->ReadyToCommit();
RenderFrameHostImpl* host2 = GetPendingFrameHost(manager);
ASSERT_TRUE(host2);
// The previous navigation should still be ongoing along with the new,
// cross-site one.
// Note: Simultaneous navigations are weird: there are two ongoing
// navigations, a same-site using a WebUI and a cross-site not using one. So
// it's unclear what GetNavigatingWebUI should return in this case. As it
// currently favors the cross-site navigation it returns null.
EXPECT_FALSE(manager->GetNavigatingWebUI());
EXPECT_EQ(web_ui, host1->web_ui());
EXPECT_EQ(web_ui, host1->pending_web_ui());
EXPECT_NE(host2, host1);
EXPECT_FALSE(host2->web_ui());
EXPECT_FALSE(host2->pending_web_ui());
EXPECT_NE(web_ui, host2->web_ui());
// The current RenderFrameHost commits; its WebUI should still be in place.
reload->Commit();
EXPECT_EQ(host1, manager->current_frame_host());
EXPECT_EQ(web_ui, host1->web_ui());
EXPECT_FALSE(host1->pending_web_ui());
EXPECT_FALSE(manager->GetNavigatingWebUI());
// Because the Navigation that committed was browser-initiated, it will not
// have the user gesture bit set to true. This has the side-effect of not
// deleting the speculative RenderFrameHost at commit time.
// TODO(clamy): The speculative RenderFrameHost should be deleted at commit
// time if a browser-initiated navigation commits.
EXPECT_TRUE(GetPendingFrameHost(manager));
}
// Simulates two simultaneous navigations involving one WebUI where the new,
// cross-site RenderFrameHost commits.
TEST_F(RenderFrameHostManagerTest, SimultaneousNavigationWithOneWebUI2) {
set_should_create_webui(true);
NavigationSimulator::NavigateAndCommitFromBrowser(contents(),
GetWebUIURL("foo/"));
RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();
RenderFrameHostImpl* host1 = manager->current_frame_host();
EXPECT_TRUE(host1->IsRenderFrameLive());
WebUIImpl* web_ui = host1->web_ui();
EXPECT_TRUE(web_ui);
// Starts a reload of the WebUI page.
contents()->GetController().Reload(ReloadType::NORMAL, true);